Skip to content

Follow laravel in the way that attributes are first then relation #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/FUNDING.yml

This file was deleted.

21 changes: 20 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,29 @@
All notable changes to this project will be documented in this file.

## [Unreleased]
None yet.

## [10.0] - 2023-08-22

### Added
- MongoDB v6.0 supports and tests
- use Mongosh in tests instead of the old Mongo
- Mysql 8 Hybrid relation tests

### Removed
- MongoDB v4.* support was dropped

### Fixed
- Fixing the priority between attributes and relations dynamic property call to be the same as Laravel, that mean attribute first then relation (previous it was relation then attribute)
- using function names that exist as attribute accessor/caster: previously if you use a function name that has an accessor; say u use foo() and you have getFooAttribute, calling $model->foo will always fail as the function is called before the accessor, this has been fixed.

### Breaking
- EmbedsOne and EmbedsMany now require return type to work (Example in the documentation has been updated)
- You can't have the belongsToMany foreign key to be the same as the relation name (check documentation for more details)

## [3.9.2] - 2022-09-01

### Addded
### Addded
- Add single word name mutators [#2438](https://github.com/jenssegers/laravel-mongodb/pull/2438) by [@RosemaryOrchard](https://github.com/RosemaryOrchard) & [@mrneatly](https://github.com/mrneatly).

### Fixed
Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -839,19 +839,23 @@ class User extends Model
}
}
```
**Warning:** naming the foreign key same as the relation name will prevent the relation for being called on dynamic property, i.e. in the example above if you replaced `group_ids` with `groups` calling `$user->groups` will return the column instead of the relation.

### EmbedsMany Relationship

If you want to embed models, rather than referencing them, you can use the `embedsMany` relation. This relation is similar to the `hasMany` relation but embeds the models inside the parent object.

**REMEMBER**: These relations return Eloquent collections, they don't return query builder objects!

**Breaking changes** starting from v10.0 you need to define the return type of EmbedsOne and EmbedsMany relation for it to work

```php
use Jenssegers\Mongodb\Eloquent\Model;
use Jenssegers\Mongodb\Relations\EmbedsMany;

