Passed
Push — misc/create-event-dispatching-... ( d93e6c )
by Chema
05:17 queued 47s
created

AbstractClassResolver.php$1 ➔ dispatchEvent()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gacela\Framework\ClassResolver;
6
7
use Gacela\Framework\AbstractConfig;
8
use Gacela\Framework\AbstractFactory;
9
use Gacela\Framework\ClassResolver\Cache\GacelaFileCache;
10
use Gacela\Framework\ClassResolver\ClassNameFinder\ClassNameFinderInterface;
11
use Gacela\Framework\ClassResolver\Config\ConfigResolver;
12
use Gacela\Framework\ClassResolver\Factory\FactoryResolver;
13
use Gacela\Framework\ClassResolver\GlobalInstance\AnonymousGlobal;
14
use Gacela\Framework\ClassResolver\InstanceCreator\InstanceCreator;
15
use Gacela\Framework\Config\Config;
16
use Gacela\Framework\Config\GacelaFileConfig\GacelaConfigFileInterface;
17
use Gacela\Framework\Event\ClassResolver\ResolvedClassCachedEvent;
18
use Gacela\Framework\Event\ClassResolver\ResolvedClassCreatedEvent;
19
use Gacela\Framework\Event\ClassResolver\ResolvedClassTriedFromParentEvent;
20
use Gacela\Framework\Event\ClassResolver\ResolvedCreatedDefaultClassEvent;
21
use Gacela\Framework\Event\Dispatcher\EventDispatchingCapabilities;
22
23
use function is_array;
24
use function is_object;
25
26
abstract class AbstractClassResolver
27
{
28
    use EventDispatchingCapabilities;
29
30
    /** @var array<string,null|object> */
31
    private static array $cachedInstances = [];
32
33
    private ?ClassNameFinderInterface $classNameFinder = null;
34
35
    private ?GacelaConfigFileInterface $gacelaFileConfig = null;
36
37
    private ?InstanceCreator $instanceCreator = null;
38
39
    /**
40
     * @internal remove all cached instances: facade, factory, config, dependency-provider
41
     */
42 16
    public static function resetCache(): void
43
    {
44 16
        self::$cachedInstances = [];
45
    }
46
47
    /**
48
     * @param object|class-string $caller
0 ignored issues
show
Documentation Bug introduced by
The doc comment object|class-string at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in object|class-string.
Loading history...
49
     */
50
    abstract public function resolve($caller): ?object;
51
52
    /**
53
     * @param object|class-string $caller
0 ignored issues
show
Documentation Bug introduced by
The doc comment object|class-string at position 2 could not be parsed: Unknown type name 'class-string' at position 2 in object|class-string.
Loading history...
54
     */
55 60
    public function doResolve($caller, ?string $previousCacheKey = null): ?object
56
    {
57 60
        $classInfo = ClassInfo::from($caller, $this->getResolvableType());
58 60
        $cacheKey = $previousCacheKey ?? $classInfo->getCacheKey();
59
60 60
        $resolvedClass = $this->resolveCached($cacheKey);
61 60
        if ($resolvedClass !== null) {
62 30
            $this->dispatchEvent(new ResolvedClassCachedEvent($classInfo));
63
64 30
            return $resolvedClass;
65
        }
66
67 39
        $resolvedClassName = $this->findClassName($classInfo);
68 39
        if ($resolvedClassName !== null) {
69 34
            $instance = $this->createInstance($resolvedClassName);
70 34
            $this->dispatchEvent(new ResolvedClassCreatedEvent($classInfo));
71
        } else {
72
            // Try again with its parent class
73 7
            if (is_object($caller)) {
74 7
                $parentClass = get_parent_class($caller);
75 7
                if ($parentClass !== false) {
76 7
                    $this->dispatchEvent(new ResolvedClassTriedFromParentEvent($classInfo));
77
78 7
                    return $this->doResolve($parentClass, $cacheKey);
79
                }
80
            }
81
82 6
            $this->dispatchEvent(new ResolvedCreatedDefaultClassEvent($classInfo));
83 6
            $instance = $this->createDefaultGacelaClass();
84
        }
85
86 39
        self::$cachedInstances[$cacheKey] = $instance;
87
88 39
        return self::$cachedInstances[$cacheKey];
89
    }
90
91
    abstract protected function getResolvableType(): string;
92
93 60
    private function resolveCached(string $cacheKey): ?object
94
    {
95 60
        return AnonymousGlobal::getByKey($cacheKey)
96 59
            ?? self::$cachedInstances[$cacheKey]
97
            ?? null;
98
    }
99
100
    /**
101
     * @return class-string|null
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string|null at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string|null.
Loading history...
102
     */
103 39
    private function findClassName(ClassInfo $classInfo): ?string
104
    {
105 39
        return $this->getClassNameFinder()->findClassName(
106
            $classInfo,
107 39
            $this->getPossibleResolvableTypes()
108
        );
109
    }
110
111 39
    private function getClassNameFinder(): ClassNameFinderInterface
112
    {
113 39
        if ($this->classNameFinder === null) {
114 39
            $this->classNameFinder = (new ClassResolverFactory(
115 39
                new GacelaFileCache(Config::getInstance()),
116 39
                Config::getInstance()->getSetupGacela()
117 39
            ))->createClassNameFinder();
118
        }
119
120 39
        return $this->classNameFinder;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->classNameFinder could return the type null which is incompatible with the type-hinted return Gacela\Framework\ClassRe...lassNameFinderInterface. Consider adding an additional type-check to rule them out.
Loading history...
121
    }
122
123
    /**
124
     * Allow overriding gacela suffixes resolvable types.
125
     *
126
     * @return list<string>
127
     */
128 39
    private function getPossibleResolvableTypes(): array
129
    {
130 39
        $suffixTypes = $this->getGacelaConfigFile()->getSuffixTypes();
131
132 39
        $resolvableTypes = $suffixTypes[$this->getResolvableType()] ?? $this->getResolvableType();
133
134 39
        return is_array($resolvableTypes) ? $resolvableTypes : [$resolvableTypes];
135
    }
136
137
    /**
138
     * @param class-string $resolvedClassName
139
     */
140 34
    private function createInstance(string $resolvedClassName): ?object
141
    {
142 34
        if ($this->instanceCreator === null) {
143 34
            $this->instanceCreator = new InstanceCreator($this->getGacelaConfigFile());
144
        }
145
146 34
        return $this->instanceCreator->createByClassName($resolvedClassName);
0 ignored issues
show
Bug introduced by
The method createByClassName() does not exist on null. ( Ignorable by Annotation )

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

146
        return $this->instanceCreator->/** @scrutinizer ignore-call */ createByClassName($resolvedClassName);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
147
    }
148
149 39
    private function getGacelaConfigFile(): GacelaConfigFileInterface
150
    {
151 39
        if ($this->gacelaFileConfig === null) {
152 39
            $this->gacelaFileConfig = Config::getInstance()
153 39
                ->getFactory()
154 39
                ->createGacelaFileConfig();
155
        }
156
157 39
        return $this->gacelaFileConfig;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->gacelaFileConfig could return the type null which is incompatible with the type-hinted return Gacela\Framework\Config\...celaConfigFileInterface. Consider adding an additional type-check to rule them out.
Loading history...
158
    }
159
160 6
    private function createDefaultGacelaClass(): ?object
161
    {
162 6
        switch ($this->getResolvableType()) {
163
            case FactoryResolver::TYPE:
164 3
                return new class() extends AbstractFactory {
165
                };
166 5
            case ConfigResolver::TYPE:
167 4
                return new class() extends AbstractConfig {
168
                };
169
            default:
170 1
                return null;
171
        }
172
    }
173
}
174