Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 22 additions & 14 deletions src/State/ParameterProvider/ReadLinkParameterProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,13 @@ public function provide(Parameter $parameter, array $parameters = [], array $con
}

/**
* @return array<string, string>
* @return array<string, mixed>
*/
private function getUriVariables(mixed $value, Parameter $parameter, Operation $operation): array
{
$extraProperties = $parameter->getExtraProperties();
if (\is_array($value)) {
return $value;
}

if ($operation instanceof HttpOperation) {
$links = $operation->getUriVariables();
Expand All @@ -119,24 +121,30 @@ private function getUriVariables(mixed $value, Parameter $parameter, Operation $
$links = [];
}

if (!\is_array($value)) {
$uriVariables = [];
$extraProperties = $parameter->getExtraProperties();
$linkClass = $parameter instanceof Link
? ($parameter->getFromClass() ?? $parameter->getToClass())
: null;

$fallbackKey = null;
foreach ($links as $key => $link) {
if (!\is_string($key)) {
$key = $link->getParameterName() ?? $extraProperties['uri_variable'] ?? $link->getFromProperty();
}

foreach ($links as $key => $link) {
if (!\is_string($key)) {
$key = $link->getParameterName() ?? $extraProperties['uri_variable'] ?? $link->getFromProperty();
}
if (!$key || !\is_string($key)) {
continue;
}

if (!$key || !\is_string($key)) {
continue;
}
$linkFromClass = $link instanceof Link ? ($link->getFromClass() ?? $link->getToClass()) : null;

$uriVariables[$key] = $value;
if (null !== $linkClass && $linkFromClass === $linkClass) {
return [$key => $value];
}

return $uriVariables;
$fallbackKey ??= $key;
}

return $value;
return null === $fallbackKey ? [] : [$fallbackKey => $value];
}
}
62 changes: 62 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/Issue7939BarResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Operation;

#[ApiResource(
operations: [
new Get(
uriTemplate: '/issue7939_foos/{fooId}/bars/{id}',
uriVariables: [
'fooId' => new Link(fromClass: Issue7939FooResource::class, toProperty: 'foo'),
'id' => new Link(fromClass: self::class),
],
provider: [self::class, 'provide'],
),
],
)]
final class Issue7939BarResource
{
private const PARENTS = ['B' => 'F2'];

public string $id = '';
public ?Issue7939FooResource $foo = null;

public static function parentOf(string $barId): ?string
{
return self::PARENTS[$barId] ?? null;
}

public static function provide(Operation $operation, array $uriVariables = [])
{
$id = (string) ($uriVariables['id'] ?? '');
$parent = self::parentOf($id);

if (null === $parent) {
return null;
}

$bar = new self();
$bar->id = $id;
$foo = new Issue7939FooResource();
$foo->id = $parent;
$bar->foo = $foo;

return $bar;
}
}
81 changes: 81 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/Issue7939BazResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Link;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Parameter;
use ApiPlatform\State\ParameterProvider\ReadLinkParameterProvider;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

