Completed
Pull Request — 2.x (#216)
by Akihito
09:47
created

ScriptInjector::registerLoader()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
c 0
b 0
f 0
rs 9.3222
cc 5
nc 3
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Compiler;
6
7
use Ray\Compiler\Exception\Unbound;
8
use Ray\Di\AbstractModule;
9
use Ray\Di\Dependency;
10
use Ray\Di\DependencyInterface;
11
use Ray\Di\InjectorInterface;
12
use Ray\Di\Name;
13
use Ray\Di\NullModule;
14
15
final class ScriptInjector implements InjectorInterface
16
{
17
    const MODULE = '/_module.txt';
18
19
    const AOP = '/_aop.txt';
20
21
    const INSTANCE = '%s/%s.php';
22
23
    const QUALIFIER = '%s/qualifer/%s-%s-%s';
24
25
    /**
26
     * @var string
27
     */
28
    private $scriptDir;
29
30
    /**
31
     * Injection Point
32
     *
33
     * [$class, $method, $parameter]
34
     *
35
     * @var array{0: string, 1: string, 2: string}
36
     */
37
    private $ip = ['', '', ''];
38
39
    /**
40
     * Singleton instance container
41
     *
42
     * @var array<object>
43
     */
44
    private $singletons = [];
45
46
    /**
47
     * @var array<callable>
48
     */
49
    private $functions;
50
51
    /**
52
     * @var callable
53
     */
54
    private $lazyModule;
55
56
    /**
57
     * @var null|AbstractModule
58
     */
59
    private $module;
60
61
    /**
62
     * @var ?array<DependencyInterface>
63
     */
64
    private $container;
65
66
    /**
67
     * @var bool
68
     */
69
    private $isModuleLocked = false;
70
71
    /**
72
     * @var array<string>
73
     */
74
    private static $scriptDirs = [];
75
76
    /**
77
     * @param string   $scriptDir  generated instance script folder path
78
     * @param callable $lazyModule callable variable which return AbstractModule instance
79
     *
80
     * @psalm-suppress UnresolvableInclude
81
     */
82
    public function __construct($scriptDir, callable $lazyModule = null)
83
    {
84
        $this->scriptDir = $scriptDir;
85
        $this->lazyModule = $lazyModule ?: function () : NullModule {
86
            return new NullModule;
87
        };
88
        $this->registerLoader();
89
        $prototype =
90
            /**
91
             * @param array{0: string, 1: string, 2: string} $injectionPoint
92
             *
93
             * @return mixed
94
             */
95
            function (string $dependencyIndex, array $injectionPoint = ['', '', '']) {
96
                $this->ip = $injectionPoint; // @phpstan-ignore-line
97
                [$prototype, $singleton, $injection_point, $injector] = $this->functions;
0 ignored issues
show
Bug introduced by
The variable $prototype does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $singleton does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $injection_point does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $injector does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
98
99
                return require $this->getInstanceFile($dependencyIndex);
100
            };
101
        $singleton =
102
            /**
103
             * @param array{0: string, 1: string, 2: string} $injectionPoint
104
             *
105
             * @return mixed
106
             */
107
            function (string $dependencyIndex, $injectionPoint = ['', '', '']) {
108
                if (isset($this->singletons[$dependencyIndex])) {
109
                    return $this->singletons[$dependencyIndex];
110
                }
111
                $this->ip = $injectionPoint;
112
                [$prototype, $singleton, $injection_point, $injector] = $this->functions;
0 ignored issues
show
Bug introduced by
The variable $prototype does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $singleton does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $injection_point does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $injector does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
113
114
                $instance = require $this->getInstanceFile($dependencyIndex);
115
                $this->singletons[$dependencyIndex] = $instance;
116
117
                return $instance;
118
            };
119
        $injection_point = function () use ($scriptDir) : InjectionPoint {
120
            return new InjectionPoint(
121
                new \ReflectionParameter([$this->ip[0], $this->ip[1]], $this->ip[2]),
122
                $scriptDir
123
            );
124
        };
125
        $injector = function () : self {
126
            return $this;
127
        };
128
        $this->functions = [$prototype, $singleton, $injection_point, $injector];
129
    }
130
131
    public function __sleep()
132
    {
133
        $this->saveModule();
134
135
        return ['scriptDir', 'singletons'];
136
    }
137
138
    public function __wakeup()
139
    {
140
        $this->__construct(
141
            $this->scriptDir,
142
            function () {
143
                return $this->getModule();
144
            }
145
        );
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function getInstance($interface, $name = Name::ANY)
152
    {
153
        $dependencyIndex = $interface . '-' . $name;
154
        if (isset($this->singletons[$dependencyIndex])) {
155
            return $this->singletons[$dependencyIndex];
156
        }
157
        [$prototype, $singleton, $injection_point, $injector] = $this->functions;
0 ignored issues
show
Bug introduced by
The variable $prototype does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $singleton does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $injection_point does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $injector does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
158
        /** @psalm-suppress UnresolvableInclude */
159
        $instance = require $this->getInstanceFile($dependencyIndex);
160
        /** @global bool $is_singleton */
161
        $isSingleton = (isset($is_singleton) && $is_singleton) ? true : false; // @phpstan-ignore-line
0 ignored issues
show
Bug introduced by
The variable $is_singleton seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
162
        if ($isSingleton) {
163
            $this->singletons[$dependencyIndex] = $instance;
164
        }
165
166
        return $instance;
167
    }
168
169
    public function clear() : void
170
    {
171
        $unlink = function (string $path) use (&$unlink) : void {
172
            foreach ((array) \glob(\rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*') as $f) {
173
                $file = (string) $f;
174
                \is_dir($file) ? $unlink($file) : \unlink($file);
175
                @\rmdir($file);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
176
            }
177
        };
178
        $unlink($this->scriptDir);
179
    }
180
181
    public function isSingleton(string $dependencyIndex) : bool
182
    {
183
        if (! $this->container) {
184
            $module = $this->getModule();
185
            /* @var AbstractModule $module */
186
            $this->container = $module->getContainer()->getContainer();
187
        }
188
189
        if (! isset($this->container[$dependencyIndex])) {
190
            throw new Unbound($dependencyIndex);
191
        }
192
        $dependency = $this->container[$dependencyIndex];
193
194
        return $dependency instanceof Dependency ? (new PrivateProperty)($dependency, 'isSingleton') : false;
195
    }
196
197
    private function getModule() : AbstractModule
198
    {
199
        $modulePath = $this->scriptDir . self::MODULE;
200
        if (! file_exists($modulePath)) {
201
            return new NullModule;
202
        }
203
        $serialized = file_get_contents($modulePath);
204
        assert(! is_bool($serialized));
205
        $er = error_reporting(error_reporting() ^ E_NOTICE);
206
        $module = unserialize($serialized, ['allowed_classes' => true]);
207
        error_reporting($er);
208
        assert($module instanceof AbstractModule);
209
210
        return $module;
211
    }
212
213
    /**
214
     * Return compiled script file name
215
     */
216
    private function getInstanceFile(string $dependencyIndex) : string
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
217
    {
218
        $file = \sprintf(self::INSTANCE, $this->scriptDir, \str_replace('\\', '_', $dependencyIndex));
219
        if (\file_exists($file)) {
220
            return $file;
221
        }
222
        $this->compileOnDemand($dependencyIndex);
223
        assert(\file_exists($file));
224
225
        return $file;
226
    }
227
228
    private function saveModule() : void
229
    {
230
        if ($this->isModuleLocked || \file_exists($this->scriptDir . self::MODULE)) {
231
            return;
232
        }
233
        $this->isModuleLocked = true;
234
        $module = $this->module instanceof AbstractModule ? $this->module : ($this->lazyModule)();
235
        (new FilePutContents)($this->scriptDir . self::MODULE, \serialize($module));
236
    }
237
238
    private function registerLoader() : void
239
    {
240
        if (in_array($this->scriptDir, self::$scriptDirs, true)) {
241
            return;
242
        }
243
        if (self::$scriptDirs === []) {
244
            \spl_autoload_register(
245
                function (string $class) : void {
246
                    foreach (self::$scriptDirs as $scriptDir) {
247
                        $file = \sprintf('%s/%s.php', $scriptDir, \str_replace('\\', '_', $class));
248
                        if (\file_exists($file)) {
249
                            require $file; // @codeCoverageIgnore
250
                        }
251
                    }
252
                }
253
            );
254
        }
255
        self::$scriptDirs[] = $this->scriptDir;
256
    }
257
258
    private function compileOnDemand(string $dependencyIndex) : void
259
    {
260
        if (! $this->module instanceof AbstractModule) {
261
            $this->module = ($this->lazyModule)();
262
        }
263
        $isFirstCompile = ! \file_exists($this->scriptDir . self::AOP);
264
        if ($isFirstCompile) {
265
            (new DiCompiler(($this->lazyModule)(), $this->scriptDir))->savePointcuts($this->module->getContainer());
266
            $this->saveModule();
267
        }
268
        assert($this->module instanceof AbstractModule);
269
        (new OnDemandCompiler($this, $this->scriptDir, $this->module))($dependencyIndex);
270
    }
271
}
272