ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
class.ilAtomQueryBase.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
27 abstract class ilAtomQueryBase
28 {
29  protected const ITERATIONS = 10;
33  protected static array $available_isolations_levels = [
38  ];
42  protected static array $possible_anomalies = [
47  ];
51  protected static array $anomalies_map = [
57  ],
58  ilAtomQuery::ISOLATION_READ_COMMITED => [
61  ],
62  ilAtomQuery::ISOLATION_REPEATED_READ => [
64  ],
65  ilAtomQuery::ISOLATION_SERIALIZABLE => [],
66  ];
71  protected array $tables = [];
75  protected $query;
76 
81  public function __construct(protected \ilDBInterface $ilDBInstance, int $isolation_level = ilAtomQuery::ISOLATION_SERIALIZABLE)
82  {
83  static::checkIsolationLevel($isolation_level);
84  $this->isolation_level = $isolation_level;
85  }
86 
87  //
88  //
89  //
93  public function getRisks(): array
94  {
95  return static::getPossibleAnomalies($this->getIsolationLevel());
96  }
97 
104  public function addTableLock(string $table_name): ilTableLockInterface
105  {
106  $ilTableLock = new ilTableLock($table_name, $this->ilDBInstance);
107  $ilTableLock->setLockLevel($this->getDeterminedLockLevel());
108  $this->tables[] = $ilTableLock;
109 
110  return $ilTableLock;
111  }
112 
113  protected function getDeterminedLockLevel(): int
114  {
116  }
117 
133  public function addQueryCallable(callable $query): void
134  {
135  if ($this->query) {
137  }
138  if (!$this->checkCallable($query)) {
140  }
141  $this->query = $query;
142  }
143 
147  public function replaceQueryCallable(callable $query): void
148  {
149  if (!$this->checkCallable($query)) {
151  }
152  $this->query = $query;
153  }
154 
159  abstract public function run(): void;
160 
161  public function getIsolationLevel(): int
162  {
163  return $this->isolation_level;
164  }
165 
169  public static function isThereRiskThat(int $isolation_level, int $anomaly): bool
170  {
171  static::checkIsolationLevel($isolation_level);
172  static::checkAnomaly($anomaly);
173 
174  return in_array($anomaly, static::getPossibleAnomalies($isolation_level));
175  }
176 
180  public static function getPossibleAnomalies(int $isolation_level): array
181  {
182  static::checkIsolationLevel($isolation_level);
183 
184  return self::$anomalies_map[$isolation_level];
185  }
186 
190  public static function checkIsolationLevel(int $isolation_level): void
191  {
192  // The following Isolations are currently not supported
193  if (in_array($isolation_level, [
195  ilAtomQuery::ISOLATION_READ_COMMITED,
196  ilAtomQuery::ISOLATION_REPEATED_READ,
197  ])) {
198  throw new ilAtomQueryException('Level: ' . $isolation_level, ilAtomQueryException::DB_ATOM_ISO_WRONG_LEVEL);
199  }
200  // Check if a available Isolation level is selected
201  if (!in_array($isolation_level, self::$available_isolations_levels)) {
202  throw new ilAtomQueryException('Level: ' . $isolation_level, ilAtomQueryException::DB_ATOM_ISO_WRONG_LEVEL);
203  }
204  }
205 
209  public static function checkAnomaly(int $anomaly): void
210  {
211  if (!in_array($anomaly, self::$possible_anomalies)) {
213  }
214  }
215 
219  protected function checkQueries(): void
220  {
221  if (!($this->query instanceof \Traversable) && (is_array($this->query) && [] === $this->query)) {
223  }
224 
225  foreach ($this->query as $query) {
226  if (!$this->checkCallable($query)) {
228  }
229  }
230  }
231 
232  public function checkCallable(callable $query): bool
233  {
234  if (!is_callable($query)) {
235  return false; // Won't be triggered sidn type-hinting already checks this
236  }
237  if (is_array($query)) {
238  return false;
239  }
240  if (is_string($query)) {
241  return false;
242  }
243 
244  $is_a_closure = ($query instanceof Closure);
245  if (!$is_a_closure) {
246  $ref = new ReflectionClass($query);
247  foreach ($ref->getMethods() as $method) {
248  if ($method->getName() === '__invoke') {
249  return true;
250  }
251  }
252 
253  return false;
254  }
255  if ($is_a_closure) {
256  $ref = new ReflectionFunction($query);
257  $parameters = $ref->getParameters();
258  if (count($parameters) !== 1) {
259  return false;
260  }
261  $reflectionClass = $parameters[0]->getType();
262  return $reflectionClass && $reflectionClass->getName() === ilDBInterface::class;
263  }
264 
265  return true;
266  }
267 
268  protected function hasWriteLocks(): bool
269  {
270  $has_write_locks = false;
271  foreach ($this->tables as $table) {
272  if ($table->getLockLevel() === ilAtomQuery::LOCK_WRITE) {
273  $has_write_locks = true;
274  }
275  }
276 
277  return $has_write_locks;
278  }
279 
283  protected function runQueries(): void
284  {
286  $query($this->ilDBInstance);
287  }
288 
292  protected function checkBeforeRun(): void
293  {
294  $this->checkQueries();
295 
296  if ($this->hasWriteLocks() && $this->getIsolationLevel() != ilAtomQuery::ISOLATION_SERIALIZABLE) {
298  }
299 
300  if ($this->tables === []) {
302  }
303  }
304 }
static array $available_isolations_levels
Class ilAtomQuery Use ilAtomQuery to fire Database-Actions which have to be done without beeing influ...
static checkIsolationLevel(int $isolation_level)
addTableLock(string $table_name)
Add table-names which are influenced by your queries, MyISAm has to lock those tables.
Class ilTableLock.
static checkAnomaly(int $anomaly)
__construct(protected \ilDBInterface $ilDBInstance, int $isolation_level=ilAtomQuery::ISOLATION_SERIALIZABLE)
ilAtomQuery constructor.
Class ilTableLockInterface Defines methods, which a Table-Lock used in ilAtomQuery provides...
run()
Fire your Queries.
addQueryCallable(callable $query)
All action on the database during this isolation has to be passed as Callable to ilAtomQuery.
static isThereRiskThat(int $isolation_level, int $anomaly)
Class ilAtomQueryException.
static array $possible_anomalies
checkCallable(callable $query)
static getPossibleAnomalies(int $isolation_level)
static array $anomalies_map
replaceQueryCallable(callable $query)