Skip to content

Commit

Permalink
Merge 4.0
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Dec 6, 2024
2 parents de3bed2 + b4e5494 commit 81a0f85
Show file tree
Hide file tree
Showing 25 changed files with 235 additions and 104 deletions.
1 change: 1 addition & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
]);

return (new PhpCsFixer\Config())
->setParallelConfig(PhpCsFixer\Runner\Parallel\ParallelConfigFactory::detect())
->setRiskyAllowed(true)
->setRules([
'@DoctrineAnnotation' => true,
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## v4.0.11

### Bug fixes

* [af66075fd](https://github.com/api-platform/core/commit/af66075fdd6b83bdebc1c4ca33cc0ab7e1a7f8af) fix(laravel): fix foregin keys (relations) beeing in attributes (#6843)


### Features

* [2d59c6369](https://github.com/api-platform/core/commit/2d59c63699b4602cfe4d62504896c6d4121c1be4) feat(laravel): belongs to many relations (#6818)

Also contains [v3.4.8 changes](#v348).

## v4.0.10

### Bug fixes
Expand Down Expand Up @@ -211,6 +224,16 @@ Notes:

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

## v3.4.8

### Bug fixes

* [4d7deeaf7](https://github.com/api-platform/core/commit/4d7deeaf794178b5496ae989520095831a86df8a) fix(jsonld): check if supportedTypes exists (#6825)
* [5111935d4](https://github.com/api-platform/core/commit/5111935d4f917920c6f3d24b828f9d59fd0e3520) fix(symfony): object typed property schema collection restriction (#6823)
* [6bf894f6f](https://github.com/api-platform/core/commit/6bf894f6f0ead0751936aeddcfc527f017498bb3) fix(serializer): use attribute denormalization context for constructor arguments (#6821)
* [86c97cac3](https://github.com/api-platform/core/commit/86c97cac3b8d45b6190e2999b99a02e88dd4e527) fix(symfony): symfony 7.2 deprecations (#6835)
* [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)

## v3.4.7

### Bug fixes
Expand Down
13 changes: 13 additions & 0 deletions features/security/strong_typing.feature
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,19 @@ 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"

Scenario: Ignore date with wrong format
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/dummies" with body:
"""
{
"name": "Invalid date format",
"dummyDateWithFormat": "2020-01-01T00:00:00+00:00"
}
"""
Then the response status code should be 400
And the response should be in JSON
And the header "Content-Type" should be equal to "application/problem+json; charset=utf-8"

Scenario: Send non-array data when an array is expected
When I add "Content-Type" header equal to "application/ld+json"
And I send a "POST" request to "/dummies" with body:
Expand Down
2 changes: 1 addition & 1 deletion src/Doctrine/Orm/Filter/DateFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
$alias = $queryBuilder->getRootAliases()[0];
$field = $property;

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*
* @author Baptiste Meyer <baptiste.meyer@gmail.com>
*/
final class InnerFieldsNameConverter implements AdvancedNameConverterInterface
final class InnerFieldsNameConverter implements NameConverterInterface, AdvancedNameConverterInterface
{
public function __construct(private readonly NameConverterInterface $inner = new CamelCaseToSnakeCaseNameConverter())
{
Expand Down
2 changes: 1 addition & 1 deletion src/JsonApi/Serializer/ReservedAttributeNameConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
*
* @author Baptiste Meyer <baptiste.meyer@gmail.com>
*/
final class ReservedAttributeNameConverter implements AdvancedNameConverterInterface
final class ReservedAttributeNameConverter implements NameConverterInterface, AdvancedNameConverterInterface
{
public const JSON_API_RESERVED_ATTRIBUTES = [
'id' => '_id',
Expand Down
6 changes: 5 additions & 1 deletion src/JsonLd/Serializer/ErrorNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ public function supportsNormalization(mixed $data, ?string $format = null, array

public function getSupportedTypes(?string $format): array
{
return $this->inner->getSupportedTypes($format);
if (method_exists($this->inner, 'getSupportedTypes')) {
return $this->inner->getSupportedTypes($format);
}

return [];
}
}
2 changes: 1 addition & 1 deletion src/Laravel/ApiPlatformMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function __construct(
}

/**
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param \Closure(Request): (Response) $next
*/
public function handle(Request $request, \Closure $next, ?string $operationName = null): Response
{
Expand Down
6 changes: 6 additions & 0 deletions src/Laravel/Eloquent/Metadata/ModelMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,14 @@ public function getAttributes(Model $model): Collection
$table = $model->getTable();
$columns = $schema->getColumns($table);
$indexes = $schema->getIndexes($table);
$relations = $this->getRelations($model);

return collect($columns)
->reject(
fn ($column) => $relations->contains(
fn ($relation) => $relation['foreign_key'] === $column['name']
)
)
->map(fn ($column) => [
'name' => $column['name'],
'type' => $column['type'],
Expand Down
23 changes: 23 additions & 0 deletions src/Laravel/Eloquent/State/CollectionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,29 @@ public function provide(Operation $operation, array $uriVariables = [], array $c
$query = $extension->apply($query, $uriVariables, $operation, $context);
}

if ($order = $operation->getOrder()) {
$isList = array_is_list($order);
foreach ($order as $property => $direction) {
if ($isList) {
$property = $direction;
$direction = 'ASC';
}

if (str_contains($property, '.')) {
[$table, $property] = explode('.', $property);

// Relation Order by, we need to do laravel eager loading
$query->with([
$table => fn ($query) => $query->orderBy($property, $direction),
]);

continue;
}

$query->orderBy($property, $direction);
}
}

if (false === $this->pagination->isEnabled($operation, $context)) {
return $query->get();
}
Expand Down
11 changes: 11 additions & 0 deletions src/Laravel/Eloquent/State/LinksHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,17 @@ private function buildQuery(Builder $builder, Link $link, mixed $identifier): Bu
if ($from = $link->getFromProperty()) {
$relation = $this->application->make($link->getFromClass());

if (!method_exists($relation->{$from}(), 'getQualifiedForeignKeyName') && method_exists($relation->{$from}(), 'getQualifiedForeignPivotKeyName')) {
/** @var \Illuminate\Database\Eloquent\Relations\BelongsToMany<Model, Model> $relation */
/** @var \Illuminate\Database\Eloquent\Relations\BelongsToMany<Model, Model> $relation_query */
$relation_query = $relation->{$from}();

return $builder->getModel()->join(
$relation_query->getTable(), $relation->{$from}()->getQualifiedForeignPivotKeyName(), $builder->getModel()->getQualifiedKeyName())
->where($relation->{$from}()->getQualifiedForeignPivotKeyName(),
$identifier);
}

return $builder->getModel()->where($relation->{$from}()->getQualifiedForeignKeyName(), $identifier);
}

Expand Down
5 changes: 2 additions & 3 deletions src/Metadata/Parameter.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

namespace ApiPlatform\Metadata;

use ApiPlatform\OpenApi;
use ApiPlatform\OpenApi\Model\Parameter as OpenApiParameter;
use ApiPlatform\State\ParameterNotFound;
use ApiPlatform\State\ParameterProviderInterface;
Expand Down Expand Up @@ -63,7 +62,7 @@ public function getSchema(): ?array
}

/**
* @return OpenApi\Model\Parameter[]|OpenApi\Model\Parameter|bool|null
* @return OpenApiParameter[]|OpenApiParameter|bool|null
*/
public function getOpenApi(): OpenApiParameter|array|bool|null
{
Expand Down Expand Up @@ -171,7 +170,7 @@ public function withSchema(array $schema): static
}

/**
* @param OpenApi\Model\Parameter[]|OpenApi\Model\Parameter|bool $openApi
* @param OpenApiParameter[]|OpenApiParameter|bool $openApi
*/
public function withOpenApi(OpenApiParameter|array|bool $openApi): static
{
Expand Down
10 changes: 8 additions & 2 deletions src/Metadata/Util/PropertyInfoToTypeInfoHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Symfony\Component\TypeInfo\Exception\InvalidArgumentException;
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\Type\NullableType;
use Symfony\Component\TypeInfo\Type\UnionType;
use Symfony\Component\TypeInfo\TypeIdentifier;

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

public static function unwrapNullableType(Type $type): Type
{
if (!$type instanceof UnionType) {
// BC layer for "symfony/type-info" < 7.2
if (method_exists($type, 'asNonNullable')) {
return (!$type instanceof UnionType) ? $type : $type->asNonNullable();
}

if (!$type instanceof NullableType) {
return $type;
}

return $type->asNonNullable();
return $type->getWrappedType();
}

/**
Expand Down
9 changes: 4 additions & 5 deletions src/OpenApi/Factory/OpenApiFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface;
use ApiPlatform\Metadata\Resource\ResourceMetadataCollection;
use ApiPlatform\OpenApi\Attributes\Webhook;
use ApiPlatform\OpenApi\Model;
use ApiPlatform\OpenApi\Model\Components;
use ApiPlatform\OpenApi\Model\Contact;
use ApiPlatform\OpenApi\Model\Info;
Expand Down Expand Up @@ -415,11 +414,11 @@ private function buildOpenApiResponse(array $existingResponses, int|string $stat
}

/**
* @return \ArrayObject<Model\MediaType>
* @return \ArrayObject<MediaType>
*/
private function buildContent(array $responseMimeTypes, array $operationSchemas): \ArrayObject
{
/** @var \ArrayObject<Model\MediaType> $content */
/** @var \ArrayObject<MediaType> $content */
$content = new \ArrayObject();

foreach ($responseMimeTypes as $mimeType => $format) {
Expand Down Expand Up @@ -506,11 +505,11 @@ private function getPathDescription(string $resourceShortName, string $method, b
/**
* @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#linkObject.
*
* @return \ArrayObject<Model\Link>
* @return \ArrayObject<Link>
*/
private function getLinks(ResourceMetadataCollection $resourceMetadataCollection, HttpOperation $currentOperation): \ArrayObject
{
/** @var \ArrayObject<Model\Link> $links */
/** @var \ArrayObject<Link> $links */
$links = new \ArrayObject();

// Only compute get links for now
Expand Down
11 changes: 5 additions & 6 deletions src/Serializer/AbstractItemNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex
foreach ($constructorParameters as $constructorParameter) {
$paramName = $constructorParameter->name;
$key = $this->nameConverter ? $this->nameConverter->normalize($paramName, $class, $format, $context) : $paramName;
$attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context);
$attributeContext['deserialization_path'] = $attributeContext['deserialization_path'] ?? $key;

$allowed = false === $allowedAttributes || (\is_array($allowedAttributes) && \in_array($paramName, $allowedAttributes, true));
$ignored = !$this->isAllowedAttribute($class, $paramName, $format, $context);
Expand All @@ -310,10 +312,8 @@ protected function instantiateObject(array &$data, string $class, array &$contex
$params[] = $data[$paramName];
}
} elseif ($allowed && !$ignored && (isset($data[$key]) || \array_key_exists($key, $data))) {
$constructorContext = $context;
$constructorContext['deserialization_path'] = $context['deserialization_path'] ?? $key;
try {
$params[] = $this->createConstructorArgument($data[$key], $key, $constructorParameter, $constructorContext, $format);
$params[] = $this->createConstructorArgument($data[$key], $key, $constructorParameter, $attributeContext, $format);
} catch (NotNormalizableValueException $exception) {
if (!isset($context['not_normalizable_value_exceptions'])) {
throw $exception;
Expand All @@ -332,7 +332,6 @@ protected function instantiateObject(array &$data, string $class, array &$contex
$missingConstructorArguments[] = $constructorParameter->name;
}

$attributeContext = $this->getAttributeDenormalizationContext($class, $paramName, $context);
$constructorParameterType = 'unknown';
$reflectionType = $constructorParameter->getType();
if ($reflectionType instanceof \ReflectionNamedType) {
Expand All @@ -343,7 +342,7 @@ protected function instantiateObject(array &$data, string $class, array &$contex
\sprintf('Failed to create object because the class misses the "%s" property.', $constructorParameter->name),
null,
[$constructorParameterType],
$attributeContext['deserialization_path'] ?? null,
$attributeContext['deserialization_path'],
true
);
$context['not_normalizable_value_exceptions'][] = $exception;
Expand Down Expand Up @@ -386,7 +385,7 @@ protected function getClassDiscriminatorResolvedClass(array $data, string $class
return $mappedClass;
}

protected function createConstructorArgument($parameterData, string $key, \ReflectionParameter $constructorParameter, array &$context, ?string $format = null): mixed
protected function createConstructorArgument($parameterData, string $key, \ReflectionParameter $constructorParameter, array $context, ?string $format = null): mixed
{
return $this->createAndValidateAttributeValue($constructorParameter->name, $parameterData, $format, $context);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Symfony/Bundle/Resources/config/doctrine_mongodb_odm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
<service id="api_platform.doctrine_mongodb.odm.state.collection_provider" class="ApiPlatform\Doctrine\Odm\State\CollectionProvider" public="false">
<argument type="service" id="api_platform.metadata.resource.metadata_collection_factory" />
<argument type="service" id="doctrine_mongodb" />
<argument type="tagged" tag="api_platform.doctrine_mongodb.odm.aggregation_extension.collection" />
<argument type="tagged_iterator" tag="api_platform.doctrine_mongodb.odm.aggregation_extension.collection" />
<argument type="tagged_locator" tag="api_platform.doctrine.odm.links_handler" index-by="key" />

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

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

0 comments on commit 81a0f85

Please sign in to comment.