Skip to content

Commit

Permalink
Support for range integers + drop support for PHP 7.2 & 7.3 (#2236)
Browse files Browse the repository at this point in the history
* range integers + test

* backward-compatibility for < SF 6.0 which doesn't support positive-int & negative-int

* tests fix

* added missing description

* fix order of expected required fields

* changed test fixtures

* add min phpdocumentor/type-resolver ver to composer

* composer test

* test for lowest dependencies with php 7.3

* bump phpdocumentor/type-resolver for test

* update composer dependencies

* remove php 7.2 backward-compatibility

* drop support for PHP 7.3 + changelog updated

* bump zircote/swagger-php version to 4.6.1

* adjusted changelog

* force hateoas metadata cache to be cleared between tests

* Update CHANGELOG.md

Co-authored-by: Djordy Koert <djordy.koert@live.nl>

---------

Co-authored-by: Bartłomiej Nowak <bartlomiej.nowak@workbuzz.com>
Co-authored-by: DjordyKoert <djordy.koert@yoursurprise.com>
Co-authored-by: Djordy Koert <djordy.koert@live.nl>
  • Loading branch information
4 people committed Mar 14, 2024
1 parent 7bc02c7 commit bbfe64a
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 191 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/continuous-integration.yml
Expand Up @@ -25,12 +25,9 @@ jobs:
fail-fast: false
matrix:
include:
- php-version: 7.2
- php-version: 7.4
composer-flags: "--prefer-lowest"
doctrine-annotations: true
- php-version: 7.3
symfony-require: "5.4.*"
doctrine-annotations: true
- php-version: 7.4
symfony-require: "5.4.*"
doctrine-annotations: true
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,24 @@
CHANGELOG
=========

4.24.0
-----
* Added support for some integer ranges (https://phpstan.org/writing-php-code/phpdoc-types#integer-ranges).
Annotations attached to integer properties like:
```php
/**
* @var int<6, 11>
* @var int<min, 11>
* @var int<6, max>
* @var positive-int
* @var negative-int
*/
```
will be interpreted as appropriate `minimum` and `maximum` properties in the generated OpenAPI specification.

### Breaking change
Dropped support for PHP 7.2 and PHP 7.3. PHP 7.4 is the minimum required version now.

4.23.0
-----
* Cache configuration option `nelmio_api_doc.cache.item_id` now automatically gets the area appended.
Expand Down
9 changes: 5 additions & 4 deletions composer.json
Expand Up @@ -11,7 +11,7 @@
}
],
"require": {
"php": ">=7.2",
"php": ">=7.4",
"ext-json": "*",
"psr/cache": "^1.0|^2.0|^3.0",
"psr/container": "^1.0|^2.0",
Expand All @@ -23,10 +23,11 @@
"symfony/http-foundation": "^5.4|^6.0|^7.0",
"symfony/http-kernel": "^5.4|^6.0|^7.0",
"symfony/options-resolver": "^5.4|^6.0|^7.0",
"symfony/property-info": "^5.4|^6.0|^7.0",
"symfony/property-info": "^5.4.10|^6.0|^7.0",
"symfony/routing": "^5.4|^6.0|^7.0",
"zircote/swagger-php": "^4.2.15",
"phpdocumentor/reflection-docblock": "^3.1|^4.0|^5.0",
"zircote/swagger-php": "^4.6.1",
"phpdocumentor/reflection-docblock": "^4.3.4|^5.0",
"phpdocumentor/type-resolver": "^1.8.2",
"symfony/deprecation-contracts": "^2.1|^3"
},
"require-dev": {
Expand Down
46 changes: 38 additions & 8 deletions src/ModelDescriber/Annotations/PropertyPhpDocReader.php
Expand Up @@ -15,6 +15,10 @@
use OpenApi\Generator;
use phpDocumentor\Reflection\DocBlock\Tags\Var_;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\PseudoTypes\IntegerRange;
use phpDocumentor\Reflection\PseudoTypes\NegativeInteger;
use phpDocumentor\Reflection\PseudoTypes\PositiveInteger;
use phpDocumentor\Reflection\Types\Compound;

/**
* Extract information about properties of a model from the DocBlock comment.
Expand Down Expand Up @@ -42,23 +46,49 @@ public function updateProperty($reflection, OA\Property $property): void
return;
}

if (!$title = $docBlock->getSummary()) {
/** @var Var_ $var */
foreach ($docBlock->getTagsByName('var') as $var) {
if (!method_exists($var, 'getDescription') || !$description = $var->getDescription()) {
continue;
}
$title = $docBlock->getSummary();

/** @var Var_ $var */
foreach ($docBlock->getTagsByName('var') as $var) {
if (!$title && method_exists($var, 'getDescription') && $description = $var->getDescription()) {
$title = $description->render();
if ($title) {
break;
}

if (
(!isset($min) || null !== $min) && (!isset($max) || null !== $max)
&& method_exists($var, 'getType') && $type = $var->getType()
) {
$types = $type instanceof Compound ? $type->getIterator() : [$type];

foreach ($types as $type) {
if ($type instanceof IntegerRange) {
$min = is_numeric($type->getMinValue()) ? (int) $type->getMinValue() : null;
$max = is_numeric($type->getMaxValue()) ? (int) $type->getMaxValue() : null;
break;
} elseif ($type instanceof PositiveInteger) {
$min = 1;
$max = null;
break;
} elseif ($type instanceof NegativeInteger) {
$min = null;
$max = -1;
break;
}
}
}
}

if (Generator::UNDEFINED === $property->title && $title) {
$property->title = $title;
}
if (Generator::UNDEFINED === $property->description && $docBlock->getDescription() && $docBlock->getDescription()->render()) {
$property->description = $docBlock->getDescription()->render();
}
if (Generator::UNDEFINED === $property->minimum && isset($min)) {
$property->minimum = $min;
}
if (Generator::UNDEFINED === $property->maximum && isset($max)) {
$property->maximum = $max;
}
}
}
12 changes: 12 additions & 0 deletions tests/Functional/BazingaFunctionalTest.php
Expand Up @@ -12,8 +12,11 @@
namespace Nelmio\ApiDocBundle\Tests\Functional;

use Hateoas\Configuration\Embedded;
use Metadata\Cache\PsrCacheAdapter;
use Metadata\MetadataFactory;
use ReflectionException;
use ReflectionMethod;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\KernelInterface;

Expand All @@ -28,6 +31,15 @@ protected function setUp(): void
parent::setUp();

static::createClient([], ['HTTP_HOST' => 'api.example.com']);

$metaDataFactory = self::getContainer()->get('hateoas.configuration.metadata_factory');

if (!$metaDataFactory instanceof MetadataFactory) {
$this->fail('The hateoas.metadata_factory service is not an instance of MetadataFactory');
}

// Reusing the cache from previous tests causes relations metadata to be lost, so we need to clear it
$metaDataFactory->setCache(new PsrCacheAdapter('BazingaFunctionalTest', new ArrayAdapter()));
}

