How to use VichUploaderBundle in CrudController's configureFields - symfony

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

Related

How to add a dynamic constraint on a form field

I have a non required field in my 'Session' entity, field is named 'location'. I want to make it required only if another field is equal to true, this other property is named 'isVirtual'
I thaught i'd create an eventSubscriber with the event BeforeEntityUpdatedEvent but I don't know how to add a constraint and I am not sure when this event is triggered.
I tried to do a custom validation constraint but I don't know how to get value of 'isVirtual' value in my ConstraintValidator class.
I need someone to help me look in the right direction. I put there my Entity
<?php
namespace App\Entity;
use App\Repository\SessionRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: SessionRepository::class)]
class Session
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column]
private ?\DateTimeImmutable $beginsAt = null;
#[ORM\Column]
private ?\DateTimeImmutable $endsAt = null;
#[ORM\ManyToOne(inversedBy: 'sessions')]
#[ORM\JoinColumn(nullable: false)]
private ?Formation $formation = null;
#[ORM\ManyToOne(inversedBy: 'sessions')]
#[ORM\JoinColumn(nullable: true)]
private ?Location $location = null;
#[ORM\Column]
private ?bool $isVirtual = null;
#[ORM\OneToMany(mappedBy: 'session', targetEntity: UserSession::class)]
private Collection $users;
#[ORM\Column(nullable: true)]
private ?int $numberOfSeats = null;
public function __construct()
{
$this->users = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getBeginsAt(): ?\DateTimeImmutable
{
return $this->beginsAt;
}
public function setBeginsAt(\DateTimeImmutable $beginsAt): self
{
$this->beginsAt = $beginsAt;
return $this;
}
public function getEndsAt(): ?\DateTimeImmutable
{
return $this->endsAt;
}
public function setEndsAt(\DateTimeImmutable $endsAt): self
{
$this->endsAt = $endsAt;
return $this;
}
public function getFormation(): ?Formation
{
return $this->formation;
}
public function setFormation(?Formation $formation): self
{
$this->formation = $formation;
return $this;
}
public function getLocation(): ?Location
{
return $this->location;
}
public function setLocation(?Location $location): self
{
$this->location = $location;
return $this;
}
public function isIsVirtual(): ?bool
{
return $this->isVirtual;
}
public function setIsVirtual(bool $isVirtual): self
{
$this->isVirtual = $isVirtual;
return $this;
}
/**
* #return Collection<int, UserSession>
*/
public function getUsers(): Collection
{
return $this->users;
}
public function addUser(UserSession $user): self
{
if (!$this->users->contains($user)) {
$this->users->add($user);
$user->setSession($this);
}
return $this;
}
public function removeUser(UserSession $user): self
{
if ($this->users->removeElement($user)) {
// set the owning side to null (unless already changed)
if ($user->getSession() === $this) {
$user->setSession(null);
}
}
return $this;
}
public function getNumberOfSeats(): ?int
{
return $this->numberOfSeats;
}
public function setNumberOfSeats(int $numberOfSeats): self
{
$this->numberOfSeats = $numberOfSeats;
return $this;
}
public function retrieveSeat($number): self
{
$this->setNumberOfSeats($this->getNumberOfSeats() - $number);
return $this;
}
}
and my EasyAdmin Controller
<?php
namespace App\Controller\Admin;
use App\Entity\Session;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateField;
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class SessionCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Session::class;
}
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id')->hideOnForm(),
AssociationField::new('formation'),
DateField::new('beginsAt'),
DateField::new('endsAt'),
BooleanField::new('isVirtual'),
AssociationField::new('location')
->setRequired(false)
->setFormTypeOption('attr', ['placeholder' => 'Enter location']),
IntegerField::new('numberOfSeats')
->setRequired(false)
];
}
}
Thanks a lot in advance.
Here is the callback implementation. Thanks to craigh
<?php
namespace App\Entity;
........
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
class Session
{
........
#[Assert\Callback]
public function validate(ExecutionContextInterface $context, $payload)
{
if (!$this->isIsVirtual()) {
if ($this->getLocation() === null) {
$context->buildViolation("Session needs a location when it's not virtual")
->atPath('location')
->addViolation();
}
if ($this->getNumberOfSeats() === null) {
$context->buildViolation("Session needs a number of seats when it's not virtual")
->atPath('numberOfSeats')
->addViolation();
}
}
}
}

Symfony EasyAdmin3: Argument 1 passed must be an instance of App\Entity

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")

Symfony get curent entity from custom DBAL type

