Symfony validator with dependencies - php

I want to validate an object with constraint annotations, and use dependencies (entityManager) at the validator.
The validator does not work, if it has dependencies (eg. entityManager) in the constructor.
I followed the docs, but it does not work:
https://symfony.com/doc/current/validation/custom_constraint.html#constraint-validators-with-dependencies
"ClassNotFoundException
Attempted to load class "validator_question_exists" from the global namespace.
Did you forget a "use" statement?"
I try to validate the 'Question' object like this (maybe here is the problem):
$validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->getValidator()
;
$question = new Question();
$errors = $validator->validate($question);
Question (the object to validate)
/** #App\Validator\Constraint\Question\QuestionExists() */
class QuestionReadInput{
....
}
services.yaml
services:
validator.unique.question_exists:
class: App\Validator\Constraint\Question\QuestionExistsValidator
tags:
- { name: validator.constraint_validator, alias: validator_question_exists}
Constraint
namespace App\Validator\Constraint\Question;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class QuestionExists extends Constraint
{
public $message;
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function validatedBy()
{
//if i delete this function, symfony cant autowire the entitymanager to the validator
//this throws an error, wants to make a new validator_question_exists(), which not exists, because its a service alias, the docs said it should be okay
return 'validator_question_exists';
}
}
Validator
class QuestionExistsValidator extends ConstraintValidator
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function validate($value, Constraint $constraint)
{
die('I dont see this message...');
}
debug:container
Information for Service "validator.unique.question_exists"
---------------- -------------------------------------------------------------------
Option Value
---------------- -------------------------------------------------------------------
Service ID validator.unique.question_exists
Class App\Validator\Constraint\Question\QuestionExistsValidator
Tags validator.constraint_validator (alias: validator_question_exists)
validator.constraint_validator
Public no
Synthetic no
Lazy no
Shared yes
Abstract no
Autowired yes
Autoconfigured yes

If you are declaring the service yourself, then you should also be adding it's arguments:
services:
validator.unique.question_exists:
class: App\Validator\Constraint\Question\QuestionExistsValidator
arguments: ['#doctrine.orm.entity_manager']
tags:
- { name: validator.constraint_validator }
Other than that it should work without any tag aliases or validatedBy method.
Sidenote: for quite a while now in Symfony it's recommended to name (id) your services by the class names, that way auto-wiring could handle your service, and you wouldn't need the class parameter, i.e.:
services:
App\Validator\Constraint\Question\QuestionExistsValidator:
arguments: ['#doctrine.orm.entity_manager']
tags:
- { name: validator.constraint_validator }

I just used the default symfony Validator, as #xabbuh mentioned, and it worked.
__construct(ValidatorInterface $validator){
$question = new Question();
$errors = $validator->validate($question);
}
I should have not create my own validator with Validaton::createValidatorBuilder.
It does not matter if you use this config:
App\Validator\Constraint\Question\QuestionExistsValidator:
tags: ['validator.constraint_validator']
with no validatedBy()
or that:
validator.unique.question_exists:
class: App\Validator\Constraint\Question\QuestionExistsValidator
tags:
- { name: validator.constraint_validator, alias: validator_question_exists}
with:
public function validatedBy() {
return 'validator_question_exists';
}

Related

Symfony Custom Validator not loading Dependency

