AbstractClassResolver::doResolve()   A
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 5

Importance

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

149
        /** @scrutinizer ignore-call */ 
150
        $instance = $this->container->get($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...
150 56
151
        return $instance;
152 56
    }
153
154
    private function getGacelaConfigFile(): GacelaConfigFileInterface
155 63
    {
156
        if (!$this->gacelaFileConfig instanceof GacelaConfigFileInterface) {
157 63
            $this->gacelaFileConfig = Config::getInstance()
158 63
                ->getFactory()
159 63
                ->createGacelaFileConfig();
160 63
        }
161
162
        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...
163 63
    }
164
165
    private function createDefaultGacelaClass(): ?object
166 13
    {
167
        return match ($this->getResolvableType()) {
168 13
            FacadeResolver::TYPE => new class() extends AbstractFacade {},
169 13
            FactoryResolver::TYPE => new class() extends AbstractFactory {},
170 13
            ConfigResolver::TYPE => new class() extends AbstractConfig {},
171 13
            default => null,
172 13
        };
173 13
    }
174
}
175