1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
Copyright (C) 2013-2017 David Négrier - THE CODING MACHINE |
4
|
|
|
|
5
|
|
|
This program is free software; you can redistribute it and/or modify |
6
|
|
|
it under the terms of the GNU General Public License as published by |
7
|
|
|
the Free Software Foundation; either version 2 of the License, or |
8
|
|
|
(at your option) any later version. |
9
|
|
|
|
10
|
|
|
This program is distributed in the hope that it will be useful, |
11
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
12
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13
|
|
|
GNU General Public License for more details. |
14
|
|
|
|
15
|
|
|
You should have received a copy of the GNU General Public License |
16
|
|
|
along with this program; if not, write to the Free Software |
17
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
18
|
|
|
*/ |
19
|
|
|
|
20
|
|
|
namespace Mouf\Utils\Patcher; |
21
|
|
|
|
22
|
|
|
use Mouf\Utils\Patcher\Dumper\DumpableInterface; |
23
|
|
|
use Mouf\Utils\Patcher\Dumper\DumperInterface; |
24
|
|
|
use Mouf\Validator\MoufValidatorInterface; |
25
|
|
|
use Mouf\MoufManager; |
26
|
|
|
use Mouf\Validator\MoufValidatorResult; |
27
|
|
|
/** |
28
|
|
|
* The patch service is in charge of applying a list of patches attached to this application. |
29
|
|
|
* Especially, it contains the list of patch that has ever been declared. |
30
|
|
|
* |
31
|
|
|
* @author David Negrier <[email protected]> |
32
|
|
|
* @ExtendedAction {"name":"View patches list", "url":"patcher/", "default":false} |
33
|
|
|
*/ |
34
|
|
|
class PatchService implements MoufValidatorInterface, DumpableInterface { |
35
|
|
|
const IFEXISTS_EXCEPTION = "exception"; |
36
|
|
|
const IFEXISTS_IGNORE = "ignore"; |
37
|
|
|
|
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* The list of patches declared for this application. |
41
|
|
|
* |
42
|
|
|
* @var PatchInterface[] |
43
|
|
|
*/ |
44
|
|
|
private $patchs = []; |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* The list of exiting patch types for this application. |
48
|
|
|
* |
49
|
|
|
* @var PatchType[] |
50
|
|
|
*/ |
51
|
|
|
private $types = []; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* The list of listeners on the patch service. |
55
|
|
|
* |
56
|
|
|
* @var array|PatchListenerInterface[] |
57
|
|
|
*/ |
58
|
|
|
private $listeners; |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @var DumperInterface |
62
|
|
|
*/ |
63
|
|
|
private $dumper; |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @param PatchType[] $types |
67
|
|
|
* @param PatchListenerInterface[] $listeners |
68
|
|
|
*/ |
69
|
|
|
public function __construct(array $types, array $listeners = []) |
70
|
|
|
{ |
71
|
|
|
$this->types = $types; |
72
|
|
|
$this->listeners = $listeners; |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* The list of patches declared for this application. |
77
|
|
|
* @param PatchInterface[] $patchs |
78
|
|
|
* @return PatchService |
79
|
|
|
*/ |
80
|
|
|
public function setPatchs(array $patchs) { |
81
|
|
|
$this->patchs = $patchs; |
82
|
|
|
return $this; |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* The list of exiting patch types for this application. |
87
|
|
|
* |
88
|
|
|
* @return PatchType[] |
89
|
|
|
*/ |
90
|
|
|
public function getTypes(): array |
91
|
|
|
{ |
92
|
|
|
return $this->types; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @internal Returns a serialized list of types for the patch UI. |
97
|
|
|
* @return array |
98
|
|
|
*/ |
99
|
|
|
public function _getSerializedTypes(): array |
100
|
|
|
{ |
101
|
|
|
return array_map(function(PatchType $type) { |
102
|
|
|
return $type->jsonSerialize(); |
103
|
|
|
}, $this->types); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Adds this patch to the list of existing patches. |
108
|
|
|
* If the patch already exists, an exception is thrown. |
109
|
|
|
* Patches are identified by their unique name. |
110
|
|
|
* |
111
|
|
|
* |
112
|
|
|
* @param PatchInterface $patch |
113
|
|
|
* @param string $ifExists |
114
|
|
|
* @throws PatchException |
115
|
|
|
* @return \Mouf\Utils\Patcher\PatchService |
116
|
|
|
*/ |
117
|
|
|
public function register(PatchInterface $patch, $ifExists = self::IFEXISTS_IGNORE) { |
118
|
|
|
if ($this->has($patch->getUniqueName())) { |
119
|
|
|
if ($ifExists === self::IFEXISTS_IGNORE) { |
120
|
|
|
return $this; |
121
|
|
|
} else { |
122
|
|
|
throw new PatchException("The patch '".$patch->getUniqueName()."' is already registered."); |
123
|
|
|
} |
124
|
|
|
} |
125
|
|
|
$this->patchs[] = $patch; |
126
|
|
|
return $this; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* Returns true if the patch whose name is $uniqueName is declared in this service. |
131
|
|
|
* @param string $uniqueName |
132
|
|
|
* @return boolean |
133
|
|
|
*/ |
134
|
|
|
public function has($uniqueName) { |
135
|
|
|
foreach ($this->patchs as $patch) { |
136
|
|
|
if ($patch->getUniqueName() === $uniqueName) { |
137
|
|
|
return true; |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
return false; |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Returns the patch whose name is $uniqueName. |
145
|
|
|
* Throws an exception if the patch does not exists. |
146
|
|
|
* @param string $uniqueName |
147
|
|
|
* @return PatchInterface |
148
|
|
|
*/ |
149
|
|
|
public function get($uniqueName) { |
150
|
|
|
foreach ($this->patchs as $patch) { |
151
|
|
|
if ($patch->getUniqueName() === $uniqueName) { |
152
|
|
|
return $patch; |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
throw new PatchException("Unable to find patch whose unique name is '".$uniqueName."'"); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Returns the number of patches that needs to be applied. |
160
|
|
|
* |
161
|
|
|
* @return int |
162
|
|
|
*/ |
163
|
|
View Code Duplication |
public function getNbAwaitingPatchs(): int { |
|
|
|
|
164
|
|
|
$cnt = 0; |
165
|
|
|
foreach ($this->patchs as $patch) { |
166
|
|
|
if ($patch->getStatus() === PatchInterface::STATUS_AWAITING) { |
167
|
|
|
$cnt++; |
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
return $cnt; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Returns the number of patches that have errors. |
175
|
|
|
* |
176
|
|
|
* @return int |
177
|
|
|
*/ |
178
|
|
View Code Duplication |
public function getNbPatchsInError(): int { |
|
|
|
|
179
|
|
|
$cnt = 0; |
180
|
|
|
foreach ($this->patchs as $patch) { |
181
|
|
|
if ($patch->getStatus() === PatchInterface::STATUS_ERROR) { |
182
|
|
|
$cnt++; |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
return $cnt; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* (non-PHPdoc) |
190
|
|
|
* @see \Mouf\Validator\MoufValidatorInterface::validateInstance() |
191
|
|
|
*/ |
192
|
|
|
public function validateInstance() { |
193
|
|
|
|
194
|
|
|
$nbPatchs = count($this->patchs); |
195
|
|
|
$nbAwaitingPatchs = $this->getNbAwaitingPatchs(); |
196
|
|
|
$nbPatchesInError = $this->getNbPatchsInError(); |
197
|
|
|
$instanceName = MoufManager::getMoufManager()->findInstanceName($this); |
198
|
|
|
|
199
|
|
|
if ($nbAwaitingPatchs === 0 && $nbPatchesInError === 0) { |
200
|
|
|
if ($nbPatchs === 0) { |
201
|
|
|
return new MoufValidatorResult(MoufValidatorResult::SUCCESS, "<strong>Patcher</strong>: No patches declared"); |
202
|
|
|
} elseif ($nbPatchs == 0) { |
203
|
|
|
return new MoufValidatorResult(MoufValidatorResult::SUCCESS, "<strong>Patcher</strong>: The patch has been successfully applied"); |
204
|
|
|
} else { |
205
|
|
|
return new MoufValidatorResult(MoufValidatorResult::SUCCESS, "<strong>Patcher</strong>: All $nbPatchs patches have been successfully applied"); |
206
|
|
|
} |
207
|
|
|
} else { |
208
|
|
|
if ($nbPatchesInError == 0) { |
209
|
|
|
$status = MoufValidatorResult::WARN; |
210
|
|
|
} else { |
211
|
|
|
$status = MoufValidatorResult::ERROR; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
$html = '<strong>Patcher</strong>: <a href="'.ROOT_URL.'vendor/mouf/mouf/patcher/?name='.$instanceName.'" class="btn btn-large btn-success patch-run-all"><i class="icon-arrow-right icon-white"></i> Apply '; |
215
|
|
View Code Duplication |
if ($nbAwaitingPatchs != 0) { |
|
|
|
|
216
|
|
|
$html .= $nbAwaitingPatchs." awaiting patch".(($nbAwaitingPatchs != 1)?"es":""); |
217
|
|
|
if ($nbPatchesInError != 0) { |
218
|
|
|
$html .=" and"; |
219
|
|
|
} |
220
|
|
|
} |
221
|
|
View Code Duplication |
if ($nbPatchesInError != 0) { |
|
|
|
|
222
|
|
|
$html .=$nbPatchesInError." patch".(($nbPatchesInError != 1)?"es":"")." in error"; |
223
|
|
|
} |
224
|
|
|
$html .='</a>'; |
225
|
|
|
|
226
|
|
|
|
227
|
|
|
return new MoufValidatorResult($status, $html); |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
/** |
232
|
|
|
* Returns a PHP array representing the patchs. |
233
|
|
|
* |
234
|
|
|
* @internal |
235
|
|
|
*/ |
236
|
|
|
public function getView(): array { |
237
|
|
|
$view = array(); |
238
|
|
|
foreach ($this->patchs as $patch) { |
239
|
|
|
$uniqueName = null; |
240
|
|
|
$status = null; |
241
|
|
|
$canRevert = null; |
242
|
|
|
$description = null; |
243
|
|
|
$error_message = null; |
244
|
|
|
$editUrl = null; |
245
|
|
|
$patchType = null; |
246
|
|
|
|
247
|
|
|
try { |
248
|
|
|
$uniqueName = $patch->getUniqueName(); |
249
|
|
|
$canRevert = $patch->canRevert(); |
250
|
|
|
$description = $patch->getDescription(); |
251
|
|
|
$editUrl = $patch->getEditUrl()."&name=".MoufManager::getMoufManager()->findInstanceName($this); |
252
|
|
|
$status = $patch->getStatus(); |
253
|
|
|
$error_message = $patch->getLastErrorMessage(); |
254
|
|
|
$patchType = $patch->getPatchType()->getName(); |
|
|
|
|
255
|
|
|
|
256
|
|
|
} catch (\Exception $e) { |
257
|
|
|
$status = PatchInterface::STATUS_ERROR; |
258
|
|
|
$error_message = $e->getMessage(); |
259
|
|
|
} |
260
|
|
|
|
261
|
|
|
$patchView = array( |
262
|
|
|
"uniqueName"=>$uniqueName, |
263
|
|
|
"status"=>$status, |
264
|
|
|
"canRevert"=>$canRevert, |
265
|
|
|
"description"=>$description, |
266
|
|
|
"error_message"=>$error_message, |
267
|
|
|
"edit_url"=>$editUrl, |
268
|
|
|
"patch_type"=>$patchType |
269
|
|
|
); |
270
|
|
|
$view[] = $patchView; |
271
|
|
|
} |
272
|
|
|
return $view; |
273
|
|
|
} |
274
|
|
|
|
275
|
|
|
/** |
276
|
|
|
* Applies the patch whose unique name is passed in parameter. |
277
|
|
|
* @param string $uniqueName |
278
|
|
|
*/ |
279
|
|
View Code Duplication |
public function apply($uniqueName): void { |
|
|
|
|
280
|
|
|
$patch = $this->get($uniqueName); |
281
|
|
|
if ($patch instanceof DumpableInterface && $this->dumper !== null) { |
282
|
|
|
$patch->setDumper($this->dumper); |
283
|
|
|
} |
284
|
|
|
// TODO: in next major version, get rid of the DumpableInterface and pass the dumper right in the apply method. |
285
|
|
|
$patch->apply(); |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* Skips the patch whose unique name is passed in parameter. |
290
|
|
|
* @param string $uniqueName |
291
|
|
|
*/ |
292
|
|
View Code Duplication |
public function skip($uniqueName): void { |
|
|
|
|
293
|
|
|
$patch = $this->get($uniqueName); |
294
|
|
|
if ($patch instanceof DumpableInterface && $this->dumper !== null) { |
295
|
|
|
$patch->setDumper($this->dumper); |
296
|
|
|
} |
297
|
|
|
$patch->skip(); |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Reverts the patch whose unique name is passed in parameter. |
303
|
|
|
* @param string $uniqueName |
304
|
|
|
*/ |
305
|
|
View Code Duplication |
public function revert($uniqueName): void { |
|
|
|
|
306
|
|
|
$patch = $this->get($uniqueName); |
307
|
|
|
if ($patch instanceof DumpableInterface && $this->dumper !== null) { |
308
|
|
|
$patch->setDumper($this->dumper); |
309
|
|
|
} |
310
|
|
|
$patch->revert(); |
311
|
|
|
} |
312
|
|
|
|
313
|
|
|
/** |
314
|
|
|
* Apply all remaining patches (patches in state "awaiting" or in "error"). |
315
|
|
|
* The types of the patches can be passed as an array of string where the string is the name of the patch. |
316
|
|
|
* Patches with the "default" type are always applied. |
317
|
|
|
* |
318
|
|
|
* @param string[] $types |
319
|
|
|
* @return array An array containing 2 keys: "applied" and "skipped". Each key contains an associative array with the type of the patch and the number of patches of this type applied. |
320
|
|
|
*/ |
321
|
|
|
public function applyAll(array $types = []): array { |
322
|
|
|
// Array of count of applied and skipped patches. Key is the patch type. |
323
|
|
|
$appliedPatchArray = []; |
324
|
|
|
$skippedPatchArray = []; |
325
|
|
|
|
326
|
|
|
foreach ($this->patchs as $patch) { |
327
|
|
|
if ($patch->getStatus() === PatchInterface::STATUS_AWAITING || $patch->getStatus() === PatchInterface::STATUS_ERROR) { |
328
|
|
|
$type = $patch->getPatchType()->getName(); |
|
|
|
|
329
|
|
|
if ($type === '' || in_array($type, $types, true)) { |
330
|
|
|
$this->apply($patch->getUniqueName()); |
331
|
|
|
if (!isset($appliedPatchArray[$type])) { |
332
|
|
|
$appliedPatchArray[$type] = 0; |
333
|
|
|
} |
334
|
|
|
$appliedPatchArray[$type]++; |
335
|
|
|
} else { |
336
|
|
|
$this->skip($patch->getUniqueName()); |
337
|
|
|
if (!isset($skippedPatchArray[$type])) { |
338
|
|
|
$skippedPatchArray[$type] = 0; |
339
|
|
|
} |
340
|
|
|
$skippedPatchArray[$type]++; |
341
|
|
|
} |
342
|
|
|
} |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
return [ |
346
|
|
|
'applied' => $appliedPatchArray, |
347
|
|
|
'skipped' => $skippedPatchArray |
348
|
|
|
]; |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
/** |
352
|
|
|
* Reset all patches to a not applied state. |
353
|
|
|
* |
354
|
|
|
* Note: this does NOT run the "revert" method on each patch but DOES trigger a "reset" event. |
355
|
|
|
*/ |
356
|
|
View Code Duplication |
public function reset(): void { |
|
|
|
|
357
|
|
|
foreach ($this->listeners as $listener) { |
358
|
|
|
if ($listener instanceof DumpableInterface && $this->dumper !== null) { |
359
|
|
|
$listener->setDumper($this->dumper); |
360
|
|
|
} |
361
|
|
|
$listener->onReset(); |
362
|
|
|
} |
363
|
|
|
} |
364
|
|
|
|
365
|
|
|
public function setDumper(DumperInterface $dumper) |
366
|
|
|
{ |
367
|
|
|
$this->dumper = $dumper; |
368
|
|
|
} |
369
|
|
|
} |
370
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.