/** * Recursively parse all metadata for a class * * @param string $className Class to get all metadata for * @param array $visited Classes we've already visited to prevent infinite recursion. * @param array $groups Serialization groups to include. * @return array metadata for given class * @throws \InvalidArgumentException */ protected function doParse($className, $visited = array(), array $groups = array()) { $meta = $this->factory->getMetadataForClass($className); if (null === $meta) { throw new \InvalidArgumentException(sprintf("No metadata found for class %s", $className)); } $exclusionStrategies = array(); $exclusionStrategies[] = new GroupsExclusionStrategy($groups); $params = array(); // iterate over property metadata foreach ($meta->propertyMetadata as $item) { if (!is_null($item->type)) { $name = $this->namingStrategy->translateName($item); $dataType = $this->processDataType($item); // apply exclusion strategies foreach ($exclusionStrategies as $strategy) { if (true === $strategy->shouldSkipProperty($item, SerializationContext::create())) { continue 2; } } $params[$name] = array('dataType' => $dataType['normalized'], 'required' => false, 'description' => $this->getDescription($className, $item), 'readonly' => $item->readOnly, 'sinceVersion' => $item->sinceVersion, 'untilVersion' => $item->untilVersion); // if class already parsed, continue, to avoid infinite recursion if (in_array($dataType['class'], $visited)) { continue; } // check for nested classes with JMS metadata if ($dataType['class'] && null !== $this->factory->getMetadataForClass($dataType['class'])) { $visited[] = $dataType['class']; $params[$name]['children'] = $this->doParse($dataType['class'], $visited, $groups); } } } return $params; }
/** * Recursively parse all metadata for a class * * @param string $className Class to get all metadata for * @param array $visited Classes we've already visited to prevent infinite recursion. * @param array $groups Serialization groups to include. * @return array metadata for given class * @throws \InvalidArgumentException */ protected function doParse($className, $visited = array(), array $groups = array()) { $meta = $this->factory->getMetadataForClass($className); if (null === $meta) { throw new \InvalidArgumentException(sprintf("No metadata found for class %s", $className)); } $exclusionStrategies = array(); if ($groups) { $exclusionStrategies[] = new GroupsExclusionStrategy($groups); } $params = array(); $reflection = new \ReflectionClass($className); $defaultProperties = array_map(function ($default) { if (is_array($default) && count($default) === 0) { return null; } return $default; }, $reflection->getDefaultProperties()); // iterate over property metadata foreach ($meta->propertyMetadata as $item) { if (!is_null($item->type)) { $name = $this->namingStrategy->translateName($item); $dataType = $this->processDataType($item); // apply exclusion strategies foreach ($exclusionStrategies as $strategy) { if (true === $strategy->shouldSkipProperty($item, SerializationContext::create())) { $params[$name] = null; continue 2; } } if (!$dataType['inline']) { $params[$name] = array('dataType' => $dataType['normalized'], 'actualType' => $dataType['actualType'], 'subType' => $dataType['class'], 'required' => false, 'default' => isset($defaultProperties[$item->name]) ? $defaultProperties[$item->name] : null, 'description' => $this->getDescription($item), 'readonly' => $item->readOnly, 'sinceVersion' => $item->sinceVersion, 'untilVersion' => $item->untilVersion); if (!is_null($dataType['class']) && false === $dataType['primitive']) { $params[$name]['class'] = $dataType['class']; } } // we can use type property also for custom handlers, then we don't have here real class name if (!class_exists($dataType['class'])) { continue; } // if class already parsed, continue, to avoid infinite recursion if (in_array($dataType['class'], $visited)) { continue; } // check for nested classes with JMS metadata if ($dataType['class'] && false === $dataType['primitive'] && null !== $this->factory->getMetadataForClass($dataType['class'])) { $visited[] = $dataType['class']; $children = $this->doParse($dataType['class'], $visited, $groups); if ($dataType['inline']) { $params = array_merge($params, $children); } else { $params[$name]['children'] = $children; } } } } return $params; }
/** * rename extracted parameters with JMS metadata naming strategy * * @param array $parameters * @param array $input * @return array */ protected function renameJmsParameters($parameters, $input) { $result = []; foreach ($parameters as $name => $value) { $className = $input['class']; $meta = null; try { $meta = $this->factory->getMetadataForClass($className); } catch (\ReflectionException $e) { } if (isset($meta->propertyMetadata[$name])) { $name = $this->namingStrategy->translateName($meta->propertyMetadata[$name]); } $result[$name] = $value; } return $result; }
public function onPostSerialize(ObjectEvent $event) { $visitor = $event->getVisitor(); $object = $event->getObject(); $context = $event->getContext(); /** @var ClassMetadata $metadata */ $metadata = $this->hateoasMetadataFactory->getMetadataForClass(get_class($object)); // if it has no json api metadata, skip it if (null === $metadata) { return; } /** @var \JMS\Serializer\Metadata\ClassMetadata $jmsMetadata */ $jmsMetadata = $this->jmsMetadataFactory->getMetadataForClass(get_class($object)); $propertyAccessor = PropertyAccess::createPropertyAccessor(); if ($visitor instanceof JsonApiSerializationVisitor) { $visitor->addData(self::EXTRA_DATA_KEY, $this->getRelationshipDataArray($metadata, $this->getId($metadata, $object))); $relationships = array(); foreach ($metadata->getRelationships() as $relationship) { $relationshipPropertyName = $relationship->getName(); $relationshipObject = $propertyAccessor->getValue($object, $relationshipPropertyName); // JMS Serializer support if (!isset($jmsMetadata->propertyMetadata[$relationshipPropertyName])) { continue; } $jmsPropertyMetadata = $jmsMetadata->propertyMetadata[$relationshipPropertyName]; $relationshipPayloadKey = $this->namingStrategy->translateName($jmsPropertyMetadata); $relationshipData =& $relationships[$relationshipPayloadKey]; $relationshipData = array(); // add `links` $links = $this->processRelationshipLinks($object, $relationship, $relationshipPayloadKey); if ($links) { $relationshipData['links'] = $links; } $include = []; if ($request = $this->requestStack->getCurrentRequest()) { $include = $request->query->get('include'); $include = $this->parseInclude($include); } // FIXME: $includePath always is relative to the primary resource, so we can build our way with // class metadata to find out if we can include this relationship. foreach ($include as $includePath) { $last = end($includePath); if ($last === $relationship->getName()) { // keep track of the path we are currently following (e.x. comments -> author) $this->currentPath = $includePath; $relationship->setIncludedByDefault(true); // we are done here, since we have found out we can include this relationship :) break; } } // We show the relationships data if it is included or if there are no links. We do this // because there MUST be links or data (see: http://jsonapi.org/format/#document-resource-object-relationships). if ($relationship->isIncludedByDefault() || !$links || $relationship->getShowData()) { // hasMany relationship if ($this->isIteratable($relationshipObject)) { $relationshipData['data'] = array(); foreach ($relationshipObject as $item) { $relationshipData['data'][] = $this->processRelationship($item, $relationship, $context); } } else { $relationshipData['data'] = $this->processRelationship($relationshipObject, $relationship, $context); } } } if ($relationships) { $visitor->addData('relationships', $relationships); } // TODO: Improve link handling if (true === $metadata->getResource()->getShowLinkSelf()) { $visitor->addData('links', array('self' => $this->baseUrl . '/' . $metadata->getResource()->getType() . '/' . $this->getId($metadata, $object))); } $root = (array) $visitor->getRoot(); $root['included'] = array_values($this->includedRelationships); $visitor->setRoot($root); } }