Completed
Pull Request — 2.1 (#7)
by David
02:07
created

PatchService::applyAll()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 29
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 5.3846
c 0
b 0
f 0
cc 8
eloc 19
nc 6
nop 1
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\Validator\MoufValidatorInterface;
23
use Mouf\MoufManager;
24
use Mouf\Validator\MoufValidatorResult;
25
/**
26
 * The patch service is in charge of applying a list of patches attached to this application.
27
 * Especially, it contains the list of patch that has ever been declared.
28
 * 
29
 * @author David Negrier <[email protected]>
30
 * @ExtendedAction {"name":"View patches list", "url":"patcher/", "default":false}
31
 */
32
class PatchService implements MoufValidatorInterface {
33
    const IFEXISTS_EXCEPTION = "exception";
34
    const IFEXISTS_IGNORE = "ignore";
35
36
37
    /**
38
	 * The list of patches declared for this application.
39
	 * 
40
	 * @var PatchInterface[]
41
	 */
42
	private $patchs = [];
43
44
    /**
45
     * The list of exiting patch types for this application.
46
     *
47
     * @var PatchType[]
48
     */
49
	private $types = [];
50
51
    /**
52
     * The list of listeners on the patch service.
53
     *
54
     * @var array|PatchListenerInterface[]
55
     */
56
    private $listeners;
57
58
    /**
59
     * @param PatchType[] $types
60
     * @param PatchListenerInterface[] $listeners
61
     */
62
    public function __construct(array $types, array $listeners = [])
63
    {
64
        $this->types = $types;
65
        $this->listeners = $listeners;
66
    }
67
68
    /**
69
	 * The list of patches declared for this application.
70
	 * @param PatchInterface[] $patchs
71
	 * @return PatchService
72
	 */
73
	public function setPatchs(array $patchs) {
74
		$this->patchs = $patchs;
75
		return $this;
76
	}
77
78
    /**
79
     * The list of exiting patch types for this application.
80
     *
81
     * @return PatchType[]
82
     */
83
    public function getTypes(): array
84
    {
85
        return $this->types;
86
    }
87
88
    /**
89
     * @internal Returns a serialized list of types for the patch UI.
90
     * @return array
91
     */
92
    public function _getSerializedTypes(): array
93
    {
94
        return array_map(function(PatchType $type) {
95
            return $type->jsonSerialize();
96
        }, $this->types);
97
    }
98
99
	/**
100
	 * Adds this patch to the list of existing patches.
101
	 * If the patch already exists, an exception is thrown.
102
	 * Patches are identified by their unique name.
103
	 * 
104
	 * 
105
	 * @param PatchInterface $patch
106
	 * @param string $ifExists
107
	 * @throws PatchException
108
	 * @return \Mouf\Utils\Patcher\PatchService
109
	 */
110
	public function register(PatchInterface $patch, $ifExists = self::IFEXISTS_IGNORE) {
111
		if ($this->has($patch->getUniqueName())) {
112
			if ($ifExists === self::IFEXISTS_IGNORE) {
113
				return $this;
114
			} else {
115
				throw new PatchException("The patch '".$patch->getUniqueName()."' is already registered.");
116
			}
117
		}
118
		$this->patchs[] = $patch;
119
		return $this;
120
	}
121
	
122
	/**
123
	 * Returns true if the patch whose name is $uniqueName is declared in this service.
124
	 * @param string $uniqueName
125
	 * @return boolean
126
	 */
127
	public function has($uniqueName) {
128
		foreach ($this->patchs as $patch) {
129
			if ($patch->getUniqueName() === $uniqueName) {
130
				return true;
131
			}
132
		}
133
		return false;
134
	}
135
	
136
	/**
137
	 * Returns the patch whose name is $uniqueName.
138
	 * Throws an exception if the patch does not exists.
139
	 * @param string $uniqueName
140
	 * @return PatchInterface
141
	 */
142
	public function get($uniqueName) {
143
		foreach ($this->patchs as $patch) {
144
			if ($patch->getUniqueName() === $uniqueName) {
145
				return $patch;
146
			}
147
		}
148
		throw new PatchException("Unable to find patch whose unique name is '".$uniqueName."'");
149
	}
150
	
151
	/**
152
	 * Returns the number of patches that needs to be applied.
153
	 * 
154
	 * @return int
155
	 */
156 View Code Duplication
	public function getNbAwaitingPatchs(): int {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
157
		$cnt = 0;
158
		foreach ($this->patchs as $patch) {
159
			if ($patch->getStatus() === PatchInterface::STATUS_AWAITING) {
160
				$cnt++;
161
			}
162
		}
163
		return $cnt;
164
	}
165
	
166
	/**
167
	 * Returns the number of patches that have errors.
168
	 *
169
	 * @return int
170
	 */
171 View Code Duplication
	public function getNbPatchsInError(): int {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
172
		$cnt = 0;
173
		foreach ($this->patchs as $patch) {
174
			if ($patch->getStatus() === PatchInterface::STATUS_ERROR) {
175
				$cnt++;
176
			}
177
		}
178
		return $cnt;
179
	}
180
	
181
	/**
182
	 * (non-PHPdoc)
183
	 * @see \Mouf\Validator\MoufValidatorInterface::validateInstance()
184
	 */
185
	public function validateInstance() {
186
		
187
		$nbPatchs = count($this->patchs);
188
		$nbAwaitingPatchs = $this->getNbAwaitingPatchs();
189
		$nbPatchesInError = $this->getNbPatchsInError();
190
		$instanceName = MoufManager::getMoufManager()->findInstanceName($this);
191
		
192
		if ($nbAwaitingPatchs === 0 && $nbPatchesInError === 0) {
193
			if ($nbPatchs === 0) {
194
				return new MoufValidatorResult(MoufValidatorResult::SUCCESS, "<strong>Patcher</strong>: No patches declared");
195
			} elseif ($nbPatchs == 0) {
196
				return new MoufValidatorResult(MoufValidatorResult::SUCCESS, "<strong>Patcher</strong>: The patch has been successfully applied");
197
			} else {
198
				return new MoufValidatorResult(MoufValidatorResult::SUCCESS, "<strong>Patcher</strong>: All $nbPatchs patches have been successfully applied");
199
			}
200
		} else {
201
			if ($nbPatchesInError == 0) {
202
				$status = MoufValidatorResult::WARN;
203
			} else {
204
				$status = MoufValidatorResult::ERROR;
205
			}
206
			
207
			$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 ';
208 View Code Duplication
			if ($nbAwaitingPatchs != 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
209
				$html .= $nbAwaitingPatchs." awaiting patch".(($nbAwaitingPatchs != 1)?"es":"");
210
				if ($nbPatchesInError != 0) {
211
					$html .=" and";
212
				}
213
			}
214 View Code Duplication
			if ($nbPatchesInError != 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
215
				$html .=$nbPatchesInError." patch".(($nbPatchesInError != 1)?"es":"")." in error";
216
			}
217
			$html .='</a>';
218
				
219
			
220
			return new MoufValidatorResult($status, $html);
221
		}	
222
	}
223
	
224
	/**
225
	 * Returns a PHP array representing the patchs.
226
     *
227
     * @internal
228
	 */
229
	public function getView(): array {
230
		$view = array();
231
		foreach ($this->patchs as $patch) {
232
			$uniqueName = null;
233
			$status = null;
234
			$canRevert = null;
235
			$description = null;
236
			$error_message = null;
237
			$editUrl = null;
238
			$patchType = null;
239
			
240
			try {
241
				$uniqueName = $patch->getUniqueName();
242
				$canRevert = $patch->canRevert();
243
				$description = $patch->getDescription();
244
				$editUrl = $patch->getEditUrl()."&name=".MoufManager::getMoufManager()->findInstanceName($this);
245
				$status = $patch->getStatus();
246
				$error_message = $patch->getLastErrorMessage();
247
				$patchType = $patch->getPatchType()->getName();
0 ignored issues
show
Bug introduced by
Consider using $patch->getPatchType()->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
248
				
249
			} catch (\Exception $e) {
250
				$status = PatchInterface::STATUS_ERROR;
251
				$error_message = $e->getMessage();
252
			}
253
			
254
			$patchView = array(
255
				"uniqueName"=>$uniqueName,
256
				"status"=>$status,
257
				"canRevert"=>$canRevert,
258
				"description"=>$description,
259
				"error_message"=>$error_message,
260
				"edit_url"=>$editUrl,
261
                "patch_type"=>$patchType
262
			);
263
			$view[] = $patchView;
264
		}
265
		return $view;
266
	}
267
	
268
	/**
269
	 * Applies the patch whose unique name is passed in parameter.
270
	 * @param string $uniqueName
271
	 */
272
	public function apply($uniqueName): void {
273
		$patch = $this->get($uniqueName);
274
		$patch->apply();
275
	}
276
	
277
	/**
278
	 * Skips the patch whose unique name is passed in parameter.
279
	 * @param string $uniqueName
280
	 */
281
	public function skip($uniqueName): void {
282
		$patch = $this->get($uniqueName);
283
		$patch->skip();
284
	}
285
	
286
	
287
	/**
288
	 * Reverts the patch whose unique name is passed in parameter.
289
	 * @param string $uniqueName
290
	 */
291
	public function revert($uniqueName): void {
292
		$patch = $this->get($uniqueName);
293
		$patch->revert();
294
	}
295
296
    /**
297
     * Apply all remaining patches (patches in state "awaiting" or in "error").
298
     * The types of the patches can be passed as an array of string where the string is the name of the patch.
299
     * Patches with the "default" type are always applied.
300
     *
301
     * @param string[] $types
302
     * @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.
303
     */
304
	public function applyAll(array $types = []): array {
305
        // Array of count of applied and skipped patches. Key is the patch type.
306
        $appliedPatchArray = [];
307
        $skippedPatchArray = [];
308
309
        foreach ($this->patchs as $patch) {
310
            if ($patch->getStatus() === PatchInterface::STATUS_AWAITING || $patch->getStatus() === PatchInterface::STATUS_ERROR) {
311
                $type = $patch->getPatchType()->getName();
0 ignored issues
show
Bug introduced by
Consider using $patch->getPatchType()->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
312
                if ($type === '' || in_array($type, $types, true)) {
313
                    $this->apply($patch->getUniqueName());
314
                    if (!isset($appliedPatchArray[$type])) {
315
                        $appliedPatchArray[$type] = 0;
316
                    }
317
                    $appliedPatchArray[$type]++;
318
                } else {
319
                    $this->skip($patch->getUniqueName());
320
                    if (!isset($skippedPatchArray[$type])) {
321
                        $skippedPatchArray[$type] = 0;
322
                    }
323
                    $skippedPatchArray[$type]++;
324
                }
325
            }
326
        }
327
328
        return [
329
            'applied' => $appliedPatchArray,
330
            'skipped' => $skippedPatchArray
331
        ];
332
    }
333
334
    /**
335
     * Reset all patches to a not applied state.
336
     *
337
     * Note: this does NOT run the "revert" method on each patch but DOES trigger a "reset" event.
338
     */
339
	public function reset(): void {
340
        foreach ($this->listeners as $listener) {
341
            $listener->onReset();
342
        }
343
    }
344
}
345