Completed
Push — master ( 378c5b...5052d2 )
by Markus
08:56
created

JmsMetadataParser::isPrimitive()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Mathielen\ImportEngine\Storage\Parser;
4
5
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
6
use JMS\Serializer\SerializationContext;
7
use Metadata\MetadataFactoryInterface;
8
use JMS\Serializer\Metadata\PropertyMetadata;
9
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
10
11
/**
12
 * Uses the JMS metadata factory to extract input/output model information.
13
 */
14
class JmsMetadataParser
15
{
16
    /**
17
     * @var \Metadata\MetadataFactoryInterface
18
     */
19
    private $factory;
20
21
    /**
22
     * @var PropertyNamingStrategyInterface
23
     */
24
    private $namingStrategy;
25
26
    /**
27
     * Constructor, requires JMS Metadata factory.
28
     */
29 2
    public function __construct(
30
        MetadataFactoryInterface $factory,
31
        PropertyNamingStrategyInterface $namingStrategy
32
    ) {
33 2
        $this->factory = $factory;
34 2
        $this->namingStrategy = $namingStrategy;
35 2
    }
36
37
    /**
38
     * {@inheritdoc}
39
     */
40 2
    public function parse(array $input)
41
    {
42 2
        $className = $input['class'];
43 2
        $groups = $input['groups'];
44
45 2
        return $this->doParse($className, array(), $groups);
46
    }
47
48
    /**
49
     * Recursively parse all metadata for a class.
50
     *
51
     * @param string $className Class to get all metadata for
52
     * @param array  $visited   Classes we've already visited to prevent infinite recursion.
53
     * @param array  $groups    Serialization groups to include.
54
     *
55
     * @return array metadata for given class
56
     *
57
     * @throws \InvalidArgumentException
58
     */
59 2
    protected function doParse($className, $visited = array(), array $groups = array())
60
    {
61 2
        $meta = $this->factory->getMetadataForClass($className);
62
63 2
        if (null === $meta) {
64
            throw new \InvalidArgumentException(sprintf('No metadata found for class %s', $className));
65
        }
66
67 2
        $exclusionStrategies = array();
68 2
        if ($groups) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $groups of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
69 1
            $exclusionStrategies[] = new GroupsExclusionStrategy($groups);
70
        }
71
72 2
        $params = array();
73
74
        // iterate over property metadata
75 2
        foreach ($meta->propertyMetadata as $item) {
76 2
            if (!is_null($item->type)) {
77 2
                $name = $this->namingStrategy->translateName($item);
78
79 2
                $dataType = $this->processDataType($item);
80
81
                // apply exclusion strategies
82 2
                foreach ($exclusionStrategies as $strategy) {
83 1
                    if (true === $strategy->shouldSkipProperty($item, SerializationContext::create())) {
84 1
                        continue 2;
85
                    }
86
                }
87
88 2
                $params[$name] = array(
89 2
                    'dataType' => $dataType['normalized'],
90
                    'required' => false,
91 2
                    'readonly' => $item->readOnly,
92 2
                    'sinceVersion' => $item->sinceVersion,
93 2
                    'untilVersion' => $item->untilVersion,
94
                );
95
96 2
                if (!is_null($dataType['class'])) {
97 1
                    $params[$name]['class'] = $dataType['class'];
98
                }
99
100
                // if class already parsed, continue, to avoid infinite recursion
101 2
                if (in_array($dataType['class'], $visited)) {
102 1
                    continue;
103
                }
104
105
                // check for nested classes with JMS metadata
106 2
                if ($dataType['class'] && null !== $this->factory->getMetadataForClass($dataType['class'])) {
107 1
                    $visited[] = $dataType['class'];
108 2
                    $params[$name]['children'] = $this->doParse($dataType['class'], $visited, $groups);
109
                }
110
            }
111
        }
112
113 2
        return $params;
114
    }
115
116
    /**
117
     * Figure out a normalized data type (for documentation), and get a
118
     * nested class name, if available.
119
     *
120
     * @return array
121
     */
122 2
    protected function processDataType(PropertyMetadata $item)
123
    {
124
        // check for a type inside something that could be treated as an array
125 2
        if ($nestedType = $this->getNestedTypeInArray($item)) {
126 2
            if ($this->isPrimitive($nestedType)) {
127
                return array(
128 2
                    'normalized' => sprintf('array of %ss', $nestedType),
129
                    'class' => null,
130
                );
131
            }
132
133 2
            $exp = explode('\\', $nestedType);
134
135
            return array(
136 2
                'normalized' => sprintf('array of objects (%s)', end($exp)),
137 2
                'class' => $nestedType,
138
            );
139
        }
140
141 2
        $type = $item->type['name'];
142
143
        // could be basic type
144 2
        if ($this->isPrimitive($type)) {
145
            return array(
146 1
                'normalized' => $type,
147
                'class' => null,
148
            );
149
        }
150
151
        // we can use type property also for custom handlers, then we don't have here real class name
152 2
        if (!class_exists($type)) {
153
            return array(
154 2
                'normalized' => sprintf('custom handler result for (%s)', $type),
155
                'class' => null,
156
            );
157
        }
158
159
        // if we got this far, it's a general class name
160 2
        $exp = explode('\\', $type);
161
162
        return array(
163 2
            'normalized' => sprintf('object (%s)', end($exp)),
164 2
            'class' => $type,
165
        );
166
    }
167
168 2
    protected function isPrimitive($type)
169
    {
170 2
        return in_array($type, array('boolean', 'integer', 'string', 'float', 'double', 'array', 'DateTime'));
171
    }
172
173
    /**
174
     * Check the various ways JMS describes values in arrays, and
175
     * get the value type in the array.
176
     *
177
     * @param PropertyMetadata $item
178
     *
179
     * @return string|null
180
     */
181 2
    protected function getNestedTypeInArray(PropertyMetadata $item)
182
    {
183 2
        if (isset($item->type['name']) && in_array($item->type['name'], array('array', 'ArrayCollection'))) {
184 2 View Code Duplication
            if (isset($item->type['params'][1]['name'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
185
                // E.g. array<string, MyNamespaceMyObject>
186 2
                return $item->type['params'][1]['name'];
187
            }
188 2 View Code Duplication
            if (isset($item->type['params'][0]['name'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
                // E.g. array<MyNamespaceMyObject>
190 2
                return $item->type['params'][0]['name'];
191
            }
192
        }
193
194 2
        return;
195
    }
196
}
197