diff --git a/README.md b/README.md index a4a9e46..1e9a1f7 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,208 @@ composer require biiiiiigmonster/laravel-clearable ``` # Introductions -删除作为数据操作的生命周期最后一环,受到关注度较小,然而在业务中完整数据的关联性往往会因为这些疏忽而被破坏。 -这个包可以很方便的帮您管理这些关联数据删除关系,仅仅只需要简单的定义。 -让我们来尝试一下吧! +`relation` is powerful, it can help us manage complex relationship's data. +Usually, as the last section of the data life cycle, the "delete" behavior receives less attention. +We often neglect the processing of the associated model data while deleting the data itself, the business will also be damaged due to these residual data. + +This package can easily help you manage these related data's deletion relationships, with simple definitions. Let's try it! + +## Usage +For example, `User` model related `Post` model, it's also hoped that the associated `Post` model can be deleted after the `User` model deleted: + +```injectablephp +namespace App\Models; + +use Illuminate\Database\Eloquent\Model; + +class User extends Model +{ + /** + * Get the posts for the user. + * + * @return HasMany + */ + public function posts() + { + return $this->hasMany(Post::class); + } +} +``` +To accomplish this, you may add the `BiiiiiigMonster\Clears\Concerns\HasClears` trait to the models you would like to auto-clear. +After adding one of the traits to the model, add the attribute name to the `clears` property of your model. +```injectablephp +namespace App\Models; + +use Illuminate\Database\Eloquent\Model; +use BiiiiiigMonster\Clears\Concerns\HasClears; + +class User extends Model +{ + use HasClears; + + /** + * The relationships that will be auto-clear when deleted. + * + * @var array + */ + protected $clears = ['posts']; +} +``` +Once the relationship has been added to the `clears` list, it will be auto-clear when deleted. + +## Clear Configuration +### Custom Clear +Sometimes you may occasionally need to define your own clear's logic, You may accomplish this by defining a class that implements the `InvokableClear` interface. + +To generate a new clear object, you may use the `make:clear` Artisan command. we will place the new rule in the `app/Clears` directory. If this directory does not exist, We will create it when you execute the Artisan command to create your clear: +```bash +php artisan make:clear PostWithoutReleasedClear +``` + +Once the clear has been created, we are ready to define its behavior. A clear object contains a single method: `__invoke`. +This method will determine whether the relation data is cleared. + +```injectablephp +status != 'published'; + } +} +``` + +Once you have defined a custom clear type, you may attach it to a model attribute using its class name: +```injectablephp +namespace App\Models; + +use Illuminate\Database\Eloquent\Model; +use BiiiiiigMonster\Clears\Concerns\HasClears; +use App\Clears\PostWithoutReleasedClear; + +class User extends Model +{ + use HasClears; + + /** + * The relationships that will be auto-clear when deleted. + * + * @var array + */ + protected $clears = [ + 'posts' => PostWithoutReleasedClear::class + ]; +} +``` + +### Use Queue +When the relation data that we need to clear may be very large, it is a very good strategy to use `queue` to execute it. + +Making it work is also simple, add the attribute name to the `clearQueue` property of your model. +```injectablephp +namespace App\Models; + +use Illuminate\Database\Eloquent\Model; +use BiiiiiigMonster\Clears\Concerns\HasClears; +use App\Clears\PostWithoutReleasedClear; + +class User extends Model +{ + use HasClears; + + /** + * The clearable that will be dispatch on this name queue. + * + * @var bool|string + */ + protected $clearQueue = true; +} +``` +Once the `clearQueue` has been declared, the `posts`'s clear behavior will be executed using the queue, reducing the serial pressure. +> Tips: You can also set it as a string `protected $clearQueue = 'queue-name';`, which will run in the named queue. + +### Clearing At Runtime +At runtime, you may instruct a model instance to using the `clear` or `setClears` method just like +[`append`](https://laravel.com/docs/9.x/eloquent-serialization#appending-at-run-time): +```injectablephp +$user->clear(['posts' => PostWithoutReleasedClear::class])->delete(); + +$user->setClears(['posts' => PostWithoutReleasedClear::class])->delete(); +``` + +## PHP8 Attribute +The 'Attribute' feature is added to php8, which provides another form of configuration, and clear is ready for it. + +It is very simple to use `Attribute`, we have defined an attribute of `#[Clear]`, just only need to relate the method. +```injectablephp +namespace App\Models; + +use BiiiiiigMonster\Clears\Attributes\Clear; +use BiiiiiigMonster\Clears\Concerns\HasClears; +use Illuminate\Database\Eloquent\Model; + +class User extends Model +{ + use HasClears; + + /** + * Get the posts for the user. + * + * @return HasMany + */ + #[Clear] + public function posts() + { + return $this->hasMany(Post::class); + } +} +``` + +Similarly, you can set `Custom Clear` in `#[Clear]`, or even configure `clearQueue` separately: +```injectablephp +#[Clear(PostWithoutReleasedClear::class, 'queue-name')] +public function posts() +{ + return $this->hasMany(Post::class); +} +``` +> Tips:`#[Clear]` will overwrite the corresponding configuration in `protected $clears`. + +## Support Relationship +Data's "deletion" is generally a sensitive operation, we do not want important data to declare `clear` by any relationships. Therefore, we don't support `clear` in the `BelongsTo` relationships. + +Support-List: +- HasOne +- HasOneThrough +- HasMany +- HasManyThrough +- MorphMany +- MorphOne +- BelongsToMany +- MorphToMany +> Tips:When the `BelongsToMany` and `MorphToMany` relationship declare is `clear`, deleted is the pivot model data. + +Not-Support-List: +- BelongsTo +- MorphTo + +# Test +```bash +composer test +``` # License [MIT](./LICENSE) diff --git a/src/Attributes/Clear.php b/src/Attributes/Clear.php index cb4f951..c7d5e59 100644 --- a/src/Attributes/Clear.php +++ b/src/Attributes/Clear.php @@ -10,12 +10,12 @@ class Clear /** * Clear constructor. * - * @param string|null $clearsAttributesClassName - * @param string|null $clearQueue + * @param string|null $invokableClearClassName + * @param string|bool|null $clearQueue */ public function __construct( - public ?string $clearsAttributesClassName = null, - public ?string $clearQueue = null + public ?string $invokableClearClassName = null, + public string|bool|null $clearQueue = null, ) { } } diff --git a/src/ClearManager.php b/src/ClearManager.php index e88c1f3..20ffdab 100644 --- a/src/ClearManager.php +++ b/src/ClearManager.php @@ -4,11 +4,9 @@ use BiiiiiigMonster\Clearable\Attributes\Clear; use BiiiiiigMonster\Clearable\Jobs\ClearsJob; - use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; use ReflectionClass; -use ReflectionException; use ReflectionMethod; class ClearManager @@ -36,18 +34,26 @@ public static function make(Model $model): static /** * ClearManager handle. - * @throws ReflectionException */ public function handle(): void { $clears = $this->parse(); foreach ($clears as $relationName => $clear) { - $param = [$this->model::class, $this->model->getOriginal(), $relationName, $relations = Collection::wrap($this->model->$relationName), $clear->clearsAttributesClassName]; + $payload = [ + $this->model::class, + $this->model->getOriginal(), + $relationName, + $relations = Collection::wrap($this->model->$relationName), + $clear->invokableClearClassName + ]; + if ($relations->isNotEmpty()) { - $clear->clearQueue - ? ClearsJob::dispatch(...$param)->onQueue($clear->clearQueue) - : ClearsJob::dispatchSync(...$param); + match ($clear->clearQueue) { + null,false => ClearsJob::dispatchSync(...$payload), + true,'' => ClearsJob::dispatch(...$payload), + default => ClearsJob::dispatch(...$payload)->onQueue($clear->clearQueue) + }; } } } @@ -62,13 +68,13 @@ protected function parse(): array $clears = []; // from clears array - foreach ($this->model->getClears() as $relationName => $clearsAttributesClassName) { + foreach ($this->model->getClears() as $relationName => $invokableClearClassName) { if (is_numeric($relationName)) { - $relationName = $clearsAttributesClassName; - $clearsAttributesClassName = null; + $relationName = $invokableClearClassName; + $invokableClearClassName = null; } - $clears[$relationName] = new Clear($clearsAttributesClassName, $this->model->getClearQueue()); + $clears[$relationName] = new Clear($invokableClearClassName, $this->model->getClearQueue()); } // from clear attribute diff --git a/src/ClearsServiceProvider.php b/src/ClearsServiceProvider.php index aa20058..b8f6c6e 100644 --- a/src/ClearsServiceProvider.php +++ b/src/ClearsServiceProvider.php @@ -2,7 +2,7 @@ namespace BiiiiiigMonster\Clearable; -use BiiiiiigMonster\Clearable\Console\ClearsAttributesMakeCommand; +use BiiiiiigMonster\Clearable\Console\InvokableClearMakeCommand; use Illuminate\Support\ServiceProvider; class ClearsServiceProvider extends ServiceProvider @@ -14,6 +14,6 @@ class ClearsServiceProvider extends ServiceProvider */ public function register() { - $this->commands(ClearsAttributesMakeCommand::class); + $this->commands(InvokableClearMakeCommand::class); } } diff --git a/src/Concerns/HasClears.php b/src/Concerns/HasClears.php index 563f96c..b427839 100644 --- a/src/Concerns/HasClears.php +++ b/src/Concerns/HasClears.php @@ -9,7 +9,7 @@ * Trait HasClears * * @property array $clears The relationships that will be auto-cleared when deleted. - * @property ?string $clearQueue The clearable that will be dispatch on this name queue. + * @property string|bool|null $clearQueue The clearable that will be dispatch on this name queue. * @package BiiiiiigMonster\Clears\Concerns */ trait HasClears @@ -64,9 +64,9 @@ public function clear(array|string|null $clears): static /** * Get clearQueue. * - * @return ?string + * @return string|bool|null */ - public function getClearQueue(): ?string + public function getClearQueue(): string|bool|null { return $this->clearQueue; } @@ -74,10 +74,10 @@ public function getClearQueue(): ?string /** * Set the clearQueue attributes for the model. * - * @param string $clearQueue + * @param string|bool|null $clearQueue * @return $this */ - public function setClearQueue(string $clearQueue): static + public function setClearQueue(string|bool|null $clearQueue): static { $this->clearQueue = $clearQueue; diff --git a/src/Console/ClearsAttributesMakeCommand.php b/src/Console/InvokableClearMakeCommand.php similarity index 91% rename from src/Console/ClearsAttributesMakeCommand.php rename to src/Console/InvokableClearMakeCommand.php index 5b5eff4..b1ca6cd 100644 --- a/src/Console/ClearsAttributesMakeCommand.php +++ b/src/Console/InvokableClearMakeCommand.php @@ -4,7 +4,7 @@ use Illuminate\Console\GeneratorCommand; -class ClearsAttributesMakeCommand extends GeneratorCommand +class InvokableClearMakeCommand extends GeneratorCommand { /** * The console command name. @@ -43,7 +43,7 @@ class ClearsAttributesMakeCommand extends GeneratorCommand */ protected function getStub() { - $relativePath = '/stubs/clears-attributes.stub'; + $relativePath = '/stubs/invoke-clear.stub'; return file_exists($customPath = $this->laravel->basePath(trim($relativePath, '/'))) ? $customPath diff --git a/src/Console/stubs/clears-attributes.stub b/src/Console/stubs/invoke-clear.stub similarity index 59% rename from src/Console/stubs/clears-attributes.stub rename to src/Console/stubs/invoke-clear.stub index ea90523..37647c6 100644 --- a/src/Console/stubs/clears-attributes.stub +++ b/src/Console/stubs/invoke-clear.stub @@ -2,10 +2,10 @@ namespace {{ namespace }}; -use BiiiiiigMonster\Clearable\Contracts\ClearsAttributes; +use BiiiiiigMonster\Clearable\Contracts\InvokableClear; use Illuminate\Database\Eloquent\Model; -class {{ class }} implements ClearsAttributes +class {{ class }} implements InvokableClear { /** * Decide if the clearable cleared. @@ -13,7 +13,7 @@ class {{ class }} implements ClearsAttributes * @param Model $clear * @return bool */ - public function abandon($clear): bool + public function __invoke($clear): bool { // } diff --git a/src/Contracts/ClearsAttributes.php b/src/Contracts/InvokableClear.php similarity index 75% rename from src/Contracts/ClearsAttributes.php rename to src/Contracts/InvokableClear.php index c0f6cc5..6ef74dc 100644 --- a/src/Contracts/ClearsAttributes.php +++ b/src/Contracts/InvokableClear.php @@ -4,7 +4,7 @@ use Illuminate\Database\Eloquent\Model; -interface ClearsAttributes +interface InvokableClear { /** * Decide if the clearable cleared. @@ -12,5 +12,5 @@ interface ClearsAttributes * @param Model $clear * @return bool */ - public function abandon($clear): bool; + public function __invoke($clear): bool; } diff --git a/src/Jobs/ClearsJob.php b/src/Jobs/ClearsJob.php index 9c213a1..761008f 100644 --- a/src/Jobs/ClearsJob.php +++ b/src/Jobs/ClearsJob.php @@ -29,14 +29,14 @@ class ClearsJob implements ShouldQueue * @param array $original * @param string $relationName * @param Collection $collection - * @param string|null $clearsAttributesClassName + * @param string|null $invokableClearClassName */ public function __construct( protected string $className, protected array $original, protected string $relationName, protected Collection $collection, - protected ?string $clearsAttributesClassName = null, + protected ?string $invokableClearClassName = null, ) { } @@ -51,9 +51,11 @@ public function handle(): void $relation = $model->{$this->relationName}(); // to be cleared model. - $clears = $this->clearsAttributesClassName - ? $this->collection->filter(fn (Model $clear) => (new $this->clearsAttributesClassName())->abandon($clear, $model)) - : $this->collection; + $clears = $this->collection; + if ($this->invokableClearClassName) { + $invoke = new $this->invokableClearClassName(); + $clears = $clears->filter(fn (Model $clear) => $invoke($clear, $model)); + } switch (true) { case $relation instanceof HasOneOrMany: diff --git a/tests/Clears/NormalClear.php b/tests/Clears/NormalClear.php index 7d5f1ee..cd7c610 100644 --- a/tests/Clears/NormalClear.php +++ b/tests/Clears/NormalClear.php @@ -2,11 +2,11 @@ namespace BiiiiiigMonster\Clearable\Tests\Clears; -use BiiiiiigMonster\Clearable\Contracts\ClearsAttributes; +use BiiiiiigMonster\Clearable\Contracts\InvokableClear; -class NormalClear implements ClearsAttributes +class NormalClear implements InvokableClear { - public function abandon($clear): bool + public function __invoke($clear): bool { return true; } diff --git a/tests/Clears/PostVotesOddClear.php b/tests/Clears/PostVotesOddClear.php index 8a5c969..0c99c05 100644 --- a/tests/Clears/PostVotesOddClear.php +++ b/tests/Clears/PostVotesOddClear.php @@ -2,11 +2,11 @@ namespace BiiiiiigMonster\Clearable\Tests\Clears; -use BiiiiiigMonster\Clearable\Contracts\ClearsAttributes; +use BiiiiiigMonster\Clearable\Contracts\InvokableClear; -class PostVotesOddClear implements ClearsAttributes +class PostVotesOddClear implements InvokableClear { - public function abandon($clear): bool + public function __invoke($clear): bool { return $clear->votes % 2; } diff --git a/tests/Clears/RoleExceptSystemTypeClear.php b/tests/Clears/RoleExceptSystemTypeClear.php index eda3e74..f622669 100644 --- a/tests/Clears/RoleExceptSystemTypeClear.php +++ b/tests/Clears/RoleExceptSystemTypeClear.php @@ -2,16 +2,16 @@ namespace BiiiiiigMonster\Clearable\Tests\Clears; -use BiiiiiigMonster\Clearable\Contracts\ClearsAttributes; +use BiiiiiigMonster\Clearable\Contracts\InvokableClear; use BiiiiiigMonster\Clearable\Tests\Models\Role; -class RoleExceptSystemTypeClear implements ClearsAttributes +class RoleExceptSystemTypeClear implements InvokableClear { /** * @param Role $clear * @return bool */ - public function abandon($clear): bool + public function __invoke($clear): bool { return !($clear->name && $clear->pivot->type % 2); } diff --git a/tests/Clears/SupplierClear.php b/tests/Clears/SupplierClear.php index 5ddb9c3..0b0dffb 100644 --- a/tests/Clears/SupplierClear.php +++ b/tests/Clears/SupplierClear.php @@ -2,11 +2,11 @@ namespace BiiiiiigMonster\Clearable\Tests\Clears; -use BiiiiiigMonster\Clearable\Contracts\ClearsAttributes; +use BiiiiiigMonster\Clearable\Contracts\InvokableClear; -class SupplierClear implements ClearsAttributes +class SupplierClear implements InvokableClear { - public function abandon($clear): bool + public function __invoke($clear): bool { return true; } diff --git a/tests/Features/ClearQueueTest.php b/tests/Features/ClearQueueTest.php index eeca0af..efd0777 100644 --- a/tests/Features/ClearQueueTest.php +++ b/tests/Features/ClearQueueTest.php @@ -4,7 +4,16 @@ use BiiiiiigMonster\Clearable\Tests\Models\User; use Illuminate\Support\Facades\Queue; -test('clear queue test', function () { +test('Clear use queue test', function () { + Queue::fake(); + + $user = User::has('posts', '>=', 2)->with('posts')->first(); + $user->clear('posts')->setClearQueue(true)->delete(); + + Queue::assertPushed(ClearsJob::class); +}); + +test('Clear named queue test', function () { Queue::fake(); $user = User::has('posts', '>=', 2)->with('posts')->first();