public function testModelComplexDocumentationBazinga()
Expand Down
15 changes: 15 additions & 0 deletions tests/Functional/Controller/ApiController80.php
Expand Up @@ -26,6 +26,7 @@
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithNullableSchemaSet;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithObjectType;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\EntityWithRef;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\RangeInteger;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraints80;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminator80;
Expand Down Expand Up @@ -511,6 +512,20 @@ public function entityWithFalsyDefaults()
{
}

/**
* @Route("/range_integer", methods={"GET"})
*
* @OA\Response(
* response="200",
* description="",
*
* @Model(type=RangeInteger::class)
* )
*/
public function rangeInteger()
{
}

/**
* @OA\Response(
* response="200",
Expand Down
7 changes: 7 additions & 0 deletions tests/Functional/Controller/ApiController81.php
Expand Up @@ -31,6 +31,7 @@
use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\FilterQueryModel;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\PaginationQueryModel;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\QueryModel\SortQueryModel;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\RangeInteger;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraints81;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyConstraintsWithValidationGroups;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\SymfonyDiscriminator81;
Expand Down Expand Up @@ -468,6 +469,12 @@ public function enum()
{
}

#[Route('/range_integer', methods: ['GET'])]
#[OA\Response(response: '200', description: '', attachables: [new Model(type: RangeInteger::class)])]
public function rangeInteger()
{
}

#[Route('/serializename', methods: ['GET'])]
#[OA\Response(response: 200, description: 'success', content: new Model(type: SerializedNameEntity::class))]
public function serializedNameAction()
Expand Down
79 changes: 36 additions & 43 deletions tests/Functional/Entity/ArrayItems/Dictionary.php
Expand Up @@ -4,47 +4,40 @@

namespace Nelmio\ApiDocBundle\Tests\Functional\Entity\ArrayItems;

// PHP 7.2 is not able to guess these types
if (PHP_VERSION_ID < 70300) {
class Dictionary
{
}
} else {
class Dictionary
{
/**
* @var array<string, string>
*/
public $options;

/**
* @var array<string, string|integer>
*/
public $compoundOptions;

/**
* @var array<array<string, string|integer>>
*/
public $nestedCompoundOptions;

/**
* @var array<string, Foo>
*/
public $modelOptions;

/**
* @var array<int, string>
*/
public $listOptions;

/**
* @var array<int, string>|array<string, string>
*/
public $arrayOrDictOptions;

/**
* @var array<string, integer>
*/
public $integerOptions;
}
class Dictionary
{
/**
* @var array<string, string>
*/
public $options;

/**
* @var array<string, string|integer>
*/
public $compoundOptions;

/**
* @var array<array<string, string|integer>>
*/
public $nestedCompoundOptions;

/**
* @var array<string, Foo>
*/
public $modelOptions;

/**
* @var array<int, string>
*/
public $listOptions;

/**
* @var array<int, string>|array<string, string>
*/
public $arrayOrDictOptions;

/**
* @var array<string, integer>
*/
public $integerOptions;
}
52 changes: 52 additions & 0 deletions tests/Functional/Entity/RangeInteger.php
@@ -0,0 +1,52 @@
<?php

declare(strict_types=1);

namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;

use Symfony\Component\HttpKernel\Kernel;

trait RangeIntegerTrait
{
/**
* @var int<1, 99>
*/
public $rangeInt;

/**
* @var int<1, max>
*/
public $minRangeInt;

/**
* @var int<min, 99>
*/
public $maxRangeInt;

/**
* @var int<1, 99>|null
*/
public $nullableRangeInt;
}

if (version_compare(Kernel::VERSION, '6.1', '>=')) {
class RangeInteger
{
use RangeIntegerTrait;

/**
* @var positive-int
*/
public $positiveInt;

/**
* @var negative-int
*/
public $negativeInt;
}
} else {
class RangeInteger
{
use RangeIntegerTrait;
}
}

0 comments on commit bbfe64a

Please sign in to comment.