ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
ActiveContainer.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
28
32final 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) {
152 return $unprefixed_value;
154 return $unprefixed_value === self::TRUE;
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}
__construct(private Request $request, private Adaptor $adaptor, private Config $config)
getAdaptorName()
Returns the name of the adaptop used (such as apc, memcache, phpstatic)
buildFinalTransformation(Transformation $transformation)
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.
getContainerName()
Returns the name of the container.
has(string $key)
Returns true if the container contains a value for the given key.
flush()
Deletes all values in the container.
Builds data types.
Definition: Factory.php:36
A transformation is a function from one datatype to another.