Completed
Push — standalone ( e42fd2...f309de )
by Philip
10:13
created

YamlDriver   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 197
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 86.87%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 36
lcom 1
cbo 8
dl 0
loc 197
c 1
b 0
f 0
ccs 86
cts 99
cp 0.8687
rs 8.8

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
C loadMetadataFromFile() 0 59 10
D parseFieldConfig() 0 34 9
A getBool() 0 13 3
A getArrayValue() 0 12 3
A getExtension() 0 4 1
A getOrCreatePropertymetadata() 0 11 2
B parseMethods() 0 24 6
A parseRight() 0 8 1
1
<?php
2
3
namespace Dontdrinkandroot\RestBundle\Metadata\Driver;
4
5
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Method;
6
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Right;
7
use Dontdrinkandroot\RestBundle\Metadata\ClassMetadata;
8
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
9
use Metadata\Driver\AbstractFileDriver;
10
use Metadata\Driver\DriverInterface;
11
use Metadata\Driver\FileLocatorInterface;
12
use Symfony\Component\Yaml\Yaml;
13
14
class YamlDriver extends AbstractFileDriver
15
{
16
    /**
17
     * @var DriverInterface
18
     */
19
    private $doctrineDriver;
20
21 62
    public function __construct(FileLocatorInterface $locator, DriverInterface $doctrineDriver)
22
    {
23 62
        parent::__construct($locator);
24 62
        $this->doctrineDriver = $doctrineDriver;
25 62
    }
26
27
    /**
28
     * {@inheritdoc}
29
     */
30 22
    protected function loadMetadataFromFile(\ReflectionClass $class, $file)
31
    {
32
        /** @var ClassMetadata $ddrRestClassMetadata */
33 22
        $classMetadata = $this->doctrineDriver->loadMetadataForClass($class);
34 22
        if (null === $classMetadata) {
35
            $classMetadata = new ClassMetadata($class->getName());
36
        }
37
38 22
        $config = Yaml::parse(file_get_contents($file));
39 22
        $className = key($config);
40
41 22
        if ($className !== $class->name) {
42
            throw new \RuntimeException(
43
                sprintf('Class definition mismatch for "%s" in "%s": %s', $class->getName(), $file, key($config))
44
            );
45
        }
46
47 22
        $config = $config[$className];
48 22
        if (!is_array($config)) {
49
            $config = [];
50
        }
51
52 22
        if (array_key_exists('rootResource', $config) && true === $config['rootResource']) {
53 22
            $classMetadata->setRestResource(true);
54
        }
55
56 22
        $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...
57 22
        $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...
58
59 22
        $classMetadata->setMethods($this->parseMethods($config));
60
61 22
        $fieldConfigs = [];
62 22
        if (array_key_exists('fields', $config)) {
63 22
            $fieldConfigs = $config['fields'];
64
        }
65
66 22
        foreach ($class->getProperties() as $reflectionProperty) {
67
68 22
            $propertyName = $reflectionProperty->getName();
69 22
            $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...
70
71 22
            if (array_key_exists($propertyName, $fieldConfigs)) {
72 22
                $fieldConfig = $fieldConfigs[$propertyName];
73 22
                $this->parseFieldConfig($propertyName, $fieldConfig, $propertyMetadata);
74 22
                unset($fieldConfigs[$propertyName]);
75
            }
76
77 22
            $classMetadata->addPropertyMetadata($propertyMetadata);
78
        }
79
80
        /* Parse unbacked field definitions */
81 22
        foreach ($fieldConfigs as $name => $fieldConfig) {
82 6
            $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...
83 6
            $this->parseFieldConfig($name, $fieldConfig, $propertyMetadata);
84 6
            $classMetadata->addPropertyMetadata($propertyMetadata);
85
        }
86
87 22
        return $classMetadata;
88
    }
89
90 22
    protected function parseFieldConfig(string $name, array $fieldConfig, PropertyMetadata $propertyMetadata): void
91
    {
92 22
        if (null !== $value = $this->getBool('postable', $fieldConfig)) {
93
            $propertyMetadata->setPostable($value);
94
        }
95
96 22
        if (null !== $value = $this->getBool('puttable', $fieldConfig)) {
97
            $propertyMetadata->setPuttable($value);
98
        }
99
100 22
        if (null !== $value = $this->getBool('excluded', $fieldConfig)) {
101 6
            $propertyMetadata->setExcluded($value);
102
        }
103
104 22
        if (null !== $value = $this->getBool('virtual', $fieldConfig)) {
105
            $propertyMetadata->setVirtual($value);
106
        }
107
108 22
        if (null !== $subResourceConfig = $fieldConfig['subResource'] ?? null) {
109 20
            $propertyMetadata->setSubResource(true);
110 20
            $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...
111
        }
112
113 22
        if (array_key_exists('includable', $fieldConfig)) {
114 20
            $value = $fieldConfig['includable'];
115 20
            if (is_array($value)) {
116 20
                $propertyMetadata->setIncludable(true);
117 20
                $propertyMetadata->setIncludablePaths($value);
118
            } elseif (true === $value) {
119
                $propertyMetadata->setIncludable(true);
120
                $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...
121
            }
122
        }
123 22
    }
124
125 22
    private function getBool(string $key, array $haystack, bool $required = false)
126
    {
127 22
        $value = $this->getArrayValue($key, $haystack, $required);
128 22
        if (null === $value) {
129 22
            return null;
130
        }
131
132 6
        if (!is_bool($value)) {
133
            throw new \RuntimeException(sprintf('Value %s must be of type bool', $key));
134
        }
135
136 6
        return $value;
137
    }
138
139 22
    private function getArrayValue(string $key, array $haystack, bool $required = false)
140
    {
141 22
        if (!array_key_exists($key, $haystack)) {
142 22
            if ($required) {
143
                throw new \RuntimeException(sprintf('Value %s is required', $key));
144
            }
145
146 22
            return null;
147
        }
148
149 22
        return $haystack[$key];
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155 62
    protected function getExtension()
156
    {
157 62
        return 'rest.yml';
158
    }
159
160 22
    protected function getOrCreatePropertymetadata(ClassMetadata $classMetadata, $propertyName): PropertyMetadata
161
    {
162 22
        $propertyMetadata = $classMetadata->getPropertyMetadata($propertyName);
163 22
        if (null === $propertyMetadata) {
164 6
            $propertyMetadata = new PropertyMetadata($classMetadata->name, $propertyName);
165
166 6
            return $propertyMetadata;
167
        }
168
169 20
        return $propertyMetadata;
170
    }
171
172
    /**
173
     * @param array $config
174
     *
175
     * @return Method[]
176
     */
177 22
    private function parseMethods(array $config)
178
    {
179 22
        if (!array_key_exists('methods', $config)) {
180
            return null;
181
        }
182
183 22
        $methods = [];
184 22
        $methodsConfig = $config['methods'];
185 22
        foreach ($methodsConfig as $name => $config) {
186 22
            $method = new Method();
187 22
            $method->name = $name;
0 ignored issues
show
Documentation Bug introduced by
It seems like $name can also be of type integer. However, the property $name is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
188 22
            if (null !== $config) {
189 20
                if (array_key_exists('right', $config)) {
190 20
                    $method->right = $this->parseRight($config['right']);
191
                }
192 20
                if (array_key_exists('defaultIncludes', $config)) {
193 20
                    $method->defaultIncludes = $config['defaultIncludes'];
194
                }
195
            }
196 22
            $methods[$method->name] = $method;
197
        }
198
199 22
        return $methods;
200
    }
201
202 20
    private function parseRight(array $config): Right
203
    {
204 20
        $right = new Right();
205 20
        $right->attributes = $this->getArrayValue('attributes', $config);
206 20
        $right->propertyPath = $this->getArrayValue('propertyPath', $config);
207
208 20
        return $right;
209
    }
210
}
211