vendor/nelmio/api-doc-bundle/Model/ModelRegistry.php line 96

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of the NelmioApiDocBundle package.
  4.  *
  5.  * (c) Nelmio
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Nelmio\ApiDocBundle\Model;
  11. use Nelmio\ApiDocBundle\Describer\ModelRegistryAwareInterface;
  12. use Nelmio\ApiDocBundle\ModelDescriber\ModelDescriberInterface;
  13. use Nelmio\ApiDocBundle\OpenApiPhp\Util;
  14. use OpenApi\Annotations as OA;
  15. use Psr\Log\LoggerAwareTrait;
  16. use Psr\Log\NullLogger;
  17. use Symfony\Component\PropertyInfo\Type;
  18. final class ModelRegistry
  19. {
  20.     use LoggerAwareTrait;
  21.     private $registeredModelNames = [];
  22.     private $alternativeNames = [];
  23.     private $unregistered = [];
  24.     private $models = [];
  25.     private $names = [];
  26.     private $modelDescribers = [];
  27.     private $api;
  28.     /**
  29.      * @param ModelDescriberInterface[]|iterable $modelDescribers
  30.      *
  31.      * @internal
  32.      */
  33.     public function __construct($modelDescribersOA\OpenApi $api, array $alternativeNames = [])
  34.     {
  35.         $this->modelDescribers $modelDescribers;
  36.         $this->api $api;
  37.         $this->logger = new NullLogger();
  38.         foreach (array_reverse($alternativeNames) as $alternativeName => $criteria) {
  39.             $this->alternativeNames[] = $model = new Model(new Type('object'false$criteria['type']), $criteria['groups']);
  40.             $this->names[$model->getHash()] = $alternativeName;
  41.             $this->registeredModelNames[$alternativeName] = $model;
  42.             Util::getSchema($this->api$alternativeName);
  43.         }
  44.     }
  45.     public function register(Model $model): string
  46.     {
  47.         $hash $model->getHash();
  48.         if (!isset($this->models[$hash])) {
  49.             $this->models[$hash] = $model;
  50.             $this->unregistered[] = $hash;
  51.         }
  52.         if (!isset($this->names[$hash])) {
  53.             $this->names[$hash] = $this->generateModelName($model);
  54.             $this->registeredModelNames[$this->names[$hash]] = $model;
  55.         }
  56.         // Reserve the name
  57.         Util::getSchema($this->api$this->names[$hash]);
  58.         return OA\Components::SCHEMA_REF.$this->names[$hash];
  59.     }
  60.     /**
  61.      * @internal
  62.      */
  63.     public function registerSchemas(): void
  64.     {
  65.         while (count($this->unregistered)) {
  66.             $tmp = [];
  67.             foreach ($this->unregistered as $hash) {
  68.                 $tmp[$this->names[$hash]] = $this->models[$hash];
  69.             }
  70.             $this->unregistered = [];
  71.             foreach ($tmp as $name => $model) {
  72.                 $schema null;
  73.                 foreach ($this->modelDescribers as $modelDescriber) {
  74.                     if ($modelDescriber instanceof ModelRegistryAwareInterface) {
  75.                         $modelDescriber->setModelRegistry($this);
  76.                     }
  77.                     if ($modelDescriber->supports($model)) {
  78.                         $schema Util::getSchema($this->api$name);
  79.                         $modelDescriber->describe($model$schema);
  80.                         break;
  81.                     }
  82.                 }
  83.                 if (null === $schema) {
  84.                     $errorMessage sprintf('Schema of type "%s" can\'t be generated, no describer supports it.'$this->typeToString($model->getType()));
  85.                     if (Type::BUILTIN_TYPE_OBJECT === $model->getType()->getBuiltinType() && !class_exists($className $model->getType()->getClassName())) {
  86.                         $errorMessage .= sprintf(' Class "\\%s" does not exist, did you forget a use statement, or typed it wrong?'$className);
  87.                     }
  88.                     throw new \LogicException($errorMessage);
  89.                 }
  90.             }
  91.         }
  92.         if (empty($this->unregistered) && !empty($this->alternativeNames)) {
  93.             foreach ($this->alternativeNames as $model) {
  94.                 $this->register($model);
  95.             }
  96.             $this->alternativeNames = [];
  97.             $this->registerSchemas();
  98.         }
  99.     }
  100.     private function generateModelName(Model $model): string
  101.     {
  102.         $name $base $this->getTypeShortName($model->getType());
  103.         $names array_column(
  104.             $this->api->components instanceof OA\Components && is_array($this->api->components->schemas) ? $this->api->components->schemas : [],
  105.             'schema'
  106.         );
  107.         $i 1;
  108.         while (\in_array($name$namestrue)) {
  109.             if (isset($this->registeredModelNames[$name])) {
  110.                 $this->logger->info(sprintf('Can not assign a name for the model, the name "%s" has already been taken.'$name), [
  111.                     'model' => $this->modelToArray($model),
  112.                     'taken_by' => $this->modelToArray($this->registeredModelNames[$name]),
  113.                 ]);
  114.             }
  115.             ++$i;
  116.             $name $base.$i;
  117.         }
  118.         return $name;
  119.     }
  120.     private function modelToArray(Model $model): array
  121.     {
  122.         $getType = function (Type $type) use (&$getType) {
  123.             return [
  124.                 'class' => $type->getClassName(),
  125.                 'built_in_type' => $type->getBuiltinType(),
  126.                 'nullable' => $type->isNullable(),
  127.                 'collection' => $type->isCollection(),
  128.                 'collection_key_types' => $type->isCollection() ? array_map($getType$this->getCollectionKeyTypes($type)) : null,
  129.                 'collection_value_types' => $type->isCollection() ? array_map($getType$this->getCollectionValueTypes($type)) : null,
  130.             ];
  131.         };
  132.         return [
  133.             'type' => $getType($model->getType()),
  134.             'options' => $model->getOptions(),
  135.             'groups' => $model->getGroups(),
  136.         ];
  137.     }
  138.     private function getTypeShortName(Type $type): string
  139.     {
  140.         if (null !== $collectionType $this->getCollectionValueType($type)) {
  141.             return $this->getTypeShortName($collectionType).'[]';
  142.         }
  143.         if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType()) {
  144.             $parts explode('\\'$type->getClassName());
  145.             return end($parts);
  146.         }
  147.         return $type->getBuiltinType();
  148.     }
  149.     private function typeToString(Type $type): string
  150.     {
  151.         if (Type::BUILTIN_TYPE_OBJECT === $type->getBuiltinType()) {
  152.             return '\\'.$type->getClassName();
  153.         } elseif ($type->isCollection()) {
  154.             if (null !== $collectionType $this->getCollectionValueType($type)) {
  155.                 return $this->typeToString($collectionType).'[]';
  156.             } else {
  157.                 return 'mixed[]';
  158.             }
  159.         } else {
  160.             return $type->getBuiltinType();
  161.         }
  162.     }
  163.     private function getCollectionKeyTypes(Type $type): array
  164.     {
  165.         // BC layer, this condition should be removed after removing support for symfony < 5.3
  166.         if (!method_exists($type'getCollectionKeyTypes')) {
  167.             return null !== $type->getCollectionKeyType() ? [$type->getCollectionKeyType()] : [];
  168.         }
  169.         return $type->getCollectionKeyTypes();
  170.     }
  171.     private function getCollectionValueTypes(Type $type): array
  172.     {
  173.         // BC layer, this condition should be removed after removing support for symfony < 5.3
  174.         if (!method_exists($type'getCollectionValueTypes')) {
  175.             return null !== $type->getCollectionValueType() ? [$type->getCollectionValueType()] : [];
  176.         }
  177.         return $type->getCollectionValueTypes();
  178.     }
  179.     private function getCollectionValueType(Type $type): ?Type
  180.     {
  181.         // BC layer, this condition should be removed after removing support for symfony < 5.3
  182.         if (!method_exists($type'getCollectionValueTypes')) {
  183.             return $type->getCollectionValueType();
  184.         }
  185.         return $type->getCollectionValueTypes()[0] ?? null;
  186.     }
  187. }