Skip to content

Commit

Permalink
Support php7.4 nullable typed properties for JMS serializer.
Browse files Browse the repository at this point in the history
  • Loading branch information
zviryatko committed May 1, 2024
1 parent 2af8c5d commit e9a1979
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 1 deletion.
2 changes: 1 addition & 1 deletion composer.json
Expand Up @@ -13,6 +13,7 @@
"require": {
"php": ">=7.4",
"ext-json": "*",
"doctrine/annotations": "^2.0",
"phpdocumentor/reflection-docblock": "^4.3.4 || ^5.0",
"phpdocumentor/type-resolver": "^1.8.2",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
Expand All @@ -33,7 +34,6 @@
"require-dev": {
"api-platform/core": "^2.7.0 || ^3",
"composer/package-versions-deprecated": "1.11.99.1",
"doctrine/annotations": "^2.0",
"friendsofphp/php-cs-fixer": "^3.52",
"friendsofsymfony/rest-bundle": "^2.8 || ^3.0",
"jms/serializer": "^1.14 || ^3.0",
Expand Down
31 changes: 31 additions & 0 deletions src/ModelDescriber/JMSModelDescriber.php
Expand Up @@ -152,6 +152,7 @@ public function describe(Model $model, OA\Schema $schema)
} catch (\ReflectionException $ignored) {
}
}
$this->checkRequiredFields($reflections, $schema, $name);
if (null !== $item->setter) {
try {
$reflections[] = new \ReflectionMethod($item->class, $item->setter);
Expand Down Expand Up @@ -397,4 +398,34 @@ private function propertyTypeUsesGroups(array $type): ?bool
return null;
}
}

/**
* Mark property as required if it is not nullable.
*
* @param array<\ReflectionProperty|\ReflectionMethod> $reflections
*/
private function checkRequiredFields(array $reflections, OA\Schema $schema, string $name): void
{
foreach ($reflections as $reflection) {
$nullable = false;
if ($reflection instanceof \ReflectionProperty) {
$type = PHP_VERSION_ID >= 70400 ? $reflection->getType() : null;
if (null !== $type && !$type->allowsNull()) {
$nullable = true;
}
} elseif ($reflection instanceof \ReflectionMethod) {
$returnType = $reflection->getReturnType();
if (null !== $returnType && !$returnType->allowsNull()) {
$nullable = true;
}
}
if ($nullable) {
$required = Generator::UNDEFINED !== $schema->required ? $schema->required : [];
$required[] = $name;

$schema->required = $required;
break;
}
}
}
}
37 changes: 37 additions & 0 deletions tests/Functional/Controller/JMSController74.php
@@ -0,0 +1,37 @@
<?php

/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nelmio\ApiDocBundle\Tests\Functional\Controller;

use Nelmio\ApiDocBundle\Annotation\Model;
use Nelmio\ApiDocBundle\Tests\Functional\Entity\JMSTyped;
use OpenApi\Annotations as OA;
use Symfony\Component\Routing\Annotation\Route;

/**
* @Route(host="api.example.com")
*/
class JMSController74
{
/**
* @Route("/api/jms_typed", methods={"GET"})
*
* @OA\Response(
* response=200,
* description="Success",
*
* @Model(type=JMSTyped::class)
* )
*/
public function typedAction()
{
}
}
46 changes: 46 additions & 0 deletions tests/Functional/Entity/JMSTyped.php
@@ -0,0 +1,46 @@
<?php

/*
* This file is part of the NelmioApiDocBundle package.
*
* (c) Nelmio
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Nelmio\ApiDocBundle\Tests\Functional\Entity;

use JMS\Serializer\Annotation as Serializer;
use Nelmio\ApiDocBundle\Annotation\Model;
use OpenApi\Annotations as OA;

class JMSTyped
{
/**
* @Serializer\Type("integer")
*/
private int $id;

/**
* @OA\Property(ref=@Model(type=JMSUser::class))
*
* @Serializer\SerializedName("user")
*/
private JMSUser $User;

/**
* @Serializer\Type("string")
*/
private ?string $name;

/**
* @Serializer\VirtualProperty
*
* @OA\Property(ref=@Model(type=JMSUser::class))
*/
public function getVirtualFriend(): JMSUser
{
return new JMSUser();
}
}
28 changes: 28 additions & 0 deletions tests/Functional/JMSFunctionalTest.php
Expand Up @@ -360,6 +360,12 @@ public function testEnumSupport(): void
],
],
'schema' => 'Article81',
'required'=> [
'id',
'type',
'int_backed_type',
'not_backed_type',
],
], json_decode($this->getModel('Article81')->toJson(), true));

self::assertEquals([
Expand Down Expand Up @@ -420,4 +426,26 @@ protected static function createKernel(array $options = []): KernelInterface
{
return new TestKernel(TestKernel::USE_JMS);
}

public function testModelTypedDocumentation(): void
{
if (PHP_VERSION_ID < 70400) {
self::markTestSkipped('PHP 7.4 required.');
}
self::assertEquals([
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'user' => ['$ref' => '#/components/schemas/JMSUser'],
'name' => ['type' => 'string'],
'virtual_friend' => ['$ref' => '#/components/schemas/JMSUser'],
],
'required' => [
'virtual_friend',
'id',
'user',
],
'schema' => 'JMSTyped',
], json_decode($this->getModel('JMSTyped')->toJson(), true));
}
}
3 changes: 3 additions & 0 deletions tests/Functional/TestKernel.php
Expand Up @@ -87,6 +87,9 @@ protected function configureRoutes(RoutingConfigurator $routes): void

if (self::USE_JMS === $this->flag || self::USE_BAZINGA === $this->flag) {
$routes->withPath('/')->import(__DIR__.'/Controller/JMSController.php', self::isAnnotationsAvailable() ? 'annotation' : 'attribute');
if (\PHP_VERSION_ID >= 70400) {
$routes->withPath('/')->import(__DIR__.'/Controller/JMSController74.php', self::isAnnotationsAvailable() ? 'annotation' : 'attribute');
}
}

if (self::USE_BAZINGA === $this->flag) {
Expand Down

0 comments on commit e9a1979

Please sign in to comment.