class User extends Model
{
public function books()
public function books(): EmbedsMany
{
return $this->embedsMany(Book::class);
}
Expand Down Expand Up @@ -920,10 +924,11 @@ Like other relations, embedsMany assumes the local key of the relationship based

```php
use Jenssegers\Mongodb\Eloquent\Model;
use Jenssegers\Mongodb\Relations\EmbedsMany;

class User extends Model
{
public function books()
public function books(): EmbedsMany
{
return $this->embedsMany(Book::class, 'local_key');
}
Expand All @@ -936,12 +941,15 @@ Embedded relations will return a Collection of embedded items instead of a query

The embedsOne relation is similar to the embedsMany relation, but only embeds a single model.

**Breaking changes** starting from v10.0 you need to define the return type of EmbedsOne and EmbedsMany relation for it to work

```php
use Jenssegers\Mongodb\Eloquent\Model;
use Jenssegers\Mongodb\Relations\EmbedsOne;

class Book extends Model
{
public function author()
public function author(): EmbedsOne
{
return $this->embedsOne(Author::class);
}
Expand Down
39 changes: 34 additions & 5 deletions src/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@
use Illuminate\Support\Str;
use function in_array;
use Jenssegers\Mongodb\Query\Builder as QueryBuilder;
use Jenssegers\Mongodb\Relations\EmbedsMany;
use Jenssegers\Mongodb\Relations\EmbedsOne;
use MongoDB\BSON\Binary;
use MongoDB\BSON\ObjectID;
use MongoDB\BSON\UTCDateTime;
use ReflectionException;
use ReflectionMethod;
use ReflectionNamedType;
use function uniqid;

abstract class Model extends BaseModel
{
use HybridRelations, EmbedsRelations;

public static array $embeddedCache = [];

/**
* The collection associated with the model.
*
Expand Down Expand Up @@ -161,16 +168,38 @@ public function getAttribute($key)

// This checks for embedded relation support.
if (
method_exists($this, $key)
&& ! method_exists(self::class, $key)
&& ! $this->hasAttributeGetMutator($key)
$this->hasEmbeddedRelation($key)
) {
return $this->getRelationValue($key);
}

return parent::getAttribute($key);
}

/**
* Determine if an attribute is an embedded relation.
*
* @param string $key
* @return bool
* @throws ReflectionException
*/
public function hasEmbeddedRelation(string $key): bool
{
if (! method_exists($this, $method = Str::camel($key))) {
return false;
}

if (isset(static::$embeddedCache[get_class($this)][$key])) {
return static::$embeddedCache[get_class($this)][$key];
}

$returnType = (new ReflectionMethod($this, $method))->getReturnType();

return $returnType && static::$embeddedCache[get_class($this)][$key] =
$returnType instanceof ReflectionNamedType &&
$returnType->getName() === EmbedsOne::class || $returnType->getName() === EmbedsMany::class;
}

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -401,7 +430,7 @@ public function getForeignKey()
/**
* Set the parent relation.
*
* @param \Illuminate\Database\Eloquent\Relations\Relation $relation
* @param Relation $relation
*/
public function setParentRelation(Relation $relation)
{
Expand All @@ -411,7 +440,7 @@ public function setParentRelation(Relation $relation)
/**
* Get the parent relation.
*
* @return \Illuminate\Database\Eloquent\Relations\Relation
* @return Relation
*/
public function getParentRelation()
{
Expand Down
12 changes: 12 additions & 0 deletions tests/HybridRelationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,27 @@ public function setUp(): void
MysqlUser::executeSchema();
MysqlBook::executeSchema();
MysqlRole::executeSchema();
// MysqlGroup::executeSchema();
}

public function tearDown(): void
{
MysqlUser::truncate();
MysqlBook::truncate();
MysqlRole::truncate();
// MysqlGroup::truncate();
}

// public function testMysqlGroups()
// {
// $user = new MysqlUser;
// $user->name = 'John Doe';
// $user->save();
// $this->assertIsInt($user->id);
//
// $group = $user->groups()->create(['name' => 'test']);
// }

public function testMysqlRelations()
{
$user = new MysqlUser;
Expand Down
7 changes: 7 additions & 0 deletions tests/ModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -801,4 +801,11 @@ public function testEnumCast(): void
$this->assertSame(MemberStatus::Member->value, $check->getRawOriginal('member_status'));
$this->assertSame(MemberStatus::Member, $check->member_status);
}

public function testGetFooAttributeAccessor()
{
$user = new User();

$this->assertSame('normal attribute', $user->foo);
}
}
4 changes: 2 additions & 2 deletions tests/RelationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,8 @@ public function testBelongsToManyCustom(): void
$group = Group::find($group->_id);

// Check for custom relation attributes
$this->assertArrayHasKey('users', $group->getAttributes());
$this->assertArrayHasKey('groups', $user->getAttributes());
$this->assertArrayHasKey('user_ids', $group->getAttributes());
$this->assertArrayHasKey('group_ids', $user->getAttributes());

// Assert they are attached
$this->assertContains($group->_id, $user->groups->pluck('_id')->toArray());
Expand Down
2 changes: 1 addition & 1 deletion tests/models/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ class Group extends Eloquent

public function users(): BelongsToMany
{
return $this->belongsToMany('User', 'users', 'groups', 'users', '_id', '_id', 'users');
return $this->belongsToMany('User');
}
}
48 changes: 48 additions & 0 deletions tests/models/MysqlGroup.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\MySqlBuilder;
use Illuminate\Support\Facades\Schema;
use Jenssegers\Mongodb\Eloquent\HybridRelations;

class MysqlGroup extends Eloquent
{
use HybridRelations;

protected $connection = 'mysql';
protected $table = 'groups';
protected static $unguarded = true;
protected $primaryKey = 'name';

public function users(): BelongsToMany
{
return $this->belongsToMany(MysqlUser::class);
}

/**
* Check if we need to run the schema.
*/
public static function executeSchema(): void
{
/** @var MySqlBuilder $schema */
$schema = Schema::connection('mysql');

if (! $schema->hasTable('groups')) {
Schema::connection('mysql')->create('groups', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}

if (! $schema->hasTable('group_user')) {
Schema::connection('mysql')->create('group_user', function (Blueprint $table) {
$table->foreignId('user_id')->constrained('users')->cascadeOnUpdate()->cascadeOnDelete();
$table->foreignId('group_id')->constrained('groups')->cascadeOnUpdate()->cascadeOnDelete();
});
}
}
}
11 changes: 9 additions & 2 deletions tests/models/MysqlUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

declare(strict_types=1);

use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\MySqlBuilder;
use Illuminate\Support\Facades\Schema;
use Jenssegers\Mongodb\Eloquent\HybridRelations;

Expand All @@ -31,17 +33,22 @@ public function mysqlBooks(): HasMany
return $this->hasMany(MysqlBook::class);
}

public function groups(): BelongsToMany
{
return $this->belongsToMany(MysqlGroup::class, 'group_user', 'user_id', 'group_id');
}

/**
* Check if we need to run the schema.
*/
public static function executeSchema(): void
{
/** @var \Illuminate\Database\Schema\MySqlBuilder $schema */
/** @var MySqlBuilder $schema */
$schema = Schema::connection('mysql');

if (! $schema->hasTable('users')) {
Schema::connection('mysql')->create('users', function (Blueprint $table) {
$table->increments('id');
$table->id();
$table->string('name');
$table->timestamps();
});
Expand Down
25 changes: 19 additions & 6 deletions tests/models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

declare(strict_types=1);

use Carbon\Carbon;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
Expand All @@ -11,6 +12,8 @@
use Illuminate\Support\Str;
use Jenssegers\Mongodb\Eloquent\HybridRelations;
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
use Jenssegers\Mongodb\Relations\EmbedsMany;
use Jenssegers\Mongodb\Relations\EmbedsOne;

/**
* Class User.
Expand All @@ -20,9 +23,9 @@
* @property string $email
* @property string $title
* @property int $age
* @property \Carbon\Carbon $birthday
* @property \Carbon\Carbon $created_at
* @property \Carbon\Carbon $updated_at
* @property Carbon $birthday
* @property Carbon $created_at
* @property Carbon $updated_at
* @property string $username
* @property MemberStatus member_status
*/
Expand Down Expand Up @@ -73,20 +76,20 @@ public function clients()

public function groups()
{
return $this->belongsToMany('Group', 'groups', 'users', 'groups', '_id', '_id', 'groups');
return $this->belongsToMany('Group');
}

public function photos()
{
return $this->morphMany('Photo', 'has_image');
}

public function addresses()
public function addresses(): EmbedsMany
{
return $this->embedsMany('Address');
}

public function father()
public function father(): EmbedsOne
{
return $this->embedsOne('User');
}
Expand All @@ -103,4 +106,14 @@ protected function username(): Attribute
set: fn ($value) => Str::slug($value)
);
}

public function getFooAttribute()
{
return 'normal attribute';
}

public function foo()
{
return 'normal function';
}
}