Skip to content
This repository was archived by the owner on Feb 18, 2022. It is now read-only.

Commit 6c79c61

Browse files
Merge pull request #3 from DarkGhostHunter/master
Better filtering scopes, auto-delete
2 parents 0a8c3f8 + c2abe85 commit 6c79c61

File tree

7 files changed

+203
-40
lines changed

7 files changed

+203
-40
lines changed

README.md

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -131,21 +131,7 @@ Setting::name('notify_email')->boolean()->default(true)->bag('notifications');
131131
Setting::name('notify_sms')->boolean()->default(false)->bag('notifications');
132132
```
133133

134-
Later, in your model, you can filter the bags you want to work with using `filterBags()`:
135-
136-
```php
137-
/**
138-
* Returns the bags this model uses for settings.
139-
*
140-
* @return array|string
141-
*/
142-
public function filterBags(): array|string
143-
{
144-
return ['style', 'notifications'];
145-
}
146-
```
147-
148-
> All settings are created for all models. Bags only apply a filter to them at query time to virtually "exclude" them from results. **This filter cannot be disabled**.
134+
Later, in your model, you can filter the bags you want to work with using [`filterBags()`](#setting-bags) in your model.
149135

150136
## Migrating settings
151137

@@ -461,6 +447,34 @@ public function getSettingsBags(): array|string
461447
462448
The above will apply a filter to the query when retrieving settings from the database. This makes easy to swap bags when a user has a different role or property, or programmatically.
463449

450+
> **All** settings are created for all models with `HasConfig` trait, regardless of the bags used by the model.
451+
452+
#### Disabling the bag filter scope
453+
454+
Laraconfig applies a query filter to exclude the settings not in the model bag. While this eases the development, sometimes you will want to work with the full set of settings available.
455+
456+
There are two ways to disable the bag filter. The first one is relatively easy: simply use the `withoutGlobalScope()` at query time, which will allow to query all the settings available to the user.
457+
458+
```php
459+
use DarkGhostHunter\Laraconfig\Eloquent\Scopes\FilterBags;
460+
461+
$allSettings = $user->settings()->withoutGlobalScope(FilterBags::class)->get();
462+
```
463+
464+
If you want a more _permanent_ solution, just simply return an empty array or `null` when using the `filterBags()` method in you model, which will disable the scope completely.
465+
466+
```php
467+
/**
468+
* Returns the bags this model uses for settings.
469+
*
470+
* @return array|string
471+
*/
472+
public function filterBags(): array|string|null
473+
{
474+
return null;
475+
}
476+
```
477+
464478
## Cache
465479

466480
Hitting the database each request to retrieve the user settings can be detrimental if you expect to happen a lot. To avoid this, you can activate a cache which will be regenerated each time a setting changes.

src/Eloquent/Scopes/FilterBags.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace DarkGhostHunter\Laraconfig\Eloquent\Scopes;
4+
5+
use Illuminate\Database\Eloquent\Builder;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Eloquent\Scope;
8+
9+
class FilterBags implements Scope
10+
{
11+
/**
12+
* FilterBags constructor.
13+
*
14+
* @param array $bags
15+
*/
16+
public function __construct(protected array $bags)
17+
{
18+
}
19+
20+
/**
21+
* Apply the scope to a given Eloquent query builder.
22+
*
23+
* @param \Illuminate\Database\Eloquent\Builder $builder
24+
* @param \Illuminate\Database\Eloquent\Model $model
25+
*
26+
* @return void
27+
*/
28+
public function apply(Builder $builder, Model $model): void
29+
{
30+
$builder->whereHas('metadata', function (Builder $query): void {
31+
$query->whereIn('bag', $this->bags);
32+
});
33+
}
34+
}

src/Eloquent/Scopes/WhereConfig.php

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ class WhereConfig implements Scope
1616
*
1717
* @return void
1818
*/
19-
public function apply(Builder $builder, Model $model)
19+
public function apply(Builder $builder, Model $model): void
2020
{
2121
//
2222
}
@@ -28,7 +28,7 @@ public function apply(Builder $builder, Model $model)
2828
*
2929
* @return void
3030
*/
31-
public function extend(Builder $builder)
31+
public function extend(Builder $builder): void
3232
{
3333
$builder->macro('whereConfig', [static::class, 'whereConfig']);
3434
}
@@ -53,17 +53,16 @@ public static function whereConfig(Builder $builder, string|array $name, mixed $
5353
}
5454

5555
return $builder->whereHas('settings', static function (Builder $builder) use ($name, $value): void {
56-
if (!in_array(AddMetadata::class, $builder->removedScopes(), true)) {
57-
$builder->withoutGlobalScope(AddMetadata::class);
58-
}
59-
60-
$builder->where(static function (Builder $builder) use ($name, $value): void {
61-
$builder
62-
->where('value', $value)
63-
->whereHas('metadata', static function (Builder $builder) use ($name): void {
64-
$builder->where('name', $name);
65-
});
56+
$builder
57+
->withoutGlobalScope(AddMetadata::class)
58+
->where(
59+
static function (Builder $builder) use ($name, $value): void {
60+
$builder
61+
->where('value', $value)
62+
->whereHas('metadata', static function (Builder $builder) use ($name): void {
63+
$builder->where('name', $name);
64+
});
6665
});
6766
});
6867
}
69-
}
68+
}

src/HasConfig.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,5 +49,14 @@ static function (Model $model): void {
4949
}
5050
}
5151
);
52+
53+
static::deleting(
54+
static function (Model $model): void {
55+
// Bye settings on delete, or force-delete.
56+
if (!method_exists($model, 'isForceDeleting') || $model->isForceDeleting()) {
57+
$model->settings()->withoutGlobalScopes()->delete();
58+
}
59+
}
60+
);
5261
}
5362
}

src/MorphManySettings.php

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,21 +73,23 @@ public function windUp(Model $parent): void
7373
}
7474

7575
/**
76-
* Set the base constraints on the relation query.
76+
* Get the relationship query.
7777
*
78-
* @return void
78+
* @param \Illuminate\Database\Eloquent\Builder $query
79+
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
80+
* @param array|mixed $columns
81+
* @return \Illuminate\Database\Eloquent\Builder
7982
*/
80-
public function addConstraints(): void
83+
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']): Builder
8184
{
82-
parent::addConstraints();
83-
84-
// Filter the bags of the model for this relationship.
85-
$this->getQuery()->whereHas('metadata', function (Builder $query): void {
86-
$query->whereIn('bag', $this->bags);
87-
});
85+
// We will add the global scope only when checking for existence.
86+
// This should appear on SELECT instead of other queries.
87+
return parent::getRelationExistenceQuery($query, $parentQuery, $columns)
88+
->when($this->bags, static function (Builder $builder, $value): void {
89+
$builder->withGlobalScope(Eloquent\Scopes\FilterBags::class, new Eloquent\Scopes\FilterBags($value));
90+
});
8891
}
8992

90-
9193
/**
9294
* Generates the key for the model to save into the cache.
9395
*

tests/Eloquent/Scopes/FilterBySettingTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,7 @@ public function test_filters_user_by_config(): void
101101

102102
static::assertCount(1, $users);
103103

104-
$users = DummyModel::whereConfig('quz', 'quz-value')
105-
->first();
104+
$users = DummyModel::whereConfig('quz', 'quz-value')->first();
106105

107106
static::assertNull($users);
108107

tests/HasConfigTest.php

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Tests;
44

55
use DarkGhostHunter\Laraconfig\Eloquent\Metadata;
6+
use DarkGhostHunter\Laraconfig\Eloquent\Scopes\FilterBags;
67
use DarkGhostHunter\Laraconfig\Eloquent\Setting;
78
use DarkGhostHunter\Laraconfig\HasConfig;
89
use DarkGhostHunter\Laraconfig\SettingsCollection;
@@ -12,8 +13,11 @@
1213
use Illuminate\Contracts\Cache\Repository;
1314
use Illuminate\Database\Eloquent\Collection;
1415
use Illuminate\Database\Eloquent\Model;
16+
use Illuminate\Database\Eloquent\SoftDeletes;
17+
use Illuminate\Database\Schema\Blueprint;
1518
use Illuminate\Foundation\Testing\RefreshDatabase;
1619
use Illuminate\Support\Facades\Cache;
20+
use Illuminate\Support\Facades\Schema;
1721
use Illuminate\Support\HigherOrderCollectionProxy;
1822
use Mockery;
1923
use RuntimeException;
@@ -855,4 +859,106 @@ public function test_get_allows_pass_to_higher_order_proxy(): void
855859

856860
static::assertInstanceOf(HigherOrderCollectionProxy::class, $user->settings->map);
857861
}
862+
863+
public function test_deletes_settings_when_model_deletes_itself(): void
864+
{
865+
DummyModel::find(1)->delete();
866+
867+
$this->assertDatabaseMissing('user_settings', ['settable_id' => 1]);
868+
}
869+
870+
public function test_deletes_settings_when_model_force_deletes_itself(): void
871+
{
872+
Metadata::forceCreate([
873+
'name' => 'bar',
874+
'type' => 'string',
875+
'default' => 'quz',
876+
'bag' => 'test-users',
877+
'group' => 'default',
878+
]);
879+
880+
Schema::table('users', function (Blueprint $table) {
881+
$table->softDeletes();
882+
});
883+
884+
$user = new class extends Model {
885+
use SoftDeletes;
886+
use HasConfig;
887+
protected $table = 'users';
888+
protected $attributes = [
889+
'name' => 'john',
890+
'email' => 'email@email.com',
891+
'password' => '123456'
892+
];
893+
};
894+
895+
$user->save();
896+
897+
$this->assertDatabaseHas('user_settings', ['settable_id' => 2]);
898+
899+
$user->delete();
900+
901+
$this->assertDatabaseHas('user_settings', ['settable_id' => 2]);
902+
903+
$user->restore();
904+
905+
$this->assertDatabaseHas('user_settings', ['settable_id' => 2]);
906+
907+
$user->forceDelete();
908+
909+
$this->assertDatabaseMissing('user_settings', ['settable_id' => 2]);
910+
}
911+
912+
public function test_allows_for_removing_bags_filter_on_query(): void
913+
{
914+
Setting::forceCreate([
915+
'value' => 'quz',
916+
'settable_id' => 1,
917+
'settable_type' => (new DummyModel())->getMorphClass(),
918+
'metadata_id' => Metadata::forceCreate([
919+
'name' => 'bar',
920+
'type' => 'string',
921+
'default' => 'quz',
922+
'bag' => 'test-users',
923+
'group' => 'default',
924+
])->getKey()
925+
]);
926+
927+
$user = DummyModel::find(1);
928+
929+
$settings = $user->settings()->withoutGlobalScope(FilterBags::class)->get();
930+
931+
static::assertCount(2, $settings);
932+
}
933+
934+
public function test_allows_to_disable_bag_filter(): void
935+
{
936+
Metadata::forceCreate([
937+
'name' => 'bar',
938+
'type' => 'string',
939+
'default' => 'quz',
940+
'bag' => 'test-users',
941+
'group' => 'default',
942+
]);
943+
944+
$user = new class extends Model {
945+
use SoftDeletes;
946+
use HasConfig;
947+
protected $table = 'users';
948+
protected $attributes = [
949+
'name' => 'john',
950+
'email' => 'email@email.com',
951+
'password' => '123456'
952+
];
953+
public function filterBags() {
954+
return [];
955+
}
956+
};
957+
958+
$user->save();
959+
960+
$settings = $user->settings()->get();
961+
962+
static::assertCount(2, $settings);
963+
}
858964
}

0 commit comments

Comments
 (0)