Skip to content

Commit 81a0f85

Browse files
committed
Merge 4.0
2 parents de3bed2 + b4e5494 commit 81a0f85

File tree

25 files changed

+235
-104
lines changed

25 files changed

+235
-104
lines changed

.php-cs-fixer.dist.php

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
]);
3030

3131
return (new PhpCsFixer\Config())
32+
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
3233
->setRiskyAllowed(true)
3334
->setRules([
3435
'@DoctrineAnnotation' => true,

CHANGELOG.md

+23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
# Changelog
22

3+
## v4.0.11
4+
5+
### Bug fixes
6+
7+
* [af66075fd](https://github.com/api-platform/core/commit/af66075fdd6b83bdebc1c4ca33cc0ab7e1a7f8af) fix(laravel): fix foregin keys (relations) beeing in attributes (#6843)
8+
9+
10+
### Features
11+
12+
* [2d59c6369](https://github.com/api-platform/core/commit/2d59c63699b4602cfe4d62504896c6d4121c1be4) feat(laravel): belongs to many relations (#6818)
13+
14+
Also contains [v3.4.8 changes](#v348).
15+
316
## v4.0.10
417

518
### Bug fixes
@@ -211,6 +224,16 @@ Notes:
211224

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

227+
## v3.4.8
228+
229+
### Bug fixes
230+
231+
* [4d7deeaf7](https://github.com/api-platform/core/commit/4d7deeaf794178b5496ae989520095831a86df8a) fix(jsonld): check if supportedTypes exists (#6825)
232+
* [5111935d4](https://github.com/api-platform/core/commit/5111935d4f917920c6f3d24b828f9d59fd0e3520) fix(symfony): object typed property schema collection restriction (#6823)
233+
* [6bf894f6f](https://github.com/api-platform/core/commit/6bf894f6f0ead0751936aeddcfc527f017498bb3) fix(serializer): use attribute denormalization context for constructor arguments (#6821)
234+
* [86c97cac3](https://github.com/api-platform/core/commit/86c97cac3b8d45b6190e2999b99a02e88dd4e527) fix(symfony): symfony 7.2 deprecations (#6835)
235+
* [d312eae7e](https://github.com/api-platform/core/commit/d312eae7ef590ec05139c09bfaf2d3c7668a3f22) fix(doctrine): fixed orm datefilter applying inner join when no filtering values have been provided (#6849)
236+
214237
## v3.4.7
215238

216239
### Bug fixes

features/security/strong_typing.feature

+13
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,19 @@ Feature: Handle properly invalid data submitted to the API
8989
And the response should be in JSON
9090
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
9191

92+
Scenario: Ignore date with wrong format
93+
When I add "Content-Type" header equal to "application/ld+json"
94+
And I send a "POST" request to "/dummies" with body:
95+
"""
96+
{
97+
"name": "Invalid date format",
98+
"dummyDateWithFormat": "2020-01-01T00:00:00+00:00"
99+
}
100+
"""
101+
Then the response status code should be 400
102+
And the response should be in JSON
103+
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"
104+
92105
Scenario: Send non-array data when an array is expected
93106
When I add "Content-Type" header equal to "application/ld+json"
94107
And I send a "POST" request to "/dummies" with body:

src/Doctrine/Orm/Filter/DateFilter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
153153
$alias = $queryBuilder->getRootAliases()[0];
154154
$field = $property;
155155

156-
if ($this->isPropertyNested($property, $resourceClass)) {
156+
if ($this->isPropertyNested($property, $resourceClass) && \count($values) > 0) {
157157
[$alias, $field] = $this->addJoinsForNestedProperty($property, $alias, $queryBuilder, $queryNameGenerator, $resourceClass, Join::INNER_JOIN);
158158
}
159159

src/Elasticsearch/Serializer/NameConverter/InnerFieldsNameConverter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
*
2525
* @author Baptiste Meyer <[email protected]>
2626
*/
27-
final class InnerFieldsNameConverter implements AdvancedNameConverterInterface
27+
final class InnerFieldsNameConverter implements NameConverterInterface, AdvancedNameConverterInterface
2828
{
2929
public function __construct(private readonly NameConverterInterface $inner = new CamelCaseToSnakeCaseNameConverter())
3030
{

src/JsonApi/Serializer/ReservedAttributeNameConverter.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*
2323
* @author Baptiste Meyer <[email protected]>
2424
*/
25-
final class ReservedAttributeNameConverter implements AdvancedNameConverterInterface
25+
final class ReservedAttributeNameConverter implements NameConverterInterface, AdvancedNameConverterInterface
2626
{
2727
public const JSON_API_RESERVED_ATTRIBUTES = [
2828
'id' => '_id',

src/JsonLd/Serializer/ErrorNormalizer.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ public function supportsNormalization(mixed $data, ?string $format = null, array
5757

5858
public function getSupportedTypes(?string $format): array
5959
{
60-
return $this->inner->getSupportedTypes($format);
60+
if (method_exists($this->inner, 'getSupportedTypes')) {
61+
return $this->inner->getSupportedTypes($format);
62+
}
63+
64+
return [];
6165
}
6266
}

src/Laravel/ApiPlatformMiddleware.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function __construct(
2626
}
2727

2828
/**
29-
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
29+
* @param \Closure(Request): (Response) $next
3030
*/
3131
public function handle(Request $request, \Closure $next, ?string $operationName = null): Response
3232
{

src/Laravel/Eloquent/Metadata/ModelMetadata.php

+6
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,14 @@ public function getAttributes(Model $model): Collection
6767
$table = $model->getTable();
6868
$columns = $schema->getColumns($table);
6969
$indexes = $schema->getIndexes($table);
70+
$relations = $this->getRelations($model);
7071

7172
return collect($columns)
73+
->reject(
74+
fn ($column) => $relations->contains(
75+
fn ($relation) => $relation['foreign_key'] === $column['name']
76+
)
77+
)
7278
->map(fn ($column) => [
7379
'name' => $column['name'],
7480
'type' => $column['type'],

src/Laravel/Eloquent/State/CollectionProvider.php

+23
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,29 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
5858
$query = $extension->apply($query, $uriVariables, $operation, $context);
5959
}
6060

61+
if ($order = $operation->getOrder()) {
62+
$isList = array_is_list($order);
63+
foreach ($order as $property => $direction) {
64+
if ($isList) {
65+
$property = $direction;
66+
$direction = 'ASC';
67+
}
68+
69+
if (str_contains($property, '.')) {
70+
[$table, $property] = explode('.', $property);
71+
72+
// Relation Order by, we need to do laravel eager loading
73+
$query->with([
74+
$table => fn ($query) => $query->orderBy($property, $direction),
75+
]);
76+
77+
continue;
78+
}
79+
80+
$query->orderBy($property, $direction);
81+
}
82+
}
83+
6184
if (false === $this->pagination->isEnabled($operation, $context)) {
6285
return $query->get();
6386
}

src/Laravel/Eloquent/State/LinksHandler.php

+11
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,17 @@ private function buildQuery(Builder $builder, Link $link, mixed $identifier): Bu
103103
if ($from = $link->getFromProperty()) {
104104
$relation = $this->application->make($link->getFromClass());
105105

106+
if (!method_exists($relation->{$from}(), 'getQualifiedForeignKeyName') && method_exists($relation->{$from}(), 'getQualifiedForeignPivotKeyName')) {
107+
/** @var \Illuminate\Database\Eloquent\Relations\BelongsToMany<Model, Model> $relation */
108+
/** @var \Illuminate\Database\Eloquent\Relations\BelongsToMany<Model, Model> $relation_query */
109+
$relation_query = $relation->{$from}();
110+
111+
return $builder->getModel()->join(
112+
$relation_query->getTable(), $relation->{$from}()->getQualifiedForeignPivotKeyName(), $builder->getModel()->getQualifiedKeyName())
113+
->where($relation->{$from}()->getQualifiedForeignPivotKeyName(),
114+
$identifier);
115+
}
116+
106117
return $builder->getModel()->where($relation->{$from}()->getQualifiedForeignKeyName(), $identifier);
107118
}
108119

src/Metadata/Parameter.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
namespace ApiPlatform\Metadata;
1515

16-
use ApiPlatform\OpenApi;
1716
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
1817
use ApiPlatform\State\ParameterNotFound;
1918
use ApiPlatform\State\ParameterProviderInterface;
@@ -63,7 +62,7 @@ public function getSchema(): ?array
6362
}
6463

6564
/**
66-
* @return OpenApi\Model\Parameter[]|OpenApi\Model\Parameter|bool|null
65+
* @return OpenApiParameter[]|OpenApiParameter|bool|null
6766
*/
6867
public function getOpenApi(): OpenApiParameter|array|bool|null
6968
{
@@ -171,7 +170,7 @@ public function withSchema(array $schema): static
171170
}
172171

173172
/**
174-
* @param OpenApi\Model\Parameter[]|OpenApi\Model\Parameter|bool $openApi
173+
* @param OpenApiParameter[]|OpenApiParameter|bool $openApi
175174
*/
176175
public function withOpenApi(OpenApiParameter|array|bool $openApi): static
177176
{

src/Metadata/Util/PropertyInfoToTypeInfoHelper.php

+8-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
1818
use Symfony\Component\TypeInfo\Type;
1919
use Symfony\Component\TypeInfo\Type\BuiltinType;
20+
use Symfony\Component\TypeInfo\Type\NullableType;
2021
use Symfony\Component\TypeInfo\Type\UnionType;
2122
use Symfony\Component\TypeInfo\TypeIdentifier;
2223

@@ -126,11 +127,16 @@ public static function createTypeFromLegacyValues(string $builtinType, bool $nul
126127

127128
public static function unwrapNullableType(Type $type): Type
128129
{
129-
if (!$type instanceof UnionType) {
130+
// BC layer for "symfony/type-info" < 7.2
131+
if (method_exists($type, 'asNonNullable')) {
132+
return (!$type instanceof UnionType) ? $type : $type->asNonNullable();
133+
}
134+
135+
if (!$type instanceof NullableType) {
130136
return $type;
131137
}
132138

133-
return $type->asNonNullable();
139+
return $type->getWrappedType();
134140
}
135141

136142
/**

src/OpenApi/Factory/OpenApiFactory.php

+4-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
3333
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
3434
use ApiPlatform\OpenApi\Attributes\Webhook;
35-
use ApiPlatform\OpenApi\Model;
3635
use ApiPlatform\OpenApi\Model\Components;
3736
use ApiPlatform\OpenApi\Model\Contact;
3837
use ApiPlatform\OpenApi\Model\Info;
@@ -415,11 +414,11 @@ private function buildOpenApiResponse(array $existingResponses, int|string $stat
415414
}
416415

417416
/**
418-
* @return \ArrayObject<Model\MediaType>
417+
* @return \ArrayObject<MediaType>
419418
*/
420419
private function buildContent(array $responseMimeTypes, array $operationSchemas): \ArrayObject
421420
{
422-
/** @var \ArrayObject<Model\MediaType> $content */
421+
/** @var \ArrayObject<MediaType> $content */
423422
$content = new \ArrayObject();
424423

425424
foreach ($responseMimeTypes as $mimeType => $format) {
@@ -506,11 +505,11 @@ private function getPathDescription(string $resourceShortName, string $method, b
506505
/**
507506
* @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#linkObject.
508507
*
509-
* @return \ArrayObject<Model\Link>
508+
* @return \ArrayObject<Link>
510509
*/
511510
private function getLinks(ResourceMetadataCollection $resourceMetadataCollection, HttpOperation $currentOperation): \ArrayObject
512511
{
513-
/** @var \ArrayObject<Model\Link> $links */
512+
/** @var \ArrayObject<Link> $links */
514513
$links = new \ArrayObject();
515514

516515
// Only compute get links for now

src/Serializer/AbstractItemNormalizer.php

+5-6
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex
298298
foreach ($constructorParameters as $constructorParameter) {
299299
$paramName = $constructorParameter->name;
300300
$key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName;
301+
$attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context);
302+
$attributeContext['deserialization_path'] = $attributeContext['deserialization_path'] ?? $key;
301303

302304
$allowed = false === $allowedAttributes || (\is_array($allowedAttributes) && \in_array($paramName, $allowedAttributes, true));
303305
$ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
@@ -310,10 +312,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex
310312
$params[] = $data[$paramName];
311313
}
312314
} elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
313-
$constructorContext = $context;
314-
$constructorContext['deserialization_path'] = $context['deserialization_path'] ?? $key;
315315
try {
316-
$params[] = $this->createConstructorArgument($data[$key], $key, $constructorParameter, $constructorContext, $format);
316+
$params[] = $this->createConstructorArgument($data[$key], $key, $constructorParameter, $attributeContext, $format);
317317
} catch (NotNormalizableValueException $exception) {
318318
if (!isset($context['not_normalizable_value_exceptions'])) {
319319
throw $exception;
@@ -332,7 +332,6 @@ protected function instantiateObject(array &$data, string $class, array &$contex
332332
$missingConstructorArguments[] = $constructorParameter->name;
333333
}
334334

335-
$attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context);
336335
$constructorParameterType = 'unknown';
337336
$reflectionType = $constructorParameter->getType();
338337
if ($reflectionType instanceof \ReflectionNamedType) {
@@ -343,7 +342,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex
343342
\sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name),
344343
null,
345344
[$constructorParameterType],
346-
$attributeContext['deserialization_path'] ?? null,
345+
$attributeContext['deserialization_path'],
347346
true
348347
);
349348
$context['not_normalizable_value_exceptions'][] = $exception;
@@ -386,7 +385,7 @@ protected function getClassDiscriminatorResolvedClass(array $data, string $class
386385
return $mappedClass;
387386
}
388387

389-
protected function createConstructorArgument($parameterData, string $key, \ReflectionParameter $constructorParameter, array &$context, ?string $format = null): mixed
388+
protected function createConstructorArgument($parameterData, string $key, \ReflectionParameter $constructorParameter, array $context, ?string $format = null): mixed
390389
{
391390
return $this->createAndValidateAttributeValue($constructorParameter->name, $parameterData, $format, $context);
392391
}

src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@
154154
<service id="api_platform.doctrine_mongodb.odm.state.collection_provider" class="ApiPlatform\Doctrine\Odm\State\CollectionProvider" public="false">
155155
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
156156
<argument type="service" id="doctrine_mongodb" />
157-
<argument type="tagged" tag="api_platform.doctrine_mongodb.odm.aggregation_extension.collection" />
157+
<argument type="tagged_iterator" tag="api_platform.doctrine_mongodb.odm.aggregation_extension.collection" />
158158
<argument type="tagged_locator" tag="api_platform.doctrine.odm.links_handler" index-by="key" />
159159

160160
<tag name="api_platform.state_provider" priority="-100" key="ApiPlatform\Doctrine\Odm\State\CollectionProvider" />
@@ -165,7 +165,7 @@
165165
<service id="api_platform.doctrine_mongodb.odm.state.item_provider" class="ApiPlatform\Doctrine\Odm\State\ItemProvider" public="false">
166166
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
167167
<argument type="service" id="doctrine_mongodb" />
168-
<argument type="tagged" tag="api_platform.doctrine_mongodb.odm.aggregation_extension.item" />
168+
<argument type="tagged_iterator" tag="api_platform.doctrine_mongodb.odm.aggregation_extension.item" />
169169
<argument type="tagged_locator" tag="api_platform.doctrine.odm.links_handler" index-by="key" />
170170

171171
<tag name="api_platform.state_provider" priority="-100" key="ApiPlatform\Doctrine\Odm\State\ItemProvider" />

0 commit comments

Comments
 (0)