diff --git a/Extractor/ApiDocExtractor.php b/Extractor/ApiDocExtractor.php index da7cead..7277732 100644 --- a/Extractor/ApiDocExtractor.php +++ b/Extractor/ApiDocExtractor.php @@ -14,6 +14,7 @@ namespace Nelmio\ApiDocBundle\Extractor; use Doctrine\Common\Annotations\Reader; use Nelmio\ApiDocBundle\Annotation\ApiDoc; use Nelmio\ApiDocBundle\Parser\ParserInterface; +use Nelmio\ApiDocBundle\Parser\PostParserInterface; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -268,15 +269,17 @@ class ApiDocExtractor $normalizedInput = $this->normalizeClassParameter($input); + $supportedParsers = array(); $parameters = array(); foreach ($this->parsers as $parser) { if ($parser->supports($normalizedInput)) { + $supportedParsers[] = $parser; $parameters = $this->mergeParameters($parameters, $parser->parse($normalizedInput)); } } - foreach($this->parsers as $parser) { - if($parser->supports($normalizedInput) && method_exists($parser, 'postParse')) { + foreach($supportedParsers as $parser) { + if($parser instanceof PostParserInterface) { $mp = $parser->postParse($normalizedInput, $parameters); $parameters = $this->mergeParameters($parameters, $mp); } @@ -383,6 +386,18 @@ class ApiDocExtractor 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) { $params = $p1; @@ -403,7 +418,7 @@ class ApiDocExtractor } elseif(!is_null($value)) { if(in_array($name, array('required', 'readonly'))) { $v1[$name] = $v1[$name] || $value; - } elseif($name == 'requirement') { + } elseif(in_array($name, array('requirement'))) { if(isset($v1[$name])) { $v1[$name] .= ', ' . $value; } 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) { if(is_array($array)) { diff --git a/Parser/ParserInterface.php b/Parser/ParserInterface.php index 398b230..3710bcf 100644 --- a/Parser/ParserInterface.php +++ b/Parser/ParserInterface.php @@ -33,6 +33,8 @@ interface ParserInterface * - readonly boolean * - children (optional) array of nested property names mapped to arrays * 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. * @return array diff --git a/Parser/PostParserInterface.php b/Parser/PostParserInterface.php new file mode 100644 index 0000000..51832a7 --- /dev/null +++ b/Parser/PostParserInterface.php @@ -0,0 +1,38 @@ + + * + * 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); +} diff --git a/Parser/ValidationParser.php b/Parser/ValidationParser.php index 0cb4399..363a663 100644 --- a/Parser/ValidationParser.php +++ b/Parser/ValidationParser.php @@ -1,18 +1,34 @@ + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + namespace Nelmio\ApiDocBundle\Parser; -use Nelmio\ApiDocBundle\Parser\ParserInterface; use Symfony\Component\Validator\MetadataFactoryInterface; 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 */ protected $factory; + /** + * Requires a validation MetadataFactory. + * + * @param MetadataFactoryInterface $factory + */ public function __construct(MetadataFactoryInterface $factory) { $this->factory = $factory; @@ -66,7 +82,10 @@ class ValidationParser implements ParserInterface return $params; } - public function postParse(array $input, $parameters) + /** + * {@inheritDoc} + */ + public function postParse(array $input, array $parameters) { foreach($parameters as $param => $data) { if(isset($data['class']) && isset($data['children'])) { @@ -83,6 +102,22 @@ class ValidationParser implements ParserInterface 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) { $class = substr(get_class($constraint), strlen('Symfony\\Component\\Validator\\Constraints\\')); @@ -130,11 +165,11 @@ class ValidationParser implements ParserInterface } break; case 'Regex': - if($constraint->match) { - $vparams['format'][] = '{match: ' . $constraint->pattern . '}'; - } else { - $vparams['format'][] = '{not match: ' . $constraint->pattern . '}'; - } + if($constraint->match) { + $vparams['format'][] = '{match: ' . $constraint->pattern . '}'; + } else { + $vparams['format'][] = '{not match: ' . $constraint->pattern . '}'; + } break; }