Passed
Push — refactor-FacadeResolver ( 5b5bce )
by Chema
03:48
created

php$2 ➔ createDefaultGacelaClass()   A

Complexity

Conditions 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 7
ccs 6
cts 6
cp 1
crap 1
rs 10
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 51
    public static function resetCache(): void
44
    {
45 51
        self::$cachedInstances = [];
46 51
        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 71
    protected function doResolve(object|string $caller, ?string $previousCacheKey = null): ?object
60
    {
61 71
        $classInfo = ClassInfo::from($caller, $this->getResolvableType());
62 71
        $cacheKey = $previousCacheKey ?? $classInfo->getCacheKey();
63
64 71
        $resolvedClass = $this->resolveCached($cacheKey);
65 71
        if ($resolvedClass !== null) {
66 21
            self::dispatchEvent(new ResolvedClassCachedEvent($classInfo));
67
68 21
            return $resolvedClass;
69
        }
70
71 61
        $resolvedClassName = $this->findClassName($classInfo);
72 61
        if ($resolvedClassName !== null) {
73 55
            $instance = $this->createInstance($resolvedClassName);
74 55
            self::dispatchEvent(new ResolvedClassCreatedEvent($classInfo));
75
        } else {
76
            // Try again with its parent class
77 13
            if (is_object($caller)) {
78 6
                $parentClass = get_parent_class($caller);
79 6
                if ($parentClass !== false) {
80 6
                    self::dispatchEvent(new ResolvedClassTriedFromParentEvent($classInfo));
81
82 6
                    return $this->doResolve($parentClass, $cacheKey);
83
                }
84
            }
85
86 12
            self::dispatchEvent(new ResolvedCreatedDefaultClassEvent($classInfo));
87 12
            $instance = $this->createDefaultGacelaClass();
88
        }
89
90 61
        self::$cachedInstances[$cacheKey] = $instance;
91
92 61
        return self::$cachedInstances[$cacheKey];
93
    }
94
95 71
    private function resolveCached(string $cacheKey): ?object
96
    {
97 71
        return AnonymousGlobal::getByKey($cacheKey)
98 71
            ?? self::$cachedInstances[$cacheKey]
99 71
            ?? null;
100
    }
101
102
    /**
103
     * @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...
104
     */
105 61
    private function findClassName(ClassInfo $classInfo): ?string
106
    {
107 61
        return $this->getClassNameFinder()->findClassName(
108 61
            $classInfo,
109 61
            $this->getPossibleResolvableTypes(),
110 61
        );
111
    }
112
113 61
    private function getClassNameFinder(): ClassNameFinderInterface
114
    {
115 61
        if (self::$classNameFinder === null) {
116 43
            self::$classNameFinder = (new ClassResolverFactory(
117 43
                Config::getInstance()->getSetupGacela(),
118 43
            ))->createClassNameFinder();
119
        }
120
121 61
        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...
122
    }
123
124
    /**
125
     * Allow overriding gacela suffixes resolvable types.
126
     *
127
     * @return list<string>
128
     */
129 61
    private function getPossibleResolvableTypes(): array
130
    {
131 61
        $suffixTypes = $this->getGacelaConfigFile()->getSuffixTypes();
132
133 61
        $resolvableTypes = $suffixTypes[$this->getResolvableType()] ?? $this->getResolvableType();
134
135 61
        return is_array($resolvableTypes) ? $resolvableTypes : [$resolvableTypes];
136
    }
137
138
    /**
139
     * @param class-string $resolvedClassName
140
     */
141 55
    private function createInstance(string $resolvedClassName): object
142
    {
143 55
        if ($this->container === null) {
144 55
            $this->container = new Container(
145 55
                $this->getGacelaConfigFile()->getBindings(),
146 55
            );
147
        }
148
149
        /** @var object $instance */
150 55
        $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

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