Error:
...ThemeValidator::__construct() must be of the type array, null given...
For some reason the Service is not being called, but the Class is being loaded directly.
validation.yml
theme:
- NotBlank: ~
- DashboardHub\Bundle\AppBundle\Validator\Constraints\ThemeValidator: ~
Validator Class
<?php
namespace DashboardHub\Bundle\AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class ThemeValidator extends ConstraintValidator
{
protected $config;
public function __construct(array $config)
{
$this->config = $config;
}
public function validatedBy()
{
return 'theme.validator';
}
public function validate($value, Constraint $constraint)
{
var_dump($this->config); exit;
}
// ...
service.yml
dashboardhub_app_main.validator.constraints.theme:
class: DashboardHub\Bundle\AppBundle\Validator\Constraints\ThemeValidator
arguments: ["%dashboard_hub_app%"]
tags:
- { name: validator.constraint_validator, alias: theme.validator }
Edit
parameters:
dashboard_hub_app:
themes:
Github: DashboardHubAppBundle:Template:Github.html.twig
GithubTravis: DashboardHubAppBundle:Template:GithubTravis.html.twig
Edit2
It works find when used in the Form Service
dashboardhub_app_main.form.type.dashboard:
class: DashboardHub\Bundle\AppBundle\Form\DashboardType
arguments: ["%dashboard_hub_app%"]
tags:
- { name: form.type, alias: dashboard }
parameters:
dashboard_hub_app: -- !!!!! IS NULL yes
funny :)
parameters follow one by one, so you need to be careful, paramenters && themes - are different.
to inject an array you need provide a value for this paramenter, but i guess you need to inject this one
themes:
Github: DashboardHubAppBundle:Template:Github.html.twig
GithubTravis: DashboardHubAppBundle:Template:GithubTravis.html.twig
arguments: ["%themes%"]
and result will be an array inside your Validator class
array (size=2)
'Github' => string 'DashboardHubAppBundle:Template:Github.html.twig' (length=47)
'GithubTravis' => string 'DashboardHubAppBundle:Template:GithubTravis.html.twig' (length=53)

UnexpectedTypeException when trying to create a custom validation constraint

I'm trying to create a custom validation constraint, this is the relevant code:
ValidCoupon.php
<?php
namespace Bcg\UtilsBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* #Annotation
*/
class ValidCoupon extends Constraint
{
public function validatedBy()
{
return 'valid_coupon';
}
public $message = 'The coupon is not valid.';
}
class ValidCouponValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
var_dump($value);
if (true) {
$this->context->addViolation(
$constraint->message,
array()
);
}
}
}
I call the service in the config.yml like this:
services:
validator.unique.valid_coupon:
class: Bcg\UtilsBundle\Validator\Constraints\ValidCoupon
tags:
- { name: validator.constraint_validator, alias: valid_coupon }
The validation.yml looks like this:
Bcg\UtilsBundle\Entity\Order:
properties:
coupon:
- Bcg\UtilsBundle\Validator\Constraints\ValidCoupon: ~
And the error I get is the following:
Expected argument of type
"Symfony\Component\Validator\ConstraintValidatorInterface",
"Bcg\UtilsBundle\Validator\Constraints\ValidCoupon" given 500 Internal
Server Error - UnexpectedTypeException
Full stack trace here.
I'm pretty stuck, it doesn't seem to find ValidCouponValidator I don't really know how to continue from here, I know that the public function validateBy() is executed, so it should be correctly overridden but it doesn't seem so...
Seems like you have a type in your validator service configuration :
You declare your ValidCoupon class as a validator instead of your ValidCouponValidator (which indeed implements the ConstraintValidatorInterface as the error complains about).
Try this:
services:
validator.unique.valid_coupon:
class: Bcg\UtilsBundle\Validator\Constraints\ValidCouponValidator
tags:
- { name: validator.constraint_validator, alias: valid_coupon }

Symfony2 - Custom validator and dependancy injection

