Passed
Push — feature/class-resolver-events ( b07999...bba964 )
by Chema
03:27
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\EventListener\ClassResolver\ResolvedClassCachedEvent;
18
use Gacela\Framework\EventListener\ClassResolver\ResolvedClassCreatedEvent;
19
use Gacela\Framework\EventListener\ClassResolver\ResolvedClassTryFormParentEvent;
20
use Gacela\Framework\EventListener\ClassResolver\ResolvedDefaultClassEvent;
21
use Gacela\Framework\EventListener\GacelaEventInterface;
22
23
use function is_array;
24
use function is_object;
25
26
abstract class AbstractClassResolver
27
{
28
    /** @var array<string,null|object> */
29
    private static array $cachedInstances = [];
30
31
    private ?ClassNameFinderInterface $classNameFinder = null;
32
33
    private ?GacelaConfigFileInterface $gacelaFileConfig = null;
34
35
    private ?InstanceCreator $instanceCreator = null;
36
37
    /**
38
     * @internal remove all cached instances: facade, factory, config, dependency-provider
39
     */
40 11
    public static function resetCache(): void
41
    {
42 11
        self::$cachedInstances = [];
43
    }
44
45
    /**
46
     * @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...
47
     */
48
    abstract public function resolve($caller): ?object;
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 57
    public function doResolve($caller, ?string $previousCacheKey = null): ?object
54
    {
55 57
        $classInfo = ClassInfo::from($caller, $this->getResolvableType());
56 57
        $cacheKey = $previousCacheKey ?? $classInfo->getCacheKey();
57
58 57
        $resolvedClass = $this->resolveCached($cacheKey);
59 57
        if ($resolvedClass !== null) {
60 29
            $this->dispatchEvent(new ResolvedClassCachedEvent($classInfo));
61
62 29
            return $resolvedClass;
63
        }
64
65 37
        $resolvedClassName = $this->findClassName($classInfo);
66 37
        if ($resolvedClassName !== null) {
67 33
            $instance = $this->createInstance($resolvedClassName);
68 33
            $this->dispatchEvent(new ResolvedClassCreatedEvent($classInfo));
69
        } else {
70
            // Try again with its parent class
71 6
            if (is_object($caller)) {
72 6
                $parentClass = get_parent_class($caller);
73 6
                if ($parentClass !== false) {
74 6
                    $this->dispatchEvent(new ResolvedClassTryFormParentEvent($classInfo));
75
76 6
                    return $this->doResolve($parentClass, $cacheKey);
77
                }
78
            }
79
80 5
            $this->dispatchEvent(new ResolvedDefaultClassEvent($classInfo));
81 5
            $instance = $this->createDefaultGacelaClass();
82
        }
83
84 37
        self::$cachedInstances[$cacheKey] = $instance;
85
86 37
        return self::$cachedInstances[$cacheKey];
87
    }
88
89
    abstract protected function getResolvableType(): string;
90
91 57
    private function resolveCached(string $cacheKey): ?object
92
    {
93 57
        return AnonymousGlobal::getByKey($cacheKey)
94 56
            ?? self::$cachedInstances[$cacheKey]
95
            ?? null;
96
    }
97
98
    /**
99
     * @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...
100
     */
101 37
    private function findClassName(ClassInfo $classInfo): ?string
102
    {
103 37
        return $this->getClassNameFinder()->findClassName(
104
            $classInfo,
105 37
            $this->getPossibleResolvableTypes()
106
        );
107
    }
108
109 37
    private function getClassNameFinder(): ClassNameFinderInterface
110
    {
111 37
        if ($this->classNameFinder === null) {
112 37
            $this->classNameFinder = (new ClassResolverFactory(
113 37
                new GacelaFileCache(Config::getInstance()),
114 37
                Config::getInstance()->getSetupGacela()
115 37
            ))->createClassNameFinder();
116
        }
117
118 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...
119
    }
120
121
    /**
122
     * Allow overriding gacela suffixes resolvable types.
123
     *
124
     * @return list<string>
125
     */
126 37
    private function getPossibleResolvableTypes(): array
127
    {
128 37
        $suffixTypes = $this->getGacelaConfigFile()->getSuffixTypes();
129
130 37
        $resolvableTypes = $suffixTypes[$this->getResolvableType()] ?? $this->getResolvableType();
131
132 37
        return is_array($resolvableTypes) ? $resolvableTypes : [$resolvableTypes];
133
    }
134
135
    /**
136
     * @param class-string $resolvedClassName
137
     */
138 33
    private function createInstance(string $resolvedClassName): ?object
139
    {
140 33
        if ($this->instanceCreator === null) {
141 33
            $this->instanceCreator = new InstanceCreator($this->getGacelaConfigFile());
142
        }
143
144 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

144
        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...
145
    }
146
147 37
    private function getGacelaConfigFile(): GacelaConfigFileInterface
148
    {
149 37
        if ($this->gacelaFileConfig === null) {
150 37
            $this->gacelaFileConfig = Config::getInstance()
151 37
                ->getFactory()
152 37
                ->createGacelaFileConfig();
153
        }
154
155 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...
156
    }
157
158 5
    private function createDefaultGacelaClass(): ?object
159
    {
160 5
        switch ($this->getResolvableType()) {
161 5
            case FactoryResolver::TYPE:
162 3
                return new class() extends AbstractFactory {
163
                };
164 3
            case ConfigResolver::TYPE:
165 2
                return new class() extends AbstractConfig {
166
                };
167
            default:
168 1
                return null;
169
        }
170
    }
171
172 57
    private function dispatchEvent(GacelaEventInterface $event): void
173
    {
174 57
        Config::getEventDispatcher()->dispatch($event);
175
    }
176
}
177