Issues (3641)

Kernel/ClassResolver/AbstractClassResolver.php (1 issue)

1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace Spryker\Shared\Kernel\ClassResolver;
9
10
use RuntimeException;
11
use Spryker\Shared\Kernel\ClassResolver\ClassNameFinder\ClassNameFinderInterface;
12
use Spryker\Shared\Kernel\ClassResolver\ResolvableCache\CacheReader\CacheReaderInterface;
13
use Spryker\Shared\Kernel\ClassResolver\ResolvableCache\CacheReader\CacheReaderPhp;
14
use Spryker\Shared\Kernel\KernelConfig;
15
use Spryker\Shared\Kernel\KernelSharedFactory;
16
17
abstract class AbstractClassResolver
18
{
19
    /**
20
     * @var string
21
     */
22
    public const KEY_NAMESPACE = '%namespace%';
23
24
    /**
25
     * @var string
26
     */
27
    public const KEY_BUNDLE = '%bundle%';
28
29
    /**
30
     * @var string
31
     */
32
    public const KEY_CODE_BUCKET = '%codeBucket%';
33
34
    /**
35
     * @var string|null
36
     */
37
    protected const CLASS_NAME_PATTERN = null;
38
39
    /**
40
     * @var string|null
41
     */
42
    protected const RESOLVABLE_TYPE = null;
43
44
    /**
45
     * @var array|null
46
     */
47
    protected static $resolvableClassNamesCache;
48
49
    /**
50
     * @var bool|null
51
     */
52
    protected static $isCacheEnabled;
53
54
    /**
55
     * @var bool|null
56
     */
57
    protected static $isInstanceCacheEnabled;
58
59
    /**
60
     * @var string|null
61
     */
62
    protected static $storeName;
63
64
    /**
65
     * @var array|null
66
     */
67
    protected static $projectNamespaces;
68
69
    /**
70
     * @var array|null
71
     */
72
    protected static $coreNamespaces;
73
74
    /**
75
     * @var \Spryker\Shared\Kernel\KernelConfig
76
     */
77
    protected static $sharedConfig;
78
79
    /**
80
     * @var \Spryker\Shared\Kernel\KernelSharedFactory
81
     */
82
    protected static $sharedFactory;
83
84
    /**
85
     * @var array<string>|null
86
     */
87
    protected static $resolvableTypeClassNamePatternMap;
88
89
    /**
90
     * @var \Spryker\Shared\Kernel\ClassResolver\ClassNameFinder\ClassNameFinderInterface
91
     */
92
    protected static $classNameFinder;
93
94
    /**
95
     * @var string
96
     */
97
    protected $resolvedClassName;
98
99
    /**
100
     * @var \Spryker\Shared\Kernel\ClassResolver\ClassInfo|null
101
     */
102
    protected $classInfo;
103
104
    /**
105
     * @var array<object>
106
     */
107
    protected static $cachedInstances = [];
108
109
    /**
110
     * @var \Spryker\Shared\Kernel\ClassResolver\ResolverCacheFactoryInterface
111
     */
112
    protected static $resolverCacheManager;
113
114
    /**
115
     * @var bool|null
116
     */
117
    protected static $useResolverCache;
118
119
    /**
120
     * @param object|string $callerClass
121
     *
122
     * @return object|null
123
     */
124
    abstract public function resolve($callerClass);
125
126
    /**
127
     * @param string $namespace
128
     * @param string|null $codeBucket
129
     *
130
     * @return string
131
     */
132
    abstract protected function buildClassName($namespace, $codeBucket = null);
133
134
    /**
135
     * @param object|string $callerClass
136
     *
137
     * @return object|null
138
     */
139
    public function doResolve($callerClass)
140
    {
141
        $this->setCallerClass($callerClass);
142
143
        $cacheKey = $this->findCacheKey();
144
        $isInstanceCacheEnabled = $this->isInstanceCacheEnabled();
145
146
        if ($cacheKey !== null && $isInstanceCacheEnabled && isset(static::$cachedInstances[$cacheKey])) {
147
            return static::$cachedInstances[$cacheKey];
148
        }
149
150
        $resolvedClassName = $this->resolveClassName($cacheKey);
151
152
        if ($resolvedClassName !== null) {
153
            $resolvedInstance = $this->createInstance($resolvedClassName);
154
            if ($cacheKey !== null && $isInstanceCacheEnabled) {
155
                static::$cachedInstances[$cacheKey] = $resolvedInstance;
156
            }
157
158
            return $resolvedInstance;
159
        }
160
161
        return null;
162
    }
163
164
    /**
165
     * @param object|string $callerClass
166
     *
167
     * @return $this
168
     */
169
    public function setCallerClass($callerClass)
170
    {
171
        $this->classInfo = new ClassInfo();
172
        $this->classInfo->setClass($callerClass);
173
174
        return $this;
175
    }
176
177
    /**
178
     * @return \Spryker\Shared\Kernel\ClassResolver\ClassInfo
179
     */
180
    public function getClassInfo()
181
    {
182
        /** @var \Spryker\Shared\Kernel\ClassResolver\ClassInfo $classInfo */
183
        $classInfo = $this->classInfo;
184
185
        return $classInfo;
186
    }
187
188
    /**
189
     * @return bool
190
     */
191
    public function canResolve()
192
    {
193
        $cacheKey = null;
194
        if ($this->canUseCaching()) {
195
            $cacheKey = $this->getCacheKey();
196
197
            if ($this->hasCache($cacheKey)) {
198
                $this->resolvedClassName = $this->getCached($cacheKey);
199
200
                return true;
201
            }
202
        }
203
204
        $classNames = $this->buildClassNames();
205
206
        foreach ($classNames as $className) {
207
            if ($this->classExists($className)) {
208
                $this->resolvedClassName = $className;
209
210
                if ($cacheKey !== null) {
211
                    $this->addCache($cacheKey, $className);
212
                }
213
214
                return true;
215
            }
216
        }
217
218
        return false;
219
    }
220
221
    /**
222
     * @deprecated Will be removed without replacement. This is no longer required as all class name patterns are now
223
     * configurable in {@link \Spryker\Shared\Kernel\KernelConfig::getResolvableTypeClassNamePatternMap}
224
     *
225
     * @return string
226
     */
227
    protected function getClassPattern()
228
    {
229
        return '';
230
    }
231
232
    /**
233
     * @return string|null
234
     */
235
    protected function findCacheKey(): ?string
236
    {
237
        if (!$this->isCacheEnabled()) {
238
            return null;
239
        }
240
241
        return $this->getCacheKey();
242
    }
243
244
    /**
245
     * @param string|null $cacheKey
246
     *
247
     * @return string|null
248
     */
249
    protected function resolveClassName(?string $cacheKey = null): ?string
250
    {
251
        if ($cacheKey !== null && $this->hasCache($cacheKey)) {
252
            return $this->getCached($cacheKey);
253
        }
254
255
        return $this->findClassName();
256
    }
257
258
    /**
259
     * @return string|null
260
     */
261
    protected function findClassName(): ?string
262
    {
263
        $classNamePattern = $this->getResolvableTypeClassNamePatternMap()[static::RESOLVABLE_TYPE] ?? null;
264
        if ($classNamePattern === null) {
265
            return null;
266
        }
267
268
        return $this->getClassNameFinder()->findClassName($this->getClassInfo()->getModule(), $classNamePattern);
269
    }
270
271
    /**
272
     * @return array<string>
273
     */
274
    protected function getResolvableTypeClassNamePatternMap(): array
275
    {
276
        if (static::$resolvableTypeClassNamePatternMap === null) {
277
            static::$resolvableTypeClassNamePatternMap = $this->getSharedConfig()->getResolvableTypeClassNamePatternMap();
278
        }
279
280
        return static::$resolvableTypeClassNamePatternMap;
281
    }
282
283
    /**
284
     * @return \Spryker\Shared\Kernel\ClassResolver\ClassNameFinder\ClassNameFinderInterface
285
     */
286
    protected function getClassNameFinder(): ClassNameFinderInterface
287
    {
288
        if (static::$classNameFinder === null) {
289
            static::$classNameFinder = $this->getSharedFactory()->createClassNameFinder();
290
        }
291
292
        return static::$classNameFinder;
293
    }
294
295
    /**
296
     * @param string|null $resolvedClassName
297
     *
298
     * @return object
299
     */
300
    protected function createInstance(?string $resolvedClassName = null)
301
    {
302
        if ($resolvedClassName !== null) {
303
            return new $resolvedClassName();
304
        }
305
306
        return new $this->resolvedClassName();
307
    }
308
309
    /**
310
     * @param string $cacheKey
311
     *
312
     * @return bool
313
     */
314
    protected function hasCache(string $cacheKey): bool
315
    {
316
        $cache = $this->getCache();
317
318
        return isset($cache[$cacheKey]);
319
    }
320
321
    /**
322
     * @param string $cacheKey
323
     *
324
     * @return string
325
     */
326
    protected function getCached(string $cacheKey): string
327
    {
328
        $cache = $this->getCache();
329
330
        return str_replace('\\\\', '\\', $cache[$cacheKey]);
331
    }
332
333
    /**
334
     * @param string $cacheKey
335
     * @param string $className
336
     *
337
     * @return void
338
     */
339
    protected function addCache(string $cacheKey, string $className): void
340
    {
341
        $cache = $this->getCache();
342
343
        $cache[$cacheKey] = $className;
344
    }
345
346
    /**
347
     * @return array
348
     */
349
    protected function getCache(): array
350
    {
351
        if (static::$resolvableClassNamesCache === null) {
352
            static::$resolvableClassNamesCache = [];
353
354
            if ($this->canUseCaching()) {
355
                static::$resolvableClassNamesCache = $this->createCacheReader()->read();
356
            }
357
        }
358
359
        return static::$resolvableClassNamesCache;
360
    }
361
362
    /**
363
     * @return \Spryker\Shared\Kernel\ClassResolver\ResolvableCache\CacheReader\CacheReaderInterface
364
     */
365
    protected function createCacheReader(): CacheReaderInterface
366
    {
367
        return new CacheReaderPhp($this->getSharedConfig());
368
    }
369
370
    /**
371
     * @return bool
372
     */
373
    protected function canUseCaching(): bool
374
    {
375
        if ($this->isCacheEnabled() === false || $this->classInfo === null || static::RESOLVABLE_TYPE === null) {
376
            return false;
377
        }
378
379
        return true;
380
    }
381
382
    /**
383
     * @return bool
384
     */
385
    protected function isCacheEnabled(): bool
386
    {
387
        // For PHP versions lower 7.3 class resolver cache might not work because of bug https://bugs.php.net/bug.php?id=75765
388
        if (PHP_VERSION_ID < 70300) {
389
            return false;
390
        }
391
392
        if (static::$isCacheEnabled === null) {
393
            static::$isCacheEnabled = $this->getSharedConfig()->isResolvableClassNameCacheEnabled();
394
        }
395
396
        return static::$isCacheEnabled;
397
    }
398
399
    /**
400
     * @return \Spryker\Shared\Kernel\KernelConfig
401
     */
402
    protected function getSharedConfig(): KernelConfig
403
    {
404
        if (static::$sharedConfig === null) {
405
            static::$sharedConfig = new KernelConfig();
406
        }
407
408
        return static::$sharedConfig;
409
    }
410
411
    /**
412
     * @return \Spryker\Shared\Kernel\KernelSharedFactory
413
     */
414
    protected function getSharedFactory(): KernelSharedFactory
415
    {
416
        if (static::$sharedFactory === null) {
417
            static::$sharedFactory = new KernelSharedFactory();
418
            static::$sharedFactory->setSharedConfig($this->getSharedConfig());
419
        }
420
421
        return static::$sharedFactory;
422
    }
423
424
    /**
425
     * @return bool
426
     */
427
    protected function isInstanceCacheEnabled(): bool
428
    {
429
        if (static::$isInstanceCacheEnabled === null) {
430
            static::$isInstanceCacheEnabled = $this->getSharedConfig()->isResolvedInstanceCacheEnabled();
431
        }
432
433
        return static::$isInstanceCacheEnabled;
434
    }
435
436
    /**
437
     * This is needed to be able to use `canResolve` in a loop for the DependencyInjectorResolver.
438
     * The cache would always return the first found Injector without the reset here.
439
     *
440
     * @deprecated This method can be removed together with the DependencyInjectors
441
     *
442
     * @return void
443
     */
444
    protected function unsetCurrentCacheEntry()
445
    {
446
        if (!$this->canUseCaching()) {
447
            return;
448
        }
449
450
        $cache = $this->getCache();
451
452
        unset($cache[$this->getCacheKey()]);
453
    }
454
455
    /**
456
     * @param string $className
457
     *
458
     * @return bool
459
     */
460
    protected function classExists($className)
461
    {
462
        if (!$this->useResolverCache()) {
463
            return class_exists($className);
464
        }
465
466
        $cacheProvider = $this->getResolverCacheManager()->createClassResolverCacheProvider();
467
468
        return $cacheProvider->getCache()->classExists($className);
469
    }
470
471
    /**
472
     * @return bool
473
     */
474
    protected function useResolverCache(): bool
475
    {
476
        if (static::$useResolverCache === null) {
477
            static::$useResolverCache = $this->getResolverCacheManager()->useCache();
478
        }
479
480
        return static::$useResolverCache;
481
    }
482
483
    /**
484
     * @return \Spryker\Shared\Kernel\ClassResolver\ResolverCacheFactoryInterface
485
     */
486
    protected function getResolverCacheManager(): ResolverCacheFactoryInterface
487
    {
488
        if (static::$resolverCacheManager === null) {
489
            static::$resolverCacheManager = new ResolverCacheManager();
490
        }
491
492
        return static::$resolverCacheManager;
493
    }
494
495
    /**
496
     * @deprecated Use {@link \Spryker\Shared\Kernel\ClassResolver\AbstractClassResolver::getResolverCacheManager} instead.
497
     *
498
     * @return \Spryker\Shared\Kernel\ClassResolver\ResolverCacheFactoryInterface
499
     */
500
    protected function createResolverCacheManager()
501
    {
502
        return new ResolverCacheManager();
0 ignored issues
show
Deprecated Code introduced by
The class Spryker\Shared\Kernel\Cl...er\ResolverCacheManager has been deprecated: Use {@link \Spryker\Shared\Kernel\KernelConstants::RESOLVABLE_CLASS_NAMES_CACHE_ENABLED} instead. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

502
        return /** @scrutinizer ignore-deprecated */ new ResolverCacheManager();
Loading history...
503
    }
504
505
    /**
506
     * @return object
507
     */
508
    protected function getResolvedClassInstance()
509
    {
510
        if (!$this->canUseCaching()) {
511
            return new $this->resolvedClassName();
512
        }
513
514
        $cacheKey = $this->getCacheKey();
515
516
        if (!isset(static::$cachedInstances[$cacheKey])) {
517
            static::$cachedInstances[$cacheKey] = new $this->resolvedClassName();
518
        }
519
520
        return static::$cachedInstances[$cacheKey];
521
    }
522
523
    /**
524
     * @return array<string>
525
     */
526
    private function buildClassNames()
527
    {
528
        $classNames = [];
529
530
        $classNames = $this->addProjectClassNames($classNames);
531
        $classNames = $this->addCoreClassNames($classNames);
532
533
        return $classNames;
534
    }
535
536
    /**
537
     * @param array<string> $classNames
538
     *
539
     * @return array<string>
540
     */
541
    private function addProjectClassNames(array $classNames)
542
    {
543
        $codeBucket = APPLICATION_CODE_BUCKET;
544
        foreach ($this->getProjectNamespaces() as $namespace) {
545
            if ($codeBucket !== '') {
546
                $classNames[] = $this->buildClassName($namespace, $codeBucket);
547
            }
548
549
            $classNames[] = $this->buildClassName($namespace);
550
        }
551
552
        return $classNames;
553
    }
554
555
    /**
556
     * @deprecated Will be removed in the next major without replacement.
557
     *
558
     * @return string
559
     */
560
    protected function getStoreName(): string
561
    {
562
        if (static::$storeName === null) {
563
            static::$storeName = $this->getSharedConfig()->getCurrentStoreName();
564
        }
565
566
        return static::$storeName;
567
    }
568
569
    /**
570
     * @param array<string> $classNames
571
     *
572
     * @return array<string>
573
     */
574
    private function addCoreClassNames(array $classNames)
575
    {
576
        foreach ($this->getCoreNamespaces() as $namespace) {
577
            $classNames[] = $this->buildClassName($namespace);
578
        }
579
580
        return $classNames;
581
    }
582
583
    /**
584
     * @return array<string>
585
     */
586
    protected function getProjectNamespaces()
587
    {
588
        if (static::$projectNamespaces === null) {
589
            static::$projectNamespaces = $this->getSharedConfig()->getProjectOrganizations();
590
        }
591
592
        return static::$projectNamespaces;
593
    }
594
595
    /**
596
     * @return array<string>
597
     */
598
    protected function getCoreNamespaces()
599
    {
600
        if (static::$coreNamespaces === null) {
601
            static::$coreNamespaces = $this->getSharedConfig()->getCoreOrganizations();
602
        }
603
604
        return static::$coreNamespaces;
605
    }
606
607
    /**
608
     * @throws \RuntimeException
609
     *
610
     * @return string
611
     */
612
    protected function getCacheKey(): string
613
    {
614
        if ($this->classInfo === null) {
615
            throw new RuntimeException('Must set $classInfo first using setCallerClass() before accessing it.');
616
        }
617
618
        /** @var string $key */
619
        $key = static::RESOLVABLE_TYPE;
620
621
        return $this->classInfo->getCacheKey($key);
622
    }
623
}
624