Closed akozhemiakin closed 8 years ago
@soyuka How? The delete action in standard controller dispatches only one event ("pre-delete").
public function deleteAction(Request $request, $id)
{
$resource = $this->getResource($request);
$object = $this->findOrThrowNotFound($resource, $id);
$this->get('event_dispatcher')->dispatch(Events::PRE_DELETE, new DataEvent($resource, $object));
return new Response(null, 204);
}
The only way to intercept this action is to register pre-delete event handler with higher priority and to throw exception. But I do not want to throw some random exception, I want to return some response containing the information about error. Like, for example, it is done in the post or put action during the validation phase:
public function cpostAction(Request $request)
{
...
return $this->getErrorResponse($violations);
}
protected function getErrorResponse(ConstraintViolationListInterface $violations)
{
return new Response($this->get('serializer')->normalize($violations, 'hydra-error'), 400);
}
Without thinking to much I think that you could inject the @request_stack
in your event listener to handle the request yourself, or maybe use another listener (doctrine, kernel).
Actually in master
, the event system documented for 1.x has been removed (commit).
You may take a look at the ValidationViewListener
. Exception throwed in it are handled through listeners and normalized with Hydra to return informations with a correct HTTP code. Those are not random exceptions :).
Massive commit xD I should dig into it before I can say anything about it.
As for ValidationViewListener, I believe (and onKernelView method description proves it) that it is executed after controller execution. So the object will be already deleted at that time. Am I missing something?
And yes, of course I can use kernel listeners, and doctrine listeners and many other things to achieve what I want. But, I believe, that if APIBundle is responsible for validating data during POST and PUT requests than it should give developer the possibility to validate any request to the API using some standard approach.
I'm using the master branch so I can't really give you the best way with 1.0.
From what I see in ValidationListener it's only handling POST and PUT requests, and I would do the same if I wanted to pre-validate a DELETE request. POST and PUT are handling validation groups, but I'm not sure they are handled with DELETE requests.
If I'm not mistaken, in the master branch, controller (for example DeleteItemAction) returns the element to be deleted, and it's then removed with doctrine in the ManagerViewListener.
Sorry about the amount of informations, I try to help the best I can (I'm a symfony newbie and still learning ;)).
Oh, I thought you show me proposed commit from some pull request, my mistake. Actually I'm working with a master branch too.
From what I see in ValidationListener it's only handling POST and PUT requests, and I would do the same if I wanted to pre-validate a DELETE request. POST and PUT are handling validation groups, but I'm not sure they are handled with DELETE requests.
ValidationListener responds to the Symfony kernel.view event. This event fires AFTER the controller action execution and is intended to handle custom (other than standard symfony Response) response objects. So it has nothing to do with pre-validation.
If I'm not mistaken, in the master branch, controller (for example DeleteItemAction) returns the element to be deleted, and it's then removed with doctrine in the ManagerViewListener.
In 1.x Controller does not behave this way. After loading the resource it just fires PRE_DELETE event and Dunglas\ApiBundle\Doctrine\EventSubscriber subscribed to it performs the "dirty job". As for new Action based mechanics in master I'm going to look into it.
Ah, no problem. Appreciate for your help anyway)
Should be easier with the new event system of v2.
Should be easier with the new event system of v2.
I'm looking at the doc and I don't see any reference.
Could we use this to prevent deletion? https://api-platform.com/docs/core/validation how?
If not, how should it be prepared?
The only way I found is like this:
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => [
['setup', EventPriorities::PRE_WRITE],
['checkForDeletion', EventPriorities::PRE_WRITE],
['letsEncryptInit', EventPriorities::POST_WRITE],
],
];
}
public function checkForDeletion(GetResponseForControllerResultEvent $event)
{
if (!$event->getRequest()->isMethod('DELETE')) {
return;
}
throw new AccessDeniedException('NOPE!');
}
But nothing with the validation system. Looks ok because validation is for creation/edition AFAIK.
Is it the best way to do?
Thanks.
Indeed, you could use an event listener like you did. :) Or you could handle it at the persistence layer (e.g. Doctrine ORM).
If anyone wondering how to validate on DELETE method:
<?php
declare(strict_types=1);
namespace App\EventSubscriber;
use ApiPlatform\Core\EventListener\EventPriorities;
use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ViewEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use ApiPlatform\Core\Validator\ValidatorInterface;
final class ValidateBeforeDeleteSubscriber implements EventSubscriberInterface
{
public const DEFAULT_VALIDATION_GROUPS = [
'item:delete'
];
private $validationGroups = null;
private $validator;
public function __construct(ValidatorInterface $validator, array $validationGroups = null)
{
$this->validator = $validator;
$this->validationGroups = $validationGroups;
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::VIEW => ['validate', EventPriorities::PRE_WRITE],
];
}
/**
* @throws ValidationException
*/
public function validate(ViewEvent $event): void
{
$entity = $event->getControllerResult();
$request = $event->getRequest();
if (
$entity instanceof Response
|| $request->getMethod() !== 'DELETE'
) {
return;
}
// add your entity validators to proper groups for validation to trigger
$this->validator->validate($entity, ['groups' => $this->validationGroups ?? self::DEFAULT_VALIDATION_GROUPS]);
}
}
In Entity add item:delete group to validator:
<?php
declare(strict_types=1);
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\OrderFilter;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
use App\Validator\Constraints as AppAssert;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
use Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity;
use Gedmo\Timestampable\Traits\TimestampableEntity;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Template
*
* @ApiResource(
* collectionOperations={
* "POST"={"path"="/tree_templates"},
* "GET"={"path"="/tree_templates", "normalization_context"={"groups"={"template.collection.read"}}}
* },
* itemOperations={
* "PATCH"={"path"="/tree_templates/{id}"},
* "GET"={"path"="/tree_templates/{id}"},
* "DELETE"={"path"="/tree_templates/{id}"}
* },
* normalizationContext={"groups"={"template:read"}},
* denormalizationContext={"groups"={"template:write"}},
* attributes={"order"={"id": "desc"}}
* )
* @ApiFilter(OrderFilter::class, properties={"name"})
* @ApiFilter(OrderFilter::class, properties={"id"})
* @ApiFilter(SearchFilter::class, properties={"name": "istart"})
* @ApiFilter(SearchFilter::class, properties={"treeTemplateEntries.name": "istart"})
*
* @AppAssert\TreeTemplateCanChange(groups={"template:write", "item:delete"})
*
* @Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
* @ORM\Entity(repositoryClass="App\Repository\TreeTemplateRepository")
* @ORM\Table(name="tree_template")
*/
class TreeTemplate
{
// ...
}
The current recommendation would be to decorate the DataPersister
(or use a custom one): https://api-platform.com/docs/core/data-persisters/
If you use DataPersisters whats best answer you have to use Api Platform Validator Interface and not from Symfony!
Examble Code from DataPersister
use ApiPlatform\Core\Validator\ValidatorInterface;
// ...
/**
* @param mixed $data
*/
public function remove($data, array $context = []): void
{
/** @var User $data */
$this->validator->validate($data, ['groups' => 'Delete']);
$this->em->remove($data);
$this->em->flush();
}
For example, imagine that we have some system where we have Users and Projects assigned to them and we want to prevent User from being deleted if he still has some projects assigned to him. Am I right that for now there is no way to do this using standard controller?