I've created a custom phone constraint following the steps in this recipe from the Symfony2 cookbook.
The constraint class:
namespace Foo\Bundle\StackBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class Phone extends Constraint
{
public $message = 'The Phone contains an illegal character';
}
The validator class:
namespace Foo\Bundle\StackBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* #Annotation
*/
class PhoneValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
$length = strlen($value);
if (is_null($value)) {
return;
}
if ( $length > 14 || ! preg_match("/\([1-9]{2}\) [0-9]{4}-[0-9]{4}/", $value)) {
$this->context->addViolation($constraint->message, array(), $value);
}
}
}
This validator works fine, however I would like to use the Regex string constraint provided by Symfony2.
I've tried to implement this in the constraint class:
namespace Foo\Bundle\StackBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #Annotation
*/
class Phone extends Constraint
{
public $message = 'The Phone contains an illegal character';
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('description', new Assert\Regex(array(
'pattern' => '/\([1-9]{2}\) [0-9]{4}-[0-9]{4}/'
)));
}
}
But it gives me a fatal error asking me to implement the validate method:
Fatal error: Class
Foo\Bundle\StackBundle\Validator\Constraints\CepValidator contains
1 abstract method and must therefore be declared abstract or implement
the remaining methods
(Symfony\Component\Validator\ConstraintValidatorInterface::validate)
But the validate method is already implemented in the ConstraintValidator class (although if properly implemented, I think the pattern indicated in loadValidatorMetadata should be enough).
Any suggestions of how can I achieve this?
UPDATE:
It seems that all was working properly, in order for the Regex constraint to work, after you set the pattern in the constraint class, the validate method can be declare empty in the validator class like this:
namespace Foo\Bundle\StackBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
/**
* #Annotation
*/
class PhoneValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
}
}
The error is thrown for CepValidator not for PhoneValidator.
You have another file ...
src/Foo/Bundle/StackBundle/Validator/Constraints/CepValidator.php
... that's missing the validate() method.
Related
As my IDE points out, the AbstractController::getDoctrine() method is now deprecated.
I haven't found any reference for this deprecation neither in the official documentation nor in the Github changelog.
What is the new alternative or workaround for this shortcut?
As mentioned here:
Instead of using those shortcuts, inject the related services in the constructor or the controller methods.
You need to use dependency injection.
For a given controller, simply inject ManagerRegistry on the controller's constructor.
use Doctrine\Persistence\ManagerRegistry;
class SomeController {
public function __construct(private ManagerRegistry $doctrine) {}
public function someAction(Request $request) {
// access Doctrine
$this->doctrine;
}
}
You can use EntityManagerInterface $entityManager:
public function delete(Request $request, Test $test, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$test->getId(), $request->request->get('_token'))) {
$entityManager->remove($test);
$entityManager->flush();
}
return $this->redirectToRoute('test_index', [], Response::HTTP_SEE_OTHER);
}
As per the answer of #yivi and as mentionned in the documentation, you can also follow the example below by injecting Doctrine\Persistence\ManagerRegistry directly in the method you want:
// src/Controller/ProductController.php
namespace App\Controller;
// ...
use App\Entity\Product;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
/**
* #Route("/product", name="create_product")
*/
public function createProduct(ManagerRegistry $doctrine): Response
{
$entityManager = $doctrine->getManager();
$product = new Product();
$product->setName('Keyboard');
$product->setPrice(1999);
$product->setDescription('Ergonomic and stylish!');
// tell Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($product);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
return new Response('Saved new product with id '.$product->getId());
}
}
Add code in controller, and not change logic the controller
<?php
//...
use Doctrine\Persistence\ManagerRegistry;
//...
class AlsoController extends AbstractController
{
public static function getSubscribedServices(): array
{
return array_merge(parent::getSubscribedServices(), [
'doctrine' => '?'.ManagerRegistry::class,
]);
}
protected function getDoctrine(): ManagerRegistry
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
}
return $this->container->get('doctrine');
}
...
}
read more https://symfony.com/doc/current/service_container/service_subscribers_locators.html#including-services
In my case, relying on constructor- or method-based autowiring is not flexible enough.
I have a trait used by a number of Controllers that define their own autowiring. The trait provides a method that fetches some numbers from the database. I didn't want to tightly couple the trait's functionality with the controller's autowiring setup.
I created yet another trait that I can include anywhere I need to get access to Doctrine. The bonus part? It's still a legit autowiring approach:
<?php
namespace App\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Symfony\Contracts\Service\Attribute\Required;
trait EntityManagerTrait
{
protected readonly ManagerRegistry $managerRegistry;
#[Required]
public function setManagerRegistry(ManagerRegistry $managerRegistry): void
{
// #phpstan-ignore-next-line PHPStan complains that the readonly property is assigned outside of the constructor.
$this->managerRegistry = $managerRegistry;
}
protected function getDoctrine(?string $name = null, ?string $forClass = null): ObjectManager
{
if ($forClass) {
return $this->managerRegistry->getManagerForClass($forClass);
}
return $this->managerRegistry->getManager($name);
}
}
and then
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\Foobar;
class SomeController extends AbstractController
{
use EntityManagerTrait
public function someAction()
{
$result = $this->getDoctrine()->getRepository(Foobar::class)->doSomething();
// ...
}
}
If you have multiple managers like I do, you can use the getDoctrine() arguments to fetch the right one too.
In Symfony (& easyadmin), I would like to validate, using a custom function, some of the edit/new form fields on submission.
I then tought creating a custom validator, but it is never called.
Here are the created files. What did I miss?
config/validator/validation.yaml
App\Entity\ClassePrice:
constraints:
- App\Validator\Constraints\StepsCoverage: ~
src/Validator/Constraints/StepsCoverage.php
<?php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class StepsCoverage extends Constraint
{
public $message = 'The steps coverage is not valid';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
src/Validator/Constraints/StepsCoverageValidator.php
<?php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
class StepsCoverageValidator extends ConstraintValidator
{
public function validate($prices, Constraint $constraint)
{
$toto=1;
}
}
A breakpoint on $toto=1 never reached...
Thank you for your help,
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 have 2 bundles and i want to override the repository of one of them in the other :
I have a source bundle : SourceBundle.
I have my override bundle : OverrideBundle
First, in OurVendorOverrideBundle.php, I added :
public function getParent()
{
return 'SomeVendorSourceBundle';
}
Then
I wanted to add a custom method for the repository of an entity of SourceBundle.
The source entity is Response.php, and its repository is ResponseRepository.php
So i did :
<?php
namespace OurVendor\OverrideBundle\Repository;
use Doctrine\ORM\EntityRepository;
use SomeVendor\SourceBundle\Repository\ResponseRepository as BaseRepository;
class ResponseRepository extends BaseRepository
{
/**
*
* #return array
*/
public function getQueryExerciseAllResponsesForAllUsers($exoId)
{
$qb = $this->createQueryBuilder('r');
$qb->join('r.paper', 'p')
->join('p.exercise', 'e')
->where('e.id = ?1')
->andWhere('p.interupt = ?2')
->setParameters(array(1 => $exoId, 2 => 0));
return $qb->getQuery();
}
}
If i dont set the Entity in the OverrideBundle i have this error :
The autoloader expected class "CPASimUSante\ExoverrideBundle\Entity\Response" to be defined in file "/home/www/myproject/vendor/ourvendor/override-bundle/OurVendor/OverrideBundle/Entity/Response.php". The file was found but the class was not in it, the class name or namespace probably has a typo.
The SourceBundle entity is :
<?php
namespace SomeVendor\SourceBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* SomeVendor\SourceBundle\Entity\Response
*
* #ORM\Entity(repositoryClass="SomeVendor\SourceBundle\Repository\ResponseRepository")
* #ORM\Table(name="source_response")
*/
class Response
{
...
}
So i add the Entity in the OverrideBudle :
<?php
namespace OurVendor\OverrideBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use SomeVendor\SourceBundle\Entity\Response as BaseEntity;
/**
* SomeVendor\SourceBundle\Entity\Response
*
* #ORM\Entity(repositoryClass="OurVendor\OverrideBundle\Repository\ResponseRepository")
* #ORM\Table(name="override_response")
*/
class Response extends BaseEntity
{
public function __construct()
{
parent::__construct();
}
}
But then i have this error :
An exception occurred while executing 'SELECT u0_.id AS id0, u0_.ip AS ip1, u0_.mark AS mark2, u0_.nb_tries AS nb_tries3, u0_.response AS response4, u0_.paper_id AS paper_id5, u0_.interaction_id AS interaction_id6 FROM ujm_exoverride_response u1_ INNER JOIN ujm_paper u2_ ON u0_.paper_id = u2_.id INNER JOIN ujm_exercise u3_ ON u2_.exercise_id = u3_.id WHERE u3_.id = ? AND u2_.interupt = ?' with params ["1", 0]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'u0_.id' in 'field list'
This seems that fields in the table are not found. So i changed to
<?php
namespace OurVendor\OverrideBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use SomeVendor\SourceBundle\Entity\Response as BaseEntity;
/**
* SomeVendor\SourceBundle\Entity\Response
*
* #ORM\Entity(repositoryClass="OurVendor\OverrideBundle\Repository\ResponseRepository")
* #ORM\Table(name="source_response")
*/
class Response extends BaseEntity
{
public function __construct()
{
parent::__construct();
}
}
And it worked.
...But when i reinstalled the bundle, to test if everything was ok i had this fatal error stating that source_response is already defined (which is, indeed).
So what can i do then ?
I have also read that i can't override an entity unless the source extends MappedSuperclass, in my case, it doesn't.
But i am doomed if i only want to override its repository ? Is it possible ? If then, what is the right way ?
If i remove altogether the annotation for the override entity, i have :
Class "OurVendor\OverrideBundle\Entity\Response" sub class of "SomeVendor\SourceBundle\Entity\Response" is not a valid entity or mapped super class.
500 Internal Server Error - MappingException
Doctrine mapping in another bundles can be overriden on entity class metadata loading.
EventListener
<?php
namespace Lol\RandomBundle\EventListener;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class ClassMetadataListener
{
/**
* Run when Doctrine ORM metadata is loaded.
*
* #param LoadClassMetadataEventArgs $eventArgs
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if ('AnotherLol\AnotherRandomBundle\Entity\Response' === $classMetadata->name) {
// Do whatever you want...
$classMetadata->customRepositoryClassName = 'ThirdLol\SomeBundle\Repository\NewResponseRepository';
}
}
}
Service configuration
services:
lol.random.listener.class_metadata:
class: Lol\RandomBundle\EventListener\ClassMetadataListener
tags:
- { name: doctrine.event_listener, event: loadClassMetadata }
How can I add constraints to upload an image, for example : max size, error message, there is not thing about that in the config of sonata_media.
thank you very much.
First you will use the CompilerPassInterface component to override the SonataMediaBundle's MediaAdmin class as per link:
Overriding the part of bundle
supose you are in AcmeDemoBundle:
<?php
namespace Acme\DemoBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition1 = $container->getDefinition('sonata.media.admin.media');
$definition1->setClass('Acme\DemoBundle\Admin\MediaAdmin');
}
}
Second you will activate your CompilerPassInterface as per the link:
how to activate CompilerPassInterface
<?php
namespace Acme\DemoBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Acme\DemoBundle\DependencyInjection\OverrideServiceCompilerPass;
class AcmeDemoBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new OverrideServiceCompilerPass());
}
}
and in third and final You will override MediaAdmin class of sonatamediabundle as per the link:
INLINE VALIDATION¶(19.3 USING THE ADMIN CLASS¶)
<?php
namespace Acme\DemoBundle\Admin;
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
use Sonata\MediaBundle\Provider\Pool;
use Sonata\AdminBundle\Route\RouteCollection;
use Sonata\MediaBundle\Admin\BaseMediaAdmin as BaseAdmin;
use Sonata\MediaBundle\Form\DataTransformer\ProviderDataTransformer;
use Sonata\AdminBundle\Validator\ErrorElement;
class MediaAdmin extends BaseAdmin
{
// add this method
public function validate(ErrorElement $errorElement, $object)
{
$errorElement
->with('name')
->assertMaxLength(array('limit' => 32))
->end()
->with('description')
->assertNotNull(array())
->assertLength(array('min' => 2,
'max' => 50))
->end()
// remaining field here
;
}
}
Now you may validate remaing fields of SonataMediaBundle's Media class located in
Sonata\MediaBundle\Model\Media
That's all above the need ..
I got this issue in my project recently. There is my little hack(symfony 2.3):
use Symfony\Component\Validator\ExecutionContextInterface;
/**
* #ORM\Entity(repositoryClass="CMS\RequestBundle\Repository\RequestVideoRepository")
* #ORM\Table(name="request")
* #Assert\Callback(methods={"isMediaSizeValid"})
*
*/
class RequestVideo
{
property
/**
* #ORM\OneToOne(targetEntity="Application\Sonata\MediaBundle\Entity\Media",cascade={"persist"})
*/
protected $file;
Validation method
/**
* #param ExecutionContextInterface $context
*/
public function isMediaSizeValid(ExecutionContextInterface $context)
{
if($this->getFile() && $this->getFile()->getSize() > 5242880){
$context->addViolationAt('file', 'File is too big. Please upload file less than 5MB', array(), null);
}
}
Kinda dirty but I didn't find anything to sort this out. I hope someone will suggest solution better than this.