I am trying to use dependancy injection for a custom validator, in order to be able to use the entityManager.
I followed the Symfony Example: Dependency Injection, but I am allways getting this error message:
FatalErrorException: Error: Class 'isdoi' not found in
/home/milos/workspace/merrin3/vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Validator/ConstraintValidatorFactory.php
line 68
Here are my classes:
1. The IsDOI class:
<?php
namespace Merrin\MainBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class IsDOI extends Constraint
{
public $message_publisher_DOI = 'The Publisher DOI abbreviation does not correspond to the DOI you filled in !';
public $message_journal_DOI = 'No journal found with the DOI you filled in !';
public $journal;
public $doiAbbreviation;
public function validatedBy() {
return "isdoi";
}
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
2. The IsDOIValidator class:
<?php
namespace Merrin\MainBundle\Validator\Constraints;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class IsDOIValidator extends ConstraintValidator
{
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function validate($value, Constraint $constraint)
{
$em_mdpipub = $this->entityManager('mdpipub');
//Do some tests here...
}
}
3. Service:
merrin.main.validator.isdoi:
class: Merrin\MainBundle\Validator\Constraints\IsDOIValidator
arguments:
entityManager: "#doctrine.orm.entity_manager"
Where am I wrong? Thank you for your help.
You have wrong service file, when You add tags and alias you could use "isdoi" name
merrin.main.validator.isdoi:
class: Merrin\MainBundle\Validator\Constraints\IsDOIValidator
arguments:
entityManager: "#doctrine.orm.entity_manager"
tags:
- { name: validator.constraint_validator, alias: isdoi }
You're telling Symfony2 that the validator class for your constraint is isdoi (validateBy method). However, your validator is IsDOIValidator.
You must use :
public function validateBy()
{
return "IsDOIValidator";
}
However, if your Constraint class name is IsDOI, Symfony will automatically look for IsDOIValidator as a ConstraintValidator. The default behavior for validateBy is to append "Validator" to the constraint name, and look for the class with this name. So if you do not overload validateBy, Symfony2 will automatically search for IsDOIValidator.

How to get Doctrine to work inside a helper function on Symfony2

I need to get doctrine working inside my helper, im trying to use like i normaly do in a controller:
$giftRepository = $this->getDoctrine( )->getRepository( 'DonePunctisBundle:Gift' );
But this gave me:
FATAL ERROR: CALL TO UNDEFINED METHOD
DONE\PUNCTISBUNDLE\HELPER\UTILITYHELPER::GETDOCTRINE() IN
/VAR/WWW/VHOSTS/PUNCTIS.COM/HTTPDOCS/SRC/DONE/PUNCTISBUNDLE/HELPER/UTILITYHELPER.PH
What Im missing here?
EDIT:
services file
services:
templating.helper.utility:
class: Done\PunctisBundle\Helper\UtilityHelper
arguments: [#service_container]
tags:
- { name: templating.helper, alias: utility }
Firts lines of helper file
<?php
namespace Done\PunctisBundle\Helper;
use Symfony\Component\Templating\Helper\Helper;
use Symfony\Component\Templating\EngineInterface;
class UtilityHelper extends Helper {
/*
* Dependency injection
*/
private $container;
public function __construct( $container )
{
$this->container = $container;
}
The problem here is that your Helper class is not container-aware; that is, it has no idea about all the services Symfony has loaded (monolog, twig, ...and doctrine).
You fix this by passing "doctrine" to it. This is called Dependency Injection, and is one of the core things that makes Symfony awesome. Here's how it works:
First, give your Helper class a place for the Doctrine service to live, and require it in the Helper's constructor:
class UtilityHelper
{
private $doctrine;
public function __construct($doctrine)
{
$this->doctrine = $doctrine;
}
public function doSomething()
{
// Do something here
}
}
Then, you use services.yml to define how Symfony should construct that instance of Helper:
services:
helper:
class: Done\PunctisBundle\Helper\UtilityHelper
arguments: [#doctrine]
In this case, #doctrine is a placeholder that means "insert the Doctrine service here".
So now, in your Controller, or in anything else that is container-aware, you can get access to Doctrine through the Helper class like this:
class SomeController()
{
public function someAction()
{
$this->get("helper")->doctrine->getRepository(...);
}
}
EDIT
After looking at your edit, it appears that you're injecting the entire service container into the Helper class. That's not a best practice -- you should only inject what you need. However, you can still do it:
services.yml
services:
helper:
class: Done\PunctisBundle\Helper\UtilityHelper
arguments: [#service_container]
UtilityHelper.php
class UtilityHelper
{
private $container;
public function __construct($container)
{
$this->container = $container;
}
public function doSomething()
{
// This won't work, because UtilityHelper doesn't have a getDoctrine() method:
// $this->getDoctrine()->getRepository(...)
// Instead, think about what you have access to...
$container = $this->container;
// Now, you need to get Doctrine
// This won't work... getDoctrine() is a shortcut method, available only in a Controller
// $container->getDoctrine()->getRepository(...)
$container->get("doctrine")->getRepository(...)
}
}
I've included a few comments there that highlight some common pitfalls. Hope this helps.
In Helper, Services etc you cannot use it like in actions.
You need to pass it like argument to youre Helper via service description in conf file(services.yml or *.xml).
Example:
services:
%service_name%:
class: %path_to_youre_helper_class%
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: %name% }
And dont forget catch it in __construct of youre Helper.
Example:
use Doctrine\ORM\EntityManager;
....
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
You can use it like:
public function myMethod()
{
$repo = $this->em->getRepository('DonePunctisBundle:Gift');
}

How to inject a repository into a service in Symfony?

I need to inject two objects into ImageService. One of them is an instance of Repository/ImageRepository, which I get like this:
$image_repository = $container->get('doctrine.odm.mongodb')
->getRepository('MycompanyMainBundle:Image');
So how do I declare that in my services.yml? Here is the service:
namespace Mycompany\MainBundle\Service\Image;
use Doctrine\ODM\MongoDB\DocumentRepository;
class ImageManager {
private $manipulator;
private $repository;
public function __construct(ImageManipulatorInterface $manipulator, DocumentRepository $repository) {
$this->manipulator = $manipulator;
$this->repository = $repository;
}
public function findAll() {
return $this->repository->findAll();
}
public function createThumbnail(ImageInterface $image) {
return $this->manipulator->resize($image->source(), 300, 200);
}
}
Here is a cleaned up solution for those coming from Google like me:
Update: here is the Symfony 2.6 (and up) solution:
services:
myrepository:
class: Doctrine\ORM\EntityRepository
factory: ["#doctrine.orm.entity_manager", getRepository]
arguments:
- MyBundle\Entity\MyClass
myservice:
class: MyBundle\Service\MyService
arguments:
- "#myrepository"
Deprecated solution (Symfony 2.5 and less):
services:
myrepository:
class: Doctrine\ORM\EntityRepository
factory_service: doctrine.orm.entity_manager
factory_method: getRepository
arguments:
- MyBundle\Entity\MyClass
myservice:
class: MyBundle\Service\MyService
arguments:
- "#myrepository"
I found this link and this worked for me:
parameters:
image_repository.class: Mycompany\MainBundle\Repository\ImageRepository
image_repository.factory_argument: 'MycompanyMainBundle:Image'
image_manager.class: Mycompany\MainBundle\Service\Image\ImageManager
image_manipulator.class: Mycompany\MainBundle\Service\Image\ImageManipulator
services:
image_manager:
class: %image_manager.class%
arguments:
- #image_manipulator
- #image_repository
image_repository:
class: %image_repository.class%
factory_service: doctrine.odm.mongodb
factory_method: getRepository
arguments:
- %image_repository.factory_argument%
image_manipulator:
class: %image_manipulator.class%
In case if do not want to define each repository as a service, starting from version 2.4 you can do following, (default is a name of the entity manager):
#=service('doctrine.orm.default_entity_manager').getRepository('MycompanyMainBundle:Image')
Symfony 3.3, 4 and 5 makes this much simpler.
Check my post How to use Repository with Doctrine as Service in Symfony for more general description.
To your code, all you need to do is use composition over inheritance - one of SOLID patterns.
1. Create own repository without direct dependency on Doctrine
<?php
namespace MycompanyMainBundle\Repository;
use Doctrine\ORM\EntityManagerInterface;
use MycompanyMainBundle\Entity\Image;
class ImageRepository
{
private $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(Image::class);
}
// add desired methods here
public function findAll()
{
return $this->repository->findAll();
}
}
2. Add config registration with PSR-4 based autoregistration
# app/config/services.yml
services:
_defaults:
autowire: true
MycompanyMainBundle\:
resource: ../../src/MycompanyMainBundle
3. Now you can add any dependency anywhere via constructor injection
use MycompanyMainBundle\Repository\ImageRepository;
class ImageService
{
public function __construct(ImageRepository $imageRepository)
{
$this->imageRepository = $imageRepository;
}
}
In my case bases upon #Tomáš Votruba answer and this question I propose the following approaches:
Adapter Approach
Without Inheritance
Create a generic Adapter Class:
namespace AppBundle\Services;
use Doctrine\ORM\EntityManagerInterface;
class RepositoryServiceAdapter
{
private $repository=null;
/**
* #param EntityManagerInterface the Doctrine entity Manager
* #param String $entityName The name of the entity that we will retrieve the repository
*/
public function __construct(EntityManagerInterface $entityManager,$entityName)
{
$this->repository=$entityManager->getRepository($entityName)
}
public function __call($name,$arguments)
{
if(empty($arrguments)){ //No arguments has been passed
$this->repository->$name();
} else {
//#todo: figure out how to pass the parameters
$this->repository->$name(...$argument);
}
}
}
Then foreach entity Define a service, for examplein my case to define a (I use php to define symfony services):
$container->register('ellakcy.db.contact_email',AppBundle\Services\Adapters\RepositoryServiceAdapter::class)
->serArguments([new Reference('doctrine'),AppBundle\Entity\ContactEmail::class]);
With Inheritance
Same step 1 mentioned above
Extend the RepositoryServiceAdapter class for example:
namespace AppBundle\Service\Adapters;
use Doctrine\ORM\EntityManagerInterface;
use AppBundle\Entity\ContactEmail;
class ContactEmailRepositoryServiceAdapter extends RepositoryServiceAdapter
{
public function __construct(EntityManagerInterface $entityManager)
{
parent::__construct($entityManager,ContactEmail::class);
}
}
Register service:
$container->register('ellakcy.db.contact_email',AppBundle\Services\Adapters\RepositoryServiceAdapter::class)
->serArguments([new Reference('doctrine')]);
Either the case you have a good testable way to function tests your database beavior also it aids you on mocking in case you want to unit test your service without the need to worry too much on how to do that. For example, let us suppose we have the following service:
//Namespace definitions etc etc
class MyDummyService
{
public function __construct(RepositoryServiceAdapter $adapter)
{
//Do stuff
}
}
And the RepositoryServiceAdapter adapts the following repository:
//Namespace definitions etc etc
class SomeRepository extends \Doctrine\ORM\EntityRepository
{
public function search($params)
{
//Search Logic
}
}
Testing
So you can easily mock/hardcode/emulate the behavior of the method search defined in SomeRepository by mocking aither the RepositoryServiceAdapter in non-inheritance approach or the ContactEmailRepositoryServiceAdapter in the inheritance one.
The Factory Approach
Alternatively you can define the following factory:
namespace AppBundle\ServiceFactories;
use Doctrine\ORM\EntityManagerInterface;
class RepositoryFactory
{
/**
* #param EntityManagerInterface $entityManager The doctrine entity Manager
* #param String $entityName The name of the entity
* #return Class
*/
public static function repositoryAsAService(EntityManagerInterface $entityManager,$entityName)
{
return $entityManager->getRepository($entityName);
}
}
And then Switch to php service annotation by doing the following:
Place this into a file ./app/config/services.php (for symfony v3.4, . is assumed your ptoject's root)
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
$definition = new Definition();
$definition->setAutowired(true)->setAutoconfigured(true)->setPublic(false);
// $this is a reference to the current loader
$this->registerClasses($definition, 'AppBundle\\', '../../src/AppBundle/*', '../../src/AppBundle/{Entity,Repository,Tests,Interfaces,Services/Adapters/RepositoryServiceAdapter.php}');
$definition->addTag('controller.service_arguments');
$this->registerClasses($definition, 'AppBundle\\Controller\\', '../../src/AppBundle/Controller/*');
And cange the ./app/config/config.yml (. is assumed your ptoject's root)
imports:
- { resource: parameters.yml }
- { resource: security.yml }
#Replace services.yml to services.php
- { resource: services.php }
#Other Configuration
Then you can clace the service as follows (used from my example where I used a Dummy entity named Item):
$container->register(ItemRepository::class,ItemRepository::class)
->setFactory([new Reference(RepositoryFactory::class),'repositoryAsAService'])
->setArguments(['$entityManager'=>new Reference('doctrine.orm.entity_manager'),'$entityName'=>Item::class]);
Also as a generic tip, switching to php service annotation allows you to do trouble-free more advanced service configuration thin one above. For code snippets use a special repository I made using the factory method.
For Symfony 5 it is really simple, without need of services.yml to inject the dependency:
inject the Entity Manager in the service constructor
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
Then get the repository :
$this->em->getRepository(ClassName::class)
by replacing ClassName with your entity name.

Categories