Skip to content

Commit de3bed2

Browse files
committed
Merge 4.0
2 parents fb47197 + eb53218 commit de3bed2

File tree

14 files changed

+173
-28
lines changed

14 files changed

+173
-28
lines changed

CHANGELOG.md

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

3+
## v4.0.10
4+
5+
### Bug fixes
6+
7+
* [774590069](https://github.com/api-platform/core/commit/77459006912d9162fdae9eadd496a62858ac990e) fix(laravel): add contact & license options to fix swagger UI issues (#6804)
8+
* [7ff9790c8](https://github.com/api-platform/core/commit/7ff9790c8e326de8152c653974fd523770dc6501) fix(laravel): allow boolean cast (#6808)
9+
* [f19dd9446](https://github.com/api-platform/core/commit/f19dd9446f5722d243e82b1e7bc34ebdab95719d) fix(laravel): graphQl type locator indexes (#6815)
10+
11+
Also contains [v3.4.7 changes](#v347).
12+
13+
### Features
14+
315
## v4.0.9
416

517
### Bug fixes
@@ -199,6 +211,12 @@ Notes:
199211

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

214+
## v3.4.7
215+
216+
### Bug fixes
217+
218+
* [2d25e79e0](https://github.com/api-platform/core/commit/2d25e79e0e04ce549fb67ecc2017798a8deb7458) fix(symfony): unset item_uri_template when serializing an error (#6816)
219+
202220
## v3.4.6
203221

204222
### Bug fixes

src/Laravel/ApiPlatformProvider.php

+11-2
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,11 @@ public function register(): void
744744
oAuthRefreshUrl: $config->get('api-platform.swagger_ui.oauth.refreshUrl', null),
745745
oAuthScopes: $config->get('api-platform.swagger_ui.oauth.scopes', []),
746746
apiKeys: $config->get('api-platform.swagger_ui.apiKeys', []),
747+
contactName: $config->get('api-platform.swagger_ui.contact.name', ''),
748+
contactUrl: $config->get('api-platform.swagger_ui.contact.url', ''),
749+
contactEmail: $config->get('api-platform.swagger_ui.contact.email', ''),
750+
licenseName: $config->get('api-platform.swagger_ui.license.name', ''),
751+
licenseUrl: $config->get('api-platform.swagger_ui.license.url', ''),
747752
);
748753
});
749754

@@ -1130,14 +1135,18 @@ private function registerGraphQl(Application $app): void
11301135

11311136
$app->singleton('api_platform.graphql.type_locator', function (Application $app) {
11321137
$tagged = iterator_to_array($app->tagged('api_platform.graphql.type'));
1138+
$services = [];
1139+
foreach ($tagged as $service) {
1140+
$services[$service->name] = $service;
1141+
}
11331142

1134-
return new ServiceLocator($tagged);
1143+
return new ServiceLocator($services);
11351144
});
11361145

11371146
$app->singleton(TypesFactoryInterface::class, function (Application $app) {
11381147
$tagged = iterator_to_array($app->tagged('api_platform.graphql.type'));
11391148

1140-
return new TypesFactory($app->make('api_platform.graphql.type_locator'), array_keys($tagged));
1149+
return new TypesFactory($app->make('api_platform.graphql.type_locator'), array_column($tagged, 'name'));
11411150
});
11421151
$app->singleton(TypesContainerInterface::class, function () {
11431152
return new TypesContainer();

src/Laravel/Eloquent/Metadata/Factory/Property/EloquentPropertyMetadataFactory.php

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public function create(string $resourceClass, string $property, array $options =
7777
$type = match ($builtinType) {
7878
'integer' => new Type(Type::BUILTIN_TYPE_INT, $p['nullable']),
7979
'double', 'real' => new Type(Type::BUILTIN_TYPE_FLOAT, $p['nullable']),
80+
'boolean', 'bool' => new Type(Type::BUILTIN_TYPE_BOOL, $p['nullable']),
8081
'datetime', 'date', 'timestamp' => new Type(Type::BUILTIN_TYPE_OBJECT, $p['nullable'], \DateTime::class),
8182
'immutable_datetime', 'immutable_date' => new Type(Type::BUILTIN_TYPE_OBJECT, $p['nullable'], \DateTimeImmutable::class),
8283
'collection', 'encrypted:collection' => new Type(Type::BUILTIN_TYPE_ITERABLE, $p['nullable'], Collection::class, true),

src/Laravel/Tests/GraphQlTest.php

+34
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,38 @@ public function testGetBooksWithPaginationAndOrder(): void
6666
$this->assertCount(3, $data['data']['books']['edges']);
6767
$this->assertArrayNotHasKey('errors', $data);
6868
}
69+
70+
public function testCreateBook(): void
71+
{
72+
/** @var \Workbench\App\Models\Author $author */
73+
$author = AuthorFactory::new()->create();
74+
$response = $this->postJson('/api/graphql', [
75+
'query' => '
76+
mutation createBook($book: createBookInput!){
77+
createBook(input: $book){
78+
book{
79+
name
80+
isAvailable
81+
}
82+
}
83+
}
84+
',
85+
'variables' => [
86+
'book' => [
87+
'name' => fake()->name(),
88+
'author' => 'api/authors/'.$author->id,
89+
'isbn' => fake()->isbn13(),
90+
'isAvailable' => 1 === random_int(0, 1),
91+
],
92+
],
93+
], ['accept' => ['application/json']]);
94+
$response->assertStatus(200);
95+
$data = $response->json();
96+
$this->assertArrayNotHasKey('errors', $data);
97+
$this->assertArrayHasKey('data', $data);
98+
$this->assertArrayHasKey('createBook', $data['data']);
99+
$this->assertArrayHasKey('book', $data['data']['createBook']);
100+
$this->assertArrayHasKey('isAvailable', $data['data']['createBook']['book']);
101+
$this->assertIsBool($data['data']['createBook']['book']['isAvailable']);
102+
}
69103
}

src/Laravel/config/api-platform.php

+10-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,16 @@
8888
// 'refreshUrl' => '',
8989
// 'scopes' => ['scope1' => 'Description scope 1'],
9090
// 'pkce' => true
91-
//]
91+
//],
92+
//'license' => [
93+
// 'name' => 'Apache 2.0',
94+
// 'url' => 'https://www.apache.org/licenses/LICENSE-2.0.html',
95+
//],
96+
//'contact' => [
97+
// 'name' => 'API Support',
98+
// 'url' => 'https://www.example.com/support',
99+
// 'email' => 'support@example.com',
100+
//],
92101
],
93102

94103
'url_generation_strategy' => UrlGeneratorInterface::ABS_PATH,

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

+7-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
use ApiPlatform\Metadata\Delete;
2424
use ApiPlatform\Metadata\Get;
2525
use ApiPlatform\Metadata\GetCollection;
26+
use ApiPlatform\Metadata\GraphQl\Mutation;
2627
use ApiPlatform\Metadata\GraphQl\Query;
2728
use ApiPlatform\Metadata\GraphQl\QueryCollection;
2829
use ApiPlatform\Metadata\Patch;
@@ -55,6 +56,7 @@
5556
new QueryParameter(key: 'order[:property]', filter: OrderFilter::class),
5657
],
5758
),
59+
new Mutation(name: 'create'),
5860
]
5961
)]
6062
#[QueryParameter(key: 'isbn', filter: PartialSearchFilter::class, constraints: 'min:2')]
@@ -74,8 +76,11 @@ class Book extends Model
7476
use HasFactory;
7577
use HasUlids;
7678

