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

Commit

Permalink
Merge pull request #3 from DarkGhostHunter/master
Browse files Browse the repository at this point in the history
Better filtering scopes, auto-delete
  • Loading branch information
DarkGhostHunter authored Jul 19, 2021
2 parents 0a8c3f8 + c2abe85 commit 6c79c61
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 40 deletions.
44 changes: 29 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,21 +131,7 @@ Setting::name('notify_email')->boolean()->default(true)->bag('notifications');
Setting::name('notify_sms')->boolean()->default(false)->bag('notifications');
```

Later, in your model, you can filter the bags you want to work with using `filterBags()`:

```php
/**
* Returns the bags this model uses for settings.
*
* @return array|string
*/
public function filterBags(): array|string
{
return ['style', 'notifications'];
}
```

> 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**.
Later, in your model, you can filter the bags you want to work with using [`filterBags()`](#setting-bags) in your model.

## Migrating settings

Expand Down Expand Up @@ -461,6 +447,34 @@ public function getSettingsBags(): array|string
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.

> **All** settings are created for all models with `HasConfig` trait, regardless of the bags used by the model.
#### Disabling the bag filter scope

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.

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.

```php
use DarkGhostHunter\Laraconfig\Eloquent\Scopes\FilterBags;

$allSettings = $user->settings()->withoutGlobalScope(FilterBags::class)->get();
```

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.

```php
/**
* Returns the bags this model uses for settings.
*
* @return array|string
*/
public function filterBags(): array|string|null
{
return null;
}
```

## Cache

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.
Expand Down
34 changes: 34 additions & 0 deletions src/Eloquent/Scopes/FilterBags.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace DarkGhostHunter\Laraconfig\Eloquent\Scopes;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;

class FilterBags implements Scope
{
/**
* FilterBags constructor.
*
* @param array $bags
*/
public function __construct(protected array $bags)
{
}

/**
* Apply the scope to a given Eloquent query builder.
*
* @param \Illuminate\Database\Eloquent\Builder $builder
* @param \Illuminate\Database\Eloquent\Model $model
*
* @return void
*/
public function apply(Builder $builder, Model $model): void
{
$builder->whereHas('metadata', function (Builder $query): void {
$query->whereIn('bag', $this->bags);
});
}
}
25 changes: 12 additions & 13 deletions src/Eloquent/Scopes/WhereConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class WhereConfig implements Scope
*
* @return void
*/
public function apply(Builder $builder, Model $model)
public function apply(Builder $builder, Model $model): void
{
//
}
Expand All @@ -28,7 +28,7 @@ public function apply(Builder $builder, Model $model)
*
* @return void
*/
public function extend(Builder $builder)
public function extend(Builder $builder): void
{
$builder->macro('whereConfig', [static::class, 'whereConfig']);
}
Expand All @@ -53,17 +53,16 @@ public static function whereConfig(Builder $builder, string|array $name, mixed $
}

return $builder->whereHas('settings', static function (Builder $builder) use ($name, $value): void {
if (!in_array(AddMetadata::class, $builder->removedScopes(), true)) {
$builder->withoutGlobalScope(AddMetadata::class);
}

$builder->where(static function (Builder $builder) use ($name, $value): void {
$builder
->where('value', $value)
->whereHas('metadata', static function (Builder $builder) use ($name): void {
$builder->where('name', $name);
});
$builder
->withoutGlobalScope(AddMetadata::class)
->where(
static function (Builder $builder) use ($name, $value): void {
$builder
->where('value', $value)
->whereHas('metadata', static function (Builder $builder) use ($name): void {
$builder->where('name', $name);
});
});
});
}
}
}
9 changes: 9 additions & 0 deletions src/HasConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,14 @@ static function (Model $model): void {
}
}
);

static::deleting(
static function (Model $model): void {
// Bye settings on delete, or force-delete.
if (!method_exists($model, 'isForceDeleting') || $model->isForceDeleting()) {
$model->settings()->withoutGlobalScopes()->delete();
}
}
);
}
}
22 changes: 12 additions & 10 deletions src/MorphManySettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,21 +73,23 @@ public function windUp(Model $parent): void
}

/**
* Set the base constraints on the relation query.
* Get the relationship query.
*
* @return void
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Builder $parentQuery
* @param array|mixed $columns
* @return \Illuminate\Database\Eloquent\Builder
*/
public function addConstraints(): void
public function getRelationExistenceQuery(Builder $query, Builder $parentQuery, $columns = ['*']): Builder
{
parent::addConstraints();

// Filter the bags of the model for this relationship.
$this->getQuery()->whereHas('metadata', function (Builder $query): void {
$query->whereIn('bag', $this->bags);
});
// We will add the global scope only when checking for existence.
// This should appear on SELECT instead of other queries.
return parent::getRelationExistenceQuery($query, $parentQuery, $columns)
->when($this->bags, static function (Builder $builder, $value): void {
$builder->withGlobalScope(Eloquent\Scopes\FilterBags::class, new Eloquent\Scopes\FilterBags($value));
});
}


/**
* Generates the key for the model to save into the cache.
*
Expand Down
3 changes: 1 addition & 2 deletions tests/Eloquent/Scopes/FilterBySettingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,7 @@ public function test_filters_user_by_config(): void

static::assertCount(1, $users);

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

static::assertNull($users);

Expand Down
106 changes: 106 additions & 0 deletions tests/HasConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Tests;

use DarkGhostHunter\Laraconfig\Eloquent\Metadata;
use DarkGhostHunter\Laraconfig\Eloquent\Scopes\FilterBags;
use DarkGhostHunter\Laraconfig\Eloquent\Setting;
use DarkGhostHunter\Laraconfig\HasConfig;
use DarkGhostHunter\Laraconfig\SettingsCollection;
Expand All @@ -12,8 +13,11 @@
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\HigherOrderCollectionProxy;
use Mockery;
use RuntimeException;
Expand Down Expand Up @@ -855,4 +859,106 @@ public function test_get_allows_pass_to_higher_order_proxy(): void

static::assertInstanceOf(HigherOrderCollectionProxy::class, $user->settings->map);
}

public function test_deletes_settings_when_model_deletes_itself(): void
{
DummyModel::find(1)->delete();

$this->assertDatabaseMissing('user_settings', ['settable_id' => 1]);
}

public function test_deletes_settings_when_model_force_deletes_itself(): void
{
Metadata::forceCreate([
'name' => 'bar',
'type' => 'string',
'default' => 'quz',
'bag' => 'test-users',
'group' => 'default',
]);

Schema::table('users', function (Blueprint $table) {
$table->softDeletes();
});

$user = new class extends Model {
use SoftDeletes;
use HasConfig;
protected $table = 'users';
protected $attributes = [
'name' => 'john',
'email' => '[email protected]',
'password' => '123456'
];
};

$user->save();

$this->assertDatabaseHas('user_settings', ['settable_id' => 2]);

$user->delete();

$this->assertDatabaseHas('user_settings', ['settable_id' => 2]);

$user->restore();

$this->assertDatabaseHas('user_settings', ['settable_id' => 2]);

$user->forceDelete();

$this->assertDatabaseMissing('user_settings', ['settable_id' => 2]);
}

public function test_allows_for_removing_bags_filter_on_query(): void
{
Setting::forceCreate([
'value' => 'quz',
'settable_id' => 1,
'settable_type' => (new DummyModel())->getMorphClass(),
'metadata_id' => Metadata::forceCreate([
'name' => 'bar',
'type' => 'string',
'default' => 'quz',
'bag' => 'test-users',
'group' => 'default',
])->getKey()
]);

$user = DummyModel::find(1);

$settings = $user->settings()->withoutGlobalScope(FilterBags::class)->get();

static::assertCount(2, $settings);
}

public function test_allows_to_disable_bag_filter(): void
{
Metadata::forceCreate([
'name' => 'bar',
'type' => 'string',
'default' => 'quz',
'bag' => 'test-users',
'group' => 'default',
]);

$user = new class extends Model {
use SoftDeletes;
use HasConfig;
protected $table = 'users';
protected $attributes = [
'name' => 'john',
'email' => '[email protected]',
'password' => '123456'
];
public function filterBags() {
return [];
}
};

$user->save();

$settings = $user->settings()->get();

static::assertCount(2, $settings);
}
}

0 comments on commit 6c79c61

Please sign in to comment.