Completed
Push — master ( 6366df...fbe022 )
by Kirill
23s queued 19s
created

DoctrineInstantiator::instantiate()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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