Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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)

AltStyle によって変換されたページ (->オリジナル) /