Completed
Push — standalone ( 1b0272...9e8eb1 )
by Philip
04:15
created

YamlDriver::parseFieldConfig()   C

Complexity

Conditions 7
Paths 32

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 19
cts 19
cp 1
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 18
nc 32
nop 3
crap 7
1
<?php
2
3
namespace Dontdrinkandroot\RestBundle\Metadata\Driver;
4
5
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Method;
6
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Postable;
7
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Puttable;
8
use Dontdrinkandroot\RestBundle\Metadata\ClassMetadata;
9
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
10
use Metadata\Driver\AbstractFileDriver;
11
use Metadata\Driver\DriverInterface;
12
use Metadata\Driver\FileLocatorInterface;
13
use Symfony\Component\Yaml\Yaml;
14
15
class YamlDriver extends AbstractFileDriver
16
{
17
    /**
18
     * @var DriverInterface
19
     */
20
    private $doctrineDriver;
21
22 84
    public function __construct(FileLocatorInterface $locator, DriverInterface $doctrineDriver)
23
    {
24 84
        parent::__construct($locator);
25 84
        $this->doctrineDriver = $doctrineDriver;
26 84
    }
27
28
    /**
29
     * {@inheritdoc}
30
     */
31 48
    protected function loadMetadataFromFile(\ReflectionClass $class, $file)
32
    {
33
        /** @var ClassMetadata $ddrRestClassMetadata */
34 48
        $classMetadata = $this->doctrineDriver->loadMetadataForClass($class);
35 48
        if (null === $classMetadata) {
36
            $classMetadata = new ClassMetadata($class->getName());
37
        }
38
39 48
        $config = Yaml::parse(file_get_contents($file));
40 48
        $className = key($config);
41
42 48
        if ($className !== $class->name) {
43
            throw new \RuntimeException(
44
                sprintf('Class definition mismatch for "%s" in "%s": %s', $class->getName(), $file, key($config))
45
            );
46
        }
47
48 48
        $config = $config[$className];
49 48
        if (!is_array($config)) {
50
            $config = [];
51
        }
52
53 48
        if (array_key_exists('rootResource', $config) && true === $config['rootResource']) {
54 48
            $classMetadata->setRestResource(true);
55
        }
56
57 48
        $classMetadata->service = $config['service'] ?? null;
0 ignored issues
show
Bug introduced by
The property service does not seem to exist in Metadata\ClassMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
58 48
        $classMetadata->idField = $config['idField'] ?? null;
0 ignored issues
show
Bug introduced by
The property idField does not seem to exist in Metadata\ClassMetadata.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
59
60 48
        $classMetadata->setMethods($this->parseMethods($config));
61
62 48
        $fieldConfigs = [];
63 48
        if (array_key_exists('fields', $config)) {
64 48
            $fieldConfigs = $config['fields'];
65
        }
66
67 48
        foreach ($class->getProperties() as $reflectionProperty) {
68
69 48
            $propertyName = $reflectionProperty->getName();
70 48
            $propertyMetadata = $this->getOrCreatePropertymetadata($classMetadata, $propertyName);
0 ignored issues
show
Compatibility introduced by
$classMetadata of type object<Metadata\ClassMetadata> is not a sub-type of object<Dontdrinkandroot\...Metadata\ClassMetadata>. It seems like you assume a child class of the class Metadata\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
71
72 48
            if (array_key_exists($propertyName, $fieldConfigs)) {
73 48
                $fieldConfig = $fieldConfigs[$propertyName];
74 48
                $this->parseFieldConfig($propertyName, $fieldConfig, $propertyMetadata);
75 48
                unset($fieldConfigs[$propertyName]);
76
            }
77
78 48
            $classMetadata->addPropertyMetadata($propertyMetadata);
79
        }
80
81
        /* Parse unbacked field definitions */
82 48
        foreach ($fieldConfigs as $name => $fieldConfig) {
83 30
            $propertyMetadata = $this->getOrCreatePropertymetadata($classMetadata, $name);
0 ignored issues
show
Compatibility introduced by
$classMetadata of type object<Metadata\ClassMetadata> is not a sub-type of object<Dontdrinkandroot\...Metadata\ClassMetadata>. It seems like you assume a child class of the class Metadata\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
84 30
            $this->parseFieldConfig($name, $fieldConfig, $propertyMetadata);
85 30
            $classMetadata->addPropertyMetadata($propertyMetadata);
86
        }
87
88 48
        return $classMetadata;
89
    }
90
91 48
    protected function parseFieldConfig(string $name, array $fieldConfig, PropertyMetadata $propertyMetadata): void
92
    {
93 48
        $propertyMetadata->setPostable(Postable::parse($fieldConfig['postable'] ?? null));
94 48
        $propertyMetadata->setPuttable(Puttable::parse($fieldConfig['puttable'] ?? null));
95
96 48
        if (null !== $value = $this->getBool('excluded', $fieldConfig)) {
97 30
            $propertyMetadata->setExcluded($value);
98
        }
99
100 48
        if (null !== $value = $this->getBool('virtual', $fieldConfig)) {
101 28
            $propertyMetadata->setVirtual($value);
102
        }
103
104 48
        if (null !== $subResourceConfig = $fieldConfig['subResource'] ?? null) {
105 46
            $propertyMetadata->setSubResource(true);
106 46
            $propertyMetadata->setMethods($this->parseMethods($subResourceConfig));
0 ignored issues
show
Documentation introduced by
$this->parseMethods($subResourceConfig) is of type null|array, but the function expects a array<integer,object<Don...ata\Annotation\Method>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
107
        }
108
109 48
        if (array_key_exists('includable', $fieldConfig)) {
110 46
            $value = $fieldConfig['includable'];
111 46
            if (is_array($value)) {
112 30
                $propertyMetadata->setIncludable(true);
113 30
                $propertyMetadata->setIncludablePaths($value);
114 46
            } elseif (true === $value) {
115 46
                $propertyMetadata->setIncludable(true);
116 46
                $propertyMetadata->setIncludablePaths([$name]);
0 ignored issues
show
Documentation introduced by
array($name) is of type array<integer,string,{"0":"string"}>, but the function expects a null|array<integer,object<string>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
117
            }
118
        }
119 48
    }
120
121 48
    private function getBool(string $key, array $haystack, bool $required = false)
122
    {
123 48
        $value = $this->getArrayValue($key, $haystack, $required);
124 48
        if (null === $value) {
125 48
            return null;
126
        }
127
128 30
        if (!is_bool($value)) {
129
            throw new \RuntimeException(sprintf('Value %s must be of type bool', $key));
130
        }
131
132 30
        return $value;
133
    }
134
135 48
    private function getArrayValue(string $key, array $haystack, bool $required = false)
136
    {
137 48
        if (!array_key_exists($key, $haystack)) {
138 48
            if ($required) {
139
                throw new \RuntimeException(sprintf('Value %s is required', $key));
140
            }
141
142 48
            return null;
143
        }
144
145 30
        return $haystack[$key];
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151 84
    protected function getExtension()
152
    {
153 84
        return 'rest.yml';
154
    }
155
156 48
    protected function getOrCreatePropertymetadata(ClassMetadata $classMetadata, $propertyName): PropertyMetadata
157
    {
158 48
        $propertyMetadata = $classMetadata->getPropertyMetadata($propertyName);
159 48
        if (null === $propertyMetadata) {
160 30
            $propertyMetadata = new PropertyMetadata($classMetadata->name, $propertyName);
161
162 30
            return $propertyMetadata;
163
        }
164
165 46
        return $propertyMetadata;
166
    }
167
168
    /**
169
     * @param array $config
170
     *
171
     * @return Method[]
172
     */
173 48
    private function parseMethods(array $config)
174
    {
175 48
        if (!array_key_exists('methods', $config)) {
176
            return null;
177
        }
178
179 48
        $methods = [];
180 48
        $methodsConfig = $config['methods'];
181 48
        foreach ($methodsConfig as $name => $config) {
182 48
            $method = Method::parse($name, $config);
183 48
            $methods[$method->name] = $method;
184
        }
185
186 48
        return $methods;
187
    }
188
}
189