1: <?php
2:
3: namespace Himedia\Padocc;
4:
5: use GAubry\Shell\ShellAdapter;
6: use Himedia\Padocc\Numbering\NumberingInterface;
7: use Himedia\Padocc\Properties\PropertiesInterface;
8: use Himedia\Padocc\Task\Base\Project;
9: use Psr\Log\LoggerInterface;
10: use Psr\Log\NullLogger;
11:
12: /**
13: * @author Geoffroy AUBRY <gaubry@hi-media.com>
14: */
15: abstract class Task
16: {
17:
18: /**
19: * Compteur d'instances pour mieux s'y retrouver dans les logs des tâches.
20: * @var NumberingInterface
21: * @see $sName
22: */
23: protected $oNumbering;
24:
25: /**
26: * Collection de services.
27: *
28: * @var DIContainerInterface
29: */
30: protected $oDIContainer;
31:
32: /**
33: * Instance de AttributeProperties.
34: * @var AttributeProperties
35: * @see check()
36: */
37: protected $oAttrProperties;
38:
39: /**
40: * @var ShellAdapter Shell adapter
41: */
42: protected $oShell;
43:
44: /**
45: * @var LoggerInterface Logger adapter
46: */
47: private $oLogger;
48:
49: /**
50: * @var array
51: */
52: protected $aConfig;
53:
54: /**
55: * @var PropertiesInterface Properties adapter
56: */
57: protected $oProperties;
58:
59: /**
60: * Contenu XML de la tâche.
61: * @var \SimpleXMLElement
62: */
63: protected $oXMLTask;
64:
65: /**
66: * @var Project
67: */
68: protected $oProject;
69:
70: /**
71: * Chaîne numérotant la tâche.
72: * @var string
73: * @see NumberingInterface::getNextCounterValue()
74: */
75: protected $sCounter;
76:
77: /**
78: * Nom complet de la tâche, utilisé notamment dans le suivi d'exécution.
79: * @var string
80: */
81: protected $sName;
82:
83: /**
84: * Attributs XML de la tâche.
85: * Tableau ((string) clé, (string) valeur).
86: * @var array
87: */
88: protected $aAttValues;
89:
90: /**
91: * Liste des propriétés des attributs déclarés de la tâche.
92: *
93: * Structure : array('attribute' => iValue, ...)
94: * Où iValue vaut 0 ou une combinaison de bits au sens |,
95: * à partir des constantes de la classe AttributeProperties.
96: *
97: * @var array
98: * @see check()
99: * @see AttributeProperties
100: */
101: protected $aAttrProperties;
102:
103: /**
104: * Retourne le nom du tag XML correspondant à cette tâche dans les config projet.
105: *
106: * @return string nom du tag XML correspondant à cette tâche dans les config projet.
107: * @throws \RuntimeException si appelée directement sur Task.
108: */
109: public static function getTagName ()
110: {
111: throw new \RuntimeException('Not implemented at this level!');
112: }
113:
114: /**
115: * Surcharge du constructeur, dont le premier paramètre est passé d'une instance de \SimpleXMLElement à
116: * un tableau associatif attribut => valeur.
117: * Ne peut être utilisé pour créer une instance de Project.
118: *
119: * @param array $aAttributes Tableau associatif listant des attributs et leur valeur.
120: * @param Project $oProject Super tâche projet.
121: * @param DIContainerInterface $oDIContainer Register de services prédéfinis (ShellInterface, ...).
122: * @return Task
123: * @throws \RuntimeException si appelée directement sur Task.
124: */
125: public static function getNewInstance (array $aAttributes, Project $oProject, DIContainerInterface $oDIContainer)
126: {
127: $sAttributes = '';
128: foreach ($aAttributes as $sName => $sValue) {
129: $sAttributes .= ' ' . $sName . '="' . $sValue . '"';
130: }
131: $sXML = '<' . static::getTagName() . $sAttributes . ' />';
132:
133: $oXML = new \SimpleXMLElement($sXML);
134: return new static($oXML, $oProject, $oDIContainer);
135: }
136:
137: /**
138: * Constructeur.
139: * @param \SimpleXMLElement $oXMLTask Contenu XML de la tâche.
140: * @param Project $oProject Super tâche projet.
141: * @param DIContainerInterface $oDIContainer Register de services prédéfinis (ShellInterface, ...).
142: */
143: public function __construct (\SimpleXMLElement $oXMLTask, Project $oProject, DIContainerInterface $oDIContainer)
144: {
145: $this->oXMLTask = $oXMLTask;
146: $this->oProject = $oProject;
147:
148: $this->oDIContainer = $oDIContainer;
149: $this->aConfig = $oDIContainer->getConfig();
150:
151: $this->setShellAdapter($oDIContainer->getShellAdapter());
152: $this->setNumberingAdapter($oDIContainer->getNumberingAdapter());
153: $this->setPropertiesAdapter($oDIContainer->getPropertiesAdapter());
154:
155: if ($logger = $oDIContainer->getLogger()) {
156: $this->setLogger($logger);
157: }
158:
159: // TODO à injecter :
160: $this->setAttributePropertiesAdapter(new AttributeProperties($this->oShell));
161:
162: $sCounter = $this->oNumbering->getNextCounterValue();
163: $this->sCounter = $sCounter;
164: $this->sName = (strlen($this->sCounter) === 0 ? '' : $this->sCounter . '_') . get_class($this);
165:
166: $this->aAttrProperties = array();
167: $this->fetchAttributes();
168:
169: $this->init();
170: }
171:
172: /**
173: * Initializes task.
174: *
175: * @return void
176: */
177: protected function init()
178: {
179: }
180:
181: /**
182: * Gets the logger.
183: *
184: * @return LoggerInterface
185: */
186: public function getLogger()
187: {
188: if ($this->oLogger === null) {
189: $this->oLogger = new NullLogger();
190: }
191:
192: return $this->oLogger;
193: }
194:
195: /**
196: * Sets a logger.
197: *
198: * @param LoggerInterface $logger
199: */
200: public function setLogger(LoggerInterface $logger)
201: {
202: $this->oLogger = $logger;
203: }
204:
205: /**
206: * Sets the Shell adapter.
207: *
208: * @param ShellAdapter $adapter
209: */
210: public function setShellAdapter(ShellAdapter $adapter)
211: {
212: $this->oShell = $adapter;
213: }
214:
215: /**
216: * Sets the numbering adapter.
217: *
218: * @param NumberingInterface $adapter
219: */
220: public function setNumberingAdapter(NumberingInterface $adapter)
221: {
222: $this->oNumbering = $adapter;
223: }
224:
225: /**
226: * Sets the properties adapter.
227: *
228: * @param PropertiesInterface $adapter
229: */
230: public function setPropertiesAdapter(PropertiesInterface $adapter)
231: {
232: $this->oProperties = $adapter;
233: }
234:
235: /**
236: * Sets the attribute properties adapter.
237: *
238: * @param AttributeProperties $adapter
239: */
240: public function setAttributePropertiesAdapter(AttributeProperties $adapter)
241: {
242: $this->oAttrProperties = $adapter;
243: }
244:
245: /**
246: * Récupère les attributs XML du nœud $this->oXMLTask et les enregistre dans $this->aAttValues.
247: */
248: protected function fetchAttributes ()
249: {
250: $this->aAttValues = array();
251: foreach ($this->oXMLTask->attributes() as $key => $val) {
252: $this->aAttValues[$key] = (string)$val;
253: }
254: }
255:
256: /**
257: * Appels combinés à expandPath() puis reroutePaths()
258: *
259: * @param string $sPath chemin pouvant contenir des paramètres
260: * @return array liste de tous les chemins générés en remplaçant les paramètres par leurs valeurs
261: * et en reroutant ceux tombant dans 'basedir'.
262: * @see expandPath()
263: * @see reroutePaths()
264: */
265: protected function processPath ($sPath)
266: {
267: $aExpandedPaths = $this->expandPath($sPath);
268: $aReroutedPaths = $this->reroutePaths($aExpandedPaths);
269: return $aReroutedPaths;
270: }
271:
272: /**
273: * Appel à processPath(), puis retourne le premier chemin récupéré
274: * en s'assurant qu'il n'y en a pas d'autres.
275: *
276: * @param string $sPath chemin pouvant contenir des paramètres
277: * @return string l'unique chemin généré en remplaçant les paramètres par leurs valeurs
278: * et en reroutant le chemin s'il tombe dans 'basedir'.
279: * @throws \RuntimeException si plus d'un chemin a été généré
280: * @see processPath()
281: */
282: protected function processSimplePath ($sPath)
283: {
284: $aProcessedPaths = $this->processPath($sPath);
285: if (count($aProcessedPaths) !== 1) {
286: $sMsg = "String '$sPath' should return a single path after process: " . print_r($aProcessedPaths, true);
287: throw new \RuntimeException($sMsg);
288: }
289: return reset($aProcessedPaths);
290: }
291:
292: /**
293: * Retourne la liste de tous les chemins générés en remplaçant les paramètres
294: * du chemin spécifié par leurs valeurs.
295: *
296: * @param string $sPath chemin pouvant contenir des paramètres
297: * @return array liste de tous les chemins générés en remplaçant les paramètres par leurs valeurs,
298: */
299: protected function expandPath ($sPath)
300: {
301: if (preg_match_all('/\$\{([^}]+)\}/', $sPath, $aMatches) > 0) {
302: // On traite dans un premier temps un maximum de remplacements sans récursivité :
303: $aPaths = array($sPath);
304: foreach ($aMatches[1] as $property) {
305: $aToProcessPaths = $aPaths;
306: $aPaths = array();
307:
308: $sRawValue = $this->oProperties->getProperty($property);
309: $values = explode(' ', $sRawValue);
310: foreach ($aToProcessPaths as $sPath) {
311: foreach ($values as $value) {
312: $aPaths[] = str_replace('${' . $property . '}', $value, $sPath);
313: }
314: }
315: }
316:
317: // Perfectible mais suffisant, récursivité sur les propriétés de propriétés :
318: $aRecursivePaths = $aPaths;
319: $aPaths = array();
320: foreach ($aRecursivePaths as $sPath) {
321: $aPaths = array_merge($aPaths, $this->expandPath($sPath));
322: }
323: $aPaths = array_values(array_unique($aPaths));
324: } else {
325: $aPaths = array($sPath);
326: }
327:
328: // Set default remote user if not specified:
329: foreach ($aPaths as $idx => $sPath) {
330: if (preg_match('#^[^:@]+:(?!//).+$#i', $sPath) === 1) {
331: $aPaths[$idx] = $this->aConfig['default_remote_shell_user'] . '@' . $sPath;
332: }
333: }
334: return $aPaths;
335: }
336:
337: /**
338: * Reroute de façon transparente tous les chemins système inclus ou égal à la valeur de la propriété 'basedir'
339: * dans le répertoire de releases nommé de la valeur de 'basedir'
340: * avec le suffixe $aConfig['symlink_releases_dir_suffix'].
341: * Les autres chemins, ceux hors 'basedir', restent inchangés.
342: *
343: * @param array $aPaths liste de chemins sans paramètres (par exemple provenant de expandPath())
344: * @return array liste de ces mêmes chemins en reroutant ceux tombant dans 'basedir'.
345: */
346: protected function reroutePaths (array $aPaths)
347: {
348: if ($this->oProperties->getProperty('with_symlinks') === 'true') {
349: $sBaseSymLink = $this->oProperties->getProperty('basedir');
350: $sReleaseSymLink = $sBaseSymLink . $this->aConfig['symlink_releases_dir_suffix'] . '/'
351: . $this->oProperties->getProperty('execution_id');
352: for ($i=0, $iMax=count($aPaths); $i<$iMax; $i++) {
353: if (preg_match('#^(.*?:)' . preg_quote($sBaseSymLink, '#') . '\b#', $aPaths[$i], $aMatches) === 1) {
354: $sNewPath = str_replace(
355: $aMatches[1] . $sBaseSymLink,
356: $aMatches[1] . $sReleaseSymLink,
357: $aPaths[$i]
358: );
359: $aPaths[$i] = $sNewPath;
360: }
361: }
362: }
363: return $aPaths;
364: }
365:
366: /**
367: * Centralisation de tous les chemins systèmes définis dans l'une ou l'autre des tâches.
368: * Dédoublonnés et triés par ordre alphabétique.
369: * Structure : array((string)path => true, ...)
370: * @var array
371: * @see registerPaths()
372: */
373: protected static $aRegisteredPaths = array();
374:
375: /**
376: * Collecte les chemins système définis dans les attributs de la tâche,
377: * et les centralise au niveau de la classe pour analyse ultérieure.
378: */
379: protected function registerPaths ()
380: {
381: foreach ($this->aAttrProperties as $sAttribute => $iProperties) {
382: if ((($iProperties & AttributeProperties::DIR) > 0 || ($iProperties & AttributeProperties::FILE) > 0)
383: && isset($this->aAttValues[$sAttribute])
384: ) {
385: self::$aRegisteredPaths[$this->aAttValues[$sAttribute]] = true;
386: }
387: }
388: ksort(self::$aRegisteredPaths);
389: }
390:
391: /**
392: * Prépare la tâche avant exécution : vérifications basiques, analyse des serveurs concernés...
393: */
394: public function setUp ()
395: {
396: $this->check();
397: $this->registerPaths();
398: }
399:
400: /**
401: * Vérifie au moyen de tests basiques que la tâche peut être exécutée.
402: * Lance une exception si tel n'est pas le cas.
403: *
404: * Comme toute les tâches sont vérifiées avant que la première ne soit exécutée,
405: * doit permettre de remonter au plus tôt tout dysfonctionnement.
406: * Appelé avant la méthode execute().
407: *
408: * @throws \UnexpectedValueException en cas d'attribut ou fichier manquant
409: * @throws \DomainException en cas d'attribut non permis
410: * @see self::$aAttributeProperties
411: */
412: protected function check ()
413: {
414: $sMsg = "Check '" . $this->sName . "' task";
415: if (! empty($this->aAttValues['name'])) {
416: $sMsg .= ': \'' . $this->aAttValues['name'] . '\'';
417: }
418: $this->getLogger()->info($sMsg . '+++');
419: $this->oAttrProperties->checkAttributes($this->aAttrProperties, $this->aAttValues);
420: $this->getLogger()->info('---');
421: }
422:
423: /**
424: * Phase de pré-traitements de l'exécution de la tâche.
425: * Elle devrait systématiquement commencer par "parent::preExecute();".
426: * Appelé par execute().
427: * @see execute()
428: */
429: protected function preExecute ()
430: {
431: $sMsg = "Execute '" . $this->sName . "' task";
432: if (! empty($this->aAttValues['name'])) {
433: $sMsg .= ': \'' . $this->aAttValues['name'] . '\'';
434: }
435: $this->getLogger()->info($sMsg);
436: }
437:
438: /**
439: * Phase de traitements centraux de l'exécution de la tâche.
440: * Elle devrait systématiquement commencer par "parent::centralExecute();".
441: * Appelé par execute().
442: * @see execute()
443: * @codeCoverageIgnore
444: */
445: protected function centralExecute ()
446: {
447: }
448:
449: /**
450: * Phase de post-traitements de l'exécution de la tâche.
451: * Elle devrait systématiquement finir par "parent::postExecute();".
452: * Appelé par execute().
453: * @see execute()
454: * @codeCoverageIgnore
455: */
456: protected function postExecute ()
457: {
458: }
459:
460: /**
461: * Exécute la tâche en trois phases : pré-traitements, traitements centraux et post-traitements.
462: * Si l'on a la classe F fille de la tâche P, alors on peut s'attendre à :
463: * P::preExecute()
464: * F::preExecute()
465: * P::centralExecute()
466: * F::centralExecute()
467: * F::postExecute()
468: * P::postExecute()
469: *
470: * @see preExecute()
471: * @see centralExecute()
472: * @see postExecute()
473: */
474: public function execute ()
475: {
476: $this->preExecute();
477: $this->centralExecute();
478: $this->postExecute();
479: }
480: }
481: