| 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.