Skip to content

Commit

Permalink
Merge pull request #6190 from soyuka/merge
Browse files Browse the repository at this point in the history
Merge 3.2
  • Loading branch information
soyuka committed Feb 29, 2024
2 parents 304951a + c5767c9 commit bfd7335
Show file tree
Hide file tree
Showing 18 changed files with 262 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
Feature: Update properties of a resource that are inherited with standard PUT operation

@!mongodb
@createSchema
Scenario: Update properties of a resource that are inherited with standard PUT operation
Given there is a dummy entity with a mapped superclass
When I add "Content-Type" header equal to "application/ld+json"
And I send a "PUT" request to "/dummy_mapped_subclasses/1" with body:
"""
{
"foo": "updated value"
}
"""
Then the response status code should be 200
And the response should be in JSON
And the JSON should be equal to:
"""
{
"@context": "/contexts/DummyMappedSubclass",
"@id": "/dummy_mapped_subclasses/1",
"@type": "DummyMappedSubclass",
"id": 1,
"foo": "updated value"
}
"""
12 changes: 8 additions & 4 deletions src/Doctrine/Common/State/PersistProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,15 @@ private function isDeferredExplicit(DoctrineObjectManager $manager, $data): bool
private function getReflectionProperties(mixed $data): array
{
$ret = [];
$props = (new \ReflectionObject($data))->getProperties(~\ReflectionProperty::IS_STATIC);
$r = new \ReflectionObject($data);

foreach ($props as $prop) {
$ret[$prop->getName()] = $prop;
}
do {
$props = $r->getProperties(~\ReflectionProperty::IS_STATIC);

foreach ($props as $prop) {
$ret[$prop->getName()] = $prop;
}
} while ($r = $r->getParentClass());

return $ret;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

final class ElasticsearchProviderResourceMetadataCollectionFactory implements ResourceMetadataCollectionFactoryInterface
{
public function __construct(private readonly Client|null $client, private readonly ResourceMetadataCollectionFactoryInterface $decorated, private readonly bool $triggerDeprecation = true) // @phpstan-ignore-line
public function __construct(private readonly ?Client $client, private readonly ResourceMetadataCollectionFactoryInterface $decorated, private readonly bool $triggerDeprecation = true) // @phpstan-ignore-line
{
}

Expand Down
2 changes: 1 addition & 1 deletion src/GraphQl/State/Processor/NormalizeProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public function __construct(private readonly NormalizerInterface $normalizer, pr
{
}

public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): array|null
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): ?array
{
if (!$operation instanceof GraphQlOperation) {
return $data;
Expand Down
12 changes: 11 additions & 1 deletion src/JsonSchema/BackwardCompatibleSchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,15 @@ public function buildSchema(string $className, string $format = 'json', string $

foreach ($schema->getDefinitions() as $definition) {
foreach ($definition['properties'] ?? [] as $property) {
if (isset($property['type']) && \in_array($property['type'], ['integer', 'number'], true)) {
if (!isset($property['type'])) {
continue;
}

foreach ((array) $property['type'] as $type) {
if ('integer' !== $type && 'number' !== $type) {
continue;
}

if (isset($property['exclusiveMinimum'])) {
$property['minimum'] = $property['exclusiveMinimum'];
$property['exclusiveMinimum'] = true;
Expand All @@ -52,6 +60,8 @@ public function buildSchema(string $className, string $format = 'json', string $
$property['maximum'] = $property['exclusiveMaximum'];
$property['exclusiveMaximum'] = true;
}

break;
}
}
}
Expand Down
106 changes: 106 additions & 0 deletions src/JsonSchema/Tests/BackwardCompatibleSchemaFactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\JsonSchema\Tests;

use ApiPlatform\JsonSchema\BackwardCompatibleSchemaFactory;
use ApiPlatform\JsonSchema\Schema;
use ApiPlatform\JsonSchema\SchemaFactoryInterface;
use PHPUnit\Framework\TestCase;

class BackwardCompatibleSchemaFactoryTest extends TestCase
{
public function testWithSingleType(): void
{
$schema = new Schema();
$schema->setDefinitions(new \ArrayObject([
'a' => new \ArrayObject([
'properties' => new \ArrayObject([
'foo' => new \ArrayObject(['type' => 'integer', 'exclusiveMinimum' => 0, 'exclusiveMaximum' => 1]),
]),
]),
]));
$schemaFactory = $this->createMock(SchemaFactoryInterface::class);
$schemaFactory->method('buildSchema')->willReturn($schema);
$schemaFactory = new BackwardCompatibleSchemaFactory($schemaFactory);
$schema = $schemaFactory->buildSchema('a', serializerContext: [BackwardCompatibleSchemaFactory::SCHEMA_DRAFT4_VERSION => true]);
$schema = $schema->getDefinitions()['a'];

$this->assertTrue($schema['properties']['foo']['exclusiveMinimum']);
$this->assertTrue($schema['properties']['foo']['exclusiveMaximum']);
$this->assertEquals($schema['properties']['foo']['minimum'], 0);
$this->assertEquals($schema['properties']['foo']['maximum'], 1);
}

public function testWithMultipleType(): void
{
$schema = new Schema();
$schema->setDefinitions(new \ArrayObject([
'a' => new \ArrayObject([
'properties' => new \ArrayObject([
'foo' => new \ArrayObject(['type' => ['number', 'null'], 'exclusiveMinimum' => 0, 'exclusiveMaximum' => 1]),
]),
]),
]));
$schemaFactory = $this->createMock(SchemaFactoryInterface::class);
$schemaFactory->method('buildSchema')->willReturn($schema);
$schemaFactory = new BackwardCompatibleSchemaFactory($schemaFactory);
$schema = $schemaFactory->buildSchema('a', serializerContext: [BackwardCompatibleSchemaFactory::SCHEMA_DRAFT4_VERSION => true]);
$schema = $schema->getDefinitions()['a'];

$this->assertTrue($schema['properties']['foo']['exclusiveMinimum']);
$this->assertTrue($schema['properties']['foo']['exclusiveMaximum']);
$this->assertEquals($schema['properties']['foo']['minimum'], 0);
$this->assertEquals($schema['properties']['foo']['maximum'], 1);
}

public function testWithoutNumber(): void
{
$schema = new Schema();
$schema->setDefinitions(new \ArrayObject([
'a' => new \ArrayObject([
'properties' => new \ArrayObject([
'foo' => new \ArrayObject(['type' => ['string', 'null'], 'exclusiveMinimum' => 0, 'exclusiveMaximum' => 1]),
]),
]),
]));
$schemaFactory = $this->createMock(SchemaFactoryInterface::class);
$schemaFactory->method('buildSchema')->willReturn($schema);
$schemaFactory = new BackwardCompatibleSchemaFactory($schemaFactory);
$schema = $schemaFactory->buildSchema('a', serializerContext: [BackwardCompatibleSchemaFactory::SCHEMA_DRAFT4_VERSION => true]);
$schema = $schema->getDefinitions()['a'];

$this->assertEquals($schema['properties']['foo']['exclusiveMinimum'], 0);
$this->assertEquals($schema['properties']['foo']['exclusiveMaximum'], 1);
}

public function testWithoutFlag(): void
{
$schema = new Schema();
$schema->setDefinitions(new \ArrayObject([
'a' => new \ArrayObject([
'properties' => new \ArrayObject([
'foo' => new \ArrayObject(['type' => ['string', 'null'], 'exclusiveMinimum' => 0, 'exclusiveMaximum' => 1]),
]),
]),
]));
$schemaFactory = $this->createMock(SchemaFactoryInterface::class);
$schemaFactory->method('buildSchema')->willReturn($schema);
$schemaFactory = new BackwardCompatibleSchemaFactory($schemaFactory);
$schema = $schemaFactory->buildSchema('a', serializerContext: [BackwardCompatibleSchemaFactory::SCHEMA_DRAFT4_VERSION => false]);
$schema = $schema->getDefinitions()['a'];

$this->assertEquals($schema['properties']['foo']['exclusiveMinimum'], 0);
$this->assertEquals($schema['properties']['foo']['exclusiveMaximum'], 1);
}
}
6 changes: 3 additions & 3 deletions src/Metadata/Tests/Fixtures/ApiResource/DummyCar.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ class DummyCar
#[Serializer\Groups(['colors'])]
private iterable $colors;
#[Serializer\Groups(['colors'])]
private iterable|null $secondColors = null;
private ?iterable $secondColors = null;
#[Serializer\Groups(['colors'])]
private iterable|null $thirdColors = null;
private ?iterable $thirdColors = null;
#[Serializer\Groups(['colors'])]
private iterable|null $uuid = null;
private ?iterable $uuid = null;

private string $name;
private bool $canSell;
Expand Down
2 changes: 1 addition & 1 deletion src/OpenApi/Factory/OpenApiFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ private function collectPaths(ApiResource $resource, ResourceMetadataCollection
continue;
}

$parameter = new Parameter($parameterName, 'path', (new \ReflectionClass($uriVariable->getFromClass()))->getShortName().' identifier', true, false, false, ['type' => 'string']);
$parameter = new Parameter($parameterName, 'path', "$resourceShortName identifier", true, false, false, ['type' => 'string']);
if ($this->hasParameter($openapiOperation, $parameter)) {
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

namespace ApiPlatform\Symfony\Bundle\DependencyInjection;

use ApiPlatform\Api\FilterInterface as LegacyFilterInterface;
use ApiPlatform\Doctrine\Odm\Extension\AggregationCollectionExtensionInterface;
use ApiPlatform\Doctrine\Odm\Extension\AggregationItemExtensionInterface;
use ApiPlatform\Doctrine\Odm\Filter\AbstractFilter as DoctrineMongoDbOdmAbstractFilter;
Expand Down Expand Up @@ -174,6 +175,8 @@ public function load(array $configs, ContainerBuilder $container): void

$container->registerForAutoconfiguration(FilterInterface::class)
->addTag('api_platform.filter');
$container->registerForAutoconfiguration(LegacyFilterInterface::class)
->addTag('api_platform.filter');
$container->registerForAutoconfiguration(ProviderInterface::class)
->addTag('api_platform.state_provider');
$container->registerForAutoconfiguration(ProcessorInterface::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ private function getPropertyConstraints(
*/
private function isRequired(Constraint $constraint): bool
{
if ($constraint instanceof NotBlank && $constraint->allowNull) {
return false;
}

foreach (self::REQUIRED_CONSTRAINTS as $requiredConstraint) {
if ($constraint instanceof $requiredConstraint) {
return true;
Expand Down
4 changes: 0 additions & 4 deletions src/Util/AttributesExtractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,6 @@ private function __construct()
*/
public static function extractAttributes(array $attributes): array
{
if (($attributes['_api_operation'] ?? null) && !isset($attributes['_api_resource_class'])) {
$attributes['_api_resource_class'] = $attributes['_api_operation']->getClass();
}

$result = ['resource_class' => $attributes['_api_resource_class'] ?? null, 'has_composite_identifier' => $attributes['_api_has_composite_identifier'] ?? false];

if (null === $result['resource_class']) {
Expand Down
11 changes: 11 additions & 0 deletions tests/Behat/DoctrineContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyFriend;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyGroup;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyImmutableDate;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyMappedSubclass;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyMercure;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyOffer;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\DummyPassenger;
Expand Down Expand Up @@ -2285,6 +2286,16 @@ public function thereAreAFewLinkHandledDummies(): void
$this->manager->flush();
}

/**
* @Given there is a dummy entity with a mapped superclass
*/
public function thereIsADummyEntityWithAMappedSuperclass(): void
{
$entity = new DummyMappedSubclass();
$this->manager->persist($entity);
$this->manager->flush();
}

private function isOrm(): bool
{
return null !== $this->schemaTool;
Expand Down
1 change: 1 addition & 0 deletions tests/Fixtures/DummyValidatedEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class DummyValidatedEntity
* @var string
*/
#[Assert\Email]
#[Assert\NotBlank(allowNull: true)]
public $dummyEmail;

/**
Expand Down
37 changes: 37 additions & 0 deletions tests/Fixtures/TestBundle/Entity/DummyMappedSubclass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;

use ApiPlatform\Metadata\Put;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[Put(
extraProperties: [
'standard_put' => true,
],
allowCreate: true
)]
class DummyMappedSubclass extends DummyMappedSuperclass
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;

public function getId(): ?int
{
return $this->id;
}
}
35 changes: 35 additions & 0 deletions tests/Fixtures/TestBundle/Entity/DummyMappedSuperclass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\Tests\Fixtures\TestBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

#[ORM\MappedSuperclass]
class DummyMappedSuperclass
{
#[ORM\Column(length: 255, nullable: true)]
private ?string $foo = null;

public function getFoo(): ?string
{
return $this->foo;
}

public function setFoo(?string $foo): static
{
$this->foo = $foo;

return $this;
}
}
2 changes: 1 addition & 1 deletion tests/Fixtures/TestBundle/Entity/Issue5662/Book.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static function getData(Operation $operation, array $uriVariables = [], a
return [new self('a', 'hello'), new self('b', 'you')];
}

public static function getDatum(Operation $operation, array $uriVariables = [], array $context = []): self|null
public static function getDatum(Operation $operation, array $uriVariables = [], array $context = []): ?self
{
$id = $uriVariables['id'];
foreach (static::getData($operation, $uriVariables, $context) as $datum) {
Expand Down
2 changes: 1 addition & 1 deletion tests/Fixtures/TestBundle/Entity/Issue5662/Review.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public static function getData(Operation $operation, array $uriVariables = [], a
];
}

public static function getDatum(Operation $operation, array $uriVariables = [], array $context = []): self|null
public static function getDatum(Operation $operation, array $uriVariables = [], array $context = []): ?self
{
$id = (int) $uriVariables['id'];
foreach (static::getData($operation, $uriVariables, $context) as $datum) {
Expand Down
Loading

0 comments on commit bfd7335

Please sign in to comment.