Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
1 | <?php |
||
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) { |
|
|
|||
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) |
|
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) |
|
193 | |||
194 | } |
||
195 |
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.