Symfony 5 Can't Inject EntityManagerInterface Into Validator - php

I've just moved to symfony 5 and baffled! I've done this same thing with validators many times with Symfony 4, but now dependency injection of EntityManagerInterface into a custom validator produces this error:
Too few arguments to function
App\Validator\Constraints\UserUsernameConstraintValidator::__construct(),
0 passed in
/var/www/appbaseV4/vendor/symfony/validator/ContainerConstraintValidatorFactory.php
on line 52 and exactly 1 expected
The validator class is as follows:
<?php
namespace App\Validator\Constraints;
use App\Entity\User;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class UserUsernameConstraintValidator extends ConstraintValidator
{
/**
* #var EntityManagerInterface
*/
private $em;
/**
* UserEmailValidator constructor.
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->em = $entityManager;
}
/**
* #param mixed $value
* #param Constraint $constraint
*/
public function validate($value, Constraint $constraint) : void
{
if(!$constraint instanceof UserUsernameConstraint){
throw new UnexpectedTypeException($constraint,UserUsernameConstraint::class);
}
if(null === $value || '' === $value){
return;
}
if($value != $constraint->value){
/** #var UserRepository $repo */
$repo = $this->em->getRepository(User::class);
if($repo->findOneBy(['username'=>$value])){
$this->context->buildViolation($constraint->errorMessage)->setParameter('username',$value)->addViolation();
}
}
}
}
And then using it in the form type as i always do:
$builder
->add('username',TextType::class,[
'attr'=>[
'class'=>'form-control'
],
'required'=>true,
'constraints'=>[
new UserUsernameConstraint(),
new Length([
'min'=>6,
'max'=>20
]),
]
])
What's going on here? Have they changed this in Symfony 5 because it's just not injecting the entity manager like it normally does when i use symfony 4.

It seems that you are not using the default services.yaml configuration, in this case you have to tag your constraint validator with the validator.constraint_validator tag. Check the documentation for Constraint Validators with Dependencies or fix/update your Automatic Service Loading in config/services.yaml.

Related

ManyToMany itemOperations "access_control"

Thats the code from the docu
// https://api-platform.com/docs/core/security/#security
itemOperations={
"get"={"access_control"="is_granted('ROLE_USER') and object.owner == user"}
}
how can i get that realized with many to many, i tried many different expressions but everytime i get a error.
<?php
// api/src/Entity/Book.php
use ApiPlatform\Core\Annotation\ApiResource;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Secured resource.
*
* #ApiResource(
* itemOperations={
* "get"={"access_control"="is_granted('ROLE_USER') and object.users == user"}
* }
* )
* #ORM\Entity
*/
class Book
{
// ...
/**
* #var User The owner
*
* #ORM\ManyToMany(targetEntity="App\Entity\User", mappedBy="book", cascade={"persist"})
*/
public $users;
// ...
}
nYou cant in those cases where the target relation is a collection. In this case, users collection.
For these cases, you should create a subscriber with PRE_SERIALIZE event and throw Access Denied exception there.
You have to do something like this. As you say you have a ManyToMany relation, I guess that you have an intermediate entity between book and user, so you should use that repository for find user <-> book then.
<?php
namespace App\EventSubscriber;
use ApiPlatform\Core\EventListener\EventPriorities;
use App\Entity\User;
use App\Entity\Book;
use App\Repository\UserRepository;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
class ChatMessagePreSerializeSubscriber implements EventSubscriberInterface
{
private $tokenStorage;
private $userRepository;
private $authorizationChecker;
public function __construct(
TokenStorageInterface $tokenStorage,
UserRepository $userRepository,
AuthorizationCheckerInterface $authorizationChecker
) {
$this->tokenStorage = $tokenStorage;
$this->userRepository = $userRepository;
$this->authorizationChecker = $authorizationChecker;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => ['bookPreSerialize', EventPriorities::PRE_SERIALIZE],
];
}
public function bookPreSerialize(GetResponseForControllerResultEvent $event)
{
$book = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
if (!$book instanceof Book || (Request::METHOD_GET !== $method)) {
return;
}
$currentUser = $this->tokenStorage->getToken()->getUser();
if (!$currentUser instanceof User)
return;
$user = $this->userRepository->findOneBy(['id' => $currentUser->getId(), 'book' => $book]);
if (!$user instanceof User)
throw new AccessDeniedHttpException();
}
}
Here is something I did for a resource that is ManytoOne related to intermediate entity Events ManytoOne related to one Organizer, with Users ManyToMany related to Organizers (collection).
I transform the collection to Array and use "in" operator to compare data. For a more sophisticated operation you should look at Doctrine Extension as it's describe in API Platform doc.
#[ApiResource(
operations: [
new GetCollection(),
new Post(),
new Get(security: "object.getEvent().getOrganizer() in user.getOrganizers().toArray()"),
new Patch(),
new Delete()
]
)]

