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,
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.
I'm new to Symfony and am trying to understand that controller response function. I just want to return a simple HTML file home.html, that at the moment just has a Hello World line in it.
How do I return the file as a response? As it stands with the code below it's just returning the string 'home.html'.
Many thanks.
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class MainController
{
/**
* #Route("/")
*/
public function homepage()
{
return new Response('home.html');
}
}
Because you need to extend your controller with AbstractController and use the render method to generate the view from html.
class MainController extends AbstractController
{
/**
* #Route("/")
*/
public function homepage()
{
return $this->render('home.html');
}
}
Impossible to load Stripe classe in my Symfony controller see :
namespace AppBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Stripe\Stripe;
class PointsController extends Controller
{
/**
*
* #Route("/points/buy", name="points_buy")
*/
public function buyAction(Request $request)
{
\Stripe\Stripe::setApiKey('sk_test_');
return $this->render('points/buy.html.twig', [
]);
}
}
Stripe added with composer in vendor directory
I tried Stripe::setApiKey('sk_test_') but same error ...
Any idea?
If you're importing Stripe\Stripe in your use statement then I don't think you need the fully qualified class name.
use Stripe\Stripe;
//// etc.
/**
*
* #Route("/points/buy", name="points_buy")
*/
public function buyAction(Request $request)
{
Stripe::setApiKey($secretKey);
}
Here's a Stripe service I created for one of my projects.
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.
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.