Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: symfony 7 support #2166

Closed
wants to merge 17 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ jobs:
symfony-require: "5.4.*"
- php-version: 8.1
symfony-require: "6.3.*"
- php-version: 8.2
symfony-require: "7.0.*"

steps:
- name: "Checkout"
Expand All @@ -59,13 +61,19 @@ jobs:
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-

- name: Remove packages not compatible symfony 7
if: matrix.symfony-require == '7.0.*'
run: |
composer remove friendsofsymfony/rest-bundle sensio/framework-extra-bundle jms/serializer-bundle willdurand/hateoas-bundle --no-update --dev

- name: "Install dependencies with composer"
env:
SYMFONY_REQUIRE: "${{ matrix.symfony-require }}"
run: |
composer global config --no-plugins allow-plugins.symfony/flex true
composer global require --no-progress --no-scripts --no-plugins symfony/flex
composer update --no-interaction --no-progress ${{ matrix.composer-flags }}
composer update --no-interaction --no-progress ${{ matrix.composer-flags }}

- name: PHPUnit Tests
run: vendor/bin/simple-phpunit --configuration phpunit.xml.dist --coverage-text
5 changes: 1 addition & 4 deletions Command/DumpCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ protected function configure(): void
;
}

/**
* @return int|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$area = $input->getOption('area');
$format = $input->getOption('format');
Expand Down
3 changes: 2 additions & 1 deletion DependencyInjection/Compiler/ConfigurationPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Nelmio\ApiDocBundle\ModelDescriber\FormModelDescriber;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Reference;

/**
Expand All @@ -29,7 +30,7 @@ public function process(ContainerBuilder $container): void
$container->register('nelmio_api_doc.model_describers.form', FormModelDescriber::class)
->setPublic(false)
->addArgument(new Reference('form.factory'))
->addArgument(new Reference('annotations.reader'))
->addArgument(new Reference('annotations.reader', ContainerInterface::NULL_ON_INVALID_REFERENCE))
->addArgument($container->getParameter('nelmio_api_doc.media_types'))
->addArgument($container->getParameter('nelmio_api_doc.use_validation_groups'))
->addTag('nelmio_api_doc.model_describer', ['priority' => 100]);
Expand Down
7 changes: 4 additions & 3 deletions DependencyInjection/NelmioApiDocExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use Symfony\Component\Config\FileLocator;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
Expand Down Expand Up @@ -94,7 +95,7 @@ public function load(array $configs, ContainerBuilder $container): void
->setArguments([
new Reference(sprintf('nelmio_api_doc.routes.%s', $area)),
new Reference('nelmio_api_doc.controller_reflector'),
new Reference('annotations.reader'), // We cannot use the cached version of the annotation reader since the construction of the annotations is context dependant...
new Reference('annotations.reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), // We cannot use the cached version of the annotation reader since the construction of the annotations is context dependant...
new Reference('logger'),
])
->addTag(sprintf('nelmio_api_doc.describer.%s', $area), ['priority' => -200]);
Expand Down Expand Up @@ -123,7 +124,7 @@ public function load(array $configs, ContainerBuilder $container): void
(new Definition(FilteredRouteCollectionBuilder::class))
->setArguments(
[
new Reference('annotation_reader'), // Here we use the cached version as we don't deal with @OA annotations in this service
new Reference('annotation_reader', ContainerInterface::NULL_ON_INVALID_REFERENCE), // Here we use the cached version as we don't deal with @OA annotations in this service
new Reference('nelmio_api_doc.controller_reflector'),
$area,
$areaConfig,
Expand Down Expand Up @@ -181,7 +182,7 @@ public function load(array $configs, ContainerBuilder $container): void
->setPublic(false)
->setArguments([
new Reference('jms_serializer.metadata_factory'),
new Reference('annotations.reader'),
new Reference('annotations.reader', ContainerInterface::NULL_ON_INVALID_REFERENCE),
$config['media_types'],
$jmsNamingStrategy,
$container->getParameter('nelmio_api_doc.use_validation_groups'),
Expand Down
26 changes: 20 additions & 6 deletions Describer/OpenApiPhpDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,15 @@ final class OpenApiPhpDescriber

private $routeCollection;
private $controllerReflector;

/**
* @var Reader|null
*/
private $annotationReader;
private $logger;
private $overwrite;

