Skip to content

Rework to improve and simplify identifiers management #3664

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 2, 2020

Conversation

soyuka
Copy link
Member

@soyuka soyuka commented Aug 5, 2020

Q A
Bug fix? no
New feature? no
BC breaks? yes
Deprecations? yes
Tickets rework of #2126
License MIT
Doc PR na

Basically my idea was to specify on the operation what it's identifiers are. This helps a lot with identifiers parsing etc.

The code speaks for itself. I still need to find a solution for composite identifiers, for example we can deprecate them but keep an option:

@ApiResource(identifiedBy='id')

Would work and force a single identifier in the route path even if there were several.

@soyuka soyuka force-pushed the improve-identifiers branch from 55cae9d to e384651 Compare August 5, 2020 15:16
@nicodmf
Copy link

nicodmf commented Aug 13, 2020

Very important feature +++ 1 @soyuka can i help you ?

@soyuka
Copy link
Member Author

soyuka commented Aug 13, 2020

Very important feature +++ 1 @soyuka can i help you ?

There are several issues with this:

  • how do we keep today's composite identifiers (I have a solution for this)
  • IdentifiersExtractorInterface should not be added to the ApiLoader and IMO identifiers should be within the Metadata (this results in a recursive dependency injection) not yet a solution but I need to dig into it
  • this improves subresources a lot, we're going to talk about this with core contributors by the end of the month

I'll let you know when and if we're testing it would love feedbacks. Thanks for the help.

@vincentchalamon vincentchalamon mentioned this pull request Aug 27, 2020
11 tasks
@soyuka soyuka force-pushed the improve-identifiers branch 8 times, most recently from 7dd65dd to 7f94689 Compare September 25, 2020 14:28
@soyuka soyuka marked this pull request as ready for review September 25, 2020 14:41
@@ -0,0 +1,85 @@
# Feature: JSON relations with plain identifiers support
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it be removed?

