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

Support for range integers + drop support for PHP 7.2 & 7.3 #2236

Merged
merged 18 commits into from
Mar 14, 2024
Merged
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
5 changes: 1 addition & 4 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
CHANGELOG
=========

4.24.0
-----
* Added support for some integer ranges (https://phpstan.org/writing-php-code/phpdoc-types#integer-ranges).
DjordyKoert marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading
Loading