I'm using symfony 3.3 and php 7.0 with the FOSUserBundle version 2.1.1 and I just realize that in the User Entity if you just add in the setters parameters the type hint like for example this... the validation will fail.
<?php
namespace AppBundle\Entity;
use ...;
/**
* #ORM\Entity
* #ORM\Table(name="`user`")
*/
class User extends BaseUser
{
/**
* #Assert\NotBlank()
* #var string
* #ORM\Column(type="string", nullable=false)
*/
private $firstName;
/**
* #return string
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* #param string $firstName
*/
public function setFirstName(string $firstName) <- IF YOU ADD THIS STRING AS THE PARAMETER THE VALIDATION FAILS
{
$this->firstName = $firstName;
}
}
So finally the line should be like this next:
public function setFirstName($firstName)
If anyone knows how to add the typehint without giving problems to the validation will be nice the hear news.
This is how Symfony Validator works by default: it first sets the value (null for instance), then the validation is performed, not the other way. Because your method does not accept null values, only strings:
public function setFirstName(string $firstName)
Most probably, you are encountering error Exception: Argument 1 passed to setFirstName() must be of the type string, null given.
To overcome this you either have to set empty data for the corresponding field to '' or detach your entities from the form component. Or you can force method to accept null values:
// php 7
public function setFirstName(string $firstName = null)
{
$this->firstName = (string) $firstName;
}
// >= php7.1
public function setFirstName(?string $firstName)
I urge you to stop mixing entities with forms and validators. Your core domain should be free from such low level concerns (vide SRP from the SOLID). Also by the look of your setters I can tell you are moving towards the antipattern called Anemic Domain Model.
Apparently the FOSUserBundle does not allow the typehint at the moment if you want to use the validation functionality.
Your method setFirstName() in the User class must be compatible with FOSUserBundle/Model/UserInterface->setFirstName($username)
The interface has no typehint, so you can't add the typehint in your method...
Related
I have a ArgumentValueResolverInterface that creates and validates DTOs.
I have also setup a firewall to protect routes and additionally use IsGranted attribute for fine grained access control.
Problem is that the value resolver and validation runs before the security firewall and show validation errors even if the request is unauthenticated.
How can I change the value resolver to run after security is resolved?
Is this even possible?
class RequestDTOValueResolver implements ArgumentValueResolverInterface
{
/**
* RequestDTOValueResolver constructor.
* #param ValidatorInterface $validator
*/
public function __construct(protected ValidatorInterface $validator)
{}
/**
* #inheritDoc
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
return is_subclass_of($argument->getType(), RequestDTOInterface::class);
}
/**
* #inheritDoc
* #throws ValidationException
* #throws Exception
*/
public function resolve(Request $request, ArgumentMetadata $argument): iterable
{
$className = $argument->getType();
/** #var AbstractRequestDTO $dto */
$dto = new $className($request); //$this->parseRequest($request, $argument);
$groups = $dto->getGroups();
$errors = $this->validator->validate($dto, null, !empty($groups) ? $groups : null);
if ($errors->count()) {
throw ValidationException::create($errors, "One or more fields are invalid.");
}
yield $dto;
}
}
According to the official documentation, which is available here (it's not so different across different SF versions) : https://symfony.com/doc/5.2/controller/argument_value_resolver.html
You could probably achieve your goal by setting the proper priority
App\ArgumentResolver\UserValueResolver:
tags:
- { name: controller.argument_value_resolver, priority: 50 }
I would also advice to check in which order each service is being run. Here you can see how it's done by SF:
https://symfony.com/doc/current/introduction/http_fundamentals.html
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.
I'm trying to validate my entity via static callback.
I was able to make it work following the Symfony guide but something isn't clear to me.
public static function validate($object, ExecutionContextInterface $context, $payload)
{
// somehow you have an array of "fake names"
$fakeNames = array(/* ... */);
// check if the name is actually a fake name
if (in_array($object->getFirstName(), $fakeNames)) {
$context->buildViolation('This name sounds totally fake!')
->atPath('firstName')
->addViolation()
;
}
}
It works fine when I populate my $fakeNames array but what if I want to make it "dynamic"? Let's say I want to pick that array from the parameters or from the database or wherever.
How am I supposed to pass stuff (eg. the container or entityManager) to this class from the moment that the constructor doesn't work and it has to be necessarily static?
Of course my approach may be completely wrong but I'm just using the symfony example and few other similar issues found on the internet that I'm trying to adapt to my case.
You can create a Constraint and Validator and register it as service so you can inject entityManager or anything you need, you can read more here:
https://symfony.com/doc/2.8/validation/custom_constraint.html
or if you are on symfony 3.3 it is already a service and you can just typehint it in your constructor:
https://symfony.com/doc/current/validation/custom_constraint.html
This is the solution I was able to find in the end.
It works smoothly and I hope it may be useful for someone else.
I've set the constraint on my validation.yml
User\UserBundle\Entity\Group:
constraints:
- User\UserBundle\Validator\Constraints\Roles\RolesConstraint: ~
Here is my RolesConstraint class
namespace User\UserBundle\Validator\Constraints\Roles;
use Symfony\Component\Validator\Constraint;
class RolesConstraint extends Constraint
{
/** #var string $message */
public $message = 'The role "{{ role }}" is not recognised.';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
and here is my RolesConstraintValidator class
<?php
namespace User\UserBundle\Validator\Constraints\Roles;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class RolesConstraintValidator extends ConstraintValidator
{
/** #var ContainerInterface */
private $containerInterface;
/**
* #param ContainerInterface $containerInterface
*/
public function __construct(ContainerInterface $containerInterface)
{
$this->containerInterface = $containerInterface;
}
/**
* #param \User\UserBundle\Entity\Group $object
* #param Constraint $constraint
*/
public function validate($object, Constraint $constraint)
{
if (!in_array($object->getRole(), $this->containerInterface->getParameter('roles'))) {
$this->context
->buildViolation($constraint->message)
->setParameter('{{ role }}', $object->getRole())
->addViolation();
}
}
}
Essentially, I set up a constraint which, every time a new user user is registered along with the role, that role must be among those set in the parameters. If not, it builds a violation.
I get this exeption when I submit my form:
Found the public method "addRemote", but did not find a public "removeRemote" on class App\CoreBundle\Entity\Scene
The weired think is that the remove method exist ...
But i wrote it myself (When I did php app/console doctrine:generate:entities) doctrine didn't generated it. Did I make something wrong ?
/**
* #var array $remote
*
* #ORM\Column(name="remote", type="array", nullable=true)
*/
private $remote;
/**
* Set remote
*
* #param array $remote
* #return Scene
*/
public function addRemote($value, $key=null) {
if($key!=null){
$this->remote[$key] = $value;
}else{
$this->remote[] = $value;
}
return $this;
}
/**
* Remove remote
*/
public function removeRemote(){
unset($this->remote);
}
I allso tried:
/**
* Remove remote
*/
public function removeRemote($key=null){
if($key!=null && array_key_exists($key, $this->remote)){
unset($this->remote[$key]);
}
unset($this->remote);
return $this;
}
You have bigger problem than this; you are abusing your forms :)
Add.. and Remove... methods should be used for relations, not columns as per your code. Also, both add and remove methods must accept parameter that will be either added or removed.
If you still need an array, than getRemotes() method should return key=>value array. Adder and remover will later get that key, based on what user have picked in choice form type.
Hi i had fully successfully setted my entity onetoMany and ManyToOne i generated setters and getters and in user entity it created this method:
user entity:
/**
* #ORM\OneToMany(targetEntity="TB\RequestsBundle\Entity\Requests", mappedBy="followeeuser")
*/
protected $followees;
requests entity:
/**
* #ORM\ManyToOne(targetEntity="TB\UserBundle\Entity\User", inversedBy="followees")
* #ORM\JoinColumn(name="followee_id", referencedColumnName="id", nullable=false)
*/
protected $followeeuser;
And when i using my own custom queries it works good... but i cant figure out how to use this generated function from symfony:
public function addFollowee(\TB\UserBundle\Entity\User $followee)
{
$this->followees[] = $followee;
}
I dont know what to pass there... i tried first get user object based on id of user from twig... worked good but the error occur:
$user->addFollowee($userRepository->find($target_user_id));
Found entity of type TB\UserBundle\Entity\User on association TB\UserBundle\Entity\User#followees, but expecting TB\RequestsBundle\Entity\Requests
Maybe you should think about what you're trying to before coding it. Grab a pen and a sheet of paper. :)
Tell me if I'm wrong, but here is what I think you're trying to do :
One user can have many "followee".
One "followee" can have one user.
So, a OneToMany relation is ok.
Here is how to write it, from the doc :
Requests.php (btw, you should use Request.php)
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="requests")
**/
private $user;
User.php
/**
* #ORM\OneToMany(targetEntity="Requests", mappedBy="user", cascade={"all"})
**/
private $requests;
public function __construct()
{
$this->requests = new \ArrayCollection();
}
Now you can check if you your relation is ok, and update your schema :
php app/console doctrine:schema:validate
php app/console doctrine:schema:update --force
About getters/setters :
Requests.php
public function getUser()
{
return $this->user;
}
public function setUser(User $user) // Please add a Use statement on top of your document
{
$this->user = $user;
return $this;
}
User.php
public function addRequest(Requests $request)
{
$this->requests->add($request);
return $this;
}
public function removeRequest(Requests $request)
{
$this->requests->removeElement($request);
return $this;
}
// Get requests and set requests (you know how to write those ones)
Now, to set a user to a Request, use
$request->setUser($user);
And to add a Request to a user, use
$user->addRequest($request);