Skip to content

Commit 5ad9752

Browse files
authored
Merge pull request #78 from renoki-co/feature/many-to-many-flush
[3.x] Many to many flush
2 parents 37efbe8 + d0d097b commit 5ad9752

17 files changed

+249
-11
lines changed

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@
3030
"test": "vendor/bin/phpunit"
3131
},
3232
"require-dev": {
33+
"chelout/laravel-relationship-events": "^1.4",
3334
"laravel/legacy-factories": "^1.1",
3435
"mockery/mockery": "^1.4",
35-
"orchestra/testbench": "^5.0|^6.0",
36-
"orchestra/database": "^5.0|^6.0"
36+
"orchestra/database": "^5.0|^6.0",
37+
"orchestra/testbench": "^5.0|^6.0"
3738
},
3839
"config": {
3940
"sort-packages": true

src/FlushQueryCacheObserver.php

Lines changed: 83 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,103 @@ public function restored(Model $model)
6262
$this->invalidateCache($model);
6363
}
6464

65+
/**
66+
* Invalidate attach for belongsToMany.
67+
*
68+
* @param string $relation
69+
* @param \Illuminate\Database\Eloquent\Model $model
70+
* @param array $ids
71+
* @return void
72+
*/
73+
public function belongsToManyAttached($relation, Model $model, $ids)
74+
{
75+
$this->invalidateCache($model, $relation, $model->{$relation}()->findMany($ids));
76+
}
77+
78+
/**
79+
* Invalidate detach for belongsToMany.
80+
*
81+
* @param string $relation
82+
* @param \Illuminate\Database\Eloquent\Model $model
83+
* @param array $ids
84+
* @return void
85+
*/
86+
public function belongsToManyDetached($relation, Model $model, $ids)
87+
{
88+
$this->invalidateCache($model, $relation, $model->{$relation}()->findMany($ids));
89+
}
90+
91+
/**
92+
* Invalidate update pivot for belongsToMany.
93+
*
94+
* @param string $relation
95+
* @param \Illuminate\Database\Eloquent\Model $model
96+
* @param array $ids
97+
* @return void
98+
*/
99+
public function belongsToManyUpdatedExistingPivot($relation, Model $model, $ids)
100+
{
101+
$this->invalidateCache($model, $relation, $model->{$relation}()->findMany($ids));
102+
}
103+
104+
/**
105+
* Invalidate attach for morphToMany.
106+
*
107+
* @param string $relation
108+
* @param \Illuminate\Database\Eloquent\Model $model
109+
* @param array $ids
110+
* @return void
111+
*/
112+
public function morphToManyAttached($relation, Model $model, $ids)
113+
{
114+
$this->invalidateCache($model, $relation, $model->{$relation}()->findMany($ids));
115+
}
116+
117+
/**
118+
* Invalidate detach for morphToMany.
119+
*
120+
* @param string $relation
121+
* @param \Illuminate\Database\Eloquent\Model $model
122+
* @param array $ids
123+
* @return void
124+
*/
125+
public function morphToManyDetached($relation, Model $model, $ids)
126+
{
127+
$this->invalidateCache($model, $relation, $model->{$relation}()->findMany($ids));
128+
}
129+
130+
/**
131+
* Invalidate update pivot for morphToMany.
132+
*
133+
* @param string $relation
134+
* @param \Illuminate\Database\Eloquent\Model $model
135+
* @param array $ids
136+
* @return void
137+
*/
138+
public function morphToManyUpdatedExistingPivot($relation, Model $model, $ids)
139+
{
140+
$this->invalidateCache($model, $relation, $model->{$relation}()->findMany($ids));
141+
}
142+
65143
/**
66144
* Invalidate the cache for a model.
67145
*
68146
* @param \Illuminate\Database\Eloquent\Model $model
147+
* @param string|null $relation
148+
* @param \Illuminate\Database\Eloquent\Collection|null $pivotedModels
69149
* @return void
70150
* @throws Exception
71151
*/
72-
protected function invalidateCache(Model $model): void
152+
protected function invalidateCache(Model $model, $relation = null, $pivotedModels = null): void
73153
{
74154
$class = get_class($model);
75155

76-
if (! $model->getCacheTagsToInvalidateOnUpdate()) {
156+
if (! $model->getCacheTagsToInvalidateOnUpdate($relation, $pivotedModels)) {
77157
throw new Exception('Automatic invalidation for '.$class.' works only if at least one tag to be invalidated is specified.');
78158
}
79159

80160
$class::flushQueryCache(
81-
$model->getCacheTagsToInvalidateOnUpdate()
161+
$model->getCacheTagsToInvalidateOnUpdate($relation, $pivotedModels)
82162
);
83163
}
84164
}

