Updated postParse logic to utilize an interface and to avoid unnecessary "supports" checks.

Expanded documentation on new classes and methods.
This commit is contained in:
Josh Hall-Bachner 2013-07-02 21:57:09 -07:00
parent 23f64b84f6
commit 54a6ad566d
4 changed files with 107 additions and 11 deletions

View File

@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\Extractor;
use Doctrine\Common\Annotations\Reader; use Doctrine\Common\Annotations\Reader;
use Nelmio\ApiDocBundle\Annotation\ApiDoc; use Nelmio\ApiDocBundle\Annotation\ApiDoc;
use Nelmio\ApiDocBundle\Parser\ParserInterface; use Nelmio\ApiDocBundle\Parser\ParserInterface;
use Nelmio\ApiDocBundle\Parser\PostParserInterface;
use Symfony\Component\Routing\Route; use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ContainerInterface;
@ -268,15 +269,17 @@ class ApiDocExtractor
$normalizedInput = $this->normalizeClassParameter($input); $normalizedInput = $this->normalizeClassParameter($input);
$supportedParsers = array();
$parameters = array(); $parameters = array();
foreach ($this->parsers as $parser) { foreach ($this->parsers as $parser) {
if ($parser->supports($normalizedInput)) { if ($parser->supports($normalizedInput)) {
$supportedParsers[] = $parser;
$parameters = $this->mergeParameters($parameters, $parser->parse($normalizedInput)); $parameters = $this->mergeParameters($parameters, $parser->parse($normalizedInput));
} }
} }
foreach($this->parsers as $parser) { foreach($supportedParsers as $parser) {
if($parser->supports($normalizedInput) && method_exists($parser, 'postParse')) { if($parser instanceof PostParserInterface) {
$mp = $parser->postParse($normalizedInput, $parameters); $mp = $parser->postParse($normalizedInput, $parameters);
$parameters = $this->mergeParameters($parameters, $mp); $parameters = $this->mergeParameters($parameters, $mp);
} }
@ -383,6 +386,18 @@ class ApiDocExtractor
return array_merge($defaults, $input); return array_merge($defaults, $input);
} }
/**
* Merges two parameter arrays together. This logic allows documentation to be built
* from multiple parser passes, with later passes extending previous passes:
* - Boolean values are true if any parser returns true.
* - Requirement parameters are concatenated.
* - Other string values are overridden by later parsers when present.
* - Array parameters are recursively merged.
*
* @param array $p1 The pre-existing parameters array.
* @param array $p2 The newly-returned parameters array.
* @return array The resulting, merged array.
*/
protected function mergeParameters($p1, $p2) protected function mergeParameters($p1, $p2)
{ {
$params = $p1; $params = $p1;
@ -403,7 +418,7 @@ class ApiDocExtractor
} elseif(!is_null($value)) { } elseif(!is_null($value)) {
if(in_array($name, array('required', 'readonly'))) { if(in_array($name, array('required', 'readonly'))) {
$v1[$name] = $v1[$name] || $value; $v1[$name] = $v1[$name] || $value;
} elseif($name == 'requirement') { } elseif(in_array($name, array('requirement'))) {
if(isset($v1[$name])) { if(isset($v1[$name])) {
$v1[$name] .= ', ' . $value; $v1[$name] .= ', ' . $value;
} else { } else {
@ -438,6 +453,12 @@ class ApiDocExtractor
} }
} }
/**
* Clears the temporary 'class' parameter from the parameters array before it is returned.
*
* @param array $array The source array.
* @return array The cleared array.
*/
protected function clearClasses($array) protected function clearClasses($array)
{ {
if(is_array($array)) { if(is_array($array)) {

View File

@ -33,6 +33,8 @@ interface ParserInterface
* - readonly boolean * - readonly boolean
* - children (optional) array of nested property names mapped to arrays * - children (optional) array of nested property names mapped to arrays
* in the format described here * in the format described here
* - class (optional) the fully-qualified class name of the item, if
* it is represented by an object
* *
* @param string $item The string type of input to parse. * @param string $item The string type of input to parse.
* @return array * @return array

View File

@ -0,0 +1,38 @@
<?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;
/**
* This is the interface parsers must implement in order to register a second parsing pass after the initial structure
* is populated..
*/
interface PostParserInterface
{
/**
* Reparses an object for additional documentation details after it has already been parsed once, to allow
* parsers to extend information initially documented by other parsers.
*
* Returns an array of class property metadata where each item is a key (the property name) and
* an array of data with the following keys:
* - dataType string
* - required boolean
* - description string
* - readonly boolean
* - children (optional) array of nested property names mapped to arrays
* in the format described here
*
* @param string $item The string type of input to parse.
* @param array $parameters The previously-parsed parameters array.
* @return array
*/
public function postParse(array $item, array $parameters);
}

View File

@ -1,18 +1,34 @@
<?php <?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; namespace Nelmio\ApiDocBundle\Parser;
use Nelmio\ApiDocBundle\Parser\ParserInterface;
use Symfony\Component\Validator\MetadataFactoryInterface; use Symfony\Component\Validator\MetadataFactoryInterface;
use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraint;
class ValidationParser implements ParserInterface /**
* Uses the Symfony Validation component to extract information about API objects.
*/
class ValidationParser implements ParserInterface, PostParserInterface
{ {
/** /**
* @var \Symfony\Component\Validator\MetadataFactoryInterface * @var \Symfony\Component\Validator\MetadataFactoryInterface
*/ */
protected $factory; protected $factory;
/**
* Requires a validation MetadataFactory.
*
* @param MetadataFactoryInterface $factory
*/
public function __construct(MetadataFactoryInterface $factory) public function __construct(MetadataFactoryInterface $factory)
{ {
$this->factory = $factory; $this->factory = $factory;
@ -66,7 +82,10 @@ class ValidationParser implements ParserInterface
return $params; return $params;
} }
public function postParse(array $input, $parameters) /**
* {@inheritDoc}
*/
public function postParse(array $input, array $parameters)
{ {
foreach($parameters as $param => $data) { foreach($parameters as $param => $data) {
if(isset($data['class']) && isset($data['children'])) { if(isset($data['class']) && isset($data['children'])) {
@ -83,6 +102,22 @@ class ValidationParser implements ParserInterface
return $parameters; return $parameters;
} }
/**
* Create a valid documentation parameter based on an individual validation Constraint.
* Currently supports:
* - NotBlank/NotNull
* - Type
* - Email
* - Url
* - Ip
* - Length (min and max)
* - Choice (single and multiple, min and max)
* - Regex (match and non-match)
*
* @param Constraint $constraint The constraint metadata object.
* @param array $vparams The existing validation parameters.
* @return mixed The parsed list of validation parameters.
*/
protected function parseConstraint(Constraint $constraint, $vparams) protected function parseConstraint(Constraint $constraint, $vparams)
{ {
$class = substr(get_class($constraint), strlen('Symfony\\Component\\Validator\\Constraints\\')); $class = substr(get_class($constraint), strlen('Symfony\\Component\\Validator\\Constraints\\'));
@ -130,11 +165,11 @@ class ValidationParser implements ParserInterface
} }
break; break;
case 'Regex': case 'Regex':
if($constraint->match) { if($constraint->match) {
$vparams['format'][] = '{match: ' . $constraint->pattern . '}'; $vparams['format'][] = '{match: ' . $constraint->pattern . '}';
} else { } else {
$vparams['format'][] = '{not match: ' . $constraint->pattern . '}'; $vparams['format'][] = '{not match: ' . $constraint->pattern . '}';
} }
break; break;
} }