Completed
Push — master ( 0fb3d7...53ff75 )
by Markus
03:02
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 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
namespace Mathielen\ImportEngine\Storage\Parser;
3
4
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
5
use JMS\Serializer\SerializationContext;
6
use Metadata\MetadataFactoryInterface;
7
use JMS\Serializer\Metadata\PropertyMetadata;
8
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
9
10
/**
11
 * Uses the JMS metadata factory to extract input/output model information
12
 */
13
class JmsMetadataParser
14
{
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
     * @return array                     metadata for given class
55
     * @throws \InvalidArgumentException
56
     */
57 2
    protected function doParse($className, $visited = array(), array $groups = array())
58
    {
59 2
        $meta = $this->factory->getMetadataForClass($className);
60
61 2
        if (null === $meta) {
62
            throw new \InvalidArgumentException(sprintf("No metadata found for class %s", $className));
63
        }
64
65 2
        $exclusionStrategies   = array();
66 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...
67 1
            $exclusionStrategies[] = new GroupsExclusionStrategy($groups);
68
        }
69
70 2
        $params = array();
71
72
        // iterate over property metadata
73 2
        foreach ($meta->propertyMetadata as $item) {
74 2
            if (!is_null($item->type)) {
75 2
                $name = $this->namingStrategy->translateName($item);
76
77 2
                $dataType = $this->processDataType($item);
78
79
                // apply exclusion strategies
80 2
                foreach ($exclusionStrategies as $strategy) {
81 1
                    if (true === $strategy->shouldSkipProperty($item, SerializationContext::create())) {
82 1
                        continue 2;
83
                    }
84
                }
85
86 2
                $params[$name] = array(
87 2
                    'dataType'     => $dataType['normalized'],
88
                    'required'     => false,
89 2
                    'readonly'     => $item->readOnly,
90 2
                    'sinceVersion' => $item->sinceVersion,
91 2
                    'untilVersion' => $item->untilVersion,
92
                );
93
94 2
                if (!is_null($dataType['class'])) {
95 1
                    $params[$name]['class'] = $dataType['class'];
96
                }
97
98
                // if class already parsed, continue, to avoid infinite recursion
99 2
                if (in_array($dataType['class'], $visited)) {
100 1
                    continue;
101
                }
102
103
                // check for nested classes with JMS metadata
104 2
                if ($dataType['class'] && null !== $this->factory->getMetadataForClass($dataType['class'])) {
105 1
                    $visited[]                 = $dataType['class'];
106 2
                    $params[$name]['children'] = $this->doParse($dataType['class'], $visited, $groups);
107
                }
108
            }
109
        }
110
111 2
        return $params;
112
    }
113
114
    /**
115
     * Figure out a normalized data type (for documentation), and get a
116
     * nested class name, if available.
117
     *
118
     * @return array
119
     */
120 2
    protected function processDataType(PropertyMetadata $item)
121
    {
122
        // check for a type inside something that could be treated as an array
123 2
        if ($nestedType = $this->getNestedTypeInArray($item)) {
124 2
            if ($this->isPrimitive($nestedType)) {
125
                return array(
126 2
                    'normalized' => sprintf("array of %ss", $nestedType),
127
                    'class' => null
128
                );
129
            }
130
131 2
            $exp = explode("\\", $nestedType);
132
133
            return array(
134 2
                'normalized' => sprintf("array of objects (%s)", end($exp)),
135 2
                'class' => $nestedType
136
            );
137
        }
138
139 2
        $type = $item->type['name'];
140
141
        // could be basic type
142 2
        if ($this->isPrimitive($type)) {
143
            return array(
144 1
                'normalized' => $type,
145
                'class' => null
146
            );
147
        }
148
149
        // we can use type property also for custom handlers, then we don't have here real class name
150 2
        if (!class_exists($type)) {
151
            return array(
152 2
                'normalized' => sprintf("custom handler result for (%s)", $type),
153
                'class' => null
154
            );
155
        }
156
157
        // if we got this far, it's a general class name
158 2
        $exp = explode("\\", $type);
159
160
        return array(
161 2
            'normalized' => sprintf("object (%s)", end($exp)),
162 2
            'class' => $type
163
        );
164
    }
165
166 2
    protected function isPrimitive($type)
167
    {
168 2
        return in_array($type, array('boolean', 'integer', 'string', 'float', 'double', 'array', 'DateTime'));
169
    }
170
171
    /**
172
     * Check the various ways JMS describes values in arrays, and
173
     * get the value type in the array
174
     *
175
     * @param  PropertyMetadata $item
176
     * @return string|null
177
     */
178 2
    protected function getNestedTypeInArray(PropertyMetadata $item)
179
    {
180 2
        if (isset($item->type['name']) && in_array($item->type['name'], array('array', 'ArrayCollection'))) {
181 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...
182
                // E.g. array<string, MyNamespaceMyObject>
183 2
                return $item->type['params'][1]['name'];
184
            }
185 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...
186
                // E.g. array<MyNamespaceMyObject>
187 2
                return $item->type['params'][0]['name'];
188
            }
189
        }
190
191 2
        return null;
192
    }
193
194
}
195