I'm writing a Controller that accept a JSON representing a user I have to create. I'm trying to keep the controller it as light as possible and for that reason I let FOSRestBundle deserializing the JSON into the proper Entity I want to save:
/**
* #View()
*
* #ApiDoc(
* description="It creates a new user",
* section="Users"
* )
*
* #Post("/users")
* #ParamConverter("user", converter="fos_rest.request_body")
*/
public function postAction(User $user)
{
$res = $this->getHandler()->create($user);
...
and yes, everything works smoothly! Except for a caviat: I would like to encrypt the password, and User's entity does it in its setPassword method. Is there a way to do it automatically? Or should I call User::setPassword by myself in the handler I wrote?
Related
The documentation says: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/events.html#postupdate-postremove-postpersist
The three post events are called inside EntityManager#flush(). Changes in here are not relevant to the persistence in the database, but you can use these events to alter non-persistable items, like non-mapped fields, logging or even associated classes that are not directly mapped by Doctrine.
So let's imagine I have an Image entity:
<?php
/**
* #ORM\Entity
*/
class Image
{
/**
* #var int|null
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\Column(type="integer")
*/
private ?int $id = null;
/**
* #var string
* #ORM\Column(type="string")
*/
private string $path;
/**
* #var string
* #ORM\Column(type="string")
*/
private string $status = 'RECEIVED';
}
Once I flush my Image entity I want to upload the corresponding file on a FTP server, so I do it in the postPersist event.
But if the upload on FTP fail I want to change the status of my Image to FTP_ERROR.
<?php
public function postPersist(Image $image, LifecycleEventArgs $event)
{
$em = $event->getEntityManager();
try {
$this->someService->uploadToFtp($image);
} catch (Exception $e) {
$image->setStatus('FTP_ERROR');
$em->persist($image);
$em->flush();
}
}
And it works, but as documentation says it's not a good way to do it.
I have seen this post: Flushing in postPersist possible or not? which says:
#iiirxs:
It is ok to call flush() on a PostPersist lifecycle callback in order to change a mapped property of your entity.
So how to do so? #iiirxs and the documentation say 2 different things.
The best way to download something on such event as postPersist is using https://symfony.com/doc/current/components/messenger.html
so, you can create asynchronous task for file uploading and Symfony Messenger will be able to handle errors and retry it automatically on fail, also you will be able to update it, status and etc in separate process and it will not depend on specific doctrine cases like case in your question.
I am trying to implement the LexikJwt package for my new API:
https://github.com/lexik/LexikJWTAuthenticationBundle
I just added my custom command to create a proper JWT token including my default roles for local testing. The next step would be to actually protect my routes. What i can't find however is exactly on how to do that. This is my current controller implementation using annotation routes
/**
* #Route(
* "/search",
* name="search",
* methods={"GET"},
* )
*/
class Search
{
/**
* #param AddressService $addressService
* #param Request $request
* #return JsonApiResponse
* #throws Exception
*/
public function __invoke(AddressService $addressService, Request $request)
{
$address = $addressService->createFromParams($request->query->all());
try {
$addressCollection = $addressService->search($address);
} catch (AddressNotFoundException $e) {
$addressCollection = [];
}
return new JsonApiResponse($addressCollection);
}
}
However the docs do not say anything about annotation routes and only on yml configs on security firewalls. The main thing i need is the token to be verified:
Is the token valid (matching the public key)
Is the token not expired
Does the route match the given roles
For example, i want the code above, which is an address service to match only if the token matches above and the token holds the role or scope: address:search.
Hope someone can help,
Pim
The issue was that the role should start with ROLE_. It is possible to override it though but this was the use case.
I created a Entity with a custom contoller:
// api/src/Entity/UserRegistration.php
namespace App\Entity;
use ...
/**
* UserRegistraion Data
*
* #ApiResource(collectionOperations={},itemOperations={"post"={
* "method"="POST",
* "path"="/register",
* "controller"=CreateUser::class}})
*
*/
class UserRegistration
{
.....
/**
* #var string The E-mail
*
* #Assert\NotBlank
* #Assert\Email(
* message = "The email '{{ value }}' is not a valid email.",
* checkMX = true
* )
*/
public $email;
.....
And a custom Controller:
// api/src/Controller/CreateUser.php
class CreateUser
{
.....
public function __invoke(UserRegistration $data): UserRegistration
{
return $data;
}
}
When I call the controller with wrong data (e.g wrong email-address) I would expect an validation error, but it is not checked.
Is there a way to do this?
Api Platform does the validation on the result of your controller, to make sure your data persisters will receive the right information. Thus you may get invalid data when entering your controller, and need to perform the validation manually if your action needs a valid object.
The most common approaches are either using a Form, which provides among other things validation, or just the Validator as a standalone component. In your case you - since are using ApiPlatform - the latter would be the better choice as you don't need to render a form back to the user, but instead return an error response.
First you will need to inject the Validator into your Controller:
use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class CreateUser
{
private $validator;
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
public function __invoke(UserRegistration $data): UserRegistration
{
$errors = $this->validator->validate($data);
if (count($errors) > 0) {
throw new ValidationException($errors);
}
return $data;
}
}
You can also check how ApiPlatform does it by looking at the ValidateListener. It provides some additional features, e.g. for validation groups, which you don't seem to need at this point, but might be interesting later. ApiPlatform will then use its ValidationExceptionListener to react on the Exception you throw and render it appropriately.
Can anyone help me explaining what I am doing wrong? I try to set an entity in relation to an file. Its about Supplier and Stock.
My Stock entity looks like
/**
* #ORM\ManyToOne(targetEntity="PrClientBundle\Entity\Lieferanten")
* #ORM\JoinColumn(name="liefer_id", referencedColumnName="id")
* #var lieferant
*/
private $lieferant;
I also using getter and setter like following
/**
* Set lieferant
*
* #param \PrClientBundle\Entity\Lieferanten $lieferant
* #return Leadbase
*/
public function setLieferant(\PrClientBundle\Entity\Lieferanten $lieferant = null)
{
$this->lieferant = $lieferant;
return $this;
}
/**
* Get lieferant
*
* #return \PrClientBundle\Entity\Lieferanten
*/
public function getLieferant()
{
return $this->lieferant;
}
When I import new Stockitems like:
$lead->setLieferant($lieferant);
I get the following errormessage which I really don't understand :(
[Doctrine\ORM\ORMInvalidArgumentException]
A new entity was found through the relationship 'PrLeadBundle\Entity\Leadbase#lieferant' that was not configured to cascade persist operations for entity: PrClientBundle\Enti
ty\Lieferanten#000000002a45dae80000000002f826ff. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this
association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'PrClientBundle\Entity\Lieferante
n#__toString()' to get a clue.
It would be very great if you could help me understanding what am I doing wrong.
I'm trying to use #Security annotations for my routes. Like this:
/**
* #return Response
* #Route("/action")
* #Security("has_role('ROLE_USER')")
* #Template()
*/
public function someAction()
{
return array();
}
When the security restriction fires an exception, I get the message Expression "has_role('ROLE_USER')" denied access.
This is not acceptable to be shown to the end user, so I'm trying to find a way to customize the message for annotation.
Simple workaround is to not to use #Secutity annotations and write code like these:
/**
* #return Response
* #Route("/action")
*
* #Template()
*/
public function someAction()
{
if (!$this->get('security.context')->isGranted('ROLE_USER')) {
throw new AccessDeniedException('You have to be logged in in order to use this feature');
}
return array();
}
But this is less convenient and less readable.
Is it possible to write custom message to #Security annotations?
As soon as I realized that this is not possible, I have made a pull request to the Sensio FrameworkExtra Bundle to make this possible.
This PR allows to customize displayed message by specifying the message parameter like
#Security("has_role('ROLE_USER')",message="You have to be logged in")