Completed
Pull Request — 2.1 (#10)
by David
02:22
created

PatchService::setDumper()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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\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 {
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...
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 {
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...
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) {
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...
216
				$html .= $nbAwaitingPatchs." awaiting patch".(($nbAwaitingPatchs != 1)?"es":"");
217
				if ($nbPatchesInError != 0) {
218
					$html .=" and";
219
				}
220
			}
221 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...
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();
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...
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 {
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...
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 {
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...
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 {
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...
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();
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...
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 {
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...
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