@@ -12,6 +12,7 @@ Feature: Using custom identifier on resource
"name": "My Dummy"
}
"""
Then print last JSON response
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To remove.

@@ -63,3 +63,23 @@ Feature: Operation support
Scenario: Get a 404 response for the disabled item operation
When I send a "GET" request to "/disable_item_operations/1"
Then the response status code should be 404

@createSchema
Scenario: Get a book by it's ISBN
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Scenario: Get a book by it's ISBN
Scenario: Get a book by its ISBN

@@ -64,19 +63,13 @@ public function supports(string $resourceClass, string $operationName = null, ar
*
* @throws RuntimeException
*/
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])
public function getItem(string $resourceClass, $identifiers, ?string $operationName = null, array $context = [])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not typehinted to avoid BC? Maybe add a typehint comment for 3.0.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed I didn't touch the interfaces yet but I can update them, this needs to target 3.0 though.

@@ -49,5 +50,6 @@ public function build(ContainerBuilder $container)
$container->addCompilerPass(new GraphQlQueryResolverPass());
$container->addCompilerPass(new GraphQlMutationResolverPass());
$container->addCompilerPass(new MetadataAwareNameConverterPass());
// $container->addCompilerPass(new IdentifiedByResourceMetadataFactoryPass());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

@soyuka soyuka force-pushed the improve-identifiers branch from b1ccb46 to c689332 Compare October 2, 2020 09:13
@soyuka soyuka changed the base branch from master to next October 2, 2020 09:13
@soyuka soyuka force-pushed the improve-identifiers branch 5 times, most recently from 1dc650e to d3c0a44 Compare October 2, 2020 10:18
Ease identifiers processing with breaking changes
@soyuka soyuka force-pushed the improve-identifiers branch from d3c0a44 to 0db3206 Compare October 2, 2020 12:53
@soyuka soyuka merged commit ff701dd into api-platform:next Oct 2, 2020
@alebedev80
Copy link
Contributor

@soyuka when it will be released?

@soyuka
Copy link
Member Author

soyuka commented Jan 26, 2021

@OskarStark
Copy link
Contributor

OskarStark commented Feb 22, 2021

Hi @soyuka,

sorry but my code is breaking (only in CI 😄 ) since 2.6 release. Unfortunately there isn't an UPGRADE guide, so I have to ask.

I get the following error:

45) App\Tests\Integration\Entity\BenutzerTest::canBeSerializedWithBenutzerUpdateSerializationGroup

ApiPlatform\Core\Exception\InvalidArgumentException: Unable to generate an IRI for "App\Entity\Benutzer".

Caused by
Symfony\Component\Routing\Exception\MissingMandatoryParametersException: Some mandatory parameters are missing ("id") to generate a URL for route "api_benutzers_get_item".

So I think I am on the right track with this PR/feature.

The object is the only one which does not use $id as identifier, but $objectGuid:

<?php

namespace App\Entity;

/**
 * @API\ApiResource(
 *     collectionOperations={
 *         "get": {
 *             "method": "GET",
 *             "path": "/benutzer.{_format}",
 *             "normalization_context": {"groups": {"benutzer_collection_output"}},
 *         },
 *     },
 *     itemOperations={
 *         "get": {
 *             "method": "GET",
 *             "path": "/benutzer/{id}.{_format}",
 *             "requirements": {"id": "%uuid_format%"},
 *             "normalization_context": {"groups": {"benutzer_item_output"}},
 *         },
 *     },
 *     attributes={
 *         "order": {"anzeigename": "ASC"},
 *     },
 * )
 * @API\ApiFilter(BooleanFilter::class)
 * @ORM\Entity(repositoryClass="App\Repository\BenutzerRepository")
 * @ORM\Table(
 *     name="benutzer",
 *     indexes={
 *         @ORM\Index(name="benutzer_anzeigename_idx", columns={"anzeigename"})
 *     },
 *     uniqueConstraints={
 *         @ORM\UniqueConstraint(name="benutzer_kontoname", columns={"kontoname"}),
 *     },
 * )
 */
class Benutzer implements UserInterface
{
    /**
     * @API\ApiProperty(identifier=true, writable=false)
     * @ORM\Column(name="object_guid", type="guid")
     * @ORM\Id
     * @Serial\Groups({
     *     "benutzer_collection_output",
     *     "benutzer_item_output",
     *     "fall_item_output",
     * })
     * @Assert\Uuid
     *
     * @var string
     */
    private $objectGuid;
    
    // ...
    
}

I think I need a custom IRI converter from now on, am I right?

@soyuka
Copy link
Member Author

soyuka commented Feb 22, 2021

Mhh it should not break on this I think it's a bug. Do you really have to declare the path? In the meantime try:

  "path": "/benutzer/{objectGuid}.{_format}",

Or specify

  "identifiers": {"id": {"objectGuid", Benutzer::class}}

to see if it works?

@OskarStark
Copy link
Contributor

OskarStark commented Feb 23, 2021

Thanks for your feedback 👍

Unfortunately this doesn't work.

I used your code snippets like this:

 *     itemOperations={
 *         "get": {
 *             "method": "GET",
-*             "path": "/benutzer/{id}.{_format}",
-*             "requirements": {"id": "%uuid_format%"}, 
+*             "path": "/benutzer/{objectGuid}.{_format}",
+*             "requirements": {"objectGuid": "%uuid_format%"},
 *             "normalization_context": {"groups": {"benutzer_item_output"}},
 *         },
 *     },

and (but only one at a time) like this:

 *     attributes={
+*         "identifiers": {"id": {"objectGuid", Benutzer::class}},
 *         "order": {"anzeigename": "ASC"},
 *     },

but also tried this:

 *     itemOperations={
 *         "get": {
 *             "method": "GET",
 *             "path": "/benutzer/{id}.{_format}",
 *             "requirements": {"id": "%uuid_format%"}, 
+*             "identifiers": {"id": {"objectGuid", Benutzer::class}},
 *             "normalization_context": {"groups": {"benutzer_item_output"}},
 *         },
 *     },

Where can I find some documentation for the "identifiers" ? 🤔

My tests are failing, I add one for reference:

    /**
     * @test
     */
    public function canBeSerializedWithDefaultSerializationGroup(): void
    {
        /** @var Entity\RamicroAkte $ramicroAkte */
        $ramicroAkte = self::fixtureFactory()->createOne(Entity\RamicroAkte::class);

        $serializer = self::formatAndContextSpecificSerializer('json');

        $serialized = $serializer->serialize($ramicroAkte);

        $iriResolver = self::iriResolver();

        $expected = self::jsonEncoder()->encode([
            'aktenzeichen' => $ramicroAkte->getAktenzeichen(),
            'fall' => $iriResolver->iriFor($ramicroAkte->getFall()),
            'gegnerischeBank' => $iriResolver->iriFor($ramicroAkte->getGegnerischeBank()),
            'id' => $ramicroAkte->getId(),
            'benutzer' => $iriResolver->iriFor($ramicroAkte->getBenutzer()),
        ]);

        self::assertJsonStringEqualsJsonString($expected, $serialized);
    }

for bootstrapping the IRI Resolver:

    final protected static function iriResolver(): Util\Serializer\IriResolver
    {
        $iriConverter = self::$container->get(Core\Api\IriConverterInterface::class);

        return new Util\Serializer\IriResolver($iriConverter);
    }

and my IRI Resolver itself:

<?php

declare(strict_types=1);

namespace App\Tests\Util\Serializer;

use ApiPlatform\Core\Api\IriConverterInterface;

final class IriResolver
{
    private IriConverterInterface $iriConverter;

    public function __construct(IriConverterInterface $iriConverter)
    {
        $this->iriConverter = $iriConverter;
    }

    public function iriFor(?object $item): ?string
    {
        if (null === $item) {
            return null;
        }

        return $this->iriConverter->getIriFromItem($item);
    }

    /**
     * @return string[]
     */
    public function irisFor(object ...$items): array
    {
        return array_map(function (object $item): string {
            return $this->iriConverter->getIriFromItem($item);
        }, $items);
    }
}

cc @localheinz

@soyuka
Copy link
Member Author

soyuka commented Feb 23, 2021

 *     itemOperations={
 *         "get": {
 *             "method": "GET",
-*             "path": "/benutzer/{id}.{_format}",
-*             "requirements": {"id": "%uuid_format%"}, 
+*             "path": "/benutzer/{objectGuid}.{_format}",
+*             "requirements": {"objectGuid": "%uuid_format%"},
 *             "normalization_context": {"groups": {"benutzer_item_output"}},
 *         },
 *     },

Is working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants