api-platform / core

The server component of API Platform: hypermedia and GraphQL APIs in minutes
https://api-platform.com
MIT License
2.45k stars 878 forks source link

how to specify the IRI by any operation which I want? #2725

Closed lichnow closed 3 years ago

lichnow commented 5 years ago

From document I see "By default, API Platform uses the first GET operation defined in itemOperations to generate the IRI of an item and the first GET operation defined in collectionOperations to generate the IRI of a collection.". But now I want to uses which operation that not is the first operation.like

itemOperations:
            me:
                method: 'GET'
                path: '/users/me'
#                controller: App\Entity\User\GetMe
                access_control: 'is_granted("user.view-own")'
                access_control_message: 'Sorry, only the user himself or super admin can view his own detail info.'
            get:
                access_control: 'is_granted("user.view",object)'
                access_control_message: 'Sorry, you must login first.'

and change

{
  "@context": "\/api\/contexts\/User",
  "@id": "\/api\/users\/me?id=2",
  "@type": "User",

to

{
  "@context": "\/api\/contexts\/User",
  "@id": "\/api\/users\/2",
  "@type": "User",
 }

how to specify the IRI by any operation which I want?

soyuka commented 5 years ago

Reorder your yaml array?

In fact I'm not sure that it'd work, adding iri: '/api/users/{id} should work but I'm not sure {id} will be interpolated. @teohhanhui may you look into this case?

lichnow commented 5 years ago

@soyuka I think I have solved this problem,follow this code

<?php
namespace App\Component\ApiPlatform;

use ApiPlatform\Core\Api\IdentifiersExtractorInterface;
use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\Api\OperationType;
use ApiPlatform\Core\Api\UrlGeneratorInterface;
use ApiPlatform\Core\Bridge\Symfony\Routing\RouteNameResolverInterface;
use ApiPlatform\Core\DataProvider\OperationDataProviderTrait;
use ApiPlatform\Core\Exception\InvalidArgumentException;
use ApiPlatform\Core\Exception\RuntimeException;
use ApiPlatform\Core\Util\ClassInfoTrait;
use App\Entity\User;
use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface;
use Symfony\Component\Routing\RouterInterface;

class IriConverterDecorator implements IriConverterInterface
{
    use ClassInfoTrait;
    use OperationDataProviderTrait;

    private $decorated;
    private $routeNameResolver;
    private $router;
    private $identifiersExtractor;

    public function __construct(
        IriConverterInterface $decorated,
        RouteNameResolverInterface $routeNameResolver,
        RouterInterface $router,
        IdentifiersExtractorInterface $identifiersExtractor = null
    )
    {
        $this->decorated = $decorated;
        $this->routeNameResolver = $routeNameResolver;
        $this->router = $router;
        $this->identifiersExtractor = $identifiersExtractor;
    }

    public function getItemFromIri(string $iri, array $context = [])
    {
        return $this->decorated->getItemFromIri($iri, $context);
    }

    public function getIriFromItem($item, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
    {
        $resourceClass = $this->getObjectClass($item);
        $routeName = $this->routeNameResolver->getRouteName($resourceClass, OperationType::ITEM);

        if($resourceClass === User::class){
            $routeName = 'api_users_get_item';
        }

        try {
            $identifiers = $this->generateIdentifiersUrl($this->identifiersExtractor->getIdentifiersFromItem($item), $resourceClass);

            return $this->router->generate($routeName, ['id' => implode(';', $identifiers)], $referenceType);
        } catch (RuntimeException $e) {
            throw new InvalidArgumentException(sprintf(
                'Unable to generate an IRI for the item of type "%s"',
                $resourceClass
            ), $e->getCode(), $e);
        } catch (RoutingExceptionInterface $e) {
            throw new InvalidArgumentException(sprintf(
                'Unable to generate an IRI for the item of type "%s"',
                $resourceClass
            ), $e->getCode(), $e);
        }
    }

    private function generateIdentifiersUrl(array $identifiers, string $resourceClass): array
    {
        if (0 === \count($identifiers)) {
            throw new InvalidArgumentException(sprintf(
                'No identifiers defined for resource of type "%s"',
                $resourceClass
            ));
        }

        if (1 === \count($identifiers)) {
            return [rawurlencode((string) reset($identifiers))];
        }

        foreach ($identifiers as $name => $value) {
            $identifiers[$name] = sprintf('%s=%s', $name, $value);
        }

        return array_values($identifiers);
    }

    public function getIriFromResourceClass(string $resourceClass, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
    {
        return $this->decorated->getItemIriFromResourceClass($resourceClass, $referenceType);
    }

    public function getItemIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
    {
        return $this->decorated->getItemIriFromResourceClass($resourceClass, $identifiers, $referenceType);
    }

    public function getSubresourceIriFromResourceClass(string $resourceClass, array $identifiers, int $referenceType = UrlGeneratorInterface::ABS_PATH): string
    {
        return $this->decorated->getSubresourceIriFromResourceClass($resourceClass, $identifiers, $referenceType);
    }
}

and

    api_platform.iri_converter_decorator:
        class: App\Component\ApiPlatform\IriConverterDecorator
        decorates: api_platform.iri_converter
        arguments:
            - '@api_platform.iri_converter_decorator.inner'
            - '@api_platform.route_name_resolver'
            - '@api_platform.router'
            - '@api_platform.identifiers_extractor.cached'
soyuka commented 5 years ago

Yes this is how I'd have fixed that as a last solution. I think that this is quite demanded, maybe we can improve the DX on the subject.

soyuka commented 3 years ago

Greetings! We appreciate your concern but weren't able to reproduce this issue or it is more of a question. As described in the API Platform contributing guide, we use GitHub issues for bugs and feature requests only.

For support question ("How To", usage advice, or troubleshooting your own code), you have several options:

Feel free reach one of the support channels above. In the meantime we're closing this issue.

xsuchy09 commented 2 years ago

The easiest way how to achieve this is to left get as first item operation and add requirements for id:

itemOperations:
            get:
                access_control: 'is_granted("user.view",object)'
                access_control_message: 'Sorry, you must login first.'
                requirements:
                    id: '^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$' # uuid
                    #id: '\d+' # integer
            me:
                method: 'GET'
                path: '/users/me'
#                controller: App\Entity\User\GetMe
                access_control: 'is_granted("user.view-own")'
                access_control_message: 'Sorry, only the user himself or super admin can view his own detail info.'