Total Complexity | 64 |
Total Lines | 374 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like ItemNormalizer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ItemNormalizer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
36 | final class ItemNormalizer extends AbstractItemNormalizer |
||
37 | { |
||
38 | const FORMAT = 'jsonapi'; |
||
39 | |||
40 | private $componentsCache = []; |
||
41 | private $resourceMetadataFactory; |
||
42 | |||
43 | public function __construct(PropertyNameCollectionFactoryInterface $propertyNameCollectionFactory, PropertyMetadataFactoryInterface $propertyMetadataFactory, IriConverterInterface $iriConverter, ResourceClassResolverInterface $resourceClassResolver, PropertyAccessorInterface $propertyAccessor = null, NameConverterInterface $nameConverter = null, ResourceMetadataFactoryInterface $resourceMetadataFactory) |
||
44 | { |
||
45 | parent::__construct($propertyNameCollectionFactory, $propertyMetadataFactory, $iriConverter, $resourceClassResolver, $propertyAccessor, $nameConverter); |
||
46 | |||
47 | $this->resourceMetadataFactory = $resourceMetadataFactory; |
||
48 | } |
||
49 | |||
50 | /** |
||
51 | * {@inheritdoc} |
||
52 | */ |
||
53 | public function supportsNormalization($data, $format = null) |
||
56 | } |
||
57 | |||
58 | /** |
||
59 | * {@inheritdoc} |
||
60 | */ |
||
61 | public function normalize($object, $format = null, array $context = []) |
||
62 | { |
||
63 | if (!isset($context['cache_key'])) { |
||
64 | $context['cache_key'] = $this->getJsonApiCacheKey($format, $context); |
||
65 | } |
||
66 | |||
67 | // Get and populate attributes data |
||
68 | $objectAttributesData = parent::normalize($object, $format, $context); |
||
69 | |||
70 | if (!\is_array($objectAttributesData)) { |
||
71 | return $objectAttributesData; |
||
72 | } |
||
73 | |||
74 | // Get and populate item type |
||
75 | $resourceClass = $this->resourceClassResolver->getResourceClass($object, $context['resource_class'] ?? null, true); |
||
76 | $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); |
||
77 | |||
78 | // Get and populate relations |
||
79 | $components = $this->getComponents($object, $format, $context); |
||
80 | $populatedRelationContext = $context; |
||
81 | $objectRelationshipsData = $this->getPopulatedRelations($object, $format, $populatedRelationContext, $components['relationships']); |
||
82 | $objectRelatedResources = $this->getRelatedResources($object, $format, $context, $components['relationships']); |
||
83 | |||
84 | $item = [ |
||
85 | 'id' => $this->iriConverter->getIriFromItem($object), |
||
86 | 'type' => $resourceMetadata->getShortName(), |
||
87 | ]; |
||
88 | |||
89 | if ($objectAttributesData) { |
||
90 | $item['attributes'] = $objectAttributesData; |
||
91 | } |
||
92 | |||
93 | if ($objectRelationshipsData) { |
||
|
|||
94 | $item['relationships'] = $objectRelationshipsData; |
||
95 | } |
||
96 | |||
97 | $data = ['data' => $item]; |
||
98 | |||
99 | if ($objectRelatedResources) { |
||
100 | $data['included'] = $objectRelatedResources; |
||
101 | } |
||
102 | |||
103 | return $data; |
||
104 | } |
||
105 | |||
106 | /** |
||
107 | * {@inheritdoc} |
||
108 | */ |
||
109 | public function supportsDenormalization($data, $type, $format = null) |
||
110 | { |
||
111 | return self::FORMAT === $format && parent::supportsDenormalization($data, $type, $format); |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * {@inheritdoc} |
||
116 | */ |
||
117 | public function denormalize($data, $class, $format = null, array $context = []) |
||
118 | { |
||
119 | // Avoid issues with proxies if we populated the object |
||
120 | if (!isset($context[self::OBJECT_TO_POPULATE]) && isset($data['data']['id'])) { |
||
121 | if (isset($context['api_allow_update']) && true !== $context['api_allow_update']) { |
||
122 | throw new InvalidArgumentException('Update is not allowed for this operation.'); |
||
123 | } |
||
124 | |||
125 | $context[self::OBJECT_TO_POPULATE] = $this->iriConverter->getItemFromIri( |
||
126 | $data['data']['id'], |
||
127 | $context + ['fetch_data' => false] |
||
128 | ); |
||
129 | } |
||
130 | |||
131 | // Merge attributes and relations previous to apply parents denormalizing |
||
132 | $dataToDenormalize = array_merge( |
||
133 | $data['data']['attributes'] ?? [], |
||
134 | $data['data']['relationships'] ?? [] |
||
135 | ); |
||
136 | |||
137 | return parent::denormalize( |
||
138 | $dataToDenormalize, |
||
139 | $class, |
||
140 | $format, |
||
141 | $context |
||
142 | ); |
||
143 | } |
||
144 | |||
145 | /** |
||
146 | * {@inheritdoc} |
||
147 | */ |
||
148 | protected function getAttributes($object, $format = null, array $context) |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * {@inheritdoc} |
||
155 | */ |
||
156 | protected function setAttributeValue($object, $attribute, $value, $format = null, array $context = []) |
||
159 | } |
||
160 | |||
161 | /** |
||
162 | * {@inheritdoc} |
||
163 | * |
||
164 | * @see http://jsonapi.org/format/#document-resource-object-linkage |
||
165 | */ |
||
166 | protected function denormalizeRelation(string $attributeName, PropertyMetadata $propertyMetadata, string $className, $value, string $format = null, array $context) |
||
167 | { |
||
168 | // Give a chance to other normalizers (e.g.: DateTimeNormalizer) |
||
169 | if (!$this->resourceClassResolver->isResourceClass($className)) { |
||
170 | $context['resource_class'] = $className; |
||
171 | |||
172 | return $this->serializer->denormalize($value, $className, $format, $context); |
||
173 | } |
||
174 | |||
175 | if (!\is_array($value) || !isset($value['id'], $value['type'])) { |
||
176 | throw new InvalidArgumentException('Only resource linkage supported currently, see: http://jsonapi.org/format/#document-resource-object-linkage.'); |
||
177 | } |
||
178 | |||
179 | try { |
||
180 | return $this->iriConverter->getItemFromIri($value['id'], $context + ['fetch_data' => true]); |
||
181 | } catch (ItemNotFoundException $e) { |
||
182 | throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e); |
||
183 | } |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * {@inheritdoc} |
||
188 | * |
||
189 | * @see http://jsonapi.org/format/#document-resource-object-linkage |
||
190 | */ |
||
191 | protected function normalizeRelation(PropertyMetadata $propertyMetadata, $relatedObject, string $resourceClass, string $format = null, array $context) |
||
192 | { |
||
193 | if (null === $relatedObject) { |
||
194 | if (isset($context['operation_type'], $context['subresource_resources'][$resourceClass]) && OperationType::SUBRESOURCE === $context['operation_type']) { |
||
195 | $iri = $this->iriConverter->getItemIriFromResourceClass($resourceClass, $context['subresource_resources'][$resourceClass]); |
||
196 | } else { |
||
197 | unset($context['resource_class']); |
||
198 | |||
199 | return $this->serializer->normalize($relatedObject, $format, $context); |
||
200 | } |
||
201 | } else { |
||
202 | $iri = $this->iriConverter->getIriFromItem($relatedObject); |
||
203 | |||
204 | if (isset($context['resources'])) { |
||
205 | $context['resources'][$iri] = $iri; |
||
206 | } |
||
207 | if (isset($context['api_included'])) { |
||
208 | $context['api_sub_level'] = true; |
||
209 | $data = $this->serializer->normalize($relatedObject, $format, $context); |
||
210 | unset($context['api_sub_level']); |
||
211 | |||
212 | return $data; |
||
213 | } |
||
214 | } |
||
215 | |||
216 | return ['data' => [ |
||
217 | 'type' => $this->resourceMetadataFactory->create($resourceClass)->getShortName(), |
||
218 | 'id' => $iri, |
||
219 | ]]; |
||
220 | } |
||
221 | |||
222 | /** |
||
223 | * {@inheritdoc} |
||
224 | */ |
||
225 | protected function isAllowedAttribute($classOrObject, $attribute, $format = null, array $context = []) |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * Gets JSON API components of the resource: attributes, relationships, meta and links. |
||
232 | * |
||
233 | * @param object $object |
||
234 | * @param string|null $format |
||
235 | * @param array $context |
||
236 | * |
||
237 | * @return array |
||
238 | */ |
||
239 | private function getComponents($object, string $format = null, array $context) |
||
294 | } |
||
295 | |||
296 | /** |
||
297 | * Populates relationships keys. |
||
298 | * |
||
299 | * @param object $object |
||
300 | * @param string|null $format |
||
301 | * @param array $context |
||
302 | * @param array $relationships |
||
303 | * |
||
304 | * @throws InvalidArgumentException |
||
305 | * |
||
306 | * @return array |
||
307 | */ |
||
308 | private function getPopulatedRelations($object, string $format = null, array $context, array $relationships): array |
||
353 | } |
||
354 | |||
355 | /** |
||
356 | * Populates included keys. |
||
357 | */ |
||
358 | private function getRelatedResources($object, string $format = null, array $context, array $relationships): array |
||
393 | } |
||
394 | |||
395 | /** |
||
396 | * Gets the cache key to use. |
||
397 | * |
||
398 | * @param string|null $format |
||
399 | * @param array $context |
||
400 | * |
||
401 | * @return bool|string |
||
402 | */ |
||
403 | private function getJsonApiCacheKey(string $format = null, array $context) |
||
413 |
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.