Skip to content

Commit 7710d58

Browse files
mshamaseenGromNaN
authored andcommitted
Fix priority of embeds vs attributes
1 parent 25c73e2 commit 7710d58

File tree

7 files changed

+75
-19
lines changed

7 files changed

+75
-19
lines changed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -853,19 +853,23 @@ class User extends Model
853853
}
854854
}
855855
```
856+
**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.
856857

857858
### EmbedsMany Relationship
858859

859860
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.
860861

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

864+
**Breaking changes** starting from v4.0 you need to define the return type of EmbedsOne and EmbedsMany relation for it to work
865+
863866
```php
864867
use MongoDB\Laravel\Eloquent\Model;
868+
use MongoDB\Laravel\Relations\EmbedsMany;
865869

866870
class User extends Model
867871
{
868-
public function books()
872+
public function books(): EmbedsMany
869873
{
870874
return $this->embedsMany(Book::class);
871875
}
@@ -934,10 +938,11 @@ Like other relations, embedsMany assumes the local key of the relationship based
934938

935939
```php
936940
use MongoDB\Laravel\Eloquent\Model;
941+
use MongoDB\Laravel\Relations\EmbedsMany;
937942

938943
class User extends Model
939944
{
940-
public function books()
945+
public function books(): EmbedsMany
941946
{
942947
return $this->embedsMany(Book::class, 'local_key');
943948
}
@@ -950,12 +955,15 @@ Embedded relations will return a Collection of embedded items instead of a query
950955

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

958+
**Breaking changes** starting from v4.0 you need to define the return type of EmbedsOne and EmbedsMany relation for it to work
959+
953960
```php
954961
use MongoDB\Laravel\Eloquent\Model;
962+
use MongoDB\Laravel\Relations\EmbedsOne;
955963

956964
class Book extends Model
957965
{
958-
public function author()
966+
public function author(): EmbedsOne
959967
{
960968
return $this->embedsOne(Author::class);
961969
}

src/Eloquent/EmbedsRelations.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,19 @@
55
use Illuminate\Support\Str;
66
use MongoDB\Laravel\Relations\EmbedsMany;
77
use MongoDB\Laravel\Relations\EmbedsOne;
8+
use ReflectionMethod;
9+
use ReflectionNamedType;
810

911
/**
1012
* Embeds relations for MongoDB models.
1113
*/
1214
trait EmbedsRelations
1315
{
16+
/**
17+
* @var array{class-string<self>, bool}
18+
*/
19+
private static array $embeddedCache = [];
20+
1421
/**
1522
* Define an embedded one-to-many relationship.
1623
*
@@ -76,4 +83,27 @@ protected function embedsOne($related, $localKey = null, $foreignKey = null, $re
7683

7784
return new EmbedsOne($query, $this, $instance, $localKey, $foreignKey, $relation);
7885
}
86+
87+
/**
88+
* Determine if an attribute is an embedded relation.
89+
*
90+
* @param string $key
91+
* @return bool
92+
* @throws \ReflectionException
93+
*/
94+
private function hasEmbeddedRelation(string $key): bool
95+
{
96+
if (! method_exists($this, $method = Str::camel($key))) {
97+
return false;
98+
}
99+
100+
if (isset(self::$embeddedCache[static::class][$key])) {
101+
return self::$embeddedCache[static::class][$key];
102+
}
103+
104+
$returnType = (new ReflectionMethod($this, $method))->getReturnType();
105+
106+
return static::$embeddedCache[static::class][$key] = $returnType instanceof ReflectionNamedType
107+
&& in_array($returnType->getName(), [EmbedsOne::class, EmbedsMany::class], true);
108+
}
79109
}

src/Eloquent/Model.php

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use MongoDB\BSON\ObjectID;
1919
use MongoDB\BSON\UTCDateTime;
2020
use MongoDB\Laravel\Query\Builder as QueryBuilder;
21+
use ReflectionException;
2122
use function uniqid;
2223

2324
abstract class Model extends BaseModel
@@ -154,6 +155,7 @@ public function getTable()
154155

155156
/**
156157
* @inheritdoc
158+
* @throws ReflectionException
157159
*/
158160
public function getAttribute($key)
159161
{
@@ -172,11 +174,7 @@ public function getAttribute($key)
172174
}
173175

174176
// This checks for embedded relation support.
175-
if (
176-
method_exists($this, $key)
177-
&& ! method_exists(self::class, $key)
178-
&& ! $this->hasAttributeGetMutator($key)
179-
) {
177+
if ($this->hasEmbeddedRelation($key)) {
180178
return $this->getRelationValue($key);
181179
}
182180

@@ -474,7 +472,7 @@ public function getForeignKey()
474472
/**
475473
* Set the parent relation.
476474
*
477-
* @param \Illuminate\Database\Eloquent\Relations\Relation $relation
475+
* @param Relation $relation
478476
*/
479477
public function setParentRelation(Relation $relation)
480478
{
@@ -484,7 +482,7 @@ public function setParentRelation(Relation $relation)
484482
/**
485483
* Get the parent relation.
486484
*
487-
* @return \Illuminate\Database\Eloquent\Relations\Relation
485+
* @return Relation
488486
*/
489487
public function getParentRelation()
490488
{

tests/ModelTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,4 +1035,11 @@ public function testEnumCast(): void
10351035
$this->assertSame(MemberStatus::Member->value, $check->getRawOriginal('member_status'));
10361036
$this->assertSame(MemberStatus::Member, $check->member_status);
10371037
}
1038+
1039+
public function testGetFooAttributeAccessor()
1040+
{
1041+
$user = new User();
1042+
1043+
$this->assertSame('normal attribute', $user->foo);
1044+
}
10381045
}

tests/Models/Group.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,6 @@ class Group extends Eloquent
1515

1616
public function users(): BelongsToMany
1717
{
18-
return $this->belongsToMany(User::class, 'users', 'groups', 'users', '_id', '_id', 'users');
18+
return $this->belongsToMany(User::class);
1919
}
2020
}

tests/Models/User.php

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace MongoDB\Laravel\Tests\Models;
66

7+
use Carbon\Carbon;
78
use DateTimeInterface;
89
use Illuminate\Auth\Authenticatable;
910
use Illuminate\Auth\Passwords\CanResetPassword;
@@ -14,6 +15,8 @@
1415
use Illuminate\Support\Str;
1516
use MongoDB\Laravel\Eloquent\HybridRelations;
1617
use MongoDB\Laravel\Eloquent\Model as Eloquent;
18+
use MongoDB\Laravel\Relations\EmbedsMany;
19+
use MongoDB\Laravel\Relations\EmbedsOne;
1720

1821
/**
1922
* Class User.
@@ -23,9 +26,9 @@
2326
* @property string $email
2427
* @property string $title
2528
* @property int $age
26-
* @property \Carbon\Carbon $birthday
27-
* @property \Carbon\Carbon $created_at
28-
* @property \Carbon\Carbon $updated_at
29+
* @property Carbon $birthday
30+
* @property Carbon $created_at
31+
* @property Carbon $updated_at
2932
* @property string $username
3033
* @property MemberStatus member_status
3134
*/
@@ -76,20 +79,20 @@ public function clients()
7679

7780
public function groups()
7881
{
79-
return $this->belongsToMany(Group::class, 'groups', 'users', 'groups', '_id', '_id', 'groups');
82+
return $this->belongsToMany(Group::class);
8083
}
8184

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

87-
public function addresses()
90+
public function addresses(): EmbedsMany
8891
{
8992
return $this->embedsMany(Address::class);
9093
}
9194

92-
public function father()
95+
public function father(): EmbedsOne
9396
{
9497
return $this->embedsOne(self::class);
9598
}
@@ -106,4 +109,14 @@ protected function username(): Attribute
106109
set: fn ($value) => Str::slug($value)
107110
);
108111
}
112+
113+
public function getFooAttribute()
114+
{
115+
return 'normal attribute';
116+
}
117+
118+
public function foo()
119+
{
120+
return 'normal function';
121+
}
109122
}

tests/RelationsTest.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,8 @@ public function testBelongsToManyCustom(): void
344344
$group = Group::find($group->_id);
345345

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

350350
// Assert they are attached
351351
$this->assertContains($group->_id, $user->groups->pluck('_id')->toArray());

0 commit comments

Comments
 (0)