ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
BasicPersistence.php
Go to the documentation of this file.
1<?php
2
20
31
33{
34 protected static BasicPersistence $instance;
35 protected static array $buckets = [];
36 protected \SplObjectStorage $bucketHashToObserverContainerId;
37 protected \SplObjectStorage $taskHashToTaskContainerId;
38 protected \SplObjectStorage $valueHashToValueContainerId;
39 protected ?\arConnector $connector = null;
40 protected static array $tasks = [];
41
43 {
44 if (!isset(self::$instance)) {
45 self::$instance = new BasicPersistence($db);
46 }
47
48 return self::$instance;
49 }
50
51 public function __construct(protected \ilDBInterface $db)
52 {
53 $this->valueHashToValueContainerId = new \SplObjectStorage();
54 $this->bucketHashToObserverContainerId = new \SplObjectStorage();
55 $this->taskHashToTaskContainerId = new \SplObjectStorage();
56 }
57
58 public function garbageCollection(): void
59 {
60 $atom = $this->db->buildAtomQuery();
61
62 $atom->addTableLock('il_bt_bucket');
63 $atom->addTableLock('il_bt_task');
64 $atom->addTableLock('il_bt_value');
65 $atom->addTableLock('il_bt_value_to_task');
66 $atom->addQueryCallable(function (\ilDBInterface $db): void {
67 $this->db->manipulateF(
68 "DELETE FROM il_bt_bucket WHERE user_id = %s AND (state = %s OR state = %s) AND last_heartbeat < %s AND last_heartbeat > 0",
69 ['integer', 'integer', 'integer', 'integer'],
70 [
71 defined('ANONYMOUS_USER_ID') ? \ANONYMOUS_USER_ID : 13,
74 time() - 1 * 60 * 24 * 30
75 ]
76 );
77
78 // remove old finished buckets
79 $this->db->manipulateF(
80 "DELETE FROM il_bt_bucket WHERE state = %s AND last_heartbeat < %s AND last_heartbeat > 0",
81 ['integer', 'integer'],
82 [State::FINISHED, time() - 60 * 60 * 24 * 30] // older than 30 days
83 );
84
85 // remove old buckets with other states
86 $this->db->manipulateF(
87 "DELETE FROM il_bt_bucket WHERE state != %s AND last_heartbeat < %s AND last_heartbeat > 0",
88 ['integer', 'integer'],
89 [State::FINISHED, time() - 60 * 60 * 24 * 180] // older than 180 days
90 );
91
92 // remove tasks without a bucket
93 $this->db->manipulate(
94 "DELETE il_bt_task FROM il_bt_task LEFT JOIN il_bt_bucket ON il_bt_bucket.id = il_bt_task.bucket_id WHERE il_bt_bucket.id IS NULL;"
95 );
96
97 // remove value to bucket links without a bucket
98 $this->db->manipulate(
99 "DELETE il_bt_value_to_task FROM il_bt_value_to_task LEFT JOIN il_bt_bucket ON il_bt_bucket.id = il_bt_value_to_task.bucket_id WHERE il_bt_bucket.id IS NULL;"
100 );
101
102 // remove value to bucket links without a task
103 $this->db->manipulate(
104 "DELETE il_bt_value_to_task FROM il_bt_value_to_task LEFT JOIN il_bt_task ON il_bt_task.id = il_bt_value_to_task.task_id WHERE il_bt_task.id IS NULL;"
105 );
106
107 // remove values without a task
108 $this->db->manipulate(
109 "DELETE il_bt_value FROM il_bt_value LEFT JOIN il_bt_value_to_task ON il_bt_value_to_task.value_id = il_bt_value.id WHERE il_bt_value_to_task.id IS NULL;"
110 );
111 });
112 $atom->run();
113 }
114
115 public function setConnector(\arConnector $c): void
116 {
117 $this->connector = $c;
118 }
119
124 public function saveBucketAndItsTasks(Bucket $bucket): void
125 {
126 $bucket->checkIntegrity();
127
128 $this->saveObserver($bucket);
129 }
130
134 public function updateBucket(Bucket $bucket): void
135 {
136 $bucketContainer = new BucketContainer($this->getBucketContainerId($bucket), $this->connector);
137
138 // The basic information about the task.
139 $bucketContainer->setUserId($bucket->getUserId());
140 $bucketContainer->setState($bucket->getState());
141 $bucketContainer->setTotalNumberoftasks(count($bucket->getTask()->unfoldTask()));
142 $bucketContainer->setPercentage($bucket->getOverallPercentage());
143 $bucketContainer->setTitle($bucket->getTitle());
144 $bucketContainer->setLastHeartbeat($bucket->getLastHeartbeat());
145 $bucketContainer->setDescription($bucket->getDescription());
146 $bucketContainer->setCurrentTaskid($this->getTaskContainerId($bucket->getCurrentTask()));
147 $bucketContainer->setRootTaskid($this->getTaskContainerId($bucket->getTask()));
148
149 // Save and store the container to bucket instance.
150 $bucketContainer->update();
151 }
152
156 public function getBucketIdsOfUser(int $user_id, string $order_by = "id", string $order_direction = "ASC"): array
157 {
158 if ((new \Random\Randomizer())->getInt(0, 100) === 1) {
159 $this->garbageCollection();
160 }
161
162 return BucketContainer::where(['user_id' => $user_id])
163 ->orderBy($order_by, $order_direction)
164 ->getArray(null, 'id');
165 }
166
170 public function getBucketMetaOfUser(int $user_id): array
171 {
172 $buckets = BucketContainer::where(['user_id' => $user_id])->get();
173
174 return array_map(function (BucketContainer $bucketContainer): BasicBucketMeta {
175 $bucketMeta = new BasicBucketMeta();
176
177 $bucketMeta->setUserId($bucketContainer->getUserId());
178 $bucketMeta->setState($bucketContainer->getState());
179 $bucketMeta->setTitle($bucketContainer->getTitle());
180 $bucketMeta->setDescription($bucketContainer->getDescription());
181 $bucketMeta->setOverallPercentage($bucketContainer->getPercentage());
182
183 return $bucketMeta;
184 }, $buckets);
185 }
186
190 public function getBucketIdsByState(int $state): array
191 {
192 return array_map(
193 static fn(BucketContainer $bucket_container): int => $bucket_container->getId(),
194 BucketContainer::where(['state' => $state])->get()
195 );
196 }
197
202 protected function saveObserver(Bucket $bucket): void
203 {
204 // If the instance has a known container we use it, otherwise we create a new container.
205 if ($this->bucketHashToObserverContainerId->offsetExists($bucket)) {
206 $bucketContainer = new BucketContainer($this->bucketHashToObserverContainerId[$bucket], $this->connector);
207 } else {
208 $bucketContainer = new BucketContainer(0, $this->connector);
209 }
210
211 // The basic information about the task.
212 $bucketContainer->setUserId($bucket->getUserId());
213 $bucketContainer->setState($bucket->getState());
214 $bucketContainer->setTitle($bucket->getTitle());
215 $bucketContainer->setDescription($bucket->getDescription());
216 $bucketContainer->setTotalNumberoftasks(count($bucket->getTask()->unfoldTask()));
217 $bucketContainer->setPercentage($bucket->getOverallPercentage());
218
219 // We want to store the bucket ID in every sub task and value. Thus we need to create an id if not available yet.
220 if (!$bucketContainer->getId()) {
221 $bucketContainer->create();
222 }
223
224 // The recursive part.
225 $this->saveTask($bucket->getTask(), $bucketContainer->getId());
226 if (!$bucket->hasCurrentTask()) {
227 $bucket->setCurrentTask($bucket->getTask());
228 }
229 $bucketContainer->setCurrentTaskid($this->getTaskContainerId($bucket->getCurrentTask()));
230 $bucketContainer->setRootTaskid($this->getTaskContainerId($bucket->getTask()));
231
232 // Save and store the container to bucket instance.
233 $bucketContainer->save();
234 $this->bucketHashToObserverContainerId[$bucket] = $bucketContainer->getId();
235 }
236
243 protected function saveTask(Task $task, int $bucketId): void
244 {
245 // If the instance has a known container we use it, otherwise we create a new container.
246 if ($this->taskHashToTaskContainerId->offsetExists($task)) {
247 $taskContainer = new TaskContainer($this->taskHashToTaskContainerId[$task]);
248 } else {
249 $taskContainer = new TaskContainer(0);
250 }
251
252 // The basic information about the task.
253 $taskContainer->setType($task->getType());
254 $taskContainer->setBucketId($bucketId);
255 $reflection = new \ReflectionClass($task::class);
256 $taskContainer->setClassName($task::class);
257
258 // Recursivly save the inputs and link them to this task.
259 foreach ($task->getInput() as $k => $input) {
260 $this->saveValue($input, $bucketId, $k);
261 }
262 $this->saveValueToTask($task, $taskContainer, $bucketId);
263
264 // Save and store the container to the task instance.
265 $taskContainer->save();
266 $this->taskHashToTaskContainerId[$task] = $taskContainer->getId();
267 }
268
275 protected function saveValueToTask(Task $task, TaskContainer $taskContainer, int $bucketId): void
276 {
277 // If we have previous values to task associations we delete them.
278 if ($taskContainer->getId() !== 0) {
280 $olds = ValueToTaskContainer::where(['task_id' => $taskContainer->getId()])->get();
281 foreach ($olds as $old) {
282 $old->delete();
283 }
284 } else {
285 // We need a valid ID to link the inputs
286 $taskContainer->save();
287 }
288
289 // We create the new 1 to n relation.
290 foreach ($task->getInput() as $k => $inputValue) {
291 $v = new ValueToTaskContainer(0, $this->connector);
292 $v->setTaskId($taskContainer->getId());
293 $v->setPosition($k);
294 $v->setBucketId($bucketId);
295 $v->setValueId($this->getValueContainerId($inputValue));
296 $v->save();
297 }
298 }
299
306 protected function saveValue(Value $value, int $bucketId, int $position): void
307 {
308 // If we have previous values to task associations we delete them.
309 if ($this->valueHashToValueContainerId->offsetExists($value)) {
310 $valueContainer = new ValueContainer($this->valueHashToValueContainerId[$value], $this->connector);
311 } else {
312 $valueContainer = new ValueContainer(0, $this->connector);
313 }
314 $valueContainer->setClassName($value::class);
315 // bugfix mantis 23503
316 // $absolute_class_path = $reflection->getFileName();
317 // $relative_class_path = str_replace(ILIAS_ABSOLUTE_PATH,".",$absolute_class_path);
318 // $valueContainer->setClassPath($relative_class_path);
319 $valueContainer->setType($value->getType());
320 $valueContainer->setHasParenttask($value->hasParentTask());
321 $valueContainer->setBucketId($bucketId);
322 $valueContainer->setPosition($position);
323 $valueContainer->setHash($value->getHash());
324 $valueContainer->setSerialized($value->serialize());
325
326 // If the value is a thunk value we also store its parent.
327 if ($value->hasParentTask()) {
328 $this->saveTask($value->getParentTask(), $bucketId);
329 $valueContainer->setParentTaskid($this->getTaskContainerId($value->getParentTask()));
330 }
331
332 // We save the container and store the instance to container association.
333 $valueContainer->save();
334 $this->valueHashToValueContainerId[$value] = $valueContainer->getId();
335 }
336
340 public function getBucketContainerId(Bucket $bucket): int
341 {
342 if (!$this->bucketHashToObserverContainerId->offsetExists($bucket)) {
343 throw new SerializationException("Could not resolve container id of task: "
344 . print_r($bucket, true));
345 }
346
347 return (int) $this->bucketHashToObserverContainerId[$bucket];
348 }
349
354 protected function getTaskContainerId(Task $task): int
355 {
356 if (!$this->taskHashToTaskContainerId->offsetExists($task)) {
357 throw new SerializationException("Could not resolve container id of task: "
358 . print_r($task, true));
359 }
360
361 return (int) $this->taskHashToTaskContainerId[$task];
362 }
363
367 protected function getValueContainerId(Value $value): int
368 {
369 if (!$this->valueHashToValueContainerId->offsetExists($value)) {
370 throw new SerializationException("Could not resolve container id of value: "
371 . print_r($value, true));
372 }
373
374 return (int) $this->valueHashToValueContainerId[$value];
375 }
376
380 public function loadBucket(int $bucket_container_id): Bucket
381 {
382 if (isset(self::$buckets[$bucket_container_id])) {
383 return self::$buckets[$bucket_container_id];
384 }
386 $bucketContainer = BucketContainer::find($bucket_container_id);
387 if (!$bucketContainer) {
388 throw new BucketNotFoundException("The requested bucket with container id $bucket_container_id could not be found in the database.");
389 }
390 $bucket = new BasicBucket();
391
392 $bucket->setUserId($bucketContainer->getUserId());
393 $bucket->setState($bucketContainer->getState());
394 $bucket->setTitle($bucketContainer->getTitle());
395 $bucket->setDescription($bucketContainer->getDescription());
396 $bucket->setOverallPercentage($bucketContainer->getPercentage());
397 $bucket->setLastHeartbeat($bucketContainer->getLastHeartbeat());
398 $bucket->setTask($this->loadTask($bucketContainer->getRootTaskid(), $bucket, $bucketContainer));
399
400 $this->bucketHashToObserverContainerId[$bucket] = $bucket_container_id;
401
402 return $bucket;
403 }
404
413 private function loadTask(int $taskContainerId, Bucket $bucket, BucketContainer $bucketContainer): Task
414 {
415 global $DIC;
416 $factory = $DIC->backgroundTasks()->taskFactory();
418 $taskContainer = TaskContainer::find($taskContainerId);
419 if (!$taskContainer instanceof TaskContainer) {
420 throw new SerializationException(
421 "The requested task with container id $taskContainerId could not be found in the database."
422 );
423 }
426 $task = $factory->createTask($taskContainer->getClassName());
427
428 // Added additional orderBy for the id to ensure that the items are returned in the right order.
429 $valueToTasks = ValueToTaskContainer::where(['task_id' => $taskContainerId])
430 ->orderBy('task_id')
431 ->orderBy('position')
432 ->orderBy('id')
433 ->get();
434 $inputs = [];
435 foreach ($valueToTasks as $valueToTask) {
436 $inputs[] = $this->loadValue($valueToTask->getValueId(), $bucket, $bucketContainer);
437 }
438 $task->setInput($inputs);
439
440 if ($taskContainerId === $bucketContainer->getCurrentTaskid()) {
441 $bucket->setCurrentTask($task);
442 }
443
444 $this->taskHashToTaskContainerId[$task] = $taskContainerId;
445
446 return $task;
447 }
448
449 private function loadValue($valueContainerId, Bucket $bucket, BucketContainer $bucketContainer): Value
450 {
451 global $DIC;
452 $registry = $DIC->backgroundTasks()->registry();
453 $factory = $DIC->backgroundTasks()->injector();
454
456 $valueContainer = ValueContainer::find($valueContainerId);
457 if (!$valueContainer instanceof ValueContainer) {
458 throw new SerializationException(
459 "The requested value with container id $valueContainerId could not be found in the database."
460 );
461 }
465 $value = $registry->isRegistered($valueContainer->getClassName())
466 ? $registry->newValue($valueContainer->getClassName())
467 : $factory->createInstance($valueContainer->getClassName());
468
469 $value->unserialize($valueContainer->getSerialized());
470 if ($valueContainer->getHasParenttask() !== 0) {
471 $value->setParentTask($this->loadTask($valueContainer->getParentTaskid(), $bucket, $bucketContainer));
472 }
473
474 $this->valueHashToValueContainerId[$value] = $valueContainerId;
475
476 return $value;
477 }
478
479 public function deleteBucketById(int $bucket_id): void
480 {
481 $buckets = BucketContainer::where(['id' => $bucket_id])->get();
482 array_map(function (BucketContainer $item): void {
483 $item->delete();
484 }, $buckets);
485
487 $tasks = TaskContainer::where(['bucket_id' => $bucket_id])->get();
488 array_map(function (TaskContainer $item): void {
489 $item->delete();
490 }, $tasks);
491
493 $values = ValueContainer::where(['bucket_id' => $bucket_id])->get();
494 array_map(function (ValueContainer $item): void {
495 $item->delete();
496 }, $values);
497
499 $valueToTasks = ValueToTaskContainer::where(['bucket_id' => $bucket_id])->get();
500 array_map(function (ValueToTaskContainer $item): void {
501 $item->delete();
502 }, $valueToTasks);
503 }
504
508 public function deleteBucket(Bucket $bucket): void
509 {
510 $id = $this->getBucketContainerId($bucket);
511 $this->deleteBucketById($id);
512 $this->bucketHashToObserverContainerId->detach($bucket);
513 }
514
518 public function loadBuckets(array $bucket_container_ids): array
519 {
520 $buckets = [];
521 foreach ($bucket_container_ids as $bucket_id) {
522 try {
523 $buckets[] = $this->loadBucket($bucket_id);
524 } catch (\Throwable) {
525 // there seem to be a problem with this container, we must delete it
526 $this->deleteBucketById($bucket_id);
527 }
528 }
529
530 return $buckets;
531 }
532}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
static where($where, $operator=null)
saveBucketAndItsTasks(Bucket $bucket)
Fully updates or creates an Observer and all its tasks into the database.
getBucketIdsOfUser(int $user_id, string $order_by="id", string $order_direction="ASC")
int[] Returns an array of bucket ids for the given user Id.
updateBucket(Bucket $bucket)
Updates only the bucket! Use this if e.g.
deleteBucket(Bucket $bucket)
Delete the bucket and all its stuff.
getBucketIdsByState(int $state)
int[] Returns a list of bucket ids for the given Observer State
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const ANONYMOUS_USER_ID
Definition: constants.php:27
getLastHeartbeat()
When was the last time that something happened on this bucket?
loadBucket(int $bucket_container_id)
deleteBucketById(int $bucket_id)
Deletes the Observer AND all its tasks and values.
Interface ilDBInterface.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
Definition: Job.php:19
Interface Observer \BackgroundTasks Contains several chained tasks and infos about them.
global $DIC
Definition: shib_login.php:26