InstanceScript   A
last analyzed

Complexity

Total Complexity 23

Size/Duplication

Total Lines 140
Duplicated Lines 0 %

Importance

Changes 9
Bugs 0 Features 0
Metric Value
eloc 61
c 9
b 0
f 0
dl 0
loc 140
rs 10
wmc 23

9 Methods

Rating   Name   Duplication   Size   Complexity  
A pushAspectBind() 0 18 4
A pushClass() 0 6 1
A getScript() 0 22 4
A pushProviderContext() 0 3 1
A __construct() 0 4 1
A addInstanceArg() 0 9 3
A addArg() 0 27 6
A pushMethod() 0 4 1
A addDependencyArg() 0 9 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Compiler;
6
7
use Ray\Aop\Bind as AopBind;
8
use Ray\Compiler\Exception\Unbound;
9
use Ray\Di\Container;
10
use Ray\Di\Dependency;
11
use Ray\Di\DependencyInterface;
12
use Ray\Di\DependencyProvider;
13
use Ray\Di\Instance;
14
use Ray\Di\SetContextInterface;
15
use ReflectionParameter;
16
17
use function array_unshift;
18
use function assert;
19
use function implode;
20
use function is_a;
21
use function is_array;
22
use function is_object;
23
use function is_string;
24
use function serialize;
25
use function sprintf;
26
use function str_replace;
27
use function var_export;
28
29
use const PHP_EOL;
30
31
final class InstanceScript
32
{
33
    public const RAY_DI_INJECTOR_INTERFACE = 'Ray\Di\InjectorInterface-';
34
    public const RAY_DI_INJECTION_POINT_INTERFACE = 'Ray\Di\InjectionPointInterface-';
35
    public const COMMENT = '// prototype';
36
37
    /** @var array<mixed> */
38
    private array $args = [];
39
40
    /** @var array<string> */
41
    private array $formerLines = []; // Constructor injection and AOP
42
43
    /** @var array<string> */
44
    private array $laterLines = [];  // Setter injection and postConstruct
45
    private string $context = '';
46
    private bool $implementsSetContext = false;
47
48
    /** @var array<DependencyInterface> */
49
    private array $container;
50
51
    public function __construct(Container $container)
52
    {
53
        $container->sort();
54
        $this->container = $container->getContainer();
55
    }
56
57
    /** @param mixed $defaultValue */
58
    public function addArg(string $index, bool $isDefaultAvailable, $defaultValue, ReflectionParameter $parameter): void
59
    {
60
        if (! isset($this->container[$index])) {
61
            if ($isDefaultAvailable) {
62
                $this->addInstanceArg($defaultValue);
63
64
                return;
65
            }
66
67
            if ($index === self::RAY_DI_INJECTION_POINT_INTERFACE) {
68
                $this->args[] = '\Ray\Compiler\InjectionPoint::getInstance($ip)';
69
70
                return;
71
            }
72
73
            throw new Unbound($index);
74
        }
75
76
        $dependency = $this->container[$index];
77
        if ($dependency instanceof Dependency || $dependency instanceof DependencyProvider) {
78
            $this->addDependencyArg($dependency->isSingleton(), $index, $parameter);
79
80
            return;
81
        }
82
83
        assert($dependency instanceof Instance, 'Invalid instance value');
84
        $this->addInstanceArg($dependency->value);
85
    }
86
87
    private function addDependencyArg(bool $isSingleton, string $index, ReflectionParameter $parameter): void
88
    {
89
        /** @psalm-suppress PossiblyNullReference / The $parameter here can never be null */
90
        $ip = sprintf("['%s', '%s', '%s']", $parameter->getDeclaringClass()->getName(), $parameter->getDeclaringFunction()->getName(), $parameter->name); //@phpstan-ignore-line
91
        $filePath = sprintf('/%s.php', str_replace('\\', '_', $index));
92
        // Add prototype or singleton
93
        $this->args[] = $isSingleton ?
94
            sprintf("\\Ray\\Compiler\\singleton(\$scriptDir, \$singletons, '%s', '%s', %s)", $index, $filePath, $ip) :
95
            sprintf("\\Ray\\Compiler\\prototype(\$scriptDir, \$singletons, '%s', '%s', %s)", $index, $filePath, $ip);
96
    }
97
98
    /** @param mixed $default */
99
    public function addInstanceArg($default): void
100
    {
101
        if (is_object($default) || is_array($default)) {
102
            $this->args[] = sprintf('unserialize(\'%s\')', serialize($default));
103
104
            return;
105
        }
106
107
        $this->args[] = var_export($default, true);
108
    }
109
110
    public function pushMethod(string $method): void
111
    {
112
        $this->laterLines[] = sprintf('$instance->%s(%s);', $method, implode(', ', $this->args));
113
        $this->args = [];
114
    }
115
116
    public function pushClass(string $class): void
117
    {
118
        $this->implementsSetContext = is_a($class, SetContextInterface::class, true);
119
120
        array_unshift($this->formerLines, sprintf('$instance = new \%s(%s);', $class, implode(', ', $this->args)));
121
        $this->args = [];
122
    }
123
124
    public function pushProviderContext(string $context): void
125
    {
126
        $this->context = $context;
127
    }
128
129
    public function pushAspectBind(AopBind $aopBind): void
130
    {
131
        $aopBindings = $aopBind->getBindings();
132
        foreach ($aopBindings as &$bindings) {
133
            /** @var array<int, string> $bindings */
134
            foreach ($bindings as &$binding) {
135
                $filePath = sprintf('/%s-.php', str_replace('\\', '_', $binding));
136
                $binding = sprintf("\Ray\Compiler\singleton(\$scriptDir, \$singletons, '%s-', '%s')", $binding, $filePath);
137
            }
138
        }
139
140
        $interceptors = [];
141
        foreach ($aopBindings as $method => $aopBinding) {
142
            /** @var array<int, string> $aopBinding */
143
            $interceptors[] =  sprintf('\'%s\' => [%s]', $method, implode(', ', $aopBinding));
144
        }
145
146
        $this->formerLines[] = sprintf('$instance->bindings = [%s    %s%s];', PHP_EOL, implode(', ' . PHP_EOL . '    ', $interceptors), PHP_EOL);
147
    }
148
149
    public function getScript(string|null $postConstruct, bool $isSingleton): string
150
    {
151
        if (is_string($postConstruct)) {
152
            $this->laterLines[] = sprintf('$instance->%s();', $postConstruct);
153
        }
154
155
        if ($this->implementsSetContext) {
156
            $this->laterLines[] = sprintf('$instance->setContext(%s);', var_export($this->context, true));
157
        }
158
159
        $this->laterLines[] = self::COMMENT;
160
        if ($isSingleton) {
161
            $this->laterLines[] = '$singletons[$dependencyIndex] = $instance;';
162
        }
163
164
        $this->laterLines[] = 'return $instance;';
165
166
        $script = implode(PHP_EOL, $this->formerLines) . PHP_EOL . implode(PHP_EOL, $this->laterLines);
167
        $this->formerLines = [];
168
        $this->laterLines = [];
169
170
        return $script;
171
    }
172
}
173