I use easyadmin for Symfony (I am a beginner), I'm stuck on this problem:
Argument 1 passed to App\Entity\MyOrder::setCarrier() must be an instance of App\Entity\Carrier or null, int given, called in /Users/My/Sites/test/src/Controller/Admin/MyOrderCrudController.php
(line in code: $myorder->setCarrier(2);)
I have this problem for all field with an relation.
however, My Entity:
/**
* #ORM\ManyToOne(targetEntity=Delivery::class, inversedBy="myOrder")
*/
private $delivery;
...
public function getCarrier(): ?carrier
{
return $this->carrier;
}
public function setCarrier(?carrier $carrier): self
{
$this->carrier = $carrier;
return $this;
}
...
My CrudController:
namespace App\Controller\Admin;
use App\Entity\MyOrder;
use App\Entity\Carrier;
use Doctrine\ORM\EntityManagerInterface;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Router\CrudUrlGenerator;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
class MyOrderCrudController extends AbstractCrudController
{
private $entityManager;
private $adminUrlGenerator;
public function __construct(EntityManagerInterface $entityManager, AdminUrlGenerator $adminUrlGenerator)
{
$this->entityManager = $entityManager;
$this->adminUrlGenerator = $adminUrlGenerator;
}
public static function getEntityFqcn(): string
{
return MyOrder::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud->setDefaultSort(['id' => 'DESC']);
}
public function configureActions(Actions $actions): Actions
{
$updateDelivery = Action::new('updateDelivery', 'Delivery up', 'fas fa-truck')->linkToCrudAction('updateDelivery');
return $actions
->add('detail', $updateDelivery)
->add('index', 'detail');
}
public function updateDelivery(AdminContext $context)
{
$myorder = $context->getEntity()->getInstance();
$myorder->setCarrier(2);
$this->entityManager->flush();
$url = $this->adminUrlGenerator->setRoute('admin', [])->generateUrl();
return $this->redirect($url);
}
setCarrier only accept a Carrier object. You can't pass "2" (I suppose it's the carrier id).
Try this :
$carrier = $this->entityManager->find(Carrier::class, 2);
$myorder->setCarrier($carrier);
PS : There's a typo in your entity (a class name has the first letter in uppercase, so "Carrier" instead of "carrier")
Related
Intention: I want to test if the validations I want are in place on the School entity, for which I want to write a test class extending TypeTestCase
Questions/problems:
I want to clear the error Error: Class "doctrine.orm.validator.unique" not found
I want to assert the error messages for each constraints of my elements. When I remove #[UniqueEntity('name')] from the model, then problem one vanishes but still the assertion self::assertCount(1, $form->getErrors()); fails. Which means $form->getErrors() does not have the validation error for the name being blank.
I am trying to write a symfony test a symfony Form type with a DB entity, with the following (stripped) definitions:
namespace App\Entity;
use App\Repository\SchoolRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: SchoolRepository::class)]
// >>>>>>> If I remove it problem 1 will be solved
#[UniqueEntity('name')]
class School implements TenantAwareInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private $id;
#[Assert\NotBlank]
#[ORM\Column(type: 'string', length: 255, unique: true)]
private $name;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
}
And form being:
namespace App\Form;
use App\Entity\School;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SchoolType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->add('name');
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => School::class,
'required' => false
]);
}
}
The test:
namespace App\Tests\Integration\Form;
use App\Entity\School;
use App\Form\SchoolType;
use Doctrine\Persistence\ManagerRegistry;
use Mockery as m;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Validator\Validation;
use Symfony\Contracts\Translation\TranslatorInterface;
class SchoolTypeTest extends TypeTestCase
{
use ValidatorExtensionTrait;
protected function getExtensions(): array
{
$validator = Validation::createValidatorBuilder()
->enableAnnotationMapping()
->addDefaultDoctrineAnnotationReader()
->getValidator();
$mockedManagerRegistry = m::mock(ManagerRegistry::class, ['getManagers' => []]);
return [
new ValidatorExtension($validator),
new DoctrineOrmExtension($mockedManagerRegistry),
];
}
public function testValidationReturnsError()
{
$school = new School();
$form = $this->factory->create(SchoolType::class, $school);
$form->submit([]);
self::assertTrue($form->isSynchronized());
self::assertFalse($form->isValid());
// >>>>>>> I want this to assert, problem 2
self::assertCount(1, $form->getErrors());
}
}
A more simple solution :
namespace App\Tests\Service;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Validator\Validation;
class AppTypeWithValidationTestCase extends TypeTestCase
{
use ValidatorExtensionTrait;
protected function getExtensions(): array
{
$factory = new AppConstraintValidatorFactory();
$factory->addValidator(
'doctrine.orm.validator.unique',
$this->createMock(UniqueEntityValidator::class))
);
$validator = Validation::createValidatorBuilder()
->setConstraintValidatorFactory($factory)
->enableAnnotationMapping()
->addDefaultDoctrineAnnotationReader()
->getValidator();
return [
new ValidatorExtension($validator),
];
}
// *** Following is a helper function which ease the way to
// *** assert validation error messages
public static function assertFormViewHasError(FormView $formElement, string $message): void
{
foreach ($formElement->vars['errors'] as $error) {
self::assertSame($message, $error->getMessage());
}
}
}
In short, I ended up writing adding a mocked UniqueEntity validator. I added some generic codes to ease testing other form types, which are as following:
A base for tests:
namespace App\Tests\Service;
use Doctrine\Persistence\ManagerRegistry;
use Mockery as m;
use Symfony\Bridge\Doctrine\Form\DoctrineOrmExtension;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Test\Traits\ValidatorExtensionTrait;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Validator\Validation;
class AppTypeWithValidationTestCase extends TypeTestCase
{
use ValidatorExtensionTrait;
protected function getExtensions(): array
{
$mockedManagerRegistry = m::mock(
ManagerRegistry::class,
[
'getManagers' => []
]
);
$factory = new AppConstraintValidatorFactory();
$factory->addValidator(
'doctrine.orm.validator.unique',
m::mock(UniqueEntityValidator::class, [
'initialize' => null,
'validate' => true,
])
);
$validator = Validation::createValidatorBuilder()
->setConstraintValidatorFactory($factory)
->enableAnnotationMapping()
->addDefaultDoctrineAnnotationReader()
->getValidator();
return [
new ValidatorExtension($validator),
new DoctrineOrmExtension($mockedManagerRegistry),
];
}
// *** Following is a helper function which ease the way to
// *** assert validation error messages
public static function assertFormViewHasError(FormView $formElement, string $message): void
{
foreach ($formElement->vars['errors'] as $error) {
self::assertSame($message, $error->getMessage());
}
}
}
A constraint validator which accepts a validator, it is needed so we can add the (mocked) definition of UniqeEntity:
namespace App\Tests\Service;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\ConstraintValidatorInterface;
class AppConstraintValidatorFactory extends ConstraintValidatorFactory
{
public function addValidator(string $className, ConstraintValidatorInterface $validator): void
{
$this->validators[$className] = $validator;
}
}
And the final unit test class:
<?php
declare(strict_types=1);
namespace App\Tests\Unit\Form;
use App\Entity\School;
use App\Form\SchoolType;
use App\Tests\Service\AppTypeWithValidationTestCase;
class SchoolTypeTest extends AppTypeWithValidationTestCase
{
public function testValidationReturnsError() {
$input = [
// *** Note that 'name' is missing here
'is_enabled' => true,
];
$school = new School();
$form = $this->factory->create(SchoolType::class, $school);
$form->submit($input);
self::assertTrue($form->isSynchronized());
self::assertFalse($form->isValid());
$view = $form->createView();
self::assertFormViewHasError($view->children['name'], 'This value should not be blank.');
}
}
Images aren't saving with settings below
public function configureFields(string $pageName): iterable
{
return [
ImageField::new('imageFile')->setBasePath('%app.path.product_images%'),
];
}
This working for me...
First create VichImageField
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use Vich\UploaderBundle\Form\Type\VichImageType;
class VichImageField implements FieldInterface
{
use FieldTrait;
public static function new(string $propertyName, ?string $label = null)
{
return (new self())
->setProperty($propertyName)
->setTemplatePath('')
->setLabel($label)
->setFormType(VichImageType::class);
}
}
And
public function configureFields(string $pageName): iterable
{
return [
ImageField::new('imagename')->setBasePath($this->getParameter('app.path.product_images'))->onlyOnIndex(),
VichImageField::new('imageFile')->hideOnIndex()
];
}
More info here
https://symfony.com/doc/master/bundles/EasyAdminBundle/fields.html#creating-custom-fields
Make sure to change at least 1 doctrine mapped field in your setter, otherwise doctrine won't dispatch events. Here is an example from the docs:
/**
* #ORM\Column(type="datetime")
* #var \DateTime
*/
private $updatedAt;
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
// VERY IMPORTANT:
// It is required that at least one field changes if you are using Doctrine,
// otherwise the event listeners won't be called and the file is lost
if ($image) {
// if 'updatedAt' is not defined in your entity, use another property
$this->updatedAt = new \DateTime('now');
}
}
You need the resolve the parameter first.
Instead of
ImageField::new('imageFile')->setBasePath('%app.path.product_images%')
Try
...
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class ProductCrudController extends AbstractCrudController
{
private $params;
public function __construct(ParameterBagInterface $params)
{
$this->params = $params;
}
public function configureFields(string $pageName): iterable
{
return [
ImageField::new('imageFile')>setBasePath($this->params->get('app.path.product_images'))
];
}
More info on getting the parameter here:
https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service
I am creating an application that fetches and search for product name from different sources (DB, XML, JSON, ...)(for this code Im testing only with the DB), my idea was to create an interface for that.
I created the interface ProductRepositoryInterface and the class DoctrineProductRepository then I declared them both as services.
In my controller, I call the search function with the product name as param.
Here is my interface ProductRepositoryInterface :
namespace Tyre\TyreBundle\Repository;
interface ProductRepositoryInterface
{
function search(string $needle);
}
My interface DoctrineProductRepository:
namespace Tyre\TyreBundle\Repository;
class DoctrineProductRepository implements ProductRepositoryInterface
{
public function __constructor(EntityManager $em)
{
$this->em = $em;
}
public function search(string $needle)
{
$repository = $this->em->getRepository('TyreTyreBundle:Products');
$query = $repository->createQueryBuilder('u')
->where("u.name LIKE '%".$needle."%' or u.manufacturer LIKE '%".$needle."%'")
->getQuery();
return $query->getArrayResult();
}
}
My Service.yml
services:
Tyre\TyreBundle\Repository\DoctrineProductRepository:
class: Tyre\TyreBundle\Repository\DoctrineProductRepository
Tyre\TyreBundle\Repository\ProductRepositoryInterface:
class: Tyre\TyreBundle\Repository\ProductRepositoryInterface
and finally my controller :
namespace Tyre\TyreBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Tyre\TyreBundle\Repository\DoctrineProductRepository;
use Tyre\TyreBundle\Repository\ProductRepositoryInterface;
class DefaultController extends Controller
{
public function indexAction()
{
return $this->render('TyreTyreBundle:Default:search.html.twig');
}
public function searchAction(Request $request) {
$repositoryMap = [
'db' => DoctrineProductRepository::class,
];
$serviceName = $repositoryMap[$request->get('db')]; /***This is Line 56 ***/
/** #var ProductRepositoryInterface */
$repository = $this->get($serviceName);
$results = $repository->search($request->get('search_for'));
return $this->render('TyreTyreBundle:Default:detail.html.twig', array('results' => $results));
}
public function detailAction()
{
//forward the user to the search page when he tries to access directly to the detail page
return $this->render('TyreTyreBundle:Default:search.html.twig');
}
}
But I get an error :
EDIT
When I try http://localhost:8000/search?db=db , I get other error (I var_dumped $repositoryMap) :
click to view
Am I missing anything?
The reason for your 'ContextErrorException' is :
$request->get('search_for')
is empty because you are passing nothing in the url for that key. Pass 'search_for' also in addition with 'db' like:
http://localhost:8000/search?db=db&search_for=myvalue
I have written a class BasicRepository in order to use it instead of the EntityRepository to add some basic modification like remove all deleted-flaged items.
<?php
namespace AppBundle\Repository;
use AppBundle\DataFixtures\ORM\LoadEventPrioData;
use AppBundle\Entity\Location;
use Doctrine\ORM\EntityRepository;
class BasicRepository extends EntityRepository
{
public function createQueryBuilder($alias, $indexBy = null)
{
$query = parent::createQueryBuilder($alias);
dump(parent::getClassName());
dump($this->getClassName());
if (property_exists($this->getClassName(), 'isDeleted')) {
dump("Ping");
$query->andWhere($alias.'.isDeleted = :false')->setParameter('false', false);
}
else {
dump("Pong");
}
return $query;
}
}
Controller:
...
public function searchAction(Request $request) {
$em = $this->getDoctrine()->getManager();
$meta = new ClassMetadata('AppBundle:Location');
$er = new BasicRepository($em, $meta);
$query = $er->createQueryBuilder('u');
...
My aim is that - if the property "isDeleted" (boolean) exists in the Entity - the Query should contain an additional Where-Statement.
For some strange reason property_exists always return false - even when the property exits in the class.
I get your idea. The correct place you're looking for is Doctrine Filters. Check this package: https://github.com/DeprecatedPackages/DoctrineFilters#usage
There you can find example exactly with your use case:
<?php
use Doctrine\ORM\Mapping\ClassMetadata;
use Symplify\DoctrineFilters\Contract\Filter\FilterInterface;
final class SoftdeletableFilter implements FilterInterface
{
/**
* {#inheritdoc}
*/
public function addFilterConstraint(ClassMetadata $entity, $alias)
{
if ($entity->getReflectionClass()->hasProperty('isDeleted')) {
return "$alias.isDeleted = 0";
}
return '';
}
}
In Symfony2, the route parameters can be automatically map to the controller arguments, eg: http://a.com/test/foo will return "foo"
/**
* #Route("/test/{name}")
*/
public function action(Request $request, $name) {
return new Response(print_r($name, true));
}
see http://symfony.com/doc/current/book/routing.html#route-parameters-and-controller-arguments
But I want to use query string instead eg: http://a.com/test?name=foo
How to do that ?
For me there are only 3 solutions:
re-implement ControllerResolverInterface
use a custom ParamConverter
$name = $request->query->get('name');
Is there another solution ?
I provide you the code for those which want to use a converter :
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Put specific attribute parameter to query parameters
*/
class QueryStringConverter implements ParamConverterInterface{
public function supports(ParamConverter $configuration) {
return 'querystring' == $configuration->getConverter();
}
public function apply(Request $request, ParamConverter $configuration) {
$param = $configuration->getName();
if (!$request->query->has($param)) {
return false;
}
$value = $request->query->get($param);
$request->attributes->set($param, $value);
}
}
services.yml :
services:
querystring_paramconverter:
class: AppBundle\Extension\QueryStringConverter
tags:
- { name: request.param_converter, converter: querystring }
In your controller:
/**
* #Route("/test")
* #ParamConverter("name", converter="querystring")
*/
public function action(Request $request, $name) {
return new Response(print_r($name, true));
}
An improved solution based on Remy's answer which will map the parameter to an entity :
<?php
namespace AppBundle\Extension;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\HttpFoundation\Request;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DoctrineParamConverter;
/**
* Put specific attribute parameter to query parameters
*/
class QueryStringConverter extends DoctrineParamConverter {
protected function getIdentifier(Request $request, $options, $name)
{
if ($request->query->has($name)) {
return $request->query->get($name);
}
return false;
}
}
services.yml:
services:
querystring_paramconverter:
class: MBS\AppBundle\Extension\QueryStringConverter
arguments: ['#doctrine']
tags:
- { name: request.param_converter, converter: querystring }
in your controller:
/**
* #Route("/test")
* #ParamConverter("myobject")
*/
public function action(Request $request, AnyEntity $myobject) {
return new Response(print_r($myobject->getName(), true));
}
like #2, To solve private method (getIdentifier) first set attributes and execute normally (parent::apply). Tested on Symfony 4.4
<?php
namespace App\FrameworkExtra\Converters;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\DoctrineParamConverter;
use Symfony\Component\HttpFoundation\Request;
class QueryStringEntityConverter extends DoctrineParamConverter
{
public function supports(ParamConverter $configuration)
{
return 'querystringentity' == $configuration->getConverter();
}
public function apply(Request $request, ParamConverter $configuration)
{
$param = $configuration->getName();
if (!$request->query->has($param)) {
return false;
}
$value = $request->query->get($param);
$request->attributes->set($param, $value);
return parent::apply($request, $configuration);
}
}
I havn't checked, but it seems that the FOSRestBundle provides the #QueryParam annotation which does that :
http://symfony.com/doc/current/bundles/FOSRestBundle/param_fetcher_listener.html