Skip to content

Commit dbfa900

Browse files
authored
Merge pull request #1 from mshamaseen/next-v4
Follow laravel in the way that attributes are first then relation
2 parents 5385aa4 + d0666b6 commit dbfa900

File tree

11 files changed

+163
-22
lines changed

11 files changed

+163
-22
lines changed

.github/FUNDING.yml

Lines changed: 0 additions & 2 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,29 @@
22
All notable changes to this project will be documented in this file.
33

44
## [Unreleased]
5+
None yet.
6+
7+
## [10.0] - 2023-08-22
8+
9+
### Added
10+
- MongoDB v6.0 supports and tests
11+
- use Mongosh in tests instead of the old Mongo
12+
- Mysql 8 Hybrid relation tests
13+
14+
### Removed
15+
- MongoDB v4.* support was dropped
16+
17+
### Fixed
18+
- 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)
19+
- 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.
20+
21+
### Breaking
22+
- EmbedsOne and EmbedsMany now require return type to work (Example in the documentation has been updated)
23+
- You can't have the belongsToMany foreign key to be the same as the relation name (check documentation for more details)
524

625
## [3.9.2] - 2022-09-01
726

8-
### Addded
27+
### Addded
928
- 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).
1029

1130
### Fixed

README.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -839,19 +839,23 @@ class User extends Model
839839
}
840840
}
841841
```
842+
**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.
842843

843844
### EmbedsMany Relationship
844845

845846
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.
846847

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

850+
**Breaking changes** starting from v10.0 you need to define the return type of EmbedsOne and EmbedsMany relation for it to work
851+
849852
```php
850853
use Jenssegers\Mongodb\Eloquent\Model;
854+
use Jenssegers\Mongodb\Relations\EmbedsMany;
851855

