Skip to content

Commit 42f0ce7

Browse files
committed
Merge 4.1
2 parents d36837b + e1191ca commit 42f0ce7

File tree

17 files changed

+328
-41
lines changed

17 files changed

+328
-41
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## v4.1.17
4+
5+
### Bug fixes
6+
7+
* [34a0d54b1](https://github.com/api-platform/core/commit/34a0d54b1fe195a233726ff2e8304c29f43954ca) fix(jsonld): genId false should work with embeded resources (#7219)
8+
* [c025b8f9f](https://github.com/api-platform/core/commit/c025b8f9f8ab51489dc75e470c057d6fae879fa0) fix(openapi): correct example usage for 3.0 (#7218)
9+
* [cdda4146b](https://github.com/api-platform/core/commit/cdda4146b429f8e605504e1dc403faa41f0255a7) fix(laravel): Allow `LinksHandler` to handle polymorphic relationships (#7231)
10+
* [dba97370f](https://github.com/api-platform/core/commit/dba97370fb8996cf4fc1d5b6d8ca92faf4e68637) fix(metadata): boolean type detection from parameter's schema (#7223)
11+
312
## v4.1.16
413

514
### Bug fixes

src/JsonLd/Serializer/ItemNormalizer.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public function normalize(mixed $object, ?string $format = null, array $context
111111
} elseif ($this->contextBuilder instanceof AnonymousContextBuilderInterface) {
112112
if ($context['api_collection_sub_level'] ?? false) {
113113
unset($context['api_collection_sub_level']);
114-
$context['output']['genid'] = true;
114+
$context['output']['gen_id'] ??= true;
115115
$context['output']['iri'] = null;
116116
}
117117

@@ -124,7 +124,7 @@ public function normalize(mixed $object, ?string $format = null, array $context
124124
unset($context['operation'], $context['operation_name']);
125125
}
126126

127-
if (true === ($context['force_iri_generation'] ?? true) && $iri = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context)) {
127+
if (true === ($context['output']['gen_id'] ?? true) && true === ($context['force_iri_generation'] ?? true) && $iri = $this->iriConverter->getIriFromResource($object, UrlGeneratorInterface::ABS_PATH, $context['operation'] ?? null, $context)) {
128128
$context['iri'] = $iri;
129129
$metadata['@id'] = $iri;
130130
}

src/Laravel/Eloquent/State/LinksHandler.php

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
namespace ApiPlatform\Laravel\Eloquent\State;
1515

1616
use ApiPlatform\Metadata\Exception\OperationNotFoundException;
17+
use ApiPlatform\Metadata\Exception\RuntimeException;
1718
use ApiPlatform\Metadata\GraphQl\Operation;
1819
use ApiPlatform\Metadata\GraphQl\Query;
1920
use ApiPlatform\Metadata\HttpOperation;
@@ -22,6 +23,11 @@
2223
use Illuminate\Contracts\Foundation\Application;
2324
use Illuminate\Database\Eloquent\Builder;
2425
use Illuminate\Database\Eloquent\Model;
26+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
27+
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
28+
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
29+
use Illuminate\Database\Eloquent\Relations\MorphTo;
30+
use Illuminate\Database\Eloquent\Relations\Relation;
2531

2632
/**
2733
* @implements LinksHandlerInterface<Model>
@@ -101,34 +107,44 @@ private function buildQuery(Builder $builder, Link $link, mixed $identifier): Bu
101107
}
102108

103109
if ($from = $link->getFromProperty()) {
104-
$relation = $this->application->make($link->getFromClass());
105-
$relationQuery = $relation->{$from}();
106-
if (!method_exists($relationQuery, 'getQualifiedForeignKeyName') && method_exists($relationQuery, 'getQualifiedForeignPivotKeyName')) {
110+
/** @var Model $relatedInstance */
111+
$relatedInstance = $this->application->make($link->getFromClass());
112+
$relatedInstance->setAttribute($relatedInstance->getKeyName(), $identifier);
113+
$relatedInstance->exists = true;
114+
115+
/** @var Relation<Model, Model, mixed> $relation */
116+
$relation = $relatedInstance->{$from}();
117+
118+
if ($relation instanceof MorphTo) {
119+
throw new RuntimeException('Cannot query directly from a MorphTo relationship.');
120+
}
121+
122+
if ($relation instanceof BelongsTo) {
107123
return $builder->getModel()
108124
->join(
109-
$relationQuery->getTable(), // @phpstan-ignore-line
110-
$relationQuery->getQualifiedRelatedPivotKeyName(), // @phpstan-ignore-line
111-
$builder->getModel()->getQualifiedKeyName()
112-
)
113-
->where(
114-
$relationQuery->getQualifiedForeignPivotKeyName(), // @phpstan-ignore-line
125+
$relation->getParent()->getTable(),
126+
$relation->getParent()->getQualifiedKeyName(),
115127
$identifier
116-
)
117-
->select($builder->getModel()->getTable().'.*');
128+
);
118129
}
119130

120-
if (method_exists($relationQuery, 'dissociate')) {
121-
return $builder->getModel()
122-
->join(
123-
$relationQuery->getParent()->getTable(), // @phpstan-ignore-line
124-
$relationQuery->getParent()->getQualifiedKeyName(), // @phpstan-ignore-line
125-
$identifier
126-
);
131+
if ($relation instanceof HasOneOrMany || $relation instanceof BelongsToMany) {
132+
return $relation->getQuery();
133+
}
134+
135+
if (method_exists($relation, 'getQualifiedForeignKeyName')) {
136+
return $relation->getQuery()->where(
137+
$relation->getQualifiedForeignKeyName(),
138+
$identifier
139+
);
127140
}
128141

129-
return $builder->getModel()->where($relationQuery->getQualifiedForeignKeyName(), $identifier);
142+
throw new RuntimeException(\sprintf('Unhandled or unknown relationship type: %s for property %s on %s', $relation::class, $from, $relatedInstance::class));
130143
}
131144

132-
return $builder->where($builder->getModel()->qualifyColumn($link->getIdentifiers()[0]), $identifier);
145+
return $builder->where(
146+
$builder->getModel()->qualifyColumn($link->getIdentifiers()[0]),
147+
$identifier
148+
);
133149
}
134150
}

src/Laravel/Tests/EloquentTest.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@
1919
use Illuminate\Support\Str;
2020
use Orchestra\Testbench\Concerns\WithWorkbench;
2121
use Orchestra\Testbench\TestCase;
22+
use Workbench\App\Models\PostWithMorphMany;
2223
use Workbench\Database\Factories\AuthorFactory;
2324
use Workbench\Database\Factories\BookFactory;
25+
use Workbench\Database\Factories\CommentMorphFactory;
2426
use Workbench\Database\Factories\GrandSonFactory;
27+
use Workbench\Database\Factories\PostWithMorphManyFactory;
2528
use Workbench\Database\Factories\WithAccessorFactory;
2629

2730
class EloquentTest extends TestCase
@@ -444,6 +447,17 @@ public function testBelongsTo(): void
444447
$this->assertEquals($json['sons'][0], '/api/grand_sons/1');
445448
}
446449

450+
public function testHasMany(): void
451+
{
452+
GrandSonFactory::new()->count(1)->create();
453+
454+
$res = $this->get('/api/grand_fathers/1/grand_sons', ['Accept' => ['application/ld+json']]);
455+
$json = $res->json();
456+
$this->assertEquals($json['@id'], '/api/grand_fathers/1/grand_sons');
457+
$this->assertEquals($json['totalItems'], 1);
458+
$this->assertEquals($json['member'][0]['@id'], '/api/grand_sons/1');
459+
}
460+
447461
public function testRelationIsHandledOnCreateWithNestedData(): void
448462
{
449463
$cartData = [
@@ -538,4 +552,41 @@ public function testPostWithEmptyMorphMany(): void
538552
'comments' => [['content' => 'hello']],
539553
]);
540554
}
555+
556+
public function testPostCommentsCollectionFromMorphMany(): void
557+
{
558+
PostWithMorphManyFactory::new()->create();
559+
560+
CommentMorphFactory::new()->count(5)->create([
561+
'commentable_id' => 1,
562+
'commentable_type' => PostWithMorphMany::class,
563+
]);
564+
565+
$response = $this->getJson('/api/post_with_morph_manies/1/comments', [
566+
'accept' => 'application/ld+json',
567+
]);
568+
$response->assertStatus(200);
569+
$response->assertJsonCount(5, 'member');
570+
}
571+
572+
public function testPostCommentItemFromMorphMany(): void
573+
{
574+
PostWithMorphManyFactory::new()->create();
575+
576+
CommentMorphFactory::new()->count(5)->create([
577+
'commentable_id' => 1,
578+
'commentable_type' => PostWithMorphMany::class,
579+
])->first();
580+
581+
$response = $this->getJson('/api/post_with_morph_manies/1/comments/1', [
582+
'accept' => 'application/ld+json',
583+
]);
584+
$response->assertStatus(200);
585+
$response->assertJson([
586+
'@context' => '/api/contexts/CommentMorph',
587+
'@id' => '/api/post_with_morph_manies/1/comments/1',
588+
'@type' => 'CommentMorph',
589+
'id' => 1,
590+
]);
591+
}
541592
}

