1 | <?php |
||||||
2 | |||||||
3 | declare(strict_types=1); |
||||||
4 | |||||||
5 | namespace Yiisoft\Validator\Helper; |
||||||
6 | |||||||
7 | use Attribute; |
||||||
8 | use InvalidArgumentException; |
||||||
9 | use JetBrains\PhpStorm\ArrayShape; |
||||||
10 | use JetBrains\PhpStorm\ExpectedValues; |
||||||
11 | use ReflectionAttribute; |
||||||
12 | use ReflectionClass; |
||||||
13 | use ReflectionObject; |
||||||
14 | use ReflectionProperty; |
||||||
15 | use Yiisoft\Validator\AfterInitAttributeEventInterface; |
||||||
16 | use Yiisoft\Validator\AttributeTranslatorInterface; |
||||||
17 | use Yiisoft\Validator\AttributeTranslatorProviderInterface; |
||||||
18 | use Yiisoft\Validator\Label; |
||||||
19 | use Yiisoft\Validator\RuleInterface; |
||||||
20 | |||||||
21 | use function array_key_exists; |
||||||
22 | use function is_int; |
||||||
23 | use function is_object; |
||||||
24 | use function is_string; |
||||||
25 | |||||||
26 | /** |
||||||
27 | * A helper class used to parse rules from PHP attributes (attached to class properties and class itself) and data from |
||||||
28 | * object properties. The attributes introduced in PHP 8 simplify rules' configuration process, especially for nested |
||||||
29 | * data and relations. This way the validated structures can be presented as DTO classes with references to each other. |
||||||
30 | * |
||||||
31 | 62 | * An example of parsed object with both one-to-one (requires PHP > 8.0) and one-to-many (requires PHP > 8.1) relations: |
|||||
32 | * |
||||||
33 | * ```php |
||||||
34 | * final class Post |
||||||
35 | * { |
||||||
36 | * #[Length(max: 255)] |
||||||
37 | * public string $title = ''; |
||||||
38 | * |
||||||
39 | 62 | * #[Nested] |
|||||
40 | 61 | * public Author|null $author = null; |
|||||
41 | 1 | * |
|||||
42 | * // Passing instances is available only since PHP 8.1. |
||||||
43 | * #[Each(new Nested(File::class))] |
||||||
44 | * public array $files = []; |
||||||
45 | * |
||||||
46 | * public function __construct() |
||||||
47 | 39 | * { |
|||||
48 | * $this->author = new Author(); |
||||||
49 | 39 | * } |
|||||
50 | * } |
||||||
51 | 11 | * |
|||||
52 | * final class Author |
||||||
53 | * { |
||||||
54 | 37 | * #[Length(min: 1)] |
|||||
55 | 37 | * public string $name = ''; |
|||||
56 | * } |
||||||
57 | 36 | * |
|||||
58 | 36 | * // Some rules, like "Nested" can be also configured through the class attribute. |
|||||
59 | 34 | * |
|||||
60 | 34 | * #[Nested(['url' => new Url()])] |
|||||
61 | * final class File |
||||||
62 | 34 | * { |
|||||
63 | 4 | * public string $url = ''; |
|||||
64 | * } |
||||||
65 | * |
||||||
66 | * $post = new Post(title: 'Yii3 Overview 3', author: 'Dmitriy'); |
||||||
67 | * $parser = new ObjectParser($post); |
||||||
68 | 36 | * $rules = $parser->getRules(); |
|||||
69 | 36 | * $data = $parser->getData(); |
|||||
70 | * ``` |
||||||
71 | * |
||||||
72 | 36 | * The parsed `$rules` will contain: |
|||||
73 | * |
||||||
74 | * ```php |
||||||
75 | 40 | * [ |
|||||
76 | * new Nested([ |
||||||
77 | 40 | * 'title' => [new Length(max: 255)], |
|||||
78 | * 'author' => new Nested([ |
||||||
79 | * 'name' => [new Length(min: 1)], |
||||||
80 | 30 | * ]), |
|||||
81 | * 'files' => new Each([ |
||||||
82 | 30 | * new Nested([ |
|||||
83 | * 'url' => [new Url()], |
||||||
84 | * ]), |
||||||
85 | 26 | * ]), |
|||||
86 | * ]); |
||||||
87 | 26 | * ]; |
|||||
88 | 26 | * ``` |
|||||
89 | * |
||||||
90 | 22 | * And the result of `$data` will be: |
|||||
91 | * |
||||||
92 | * ```php |
||||||
93 | 26 | * [ |
|||||
94 | * 'title' => 'Yii3 Overview 3', |
||||||
95 | * 'author' => 'John', |
||||||
96 | * 'files' => [], |
||||||
97 | * ]; |
||||||
98 | * ``` |
||||||
99 | 66 | * |
|||||
100 | * A class name string is valid as a source too. This way only rules will be parsed: |
||||||
101 | 66 | * |
|||||
102 | * ```php |
||||||
103 | 46 | * $parser = new ObjectParser(Post::class); |
|||||
104 | * $rules = $parser->getRules(); // The result is the same as in previous example. |
||||||
105 | * $data = $parser->getData(); // Returns empty array. |
||||||
106 | 52 | * ``` |
|||||
107 | 52 | * |
|||||
108 | * Please refer to the guide for more examples. |
||||||
109 | 52 | * |
|||||
110 | 50 | * Note that the rule attributes can be combined with others without affecting parsing. Which properties to parse can be |
|||||
111 | 1 | * configured via {@see ObjectParser::$propertyVisibility} and {@see ObjectParser::$skipStaticProperties} options. |
|||||
112 | * |
||||||
113 | * Uses Reflection for getting object data and metadata. Supports caching for Reflection of a class / an obhect with |
||||||
114 | 50 | * properties and rules which can be disabled on demand. |
|||||
115 | 50 | * |
|||||
116 | * @link https://www.php.net/manual/en/language.attributes.overview.php |
||||||
117 | * |
||||||
118 | 50 | * @psalm-type RulesCacheItem = array{0:RuleInterface,1:Attribute::TARGET_*} |
|||||
119 | */ |
||||||
120 | final class ObjectParser |
||||||
121 | 52 | { |
|||||
122 | 50 | /** |
|||||
123 | * @var array A cache storage utilizing static class property: |
||||||
124 | * |
||||||
125 | 52 | * - The first nesting level is a mapping between cache keys (dynamically generated on instantiation) and item names |
|||||
126 | * (one of: `rules`, `reflectionProperties`, `reflectionSource`). |
||||||
127 | * - The second nesting level is a mapping between cache item names and their contents. |
||||||
128 | 66 | * |
|||||
129 | * Different properties' combinations of the same object are cached separately. |
||||||
130 | * |
||||||
131 | * @psalm-var array<string, array<string, mixed>> |
||||||
132 | 66 | */ |
|||||
133 | 2 | #[ArrayShape([ |
|||||
134 | [ |
||||||
135 | 'rules' => 'array', |
||||||
136 | 64 | 'reflectionAttributes' => 'array', |
|||||
137 | 50 | 'reflectionSource' => 'object', |
|||||
138 | 'labels' => 'array', |
||||||
139 | ], |
||||||
140 | 47 | ])] |
|||||
141 | private static array $cache = []; |
||||||
142 | /** |
||||||
143 | 47 | * @var string|null A cache key. Dynamically generated on instantiation. |
|||||
144 | */ |
||||||
145 | private string|null $cacheKey = null; |
||||||
146 | |||||||
147 | /** |
||||||
148 | 47 | * @throws InvalidArgumentException If a class name string provided in {@see $source} refers to a non-existing |
|||||
149 | * class. |
||||||
150 | */ |
||||||
151 | 50 | public function __construct( |
|||||
152 | /** |
||||||
153 | * @var object|string A source for parsing rules and data. Can be either a class name string or an |
||||||
154 | * instance. |
||||||
155 | * |
||||||
156 | * @psalm-var class-string|object |
||||||
157 | 50 | */ |
|||||
158 | private string|object $source, |
||||||
159 | /** |
||||||
160 | * @var int Visibility levels the parsed properties must have. For example: public and protected only, this |
||||||
161 | * means that the rest (private ones) will be skipped. Defaults to all visibility levels (public, protected and |
||||||
162 | * private). |
||||||
163 | 66 | * |
|||||
164 | * @psalm-var int-mask-of<ReflectionProperty::IS_*> |
||||||
165 | 66 | */ |
|||||
166 | private int $propertyVisibility = ReflectionProperty::IS_PRIVATE | |
||||||
167 | ReflectionProperty::IS_PROTECTED | |
||||||
168 | ReflectionProperty::IS_PUBLIC, |
||||||
169 | /** |
||||||
170 | * @var bool Whether the properties with "static" modifier must be skipped. |
||||||
171 | */ |
||||||
172 | private bool $skipStaticProperties = false, |
||||||
173 | /** |
||||||
174 | * @var bool Whether some results of parsing (Reflection of a class / an object with properties and rules) must |
||||||
175 | * be cached. |
||||||
176 | */ |
||||||
177 | bool $useCache = true, |
||||||
178 | ) { |
||||||
179 | /** @var object|string $source */ |
||||||
180 | if (is_string($source) && !class_exists($source)) { |
||||||
181 | throw new InvalidArgumentException( |
||||||
182 | sprintf('Class "%s" not found.', $source) |
||||||
183 | ); |
||||||
184 | } |
||||||
185 | |||||||
186 | if ($useCache) { |
||||||
187 | $this->cacheKey = (is_object($source) ? $source::class : $source) |
||||||
188 | . '_' . $this->propertyVisibility |
||||||
189 | . '_' . (int) $this->skipStaticProperties; |
||||||
190 | } |
||||||
191 | } |
||||||
192 | |||||||
193 | /** |
||||||
194 | * Parses rules specified via attributes attached to class properties and class itself. Repetitive calls utilize |
||||||
195 | * cache if it's enabled in {@see $useCache}. |
||||||
196 | * |
||||||
197 | * @return array<int, RuleInterface>|array<string, list<RuleInterface>> The resulting rules array with the following |
||||||
0 ignored issues
–
show
Documentation
Bug
introduced
by
![]() |
|||||||
198 | * structure: |
||||||
199 | * |
||||||
200 | * ```php |
||||||
201 | * [ |
||||||
202 | * [new AtLeast(['name', 'author'])], // Parsed from class attribute. |
||||||
203 | * 'files' => [new Count(max: 3)], // Parsed from property attribute. |
||||||
204 | * ], |
||||||
205 | * ``` |
||||||
206 | */ |
||||||
207 | public function getRules(): array |
||||||
208 | { |
||||||
209 | if ($this->hasCacheItem('rules')) { |
||||||
210 | /** @var array $rules */ |
||||||
211 | $rules = $this->getCacheItem('rules'); |
||||||
212 | return $this->prepareRules($rules); |
||||||
213 | } |
||||||
214 | |||||||
215 | $rules = []; |
||||||
216 | |||||||
217 | // Class rules |
||||||
218 | $attributes = $this |
||||||
219 | ->getReflectionSource() |
||||||
220 | ->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF); |
||||||
221 | foreach ($attributes as $attribute) { |
||||||
222 | $rules[] = [$attribute->newInstance(), Attribute::TARGET_CLASS]; |
||||||
223 | } |
||||||
224 | |||||||
225 | // Properties rules |
||||||
226 | foreach ($this->getReflectionProperties() as $property) { |
||||||
227 | // TODO: use Generator to collect attributes. |
||||||
228 | $attributes = $property->getAttributes(RuleInterface::class, ReflectionAttribute::IS_INSTANCEOF); |
||||||
229 | foreach ($attributes as $attribute) { |
||||||
230 | /** @psalm-suppress UndefinedInterfaceMethod */ |
||||||
231 | $rules[$property->getName()][] = [$attribute->newInstance(), Attribute::TARGET_PROPERTY]; |
||||||
232 | } |
||||||
233 | } |
||||||
234 | |||||||
235 | $this->setCacheItem('rules', $rules); |
||||||
236 | |||||||
237 | return $this->prepareRules($rules); |
||||||
238 | } |
||||||
239 | |||||||
240 | /** |
||||||
241 | * Parses labels specified via {@see Label} attributes attached to class properties. |
||||||
242 | * |
||||||
243 | * @return array<string, string> |
||||||
244 | */ |
||||||
245 | public function getLabels(): array |
||||||
246 | { |
||||||
247 | if ($this->hasCacheItem('labels')) { |
||||||
248 | /** @var array<string, string> */ |
||||||
249 | return $this->getCacheItem('labels'); |
||||||
250 | } |
||||||
251 | |||||||
252 | $labels = []; |
||||||
253 | |||||||
254 | foreach ($this->getReflectionProperties() as $property) { |
||||||
255 | $attributes = $property->getAttributes(Label::class, ReflectionAttribute::IS_INSTANCEOF); |
||||||
256 | foreach ($attributes as $attribute) { |
||||||
257 | /** @var Label $instance */ |
||||||
258 | $instance = $attribute->newInstance(); |
||||||
259 | $labels[$property->getName()] = $instance->getLabel(); |
||||||
260 | } |
||||||
261 | } |
||||||
262 | |||||||
263 | $this->setCacheItem('labels', $labels); |
||||||
264 | |||||||
265 | return $labels; |
||||||
266 | } |
||||||
267 | |||||||
268 | /** |
||||||
269 | * Returns a property value of the parsed object. |
||||||
270 | * |
||||||
271 | * Note that in case of non-existing property a default `null` value is returned. If you need to check the presence |
||||||
272 | * of a property or return a different default value, use {@see hasAttribute()} instead. |
||||||
273 | * |
||||||
274 | * If a {@see $source} is a class name string, `null` value is always returned. |
||||||
275 | * |
||||||
276 | * @param string $attribute Attribute name. |
||||||
277 | * |
||||||
278 | * @return mixed Attribute value. |
||||||
279 | */ |
||||||
280 | public function getAttributeValue(string $attribute): mixed |
||||||
281 | { |
||||||
282 | return is_object($this->source) |
||||||
283 | ? ($this->getReflectionProperties()[$attribute] ?? null)?->getValue($this->source) |
||||||
284 | : null; |
||||||
285 | } |
||||||
286 | |||||||
287 | /** |
||||||
288 | * Whether the parsed object has the property with a given name. Note that this means existence only and properties |
||||||
289 | * with empty values are treated as present too. |
||||||
290 | * |
||||||
291 | * If a {@see $source} is a class name string, `false` value is always returned. |
||||||
292 | * |
||||||
293 | * @return bool Whether the property exists: `true` - exists and `false` - otherwise. |
||||||
294 | */ |
||||||
295 | public function hasAttribute(string $attribute): bool |
||||||
296 | { |
||||||
297 | return is_object($this->source) && array_key_exists($attribute, $this->getReflectionProperties()); |
||||||
298 | } |
||||||
299 | |||||||
300 | /** |
||||||
301 | * Returns the parsed object's data as a whole in a form of associative array. |
||||||
302 | * |
||||||
303 | * If a {@see $source} is a class name string, an empty array is always returned. |
||||||
304 | * |
||||||
305 | * @return array A mapping between property names and their values. |
||||||
306 | */ |
||||||
307 | public function getData(): array |
||||||
308 | { |
||||||
309 | if (!is_object($this->source)) { |
||||||
310 | return []; |
||||||
311 | } |
||||||
312 | |||||||
313 | $data = []; |
||||||
314 | foreach ($this->getReflectionProperties() as $name => $property) { |
||||||
315 | /** @var mixed */ |
||||||
316 | $data[$name] = $property->getValue($this->source); |
||||||
317 | } |
||||||
318 | |||||||
319 | return $data; |
||||||
320 | } |
||||||
321 | |||||||
322 | /** |
||||||
323 | * An optional attribute names translator. It's taken from the {@see $source} object when |
||||||
324 | * {@see AttributeTranslatorProviderInterface} is implemented. In case of it's missing or {@see $source} being a |
||||||
325 | * class name string, a `null` value is returned. |
||||||
326 | * |
||||||
327 | * @return AttributeTranslatorInterface|null An attribute translator instance or `null if it was not provided. |
||||||
328 | */ |
||||||
329 | public function getAttributeTranslator(): ?AttributeTranslatorInterface |
||||||
330 | { |
||||||
331 | return $this->source instanceof AttributeTranslatorProviderInterface |
||||||
332 | ? $this->source->getAttributeTranslator() |
||||||
333 | : null; |
||||||
334 | } |
||||||
335 | |||||||
336 | /** |
||||||
337 | * Returns Reflection properties parsed from {@see $source} in accordance with {@see $propertyVisibility} and |
||||||
338 | * {@see $skipStaticProperties} values. Repetitive calls utilize cache if it's enabled in {@see $useCache}. |
||||||
339 | * |
||||||
340 | * @return array<string, ReflectionProperty> A mapping between Reflection property names and their values. |
||||||
341 | * |
||||||
342 | * @see https://github.com/yiisoft/form for usage in form collector. |
||||||
343 | */ |
||||||
344 | public function getReflectionProperties(): array |
||||||
345 | { |
||||||
346 | if ($this->hasCacheItem('reflectionProperties')) { |
||||||
347 | /** @var array<string, ReflectionProperty> */ |
||||||
348 | return $this->getCacheItem('reflectionProperties'); |
||||||
349 | } |
||||||
350 | |||||||
351 | $reflectionProperties = []; |
||||||
352 | foreach ($this->getReflectionSource()->getProperties($this->propertyVisibility) as $property) { |
||||||
353 | if ($this->skipStaticProperties && $property->isStatic()) { |
||||||
354 | continue; |
||||||
355 | } |
||||||
356 | |||||||
357 | /** @infection-ignore-all */ |
||||||
358 | if (PHP_VERSION_ID < 80100) { |
||||||
359 | /** @psalm-suppress UnusedMethodCall Need for psalm with PHP 8.1+ */ |
||||||
360 | $property->setAccessible(true); |
||||||
361 | } |
||||||
362 | |||||||
363 | $reflectionProperties[$property->getName()] = $property; |
||||||
364 | } |
||||||
365 | |||||||
366 | $this->setCacheItem('reflectionProperties', $reflectionProperties); |
||||||
367 | |||||||
368 | return $reflectionProperties; |
||||||
369 | } |
||||||
370 | |||||||
371 | /** |
||||||
372 | * Returns Reflection of {@see $source}. Repetitive calls utilize cache if it's enabled in {@see $useCache}. |
||||||
373 | * |
||||||
374 | * @return ReflectionClass|ReflectionObject Either a Reflection class or an object instance depending on what was |
||||||
375 | * provided in {@see $source}. |
||||||
376 | */ |
||||||
377 | private function getReflectionSource(): ReflectionObject|ReflectionClass |
||||||
378 | { |
||||||
379 | if ($this->hasCacheItem('reflectionSource')) { |
||||||
380 | /** @var ReflectionClass|ReflectionObject */ |
||||||
381 | return $this->getCacheItem('reflectionSource'); |
||||||
382 | } |
||||||
383 | |||||||
384 | $reflectionSource = is_object($this->source) |
||||||
385 | ? new ReflectionObject($this->source) |
||||||
386 | : new ReflectionClass($this->source); |
||||||
387 | |||||||
388 | $this->setCacheItem('reflectionSource', $reflectionSource); |
||||||
389 | |||||||
390 | return $reflectionSource; |
||||||
391 | } |
||||||
392 | |||||||
393 | /** |
||||||
394 | * @psalm-param array $source Raw rules containing additional metadata besides rule instances. |
||||||
395 | * |
||||||
396 | * @return array<int, RuleInterface>|array<string, list<RuleInterface>> An array of rules ready to use for the |
||||||
0 ignored issues
–
show
|
|||||||
397 | * validation. |
||||||
398 | */ |
||||||
399 | private function prepareRules(array $source): array |
||||||
400 | { |
||||||
401 | $rules = []; |
||||||
402 | /** |
||||||
403 | * @var mixed $data |
||||||
404 | */ |
||||||
405 | foreach ($source as $key => $data) { |
||||||
406 | if (is_int($key)) { |
||||||
407 | /** @psalm-var RulesCacheItem $data */ |
||||||
408 | $rules[$key] = $this->prepareRule($data[0], $data[1]); |
||||||
409 | } else { |
||||||
410 | /** |
||||||
411 | * @psalm-var list<RulesCacheItem> $data |
||||||
412 | * |
||||||
413 | * @psalm-suppress UndefinedInterfaceMethod |
||||||
414 | */ |
||||||
415 | foreach ($data as $rule) { |
||||||
416 | $rules[$key][] = $this->prepareRule($rule[0], $rule[1]); |
||||||
417 | } |
||||||
418 | } |
||||||
419 | } |
||||||
420 | return $rules; |
||||||
421 | } |
||||||
422 | |||||||
423 | /** |
||||||
424 | * Prepares a rule instance created from a Reflection attribute to use for the validation. |
||||||
425 | * |
||||||
426 | * @param RuleInterface $rule A rule instance. |
||||||
427 | * @param Attribute::TARGET_* $target {@see Attribute} target. |
||||||
428 | * |
||||||
429 | * @return RuleInterface The same rule instance. |
||||||
430 | */ |
||||||
431 | private function prepareRule(RuleInterface $rule, int $target): RuleInterface |
||||||
432 | { |
||||||
433 | if (is_object($this->source) && $rule instanceof AfterInitAttributeEventInterface) { |
||||||
434 | $rule->afterInitAttribute($this->source, $target); |
||||||
0 ignored issues
–
show
The call to
Yiisoft\Validator\AfterI...e::afterInitAttribute() has too many arguments starting with $target .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue. If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above. ![]() |
|||||||
435 | } |
||||||
436 | return $rule; |
||||||
437 | } |
||||||
438 | |||||||
439 | /** |
||||||
440 | * Whether a cache item with a given name exists in the cache. Note that this means existence only and items with |
||||||
441 | * empty values are treated as present too. |
||||||
442 | * |
||||||
443 | * @param string $name Cache item name. Can be on of: `rules`, `reflectionProperties`, `reflectionSource`. |
||||||
444 | * |
||||||
445 | * @return bool `true` if an item exists, `false` - if it does not or the cache is disabled in {@see $useCache}. |
||||||
446 | */ |
||||||
447 | private function hasCacheItem( |
||||||
448 | #[ExpectedValues(['rules', 'reflectionProperties', 'reflectionSource', 'labels'])] |
||||||
449 | string $name, |
||||||
450 | ): bool { |
||||||
451 | if (!$this->useCache()) { |
||||||
452 | return false; |
||||||
453 | } |
||||||
454 | |||||||
455 | if (!array_key_exists($this->cacheKey, self::$cache)) { |
||||||
456 | return false; |
||||||
457 | } |
||||||
458 | |||||||
459 | return array_key_exists($name, self::$cache[$this->cacheKey]); |
||||||
460 | } |
||||||
461 | |||||||
462 | /** |
||||||
463 | * Returns a cache item by its name. |
||||||
464 | * |
||||||
465 | * @param string $name Cache item name. Can be on of: `rules`, `reflectionProperties`, `reflectionSource`. |
||||||
466 | * |
||||||
467 | * @return mixed Cache item value. |
||||||
468 | */ |
||||||
469 | private function getCacheItem( |
||||||
470 | #[ExpectedValues(['rules', 'reflectionProperties', 'reflectionSource', 'labels'])] |
||||||
471 | string $name, |
||||||
472 | ): mixed { |
||||||
473 | /** @psalm-suppress PossiblyNullArrayOffset */ |
||||||
474 | return self::$cache[$this->cacheKey][$name]; |
||||||
475 | } |
||||||
476 | |||||||
477 | /** |
||||||
478 | * Updates cache item contents by its name. |
||||||
479 | * |
||||||
480 | * @param string $name Cache item name. Can be on of: `rules`, `reflectionProperties`, `reflectionSource`. |
||||||
481 | * @param mixed $value A new value. |
||||||
482 | */ |
||||||
483 | private function setCacheItem( |
||||||
484 | #[ExpectedValues(['rules', 'reflectionProperties', 'reflectionSource', 'labels'])] |
||||||
485 | string $name, |
||||||
486 | mixed $value, |
||||||
487 | ): void { |
||||||
488 | if (!$this->useCache()) { |
||||||
489 | return; |
||||||
490 | } |
||||||
491 | |||||||
492 | /** @psalm-suppress PossiblyNullArrayOffset, MixedAssignment */ |
||||||
493 | self::$cache[$this->cacheKey][$name] = $value; |
||||||
494 | } |
||||||
495 | |||||||
496 | /** |
||||||
497 | * Whether the cache is enabled / can be used for a particular object. |
||||||
498 | * |
||||||
499 | * @psalm-assert string $this->cacheKey |
||||||
500 | * |
||||||
501 | * @return bool `true` if the cache is enabled / can be used and `false` otherwise. |
||||||
502 | */ |
||||||
503 | private function useCache(): bool |
||||||
504 | { |
||||||
505 | return $this->cacheKey !== null; |
||||||
506 | } |
||||||
507 | } |
||||||
508 |