I have class that define custom type and i want to make validation based on class that call that type.
Its in purpose of having 2 tables when one is managed by symfony and other is not for yet.
The table that not managed by symfony ned value of 0, when is null.
namespace App\DBAL\Types\Null;
use Doctrine\DBAL\Types\IntegerType as DeafaultType;
use Doctrine\DBAL\Platforms\AbstractPlatform;
class IdentityType extends DeafaultType
{
const NULLIDENTITY = 'nullidentity';
public function getName()
{
return self::NULLIDENTITY;
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
#some logic
if ($entity instanceof ClassNotManagedBySymfony) {
return $value === null? 0: (int)$value;
}
return $value
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
#some logic
if ($entity instanceof ClassNotManagedBySymfony) {
return $value === 0? null: (int)$value;
}
return $value;
}
}
Edit
Question:
Its posible to get Entity instance inside custom type?
class User extends ClassNotManagedBySymfony
{
/**
* #var int
*
* #ORM\Column(name="entities_id", type="nullidentity")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
}

PhpUnit dataProviders vs Coverage

I decided, that it will be fine if I use data providers but when i try to generate code coverage whole tested class has 0% coverage.. Can someone tell me why?
Test class:
class AuthorDbManagerTest extends AbstractDbManagerTest
{
public function setUp()
{
parent::setUp();
}
/**
* #dataProvider instanceOfProvider
* #param bool $isInstanceOf
*/
public function testInstances(bool $isInstanceOf)
{
$this->assertTrue($isInstanceOf);
}
public function instanceOfProvider()
{
$manager = new AuthorDbManager($this->getEntityManagerMock());
return [
"create()" => [$manager->create() instanceof Author],
"save()" => [$manager->save(new Author()) instanceof AuthorDbManager],
"getRepository" => [$manager->getRepository() instanceof EntityRepository],
];
}
}
Tested class:
class AuthorDbManager implements ManagerInterface
{
protected $entityManager;
protected $repository;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
$this->repository = $entityManager->getRepository(Author::class);
}
public function create(array $data = [])
{
return new Author();
}
public function getRepository(): EntityRepository
{
return $this->repository;
}
public function save($object): ManagerInterface
{
$this->entityManager->persist($object);
$this->entityManager->flush();
return $this;
}
}
Why my code coverage is 0% on AuthorDbManager?
Screen
The data in the DataProvider is collected before the actual tests start - and there is nothing useful being tested within the testInstances() method.
If you passed the classname and expected class into testInstances($methodName, $expectedClass):
public function testInstances(callable $method, $expectedClass)
{
$this->assertInstanceOf($expectedClass, $method());
}
The dataprovider could return a callable, and the expected result:
"create()" => [[$manager,'create'], Author::class],
then you'd at least be running the code with in the actual test. You may also be better to just pass back a string methodname - 'create', and run that with a locally created $manager instance - $manager->$method() in the test.
In general, it's best to test something as specific as you can - not just letting it convert to a true/false condition.

Finding out what changed via postUpdate listener in Symfony 2.1

I have a postUpdate listener and I'd like to know what the values were prior to the update and what the values for the DB entry were after the update. Is there a way to do this in Symfony 2.1? I've looked at what's stored in getUnitOfWork() but it's empty since the update has already taken place.
You can use this ansfer Symfony2 - Doctrine - no changeset in post update
/**
* #param LifecycleEventArgs $args
*/
public function postUpdate(LifecycleEventArgs $args)
{
$changeArray = $args->getEntityManager()->getUnitOfWork()->getEntityChangeSet($args->getObject());
}
Found the solution here. What I needed was actually part of preUpdate(). I needed to call getEntityChangeSet() on the LifecycleEventArgs.
My code:
public function preUpdate(Event\LifecycleEventArgs $eventArgs)
{
$changeArray = $eventArgs->getEntityChangeSet();
//do stuff with the change array
}
Your Entitiy:
/**
* Order
*
* #ORM\Table(name="order")
* #ORM\Entity()
* #ORM\EntityListeners(
* {"\EventListeners\OrderListener"}
* )
*/
class Order
{
...
Your listener:
class OrderListener
{
protected $needsFlush = false;
protected $fields = false;
public function preUpdate($entity, LifecycleEventArgs $eventArgs)
{
if (!$this->isCorrectObject($entity)) {
return null;
}
return $this->setFields($entity, $eventArgs);
}
public function postUpdate($entity, LifecycleEventArgs $eventArgs)
{
if (!$this->isCorrectObject($entity)) {
return null;
}
foreach ($this->fields as $field => $detail) {
echo $field. ' was ' . $detail[0]
. ' and is now ' . $detail[1];
//this is where you would save something
}
$eventArgs->getEntityManager()->flush();
return true;
}
public function setFields($entity, LifecycleEventArgs $eventArgs)
{
$this->fields = array_diff_key(
$eventArgs->getEntityChangeSet(),
[ 'modified'=>0 ]
);
return true;
}
public function isCorrectObject($entity)
{
return $entity instanceof Order;
}
}
You can find example in doctrine documentation.
class NeverAliceOnlyBobListener
{
public function preUpdate(PreUpdateEventArgs $eventArgs)
{
if ($eventArgs->getEntity() instanceof User) {
if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') {
$eventArgs->setNewValue('name', 'Bob');
}
}
}
}

Resources