NelmioApiDocBundle/Parser/JmsMetadataParser.php

341 lines
11 KiB
PHP
Raw Normal View History

<?php
/*
* This file is part of the NelmioApiDocBundle.
*
* (c) Nelmio <hello@nelm.io>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Nelmio\ApiDocBundle\Parser;
use JMS\Serializer\Exclusion\GroupsExclusionStrategy;
2024-10-01 15:54:04 +03:00
use JMS\Serializer\Metadata\PropertyMetadata;
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
use JMS\Serializer\SerializationContext;
use Metadata\MetadataFactoryInterface;
Unified data types [actualType and subType] This is the result of https://github.com/nelmio/NelmioApiDocBundle/issues/410. This PR aims to provide a uniform way of declaring data-types of parameters for parsers and handlers to follow. In turn, this would allow formatters to determine data-types in a cleaner and less volatile manner. (See use-case that can be improved with this PR: https://github.com/nelmio/NelmioApiDocBundle/blob/master/Formatter/AbstractFormatter.php#L103) This is possible by the addition two properties to each property item in `response`, and `parameters` fields in each API endpoint produced by the `ApiDocExtractor`: * `actualType` Contains a value from one of the `DataTypes` class constants. * `subType` Can contain either `null`, or any other `DataTypes` class constant. This is relevant when the `actualType` is a `DataTypes::COLLECTION`, wherein `subType` would specify the type of the collection items. It is also relevant when `actualType` is a `DataTypes::MODEL`, wherein `subType` would contain an identifier of the model (the FQCN or anything the parser would wish to specify) Examples: ```php array( 'id' => array( 'dataType' => 'integer', 'actualType' => DataTypes::INTEGER, 'subType' => null, ), 'profile' => array( 'dataType' => 'object (Profile)', 'actualType' => DataTypes::MODEL, 'subType' => 'Foo\Entity\Profile', 'children' => array( 'name' => array( 'dataType' => 'string', 'actualType' => DataTypes::STRING, 'subType' => null, ), 'birthDate' => array( 'dataType' => 'date', 'actualType' => DataTypes::DATE, 'subType' => null, ), ) ), 'languages' => array( 'dataType' => 'array of strings', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::STRING, ), 'roles' => array( 'dataType' => 'array of choices', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::ENUM, ), 'groups' => array( 'dataType' => 'array of objects (Group)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Group', ), 'profileRevisions' => array( 'dataType' => 'array of objects (Profile)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Profile', ), 'address' => array( 'dataType' => 'object (a_type_a_custom_JMS_serializer_handler_handles)', 'actualType' => DataTypes::MODEL, 'subType' => 'a_type_a_custom_JMS_serializer_handler_handles', ), ); ``` When a formatter omits the `dataType` property or leaves it blank, it is inferred within `ApiDocExtractor` before everything is passed to formatters.
2014-06-17 17:05:00 -07:00
use Nelmio\ApiDocBundle\DataTypes;
use Nelmio\ApiDocBundle\Util\DocCommentExtractor;
/**
* Uses the JMS metadata factory to extract input/output model information
*/
class JmsMetadataParser implements ParserInterface, PostParserInterface
{
/**
2024-10-01 15:54:04 +03:00
* @var MetadataFactoryInterface
*/
private $factory;
/**
* @var PropertyNamingStrategyInterface
*/
private $namingStrategy;
/**
2024-10-01 15:54:04 +03:00
* @var DocCommentExtractor
*/
private $commentExtractor;
2024-10-01 15:54:04 +03:00
private $typeMap = [
Unified data types [actualType and subType] This is the result of https://github.com/nelmio/NelmioApiDocBundle/issues/410. This PR aims to provide a uniform way of declaring data-types of parameters for parsers and handlers to follow. In turn, this would allow formatters to determine data-types in a cleaner and less volatile manner. (See use-case that can be improved with this PR: https://github.com/nelmio/NelmioApiDocBundle/blob/master/Formatter/AbstractFormatter.php#L103) This is possible by the addition two properties to each property item in `response`, and `parameters` fields in each API endpoint produced by the `ApiDocExtractor`: * `actualType` Contains a value from one of the `DataTypes` class constants. * `subType` Can contain either `null`, or any other `DataTypes` class constant. This is relevant when the `actualType` is a `DataTypes::COLLECTION`, wherein `subType` would specify the type of the collection items. It is also relevant when `actualType` is a `DataTypes::MODEL`, wherein `subType` would contain an identifier of the model (the FQCN or anything the parser would wish to specify) Examples: ```php array( 'id' => array( 'dataType' => 'integer', 'actualType' => DataTypes::INTEGER, 'subType' => null, ), 'profile' => array( 'dataType' => 'object (Profile)', 'actualType' => DataTypes::MODEL, 'subType' => 'Foo\Entity\Profile', 'children' => array( 'name' => array( 'dataType' => 'string', 'actualType' => DataTypes::STRING, 'subType' => null, ), 'birthDate' => array( 'dataType' => 'date', 'actualType' => DataTypes::DATE, 'subType' => null, ), ) ), 'languages' => array( 'dataType' => 'array of strings', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::STRING, ), 'roles' => array( 'dataType' => 'array of choices', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::ENUM, ), 'groups' => array( 'dataType' => 'array of objects (Group)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Group', ), 'profileRevisions' => array( 'dataType' => 'array of objects (Profile)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Profile', ), 'address' => array( 'dataType' => 'object (a_type_a_custom_JMS_serializer_handler_handles)', 'actualType' => DataTypes::MODEL, 'subType' => 'a_type_a_custom_JMS_serializer_handler_handles', ), ); ``` When a formatter omits the `dataType` property or leaves it blank, it is inferred within `ApiDocExtractor` before everything is passed to formatters.
2014-06-17 17:05:00 -07:00
'integer' => DataTypes::INTEGER,
'boolean' => DataTypes::BOOLEAN,
'string' => DataTypes::STRING,
'float' => DataTypes::FLOAT,
'double' => DataTypes::FLOAT,
'array' => DataTypes::COLLECTION,
'DateTime' => DataTypes::DATETIME,
2024-10-01 15:54:04 +03:00
];
/**
* Constructor, requires JMS Metadata factory
*/
public function __construct(
MetadataFactoryInterface $factory,
PropertyNamingStrategyInterface $namingStrategy,
2024-10-01 15:54:04 +03:00
DocCommentExtractor $commentExtractor,
) {
$this->factory = $factory;
$this->namingStrategy = $namingStrategy;
$this->commentExtractor = $commentExtractor;
}
2012-08-07 21:57:36 -04:00
public function supports(array $input)
{
$className = $input['class'];
try {
if ($meta = $this->factory->getMetadataForClass($className)) {
return true;
2012-08-27 13:25:03 -04:00
}
} catch (\ReflectionException $e) {
}
2012-08-27 13:25:03 -04:00
return false;
}
2012-08-07 21:57:36 -04:00
public function parse(array $input)
{
$className = $input['class'];
2024-10-01 15:54:04 +03:00
$groups = $input['groups'];
2024-10-01 15:54:04 +03:00
$result = $this->doParse($className, [], $groups);
if (!isset($input['name']) || empty($input['name'])) {
return $result;
}
if ($className && class_exists($className)) {
$parts = explode('\\', $className);
$dataType = sprintf('object (%s)', end($parts));
} else {
$dataType = sprintf('object (%s)', $className);
}
2024-10-01 15:54:04 +03:00
return [
$input['name'] => [
'required' => null,
'readonly' => null,
'default' => null,
'dataType' => $dataType,
'actualType' => DataTypes::MODEL,
'subType' => $dataType,
'children' => $result,
],
];
}
/**
* Recursively parse all metadata for a class
*
2024-10-01 15:54:04 +03:00
* @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
*/
2024-10-01 15:54:04 +03:00
protected function doParse($className, $visited = [], array $groups = [])
{
$meta = $this->factory->getMetadataForClass($className);
2012-08-08 10:21:56 -04:00
if (null === $meta) {
2024-10-01 15:54:04 +03:00
throw new \InvalidArgumentException(sprintf('No metadata found for class %s', $className));
}
2012-08-07 21:57:36 -04:00
2024-10-01 15:54:04 +03:00
$exclusionStrategies = [];
if ($groups) {
$exclusionStrategies[] = new GroupsExclusionStrategy($groups);
}
2024-10-01 15:54:04 +03:00
$params = [];
2012-08-07 21:57:36 -04:00
$reflection = new \ReflectionClass($className);
$defaultProperties = array_map(function ($default) {
2024-10-01 15:54:04 +03:00
if (is_array($default) && 0 === count($default)) {
return null;
}
return $default;
}, $reflection->getDefaultProperties());
2013-02-15 13:32:51 +01:00
// iterate over property metadata
foreach ($meta->propertyMetadata as $item) {
2024-10-01 15:54:04 +03:00
if (null !== $item->type) {
$name = $this->namingStrategy->translateName($item);
2012-08-07 21:57:36 -04:00
$dataType = $this->processDataType($item);
2012-08-07 21:57:36 -04:00
// apply exclusion strategies
foreach ($exclusionStrategies as $strategy) {
if (true === $strategy->shouldSkipProperty($item, SerializationContext::create())) {
continue 2;
}
}
2014-07-08 13:20:13 +02:00
if (!$dataType['inline']) {
2024-10-01 15:54:04 +03:00
$params[$name] = [
'dataType' => $dataType['normalized'],
'actualType' => $dataType['actualType'],
'subType' => $dataType['class'],
'required' => false,
'default' => $defaultProperties[$item->name] ?? null,
// TODO: can't think of a good way to specify this one, JMS doesn't have a setting for this
'description' => $this->getDescription($item),
'readonly' => $item->readOnly,
2014-07-08 13:20:13 +02:00
'sinceVersion' => $item->sinceVersion,
'untilVersion' => $item->untilVersion,
2024-10-01 15:54:04 +03:00
];
2014-07-08 13:20:13 +02:00
2024-10-01 15:54:04 +03:00
if (null !== $dataType['class'] && false === $dataType['primitive']) {
2014-07-08 13:20:13 +02:00
$params[$name]['class'] = $dataType['class'];
}
}
// we can use type property also for custom handlers, then we don't have here real class name
if (!$dataType['class'] || !class_exists($dataType['class'])) {
continue;
}
// if class already parsed, continue, to avoid infinite recursion
if (in_array($dataType['class'], $visited)) {
continue;
}
2013-02-15 13:32:51 +01:00
// check for nested classes with JMS metadata
Unified data types [actualType and subType] This is the result of https://github.com/nelmio/NelmioApiDocBundle/issues/410. This PR aims to provide a uniform way of declaring data-types of parameters for parsers and handlers to follow. In turn, this would allow formatters to determine data-types in a cleaner and less volatile manner. (See use-case that can be improved with this PR: https://github.com/nelmio/NelmioApiDocBundle/blob/master/Formatter/AbstractFormatter.php#L103) This is possible by the addition two properties to each property item in `response`, and `parameters` fields in each API endpoint produced by the `ApiDocExtractor`: * `actualType` Contains a value from one of the `DataTypes` class constants. * `subType` Can contain either `null`, or any other `DataTypes` class constant. This is relevant when the `actualType` is a `DataTypes::COLLECTION`, wherein `subType` would specify the type of the collection items. It is also relevant when `actualType` is a `DataTypes::MODEL`, wherein `subType` would contain an identifier of the model (the FQCN or anything the parser would wish to specify) Examples: ```php array( 'id' => array( 'dataType' => 'integer', 'actualType' => DataTypes::INTEGER, 'subType' => null, ), 'profile' => array( 'dataType' => 'object (Profile)', 'actualType' => DataTypes::MODEL, 'subType' => 'Foo\Entity\Profile', 'children' => array( 'name' => array( 'dataType' => 'string', 'actualType' => DataTypes::STRING, 'subType' => null, ), 'birthDate' => array( 'dataType' => 'date', 'actualType' => DataTypes::DATE, 'subType' => null, ), ) ), 'languages' => array( 'dataType' => 'array of strings', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::STRING, ), 'roles' => array( 'dataType' => 'array of choices', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::ENUM, ), 'groups' => array( 'dataType' => 'array of objects (Group)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Group', ), 'profileRevisions' => array( 'dataType' => 'array of objects (Profile)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Profile', ), 'address' => array( 'dataType' => 'object (a_type_a_custom_JMS_serializer_handler_handles)', 'actualType' => DataTypes::MODEL, 'subType' => 'a_type_a_custom_JMS_serializer_handler_handles', ), ); ``` When a formatter omits the `dataType` property or leaves it blank, it is inferred within `ApiDocExtractor` before everything is passed to formatters.
2014-06-17 17:05:00 -07:00
if ($dataType['class'] && false === $dataType['primitive'] && null !== $this->factory->getMetadataForClass($dataType['class'])) {
2014-07-08 13:20:13 +02:00
$visited[] = $dataType['class'];
2024-10-01 15:54:04 +03:00
$children = $this->doParse($dataType['class'], $visited, $groups);
2014-07-08 13:20:13 +02:00
if ($dataType['inline']) {
$params = array_merge($params, $children);
} else {
$params[$name]['children'] = $children;
}
}
}
}
2012-08-07 21:57:36 -04:00
return $params;
}
2012-08-07 21:57:36 -04:00
/**
* Figure out a normalized data type (for documentation), and get a
* nested class name, if available.
*
* @return array
*/
protected function processDataType(PropertyMetadata $item)
{
2013-02-15 13:32:51 +01:00
// check for a type inside something that could be treated as an array
if ($nestedType = $this->getNestedTypeInArray($item)) {
if ($this->isPrimitive($nestedType)) {
2024-10-01 15:54:04 +03:00
return [
'normalized' => sprintf('array of %ss', $nestedType),
Unified data types [actualType and subType] This is the result of https://github.com/nelmio/NelmioApiDocBundle/issues/410. This PR aims to provide a uniform way of declaring data-types of parameters for parsers and handlers to follow. In turn, this would allow formatters to determine data-types in a cleaner and less volatile manner. (See use-case that can be improved with this PR: https://github.com/nelmio/NelmioApiDocBundle/blob/master/Formatter/AbstractFormatter.php#L103) This is possible by the addition two properties to each property item in `response`, and `parameters` fields in each API endpoint produced by the `ApiDocExtractor`: * `actualType` Contains a value from one of the `DataTypes` class constants. * `subType` Can contain either `null`, or any other `DataTypes` class constant. This is relevant when the `actualType` is a `DataTypes::COLLECTION`, wherein `subType` would specify the type of the collection items. It is also relevant when `actualType` is a `DataTypes::MODEL`, wherein `subType` would contain an identifier of the model (the FQCN or anything the parser would wish to specify) Examples: ```php array( 'id' => array( 'dataType' => 'integer', 'actualType' => DataTypes::INTEGER, 'subType' => null, ), 'profile' => array( 'dataType' => 'object (Profile)', 'actualType' => DataTypes::MODEL, 'subType' => 'Foo\Entity\Profile', 'children' => array( 'name' => array( 'dataType' => 'string', 'actualType' => DataTypes::STRING, 'subType' => null, ), 'birthDate' => array( 'dataType' => 'date', 'actualType' => DataTypes::DATE, 'subType' => null, ), ) ), 'languages' => array( 'dataType' => 'array of strings', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::STRING, ), 'roles' => array( 'dataType' => 'array of choices', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::ENUM, ), 'groups' => array( 'dataType' => 'array of objects (Group)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Group', ), 'profileRevisions' => array( 'dataType' => 'array of objects (Profile)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Profile', ), 'address' => array( 'dataType' => 'object (a_type_a_custom_JMS_serializer_handler_handles)', 'actualType' => DataTypes::MODEL, 'subType' => 'a_type_a_custom_JMS_serializer_handler_handles', ), ); ``` When a formatter omits the `dataType` property or leaves it blank, it is inferred within `ApiDocExtractor` before everything is passed to formatters.
2014-06-17 17:05:00 -07:00
'actualType' => DataTypes::COLLECTION,
'class' => $this->typeMap[$nestedType],
'primitive' => true,
2014-07-08 13:20:13 +02:00
'inline' => false,
2024-10-01 15:54:04 +03:00
];
}
2024-10-01 15:54:04 +03:00
$exp = explode('\\', $nestedType);
2024-10-01 15:54:04 +03:00
return [
'normalized' => sprintf('array of objects (%s)', end($exp)),
Unified data types [actualType and subType] This is the result of https://github.com/nelmio/NelmioApiDocBundle/issues/410. This PR aims to provide a uniform way of declaring data-types of parameters for parsers and handlers to follow. In turn, this would allow formatters to determine data-types in a cleaner and less volatile manner. (See use-case that can be improved with this PR: https://github.com/nelmio/NelmioApiDocBundle/blob/master/Formatter/AbstractFormatter.php#L103) This is possible by the addition two properties to each property item in `response`, and `parameters` fields in each API endpoint produced by the `ApiDocExtractor`: * `actualType` Contains a value from one of the `DataTypes` class constants. * `subType` Can contain either `null`, or any other `DataTypes` class constant. This is relevant when the `actualType` is a `DataTypes::COLLECTION`, wherein `subType` would specify the type of the collection items. It is also relevant when `actualType` is a `DataTypes::MODEL`, wherein `subType` would contain an identifier of the model (the FQCN or anything the parser would wish to specify) Examples: ```php array( 'id' => array( 'dataType' => 'integer', 'actualType' => DataTypes::INTEGER, 'subType' => null, ), 'profile' => array( 'dataType' => 'object (Profile)', 'actualType' => DataTypes::MODEL, 'subType' => 'Foo\Entity\Profile', 'children' => array( 'name' => array( 'dataType' => 'string', 'actualType' => DataTypes::STRING, 'subType' => null, ), 'birthDate' => array( 'dataType' => 'date', 'actualType' => DataTypes::DATE, 'subType' => null, ), ) ), 'languages' => array( 'dataType' => 'array of strings', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::STRING, ), 'roles' => array( 'dataType' => 'array of choices', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::ENUM, ), 'groups' => array( 'dataType' => 'array of objects (Group)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Group', ), 'profileRevisions' => array( 'dataType' => 'array of objects (Profile)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Profile', ), 'address' => array( 'dataType' => 'object (a_type_a_custom_JMS_serializer_handler_handles)', 'actualType' => DataTypes::MODEL, 'subType' => 'a_type_a_custom_JMS_serializer_handler_handles', ), ); ``` When a formatter omits the `dataType` property or leaves it blank, it is inferred within `ApiDocExtractor` before everything is passed to formatters.
2014-06-17 17:05:00 -07:00
'actualType' => DataTypes::COLLECTION,
'class' => $nestedType,
'primitive' => false,
2014-07-08 13:20:13 +02:00
'inline' => false,
2024-10-01 15:54:04 +03:00
];
}
2012-08-24 11:12:36 -04:00
$type = $item->type['name'];
2013-02-15 13:32:51 +01:00
// could be basic type
if ($this->isPrimitive($type)) {
2024-10-01 15:54:04 +03:00
return [
'normalized' => $type,
Unified data types [actualType and subType] This is the result of https://github.com/nelmio/NelmioApiDocBundle/issues/410. This PR aims to provide a uniform way of declaring data-types of parameters for parsers and handlers to follow. In turn, this would allow formatters to determine data-types in a cleaner and less volatile manner. (See use-case that can be improved with this PR: https://github.com/nelmio/NelmioApiDocBundle/blob/master/Formatter/AbstractFormatter.php#L103) This is possible by the addition two properties to each property item in `response`, and `parameters` fields in each API endpoint produced by the `ApiDocExtractor`: * `actualType` Contains a value from one of the `DataTypes` class constants. * `subType` Can contain either `null`, or any other `DataTypes` class constant. This is relevant when the `actualType` is a `DataTypes::COLLECTION`, wherein `subType` would specify the type of the collection items. It is also relevant when `actualType` is a `DataTypes::MODEL`, wherein `subType` would contain an identifier of the model (the FQCN or anything the parser would wish to specify) Examples: ```php array( 'id' => array( 'dataType' => 'integer', 'actualType' => DataTypes::INTEGER, 'subType' => null, ), 'profile' => array( 'dataType' => 'object (Profile)', 'actualType' => DataTypes::MODEL, 'subType' => 'Foo\Entity\Profile', 'children' => array( 'name' => array( 'dataType' => 'string', 'actualType' => DataTypes::STRING, 'subType' => null, ), 'birthDate' => array( 'dataType' => 'date', 'actualType' => DataTypes::DATE, 'subType' => null, ), ) ), 'languages' => array( 'dataType' => 'array of strings', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::STRING, ), 'roles' => array( 'dataType' => 'array of choices', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::ENUM, ), 'groups' => array( 'dataType' => 'array of objects (Group)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Group', ), 'profileRevisions' => array( 'dataType' => 'array of objects (Profile)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Profile', ), 'address' => array( 'dataType' => 'object (a_type_a_custom_JMS_serializer_handler_handles)', 'actualType' => DataTypes::MODEL, 'subType' => 'a_type_a_custom_JMS_serializer_handler_handles', ), ); ``` When a formatter omits the `dataType` property or leaves it blank, it is inferred within `ApiDocExtractor` before everything is passed to formatters.
2014-06-17 17:05:00 -07:00
'actualType' => $this->typeMap[$type],
'class' => null,
'primitive' => true,
2014-07-08 13:20:13 +02:00
'inline' => false,
2024-10-01 15:54:04 +03:00
];
}
// we can use type property also for custom handlers, then we don't have here real class name
if (!$type || !class_exists($type)) {
2024-10-01 15:54:04 +03:00
return [
'normalized' => sprintf('custom handler result for (%s)', $type),
Unified data types [actualType and subType] This is the result of https://github.com/nelmio/NelmioApiDocBundle/issues/410. This PR aims to provide a uniform way of declaring data-types of parameters for parsers and handlers to follow. In turn, this would allow formatters to determine data-types in a cleaner and less volatile manner. (See use-case that can be improved with this PR: https://github.com/nelmio/NelmioApiDocBundle/blob/master/Formatter/AbstractFormatter.php#L103) This is possible by the addition two properties to each property item in `response`, and `parameters` fields in each API endpoint produced by the `ApiDocExtractor`: * `actualType` Contains a value from one of the `DataTypes` class constants. * `subType` Can contain either `null`, or any other `DataTypes` class constant. This is relevant when the `actualType` is a `DataTypes::COLLECTION`, wherein `subType` would specify the type of the collection items. It is also relevant when `actualType` is a `DataTypes::MODEL`, wherein `subType` would contain an identifier of the model (the FQCN or anything the parser would wish to specify) Examples: ```php array( 'id' => array( 'dataType' => 'integer', 'actualType' => DataTypes::INTEGER, 'subType' => null, ), 'profile' => array( 'dataType' => 'object (Profile)', 'actualType' => DataTypes::MODEL, 'subType' => 'Foo\Entity\Profile', 'children' => array( 'name' => array( 'dataType' => 'string', 'actualType' => DataTypes::STRING, 'subType' => null, ), 'birthDate' => array( 'dataType' => 'date', 'actualType' => DataTypes::DATE, 'subType' => null, ), ) ), 'languages' => array( 'dataType' => 'array of strings', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::STRING, ), 'roles' => array( 'dataType' => 'array of choices', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::ENUM, ), 'groups' => array( 'dataType' => 'array of objects (Group)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Group', ), 'profileRevisions' => array( 'dataType' => 'array of objects (Profile)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Profile', ), 'address' => array( 'dataType' => 'object (a_type_a_custom_JMS_serializer_handler_handles)', 'actualType' => DataTypes::MODEL, 'subType' => 'a_type_a_custom_JMS_serializer_handler_handles', ), ); ``` When a formatter omits the `dataType` property or leaves it blank, it is inferred within `ApiDocExtractor` before everything is passed to formatters.
2014-06-17 17:05:00 -07:00
'class' => $type,
'actualType' => DataTypes::MODEL,
'primitive' => false,
2014-07-08 13:20:13 +02:00
'inline' => false,
2024-10-01 15:54:04 +03:00
];
}
2013-02-15 13:32:51 +01:00
// if we got this far, it's a general class name
2024-10-01 15:54:04 +03:00
$exp = explode('\\', $type);
2012-08-24 11:12:36 -04:00
2024-10-01 15:54:04 +03:00
return [
'normalized' => sprintf('object (%s)', end($exp)),
Unified data types [actualType and subType] This is the result of https://github.com/nelmio/NelmioApiDocBundle/issues/410. This PR aims to provide a uniform way of declaring data-types of parameters for parsers and handlers to follow. In turn, this would allow formatters to determine data-types in a cleaner and less volatile manner. (See use-case that can be improved with this PR: https://github.com/nelmio/NelmioApiDocBundle/blob/master/Formatter/AbstractFormatter.php#L103) This is possible by the addition two properties to each property item in `response`, and `parameters` fields in each API endpoint produced by the `ApiDocExtractor`: * `actualType` Contains a value from one of the `DataTypes` class constants. * `subType` Can contain either `null`, or any other `DataTypes` class constant. This is relevant when the `actualType` is a `DataTypes::COLLECTION`, wherein `subType` would specify the type of the collection items. It is also relevant when `actualType` is a `DataTypes::MODEL`, wherein `subType` would contain an identifier of the model (the FQCN or anything the parser would wish to specify) Examples: ```php array( 'id' => array( 'dataType' => 'integer', 'actualType' => DataTypes::INTEGER, 'subType' => null, ), 'profile' => array( 'dataType' => 'object (Profile)', 'actualType' => DataTypes::MODEL, 'subType' => 'Foo\Entity\Profile', 'children' => array( 'name' => array( 'dataType' => 'string', 'actualType' => DataTypes::STRING, 'subType' => null, ), 'birthDate' => array( 'dataType' => 'date', 'actualType' => DataTypes::DATE, 'subType' => null, ), ) ), 'languages' => array( 'dataType' => 'array of strings', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::STRING, ), 'roles' => array( 'dataType' => 'array of choices', 'actualType' => DataTypes::COLLECTION, 'subType' => DataTypes::ENUM, ), 'groups' => array( 'dataType' => 'array of objects (Group)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Group', ), 'profileRevisions' => array( 'dataType' => 'array of objects (Profile)', 'actualType' => DataTypes::COLLECTION, 'subType' => 'Foo\Entity\Profile', ), 'address' => array( 'dataType' => 'object (a_type_a_custom_JMS_serializer_handler_handles)', 'actualType' => DataTypes::MODEL, 'subType' => 'a_type_a_custom_JMS_serializer_handler_handles', ), ); ``` When a formatter omits the `dataType` property or leaves it blank, it is inferred within `ApiDocExtractor` before everything is passed to formatters.
2014-06-17 17:05:00 -07:00
'class' => $type,
'actualType' => DataTypes::MODEL,
'primitive' => false,
2014-07-08 13:20:13 +02:00
'inline' => $item->inline,
2024-10-01 15:54:04 +03:00
];
}
protected function isPrimitive($type)
{
2024-10-01 15:54:04 +03:00
return in_array($type, ['boolean', 'integer', 'string', 'float', 'double', 'array', 'DateTime']);
}
2012-08-24 11:12:36 -04:00
public function postParse(array $input, array $parameters)
{
2024-10-01 15:54:04 +03:00
return $this->doPostParse($parameters, [], $input['groups'] ?? []);
}
/**
* Recursive `doPostParse` to avoid circular post parsing.
*
* @return array
*/
2024-10-01 15:54:04 +03:00
protected function doPostParse(array $parameters, array $visited = [], array $groups = [])
{
2014-07-30 10:50:23 +02:00
foreach ($parameters as $param => $data) {
if (isset($data['class']) && isset($data['children']) && !in_array($data['class'], $visited)) {
$visited[] = $data['class'];
2024-10-01 15:54:04 +03:00
$input = ['class' => $data['class'], 'groups' => $data['groups'] ?? []];
$parameters[$param]['children'] = array_merge(
2014-08-08 12:49:24 +02:00
$parameters[$param]['children'], $this->doPostParse($parameters[$param]['children'], $visited, $groups)
);
$parameters[$param]['children'] = array_merge(
2014-08-08 12:49:24 +02:00
$parameters[$param]['children'], $this->doParse($input['class'], $visited, $groups)
);
}
}
return $parameters;
}
/**
* Check the various ways JMS describes values in arrays, and
* get the value type in the array
*
2012-08-24 13:32:52 -04:00
* @return string|null
*/
2013-02-15 09:24:09 +01:00
protected function getNestedTypeInArray(PropertyMetadata $item)
{
2024-10-01 15:54:04 +03:00
if (isset($item->type['name']) && in_array($item->type['name'], ['array', 'ArrayCollection'])) {
if (isset($item->type['params'][1]['name'])) {
// E.g. array<string, MyNamespaceMyObject>
return $item->type['params'][1]['name'];
}
if (isset($item->type['params'][0]['name'])) {
// E.g. array<MyNamespaceMyObject>
return $item->type['params'][0]['name'];
}
}
2012-08-24 11:12:36 -04:00
2012-08-24 13:32:52 -04:00
return null;
}
2012-08-24 11:12:36 -04:00
2013-10-11 18:17:33 +02:00
protected function getDescription(PropertyMetadata $item)
{
2013-10-11 18:17:33 +02:00
$ref = new \ReflectionClass($item->class);
if ($item instanceof VirtualPropertyMetadata) {
$extracted = $this->commentExtractor->getDocCommentText($ref->getMethod($item->getter));
2012-10-24 15:36:21 +02:00
} else {
$extracted = $this->commentExtractor->getDocCommentText($ref->getProperty($item->name));
}
2012-09-10 09:46:52 -04:00
return $extracted;
}
}