Completed
Push — standalone ( f309de...aaef72 )
by Philip
05:10
created

Normalizer::normalizeField()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 31
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 7

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 31
ccs 15
cts 15
cp 1
rs 6.7272
cc 7
eloc 16
nc 7
nop 2
crap 7
1
<?php
2
3
namespace Dontdrinkandroot\RestBundle\Service;
4
5
use Doctrine\Common\Collections\Collection;
6
use Doctrine\Common\Util\ClassUtils;
7
use Doctrine\DBAL\Types\Type;
8
use Dontdrinkandroot\RestBundle\Metadata\Annotation\Method;
9
use Dontdrinkandroot\RestBundle\Metadata\ClassMetadata;
10
use Dontdrinkandroot\RestBundle\Metadata\PropertyMetadata;
11
use Metadata\MetadataFactoryInterface;
12
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
13
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
14
15
class Normalizer
16
{
17
    /**
18
     * @var MetadataFactoryInterface
19
     */
20
    private $metadataFactory;
21
22
    /**
23
     * @var PropertyAccessorInterface
24
     */
25
    private $propertyAccessor;
26
27
    /**
28
     * @var UrlGeneratorInterface
29
     */
30
    private $urlGenerator;
31
32 58
    function __construct(
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
33
        MetadataFactoryInterface $metadataFactory,
34
        PropertyAccessorInterface $propertyAccessor,
35
        UrlGeneratorInterface $urlGenerator
36
    ) {
37 58
        $this->metadataFactory = $metadataFactory;
38 58
        $this->propertyAccessor = $propertyAccessor;
39 58
        $this->urlGenerator = $urlGenerator;
40 58
    }
41
42
    /**
43
     * @param mixed    $data
44
     * @param string[] $includes
45
     * @param int      $depth
46
     * @param string   $path
47
     *
48
     * @return array
49
     */
50 58
    public function normalize($data, $includes = [], int $depth = 0, string $path = '')
51
    {
52 58
        if (is_array($data)) {
53 28
            $normalizedData = [];
54 28
            foreach ($data as $datum) {
55 28
                $normalizedData[] = $this->normalize($datum, $includes, $depth + 1, $path);
56
            }
57
58 28
            return $normalizedData;
59
        }
60
61 58
        if (is_object($data)) {
62
63
            /** @var ClassMetadata $classMetadata */
64 58
            $classMetadata = $this->metadataFactory->getMetadataForClass(ClassUtils::getClass($data));
65
66 58
            $normalizedData = [];
67
68 58
            if ($classMetadata->isRestResource() && $classMetadata->hasMethod(Method::GET) && $this->isIncluded(
69
                    $path,
70 58
                    ['_links'],
71
                    $includes
72
                )
73
            ) {
74 2
                $selfLink = $this->urlGenerator->generate(
75 2
                    $classMetadata->namePrefix . '.get',
76 2
                    ['id' => $this->propertyAccessor->getValue($data, $classMetadata->getIdField())],
77 2
                    UrlGeneratorInterface::ABSOLUTE_URL
78
                );
79 2
                $normalizedData['_links'] = [
80
                    'self' => [
81 2
                        'href' => $selfLink
82
                    ]
83
                ];
84
            }
85
86
            /** @var PropertyMetadata $propertyMetadatum */
87 58
            foreach ($classMetadata->propertyMetadata as $propertyMetadatum) {
88
89 58
                if ($propertyMetadatum->isExcluded()) {
90 32
                    continue;
91
                }
92
93 58
                if ($propertyMetadatum->isAssociation()) {
94
95
                    /* Inlude if includable AND it is on include path */
96 40
                    if ($propertyMetadatum->isIncludable() && $this->isIncluded(
97
                            $path,
98 40
                            $propertyMetadatum->getIncludablePaths(),
99
                            $includes
100
                        )
101
                    ) {
102 24
                        $value = $this->propertyAccessor->getValue($data, $propertyMetadatum->name);
103 24
                        if ($propertyMetadatum->isCollection()) {
104
                            /** @var Collection $value */
105 14
                            $value = $value->getValues();
106
                        }
107 24
                        $normalizedData[$propertyMetadatum->name] = $this->normalize(
108
                            $value,
109
                            $includes,
110 24
                            $depth + 1,
111 40
                            $this->appendPath($path, $propertyMetadatum->name)
112
                        );
113
                    }
114
                } else {
115
116
                    /* Inlude if includable is missing OR it is on include path */
117 58
                    if (!$propertyMetadatum->isIncludable() || $this->isIncluded(
118
                            $path,
119 58
                            $propertyMetadatum->getIncludablePaths(),
120
                            $includes
121
                        )
122
                    ) {
123 58
                        $value = $this->propertyAccessor->getValue($data, $propertyMetadatum->name);
124 58
                        if (is_scalar($value) || array_key_exists($propertyMetadatum->getType(), Type::getTypesMap())) {
125 58
                            $normalizedData[$propertyMetadatum->name] = $this->normalizeField(
126
                                $value,
127
                                $propertyMetadatum
128
                            );
129
                        } else {
130 10
                            $normalizedData[$propertyMetadatum->name] = $this->normalize(
131
                                $value,
132
                                $includes,
133 10
                                $depth + 1,
134 58
                                $this->appendPath($path, $propertyMetadatum->name)
135
                            );
136
                        }
137
                    }
138
                }
139
            }
140
141 58
            return $normalizedData;
142
        }
143
144 12
        return $data;
145
    }
146
147 46
    private function isIncluded($currentPath, array $paths, ?array $includes): bool
148
    {
149 46
        if (null === $includes) {
150
            return false;
151
        }
152
153 46
        foreach ($paths as $path) {
154 46
            if (in_array($this->appendPath($currentPath, $path), $includes)) {
155 46
                return true;
156
            }
157
        }
158
159 46
        return false;
160
    }
161
162 46
    private function appendPath($path, $name)
163
    {
164 46
        if (null === $path || '' === $path) {
165 46
            return $name;
166
        }
167
168 22
        return $path . '.' . $name;
169
    }
170
171 58
    private function normalizeField($value, PropertyMetadata $propertyMetadata)
172
    {
173 58
        switch ($propertyMetadata->getType()) {
174 58
            case 'datetime':
175 8
                if (null === $value) {
176 2
                    return null;
177
                }
178
179
                /** @var $value \DateTime */
180 6
                return $value->format('Y-m-d H:i:s');
181
182 58
            case 'date':
183 8
                if (null === $value) {
184 2
                    return null;
185
                }
186
187
                /** @var $value \DateTime */
188 6
                return $value->format('Y-m-d');
189
190 58
            case 'time':
191 8
                if (null === $value) {
192 2
                    return null;
193
                }
194
195
                /** @var $value \DateTime */
196 6
                return $value->format('H:i:s');
197
198
            default:
199 58
                return $value;
200
        }
201
    }
202
}
203