src/Laravel/composer.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,5 @@
127127
"type": "vcs",
128128
"url": "https://github.com/soyuka/phpunit"
129129
}
130-
],
131-
"version": "v4.1.15"
130+
]
132131
}

src/Laravel/workbench/app/Models/CommentMorph.php

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,40 @@
1414
namespace Workbench\App\Models;
1515

1616
use ApiPlatform\Metadata\ApiProperty;
17-
use ApiPlatform\Metadata\NotExposed;
17+
use ApiPlatform\Metadata\ApiResource;
18+
use ApiPlatform\Metadata\Get;
19+
use ApiPlatform\Metadata\GetCollection;
20+
use ApiPlatform\Metadata\Link;
1821
use Illuminate\Database\Eloquent\Model;
1922
use Illuminate\Database\Eloquent\Relations\MorphTo;
2023
use Symfony\Component\Serializer\Attribute\Groups;
2124

22-
#[NotExposed]
25+
#[ApiResource(
26+
operations: [
27+
new GetCollection(
28+
uriTemplate: '/post_with_morph_manies/{id}/comments',
29+
uriVariables: [
30+
'id' => new Link(
31+
fromProperty: 'comments',
32+
fromClass: PostWithMorphMany::class,
33+
),
34+
]
35+
),
36+
new Get(
37+
uriTemplate: '/post_with_morph_manies/{postId}/comments/{id}',
38+
uriVariables: [
39+
'postId' => new Link(
40+
fromProperty: 'comments',
41+
fromClass: PostWithMorphMany::class,
42+
),
43+
'id' => new Link(
44+
fromClass: CommentMorph::class,
45+
),
46+
]
47+
),
48+
]
49+
)]
50+
#[ApiProperty(identifier: true, serialize: new Groups(['comments']), property: 'id')]
2351
#[ApiProperty(serialize: new Groups(['comments']), property: 'content')]
2452
class CommentMorph extends Model
2553
{

src/Laravel/workbench/database/factories/CommentFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use Workbench\App\Models\Comment;
1818

1919
/**
20-
* @template TModel of \Workbench\App\Models\Author
20+
* @template TModel of \Workbench\App\Models\Comment
2121
*
2222
* @extends \Illuminate\Database\Eloquent\Factories\Factory<TModel>
2323
*/
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Workbench\Database\Factories;
15+
16+
use Illuminate\Database\Eloquent\Factories\Factory;
17+
use Workbench\App\Models\CommentMorph;
18+
19+
/**
20+
* @template TModel of \Workbench\App\Models\CommentMorph
21+
*
22+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<TModel>
23+
*/
24+
class CommentMorphFactory extends Factory
25+
{
26+
/**
27+
* The name of the factory's corresponding model.
28+
*
29+
* @var class-string<TModel>
30+
*/
31+
protected $model = CommentMorph::class;
32+
33+
/**
34+
* Define the model's default state.
35+
*
36+
* @return array<string, mixed>
37+
*/
38+
public function definition(): array
39+
{
40+
return [
41+
'commentable_id' => PostWithMorphManyFactory::new(),
42+
'commentable_type' => PostWithMorphManyFactory::class,
43+
'content' => fake()->text(),
44+
];
45+
}
46+
}

src/Laravel/workbench/database/factories/PostFactory.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
use Workbench\App\Models\Post;
1818

1919
/**
20-
* @template TModel of \Workbench\App\Models\Author
20+
* @template TModel of \Workbench\App\Models\Post
2121
*
2222
* @extends \Illuminate\Database\Eloquent\Factories\Factory<TModel>
2323
*/
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace Workbench\Database\Factories;
15+
16+
use Illuminate\Database\Eloquent\Factories\Factory;
17+
use Workbench\App\Models\PostWithMorphMany;
18+
19+
/**
20+
* @template TModel of \Workbench\App\Models\PostWithMorphMany
21+
*
22+
* @extends \Illuminate\Database\Eloquent\Factories\Factory<TModel>
23+
*/
24+
class PostWithMorphManyFactory extends Factory
25+
{
26+
/**
27+
* The name of the factory's corresponding model.
28+
*
29+
* @var class-string<TModel>
30+
*/
31+
protected $model = PostWithMorphMany::class;
32+
33+
/**
34+
* Define the model's default state.
35+
*
36+
* @return array<string, mixed>
37+
*/
38+
public function definition(): array
39+
{
40+
return [
41+
'title' => fake()->unique()->sentence(10),
42+
'content' => fake()->sentences(10, true),
43+
];
44+
}
45+
}

src/Metadata/Resource/Factory/ParameterResourceMetadataCollectionFactory.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,8 @@ private function getDefaultParameters(Operation $operation, string $resourceClas
156156
$parameter = $parameter->withNativeType(Type::list(Type::string()));
157157
} elseif ('string' === ($parameter->getSchema()['type'] ?? null)) {
158158
$parameter = $parameter->withNativeType(Type::string());
159+
} elseif ('boolean' === ($parameter->getSchema()['type'] ?? null)) {
160+
$parameter = $parameter->withNativeType(Type::bool());
159161
} else {
160162
$parameter = $parameter->withNativeType(Type::union(Type::string(), Type::list(Type::string())));
161163
}

src/OpenApi/Serializer/LegacyOpenApiNormalizer.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ public function normalize(mixed $object, ?string $format = null, array $context
4545
}
4646
unset($schemas[$name]['properties'][$property]['type']);
4747
}
48+
49+
if (\is_array($value['examples'] ?? false)) {
50+
$schemas[$name]['properties'][$property]['example'] = $value['examples'];
51+
unset($schemas[$name]['properties'][$property]['examples']);
52+
}
4853
}
4954
}
5055

0 commit comments

Comments
 (0)