ProxyClassRenderer::cutDefaultValue()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
ccs 3
cts 3
cp 1
rs 10
cc 1
nc 1
nop 1
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Core\Internal\Proxy;
6
7
/**
8
 * @internal
9
 */
10
final class ProxyClassRenderer
11
{
12
    /**
13
     * @param \ReflectionClass $type Interface reflection.
14
     * @param string $className Class name to use in the generated code.
15
     * @param bool $defineOverload Define __call() and __callStatic() methods.
16
     * @param bool $attachContainer Attach container to the proxy.
17
     *
18
     * @return non-empty-string PHP code
0 ignored issues
show
Documentation Bug introduced by
The doc comment non-empty-string at position 0 could not be parsed: Unknown type name 'non-empty-string' at position 0 in non-empty-string.
Loading history...
19
     */
20 16
    public static function renderClass(
21
        \ReflectionClass $type,
22
        string $className,
23
        bool $defineOverload = false,
24
        bool $attachContainer = false,
25
    ): string {
26 16
        $traits = $defineOverload ? [
27 16
            MagicCallTrait::class,
28 16
        ] : [];
29
30 16
        if (\str_contains($className, '\\')) {
31 14
            $classShortName = \substr($className, \strrpos($className, '\\') + 1);
32 14
            $classNamespaceStr = 'namespace ' . \substr($className, 0, \strrpos($className, '\\')) . ';';
33
        } else {
34 2
            $classShortName = $className;
35 2
            $classNamespaceStr = '';
36
        }
37
38 16
        $interface = $type->getName();
39 16
        $classBody = [];
40 16
        foreach ($type->getMethods() as $method) {
41 15
            if ($method->isConstructor()) {
42 1
                throw new \LogicException('Constructor is not allowed in a proxy.');
43
            }
44
45 14
            if ($method->isDestructor()) {
46 1
                $classBody[] = self::renderMethod($method);
47 1
                continue;
48
            }
49
50 13
            $hasRefs = false;
51 13
            $return = $method->hasReturnType() && (string) $method->getReturnType() === 'void' ? '' : 'return ';
52 13
            $call = ($method->isStatic() ? '::' : '->') . $method->getName();
53 13
            $context = $method->isStatic() ? 'null' : '$this->__container_proxy_context';
54 13
            $containerStr = match (false) {
55 13
                $attachContainer => 'null',
56
                /** @see \Spiral\Core\Internal\Proxy\ProxyTrait::__container_proxy_container */
57
                $method->isStatic() => '$this->__container_proxy_container',
58
                default => \sprintf(
59
                    'throw new \Spiral\Core\Exception\Container\ContainerException(\'%s\')',
60
                    'Static method call is not allowed on a Proxy that was created without dynamic scope.',
61
                ),
62 13
            };
63 13
            $resolveStr = <<<PHP
64 13
                \\Spiral\\Core\\Internal\\Proxy\\Resolver::resolve(
65 13
                    '{$interface}',
66 13
                    {$context},
67 13
                    {$containerStr},
68
                )
69 13
                PHP;
70
71 13
            $args = [];
72 13
            foreach ($method->getParameters() as $param) {
73 11
                $hasRefs = $hasRefs || $param->isPassedByReference();
74 11
                $args[] = ($param->isVariadic() ? '...' : '') . '$' . $param->getName();
75
            }
76
77 13
            if (!$hasRefs && !$method->isVariadic()) {
78 13
                $classBody[] = self::renderMethod(
79 13
                    $method,
80 13
                    <<<PHP
81 13
                    {$return}{$resolveStr}{$call}(...\\func_get_args());
82 13
                PHP,
83 13
                );
84 13
                continue;
85
            }
86
87 1
            $argsStr = \implode(', ', $args);
88
89 1
            if ($method->isVariadic()) {
90 1
                $classBody[] = self::renderMethod(
91 1
                    $method,
92 1
                    <<<PHP
93 1
                    {$return}{$resolveStr}{$call}($argsStr);
94 1
                PHP,
95 1
                );
96 1
                continue;
97
            }
98
99 1
            $countParams = $method->getNumberOfParameters();
100 1
            $classBody[] = self::renderMethod(
101 1
                $method,
102 1
                <<<PHP
103 1
                {$return}{$resolveStr}{$call}($argsStr, ...\\array_slice(\\func_get_args(), {$countParams}));
104 1
            PHP,
105 1
            );
106
        }
107 15
        $bodyStr = \implode("\n\n", $classBody);
108
109 15
        $traitsStr = $traits === [] ? '' : \implode(
110 15
            "\n    ",
111 15
            \array_map(static fn(string $trait): string => 'use \\' . \ltrim($trait, '\\') . ';', $traits),
112 15
        );
113 15
        return <<<PHP
114 15
            $classNamespaceStr
115
116 15
            final class $classShortName implements \\$interface {
117
                use \Spiral\Core\Internal\Proxy\ProxyTrait;
118 15
                $traitsStr
119
120 15
            $bodyStr
121
            }
122 15
            PHP;
123
    }
124
125 19
    public static function renderMethod(\ReflectionMethod $m, string $body = ''): string
126
    {
127 19
        return \sprintf(
128 19
            "public%s function %s%s(%s)%s {\n%s\n}",
129 19
            $m->isStatic() ? ' static' : '',
130 19
            $m->returnsReference() ? '&' : '',
131 19
            $m->getName(),
132 19
            \implode(', ', \array_map(self::renderParameter(...), $m->getParameters())),
133 19
            $m->hasReturnType()
134 16
                ? ': ' . self::renderParameterTypes($m->getReturnType(), $m->getDeclaringClass())
135 19
                : '',
136 19
            $body,
137 19
        );
138
    }
139
140 36
    public static function renderParameter(\ReflectionParameter $param): string
141
    {
142 36
        return \ltrim(
143 36
            \sprintf(
144 36
                '%s %s%s%s%s',
145 36
                $param->hasType() ? 'mixed' : '',
146 36
                $param->isPassedByReference() ? '&' : '',
147 36
                $param->isVariadic() ? '...' : '',
148 36
                '$' . $param->getName(),
149 36
                $param->isOptional() && !$param->isVariadic() ? ' = ' . self::renderDefaultValue($param) : '',
150 36
            ),
151 36
            ' ',
152 36
        );
153
    }
154
155 33
    public static function renderParameterTypes(\ReflectionType $types, \ReflectionClass $class): string
156
    {
157 33
        if ($types instanceof \ReflectionNamedType) {
158 26
            return ($types->allowsNull() && $types->getName() !== 'mixed' ? '?' : '') . ($types->isBuiltin()
159 21
                    ? $types->getName()
160 26
                    : self::normalizeClassType($types, $class));
161
        }
162
163 8
        [$separator, $types] = match (true) {
164 8
            $types instanceof \ReflectionUnionType => ['|', $types->getTypes()],
0 ignored issues
show
Bug introduced by
The method getTypes() does not exist on ReflectionType. It seems like you code against a sub-type of said class. However, the method does not exist in ReflectionNamedType. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

164
            $types instanceof \ReflectionUnionType => ['|', $types->/** @scrutinizer ignore-call */ getTypes()],
Loading history...
165 1
            $types instanceof \ReflectionIntersectionType => ['&', $types->getTypes()],
166
            default => throw new \Exception('Unknown type.'),
167 8
        };
168
169 8
        $result = [];
170 8
        foreach ($types as $type) {
171 8
            $result[] = $type->isBuiltin()
172 7
                ? $type->getName()
173 3
                : self::normalizeClassType($type, $class);
174
        }
175
176 8
        return \implode($separator, $result);
177
    }
178
179 12
    public static function renderDefaultValue(\ReflectionParameter $param): string
180
    {
181 12
        if ($param->isDefaultValueConstant()) {
182 1
            $result = $param->getDefaultValueConstantName();
183
184 1
            return \explode('::', (string) $result)[0] === 'self'
185 1
                ? $result
186 1
                : '\\' . $result;
187
        }
188
189 11
        $cut = self::cutDefaultValue($param);
190
191 11
        return \str_starts_with($cut, 'new ')
192 1
            ? $cut
193 11
            : \var_export($param->getDefaultValue(), true);
194
    }
195
196 13
    public static function normalizeClassType(\ReflectionNamedType $type, \ReflectionClass $class): string
197
    {
198 13
        return match($type->getName()) {
199 1
            'static' => 'static',
200 3
            'self' => '\\' . $class->getName(),
201 13
            default => '\\' . $type->getName(),
202 13
        };
203
    }
204
205 11
    private static function cutDefaultValue(\ReflectionParameter $param): string
206
    {
207 11
        $string = (string) $param;
208
209 11
        return \trim(\substr($string, \strpos($string, '=') + 1, -1));
210
    }
211
}
212