Skip to content

Commit

Permalink
feat: symfony 7 support
Browse files Browse the repository at this point in the history
- makes doctrine/annotations optional
- updates tests and ci to test with both symfony 7 and < symfony 7 version
- adds routes-attributes.yaml file for symfony 7 compatible controllers/routes
- skips tests/packages related to packages like jms serializer, bazinga, etc not supported by symfony 7 for symfony 7 matrix
  • Loading branch information
faizanakram99 committed Dec 11, 2023
1 parent b7a5722 commit 16be828
Show file tree
Hide file tree
Showing 44 changed files with 1,409 additions and 672 deletions.
15 changes: 14 additions & 1 deletion .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,24 @@ jobs:
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
restore-keys: ${{ runner.os }}-composer-

- name: "Install dependencies with composer"
- name: "Prepare 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 }}
- 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 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
25 changes: 19 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,25 @@ 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) {
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

0 comments on commit 16be828

Please sign in to comment.