PackageInjector::factory()   A
last analyzed

Complexity

Conditions 5
Paths 12

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 15
c 4
b 0
f 0
dl 0
loc 25
rs 9.4555
cc 5
nc 12
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\Sunday\Extension\Application\AppInterface;
11
use Ray\Compiler\Annotation\Compile;
12
use Ray\Compiler\CompiledInjector;
13
use Ray\Compiler\Compiler;
14
use Ray\Compiler\ScriptInjector;
15
use Ray\Di\AbstractModule;
16
use Ray\Di\Injector as RayInjector;
17
use Ray\Di\InjectorInterface;
18
use Symfony\Component\Cache\Adapter\AdapterInterface;
19
use Symfony\Contracts\Cache\CacheInterface;
20
21
use function assert;
22
use function is_bool;
23
use function is_dir;
24
use function mkdir;
25
use function str_replace;
26
use function trigger_error;
27
28
use const E_USER_WARNING;
29
30
final class PackageInjector
31
{
32
    /**
33
     * Serialized injector instances
34
     *
35
     * @var array<string, InjectorInterface>
36
     */
37
    private static array $instances;
38
39
    /** @codeCoverageIgnore */
40
    private function __construct()
41
    {
42
    }
43
44
    /**
45
     * Returns an instance of InjectorInterface based on the given parameters
46
     *
47
     * - Injector instances are cached in memory and in the cache adapter.
48
     * - The injector is re-used in subsequent calls in the same context in the unit test.
49
     */
50
    public static function getInstance(AbstractAppMeta $meta, string $context, CacheInterface|null $cache): InjectorInterface
51
    {
52
        $injectorId = str_replace('\\', '_', $meta->name) . $context;
53
        if (isset(self::$instances[$injectorId])) {
54
            return self::$instances[$injectorId];
55
        }
56
57
        assert($cache instanceof AdapterInterface);
58
        /** @psalm-suppress all */
59
        [$injector, $fileUpdate] = $cache->getItem($injectorId)->get(); // @phpstan-ignore-line
60
        $isCacheableInjector = $injector instanceof ScriptInjector || ($injector instanceof InjectorInterface && $fileUpdate instanceof FileUpdate && $fileUpdate->isNotUpdated($meta));
61
        if (! $isCacheableInjector) {
62
            $injector = self::getInjector($meta, $context, $cache, $injectorId);
63
        }
64
65
        self::$instances[$injectorId] = $injector;
66
67
        return $injector;
68
    }
69
70
    /**
71
     * Return an injector instance with the given override module
72
     *
73
     * This is useful for testing purposes, where you want to override a module with a mock or stub
74
     */
75
    public static function factory(AbstractAppMeta $meta, string $context, AbstractModule|null $overrideModule = null): InjectorInterface
76
    {
77
        $scriptDir = $meta->tmpDir . '/di';
78
        ! is_dir($scriptDir) && ! @mkdir($scriptDir) && ! is_dir($scriptDir);
79
        $module = (new Module())($meta, $context);
80
81
        if ($overrideModule instanceof AbstractModule) {
82
            $module->override($overrideModule);
83
        }
84
85
        // Bind ResourceObject
86
        $module->install(new ResourceObjectModule($meta->getResourceListGenerator()));
87
88
        $injector = new RayInjector($module, $scriptDir);
89
        $isProd = $injector->getInstance('', Compile::class);
90
        assert(is_bool($isProd));
91
        if ($isProd) {
92
            $compiler = new Compiler();
93
            $compiler->compile($module, $scriptDir);
94
            $injector = new CompiledInjector($scriptDir);
95
        }
96
97
        $injector->getInstance(AppInterface::class);
98
99
        return $injector;
100
    }
101
102
    private static function getInjector(AbstractAppMeta $meta, string $context, AdapterInterface $cache, string $injectorId): InjectorInterface
103
    {
104
        $injector = self::factory($meta, $context);
105
        $cache->save($cache->getItem($injectorId)->set([$injector, new FileUpdate($meta)]));
106
        // Check the cache
107
        if ($cache->getItem($injectorId)->get() === null) {
108
            trigger_error('Failed to verify the injector cache. See https://github.com/bearsunday/BEAR.Package/issues/418', E_USER_WARNING);
109
        }
110
111
        return $injector;
112
    }
113
}
114