Skip to content

Instantly share code, notes, and snippets.

@Richard87
Last active January 3, 2023 22:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Richard87/ae8ac371c908fc650a5d3c1f35d540e5 to your computer and use it in GitHub Desktop.
Save Richard87/ae8ac371c908fc650a5d3c1f35d540e5 to your computer and use it in GitHub Desktop.
API Platform Expression Action

A idea for a Expression action that have the ability to call a method directly on the resource.

Limitations:

  • Superficial implementation, requires id in the url, and no composite identifiers
  • No idea if this is a good idea or not :D might save some time when you need custom actions but don't feel like adding more classes than nessecary
  • strugling to provide defaults (null) to Action variables

How to use it:

new Action(
    name: 'addPartnershipArea',
    input: CreateAreaDto::class,
    output: Partnership::class,
    expression: 'object.createArea(input.county)',
    uriTemplate: '/partnerships/{id}/create_area',
    uriVariables: [],
    security: "is_granted('ROLE_SKIL')",
    securityPostDenormalize: null,
),

(I wish Output, uriVariables, security and securityPostDenormalize could be null)

The Action:

<?php

namespace App\Service\ApiPlatform;

use ApiPlatform\Metadata\HttpOperation;
use App\Controller\ActionController;

#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class Action extends HttpOperation
{
    private string $expression;

    public function __construct(
        string $name,
        string $input,
        string|false|null $output,
        string $expression,
        string $uriTemplate,
        string|array|null $uriVariables,
        string $security,
        string|null $securityPostDenormalize,
    ) {
        parent::__construct(
            method: 'POST',
            uriTemplate: $uriTemplate,
            uriVariables: $uriVariables,
            controller: ActionController::class,
            security: $security,
            securityPostDenormalize: $securityPostDenormalize,
            input: $input,
            output: $output,
            name: $name
        );

        $this->expression = $expression;
    }

    public function getExpression(): string
    {
        return $this->expression;
    }
}

The magic:

<?php

namespace App\Controller;

use ApiPlatform\State\CallableProvider;
use App\Service\ApiPlatform\Action;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\HttpFoundation\Request;

class ActionController
{
    private CallableProvider $provider;

    public function __construct(
        #[AutoWire(service: 'api_platform.state_provider')] CallableProvider $provider,
        private EntityManagerInterface $em,
    ) {
        $this->provider = $provider;
    }

    public function __invoke(Request $request, $data)
    {
        $attributes = $request->attributes;
        /** @var Action $operation */
        $operation = $attributes->get('_api_operation');
        $className = $attributes->get('_api_resource_class');
        $id = $attributes->get('id');


        if (!$id || !$className || !$operation) {
            throw new \RuntimeException('Invalid action, missing operation, className or id');
        }

        $repo = $this->em->getRepository($className);
        $object = $repo->find($id);
        $expressionLanguage = new ExpressionLanguage();

        return $expressionLanguage->evaluate($operation->getExpression(), ['object' => $object, 'input' => $data]);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment