Skip to content

Instantly share code, notes, and snippets.

@develth
Last active May 15, 2018 10:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save develth/9b4e738538e27e1b6e117b4d2a4c7c03 to your computer and use it in GitHub Desktop.
Save develth/9b4e738538e27e1b6e117b4d2a4c7c03 to your computer and use it in GitHub Desktop.
Example of an ValidationParser for changing parameter keys to snake case
<?php
namespace Smoost\UtilityBundle\Parser;
use Nelmio\ApiDocBundle\DataTypes;
use Nelmio\ApiDocBundle\Parser\ValidationParser;
class SnakeCaseValidationParser extends ValidationParser
{
/**
* Recursively parse constraints.
*
* @param $className
* @param array $visited
* @return array
*/
protected function doParse($className, array $visited, $groups = [])
{
$params = array();
$classdata = $this->factory->getMetadataFor($className);
$properties = $classdata->getConstrainedProperties();
$refl = $classdata->getReflectionClass();
$defaults = $refl->getDefaultProperties();
foreach ($properties as $property) {
$vparams = array();
$vparams['default'] = isset($defaults[$property]) ? $defaults[$property] : null;
$pds = $classdata->getPropertyMetadata($property);
foreach ($pds as $propdata) {
// apply exclusion strategies
if ( TRUE === $this->shouldSkipProperty( $propdata, $groups ) ){
continue 2;
}
$constraints = $propdata->getConstraints();
foreach ($constraints as $constraint) {
$vparams = $this->parseConstraint($constraint, $vparams, $className, $visited);
}
}
if (isset($vparams['format'])) {
$vparams['format'] = join(', ', $vparams['format']);
}
foreach (array('dataType', 'readonly', 'required', 'subType') as $reqprop) {
if (!isset($vparams[$reqprop])) {
$vparams[$reqprop] = null;
}
}
// check for nested classes with All constraint
if (isset($vparams['class']) && !in_array($vparams['class'], $visited) && null !== $this->factory->getMetadataFor($vparams['class'])) {
$visited[] = $vparams['class'];
$vparams['children'] = $this->doParse($vparams['class'], $visited);
}
$vparams['actualType'] = isset($vparams['actualType']) ? $vparams['actualType'] : DataTypes::STRING;
$property = ltrim(strtolower(preg_replace('/[A-Z]/', '_$0', $property)));
$params[$property] = $vparams;
}
return $params;
}
protected function shouldSkipProperty( PropertyMetadata $propdata, $groups = array() ){
return count(array_intersect($groups, array_keys($propdata->constraintsByGroup))) == 0;
}
}
@walva
Copy link

walva commented Jun 16, 2017

Thanks! Great! here is the configuration I use:

services:
   myapp.api_doc.parser.validation_parser:
       class: MyApp\ApiBundle\Parser\SnakeCaseValidationParser
       arguments:
           - "@validator.mapping.class_metadata_factory"
       tags:
           - { name: nelmio_api_doc.extractor.parser }

@walva
Copy link

walva commented Jun 16, 2017

Hello, I added the notion of groups so we can exclude properties from the doc based on Symfony.

class SnakeCaseValidationParser extends ValidationParser
{
    /**
     * {@inheritdoc}
     */
    public function parse( array $input )
    {
        $className = $input[ 'class' ];
        $groups = isset($input[ 'groups' ])?$input[ 'groups' ]:["Default"];

        $parsed = $this->doParse( $className, [], $groups );

        if ( isset( $input[ 'name' ] ) && ! empty( $input[ 'name' ] ) )
        {
            $output = [];
            $output[ $input[ 'name' ] ] = [
                'dataType'    => 'object',
                'actualType'  => 'object',
                'class'       => $className,
                'subType'     => NULL,
                'required'    => NULL,
                'description' => NULL,
                'readonly'    => NULL,
                'children'    => $parsed,
            ];

            return $output;
        }

        return $parsed;
    }

    /**
     * Recursively parse constraints.
     *
     * @param        $className
     * @param  array $visited
     *
     * @return array
     */
    protected function doParse( $className, array $visited, $groups = [] )
    {
        $params = [];
        $classdata = $this->factory->getMetadataFor( $className );
        $properties = $classdata->getConstrainedProperties();
        $refl = $classdata->getReflectionClass();
        $defaults = $refl->getDefaultProperties();
        foreach ( $properties as $property )
        {
            $vparams = [];
            $vparams[ 'default' ] = isset( $defaults[ $property ] ) ? $defaults[ $property ] : NULL;
            $pds = $classdata->getPropertyMetadata( $property );
            foreach ( $pds as $propdata )
            {
                // apply exclusion strategies
                if ( TRUE === $this->shouldSkipProperty( $propdata, $groups ) )
                {
                    dump("CONTINUE");
                    continue 2;
                }

                $constraints = $propdata->getConstraints();
                foreach ( $constraints as $constraint )
                {
                    $vparams = $this->parseConstraint( $constraint, $vparams, $className, $visited );
                }
            }
            if ( isset( $vparams[ 'format' ] ) )
            {
                $vparams[ 'format' ] = join( ', ', $vparams[ 'format' ] );
            }
            foreach ( [ 'dataType', 'readonly', 'required', 'subType' ] as $reqprop )
            {
                if ( ! isset( $vparams[ $reqprop ] ) )
                {
                    $vparams[ $reqprop ] = NULL;
                }
            }
            // check for nested classes with All constraint
            if ( isset( $vparams[ 'class' ] ) && ! in_array( $vparams[ 'class' ], $visited ) && NULL !== $this->factory->getMetadataFor( $vparams[ 'class' ] ) )
            {
                $visited[] = $vparams[ 'class' ];
                $vparams[ 'children' ] = $this->doParse( $vparams[ 'class' ], $visited );
            }
            $vparams[ 'actualType' ] = isset( $vparams[ 'actualType' ] ) ? $vparams[ 'actualType' ] : DataTypes::STRING;
            $property = ltrim( strtolower( preg_replace( '/[A-Z]/', '_$0', $property ) ) );
            $params[ $property ] = $vparams;
        }

        return $params;
    }

    protected function shouldSkipProperty( PropertyMetadata $propdata, $groups = array() )
    {
        return count(array_intersect($groups, array_keys($propdata->constraintsByGroup)))  == 0;
    }
}

@develth
Copy link
Author

develth commented Jul 22, 2017

Great, Thanks!

@florianajir
Copy link

This is not working for recent versions of nelmioapidocbundle

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment