2012-08-07 17:50:58 -04:00
|
|
|
<?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 Metadata\MetadataFactoryInterface;
|
2012-08-31 14:57:42 -04:00
|
|
|
use Nelmio\ApiDocBundle\Util\DocCommentExtractor;
|
2012-12-03 16:32:15 +01:00
|
|
|
use JMS\Serializer\Metadata\PropertyMetadata;
|
|
|
|
use JMS\Serializer\Metadata\VirtualPropertyMetadata;
|
2013-02-14 12:20:13 -08:00
|
|
|
use JMS\Serializer\Naming\PropertyNamingStrategyInterface;
|
2012-08-07 17:50:58 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses the JMS metadata factory to extract input/output model information
|
|
|
|
*/
|
|
|
|
class JmsMetadataParser implements ParserInterface
|
|
|
|
{
|
2012-08-07 21:57:36 -04:00
|
|
|
|
2012-08-31 14:57:42 -04:00
|
|
|
/**
|
|
|
|
* @var \Metadata\MetadataFactoryInterface
|
|
|
|
*/
|
|
|
|
private $factory;
|
|
|
|
|
2013-02-14 12:20:13 -08:00
|
|
|
/**
|
|
|
|
* @var PropertyNamingStrategyInterface
|
|
|
|
*/
|
|
|
|
private $namingStrategy;
|
|
|
|
|
2012-08-31 14:57:42 -04:00
|
|
|
/**
|
|
|
|
* @var \Nelmio\ApiDocBundle\Util\DocCommentExtractor
|
|
|
|
*/
|
|
|
|
private $commentExtractor;
|
|
|
|
|
2012-08-07 17:50:58 -04:00
|
|
|
/**
|
|
|
|
* Constructor, requires JMS Metadata factory
|
|
|
|
*/
|
2013-02-14 12:20:13 -08:00
|
|
|
public function __construct(
|
|
|
|
MetadataFactoryInterface $factory,
|
|
|
|
PropertyNamingStrategyInterface $namingStrategy,
|
|
|
|
DocCommentExtractor $commentExtractor
|
|
|
|
) {
|
2012-08-07 17:50:58 -04:00
|
|
|
$this->factory = $factory;
|
2013-02-14 12:20:13 -08:00
|
|
|
$this->namingStrategy = $namingStrategy;
|
2012-08-31 14:57:42 -04:00
|
|
|
$this->commentExtractor = $commentExtractor;
|
2012-08-07 17:50:58 -04:00
|
|
|
}
|
2012-08-07 21:57:36 -04:00
|
|
|
|
2012-08-07 17:50:58 -04:00
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
|
|
|
public function supports($input)
|
|
|
|
{
|
2012-08-27 11:57:11 -04:00
|
|
|
try {
|
|
|
|
if ($meta = $this->factory->getMetadataForClass($input)) {
|
|
|
|
return true;
|
2012-08-27 13:25:03 -04:00
|
|
|
}
|
2012-08-27 11:57:11 -04:00
|
|
|
} catch (\ReflectionException $e) {
|
2012-08-07 17:50:58 -04:00
|
|
|
}
|
2012-08-27 13:25:03 -04:00
|
|
|
|
2012-08-07 17:50:58 -04:00
|
|
|
return false;
|
|
|
|
}
|
2012-08-07 21:57:36 -04:00
|
|
|
|
2012-08-07 17:50:58 -04:00
|
|
|
/**
|
|
|
|
* {@inheritdoc}
|
|
|
|
*/
|
|
|
|
public function parse($input)
|
|
|
|
{
|
2012-12-20 10:12:50 +01:00
|
|
|
return $this->doParse($input);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
* @return array metadata for given class
|
|
|
|
* @throws \InvalidArgumentException
|
|
|
|
*/
|
|
|
|
protected function doParse($className, $visited = array())
|
|
|
|
{
|
|
|
|
$meta = $this->factory->getMetadataForClass($className);
|
2012-08-07 17:50:58 -04:00
|
|
|
|
2012-08-08 10:21:56 -04:00
|
|
|
if (null === $meta) {
|
2012-12-20 10:12:50 +01:00
|
|
|
throw new \InvalidArgumentException(sprintf("No metadata found for class %s", $className));
|
2012-08-07 17:50:58 -04:00
|
|
|
}
|
2012-08-07 21:57:36 -04:00
|
|
|
|
2012-08-07 17:50:58 -04:00
|
|
|
$params = array();
|
2012-08-07 21:57:36 -04:00
|
|
|
|
2013-02-15 13:32:51 +01:00
|
|
|
// iterate over property metadata
|
2012-08-07 17:50:58 -04:00
|
|
|
foreach ($meta->propertyMetadata as $item) {
|
2012-08-07 21:47:33 -04:00
|
|
|
if (!is_null($item->type)) {
|
2013-02-14 12:20:13 -08:00
|
|
|
$name = $this->namingStrategy->translateName($item);
|
2012-08-07 21:57:36 -04:00
|
|
|
|
2013-02-11 14:42:17 +01:00
|
|
|
$dataType = $this->processDataType($item);
|
2012-08-07 21:57:36 -04:00
|
|
|
|
2012-08-07 21:47:33 -04:00
|
|
|
$params[$name] = array(
|
2012-08-24 14:34:48 -04:00
|
|
|
'dataType' => $dataType['normalized'],
|
2012-08-07 21:47:33 -04:00
|
|
|
'required' => false, //TODO: can't think of a good way to specify this one, JMS doesn't have a setting for this
|
2012-12-20 10:12:50 +01:00
|
|
|
'description' => $this->getDescription($className, $item),
|
2012-08-07 21:47:33 -04:00
|
|
|
'readonly' => $item->readOnly
|
|
|
|
);
|
2012-08-24 11:12:36 -04:00
|
|
|
|
2012-10-24 10:29:08 +02:00
|
|
|
// if class already parsed, continue, to avoid infinite recursion
|
2012-12-20 10:12:50 +01:00
|
|
|
if (in_array($dataType['class'], $visited)) {
|
2012-10-24 10:29:08 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-02-15 13:32:51 +01:00
|
|
|
// check for nested classes with JMS metadata
|
2012-08-24 14:34:48 -04:00
|
|
|
if ($dataType['class'] && null !== $this->factory->getMetadataForClass($dataType['class'])) {
|
2012-12-20 10:12:50 +01:00
|
|
|
$visited[] = $dataType['class'];
|
|
|
|
$params[$name]['children'] = $this->doParse($dataType['class'], $visited);
|
2012-08-24 11:10:25 -04:00
|
|
|
}
|
2012-08-07 21:47:33 -04:00
|
|
|
}
|
2012-08-07 17:50:58 -04:00
|
|
|
}
|
2012-08-07 21:57:36 -04:00
|
|
|
|
2012-08-07 17:50:58 -04:00
|
|
|
return $params;
|
|
|
|
}
|
2012-08-07 21:57:36 -04:00
|
|
|
|
2012-08-24 11:10:25 -04:00
|
|
|
/**
|
2012-08-24 14:34:48 -04:00
|
|
|
* Figure out a normalized data type (for documentation), and get a
|
|
|
|
* nested class name, if available.
|
2012-08-24 11:10:25 -04:00
|
|
|
*
|
2013-02-15 09:24:09 +01:00
|
|
|
* @param PropertyMetadata $type
|
2012-08-24 14:34:48 -04:00
|
|
|
* @return array
|
2012-08-24 11:10:25 -04:00
|
|
|
*/
|
2013-02-11 14:42:17 +01:00
|
|
|
protected function processDataType(PropertyMetadata $item)
|
2012-08-24 11:10:25 -04:00
|
|
|
{
|
2013-02-15 13:32:51 +01:00
|
|
|
// check for a type inside something that could be treated as an array
|
2013-02-11 14:42:17 +01:00
|
|
|
if ($nestedType = $this->getNestedTypeInArray($item)) {
|
2012-08-24 14:34:48 -04:00
|
|
|
if ($this->isPrimitive($nestedType)) {
|
|
|
|
return array(
|
|
|
|
'normalized' => sprintf("array of %ss", $nestedType),
|
|
|
|
'class' => null
|
|
|
|
);
|
2012-08-24 11:10:25 -04:00
|
|
|
}
|
2012-08-24 14:34:48 -04:00
|
|
|
|
|
|
|
$exp = explode("\\", $nestedType);
|
|
|
|
|
|
|
|
return array(
|
|
|
|
'normalized' => sprintf("array of objects (%s)", end($exp)),
|
|
|
|
'class' => $nestedType
|
|
|
|
);
|
2012-08-24 11:10:25 -04:00
|
|
|
}
|
2012-08-24 11:12:36 -04:00
|
|
|
|
2013-02-15 10:48:29 +01:00
|
|
|
$type = $item->type['name'];
|
|
|
|
|
2013-02-15 13:32:51 +01:00
|
|
|
// could be basic type
|
2013-02-11 14:42:17 +01:00
|
|
|
if ($this->isPrimitive($type)) {
|
|
|
|
return array(
|
|
|
|
'normalized' => $type,
|
|
|
|
'class' => null
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2013-02-15 13:32:51 +01:00
|
|
|
// if we got this far, it's a general class name
|
2012-08-24 11:10:25 -04:00
|
|
|
$exp = explode("\\", $type);
|
2012-08-24 11:12:36 -04:00
|
|
|
|
2012-08-24 14:34:48 -04:00
|
|
|
return array(
|
|
|
|
'normalized' => sprintf("object (%s)", end($exp)),
|
|
|
|
'class' => $type
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function isPrimitive($type)
|
|
|
|
{
|
2013-03-20 11:16:20 +01:00
|
|
|
return in_array($type, array('boolean', 'integer', 'string', 'float', 'double', 'array', 'DateTime'));
|
2012-08-24 11:10:25 -04:00
|
|
|
}
|
2012-08-24 11:12:36 -04:00
|
|
|
|
2012-08-24 11:10:25 -04:00
|
|
|
/**
|
2012-08-24 14:34:48 -04:00
|
|
|
* Check the various ways JMS describes values in arrays, and
|
|
|
|
* get the value type in the array
|
2012-08-24 11:10:25 -04:00
|
|
|
*
|
2013-02-15 09:24:09 +01:00
|
|
|
* @param PropertyMetadata $item
|
2012-08-24 13:32:52 -04:00
|
|
|
* @return string|null
|
2012-08-24 11:10:25 -04:00
|
|
|
*/
|
2013-02-15 09:24:09 +01:00
|
|
|
protected function getNestedTypeInArray(PropertyMetadata $item)
|
2012-08-24 11:10:25 -04:00
|
|
|
{
|
2013-02-11 14:42:17 +01:00
|
|
|
if (is_array($item->type)
|
2013-02-26 19:24:14 +01:00
|
|
|
&& in_array($item->type['name'], array('array', 'ArrayCollection'))
|
2013-02-11 14:42:17 +01:00
|
|
|
&& isset($item->type['params'])
|
|
|
|
&& 1 === count($item->type['params'])
|
|
|
|
&& isset($item->type['params'][0]['name'])) {
|
|
|
|
return $item->type['params'][0]['name'];
|
2012-08-24 11:10:25 -04:00
|
|
|
}
|
2012-08-24 11:12:36 -04:00
|
|
|
|
2012-08-24 13:32:52 -04:00
|
|
|
return null;
|
2012-08-24 11:10:25 -04:00
|
|
|
}
|
2012-08-24 11:12:36 -04:00
|
|
|
|
2012-10-24 15:09:36 +02:00
|
|
|
protected function getDescription($className, PropertyMetadata $item)
|
2012-08-07 17:50:58 -04:00
|
|
|
{
|
2012-08-31 14:57:42 -04:00
|
|
|
$ref = new \ReflectionClass($className);
|
2012-10-24 15:09:36 +02:00
|
|
|
if ($item instanceof VirtualPropertyMetadata) {
|
|
|
|
$extracted = $this->commentExtractor->getDocCommentText($ref->getMethod($item->getter));
|
2012-10-24 15:36:21 +02:00
|
|
|
} else {
|
2012-10-24 15:09:36 +02:00
|
|
|
$extracted = $this->commentExtractor->getDocCommentText($ref->getProperty($item->name));
|
|
|
|
}
|
2012-09-10 09:46:52 -04:00
|
|
|
|
2012-08-31 11:56:48 -04:00
|
|
|
return !empty($extracted) ? $extracted : "No description.";
|
2012-08-07 17:50:58 -04:00
|
|
|
}
|
2012-08-07 21:47:33 -04:00
|
|
|
|
2012-08-07 17:50:58 -04:00
|
|
|
}
|