77-
protected $visible = ['name', 'author', 'isbn', 'publication_date'];
78-
protected $fillable = ['name'];
79+
protected $visible = ['name', 'author', 'isbn', 'publication_date', 'is_available'];
80+
protected $fillable = ['name', 'is_available'];
81+
protected $casts = [
82+
'is_available' => 'boolean',
83+
];
7984

8085
public function author(): BelongsTo
8186
{

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

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public function definition(): array
3737
'author_id' => AuthorFactory::new(),
3838
'isbn' => fake()->isbn13(),
3939
'publication_date' => fake()->optional()->date(),
40+
'is_available' => 1 === random_int(0, 1),
4041
'internal_note' => fake()->text(),
4142
];
4243
}

src/Laravel/workbench/database/migrations/2023_07_15_231244_create_book_table.php

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public function up(): void
3232
$table->string('name');
3333
$table->string('isbn');
3434
$table->date('publication_date')->nullable();
35+
$table->boolean('is_available')->default(true);
3536
$table->text('internal_note')->nullable();
3637
$table->integer('author_id')->unsigned();
3738
$table->foreign('author_id')->references('id')->on('authors');

src/Metadata/ApiResource.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public function __construct(
106106
* 'jsonapi' => ['application/vnd.api+json'],
107107
* 'json' => ['application/json'],
108108
* 'xml' => ['application/xml', 'text/xml'],
109-
* 'yaml' => ['application/x-yaml'],
109+
* 'yaml' => ['application/yaml'],
110110
* 'csv' => ['text/csv'],
111111
* 'html' => ['text/html'],
112112
* 'myformat' =>['application/vnd.myformat'],

src/Symfony/EventListener/ErrorListener.php

+4
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ protected function duplicateRequest(\Throwable $exception, Request $request): Re
9999
$normalizationContext += ['api_error_resource' => true];
100100
}
101101

