Passed
Push — feature/class-resolver-events ( a85101...5f03d5 )
by Chema
04:51 queued 03:31
created

AbstractClassResolver.php$1 ➔ triggerEvent()   A

Complexity

Conditions 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 10
ccs 7
cts 7
cp 1
crap 3
rs 9.9332
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\EventListener\ClassResolver\GacelaClassResolverListener;
18
use Gacela\Framework\EventListener\ClassResolver\ResolvedClassCachedEvent;
19
use Gacela\Framework\EventListener\ClassResolver\ResolvedClassCreatedEvent;
20
use Gacela\Framework\EventListener\ClassResolver\ResolvedClassTryFormParentEvent;
21
use Gacela\Framework\EventListener\ClassResolver\ResolvedDefaultClassEvent;
22
use Gacela\Framework\EventListener\GacelaEventInterface;
23
24
use function is_array;
25
use function is_object;
26
27
abstract class AbstractClassResolver
28
{
29
    /** @var array<string,null|object> */
30
    private static array $cachedInstances = [];
31
32
    /** @var array<string,list<callable>>*/
1 ignored issue
show
Documentation Bug introduced by
The doc comment array<string,list<callable>> at position 4 could not be parsed: Expected '>' at position 4, but found 'list'.
Loading history...
33
    private static array $listeners = [];
34
35
    private ?ClassNameFinderInterface $classNameFinder = null;
36
37
    private ?GacelaConfigFileInterface $gacelaFileConfig = null;
38
39
    private ?InstanceCreator $instanceCreator = null;
40
41
    /**
42
     * @internal remove all cached instances: facade, factory, config, dependency-provider
43
     */
44 11
    public static function resetCache(): void
45
    {
46 11
        self::$cachedInstances = [];
47 11
        self::$listeners = [];
48
    }
49
50
    /**
51
     * @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...
52
     */
53
    abstract public function resolve($caller): ?object;
54
55
    /**
56
     * @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...
57
     */
58 58
    public function doResolve($caller): ?object
59
    {
60 58
        $classInfo = ClassInfo::from($caller, $this->getResolvableType());
61
62 58
        $cacheKey = $classInfo->getCacheKey();
63
64 58
        $resolvedClass = $this->resolveCached($cacheKey);
65 58
        if ($resolvedClass !== null) {
66 31
            $this->triggerEvent(new ResolvedClassCachedEvent($classInfo));
67
68 31
            return $resolvedClass;
69
        }
70
71 37
        $resolvedClassName = $this->findClassName($classInfo);
72 37
        if ($resolvedClassName !== null) {
73 33
            $instance = $this->createInstance($resolvedClassName);
74 33
            $this->triggerEvent(new ResolvedClassCreatedEvent($classInfo));
75
        } else {
76
            // Try again with its parent class
77 6
            if (is_object($caller)) {
78 6
                $parentClass = get_parent_class($caller);
79 6
                if ($parentClass !== false) {
80 6
                    $this->triggerEvent(new ResolvedClassTryFormParentEvent($classInfo));
81
82 6
                    return $this->doResolve($parentClass);
83
                }
84
            }
85
86 3
            $this->triggerEvent(new ResolvedDefaultClassEvent($classInfo));
87 3
            $instance = $this->createDefaultGacelaClass();
88
        }
89
90 35
        self::$cachedInstances[$cacheKey] = $instance;
91
92 35
        return self::$cachedInstances[$cacheKey];
93
    }
94
95
    abstract protected function getResolvableType(): string;
96
97 58
    private function resolveCached(string $cacheKey): ?object
98
    {
99 58
        return AnonymousGlobal::getByKey($cacheKey)
100 57
            ?? self::$cachedInstances[$cacheKey]
101
            ?? null;
102
    }
103
104
    /**
105
     * @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...
106
     */
107 37
    private function findClassName(ClassInfo $classInfo): ?string
108
    {
109 37
        return $this->getClassNameFinder()->findClassName(
110
            $classInfo,
111 37
            $this->getPossibleResolvableTypes()
112
        );
113
    }
114
115 37
    private function getClassNameFinder(): ClassNameFinderInterface
116
    {
117 37
        if ($this->classNameFinder === null) {
118 37
            $this->classNameFinder = (new ClassResolverFactory(
119 37
                new GacelaFileCache(Config::getInstance()),
120 37
                Config::getInstance()->getSetupGacela()
121 37
            ))->createClassNameFinder();
122
        }
123
124 37
        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...
125
    }
126
127
    /**
128
     * Allow overriding gacela suffixes resolvable types.
129
     *
130
     * @return list<string>
131
     */
132 37
    private function getPossibleResolvableTypes(): array
133
    {
134 37
        $suffixTypes = $this->getGacelaConfigFile()->getSuffixTypes();
135
136 37
        $resolvableTypes = $suffixTypes[$this->getResolvableType()] ?? $this->getResolvableType();
137
138 37
        return is_array($resolvableTypes) ? $resolvableTypes : [$resolvableTypes];
139
    }
140
141
    /**
142
     * @param class-string $resolvedClassName
143
     */
144 33
    private function createInstance(string $resolvedClassName): ?object
145
    {
146 33
        if ($this->instanceCreator === null) {
147 33
            $this->instanceCreator = new InstanceCreator($this->getGacelaConfigFile());
148
        }
149
150 33
        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

150
        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...
151
    }
152
153 37
    private function getGacelaConfigFile(): GacelaConfigFileInterface
154
    {
155 37
        if ($this->gacelaFileConfig === null) {
156 37
            $this->gacelaFileConfig = Config::getInstance()
157 37
                ->getFactory()
158 37
                ->createGacelaFileConfig();
159
        }
160
161 37
        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...
162
    }
163
164 3
    private function createDefaultGacelaClass(): ?object
165
    {
166 3
        switch ($this->getResolvableType()) {
167
            case FactoryResolver::TYPE:
168 1
                return new class() extends AbstractFactory {};
169 3
            case ConfigResolver::TYPE:
170 2
                return new class() extends AbstractConfig {};
171
            default:
172 1
                return null;
173
        }
174
    }
175
176 58
    private function triggerEvent(GacelaEventInterface $event): void
177
    {
178 58
        if (self::$listeners === []) {
179 52
            self::$listeners = Config::getInstance()
180 52
                ->getSetupGacela()
181 52
                ->getEventListeners();
182
        }
183
184 58
        foreach (self::$listeners[GacelaClassResolverListener::class] ?? [] as $callable) {
185 9
            $callable($event);
186
        }
187
    }
188
}
189