Passed
Push — master ( 55fca5...85d75f )
by Kirill
04:44 queued 10s
created

getAvailablePropertiesString()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * This file is part of Attributes package.
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
declare(strict_types=1);
11
12
namespace Spiral\Attributes\Internal\Instantiator;
13
14
use JetBrains\PhpStorm\Pure;
0 ignored issues
show
Bug introduced by
The type JetBrains\PhpStorm\Pure 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...
15
use Doctrine\Common\Annotations\DocParser;
16
use Spiral\Attributes\Exception\AttributeException;
17
18
/**
19
 * @internal DoctrineInstantiator is an internal library class, please do not use it in your code.
20
 * @psalm-internal Spiral\Attributes
21
 */
22
final class DoctrineInstantiator extends Instantiator
23
{
24
    /**
25
     * An error message that occurs when the attribute has no public field in
26
     * a format compatible with doctrine/annotations.
27
     *
28
     * @see DocParser::Annotation()
29
     *
30
     * @var string
31
     */
32
    private const ERROR_INVALID_PROPERTY =
33
        'The attribute #[%s] declared on %s does not have a property named "%s".' . "\n" .
34
        'Available properties: %s'
35
    ;
36
37
    /**
38
     * An error that occurs when specifying invalid arguments for an attribute
39
     * in a format compatible with doctrine/annotations.
40
     *
41
     * @see DocParser::syntaxError()
42
     *
43
     * @var string
44
     */
45
    private const ERROR_INVALID_ARGUMENT = 'Expected %s, got %s';
46
47
    /**
48
     * @var string
49
     */
50
    private const DEFAULT_PROPERTY_NAME = 'value';
51
52
    /**
53
     * @param \ReflectionClass $attr
54
     * @param array $arguments
55
     * @param string $context
56
     * @return object
57
     * @throws \ReflectionException
58
     */
59
    public function instantiate(\ReflectionClass $attr, array $arguments, string $context): object
60
    {
61
        $arguments = $this->formatArguments($arguments);
62
63
        // Using constructor
64
        if ($this->getConstructor($attr)) {
65
            return $attr->newInstance($arguments);
66
        }
67
68
        // Using direct insert
69
        $instance = $attr->newInstanceWithoutConstructor();
70
71
        foreach ($arguments as $name => $value) {
72
            try {
73
                $property = $attr->getProperty($name);
74
75
                if (!$property->isPublic()) {
76
                    throw $this->propertyNotFound($attr, $name, $context);
77
                }
78
79
                $instance->$name = $value;
80
            } catch (\Throwable $e) {
81
                throw $this->propertyNotFound($attr, $name, $context);
82
            }
83
        }
84
85
        return $instance;
86
    }
87
88
    /**
89
     * @param iterable $arguments
90
     * @return array
91
     */
92
    private function formatArguments(iterable $arguments): array
93
    {
94
        $result = [];
95
96
        foreach ($arguments as $name => $value) {
97
            if (\is_int($name)) {
98
                $this->validateArgumentPosition($name, $value);
99
100
                $name = self::DEFAULT_PROPERTY_NAME;
101
            }
102
103
            $result[$name] = $value;
104
        }
105
106
        return $result;
107
    }
108
109
    /**
110
     * @param int $index
111
     * @param mixed $value
112
     */
113
    private function validateArgumentPosition(int $index, $value): void
114
    {
115
        if ($index === 0) {
116
            return;
117
        }
118
119
        $value = \is_scalar($value) ? \var_export($value, true) : \get_debug_type($value);
120
        $message = \sprintf(self::ERROR_INVALID_ARGUMENT, self::DEFAULT_PROPERTY_NAME, $value);
121
122
        throw AttributeException::syntaxError($message);
123
    }
124
125
    /**
126
     * @param \ReflectionClass $attr
127
     * @param string $name
128
     * @param string $context
129
     * @return AttributeException
130
     */
131
    private function propertyNotFound(\ReflectionClass $attr, string $name, string $context): AttributeException
132
    {
133
        $available = $this->getAvailablePropertiesString($attr);
134
        $message = \sprintf(self::ERROR_INVALID_PROPERTY, $attr->getName(), $context, $name, $available);
135
136
        return AttributeException::creationError($message);
137
    }
138
139
    /**
140
     * @param \ReflectionClass $class
141
     * @return string
142
     */
143
    #[Pure]
144
    private function getAvailablePropertiesString(\ReflectionClass $class): string
145
    {
146
        return \implode(', ', \get_class_vars($class->getName()));
147
    }
148
}
149