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