102+
if (isset($normalizationContext['item_uri_template'])) {
103+
unset($normalizationContext['item_uri_template']);
104+
}
105+
102106
if (!isset($normalizationContext[AbstractObjectNormalizer::IGNORED_ATTRIBUTES])) {
103107
$normalizationContext[AbstractObjectNormalizer::IGNORED_ATTRIBUTES] = ['trace', 'file', 'line', 'code', 'message', 'traceAsString'];
104108
}

src/Symfony/Routing/Router.php

+1-8
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,7 @@
1414
namespace ApiPlatform\Symfony\Routing;
1515

1616
use ApiPlatform\Metadata\UrlGeneratorInterface;
17-
use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
1817
use Symfony\Component\HttpFoundation\Request;
19-
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
2018
use Symfony\Component\Routing\RequestContext;
2119
use Symfony\Component\Routing\RouteCollection;
2220
use Symfony\Component\Routing\RouterInterface;
@@ -75,12 +73,7 @@ public function match(string $pathInfo): array
7573
}
7674

7775
$request = Request::create($pathInfo, Request::METHOD_GET, [], [], [], ['HTTP_HOST' => $baseContext->getHost()]);
78-
try {
79-
$context = (new RequestContext())->fromRequest($request);
80-
} catch (RequestExceptionInterface) {
81-
throw new ResourceNotFoundException('Invalid request context.');
82-
}
83-
76+
$context = (new RequestContext())->fromRequest($request);
8477
$context->setPathInfo($pathInfo);
8578
$context->setScheme($baseContext->getScheme());
8679
$context->setHost($baseContext->getHost());
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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 ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue6718;
15+
16+
use ApiPlatform\Metadata\ApiResource;
17+
use ApiPlatform\Metadata\Get;
18+
use ApiPlatform\Metadata\Operation;
19+
20+
#[ApiResource(
21+
shortName: 'OrganisationIssue6718',
22+
extraProperties: ['rfc_7807_compliant_errors' => true],
23+
operations: [
24+
new Get(
25+
uriTemplate: '/6718_organisations/{id}',
26+
provider: [self::class, 'itemProvider'],
27+
),
28+
new Get(
29+
uriTemplate: '/6718_users/{userId}/organisation',
30+
uriVariables: [
31+
'userId',
32+
],
33+
normalizationContext: [
34+
'item_uri_template' => '/6718_organisations/{id}',
35+
'hydra_prefix' => false,
36+
],
37+
provider: [self::class, 'userOrganizationItemProvider']
38+
),
39+
],
40+
)]
41+
class Organization
42+
{
43+
public function __construct(public readonly string $id)
44+
{
45+
}
46+
47+
public static function itemProvider(Operation $operation, array $uriVariables = []): ?self
48+
{
49+
return new self($uriVariables['id']);
50+
}
51+
52+
public static function userOrganizationItemProvider(): ?self
53+
{
54+
return null;
55+
}
56+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <dunglas@gmail.com>
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 ApiPlatform\Tests\Functional;
15+
16+
use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
17+
18+
class ItemUriTemplateTest extends ApiTestCase
19+
{
20+
public function testIssue6718(): void
21+
{
22+
self::createClient()->request('GET', '/6718_users/1/organisation', [
23+
'headers' => ['accept' => 'application/ld+json'],
24+
]);
25+
$this->assertResponseStatusCodeSame(404);
26+
$this->assertJsonContains(['description' => 'Not Found']);
27+
}
28+
}

tests/Symfony/Routing/RouterTest.php

-14
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
use PHPUnit\Framework\TestCase;
1919
use Prophecy\Argument;
2020
use Prophecy\PhpUnit\ProphecyTrait;
21-
use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface;
2221
use Symfony\Component\Routing\RequestContext;
2322
use Symfony\Component\Routing\RouteCollection;
2423
use Symfony\Component\Routing\RouterInterface;
@@ -96,19 +95,6 @@ public function testMatch(): void
9695
$this->assertEquals(['bar'], $router->match('/app_dev.php/foo'));
9796
}
9897

99-
public function testMatchWithInvalidContext(): void
100-
{
101-
$this->expectException(RoutingExceptionInterface::class);
102-
$this->expectExceptionMessage('Invalid request context.');
103-
$context = new RequestContext('/app_dev.php', 'GET', 'localhost', 'https');
104-
105-
$mockedRouter = $this->prophesize(RouterInterface::class);
106-
$mockedRouter->getContext()->willReturn($context)->shouldBeCalled();
107-
108-
$router = new Router($mockedRouter->reveal());
109-
$router->match('28-01-2018 10:10');
110-
}
111-
11298
public function testMatchDuplicatedBaseUrl(): void
11399
{
114100
$context = new RequestContext('/app', 'GET', 'localhost', 'https');

0 commit comments

Comments
 (0)