src/Traits/QueryCacheModule.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,11 +318,12 @@ public function cacheBaseTags(array $tags = [])
318318
/**
319319
* Use a plain key instead of a hashed one in the cache driver.
320320
*
321+
* @param bool $usePlainKey
321322
* @return \Rennokki\QueryCache\Traits\QueryCacheModule
322323
*/
323-
public function withPlainKey()
324+
public function withPlainKey(bool $usePlainKey = true)
324325
{
325-
$this->cacheUsePlainKey = true;
326+
$this->cacheUsePlainKey = $usePlainKey;
326327

327328
return $this;
328329
}

src/Traits/QueryCacheable.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@
88
/**
99
* @method static bool flushQueryCache(string[] $array = [])
1010
* @method static bool flushQueryCacheWithTag(string $string)
11+
* @method static \Illuminate\Database\Query\Builder|static cacheFor()
1112
* @method static \Illuminate\Database\Query\Builder|static cacheForever()
1213
* @method static \Illuminate\Database\Query\Builder|static dontCache()
1314
* @method static \Illuminate\Database\Query\Builder|static doNotCache()
15+
* @method static \Illuminate\Database\Query\Builder|static cachePrefix()
16+
* @method static \Illuminate\Database\Query\Builder|static cacheTags()
17+
* @method static \Illuminate\Database\Query\Builder|static appendCacheTags()
18+
* @method static \Illuminate\Database\Query\Builder|static cacheDriver()
19+
* @method static \Illuminate\Database\Query\Builder|static cacheBaseTags()
1420
*/
1521
trait QueryCacheable
1622
{
@@ -44,9 +50,11 @@ protected static function getFlushQueryCacheObserver()
4450
* When invalidating automatically on update, you can specify
4551
* which tags to invalidate.
4652
*
53+
* @param string|null $relation
54+
* @param \Illuminate\Database\Eloquent\Collection|null $pivotedModels
4755
* @return array
4856
*/
49-
public function getCacheTagsToInvalidateOnUpdate(): array
57+
public function getCacheTagsToInvalidateOnUpdate($relation = null, $pivotedModels = null): array
5058
{
5159
return $this->getCacheBaseTags();
5260
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
namespace Rennokki\QueryCache\Test;
4+
5+
use Rennokki\QueryCache\Test\Models\Role;
6+
use Rennokki\QueryCache\Test\Models\User;
7+
8+
class FlushCacheOnUpdatePivotTest extends TestCase
9+
{
10+
public function test_belongs_to_many()
11+
{
12+
$key = 'leqc:sqlitegetselect "roles".*, "role_user"."user_id" as "pivot_user_id", "role_user"."role_id" as "pivot_role_id" from "roles" inner join "role_user" on "roles"."id" = "role_user"."role_id" where "role_user"."user_id" = ? limit 1a:1:{i:0;i:1;}';
13+
14+
$user = factory(User::class)->create();
15+
$role = factory(Role::class)->create();
16+
$storedRoles = $user->roles()->cacheFor(now()->addHours(1))->cacheTags(["user:{$user->id}:roles"])->get();
17+
$cache = $this->getCacheWithTags($key, ["user:{$user->id}:roles"]);
18+
19+
$this->assertNull($cache);
20+
$this->assertEquals(0, $storedRoles->count());
21+
22+
$user->roles()->attach($role->id);
23+
24+
$storedRoles = $user->roles()->cacheFor(now()->addHours(1))->cacheTags(["user:{$user->id}:roles"])->get();
25+
26+
$this->assertEquals(
27+
$role->id,
28+
$storedRoles->first()->id
29+
);
30+
31+
$user->roles()->detach($role->id);
32+
33+
$storedRoles = $user->roles()->cacheFor(now()->addHours(1))->cacheTags(["user:{$user->id}:roles"])->get();
34+
35+
$this->assertEquals(0, $storedRoles->count());
36+
}
37+
}

tests/MethodsTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ public function test_cache_flush_with_default_tags_attached()
119119
public function test_hashed_key()
120120
{
121121
$kid = factory(Kid::class)->create();
122-
$storedKid = Kid::cacheFor(now()->addHours(1))->first();
122+
$storedKid = Kid::cacheFor(now()->addHours(1))->withPlainKey(false)->first();
123123
$cache = Cache::get('leqc:156667fa9bcb7fb8abb01018568648406f251ef65736e89e6fd27d08bc48b5bb');
124124

125125
$this->assertNotNull($cache);

tests/Models/Kid.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ class Kid extends Model
99
{
1010
use QueryCacheable;
1111

12+
protected $cacheUsePlainKey = true;
13+
1214
protected $fillable = [
1315
'name',
1416
];

tests/Models/Role.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
namespace Rennokki\QueryCache\Test\Models;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use Rennokki\QueryCache\Traits\QueryCacheable;
7+
8+
class Role extends Model
9+
{
10+
use QueryCacheable;
11+
12+
protected $cacheUsePlainKey = true;
13+
14+
protected $fillable = [
15+
'name',
16+
];
17+
18+
protected function getCacheBaseTags(): array
19+
{
20+
return [
21+
//
22+
];
23+
}
24+
}

tests/Models/User.php

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22

33
namespace Rennokki\QueryCache\Test\Models;
44

5+
use Chelout\RelationshipEvents\Concerns\HasBelongsToManyEvents;
6+
use Chelout\RelationshipEvents\Traits\HasRelationshipObservables;
57
use Illuminate\Foundation\Auth\User as Authenticatable;
68
use Rennokki\QueryCache\Traits\QueryCacheable;
79

810
class User extends Authenticatable
911
{
12+
use HasBelongsToManyEvents;
13+
use HasRelationshipObservables;
1014
use QueryCacheable;
1115

16+
protected static $flushCacheOnUpdate = true;
17+
18+
protected $cacheUsePlainKey = true;
19+
1220
protected $fillable = [
1321
'name', 'email', 'password',
1422
];
@@ -20,12 +28,32 @@ class User extends Authenticatable
2028
protected function getCacheBaseTags(): array
2129
{
2230
return [
23-
//
31+
'test',
2432
];
2533
}
2634

35+
public function getCacheTagsToInvalidateOnUpdate($relation = null, $pivotedModels = null): array
36+
{
37+
if ($relation === 'roles') {
38+
$tags = array_reduce($pivotedModels->all(), function ($tags, Role $role) {
39+
return array_merge($tags, ["user:{$this->id}:roles:{$role->id}"]);
40+
}, []);
41+
42+
return array_merge($tags, [
43+
"user:{$this->id}:roles",
44+
]);
45+
}
46+
47+
return $this->getCacheBaseTags();
48+
}
49+
2750
public function posts()
2851
{
2952
return $this->hasMany(Post::class);
3053
}
54+
55+
public function roles()
56+
{
57+
return $this->belongsToMany(Role::class);
58+
}
3159
}

tests/TestCase.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function setUp(): void
2020
$this->loadLaravelMigrations(['--database' => 'sqlite']);
2121
$this->loadMigrationsFrom(__DIR__.'/../database/migrations');
2222
$this->loadMigrationsFrom(__DIR__.'/database/migrations');
23-
$this->withFactories(__DIR__.'/../database/factories');
23+
$this->withFactories(__DIR__.'/database/factories');
2424

2525
$this->artisan('migrate', ['--database' => 'sqlite']);
2626
}

0 commit comments

Comments
 (0)