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 #2164

Merged
merged 54 commits into from
Dec 31, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
ba71ca1
feat: symfony 7 support
faizanakram99 Dec 11, 2023
3b35a25
cs-fix
faizanakram99 Dec 11, 2023
c97bd00
fix: adds missing use statement
faizanakram99 Dec 11, 2023
4207f88
fix: fixes ci
faizanakram99 Dec 11, 2023
4988cfe
fix: fixes all failing unit tests
faizanakram99 Dec 11, 2023
ebfff92
Add symfony/expression-language to dev
DjordyKoert Dec 11, 2023
d88b9e5
Remove kernel version check
DjordyKoert Dec 11, 2023
f9cbd31
No longer extend ApiController80
DjordyKoert Dec 11, 2023
2ef40b7
Fix controller attributes passed to next controller
DjordyKoert Dec 11, 2023
6f742e7
Fix copy mistake
DjordyKoert Dec 11, 2023
13005b7
Use Route attribute
DjordyKoert Dec 11, 2023
7a5a810
Fix version skip check
DjordyKoert Dec 11, 2023
cd638ed
Skip JMS
DjordyKoert Dec 11, 2023
7a79bdf
Implement attribute class variant
DjordyKoert Dec 11, 2023
671fb54
Fix attribute to reflect annotation
DjordyKoert Dec 11, 2023
8f06b7e
Fix incorrect model name taken
DjordyKoert Dec 12, 2023
068055d
Re-add ArrayItemsErrorController tests
DjordyKoert Dec 12, 2023
c835a6a
Replace get with Response
DjordyKoert Dec 12, 2023
cc340a4
remove leftover annotation
DjordyKoert Dec 12, 2023
3a964b3
ci: do not remove symfony 7 compatible packages
DjordyKoert Dec 15, 2023
be6ea22
fix: remove $supportsLegacyAnnotations
DjordyKoert Dec 16, 2023
a3dfbb8
fix: add helper method for annotation availability check
DjordyKoert Dec 16, 2023
ce9e7aa
refactor: move SerializedNameController to ApiController
DjordyKoert Dec 16, 2023
0545d03
fix: create separate controller instead of extending
DjordyKoert Dec 16, 2023
ac1d11c
fix: ArrayItemsErrorController php 7 failure
DjordyKoert Dec 16, 2023
00cf04d
feat: re-enable symfony 7 compatible packages
DjordyKoert Dec 16, 2023
ba51c10
feat: JMS tests for attributes
DjordyKoert Dec 16, 2023
e4892df
fix: isAnnotationsAvailable symfony version check
DjordyKoert Dec 16, 2023
452b6d2
fix: ci no annotations reader found
DjordyKoert Dec 16, 2023
7b0fc34
fix: JMS could not guess type
DjordyKoert Dec 16, 2023
48e422e
fix: separate FOS test
DjordyKoert Dec 16, 2023
bc2d533
fix: only set enable_annotations on php 8+
DjordyKoert Dec 16, 2023
546a918
ci: install doctrine/annotations
DjordyKoert Dec 17, 2023
24ace89
fix: check kernel version
DjordyKoert Dec 17, 2023
25d70a8
test: check if reader available
DjordyKoert Dec 17, 2023
e7b3408
test: symfony 5 always contains annotations
DjordyKoert Dec 17, 2023
b3a4fbb
fix: php 7 failing on use statement for attributes
DjordyKoert Dec 17, 2023
365b371
Revert "test: check if reader available"
DjordyKoert Dec 17, 2023
2a37f01
fix: SymfonyConstraintsWithValidationGroups
DjordyKoert Dec 17, 2023
ff42305
test: fix routes available for both annotations & attributes
DjordyKoert Dec 17, 2023
10ac429
test: fix routes available for both annotations & attributes
DjordyKoert Dec 17, 2023
b304c0a
fix: ci php 8.0
DjordyKoert Dec 17, 2023
a043b1f
fix: aliasing JMSComplex81
DjordyKoert Dec 17, 2023
aac792e
test: fix mapping not being set
DjordyKoert Dec 19, 2023
b79fca8
test: use array_merge
DjordyKoert Dec 19, 2023
b737824
test: fix php 8.1 duplicate aliases set
DjordyKoert Dec 19, 2023
7b256d6
temp: remove fail-fast from ci
DjordyKoert Dec 19, 2023
6fc5876
test: fix php 7 extend from SymfonyConstraints81
DjordyKoert Dec 19, 2023
2b0a418
test: fix php 7 SymfonyDiscriminator
DjordyKoert Dec 19, 2023
da385b5
test: remove parent class SymfonyConstraints
DjordyKoert Dec 19, 2023
3f7ded4
Revert "temp: remove fail-fast from ci"
DjordyKoert Dec 19, 2023
5ba6a55
phpcs
DjordyKoert Dec 19, 2023
adc739f
style: apply styleci diff (#3)
DjordyKoert Dec 20, 2023
e39987d
chore: removes duplicate line from continuous-integration.yml
faizanakram99 Dec 20, 2023
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'
faizanakram99 marked this conversation as resolved.
Show resolved Hide resolved
run: |
composer remove friendsofsymfony/rest-bundle sensio/framework-extra-bundle jms/serializer-bundle willdurand/hateoas-bundle --no-update --dev
faizanakram99 marked this conversation as resolved.
Show resolved Hide resolved

- 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 }}
faizanakram99 marked this conversation as resolved.
Show resolved Hide resolved

- 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
Loading