-
-
Notifications
You must be signed in to change notification settings - Fork 934
Closed
Labels
Description
Description
In this example we wouldnt need a basic class to get the item object, the input dto, and call the function, we could define it directly :)
Cons:
- No autocomplet/typehints in the expression
- a litte more complex API-Platform
- A new distinction,
item
for the API Resource, andinput
for the DTO
Pros:
- Slightly improved DX
- Rapid application development improvements
Example
An Action could look like this:
new Action(
input: CreateAreaDto::class,
expression: 'item.createArea(input.county)',
uriTemplate: '/partnerships/{id}/create_area',
security: "is_granted('ROLE_ADMIN')",
),
A proof of concept look like this:
<?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;
}
}
And the controller:
<?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(), ['item' => $object, 'input' => $data]);
}
}
The current approach have a few limitations:
- I struggled to create optional arguments for the action (some strange error :O ) so currently all specified are required
- Requires
id
the the uriTemplate, no support for other types of identifiers (hardcoded)
I really like the separation of item
and input
, the current solution with object
, data
and previous_data
is confusing when you need to deal with it...
I would love to solve these issues with tighter integration in API-Platform Core :)