852856
class User extends Model
853857
{
854-
public function books()
858+
public function books(): EmbedsMany
855859
{
856860
return $this->embedsMany(Book::class);
857861
}
@@ -920,10 +924,11 @@ Like other relations, embedsMany assumes the local key of the relationship based
920924

921925
```php
922926
use Jenssegers\Mongodb\Eloquent\Model;
927+
use Jenssegers\Mongodb\Relations\EmbedsMany;
923928

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

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

944+
**Breaking changes** starting from v10.0 you need to define the return type of EmbedsOne and EmbedsMany relation for it to work
945+
939946
```php
940947
use Jenssegers\Mongodb\Eloquent\Model;
948+
use Jenssegers\Mongodb\Relations\EmbedsOne;
941949

942950
class Book extends Model
943951
{
944-
public function author()
952+
public function author(): EmbedsOne
945953
{
946954
return $this->embedsOne(Author::class);
947955
}

src/Eloquent/Model.php

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,22 @@
1515
use Illuminate\Support\Str;
1616
use function in_array;
1717
use Jenssegers\Mongodb\Query\Builder as QueryBuilder;
18+
use Jenssegers\Mongodb\Relations\EmbedsMany;
19+
use Jenssegers\Mongodb\Relations\EmbedsOne;
1820
use MongoDB\BSON\Binary;
1921
use MongoDB\BSON\ObjectID;
2022
use MongoDB\BSON\UTCDateTime;
23+
use ReflectionException;
24+
use ReflectionMethod;
25+
use ReflectionNamedType;
2126
use function uniqid;
2227

2328
abstract class Model extends BaseModel
2429
{
2530
use HybridRelations, EmbedsRelations;
2631

32+
public static array $embeddedCache = [];
33+
2734
/**
2835
* The collection associated with the model.
2936
*
@@ -161,16 +168,38 @@ public function getAttribute($key)
161168

162169
// This checks for embedded relation support.
163170
if (
164-
method_exists($this, $key)
165-
&& ! method_exists(self::class, $key)
166-
&& ! $this->hasAttributeGetMutator($key)
171+
$this->hasEmbeddedRelation($key)
167172
) {
168173
return $this->getRelationValue($key);
169174
}
170175

171176
return parent::getAttribute($key);
172177
}
173178

179+
/**
180+
* Determine if an attribute is an embedded relation.
181+
*
182+
* @param string $key
183+
* @return bool
184+
* @throws ReflectionException
185+
*/
186+
public function hasEmbeddedRelation(string $key): bool
187+
{
188+
if (! method_exists($this, $method = Str::camel($key))) {
189+
return false;
190+
}
191+
192+
if (isset(static::$embeddedCache[get_class($this)][$key])) {
193+
return static::$embeddedCache[get_class($this)][$key];
194+
}
195+
196+
$returnType = (new ReflectionMethod($this, $method))->getReturnType();
197+
198+
return $returnType && static::$embeddedCache[get_class($this)][$key] =
199+
$returnType instanceof ReflectionNamedType &&
200+
$returnType->getName() === EmbedsOne::class || $returnType->getName() === EmbedsMany::class;
201+
}
202+
174203
/**
175204
* @inheritdoc
176205
*/
@@ -401,7 +430,7 @@ public function getForeignKey()
401430
/**
402431
* Set the parent relation.
403432
*
404-
* @param \Illuminate\Database\Eloquent\Relations\Relation $relation
433+
* @param Relation $relation
405434
*/
406435
public function setParentRelation(Relation $relation)
407436
{
@@ -411,7 +440,7 @@ public function setParentRelation(Relation $relation)
411440
/**
412441
* Get the parent relation.
413442
*
414-
* @return \Illuminate\Database\Eloquent\Relations\Relation
443+
* @return Relation
415444
*/
416445
public function getParentRelation()
417446
{

tests/HybridRelationsTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,27 @@ public function setUp(): void
1313
MysqlUser::executeSchema();
1414
MysqlBook::executeSchema();
1515
MysqlRole::executeSchema();
16+
// MysqlGroup::executeSchema();
1617
}
1718

1819
public function tearDown(): void
1920
{
2021
MysqlUser::truncate();
2122
MysqlBook::truncate();
2223
MysqlRole::truncate();
24+
// MysqlGroup::truncate();
2325
}
2426

27+
// public function testMysqlGroups()
28+
// {
29+
// $user = new MysqlUser;
30+
// $user->name = 'John Doe';
31+
// $user->save();
32+
// $this->assertIsInt($user->id);
33+
//
34+
// $group = $user->groups()->create(['name' => 'test']);
35+
// }
36+
2537
public function testMysqlRelations()
2638
{
2739
$user = new MysqlUser;

tests/ModelTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,4 +801,11 @@ public function testEnumCast(): void
801801
$this->assertSame(MemberStatus::Member->value, $check->getRawOriginal('member_status'));
802802
$this->assertSame(MemberStatus::Member, $check->member_status);
803803
}
804+
805+
public function testGetFooAttributeAccessor()
806+
{
807+
$user = new User();
808+
809+
$this->assertSame('normal attribute', $user->foo);
810+
}
804811
}

tests/RelationsTest.php

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

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

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

tests/models/Group.php

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

1414
public function users(): BelongsToMany
1515
{
16-
return $this->belongsToMany('User', 'users', 'groups', 'users', '_id', '_id', 'users');
16+
return $this->belongsToMany('User');
1717
}
1818
}

tests/models/MysqlGroup.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
6+
use Illuminate\Database\Schema\Blueprint;
7+
use Illuminate\Database\Schema\MySqlBuilder;
8+
use Illuminate\Support\Facades\Schema;
9+
use Jenssegers\Mongodb\Eloquent\HybridRelations;
10+
11+
class MysqlGroup extends Eloquent
12+
{
13+
use HybridRelations;
14+
15+
protected $connection = 'mysql';
16+
protected $table = 'groups';
17+
protected static $unguarded = true;
18+
protected $primaryKey = 'name';
19+
20+
public function users(): BelongsToMany
21+
{
22+
return $this->belongsToMany(MysqlUser::class);
23+
}
24+
25+
/**
26+
* Check if we need to run the schema.
27+
*/
28+
public static function executeSchema(): void
29+
{
30+
/** @var MySqlBuilder $schema */
31+
$schema = Schema::connection('mysql');
32+
33+
if (! $schema->hasTable('groups')) {
34+
Schema::connection('mysql')->create('groups', function (Blueprint $table) {
35+
$table->id();
36+
$table->string('name');
37+
$table->timestamps();
38+
});
39+
}
40+
41+
if (! $schema->hasTable('group_user')) {
42+
Schema::connection('mysql')->create('group_user', function (Blueprint $table) {
43+
$table->foreignId('user_id')->constrained('users')->cascadeOnUpdate()->cascadeOnDelete();
44+
$table->foreignId('group_id')->constrained('groups')->cascadeOnUpdate()->cascadeOnDelete();
45+
});
46+
}
47+
}
48+
}

tests/models/MysqlUser.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
declare(strict_types=1);
44

5+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
56
use Illuminate\Database\Eloquent\Relations\HasMany;
67
use Illuminate\Database\Eloquent\Relations\HasOne;
78
use Illuminate\Database\Schema\Blueprint;
9+
use Illuminate\Database\Schema\MySqlBuilder;
810
use Illuminate\Support\Facades\Schema;
911
use Jenssegers\Mongodb\Eloquent\HybridRelations;
1012

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

36+
public function groups(): BelongsToMany
37+
{
38+
return $this->belongsToMany(MysqlGroup::class, 'group_user', 'user_id', 'group_id');
39+
}
40+
3441
/**
3542
* Check if we need to run the schema.
3643
*/
3744
public static function executeSchema(): void
3845
{
39-
/** @var \Illuminate\Database\Schema\MySqlBuilder $schema */
46+
/** @var MySqlBuilder $schema */
4047
$schema = Schema::connection('mysql');
4148

4249
if (! $schema->hasTable('users')) {
4350
Schema::connection('mysql')->create('users', function (Blueprint $table) {
44-
$table->increments('id');
51+
$table->id();
4552
$table->string('name');
4653
$table->timestamps();
4754
});

tests/models/User.php

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

33
declare(strict_types=1);
44

5+
use Carbon\Carbon;
56
use Illuminate\Auth\Authenticatable;
67
use Illuminate\Auth\Passwords\CanResetPassword;
78
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
@@ -11,6 +12,8 @@
1112
use Illuminate\Support\Str;
1213
use Jenssegers\Mongodb\Eloquent\HybridRelations;
1314
use Jenssegers\Mongodb\Eloquent\Model as Eloquent;
15+
use Jenssegers\Mongodb\Relations\EmbedsMany;
16+
use Jenssegers\Mongodb\Relations\EmbedsOne;
1417

1518
/**
1619
* Class User.
@@ -20,9 +23,9 @@
2023
* @property string $email
2124
* @property string $title
2225
* @property int $age
23-
* @property \Carbon\Carbon $birthday
24-
* @property \Carbon\Carbon $created_at
25-
* @property \Carbon\Carbon $updated_at
26+
* @property Carbon $birthday
27+
* @property Carbon $created_at
28+
* @property Carbon $updated_at
2629
* @property string $username
2730
* @property MemberStatus member_status
2831
*/
@@ -73,20 +76,20 @@ public function clients()
7376

7477
public function groups()
7578
{
76-
return $this->belongsToMany('Group', 'groups', 'users', 'groups', '_id', '_id', 'groups');
79+
return $this->belongsToMany('Group');
7780
}
7881

7982
public function photos()
8083
{
8184
return $this->morphMany('Photo', 'has_image');
8285
}
8386

84-
public function addresses()
87+
public function addresses(): EmbedsMany
8588
{
8689
return $this->embedsMany('Address');
8790
}
8891

89-
public function father()
92+
public function father(): EmbedsOne
9093
{
9194
return $this->embedsOne('User');
9295
}
@@ -103,4 +106,14 @@ protected function username(): Attribute
103106
set: fn ($value) => Str::slug($value)
104107
);
105108
}
109+
110+
public function getFooAttribute()
111+
{
112+
return 'normal attribute';
113+
}
114+
115+
public function foo()
116+
{
117+
return 'normal function';
118+
}
106119
}

0 commit comments

Comments
 (0)