#[ApiResource(
operations: [
new Get(
uriTemplate: '/issue7939_foos/{fooId}/bars/{barId}/baz',
uriVariables: [
'fooId' => new Link(fromClass: Issue7939FooResource::class),
'barId' => new Link(
fromClass: Issue7939BarResource::class,
identifiers: ['id'],
provider: ReadLinkParameterProvider::class,
),
],
provider: [self::class, 'provide'],
),
new Get(
uriTemplate: '/issue7939_foos/{fooId}/bars/{barId}/baz_strict',
uriVariables: [
'fooId' => new Link(
fromClass: Issue7939FooResource::class,
provider: [self::class, 'validateParent'],
),
'barId' => new Link(
fromClass: Issue7939BarResource::class,
identifiers: ['id'],
provider: ReadLinkParameterProvider::class,
),
],
provider: [self::class, 'provide'],
),
],
)]
final class Issue7939BazResource
{
public string $id = '1';
public string $barId = '';
public string $fooId = '';

public static function provide(Operation $operation, array $uriVariables = [])
{
$r = new self();
$r->fooId = (string) ($uriVariables['fooId'] ?? '');
$r->barId = (string) ($uriVariables['barId'] ?? '');

return $r;
}

public static function validateParent(Parameter $parameter, array $values = [], array $context = []): ?Operation
{
$barId = (string) ($values['barId'] ?? '');
$fooId = (string) ($values['fooId'] ?? '');

if (Issue7939BarResource::parentOf($barId) !== $fooId) {
throw new NotFoundHttpException('Bar does not belong to the requested Foo.');
}

return $context['operation'] ?? null;
}
}
39 changes: 39 additions & 0 deletions tests/Fixtures/TestBundle/ApiResource/Issue7939FooResource.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <dunglas@gmail.com>
*
* 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\Metadata\ApiResource;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\Operation;

#[ApiResource(
operations: [
new Get(
uriTemplate: '/issue7939_foos/{id}',
provider: [self::class, 'provide'],
),
],
)]
final class Issue7939FooResource
{
public string $id = '';

public static function provide(Operation $operation, array $uriVariables = [])
{
$r = new self();
$r->id = (string) ($uriVariables['id'] ?? '');

return $r;
}
}
46 changes: 45 additions & 1 deletion tests/Functional/Parameters/LinkProviderParameterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

use ApiPlatform\Symfony\Bundle\Test\ApiTestCase;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7469TestResource;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7939BarResource;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7939BazResource;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\Issue7939FooResource;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\LinkParameterProviderResource;
use ApiPlatform\Tests\Fixtures\TestBundle\ApiResource\WithParameter;
use ApiPlatform\Tests\Fixtures\TestBundle\Entity\Company;
Expand All @@ -40,7 +43,7 @@ final class LinkProviderParameterTest extends ApiTestCase
*/
public static function getResources(): array
{
return [WithParameter::class, Dummy::class, Employee::class, Company::class, LinkParameterProviderResource::class, Issue7469TestResource::class, Issue7469Dummy::class, Pairing::class, Plan::class];
return [WithParameter::class, Dummy::class, Employee::class, Company::class, LinkParameterProviderResource::class, Issue7469TestResource::class, Issue7469Dummy::class, Pairing::class, Plan::class, Issue7939FooResource::class, Issue7939BarResource::class, Issue7939BazResource::class];
}

/**
Expand Down Expand Up @@ -236,6 +239,47 @@ public function testSecurityLinkWithDifferentFromClassDoesNotBreakDoctrine(): vo
]);
}

/**
* @see https://github.com/api-platform/core/issues/7939
*/
public function testReadLinkParameterProviderResolvesNestedUriVariables(): void
{
$container = static::getContainer();
if ('mongodb' === $container->getParameter('kernel.environment')) {
$this->markTestSkipped();
}

$response = self::createClient()->request('GET', '/issue7939_foos/F/bars/B/baz');
self::assertResponseStatusCodeSame(200);
self::assertJsonContains([
'fooId' => 'F',
'barId' => 'B',
]);
}

/**
* @see https://github.com/api-platform/core/issues/7939
*/
public function testParentLinkProviderEnforcesParentScope(): void
{
$container = static::getContainer();
if ('mongodb' === $container->getParameter('kernel.environment')) {
$this->markTestSkipped();
}

$client = self::createClient();

$client->request('GET', '/issue7939_foos/F2/bars/B/baz_strict');
self::assertResponseStatusCodeSame(200);
self::assertJsonContains([
'fooId' => 'F2',
'barId' => 'B',
]);

$client->request('GET', '/issue7939_foos/F1/bars/B/baz_strict');
self::assertResponseStatusCodeSame(404);
}

public function testIssue7469IriGenerationFailsForLinkedResource(): void
{
$container = static::getContainer();
Expand Down
Loading