1 | <?php |
||||
2 | |||||
3 | namespace Spinen\GarbageMan\Commands; |
||||
4 | |||||
5 | use Carbon\Carbon; |
||||
6 | use Illuminate\Console\Command; |
||||
7 | use Illuminate\Contracts\Events\Dispatcher; |
||||
8 | use Illuminate\Database\Eloquent\Builder; |
||||
9 | use Illuminate\Database\Eloquent\Model; |
||||
10 | use Psr\Log\LoggerInterface as Log; |
||||
11 | |||||
12 | /** |
||||
13 | * Class PurgeCommand |
||||
14 | * |
||||
15 | * @package Spinen\GarbageMan\Commands |
||||
16 | */ |
||||
17 | class PurgeCommand extends Command |
||||
18 | { |
||||
19 | /** |
||||
20 | * The name and signature of the console command. |
||||
21 | * |
||||
22 | * @var string |
||||
23 | */ |
||||
24 | protected $signature = 'garbageman:purge'; |
||||
25 | |||||
26 | /** |
||||
27 | * The console command description. |
||||
28 | * |
||||
29 | * @var string |
||||
30 | */ |
||||
31 | protected $description = 'Purge the soft deleted records based on the age in the configuration file.'; |
||||
32 | |||||
33 | /** |
||||
34 | * The configured level to log information. |
||||
35 | * |
||||
36 | * These values are used as the default in case there are not any configured. |
||||
37 | * |
||||
38 | * @var array |
||||
39 | */ |
||||
40 | protected $logging_level = [ |
||||
41 | 'console' => 6, |
||||
42 | 'log' => 6, |
||||
43 | ]; |
||||
44 | |||||
45 | /** |
||||
46 | * Dispatcher instance. |
||||
47 | * |
||||
48 | * @var Dispatcher |
||||
49 | */ |
||||
50 | protected $dispatcher; |
||||
51 | |||||
52 | /** |
||||
53 | * Dispatch events when purging? |
||||
54 | * |
||||
55 | * This value is used as the default in case there it is not configured. |
||||
56 | * |
||||
57 | * @var bool |
||||
58 | */ |
||||
59 | protected $dispatch_purge_events = false; |
||||
60 | |||||
61 | /** |
||||
62 | * Logging instance. |
||||
63 | * |
||||
64 | * @var Log |
||||
65 | */ |
||||
66 | protected $log; |
||||
67 | |||||
68 | /** |
||||
69 | * Log levels to know the hierarchy. |
||||
70 | * |
||||
71 | * @var array |
||||
72 | */ |
||||
73 | protected $log_levels = [ |
||||
74 | 'alert' => 1, |
||||
75 | 'critical' => 2, |
||||
76 | 'debug' => 7, |
||||
77 | 'emergency' => 0, |
||||
78 | 'error' => 3, |
||||
79 | 'info' => 6, |
||||
80 | 'notice' => 5, |
||||
81 | 'warning' => 4, |
||||
82 | ]; |
||||
83 | |||||
84 | /** |
||||
85 | * Lock in the time that the command was called to make sure that we use that as the point of reference. |
||||
86 | * |
||||
87 | * @var Carbon |
||||
88 | */ |
||||
89 | protected $now; |
||||
90 | |||||
91 | /** |
||||
92 | * Create a new command instance. |
||||
93 | * |
||||
94 | * @param Carbon $carbon |
||||
95 | * @param Dispatcher $dispatcher |
||||
96 | * @param Log $log |
||||
97 | */ |
||||
98 | 8 | public function __construct(Carbon $carbon, Dispatcher $dispatcher, Log $log) |
|||
99 | { |
||||
100 | 8 | parent::__construct(); |
|||
101 | |||||
102 | 8 | $this->now = $carbon->now(); |
|||
103 | 8 | $this->dispatcher = $dispatcher; |
|||
104 | 8 | $this->log = $log; |
|||
105 | 8 | } |
|||
106 | |||||
107 | /** |
||||
108 | * Dispatch the given event for the record being purged. |
||||
109 | * |
||||
110 | * @param string $event |
||||
111 | * @param string $model_name |
||||
112 | * @param Model $model |
||||
113 | * @param bool|null $halt |
||||
114 | * |
||||
115 | * @return mixed |
||||
116 | */ |
||||
117 | 1 | protected function dispatchPurgeEvent($event, $model_name, Model $model, $halt = true) |
|||
118 | { |
||||
119 | 1 | $event = "garbageman.{$event}: " . $model_name; |
|||
120 | |||||
121 | 1 | $method = $halt ? 'until' : 'dispatch'; |
|||
122 | |||||
123 | 1 | $this->recordMessage(sprintf("Dispatching event [%s] with method [%s]", $event, $method), 'debug'); |
|||
124 | |||||
125 | 1 | return $this->dispatcher->{$method}($event, $model); |
|||
126 | } |
||||
127 | |||||
128 | /** |
||||
129 | * Execute the console command. |
||||
130 | * |
||||
131 | * @return void |
||||
132 | */ |
||||
133 | 7 | public function handle() |
|||
134 | { |
||||
135 | 7 | $this->dispatch_purge_events = $this->laravel->make('config') |
|||
136 | 7 | ->get( |
|||
137 | 7 | 'garbageman.dispatch_purge_events', |
|||
138 | 7 | $this->dispatch_purge_events |
|||
139 | ); |
||||
140 | |||||
141 | 7 | $this->logging_level = $this->laravel->make('config') |
|||
142 | 7 | ->get('garbageman.logging_level', $this->logging_level); |
|||
143 | |||||
144 | 7 | $schedule = $this->laravel->make('config') |
|||
145 | 7 | ->get('garbageman.schedule', []); |
|||
146 | |||||
147 | 7 | foreach ($schedule as $model => $days) { |
|||
148 | 4 | $this->purgeExpiredRecordsForModel($model, $days); |
|||
149 | } |
||||
150 | |||||
151 | 7 | if (count($schedule) < 1) { |
|||
152 | 3 | $this->recordMessage("There were no models configured to purge.", 'notice'); |
|||
153 | } |
||||
154 | 7 | } |
|||
155 | |||||
156 | /** |
||||
157 | * Purge the expired records. |
||||
158 | * |
||||
159 | * @param string $model |
||||
160 | * @param int $days |
||||
161 | * |
||||
162 | * @return int|boolean |
||||
163 | */ |
||||
164 | 4 | protected function purgeExpiredRecordsForModel($model, $days) |
|||
165 | { |
||||
166 | 4 | if (!class_exists($model)) { |
|||
167 | 1 | $this->recordMessage(sprintf("The model [%s] was not found.", $model), 'warning'); |
|||
168 | |||||
169 | 1 | return false; |
|||
170 | } |
||||
171 | |||||
172 | 3 | if (!method_exists($model, 'forceDelete')) { |
|||
173 | 1 | $this->recordMessage(sprintf("The model [%s] does not support soft deleting.", $model), 'error'); |
|||
174 | |||||
175 | 1 | return false; |
|||
176 | } |
||||
177 | |||||
178 | 2 | $expiration = $this->now->copy() |
|||
179 | 2 | ->subDays($days); |
|||
180 | |||||
181 | 2 | $query = $this->laravel->make($model) |
|||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
182 | 2 | ->where('deleted_at', '<', $expiration) |
|||
183 | 2 | ->onlyTrashed(); |
|||
184 | |||||
185 | 2 | $count = $this->purgeRecordsAsConfigured($query, $model); |
|||
0 ignored issues
–
show
$model of type object is incompatible with the type string expected by parameter $model_name of Spinen\GarbageMan\Comman...geRecordsAsConfigured() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
186 | |||||
187 | 2 | $this->recordMessage( |
|||
188 | 2 | sprintf( |
|||
189 | 2 | "Purged %s record(s) for %s that was deleted before %s days ago.", |
|||
190 | $count, |
||||
191 | $model, |
||||
0 ignored issues
–
show
$model of type object is incompatible with the type double|integer|string expected by parameter $values of sprintf() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
192 | 2 | $expiration->toIso8601String() |
|||
193 | ) |
||||
194 | ); |
||||
195 | |||||
196 | 2 | return $count; |
|||
197 | } |
||||
198 | |||||
199 | /** |
||||
200 | * Either purge all the records at once or loop through them one by one. |
||||
201 | * |
||||
202 | * This is to allow events to get dispatched for each record if needed. |
||||
203 | * |
||||
204 | * @param Builder $query |
||||
205 | * @param string $model_name |
||||
206 | * |
||||
207 | * @return int |
||||
208 | */ |
||||
209 | 2 | protected function purgeRecordsAsConfigured(Builder $query, $model_name) |
|||
210 | { |
||||
211 | 2 | if ($this->dispatch_purge_events !== true) { |
|||
212 | 1 | $this->recordMessage("Deleting all the records in a single query statement."); |
|||
213 | |||||
214 | 1 | return $query->forceDelete(); |
|||
215 | } |
||||
216 | |||||
217 | 1 | $this->recordMessage("Deleting each record separately and dispatching events."); |
|||
218 | |||||
219 | 1 | $records = $query->get(); |
|||
220 | |||||
221 | 1 | foreach ($records as $record) { |
|||
222 | 1 | $this->dispatchPurgeEvent('purging', $model_name, $record); |
|||
223 | |||||
224 | 1 | $record->forceDelete(); |
|||
225 | |||||
226 | 1 | $this->dispatchPurgeEvent('purged', $model_name, $record); |
|||
227 | } |
||||
228 | |||||
229 | 1 | return $records->count(); |
|||
230 | } |
||||
231 | |||||
232 | /** |
||||
233 | * Log the action that was taken on the record. |
||||
234 | * |
||||
235 | * @param string $message |
||||
236 | * @param string|null $level |
||||
237 | * |
||||
238 | * @return void |
||||
239 | */ |
||||
240 | 7 | protected function recordMessage($message, $level = null) |
|||
241 | { |
||||
242 | 7 | if (is_null($level)) { |
|||
243 | 2 | $level = 'info'; |
|||
244 | } |
||||
245 | |||||
246 | $console_map = [ |
||||
247 | 7 | 'alert' => 'error', |
|||
248 | 'critical' => 'error', |
||||
249 | 'debug' => 'line', |
||||
250 | 'emergency' => 'error', |
||||
251 | 'error' => 'error', |
||||
252 | 'info' => 'info', |
||||
253 | 'notice' => 'comment', |
||||
254 | 'warning' => 'warn', |
||||
255 | ]; |
||||
256 | |||||
257 | 7 | if ($this->supposedToLogAtThisLevel($level, 'log')) { |
|||
258 | 7 | $this->log->{$level}($message); |
|||
259 | } |
||||
260 | |||||
261 | 7 | if ($this->supposedToLogAtThisLevel($level, 'console')) { |
|||
262 | 6 | $this->{$console_map[$level]}($message); |
|||
263 | } |
||||
264 | 7 | } |
|||
265 | |||||
266 | /** |
||||
267 | * Decide if the system is supposed to log for the level. |
||||
268 | * |
||||
269 | * Default to true, if not configured. |
||||
270 | * |
||||
271 | * @param string $level |
||||
272 | * @param string $type |
||||
273 | * |
||||
274 | * @return bool |
||||
275 | */ |
||||
276 | 7 | protected function supposedToLogAtThisLevel($level, $type) |
|||
277 | { |
||||
278 | 7 | if (!array_key_exists($type, $this->logging_level)) { |
|||
279 | 1 | return true; |
|||
280 | } |
||||
281 | |||||
282 | 6 | return $this->log_levels[$level] <= $this->logging_level[$type]; |
|||
283 | } |
||||
284 | } |
||||
285 |