Skip to content

Commit

Permalink
Fix priority of embeds vs attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
mshamaseen authored and GromNaN committed Aug 29, 2023
1 parent f06f7b3 commit c105da6
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 19 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -856,19 +856,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 v4.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 @@ -937,10 +941,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 @@ -953,12 +958,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 v4.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
30 changes: 30 additions & 0 deletions src/Eloquent/EmbedsRelations.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
use Illuminate\Support\Str;
use Jenssegers\Mongodb\Relations\EmbedsMany;
use Jenssegers\Mongodb\Relations\EmbedsOne;
use ReflectionMethod;
use ReflectionNamedType;

/**
* Embeds relations for MongoDB models.
*/
trait EmbedsRelations
{
/**
* @var array{class-string<self>, bool}
*/
private static array $embeddedCache = [];

/**
* Define an embedded one-to-many relationship.
*
Expand Down Expand Up @@ -76,4 +83,27 @@ protected function embedsOne($related, $localKey = null, $foreignKey = null, $re

return new EmbedsOne($query, $this, $instance, $localKey, $foreignKey, $relation);
}

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

if (isset(self::$embeddedCache[static::class][$key])) {
return self::$embeddedCache[static::class][$key];
}

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

return static::$embeddedCache[static::class][$key] = $returnType instanceof ReflectionNamedType
&& in_array($returnType->getName(), [EmbedsOne::class, EmbedsMany::class], true);
}
}
12 changes: 5 additions & 7 deletions src/Eloquent/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
use MongoDB\BSON\Binary;
use MongoDB\BSON\ObjectID;
use MongoDB\BSON\UTCDateTime;
use ReflectionException;
use function uniqid;

abstract class Model extends BaseModel
Expand Down Expand Up @@ -154,6 +155,7 @@ public function getTable()

/**
* @inheritdoc
* @throws ReflectionException
*/
public function getAttribute($key)
{
Expand All @@ -172,11 +174,7 @@ public function getAttribute($key)
}

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

Expand Down Expand Up @@ -474,7 +472,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 @@ -484,7 +482,7 @@ public function setParentRelation(Relation $relation)
/**
* Get the parent relation.
*
* @return \Illuminate\Database\Eloquent\Relations\Relation
* @return Relation
*/
public function getParentRelation()
{
Expand Down
7 changes: 7 additions & 0 deletions tests/ModelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1035,4 +1035,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);
}
}
2 changes: 1 addition & 1 deletion tests/Models/Group.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@ class Group extends Eloquent

public function users(): BelongsToMany
{
return $this->belongsToMany(User::class, 'users', 'groups', 'users', '_id', '_id', 'users');
return $this->belongsToMany(User::class);
}
}
25 changes: 19 additions & 6 deletions tests/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Jenssegers\Mongodb\Tests\Models;

use Carbon\Carbon;
use DateTimeInterface;
use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\Passwords\CanResetPassword;
Expand All @@ -14,6 +15,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 @@ -23,9 +26,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 @@ -76,20 +79,20 @@ public function clients()

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

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

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

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

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

public function foo()
{
return 'normal function';
}
}
4 changes: 2 additions & 2 deletions tests/RelationsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -344,8 +344,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

0 comments on commit c105da6

Please sign in to comment.