Symfony 4 Doctrine LifecycleEventArgs getEntity() vs getObject()

What is the difference between LifecycleEventArgs::getObject() and LifecycleEventArgs::getEntity()?
namespace App\EventListener;
use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Events;
/**
* Class MyListener
*
* #package App\EventListener
*/
class MyListener implements EventSubscriber
{
/**
* #return array|string[]
*/
public function getSubscribedEvents()
{
return [
Events::postUpdate,
];
}
/**
* #param LifecycleEventArgs $event
*/
public function postUpdate(LifecycleEventArgs $event)
{
$entity = $event->getEntity();
$object = $event->getObject();
$entity === $object; //true...
}
}
So far as I can tell these two methods return the exact same object, ie they point to the same instance of a given Entity.
Is that always the case?
Should one be used over the other or does it not matter?
There is no difference. The getObject() method comes from the parent class of the LifecycleEventArgs class which is provided by the doctrine/persistence package.
The base event class is mainly helpful when you want to build an integration layer for several Doctrine implementations (e.g. ORM and ODM) and in which case you would use getObject().

Dependency Injection not Occurring in Symfony 2.8

I have a controller, ApiBundle\Controller\UserController, and it has the following contents:
<?php
namespace ApiBundle\Controller;
use Doctrine\ORM\EntityManager;
use FOS\RestBundle\Controller\Annotations\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use AppBundle\Entity\User;
use ApiBundle\Form\RegisterType;
use ApiBundle\Utility\ApiUtility;
class UserController extends ApiController
{
/**
* #var EntityManager $em
*/
private $em;
/**
* UserController constructor.
* #param EntityManager $entityManager
*/
function __construct(EntityManager $em)
{
$this->em = $em;
}
/**
* #Route("register")
* #Method({"POST"})
* #return \Symfony\Component\HttpFoundation\Response
* #param Request $request
*/
public function registerUser(Request $request)
{
$data = ApiUtility::getContentAsArray($request);
$user = new User();
$form = $this->createForm(new RegisterType(), $user);
$form->submit($data);
if($form->isValid()) {
$user->setUsername($request->get('email'));
$this->em->persist($user);
$this->em->flush();
}else{
return ApiUtility::returnResponse($form->getErrors());
}
}
}
It extends API Controller, seen here:
<?php
namespace ApiBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
class ApiController extends FOSRestController
{
}
And it has the following services:
services:
api.controller.user:
class: ApiBundle\Controller\UserController
public: false
parent: api.controller
arguments: ["#doctrine.orm.entity_manager"]
api.controller:
class: ApiBundle\Controller\ApiController
calls:
- [setContainer, ['#service_container']]
No matter what I do, I can't get the container to inject. Right now, when I just try to get doctrine into the container through the constructor, I get Type error: Too few arguments to function ApiBundle\Controller\UserController::__construct(), 0 passed in app/cache/dev/classes.php on line 2230 and exactly 1 expected, if I switch it back to $this->container->get('doctrine.orm.entity_manager') or getEntityManager, I get container is null. How can I fix this?

Create a doctrine repository with dependencies (dependency injection) in ZF2

