Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
98.08% |
51 / 52 |
|
80.00% |
4 / 5 |
CRAP | |
0.00% |
0 / 1 |
AbstractExpireCommand | |
98.08% |
51 / 52 |
|
80.00% |
4 / 5 |
7 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
configure | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
1 | |||
getTimestampedMessage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
execute | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
3 | |||
getDateThreshold | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * Generic base class for expiration commands. |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2020. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License version 2, |
12 | * as published by the Free Software Foundation. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
17 | * GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License |
20 | * along with this program; if not, write to the Free Software |
21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
22 | * |
23 | * @category VuFind |
24 | * @package Console |
25 | * @author Demian Katz <demian.katz@villanova.edu> |
26 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
27 | * @link https://vufind.org/wiki/development Wiki |
28 | */ |
29 | |
30 | namespace VuFindConsole\Command\Util; |
31 | |
32 | use DateTime; |
33 | use Symfony\Component\Console\Command\Command; |
34 | use Symfony\Component\Console\Input\InputArgument; |
35 | use Symfony\Component\Console\Input\InputInterface; |
36 | use Symfony\Component\Console\Input\InputOption; |
37 | use Symfony\Component\Console\Output\OutputInterface; |
38 | use VuFind\Db\Service\Feature\DeleteExpiredInterface; |
39 | |
40 | use function floatval; |
41 | |
42 | /** |
43 | * Generic base class for expiration commands. |
44 | * |
45 | * @category VuFind |
46 | * @package Console |
47 | * @author Demian Katz <demian.katz@villanova.edu> |
48 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
49 | * @link https://vufind.org/wiki/development Wiki |
50 | */ |
51 | class AbstractExpireCommand extends Command |
52 | { |
53 | /** |
54 | * Help description for the command. |
55 | * |
56 | * @var string |
57 | */ |
58 | protected $commandDescription = 'Expiration tool'; |
59 | |
60 | /** |
61 | * Label to use for rows in help messages. |
62 | * |
63 | * @var string |
64 | */ |
65 | protected $rowLabel = 'rows'; |
66 | |
67 | /** |
68 | * Minimum legal age (in days) of rows to delete. |
69 | * |
70 | * @var int |
71 | */ |
72 | protected $minAge = 2; |
73 | |
74 | /** |
75 | * Default age of rows (in days) to delete. $minAge is used if $defaultAge is |
76 | * null. |
77 | * |
78 | * @var int|null |
79 | */ |
80 | protected $defaultAge = null; |
81 | |
82 | /** |
83 | * Table on which to expire rows |
84 | * |
85 | * @var DeleteExpiredInterface |
86 | */ |
87 | protected $table; |
88 | |
89 | /** |
90 | * Constructor |
91 | * |
92 | * @param DeleteExpiredInterface $service Service on which to expire rows |
93 | * @param ?string $name The name of the command; passing null means it |
94 | * must be set in configure() |
95 | */ |
96 | public function __construct(protected DeleteExpiredInterface $service, ?string $name = null) |
97 | { |
98 | parent::__construct($name); |
99 | } |
100 | |
101 | /** |
102 | * Configure the command. |
103 | * |
104 | * @return void |
105 | */ |
106 | protected function configure() |
107 | { |
108 | $this |
109 | ->setDescription($this->commandDescription) |
110 | ->setHelp("Expires old {$this->rowLabel} in the database.") |
111 | ->addOption( |
112 | 'batch', |
113 | null, |
114 | InputOption::VALUE_REQUIRED, |
115 | 'Number of records to delete in a single batch', |
116 | 1000 |
117 | )->addOption( |
118 | 'sleep', |
119 | null, |
120 | InputOption::VALUE_REQUIRED, |
121 | 'Milliseconds to sleep between batches', |
122 | 100 |
123 | )->addArgument( |
124 | 'age', |
125 | InputArgument::OPTIONAL, |
126 | 'Minimum age (in days, starting from ' |
127 | . number_format($this->minAge, 1, '.', '') |
128 | . ") of {$this->rowLabel} to expire", |
129 | $this->defaultAge ?? $this->minAge |
130 | ); |
131 | } |
132 | |
133 | /** |
134 | * Add a time stamp to a message |
135 | * |
136 | * @param string $msg Message |
137 | * |
138 | * @return string |
139 | */ |
140 | protected function getTimestampedMessage($msg) |
141 | { |
142 | return '[' . date('Y-m-d H:i:s') . '] ' . $msg; |
143 | } |
144 | |
145 | /** |
146 | * Run the command. |
147 | * |
148 | * @param InputInterface $input Input object |
149 | * @param OutputInterface $output Output object |
150 | * |
151 | * @return int 0 for success |
152 | */ |
153 | protected function execute(InputInterface $input, OutputInterface $output) |
154 | { |
155 | // Collect arguments/options: |
156 | $daysOld = floatval($input->getArgument('age')); |
157 | $batchSize = $input->getOption('batch'); |
158 | $sleepTime = $input->getOption('sleep'); |
159 | |
160 | // Abort if we have an invalid expiration age. |
161 | if ($daysOld < $this->minAge) { |
162 | $output->writeln( |
163 | str_replace( |
164 | '%%age%%', |
165 | number_format($this->minAge, 1, '.', ''), |
166 | 'Expiration age must be at least %%age%% days.' |
167 | ) |
168 | ); |
169 | return 1; |
170 | } |
171 | |
172 | // Calculate date threshold once to avoid creeping a few seconds in each loop iteration. |
173 | $dateLimit = $this->getDateThreshold($daysOld); |
174 | |
175 | // Delete the expired rows--this cleans up any junk left in the database |
176 | // e.g. from old searches or sessions that were not caught by the session |
177 | // garbage collector. Records are deleted in batches until no more records to |
178 | // delete are found. |
179 | $total = 0; |
180 | do { |
181 | $count = $this->service->deleteExpired($dateLimit, $batchSize); |
182 | if ($count > 0) { |
183 | $output->writeln( |
184 | $this->getTimestampedMessage("$count {$this->rowLabel} deleted.") |
185 | ); |
186 | $total += $count; |
187 | // Be nice to others and wait between batches |
188 | usleep($sleepTime * 1000); |
189 | } |
190 | } while ($count > 0); |
191 | |
192 | $output->writeln( |
193 | $this->getTimestampedMessage("Total $total {$this->rowLabel} deleted.") |
194 | ); |
195 | return 0; |
196 | } |
197 | |
198 | /** |
199 | * Convert days to a date threshold |
200 | * |
201 | * @param float $daysOld Days before now |
202 | * |
203 | * @return DateTime |
204 | */ |
205 | protected function getDateThreshold(float $daysOld): DateTime |
206 | { |
207 | return new DateTime("now - $daysOld days"); |
208 | } |
209 | } |