PackageInjector::getInstance()   A
last analyzed

Complexity

Conditions 6
Paths 9

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 10
c 3
b 0
f 0
dl 0
loc 18
rs 9.2222
cc 6
nc 9
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BEAR\Package\Injector;
6
7
use BEAR\AppMeta\AbstractAppMeta;
8
use BEAR\Package\Module;
9
use BEAR\Package\Module\ResourceObjectModule;
10
use BEAR\Package\Types;
11
use BEAR\Sunday\Extension\Application\AppInterface;
12
use Ray\Compiler\Annotation\Compile;
13
use Ray\Compiler\CompiledInjector;
14
use Ray\Compiler\Compiler;
15
use Ray\Compiler\ScriptInjector;
16
use Ray\Di\AbstractModule;
17
use Ray\Di\Injector as RayInjector;
18
use Ray\Di\InjectorInterface;
19
use Symfony\Component\Cache\Adapter\AdapterInterface;
20
use Symfony\Contracts\Cache\CacheInterface;
21
22
use function assert;
23
use function is_bool;
24
use function is_dir;
25
use function mkdir;
26
use function str_replace;
27
use function trigger_error;
28
29
use const E_USER_WARNING;
30
31
/** @psalm-import-type Context from Types */
32
final class PackageInjector
33
{
34
    /**
35
     * Serialized injector instances
36
     *
37
     * @var array<string, InjectorInterface>
38
     */
39
    private static array $instances;
40
41
    /** @codeCoverageIgnore */
42
    private function __construct()
43
    {
44
    }
45
46
    /**
47
     * Returns an instance of InjectorInterface based on the given parameters
48
     *
49
     * @param Context $context
0 ignored issues
show
Bug introduced by
The type BEAR\Package\Injector\Context was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
50
     *
51
     * - Injector instances are cached in memory and in the cache adapter.
52
     * - The injector is re-used in subsequent calls in the same context in the unit test.
53
     */
54
    public static function getInstance(AbstractAppMeta $meta, string $context, CacheInterface|null $cache): InjectorInterface
55
    {
56
        $injectorId = str_replace('\\', '_', $meta->name) . $context;
57
        if (isset(self::$instances[$injectorId])) {
58
            return self::$instances[$injectorId];
59
        }
60
61
        assert($cache instanceof AdapterInterface);
62
        /** @psalm-suppress MixedAssignment, MixedArrayAccess */
63
        [$injector, $fileUpdate] = $cache->getItem($injectorId)->get(); // @phpstan-ignore-line
64
        $isCacheableInjector = $injector instanceof ScriptInjector || ($injector instanceof InjectorInterface && $fileUpdate instanceof FileUpdate && $fileUpdate->isNotUpdated($meta));
65
        if (! $isCacheableInjector) {
66
            $injector = self::getInjector($meta, $context, $cache, $injectorId);
67
        }
68
69
        self::$instances[$injectorId] = $injector;
70
71
        return $injector;
72
    }
73
74
    /**
75
     * Return an injector instance with the given override module
76
     *
77
     * @param Context $context
78
     *
79
     * This is useful for testing purposes, where you want to override a module with a mock or stub
80
     */
81
    public static function factory(AbstractAppMeta $meta, string $context, AbstractModule|null $overrideModule = null): InjectorInterface
82
    {
83
        $scriptDir = $meta->tmpDir . '/di';
84
        ! is_dir($scriptDir) && ! @mkdir($scriptDir) && ! is_dir($scriptDir);
85
        $module = (new Module())($meta, $context);
86
87
        if ($overrideModule instanceof AbstractModule) {
88
            $module->override($overrideModule);
89
        }
90
91
        // Bind ResourceObject
92
        $module->install(new ResourceObjectModule($meta->getResourceListGenerator()));
93
94
        $injector = new RayInjector($module, $scriptDir);
95
        $isProd = $injector->getInstance('', Compile::class);
96
        assert(is_bool($isProd));
97
        if ($isProd) {
98
            $compiler = new Compiler();
99
            $compiler->compile($module, $scriptDir);
100
            $injector = new CompiledInjector($scriptDir);
101
        }
102
103
        /** @psalm-suppress InvalidArgument */
104
        $injector->getInstance(AppInterface::class);
105
106
        return $injector;
107
    }
108
109
    /** @param Context $context */
110
    private static function getInjector(AbstractAppMeta $meta, string $context, AdapterInterface $cache, string $injectorId): InjectorInterface
111
    {
112
        $injector = self::factory($meta, $context);
113
        $cache->save($cache->getItem($injectorId)->set([$injector, new FileUpdate($meta)]));
114
        // Check the cache
115
        if ($cache->getItem($injectorId)->get() === null) {
116
            trigger_error('Failed to verify the injector cache. See https://github.com/bearsunday/BEAR.Package/issues/418', E_USER_WARNING);
117
        }
118
119
        return $injector;
120
    }
121
}
122