ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
ActiveContainer.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
28 
32 final class ActiveContainer implements Container
33 {
37  private const LOCK_UNTIL = '_lock_until';
41  private const GLUE = '|||||';
45  private const STRING_PREFIX = 'string';
49  private const ARRAY_PREFIX = 'array';
53  private const INT_PREFIX = 'int';
57  private const BOOL_PREFIX = 'bool';
61  private const NULL_PREFIX = 'null';
65  private const TRUE = 'true';
69  private const FALSE = 'false';
70 
71  private \ILIAS\Data\Factory $data_factory;
73  private string $prefix_pattern;
74 
75  private ?float $lock_cache = null;
76 
77  public function __construct(
78  private Request $request,
79  private Adaptor $adaptor,
80  private Config $config
81  ) {
82  $this->data_factory = new Factory();
83  // see comment in buildFinalTransformation why this is not a good solution.
84  $this->null_trafo = new \ILIAS\Refinery\Custom\Transformation(fn($value): null => null);
85  $this->prefix_pattern = '(' . implode('|', [
86  preg_quote(self::STRING_PREFIX, '/'),
87  preg_quote(self::ARRAY_PREFIX, '/'),
88  preg_quote(self::INT_PREFIX, '/'),
89  preg_quote(self::BOOL_PREFIX, '/'),
90  preg_quote(self::NULL_PREFIX, '/')
91  ]) . ')';
92  }
93 
94  private function pack(mixed $value): string
95  {
96  if (is_string($value)) {
97  return self::STRING_PREFIX . self::GLUE . $value;
98  }
99  if (is_array($value)) {
100  $value = $this->packRecursive($value);
101 
102  return self::ARRAY_PREFIX . self::GLUE . json_encode($value, JSON_THROW_ON_ERROR);
103  }
104  if (is_int($value)) {
105  return self::INT_PREFIX . self::GLUE . $value;
106  }
107  if (is_bool($value)) {
108  return self::BOOL_PREFIX . self::GLUE . ($value ? self::TRUE : self::FALSE);
109  }
110  if (is_null($value)) {
111  return self::NULL_PREFIX . self::GLUE;
112  }
113 
114  throw new \InvalidArgumentException(
115  'Only strings, integers and arrays containing those values are allowed, ' . gettype($value) . ' given.'
116  );
117  }
118 
119  private function packRecursive(array $value): array
120  {
121  array_walk($value, function (&$item): void {
122  $item = is_array($item) ? $this->packRecursive($item) : $this->pack($item);
123  });
124  return $value;
125  }
126 
127  private function unprefix(string $value): array
128  {
129  $str = '/^' . $this->prefix_pattern . preg_quote(self::GLUE, '/') . '(.*)/is';
130  if (!preg_match($str, $value, $matches)) {
131  return [self::NULL_PREFIX, null];
132  }
133 
134  return [$matches[1], $matches[2]];
135  }
136 
137  private function unpack(?string $value): string|int|array|bool|null
138  {
139  // simple detection
140  if ($value === null) {
141  return null;
142  }
143  if ($value === self::NULL_PREFIX . self::GLUE) {
144  return null;
145  }
146 
147  // type detection
148  [$type, $unprefixed_value] = $this->unprefix($value);
149 
150  switch ($type) {
151  case self::STRING_PREFIX:
152  return $unprefixed_value;
153  case self::BOOL_PREFIX:
154  return $unprefixed_value === self::TRUE;
155  case self::ARRAY_PREFIX:
156  $unprefixed_value = json_decode((string) $unprefixed_value, true, 512);
157  if (!is_array($unprefixed_value)) {
158  return null;
159  }
160 
161  return $this->unpackRecursive($unprefixed_value);
162  case self::INT_PREFIX:
163  return (int) $unprefixed_value;
164  default:
165  return null;
166  }
167 
168  return null;
169  }
170 
171  private function unpackRecursive(array $value): array
172  {
173  array_walk($value, function (&$item): void {
174  $item = is_array($item) ? $this->unpackRecursive($item) : $this->unpack($item);
175  });
176  return $value;
177  }
178 
179  protected function buildFinalTransformation(Transformation $transformation): ByTrying
180  {
181  // This is a workaround for the fact that the ByTrying transformation cannot be created by
182  // $DIC->refinery()->byTrying() since we are in a hell of dependencies. E.g. we cant instantiate the
183  // caching service with $DIC->refinery() since the Refinery needs ilLanguage, but ilLanguage
184  // needs the caching service...
185  return new ByTrying([$transformation, $this->getNullFallback()], $this->data_factory);
186  }
187 
188  protected function getNullFallback(): Transformation
189  {
190  return $this->null_trafo;
191  }
192 
193  public function isLocked(): bool
194  {
195  if ($this->lock_cache !== null && $this->lock_cache > microtime(true)) {
196  return true;
197  }
198 
199  if (!$this->adaptor->has($this->request->getContainerKey(), self::LOCK_UNTIL)) {
200  $this->lock_cache = null;
201  return false;
202  }
203 
204  // see comment in buildFinalTransformation why this is not a good solution.
205  $lock_until = $this->adaptor->get($this->request->getContainerKey(), self::LOCK_UNTIL);
206  $lock_until = $lock_until === null ? null : (float) $lock_until;
207 
208  $this->lock_cache = $lock_until;
209 
210  return $lock_until !== null && $lock_until > microtime(true);
211  }
212 
213  public function lock(float $seconds): void
214  {
215  if ($seconds > 300.0 || $seconds < 0.0) {
216  throw new \InvalidArgumentException('Locking for more than 5 minutes is not allowed.');
217  }
218  $lock_until = (string) (microtime(true) + $seconds);
219  $this->adaptor->set($this->request->getContainerKey(), self::LOCK_UNTIL, $lock_until, 300);
220  }
221 
222  public function has(string $key): bool
223  {
224  if ($this->isLocked()) {
225  return false;
226  }
227 
228  return $this->adaptor->has($this->request->getContainerKey(), $key);
229  }
230 
231  public function get(string $key, Transformation $transformation): string|int|array|bool|null
232  {
233  if ($this->isLocked()) {
234  return null;
235  }
236  if (!$this->has($key)) {
237  return null;
238  }
239 
240  $unpacked_values = $this->unpack(
241  $this->adaptor->get($this->request->getContainerKey(), $key)
242  );
243 
244  return $this->buildFinalTransformation($transformation)->transform($unpacked_values);
245  }
246 
247  public function set(string $key, string|int|array|bool|null $value, ?int $ttl = null): void
248  {
249  if ($this->isLocked()) {
250  return;
251  }
252  $ttl = $ttl ?? $this->config->getDefaultTTL();
253 
254  $this->adaptor->set(
255  $this->request->getContainerKey(),
256  $key,
257  $this->pack($value),
258  $ttl
259  );
260  }
261 
262  public function delete(string $key): void
263  {
264  if ($this->isLocked()) {
265  return;
266  }
267  $this->adaptor->delete($this->request->getContainerKey(), $key);
268  }
269 
270  public function flush(): void
271  {
272  $this->adaptor->flushContainer($this->request->getContainerKey());
273  }
274 
275  public function getAdaptorName(): string
276  {
277  return $this->config->getAdaptorName();
278  }
279 
280  public function getContainerName(): string
281  {
282  return $this->request->getContainerKey();
283  }
284 
288  public function unlock(): void
289  {
290  $this->adaptor->delete($this->request->getContainerKey(), self::LOCK_UNTIL);
291  $this->lock_cache = null;
292  }
293 }
has(string $key)
Returns true if the container contains a value for the given key.
flush()
Deletes all values in the container.
__construct(private Request $request, private Adaptor $adaptor, private Config $config)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
lock(float $seconds)
Locks the container for a given amount of seconds (max 300), in this time, get() will return null and...
isLocked()
Returns true if the container is locked.
getAdaptorName()
Returns the name of the adaptop used (such as apc, memcache, phpstatic)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
buildFinalTransformation(Transformation $transformation)
Builds data types.
Definition: Factory.php:35
A transformation is a function from one datatype to another.
getContainerName()
Returns the name of the container.