MethodSignatureString   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 109
Duplicated Lines 0 %

Importance

Changes 8
Bugs 1 Features 0
Metric Value
eloc 48
c 8
b 1
f 0
dl 0
loc 109
rs 10
wmc 22

8 Methods

Rating   Name   Duplication   Size   Complexity  
A formatArg() 0 7 3
A generateParameterCode() 0 13 5
A get() 0 8 1
A __construct() 0 4 2
A addAttributes() 0 23 5
A addMethodSignature() 0 12 3
A getDocComment() 0 5 2
A addAccessModifiers() 0 5 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ray\Aop;
6
7
use Reflection;
8
use ReflectionMethod;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Ray\Aop\ReflectionMethod. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
9
use ReflectionParameter;
10
use UnitEnum;
0 ignored issues
show
Bug introduced by
The type UnitEnum 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...
11
12
use function implode;
13
use function is_numeric;
14
use function is_string;
15
use function preg_replace;
16
use function sprintf;
17
use function str_replace;
18
use function var_export;
19
20
use const PHP_EOL;
21
use const PHP_MAJOR_VERSION;
22
23
final class MethodSignatureString
24
{
25
    private const PHP_VERSION_8 = 80000;
26
    private const NULLABLE_PHP8 = 'null|';
27
    private const NULLABLE_PHP7 = '?';
28
    private const INDENT = '    ';
29
30
    /** @var TypeString */
31
    private $typeString;
32
33
    public function __construct(int $phpVersion)
34
    {
35
        $nullableStr = $phpVersion >= self::PHP_VERSION_8 ? self::NULLABLE_PHP8 : self::NULLABLE_PHP7;
36
        $this->typeString = new TypeString($nullableStr);
37
    }
38
39
    public function get(ReflectionMethod $method): string
40
    {
41
        $signatureParts = $this->getDocComment($method);
42
        $this->addAttributes($method, $signatureParts);
43
        $this->addAccessModifiers($method, $signatureParts);
44
        $this->addMethodSignature($method, $signatureParts);
45
46
        return implode(' ', $signatureParts);
47
    }
48
49
    /** @return array<string> */
50
    private function getDocComment(ReflectionMethod $method): array
51
    {
52
        $docComment = $method->getDocComment();
53
54
        return is_string($docComment) ? [$docComment . PHP_EOL] : [];
0 ignored issues
show
introduced by
The condition is_string($docComment) is always true.
Loading history...
55
    }
56
57
    /** @param array<string> $signatureParts */
58
    private function addAttributes(ReflectionMethod $method, array &$signatureParts): void
59
    {
60
        if (PHP_MAJOR_VERSION < 8) {
61
            return;
62
        }
63
64
        $attributes = $method->getAttributes();
65
        foreach ($attributes as $attribute) {
66
            $argsList = $attribute->getArguments();
67
            $formattedArgs = [];
68
            /** @var mixed $value */
69
            foreach ($argsList as $name => $value) {
70
                $formattedArgs[] = $this->formatArg($name, $value);
71
            }
72
73
            $signatureParts[] = sprintf('    #[\\%s(%s)]', $attribute->getName(), implode(', ', $formattedArgs)) . PHP_EOL;
74
        }
75
76
        if (empty($signatureParts)) {
77
            return;
78
        }
79
80
        $signatureParts[] = self::INDENT;
81
    }
82
83
    /** @param array<string> $signatureParts */
84
    private function addAccessModifiers(ReflectionMethod $method, array &$signatureParts): void
85
    {
86
        $modifier = implode(' ', Reflection::getModifierNames($method->getModifiers()));
87
88
        $signatureParts[] = $modifier;
89
    }
90
91
    /** @param array<string> $signatureParts */
92
    private function addMethodSignature(ReflectionMethod $method, array &$signatureParts): void
93
    {
94
        $params = [];
95
        foreach ($method->getParameters() as $param) {
96
            $params[] = $this->generateParameterCode($param);
97
        }
98
99
        $parmsList = implode(', ', $params);
100
        $rType = $method->getReturnType();
101
        $return = $rType ? ': ' . ($this->typeString)($rType) : '';
102
103
        $signatureParts[] = sprintf('function %s(%s)%s', $method->getName(), $parmsList, $return);
104
    }
105
106
    /**
107
     * @param string|int $name
108
     * @param mixed      $value
109
     */
110
    private function formatArg($name, $value): string
111
    {
112
        $formattedValue = $value instanceof UnitEnum ?
113
            '\\' . var_export($value, true)
114
            : preg_replace('/\s+/', '', var_export($value, true));
115
116
        return is_numeric($name) ? (string) $formattedValue : "{$name}: {$formattedValue}";
117
    }
118
119
    private function generateParameterCode(ReflectionParameter $param): string
120
    {
121
        $typeStr = ($this->typeString)($param->getType());
122
        $typeStrWithSpace = $typeStr ? $typeStr . ' ' : $typeStr;
123
        $variadicStr = $param->isVariadic() ? '...' : '';
124
        $referenceStr = $param->isPassedByReference() ? '&' : '';
125
        $defaultStr = '';
126
        if ($param->isDefaultValueAvailable()) {
127
            $default = var_export($param->getDefaultValue(), true);
128
            $defaultStr = ' = ' . str_replace(["\r", "\n"], '', $default);
129
        }
130
131
        return "{$typeStrWithSpace}{$referenceStr}{$variadicStr}\${$param->getName()}{$defaultStr}";
132
    }
133
}
134