I want to make a repository with hard dependencies. I found this blog post by Jurian Sluisman but he suggests getting the repository from the service manager and injecting it into the service where needed.
It would be much better if I would be able to get my custom repositories with injected dependencies like normally from my EntityManager or ObjectManager instance by using the getRepository method:
$objectManager->getRepository('My\Entity\Class');
How can I use constructor injection in my Repositories and still get them like normally from the ObjectManager directly with the getRepository method?
Doctrine uses a factory class Doctrine\ORM\EntityManagerInterface\DefaultRepositoryFactory for creating repository instances. If no custom factory is set this default factory is created here in the getRepositoryFactory method in the Doctrine\ORM\Configuration class.
By defining a custom repository_factory we can overwrite this default factory class and add custom logic to the factory that will inject the hard dependencies:
To illustrate how you can do this I will show an example where the repository factory class creates repositories that are dependent on a ServiceLocator instance through constructor injection.
1) make a custom factory class that implements the doctrine RepositoryFactory interface
This class looks very similar to the doctrine DefaultRepositoryFactory class.
<?php
namespace My\ORM\Repository;
use Doctrine\Common\Persistence\ObjectRepository;
use Doctrine\ORM\Repository\RepositoryFactory;
use Doctrine\ORM\EntityManagerInterface;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorAwareTrait;
use Zend\ServiceManager\ServiceLocatorInterface;
class CustomRepositoryFactory implements RepositoryFactory, ServiceLocatorAwareInterface
{
use ServiceLocatorAwareTrait;
/**
* #var ObjectRepository[]
*/
private $repositoryList = array();
/**
* #var ServiceLocator
*/
protected $serviceLocator;
/**
* #param ServiceLocatorInterface $serviceLocator
*/
public function __construct(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
/**
* {#inheritdoc}
*/
public function getRepository(EntityManagerInterface $entityManager, $entityName)
{
$repositoryHash = $entityManager->getClassMetadata($entityName)->getName() . spl_object_hash($entityManager);
if (isset($this->repositoryList[$repositoryHash])) {
return $this->repositoryList[$repositoryHash];
}
return $this->repositoryList[$repositoryHash] = $this->createRepository($entityManager, $entityName);
}
/**
* #param EntityManagerInterface $entityManager The EntityManager instance.
* #param string $entityName The name of the entity.
* #return ObjectRepository
*/
private function createRepository(EntityManagerInterface $entityManager, $entityName)
{
/* #var $metadata \Doctrine\ORM\Mapping\ClassMetadata */
$metadata = $entityManager->getClassMetadata($entityName);
$repositoryClassName = $metadata->customRepositoryClassName
?: $entityManager->getConfiguration()->getDefaultRepositoryClassName();
// Constructor injection, I check with subclass of but it is just an example
if(is_subclass_of($repositoryClassName, ServiceLocatorAwareInterface::class)){
$serviceLocator = $this->getServiceLocator()
$repository = new $repositoryClassName($entityManager, $metadata, $serviceLocator);
}else{
$repository = new $repositoryClassName($entityManager, $metadata);
}
return $repository;
}
}
2) Create a factory for the repository factory
<?php
namespace My\ORM\Repository\Factory;
use My\ORM\Repository\CustomRepositoryFactory;
use Zend\Cache\Storage\StorageInterface;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class CustomRepositoryFactoryFactory implements FactoryInterface
{
/**
* #param ServiceLocatorInterface $serviceLocator
* #return StorageInterface
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
return new CustomRepositoryFactory($serviceLocator);
}
}
3) register the factory for the repository factory in the service_manager config
'service_manager' => array(
'factories' => array(
'My\ORM\Repository\CustomRepositoryFactory' => 'My\ORM\Repository\Factory\CustomRepositoryFactoryFactory'
)
)
4) register the repository factory in the doctrine config
'doctrine' => array(
'configuration' => array(
'orm_default' => array(
'repository_factory' => 'My\ORM\Repository\CustomRepositoryFactory'
)
)
)

Symfony2 Form Validator - Comparing old and new values before flush

I was wondering if there is a way to compare old and new values in a validator within an entity prior to a flush.
I have a Server entity which renders to a form fine. The entity has a relationship to status (N->1) which, when the status is changed from Unracked to Racked, needs to check for SSH and FTP access to the server. If access is not achieved, the validator should fail.
I have mapped a validator callback to the method isServerValid() within the Server entity as described here
http://symfony.com/doc/current/reference/constraints/Callback.html. I can obviously access the 'new' values via $this->status, but how can I get the original value?
In pseudo code, something like this:
public function isAuthorValid(ExecutionContextInterface $context)
{
$original = ... ; // get old values
if( $this->status !== $original->status && $this->status === 'Racked' && $original->status === 'Unracked' )
{
// check ftp and ssh connection
// $context->addViolationAt('status', 'Unable to connect etc etc');
}
}
Thanks in advance!
A complete example for Symfony 2.5 (http://symfony.com/doc/current/cookbook/validation/custom_constraint.html)
In this example, the new value for the field "integerField" of the entity "NoDecreasingInteger" must be higher of the stored value.
Creating the constraint:
// src/Acme/AcmeBundle/Validator/Constraints/IncrementOnly.php;
<?php
namespace Acme\AcmeBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class IncrementOnly extends Constraint
{
public $message = 'The new value %new% is least than the old %old%';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function validatedBy()
{
return 'increment_only';
}
}
Creating the constraint validator:
// src/Acme/AcmeBundle/Validator/Constraints/IncrementOnlyValidator.php
<?php
namespace Acme\AcmeBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\ORM\EntityManager;
class IncrementOnlyValidator extends ConstraintValidator
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function validate($object, Constraint $constraint)
{
$new_value = $object->getIntegerField();
$old_data = $this->em
->getUnitOfWork()
->getOriginalEntityData($object);
// $old_data is empty if we create a new NoDecreasingInteger object.
if (is_array($old_data) and !empty($old_data))
{
$old_value = $old_data['integerField'];
if ($new_value < $old_value)
{
$this->context->buildViolation($constraint->message)
->setParameter("%new%", $new_value)
->setParameter('%old%', $old_value)
->addViolation();
}
}
}
}
Binding the validator to entity:
// src/Acme/AcmeBundle/Resources/config/validator.yml
Acme\AcmeBundle\Entity\NoDecreasingInteger:
constraints:
- Acme\AcmeBundle\Validator\Constraints\IncrementOnly: ~
Injecting the EntityManager to IncrementOnlyValidator:
// src/Acme/AcmeBundle/Resources/config/services.yml
services:
validator.increment_only:
class: Acme\AcmeBundle\Validator\Constraints\IncrementOnlyValidator
arguments: ["#doctrine.orm.entity_manager"]
tags:
- { name: validator.constraint_validator, alias: increment_only }
Accessing the EntityManager inside a custom validator in symfony2
you could check for the previous value inside your controller action ... but that would not really be a clean solution!
normal form-validation will only access the data bound to the form ... no "previous" data accessible by default.
The callback constraint you're trying to use does not have access to the container or any other service ... therefore you cant easily access the entity-manager (or whatever previous-data provider) to check for the previous value.
What you need is a custom validator on class level. class-level is needed because you need to access the whole object not only a single value if you want to fetch the entity.
The validator itself might look like this:
namespace Vendor\YourBundle\Validation\Constraints;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class StatusValidator extends ConstraintValidator
{
protected $container;
public function __construct(ContainerInterface $container)
{
$this->container = $container;
}
public function validate($status, Constraint $constraint)
{
$em = $this->container->get('doctrine')->getEntityManager('default');
$previousStatus = $em->getRepository('YourBundle:Status')->findOneBy(array('id' => $status->getId()));
// ... do something with the previous status here
if ( $previousStatus->getValue() != $status->getValue() ) {
$this->context->addViolationAt('whatever', $constraint->message, array(), null);
}
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function validatedBy()
{
return 'previous_value';
}
}
... afterwards register the validator as a service and tag it as validator
services:
validator.previous_value:
class: Vendor\YourBundle\Validation\Constraints\StatusValidator
# example! better inject only the services you need ...
# i.e. ... #doctrine.orm.entity_manager
arguments: [ #service_container ]
tags:
- { name: validator.constraint_validator, alias: previous_value }
finally use the constraint for your status entity ( i.e. using annotations )
use Vendor\YourBundle\Validation\Constraints as MyValidation;
/**
* #MyValidation\StatusValidator
*/
class Status
{
For the record, here is the way to do it with Symfony5.
First, you need to inject your EntityManagerInterface service in the constructor of your validator.
Then, use it to retrieve the original entity.
/** #var EntityManagerInterface */
private $entityManager;
/**
* MyValidator constructor.
* #param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #param string $value
* #param Constraint $constraint
*/
public function validate($value, Constraint $constraint)
{
$originalEntity = $this->entityManager
->getUnitOfWork()
->getOriginalEntityData($this->context->getObject());
// ...
}
Previous answers are perfectly valid, and may fit your use case.
For "simple" use case, it may fill heavy though.
In the case of an entity editable through (only) a form, you can simply add the constraint on the FormBuilder:
<?php
namespace AppBundle\Form\Type;
// ...
use Symfony\Component\Validator\Constraints\GreaterThanOrEqual;
/**
* Class MyFormType
*/
class MyFormType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fooField', IntegerType::class, [
'constraints' => [
new GreaterThanOrEqual(['value' => $builder->getData()->getFooField()])
]
])
;
}
}
This is valid for any Symfony 2+ version.

Categories