public function __construct(RouteCollection $routeCollection, ControllerReflector $controllerReflector, Reader $annotationReader, LoggerInterface $logger, bool $overwrite = false)
public function __construct(RouteCollection $routeCollection, ControllerReflector $controllerReflector, ?Reader $annotationReader, LoggerInterface $logger, bool $overwrite = false)
{
$this->routeCollection = $routeCollection;
$this->controllerReflector = $controllerReflector;
Expand All @@ -51,7 +55,7 @@ public function describe(OA\OpenApi $api)
$classAnnotations = [];

/** @var \ReflectionMethod $method */
foreach ($this->getMethodsToParse() as $method => list($path, $httpMethods, $routeName)) {
foreach ($this->getMethodsToParse() as $method => [$path, $httpMethods, $routeName]) {
$declaringClass = $method->getDeclaringClass();

$path = Util::getPath($api, $path);
Expand All @@ -65,16 +69,26 @@ public function describe(OA\OpenApi $api)
$this->setContext($context);

if (!array_key_exists($declaringClass->getName(), $classAnnotations)) {
$classAnnotations = array_filter($this->annotationReader->getClassAnnotations($declaringClass), function ($v) {
$classAnnotations = [];
if (null !== $this->annotationReader) {
$classAnnotations = $this->annotationReader->getClassAnnotations($declaringClass);
}

$classAnnotations = array_filter($classAnnotations, function ($v) {
return $v instanceof OA\AbstractAnnotation;
});

$classAnnotations = array_merge($classAnnotations, $this->getAttributesAsAnnotation($declaringClass, $context));
$classAnnotations[$declaringClass->getName()] = $classAnnotations;
}

$annotations = array_filter($this->annotationReader->getMethodAnnotations($method), function ($v) {
return $v instanceof OA\AbstractAnnotation;
});
$annotations = [];
if (null !== $this->annotationReader) {
$annotations = array_filter($this->annotationReader->getMethodAnnotations($method), function ($v) {
return $v instanceof OA\AbstractAnnotation;
});
}

$annotations = array_merge($annotations, $this->getAttributesAsAnnotation($method, $context));

if (0 === count($annotations) && 0 === count($classAnnotations[$declaringClass->getName()])) {
Expand Down
8 changes: 1 addition & 7 deletions ModelDescriber/Annotations/AnnotationsReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,16 @@
*/
class AnnotationsReader
{
private $annotationsReader;
private $modelRegistry;

private $phpDocReader;
private $openApiAnnotationsReader;
private $symfonyConstraintAnnotationReader;

public function __construct(
Reader $annotationsReader,
?Reader $annotationsReader,
ModelRegistry $modelRegistry,
array $mediaTypes,
bool $useValidationGroups = false
) {
$this->annotationsReader = $annotationsReader;
$this->modelRegistry = $modelRegistry;

$this->phpDocReader = new PropertyPhpDocReader();
$this->openApiAnnotationsReader = new OpenApiAnnotationsReader($annotationsReader, $modelRegistry, $mediaTypes);
$this->symfonyConstraintAnnotationReader = new SymfonyConstraintAnnotationReader(
Expand Down
19 changes: 12 additions & 7 deletions ModelDescriber/Annotations/OpenApiAnnotationsReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ class OpenApiAnnotationsReader
{
use SetsContextTrait;

/**
* @var Reader|null
*/
private $annotationsReader;
private $modelRegister;

public function __construct(Reader $annotationsReader, ModelRegistry $modelRegistry, array $mediaTypes)
public function __construct(?Reader $annotationsReader, ModelRegistry $modelRegistry, array $mediaTypes)
{
$this->annotationsReader = $annotationsReader;
$this->modelRegister = new ModelRegister($modelRegistry, $mediaTypes);
Expand Down Expand Up @@ -97,12 +100,14 @@ private function getAnnotation(Context $parentContext, $reflection, string $clas
}
}

if ($reflection instanceof \ReflectionClass) {
return $this->annotationsReader->getClassAnnotation($reflection, $className);
} elseif ($reflection instanceof \ReflectionProperty) {
return $this->annotationsReader->getPropertyAnnotation($reflection, $className);
} elseif ($reflection instanceof \ReflectionMethod) {
return $this->annotationsReader->getMethodAnnotation($reflection, $className);
if (null !== $this->annotationsReader) {
if ($reflection instanceof \ReflectionClass) {
return $this->annotationsReader->getClassAnnotation($reflection, $className);
} elseif ($reflection instanceof \ReflectionProperty) {
return $this->annotationsReader->getPropertyAnnotation($reflection, $className);
} elseif ($reflection instanceof \ReflectionMethod) {
return $this->annotationsReader->getMethodAnnotation($reflection, $className);
}
}
} finally {
$this->setContext(null);
Expand Down
14 changes: 8 additions & 6 deletions ModelDescriber/Annotations/SymfonyConstraintAnnotationReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class SymfonyConstraintAnnotationReader
use SetsContextTrait;

/**
* @var Reader
* @var Reader|null
*/
private $annotationsReader;

Expand All @@ -42,7 +42,7 @@ class SymfonyConstraintAnnotationReader
*/
private $useValidationGroups;

public function __construct(Reader $annotationsReader, bool $useValidationGroups=false)
public function __construct(?Reader $annotationsReader, bool $useValidationGroups=false)
{
$this->annotationsReader = $annotationsReader;
$this->useValidationGroups = $useValidationGroups;
Expand Down Expand Up @@ -215,10 +215,12 @@ private function locateAnnotations($reflection): \Traversable
}
}

if ($reflection instanceof \ReflectionProperty) {
yield from $this->annotationsReader->getPropertyAnnotations($reflection);
} elseif ($reflection instanceof \ReflectionMethod) {
yield from $this->annotationsReader->getMethodAnnotations($reflection);
if (null !== $this->annotationsReader) {
if ($reflection instanceof \ReflectionProperty) {
yield from $this->annotationsReader->getPropertyAnnotations($reflection);
} elseif ($reflection instanceof \ReflectionMethod) {
yield from $this->annotationsReader->getMethodAnnotations($reflection);
}
}
}

Expand Down
7 changes: 4 additions & 3 deletions ModelDescriber/FormModelDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ final class FormModelDescriber implements ModelDescriberInterface, ModelRegistry
use SetsContextTrait;

private $formFactory;

/**
* @var Reader|null
*/
private $doctrineReader;
private $mediaTypes;
private $useValidationGroups;
Expand All @@ -52,9 +56,6 @@ public function __construct(
) {
$this->formFactory = $formFactory;
$this->doctrineReader = $reader;
if (null === $reader) {
@trigger_error(sprintf('Not passing a doctrine reader to the constructor of %s is deprecated since version 3.8 and won\'t be allowed in version 5.', self::class), E_USER_DEPRECATED);
}

if (null === $mediaTypes) {
$mediaTypes = ['json'];
Expand Down
5 changes: 4 additions & 1 deletion ModelDescriber/JMSModelDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn

private $namingStrategy;

/**
* @var Reader|null
*/
private $doctrineReader;

private $contexts = [];
Expand All @@ -60,7 +63,7 @@ class JMSModelDescriber implements ModelDescriberInterface, ModelRegistryAwareIn

public function __construct(
MetadataFactoryInterface $factory,
Reader $reader,
?Reader $reader,
array $mediaTypes,
?PropertyNamingStrategyInterface $namingStrategy = null,
bool $useValidationGroups = false,
Expand Down
4 changes: 2 additions & 2 deletions ModelDescriber/ObjectModelDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar
private $propertyInfo;
/** @var ClassMetadataFactoryInterface|null */
private $classMetadataFactory;
/** @var Reader */
/** @var Reader|null */
private $doctrineReader;
/** @var PropertyDescriberInterface[] */
private $propertyDescribers;
Expand All @@ -48,7 +48,7 @@ class ObjectModelDescriber implements ModelDescriberInterface, ModelRegistryAwar

public function __construct(
PropertyInfoExtractorInterface $propertyInfo,
Reader $reader,
?Reader $reader,
iterable $propertyDescribers,
array $mediaTypes,
NameConverterInterface $nameConverter = null,
Expand Down
2 changes: 1 addition & 1 deletion Resources/config/fos_rest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<services>
<service id="nelmio_api_doc.route_describers.fos_rest" class="Nelmio\ApiDocBundle\RouteDescriber\FosRestDescriber" public="false">
<argument type="service" id="annotation_reader" /> <!-- we don't deal with @OA annotations in this describer so we can use the cached reader -->
<argument type="service" id="annotation_reader" on-invalid="null"/> <!-- we don't deal with @OA annotations in this describer so we can use the cached reader -->
<argument />

<tag name="nelmio_api_doc.route_describer" priority="-250" />
Expand Down
2 changes: 1 addition & 1 deletion Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@

<service id="nelmio_api_doc.model_describers.object" class="Nelmio\ApiDocBundle\ModelDescriber\ObjectModelDescriber" public="false">
<argument type="service" id="property_info" />
<argument type="service" id="annotations.reader" />
<argument type="service" id="annotations.reader" on-invalid="null"/>
<argument type="tagged" tag="nelmio_api_doc.object_model.property_describer" />
<argument />
<argument type="service" id="serializer.name_converter.metadata_aware" on-invalid="ignore" />
Expand Down
8 changes: 5 additions & 3 deletions RouteDescriber/FosRestDescriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,23 @@ final class FosRestDescriber implements RouteDescriberInterface
{
use RouteDescriberTrait;

/** @var Reader */
/** @var Reader|null */
private $annotationReader;

/** @var string[] */
private $mediaTypes;

public function __construct(Reader $annotationReader, array $mediaTypes)
public function __construct(?Reader $annotationReader, array $mediaTypes)
{
$this->annotationReader = $annotationReader;
$this->mediaTypes = $mediaTypes;
}

public function describe(OA\OpenApi $api, Route $route, \ReflectionMethod $reflectionMethod)
{
$annotations = $this->annotationReader->getMethodAnnotations($reflectionMethod);
$annotations = null !== $this->annotationReader
? $this->annotationReader->getMethodAnnotations($reflectionMethod)
: [];
$annotations = array_filter($annotations, static function ($value) {
return $value instanceof RequestParam || $value instanceof QueryParam;
});
Expand Down
11 changes: 7 additions & 4 deletions Routing/FilteredRouteCollectionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

final class FilteredRouteCollectionBuilder
{
/** @var Reader */
/** @var Reader|null */
private $annotationReader;

/** @var ControllerReflector */
Expand All @@ -34,7 +34,7 @@ final class FilteredRouteCollectionBuilder
private $options;

public function __construct(
Reader $annotationReader,
?Reader $annotationReader,
ControllerReflector $controllerReflector,
string $area,
array $options = []
Expand Down Expand Up @@ -137,7 +137,7 @@ private function matchAnnotation(Route $route): bool
/** @var Areas|null $areas */
$areas = $this->getAttributesAsAnnotation($reflectionMethod->getDeclaringClass(), Areas::class)[0] ?? null;

if (null === $areas) {
if (null === $areas && null !== $this->annotationReader) {
/** @var Areas|null $areas */
$areas = $this->annotationReader->getMethodAnnotation(
$reflectionMethod,
Expand Down Expand Up @@ -167,7 +167,10 @@ private function defaultRouteDisabled(Route $route): bool
return false;
}

$annotations = $this->annotationReader->getMethodAnnotations($method);
$annotations = null !== $this->annotationReader
? $this->annotationReader->getMethodAnnotations($method)
: [];

if (method_exists(\ReflectionMethod::class, 'getAttributes')) {
$annotations = array_merge($annotations, array_map(function (\ReflectionAttribute $attribute) {
return $attribute->newInstance();
Expand Down
Loading
Loading