2017-01-14 17:36:56 +01:00
< ? php
/*
* This file is part of the NelmioApiDocBundle package .
*
* ( c ) Nelmio
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Nelmio\ApiDocBundle\ModelDescriber ;
2018-01-24 19:58:38 +01:00
use Doctrine\Common\Annotations\Reader ;
2017-01-14 17:36:56 +01:00
use EXSyst\Component\Swagger\Schema ;
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface ;
use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareTrait ;
2020-11-20 17:06:56 +01:00
use Nelmio\ApiDocBundle\Exception\UndocumentedArrayItemsException ;
2017-01-14 17:36:56 +01:00
use Nelmio\ApiDocBundle\Model\Model ;
2018-01-24 19:58:38 +01:00
use Nelmio\ApiDocBundle\ModelDescriber\Annotations\AnnotationsReader ;
2019-12-13 21:40:42 +01:00
use Nelmio\ApiDocBundle\PropertyDescriber\PropertyDescriberInterface ;
2017-01-14 17:36:56 +01:00
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface ;
use Symfony\Component\PropertyInfo\Type ;
2020-05-30 18:08:25 +02:00
use Symfony\Component\Serializer\NameConverter\NameConverterInterface ;
2017-01-14 17:36:56 +01:00
class ObjectModelDescriber implements ModelDescriberInterface , ModelRegistryAwareInterface
{
use ModelRegistryAwareTrait ;
2019-12-13 21:40:42 +01:00
/** @var PropertyInfoExtractorInterface */
2017-03-15 12:33:05 +01:00
private $propertyInfo ;
2019-12-13 21:40:42 +01:00
/** @var Reader */
2018-03-17 19:23:29 +01:00
private $doctrineReader ;
2019-12-13 21:40:42 +01:00
/** @var PropertyDescriberInterface[] */
private $propertyDescribers ;
2020-05-30 18:08:25 +02:00
/** @var NameConverterInterface[] */
private $nameConverter ;
2017-12-03 20:30:44 +02:00
2018-01-24 15:20:20 +01:00
private $swaggerDefinitionAnnotationReader ;
2017-12-03 20:30:44 +02:00
public function __construct (
PropertyInfoExtractorInterface $propertyInfo ,
2019-12-13 21:40:42 +01:00
Reader $reader ,
2020-05-30 18:08:25 +02:00
$propertyDescribers ,
NameConverterInterface $nameConverter = null
2017-12-17 10:44:07 +01:00
) {
2017-01-14 17:36:56 +01:00
$this -> propertyInfo = $propertyInfo ;
2018-03-17 19:23:29 +01:00
$this -> doctrineReader = $reader ;
2019-12-13 22:20:13 +01:00
$this -> propertyDescribers = $propertyDescribers ;
2020-05-30 18:08:25 +02:00
$this -> nameConverter = $nameConverter ;
2017-01-14 17:36:56 +01:00
}
public function describe ( Model $model , Schema $schema )
{
$schema -> setType ( 'object' );
$properties = $schema -> getProperties ();
$class = $model -> getType () -> getClassName ();
2020-06-22 16:37:32 +02:00
$context = [ 'serializer_groups' => null ]; // Use the SerializerExtractor with no groups check (sf >= 5.1)
2017-06-13 13:34:26 +02:00
if ( null !== $model -> getGroups ()) {
2020-06-22 16:37:32 +02:00
$context [ 'serializer_groups' ] = array_filter ( $model -> getGroups (), 'is_string' );
2017-06-13 13:34:26 +02:00
}
2018-03-17 19:23:29 +01:00
2020-07-12 14:54:39 +02:00
$reflClass = new \ReflectionClass ( $class );
2018-03-17 19:23:29 +01:00
$annotationsReader = new AnnotationsReader ( $this -> doctrineReader , $this -> modelRegistry );
2020-07-12 14:54:39 +02:00
$annotationsReader -> updateDefinition ( $reflClass , $schema );
2017-06-13 13:34:26 +02:00
$propertyInfoProperties = $this -> propertyInfo -> getProperties ( $class , $context );
2017-03-15 12:33:05 +01:00
if ( null === $propertyInfoProperties ) {
return ;
}
2017-05-31 18:35:02 +02:00
2017-03-15 12:33:05 +01:00
foreach ( $propertyInfoProperties as $propertyName ) {
2020-05-30 18:08:25 +02:00
$serializedName = null !== $this -> nameConverter ? $this -> nameConverter -> normalize ( $propertyName , $class , null , null !== $model -> getGroups () ? [ 'groups' => $model -> getGroups ()] : []) : $propertyName ;
2020-07-12 14:54:39 +02:00
$reflections = $this -> getReflections ( $reflClass , $propertyName );
// Check if a custom name is set
foreach ( $reflections as $reflection ) {
$serializedName = $annotationsReader -> getPropertyName ( $reflection , $serializedName );
}
2018-03-17 19:23:29 +01:00
2020-07-12 14:54:39 +02:00
$property = $properties -> get ( $serializedName );
2018-03-17 19:23:29 +01:00
2020-07-12 14:54:39 +02:00
// Interpret additional options
$groups = $model -> getGroups ();
if ( isset ( $groups [ $propertyName ]) && is_array ( $groups [ $propertyName ])) {
$groups = $model -> getGroups ()[ $propertyName ];
}
foreach ( $reflections as $reflection ) {
$annotationsReader -> updateProperty ( $reflection , $property , $groups );
2018-01-11 12:26:59 +01:00
}
// If type manually defined
2018-03-17 19:23:29 +01:00
if ( null !== $property -> getType () || null !== $property -> getRef ()) {
2018-01-11 12:26:59 +01:00
continue ;
}
2017-01-14 17:36:56 +01:00
$types = $this -> propertyInfo -> getTypes ( $class , $propertyName );
2018-02-14 13:51:06 +01:00
if ( null === $types || 0 === count ( $types )) {
2019-04-12 09:35:49 +02:00
throw new \LogicException ( sprintf ( 'The PropertyInfo component was not able to guess the type of %s::$%s. You may need to add a `@var` annotation or use `@SWG\Property(type="")` to make its type explicit.' , $class , $propertyName ));
2017-01-14 17:36:56 +01:00
}
if ( count ( $types ) > 1 ) {
2019-04-12 09:35:49 +02:00
throw new \LogicException ( sprintf ( 'Property %s::$%s defines more than one type. You can specify the one that should be documented using `@SWG\Property(type="")`.' , $class , $propertyName ));
2017-01-14 17:36:56 +01:00
}
2017-12-03 20:30:44 +02:00
$type = $types [ 0 ];
2019-12-13 22:45:32 +01:00
$this -> describeProperty ( $type , $model , $property , $propertyName );
2019-12-13 22:20:13 +01:00
}
}
2018-01-11 12:26:59 +01:00
2020-07-12 14:54:39 +02:00
/**
* @ return \ReflectionProperty [] | \ReflectionMethod []
*/
private function getReflections ( \ReflectionClass $reflClass , string $propertyName ) : array
{
$reflections = [];
if ( $reflClass -> hasProperty ( $propertyName )) {
$reflections [] = $reflClass -> getProperty ( $propertyName );
}
$camelProp = $this -> camelize ( $propertyName );
foreach ([ '' , 'get' , 'is' , 'has' , 'can' , 'add' , 'remove' , 'set' ] as $prefix ) {
if ( $reflClass -> hasMethod ( $prefix . $camelProp )) {
$reflections [] = $reflClass -> getMethod ( $prefix . $camelProp );
}
}
return $reflections ;
}
/**
* Camelizes a given string .
*/
private function camelize ( string $string ) : string
{
return str_replace ( ' ' , '' , ucwords ( str_replace ( '_' , ' ' , $string )));
}
2019-12-13 22:45:32 +01:00
private function describeProperty ( Type $type , Model $model , Schema $property , string $propertyName )
2019-12-13 22:20:13 +01:00
{
foreach ( $this -> propertyDescribers as $propertyDescriber ) {
2020-02-18 21:08:48 +01:00
if ( $propertyDescriber instanceof ModelRegistryAwareInterface ) {
$propertyDescriber -> setModelRegistry ( $this -> modelRegistry );
}
2019-12-13 22:20:13 +01:00
if ( $propertyDescriber -> supports ( $type )) {
2020-11-20 17:06:56 +01:00
try {
$propertyDescriber -> describe ( $type , $property , $model -> getGroups ());
} catch ( UndocumentedArrayItemsException $e ) {
if ( null !== $e -> getClass ()) {
throw $e ; // This exception is already complete
}
2020-11-20 17:10:21 +01:00
throw new UndocumentedArrayItemsException ( $model -> getType () -> getClassName (), sprintf ( '%s%s' , $propertyName , $e -> getPath ()));
2020-11-20 17:06:56 +01:00
}
2019-12-13 21:40:42 +01:00
2019-12-13 22:20:13 +01:00
return ;
}
2017-01-14 17:36:56 +01:00
}
2019-12-13 22:20:13 +01:00
2019-12-13 22:45:32 +01:00
throw new \Exception ( sprintf ( 'Type "%s" is not supported in %s::$%s. You may use the `@SWG\Property(type="")` annotation to specify it manually.' , $type -> getBuiltinType (), $model -> getType () -> getClassName (), $propertyName ));
2017-01-14 17:36:56 +01:00
}
2017-03-17 19:37:41 +01:00
public function supports ( Model $model ) : bool
2017-01-14 17:36:56 +01:00
{
2019-04-16 12:13:33 +02:00
return Type :: BUILTIN_TYPE_OBJECT === $model -> getType () -> getBuiltinType () && class_exists ( $model -> getType () -> getClassName ());
2017-01-14 17:36:56 +01:00
}
}