1<?php 2 3/* Copyright (c) 2019 Richard Klees <richard.klees@concepts-and-training.de> Extended GPL, see docs/LICENSE */ 4 5use ILIAS\Setup\Environment; 6use ILIAS\Setup\CallableObjective; 7use ILIAS\Setup\NullObjective; 8use ILIAS\Setup\Objective; 9 10/** 11 * This base-class simplifies the creation of (consecutive) database updates. 12 * 13 * Implement update steps on one or more tables by creating methods that follow 14 * this schema: 15 * 16 * public function step_1(\ilDBInterface $db) { ... } 17 * 18 * The class will figure out which of them haven't been performed yet and need 19 * to be executed. 20 * 21 * If the class takes care of only one table or a set of related tables it will 22 * be easier to maintain. 23 * 24 * If for some reason you rely on other objectives, e.g. steps from other db-update 25 * classes, implement `getAdditionalPreconditionsForStep`. 26 */ 27abstract class ilDatabaseUpdateSteps implements Objective 28{ 29 const STEP_METHOD_PREFIX = "step_"; 30 31 /** 32 * @var string[]|null 33 */ 34 protected $steps = null; 35 36 /** 37 * @var Objective 38 */ 39 protected $base; 40 41 /** 42 * @param \ilObjective $base for the update steps, i.e. the objective that should 43 * have been reached before the steps of this class can 44 * even begin. Most probably this should be 45 * \ilDatabasePopulatedObjective. 46 */ 47 public function __construct( 48 Objective $base 49 ) { 50 $this->base = $base; 51 } 52 53 /** 54 * Get preconditions for steps. 55 * 56 * The previous step will automatically be a precondition of every step but 57 * will not be returned from this method. 58 * 59 * @return Objective[] 60 */ 61 public function getAdditionalPreconditionsForStep(int $num) : array 62 { 63 return []; 64 } 65 66 /** 67 * The hash for the objective is calculated over the classname and the steps 68 * that are contained. 69 */ 70 final public function getHash() : string 71 { 72 return hash( 73 "sha256", 74 get_class($this) 75 ); 76 } 77 78 final public function getLabel() : string 79 { 80 return "Database update steps in " . get_class($this); 81 } 82 83 /** 84 * @inheritdocs 85 */ 86 final public function isNotable() : bool 87 { 88 return true; 89 } 90 91 /** 92 * @inheritdocs 93 */ 94 final public function getPreconditions(Environment $environment) : array 95 { 96 $log = $environment->getResource(\ilDatabaseUpdateStepExecutionLog::class); 97 98 if ($log) { 99 $finished = $log->getLastFinishedStep(get_class($this)); 100 } else { 101 $finished = 0; 102 } 103 104 return [$this->getStep($this->getLatestStepNum(), $finished)]; 105 } 106 107 /** 108 * @inheritdocs 109 */ 110 final public function achieve(Environment $environment) : Environment 111 { 112 return $environment; 113 } 114 115 /** 116 * Get a database update step. Optionally tell which step is known to have 117 * been finished to exclude it from the preconditions of the newer steps. 118 * 119 * @throws \LogicException if step is unknown 120 */ 121 final public function getStep(int $num, int $finished = 0) : ilDatabaseUpdateStep 122 { 123 $cur = $this->base; 124 foreach ($this->getSteps() as $s) { 125 if ($s <= $finished) { 126 continue; 127 } elseif ($s <= $num) { 128 $cur = new ilDatabaseUpdateStep($this, $s, $cur, ...$this->getAdditionalPreconditionsForStep($s)); 129 } else { 130 break; 131 } 132 } 133 134 return $cur; 135 } 136 137 /** 138 * Get the number of latest database step in this class. 139 */ 140 final public function getLatestStepNum() : int 141 { 142 $this->getSteps(); 143 return end($this->steps); 144 } 145 146 /** 147 * Get the numbers of the steps in this class. 148 * 149 * @return int[] 150 */ 151 final protected function getSteps() : array 152 { 153 if (!is_null($this->steps)) { 154 return $this->steps; 155 } 156 157 $this->steps = []; 158 159 foreach (get_class_methods(static::class) as $method) { 160 if (stripos($method, self::STEP_METHOD_PREFIX) !== 0) { 161 continue; 162 } 163 164 $number = substr($method, strlen(self::STEP_METHOD_PREFIX)); 165 166 if (!preg_match("/^[1-9]\d*$/", $number)) { 167 throw new \LogicException("Method $method seems to be a step but has an odd looking number"); 168 } 169 170 $this->steps[(int) $number] = (int) $number; 171 } 172 173 asort($this->steps); 174 175 return $this->steps; 176 } 177} 178