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

Merge 3.4 #6739

Merged
merged 9 commits into from
Oct 22, 2024
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,17 @@ Notes:
### Features

* [0d5f35683](https://github.com/api-platform/core/commit/0d5f356839eb6aa9f536044abe4affa736553e76) feat(laravel): laravel component (#5882)
=======
## v3.4.4

### Bug fixes

* [550347867](https://github.com/api-platform/core/commit/550347867f30611b673d8df99f65186d013919dd) fix(graphql): register query parameter arguments with filters (#6727)
* [99262dce7](https://github.com/api-platform/core/commit/99262dce739800bd841c95e026848b587ba25801) fix(jsonschema): handle @id when genId is false (#6716)
* [ad5efa535](https://github.com/api-platform/core/commit/ad5efa535a4dcbaad64ecff89514eaa6e07f5b7c) fix: multiple parameter provider #6673 (#6732)
* [d34cd7be8](https://github.com/api-platform/core/commit/d34cd7be8e7a12fd08a8b10270a614c06c10aa89) fix: use stateOptions when retrieving a Parameter filter (#6728)
* [e7fb04fab](https://github.com/api-platform/core/commit/e7fb04fab05bc077e2dbeb0fa0fc2c1d28c96105) fix(symfony): fetch api-platform/symfony version debug bar (#6722)
* [e96623ebf](https://github.com/api-platform/core/commit/e96623ebfd8691ba943bdb56a4d91e160497a311) fix(jsonld): prefix error @type with hydra: (#6721)

## v3.4.3

Expand Down
2 changes: 1 addition & 1 deletion features/main/relation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ Feature: Relations support
"properties": {
"@type": {
"type": "string",
"pattern": "^Error$"
"pattern": "^hydra:Error$"
},
"title": {
"type": "string",
Expand Down
4 changes: 2 additions & 2 deletions features/mongodb/filters.feature
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Feature: Filters on collections
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the JSON node "@context" should be equal to "/contexts/Error"
And the JSON node "@type" should be equal to "Error"
And the JSON node "@type" should be equal to "hydra:Error"
And the JSON node "title" should be equal to "An error occurred"
And the JSON node "description" should be equal to "Cannot use reference 'badFourthLevel' in class 'ThirdLevel' for lookup or graphLookup: dbRef references are not supported."
And the JSON node "trace" should exist
Expand All @@ -23,7 +23,7 @@ Feature: Filters on collections
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the JSON node "@context" should be equal to "/contexts/Error"
And the JSON node "@type" should be equal to "Error"
And the JSON node "@type" should be equal to "hydra:Error"
And the JSON node "title" should be equal to "An error occurred"
And the JSON node "description" should be equal to "Cannot use reference 'badThirdLevel' in class 'FourthLevel' for lookup or graphLookup: dbRef references are not supported."
And the JSON node "trace" should exist
10 changes: 5 additions & 5 deletions features/security/strong_typing.feature
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Feature: Handle properly invalid data submitted to the API
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the JSON node "@context" should be equal to "/contexts/Error"
And the JSON node "@type" should be equal to "Error"
And the JSON node "@type" should be equal to "hydra:Error"
And the JSON node "title" should be equal to "An error occurred"
And the JSON node "description" should be equal to 'The type of the "name" attribute must be "string", "NULL" given.'

Expand All @@ -71,7 +71,7 @@ Feature: Handle properly invalid data submitted to the API
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the JSON node "@context" should be equal to "/contexts/Error"
And the JSON node "@type" should be equal to "Error"
And the JSON node "@type" should be equal to "hydra:Error"
And the JSON node "title" should be equal to "An error occurred"
And the JSON node "description" should be equal to 'Invalid IRI "1".'
And the JSON node "trace" should exist
Expand Down Expand Up @@ -102,7 +102,7 @@ Feature: Handle properly invalid data submitted to the API
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the JSON node "@context" should be equal to "/contexts/Error"
And the JSON node "@type" should be equal to "Error"
And the JSON node "@type" should be equal to "hydra:Error"
And the JSON node "title" should be equal to "An error occurred"
And the JSON node "description" should be equal to 'The type of the "relatedDummies" attribute must be "array", "string" given.'
And the JSON node "trace" should exist
Expand All @@ -120,7 +120,7 @@ Feature: Handle properly invalid data submitted to the API
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the JSON node "@context" should be equal to "/contexts/Error"
And the JSON node "@type" should be equal to "Error"
And the JSON node "@type" should be equal to "hydra:Error"
And the JSON node "title" should be equal to "An error occurred"
And the JSON node "description" should be equal to 'The type of the key "a" must be "int", "string" given.'

Expand All @@ -136,7 +136,7 @@ Feature: Handle properly invalid data submitted to the API
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
And the JSON node "@context" should be equal to "/contexts/Error"
And the JSON node "@type" should be equal to "Error"
And the JSON node "@type" should be equal to "hydra:Error"
And the JSON node "title" should be equal to "An error occurred"
And the JSON node "description" should be equal to 'The type of the "name" attribute must be "string", "integer" given.'

Expand Down
2 changes: 1 addition & 1 deletion features/serializer/vo_relations.feature
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Feature: Value object as ApiResource
"properties": {
"@type": {
"type": "string",
"pattern": "^Error$"
"pattern": "^hydra:Error$"
},
"title": {
"type": "string",
Expand Down
7 changes: 6 additions & 1 deletion src/JsonLd/Serializer/ErrorNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@ public function __construct(private readonly NormalizerInterface $inner, private

public function normalize(mixed $object, ?string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
{
$context += $this->defaultContext;
$normalized = $this->inner->normalize($object, $format, $context);
$hydraPrefix = $this->getHydraPrefix($context + $this->defaultContext);
$hydraPrefix = $this->getHydraPrefix($context);
if (!$hydraPrefix) {
return $normalized;
}

if ('Error' === $normalized['@type']) {
$normalized['@type'] = 'hydra:Error';
}

if (isset($normalized['description'])) {
$normalized['hydra:description'] = $normalized['description'];
}
Expand Down
8 changes: 8 additions & 0 deletions src/JsonSchema/SchemaFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,14 @@ private function buildPropertySchema(Schema $schema, string $definitionName, str
continue;
}

if (false === $propertyMetadata->getGenId()) {
$subDefinitionName = $this->definitionNameFactory->create($className, $format, $className, null, $serializerContext);

if (isset($subSchema->getDefinitions()[$subDefinitionName])) {
unset($subSchema->getDefinitions()[$subDefinitionName]['properties']['@id']);
}
}

if ($isCollection) {
$propertySchema['items']['$ref'] = $subSchema['$ref'];
unset($propertySchema['items']['type']);
Expand Down
12 changes: 6 additions & 6 deletions src/Laravel/Tests/Eloquent/Metadata/ModelMetadataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ public function testHiddenAttributesAreCorrectlyIdentified(): void
/**
* @return HasMany<Book>
*/
public function secret(): HasMany
public function secret(): HasMany // @phpstan-ignore-line
{
return $this->hasMany(Book::class);
return $this->hasMany(Book::class); // @phpstan-ignore-line
}
};

Expand All @@ -55,9 +55,9 @@ public function testVisibleAttributesAreCorrectlyIdentified(): void
/**
* @return HasMany<Book>
*/
public function secret(): HasMany
public function secret(): HasMany // @phpstan-ignore-line
{
return $this->hasMany(Book::class);
return $this->hasMany(Book::class); // @phpstan-ignore-line
}
};

Expand All @@ -71,9 +71,9 @@ public function testAllAttributesVisibleByDefault(): void
/**
* @return HasMany<Book>
*/
public function secret(): HasMany
public function secret(): HasMany // @phpstan-ignore-line
{
return $this->hasMany(Book::class);
return $this->hasMany(Book::class); // @phpstan-ignore-line
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

namespace ApiPlatform\Metadata\Resource\Factory;

use ApiPlatform\Doctrine\Odm\State\Options as DoctrineODMOptions;
use ApiPlatform\Doctrine\Orm\State\Options as DoctrineORMOptions;
use ApiPlatform\Metadata\ApiProperty;
use ApiPlatform\Metadata\FilterInterface;
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
Expand Down Expand Up @@ -113,7 +115,7 @@ private function getDefaultParameters(Operation $operation, string $resourceClas
if (':property' === $key) {
foreach ($propertyNames as $property) {
$converted = $this->nameConverter?->denormalize($property) ?? $property;
$propertyParameter = $this->setDefaults($converted, $parameter, $resourceClass, $properties);
$propertyParameter = $this->setDefaults($converted, $parameter, $resourceClass, $properties, $operation);
$priority = $propertyParameter->getPriority() ?? $internalPriority--;
$parameters->add($converted, $propertyParameter->withPriority($priority)->withKey($converted));
}
Expand All @@ -133,7 +135,7 @@ private function getDefaultParameters(Operation $operation, string $resourceClas
$parameter = $parameter->withExtraProperties($parameter->getExtraProperties() + ['_properties' => $p]);
}

$parameter = $this->setDefaults($key, $parameter, $resourceClass, $properties);
$parameter = $this->setDefaults($key, $parameter, $resourceClass, $properties, $operation);
$priority = $parameter->getPriority() ?? $internalPriority--;
$parameters->add($key, $parameter->withPriority($priority));
}
Expand Down Expand Up @@ -171,7 +173,7 @@ private function addFilterMetadata(Parameter $parameter): Parameter
/**
* @param array<string, ApiProperty> $properties
*/
private function setDefaults(string $key, Parameter $parameter, string $resourceClass, array $properties): Parameter
private function setDefaults(string $key, Parameter $parameter, string $resourceClass, array $properties, Operation $operation): Parameter
{
if (null === $parameter->getKey()) {
$parameter = $parameter->withKey($key);
Expand All @@ -187,7 +189,7 @@ private function setDefaults(string $key, Parameter $parameter, string $resource
}

// Read filter description to populate the Parameter
$description = $filter instanceof FilterInterface ? $filter->getDescription($resourceClass) : [];
$description = $filter instanceof FilterInterface ? $filter->getDescription($this->getFilterClass($operation)) : [];
if (($schema = $description[$key]['schema'] ?? null) && null === $parameter->getSchema()) {
$parameter = $parameter->withSchema($schema);
}
Expand Down Expand Up @@ -220,4 +222,17 @@ private function setDefaults(string $key, Parameter $parameter, string $resource

return $this->addFilterMetadata($parameter);
}

private function getFilterClass(Operation $operation): ?string
{
$stateOptions = $operation->getStateOptions();
if ($stateOptions instanceof DoctrineORMOptions) {
return $stateOptions->getEntityClass();
}
if ($stateOptions instanceof DoctrineODMOptions) {
return $stateOptions->getDocumentClass();
}

return $operation->getClass();
}
}
2 changes: 1 addition & 1 deletion src/State/Provider/ParameterProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
$request->attributes->set('_api_header_parameters', $request->headers->all());
}

$context = ['operation' => $operation] + $context;
$parameters = $operation->getParameters();
foreach ($parameters ?? [] as $parameter) {
$extraProperties = $parameter->getExtraProperties();
unset($extraProperties['_api_values']);
$parameters->add($parameter->getKey(), $parameter = $parameter->withExtraProperties($extraProperties));

$context = ['operation' => $operation] + $context;
$values = $this->getParameterValues($parameter, $request, $context);
$value = $this->extractParameterValues($parameter, $values);

Expand Down
29 changes: 26 additions & 3 deletions src/Symfony/Bundle/DataCollector/RequestDataCollector.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use ApiPlatform\State\Util\RequestAttributesExtractor;
use Composer\InstalledVersions;
use PackageVersions\Versions;
use Psr\Container\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
Expand Down Expand Up @@ -67,16 +68,38 @@ private function setFilters(ApiResource $resourceMetadata, int $index, array &$f
}
}

// TODO: 4.1 remove Versions as its deprecated
public function getVersion(): ?string
{
if (class_exists(InstalledVersions::class)) {
return InstalledVersions::getPrettyVersion('api-platform/symfony') ?? InstalledVersions::getPrettyVersion('api-platform/core');
}

if (!class_exists(Versions::class)) {
return null;
}

$version = Versions::getVersion('api-platform/core');
preg_match('/^v(.*?)@/', (string) $version, $output);
try {
$version = strtok(Versions::getVersion('api-platform/symfony'), '@');
} catch (\OutOfBoundsException) {
$version = false;
}

if (false === $version) {
try {
$version = strtok(Versions::getVersion('api-platform/core'), '@');
} catch (\OutOfBoundsException) {
$version = false;
}
}

if (false === $version) {
return null;
}

preg_match('/^v(.*?)$/', $version, $output);

return $output[1] ?? strtok($version, '@');
return $output[1] ?? $version;
}

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

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* 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\ApiResource;

use ApiPlatform\Doctrine\Odm\Filter\DateFilter as OdmDateFilter;
use ApiPlatform\Doctrine\Odm\State\Options as OdmOptions;
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
use ApiPlatform\Doctrine\Orm\State\Options;
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\Tests\Fixtures\TestBundle\Document\AgentDocument;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Agent;

#[ApiFilter(DateFilter::class, properties: ['birthday'], alias: 'app_filter_date')]
#[ApiResource(
shortName: 'Agent',
operations: [
new GetCollection(parameters: [
'birthday' => new QueryParameter(filter: 'app_filter_date'),
]),
],
stateOptions: new Options(entityClass: Agent::class)
)]
#[ApiFilter(OdmDateFilter::class, properties: ['birthday'], alias: 'app_filter_date_odm')]
#[ApiResource(
shortName: 'AgentDocument',
operations: [
new GetCollection(parameters: [
'birthday' => new QueryParameter(filter: 'app_filter_date_odm'),
]),
],
stateOptions: new OdmOptions(documentClass: AgentDocument::class)
)]
class AgentApi
{
private ?int $id = null;

private ?string $name = null;

private ?\DateTimeInterface $birthday = null;

public function getId(): ?int
{
return $this->id;
}

public function setId(?int $id): self
{
$this->id = $id;

return $this;
}

public function getName(): ?string
{
return $this->name;
}

public function setName(?string $name): self
{
$this->name = $name;

return $this;
}

public function getBirthday(): ?\DateTimeInterface
{
return $this->birthday;
}

public function setBirthday(?\DateTimeInterface $birthday): self
{
$this->birthday = $birthday;

return $this;
}
}
Loading
Loading