Skip to content

Commit 3c8a460

Browse files
author
riccardodallavia
committed
wip
1 parent 3860900 commit 3c8a460

File tree

9 files changed

+218
-38
lines changed

9 files changed

+218
-38
lines changed

README.md

Lines changed: 161 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
[![GitHub Code Style Action Status](https://img.shields.io/github/workflow/status/maize-tech/laravel-prunable-fields/Check%20&%20fix%20styling?label=code%20style)](https://github.com/maize-tech/laravel-prunable-fields/actions?query=workflow%3A"Check+%26+fix+styling"+branch%3Amain)
77
[![Total Downloads](https://img.shields.io/packagist/dt/maize-tech/laravel-prunable-fields.svg?style=flat-square)](https://packagist.org/packages/maize-tech/laravel-prunable-fields)
88

9-
This package aims to reproduce the Prunable core feature of Laravel to clean column values instead of entire rows.
9+
This package allows you to clean model fields with an easy command.
10+
The feature is highly inspired by Laravel's Prunable core feature, allowing you to easily adapt all your existing models.
1011

1112
## Installation
1213

@@ -16,11 +17,167 @@ You can install the package via composer:
1617
composer require maize-tech/laravel-prunable-fields
1718
```
1819

19-
## Usage
20+
You can publish the config file with:
21+
22+
```bash
23+
php artisan vendor:publish --tag="prunable-fields-config"
24+
```
25+
26+
This is the contents of the published config file:
2027

2128
```php
22-
$prunableFields = new Maize\PrunableFields();
23-
echo $prunableFields->echoPhrase('Hello, Maize!');
29+
return [
30+
/*
31+
|--------------------------------------------------------------------------
32+
| Prunable models
33+
|--------------------------------------------------------------------------
34+
|
35+
| Here you may specify the list of fully qualified class names of prunable
36+
| models.
37+
| All models listed here will be pruned when executing the model:prune-fields
38+
| command without passing the --model option.
39+
|
40+
*/
41+
42+
'models' => [
43+
// \App\Models\User::class,
44+
],
45+
46+
];
47+
```
48+
49+
## Usage
50+
51+
### Prunable models
52+
53+
To use the package, simply add the `Maize\PrunableFields\PrunableFields` trait to all models you want to clean.
54+
55+
Once done, you can define the list of attributes who should be cleaned up by implementing the `$prunable` class property.
56+
The array key should be the attribute name, whereas the array value should be the value you want the attribute to be updated to.
57+
58+
After that, implement the `prunableFields` method which should return an Eloquent query builder that resolves the models that should be cleaned up.
59+
60+
If needed, you can also override the `pruningFields` and `prunedFields` methods (which are empty by default) to execute some actions before and after the model is being updated.
61+
62+
Here's an example model including the `PrunableFields` trait:
63+
64+
``` php
65+
<?php
66+
67+
namespace App\Models;
68+
69+
use Illuminate\Database\Eloquent\Model;
70+
use Maize\PrunableFields\PrunableFields;
71+
72+
class User extends Model
73+
{
74+
use PrunableFields;
75+
76+
protected $fillable = [
77+
'first_name',
78+
'last_name',
79+
'email',
80+
];
81+
82+
protected $prunable = [
83+
'first_name' => null,
84+
'last_name' => null,
85+
];
86+
87+
public function prunableFields(): Builder
88+
{
89+
return static::query()
90+
->whereDate('created_at', '<=', now()->subDay());
91+
}
92+
93+
protected function pruningFields(): void
94+
{
95+
logger()->warning("User {$this->getKey()} is being pruned");
96+
}
97+
98+
protected function prunedFields()
99+
{
100+
logger()->warning("User {$this->getKey()} has been pruned");
101+
}
102+
}
103+
```
104+
105+
All you have to do now is including the model's class name in `models` attribute under `config/prunable-fields.php`:
106+
107+
``` php
108+
'models' => [
109+
\App\Models\User::class,
110+
],
111+
```
112+
113+
That's it! From now on, the `model:prune-fields` command will do all the magic.
114+
115+
In our example, all users created before the current day will be updated with a null value for both `first_name` and `last_name` attributes.
116+
117+
### Mass prunable models
118+
119+
When using the `MassPrunableFields` trait all models will be updated with a raw database query.
120+
121+
In this case, `pruningFields` and `prunedFields` methods will not be invoked, and models will not fire the `updating` or `updated` events.
122+
123+
This way there is no need to retrieve all models before updating them, making the command execution way faster when working with a large number of entries.
124+
125+
``` php
126+
<?php
127+
128+
namespace App\Models;
129+
130+
use Illuminate\Database\Eloquent\Model;
131+
use Maize\PrunableFields\MassPrunableFields;
132+
133+
class User extends Model
134+
{
135+
use MassPrunableFields;
136+
137+
protected $fillable = [
138+
'first_name',
139+
'last_name',
140+
'email',
141+
];
142+
143+
protected $prunable = [
144+
'first_name' => null,
145+
'last_name' => null,
146+
];
147+
148+
public function prunableFields(): Builder
149+
{
150+
return static::query()
151+
->whereDate('created_at', '<=', now()->subDay());
152+
}
153+
}
154+
```
155+
156+
### Scheduling models cleanup
157+
158+
The package is pretty useful when you automatize the execution of the `model:prune-fields` command, using Laravel's scheduling.
159+
All you have to do is add the following instruction to the `schedule` method of the console kernel (usually located under the `App\Console` directory):
160+
161+
``` php
162+
$schedule->command('model:prune-fields')->daily();
163+
```
164+
165+
By default, when executing the `model:prune-fields` command the package will take all prunable models specified in `models` attribute under `config/prunable-fields.php`.
166+
167+
If you want to restrict the model list you want to automatically clean up, you can pass the `--model` option to the command:
168+
169+
``` php
170+
$schedule->command('model:prune-fields', [
171+
'--model' => [User::class],
172+
])->daily();
173+
```
174+
175+
Alternatively, you can clean up all models listed in your config and exclude some of them with the `--execpt` command option:
176+
177+
``` php
178+
$schedule->command('model:prune-fields', [
179+
'--except' => [PleaseLetMeCleanThisModelByHandsThankYou::class],
180+
])->daily();
24181
```
25182

26183
## Testing

config/prunable-fields.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
return [
4+
/*
5+
|--------------------------------------------------------------------------
6+
| Prunable models
7+
|--------------------------------------------------------------------------
8+
|
9+
| Here you may specify the list of fully qualified class names of prunable
10+
| models.
11+
| All models listed here will be pruned when executing the model:prune-fields
12+
| command without passing the --model option.
13+
|
14+
*/
15+
16+
'models' => [
17+
// \App\Models\User::class,
18+
],
19+
20+
];

phpstan.neon.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ parameters:
55
level: 4
66
paths:
77
- src
8+
- config
89
- database
910
tmpDir: build/phpstan
1011
checkOctaneCompatibility: true

src/Commands/PruneFieldsCommand.php

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66
use Illuminate\Database\Eloquent\SoftDeletes;
77
use Illuminate\Support\Collection;
88
use Illuminate\Support\Facades\Event;
9-
use Illuminate\Support\Str;
109
use Maize\PrunableFields\Events\ModelsFieldsPruned;
1110
use Maize\PrunableFields\MassPrunableFields;
1211
use Maize\PrunableFields\PrunableFields;
13-
use Symfony\Component\Finder\Finder;
12+
use Maize\PrunableFields\Support\Config;
1413

1514
class PruneFieldsCommand extends Command
1615
{
@@ -71,29 +70,13 @@ protected function models(): Collection
7170

7271
$except = $this->option('except');
7372

74-
return collect((new Finder())->in($this->getDefaultPath())->files()->name('*.php'))
75-
->map(function ($model) {
76-
$namespace = $this->laravel->getNamespace();
77-
78-
return $namespace.str_replace(
79-
['/', '.php'],
80-
['\\', ''],
81-
Str::after($model->getRealPath(), realpath(app_path()).DIRECTORY_SEPARATOR)
82-
);
83-
})
84-
->when(! empty($except), function ($models) use ($except) {
85-
return $models->reject(fn ($model) => in_array($model, $except));
86-
})
87-
->filter(fn ($model) => $this->isPrunable($model))
73+
return collect(Config::getPrunableModels())
74+
->reject(fn ($model) => in_array($model, $except))
8875
->filter(fn ($model) => class_exists($model))
76+
->filter(fn ($model) => $this->isPrunable($model))
8977
->values();
9078
}
9179

92-
protected function getDefaultPath(): string
93-
{
94-
return app_path('Models');
95-
}
96-
9780
protected function isPrunable(string $model): bool
9881
{
9982
$uses = class_uses_recursive($model);

src/MassPrunableFields.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
use Illuminate\Database\Eloquent\Builder;
66
use Illuminate\Database\Eloquent\Collection;
77
use Illuminate\Database\Eloquent\SoftDeletes;
8-
use LogicException;
98
use Maize\PrunableFields\Events\ModelsFieldsPruned;
109

1110
trait MassPrunableFields
1211
{
12+
abstract public function prunableFields(): Builder;
13+
1314
public function prunable(): array
1415
{
1516
return property_exists($this, 'prunable')
@@ -37,9 +38,4 @@ public function pruneAllFields(int $chunkSize = 1000): int
3738

3839
return $total;
3940
}
40-
41-
public function prunableFields(): Builder
42-
{
43-
throw new LogicException('Please implement the prunable method on your model.');
44-
}
4541
}

src/PrunableFields.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@
44

55
use Illuminate\Database\Eloquent\Builder;
66
use Illuminate\Database\Eloquent\SoftDeletes;
7-
use LogicException;
87
use Maize\PrunableFields\Events\ModelsFieldsPruned;
98

109
trait PrunableFields
1110
{
11+
abstract public function prunableFields(): Builder;
12+
1213
public function prunable(): array
1314
{
1415
return property_exists($this, 'prunable')
@@ -37,20 +38,24 @@ public function pruneAllFields(int $chunkSize = 1000): int
3738
return $total;
3839
}
3940

40-
public function prunableFields(): Builder
41-
{
42-
throw new LogicException('Please implement the prunable method on your model.');
43-
}
44-
4541
public function pruneFields(): bool
4642
{
4743
$this->pruningFields();
4844

49-
return $this->update($this->prunable());
45+
$total = $this->update($this->prunable());
46+
47+
$this->prunedFields();
48+
49+
return $total;
5050
}
5151

5252
protected function pruningFields(): void
5353
{
5454
//
5555
}
56+
57+
protected function prunedFields()
58+
{
59+
//
60+
}
5661
}

src/PrunableFieldsServiceProvider.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public function configurePackage(Package $package): void
1212
{
1313
$package
1414
->name('laravel-prunable-fields')
15+
->hasConfigFile()
1516
->hasCommand(PruneFieldsCommand::class);
1617
}
1718
}

src/Support/Config.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace Maize\PrunableFields\Support;
4+
5+
class Config
6+
{
7+
public static function getPrunableModels(): array
8+
{
9+
return config('prunable-fields.models', []);
10+
}
11+
}

tests/PruneFieldsCommandTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@
2020
'--pretend' => true,
2121
])->expectsOutput("1 [{$model->getMorphClass()}] records will be pruned.");
2222
})->with('user_with_prunable_fields');
23+
24+
test('should print no models found with empty list', function () {
25+
pruneFields([
26+
//
27+
])->expectsOutput("No prunable models found.");
28+
});

0 commit comments

Comments
 (0)