A Company can have multiple emails and all emails have to be unique.
This my Entites for Company and CompanyEmail
CompanyEmail Entity:
/**
* #ORM\Entity(repositoryClass="App\Repository\CompanyEmailRepository")
* #UniqueEntity("name")
*/
class CompanyEmail
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=128, unique=true)
* #Assert\Email()
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Company", inversedBy="emails")
* #ORM\JoinColumn(nullable=false)
*/
private $company;
// ...
}
Company Entity:
/**
* #ORM\Entity(repositoryClass="App\Repository\CompanyRepository")
*/
class Company
{
// ...
/**
* #ORM\OneToMany(targetEntity="App\Entity\CompanyEmail", mappedBy="company", orphanRemoval=true, cascade={"persist"})
* #Assert\Valid
*/
private $emails;
// ...
}
and I'm using an custom EmailsInputType that use this DataTransformer
class EmailArrayToStringTransformer implements DataTransformerInterface
{
public function transform($emails): string
{
return implode(', ', $emails);
}
public function reverseTransform($string): array
{
if ($string === '' || $string === null) {
return [];
}
$inputEmails = array_filter(array_unique(array_map('trim', explode(',', $string))));
$cEmails = [];
foreach($inputEmails as $email){
$cEmail = new CompanyEmail();
$cEmail->setName($email);
$cEmails[] = $cEmail;
}
return $cEmails;
}
}
and in the Controller a use this edit method
/**
* #Route("/edit/{id}", name="admin_company_edit", requirements={"id": "\d+"}, methods={"GET", "POST"})
*/
public function edit(Request $request, $id): Response
{
$entityManager = $this->getDoctrine()->getManager();
$company = $entityManager->getRepository(Company::class)->find($id);
$form = $this->createForm(CompanyType::class, $company);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->flush();
}
}
There is two problems with this code
1 - In the edit form when i try to keep an already saved email Symfony generate a validation error that tells that this email is already exits.
2 - When I remove the validation restriction from the code, Symfony thrown the database error "*Integrity constraint violation: 1062 Duplicate entry ... *"
What i should do to make my code work as expected !
The problem is right here
public function reverseTransform($string): array
{
[...]
foreach($inputEmails as $email){
$cEmail = new CompanyEmail();
[...]
}
[...]
}
You need to retrieve the email instead of creating new one.
So basically, inject a CompanyEmailRepository, try to find if email already exists (findOneBy(['name'])), if it does not exists, create a new one but if exists, use what you've retrieved.
Just few notes
Pay attention to email owner (so the retrieve should be do per user I guess as no one can share the same mail UNLESS you can specify some aliases or shared address)
Maybe you don't need an extra entity like CompanyEmail as you can use a json field where you can store them in a comma separated fashion (unless you need some extra parameters or unless you need to perform some indexing/querying operation on the emails)
Related
I have the structure like below.
----------------
MESSAGE
----------------
id
subject
body
----------------
----------------
USER
----------------
id
name
category
region
----------------
----------------
RECIPIENT
----------------
user_id
message_id
is_read
read_at
----------------
So Message 1:n Recipient m:1 User.
Recipient is not an #ApiResource.
A Backoffice user will "write" a message and choose the audience by a set of specific criteria (user region, user category, user tags...).
To POST the message i'm using a Dto
class MessageInputDto
{
/**
* #var string
*
* #Groups({"msg_message:write"})
*/
public string $subject;
/**
* #var string
*
* #Groups({"msg_message:write"})
*/
public string $body;
/**
* #var bool
*
* #Groups({"msg_message:write"})
*/
public bool $isPublished;
/**
* #var DateTimeInterface
*
* #Groups({"msg_message:write"})
*/
public DateTimeInterface $publishDate;
/**
* #var DateTimeInterface|null
*
* #Groups({"msg_message:write"})
*/
public ?DateTimeInterface $expiryDate = null;
/**
* #var MessageCategory|null
*
* #Groups({"msg_message:write"})
*/
public ?MessageCategory $category = null;
/**
* #var array
*/
public array $criteria = [];
}
The $criteria field is used to choose the audience of that message and is skipped by the DataTransformer as it is not a mapped field, a property of Message Entity that is returned by the transformer.
class MessageInputDataTransformer implements \ApiPlatform\Core\DataTransformer\DataTransformerInterface
{
/**
* #var MessageInputDto $object
* #inheritDoc
*/
public function transform($object, string $to, array $context = [])
{
$message = new Message($object->subject, $object->body);
$message->setIsPublished($object->isPublished);
$message->setPublishDate($object->publishDate);
$message->setExpiryDate($object->expiryDate);
$message->setCategory($object->category);
return $message;
}
/**
* #inheritDoc
*/
public function supportsTransformation($data, string $to, array $context = []): bool
{
// in the case of an input, the value given here is an array (the JSON decoded).
// if it's a book we transformed the data already
if ($data instanceof Message) {
return false;
}
return Message::class === $to && null !== ($context['input']['class'] ?? null);
}
}
As side effect, will be performed a bulk insert in the join table (Recipient) that keeps the m:n relations between Message and User.
My problem is how/where to perform this bulk insert and how pass the $criteria to the service that will manage it.
The only solution that i've found now (and it's working but i don't think is a good practice) is to put the bulk insert procedure in the POST_WRITE event of the Message, get the Request object and process the $criteria contained there.
class MessageSubscriber implements EventSubscriberInterface
{
/**
* #inheritDoc
*/
public static function getSubscribedEvents()
{
return [
KernelEvents::VIEW => [
['handleCriteria', EventPriorities::POST_WRITE]
],
];
}
public function handleCriteria(ViewEvent $event)
{
/** #var Message $message */
$message = $event->getControllerResult();
$method = $event->getRequest()->getMethod();
$e = $event->getRequest();
$collectionOperation = $e->get('_api_collection_operation_name');
if (!$message instanceof Message ||
$method !== Request::METHOD_POST ||
$collectionOperation !== 'post') {
return;
}
$content = json_decode($event->getRequest()->getContent(), true);
if(array_key_exists('audienceCriteria', $content)){
$criteria = Criteria::createFromArray($content['audienceCriteria']);
// Todo: Create the audience
}
}
}
So the idea is that, when the Message is persisted, the system must generate the "relations" public.
This is why i think that the post write event could be a good choice, but as i said i'm not sure this could be a good practice.
Any idea? Thanks.
As the docs on DTO's state: "in most cases the DTO pattern should be implemented using an API Resource class representing the public data model exposed through the API and a custom data provider. In such cases, the class marked with #ApiResource will act as a DTO."
IOW specifying an Input or an Output Data Representation and a DataTransformer is the exception. It does not work if the DTO holds more data then the entity or if the dto's are not one to one with the entities (for example with a report that does a group by).
Here is your DTO class as a resource:
namespace App\DTO;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use App\Entity\Message;
/**
* Class defining Message data transfer
*
* #ApiResource(
* denormalizationContext= {"groups" = {"msg_message:write"}},
* itemOperations={
* },
* collectionOperations={
* "post"={
* "path"="/messages",
* "openapi_context" = {
* "summary" = "Creates a Message",
* "description" = "Creates a Message"
* }
* }
* },
* output=Message::class
* )
*/
class MessageInputDto
{
/**
* #var string
*
* #Groups({"msg_message:write"})
*/
public string $subject;
/**
* #var string
*
* #Groups({"msg_message:write"})
*/
public string $body;
/**
* #var bool
*
* #Groups({"msg_message:write"})
*/
public bool $isPublished;
/**
* #var \DateTimeInterface
*
* #Groups({"msg_message:write"})
*/
public \DateTimeInterface $publishDate;
/**
* #var \DateTimeInterface|null
*
* #Groups({"msg_message:write"})
*/
public ?\DateTimeInterface $expiryDate = null;
/**
* #var MessageCategory|null
*
* #Groups({"msg_message:write"})
*/
public ?MessageCategory $category = null;
/**
* #var array
* #Groups({"msg_message:write"})
*/
public array $criteria = [];
}
Make sure the folder your class is in is in the paths list in api/config/packages/api_platform.yaml. There usually is the following configuration:
api_platform:
mapping:
paths: ['%kernel.project_dir%/src/Entity']
If MessageInputDto is in /src/DTO make it like:
api_platform:
mapping:
paths:
- '%kernel.project_dir%/src/Entity'
- '%kernel.project_dir%/src/DTO'
The post operation may have the same path as dhe default post operation on your Message resource. Remove that by explicitly defining collectionOperations for your Message resource without "post".
The post operation of MessageInputDto will deserialize the MessageInputDto. Your DataTransformer will not act on it so that it will arrive as is to the DataPersister:
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\ContextAwareDataPersisterInterface;
use App\DTO\MessageInputDto;
use App\Entity\Message;
use Doctrine\Persistence\ManagerRegistry;
use App\DataTransformer\MessageInputDataTransformer;
use ApiPlatform\Core\Exception\InvalidArgumentException;
class MessageDataPersister implements ContextAwareDataPersisterInterface
{
private $dataPersister;
private $entityManager;
private $dataTransformer;
public function __construct(ContextAwareDataPersisterInterface $dataPersister, ManagerRegistry $managerRegistry, MessageInputDataTransformer $dataTransformer)
{
$this->dataPersister = $dataPersister;
$this->entityManager = $managerRegistry->getManagerForClass(Message::class);
$this->dataTransformer = $dataTransformer;
}
public function supports($data, array $context = []): bool
{
$transformationContext = ['input' => ['class' => Message::class]];
return get_class($data) == MessageInputDto::class
&& $this->dataTransformer->supportsTransformation($data, Message::class, $transformationContext)
&& null !== $this->entityManager;
}
public function persist($data, array $context = [])
{
$message = $this->dataTransformer->transform($data, Message::class);
// dataPersister will flush the entityManager but we do not want incomplete data inserted
$this->entityManager->beginTransaction();
$commit = true;
$result = $this->dataPersister->persist($message, []);
if(!empty($data->criteria)){
$criteria = Criteria::createFromArray($data->criteria);
try {
// Todo: Create the audience, preferably with a single INSERT query SELECTing FROM user_table WHERE meeting the criteria
// (Or maybe better postpone until message is really sent, user region, category, tags may change over time)
} catch (\Exception $e) {
$commit = false;
$this->entityManager->rollback();
}
}
if ($commit) {
$this->entityManager->commit();
}
return $result;
}
public function remove($data, array $context = [])
{
throw new InvalidArgumentException('Operation not supported: delete');
}
}
(Maybe it should have been called MessageInputDtoDataPersister - depending on how you look at it)
Even with service autowiring and autoconfiguration enabled, you must still configure it to get the right dataPersister to delegate to:
# api/config/services.yaml
services:
# ...
'App\DataPersister\MessageDataPersister':
arguments:
$dataPersister: '#api_platform.doctrine.orm.data_persister'
This way you do not need MessageSubscriber.
Be aware that all the other phases inbetween deserialization and data persist (validation, security post denormalize) work on the MessageInputDto.
One solution when you have to generate multiple custom entities is to use data persisters: https://api-platform.com/docs/core/data-persisters/
There you have 2 options:
Decorate the doctrine persister - meaning the message will still be saved by Doctrine, but you can do something before or afterwards.
Implement a custom persister - saving both message and other related entities that you like. Or doing something completely custom, without calling Doctrine at all.
I try this code: Symfony: ManyToMany table extra columns
This new field is because the user have a diferent role for diferent events.
In the fist time i can add add the new fields on table user.evento, but now i cant. And i dont find the problem.
I get this fail:
An exception occurred while executing 'INSERT INTO user_evento (user_id, evento_id) VALUES (?, ?)' with params [3, 18]: SQLSTATE[HY000]: General error: 1364 Field 'tipoinvitado' doesn't have a default value
why not detecting the other 2 fields ?
capture of my sql update with the new fields
my entity UserHasEvento:
/**
* #ORM\Entity
* #ORM\Table(name="user_evento")
*/
class UserHasEvento
{
/**
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Evento", cascade={"persist"}, fetch="LAZY")
* #ORM\JoinColumn(name="evento_id", referencedColumnName="id")
*/
private $eventos;
/**
* #ORM\ManyToOne(targetEntity="User", cascade={"persist","remove"}, fetch="LAZY" )
* #ORM\JoinColumn(name="user_id", referencedColumnName="id",nullable=true)
*/
private $users;
/**
* #ORM\Column(name="tipoinvitado", type="string", length=255, nullable=true)
*/
private $tipoinvitado;
/**
* #var \DateTime|null
*
* #ORM\Column(name="fechainscripcion", type="datetime", nullable=true)
*/
private $fechainscripcion;
public function setTipoinvitado(string $tipoinvitado): self
{
$this->tipoinvitado = $tipoinvitado;
return $this;
}
public function getTipoinvitado(): string
{
return $this->tipoinvitado;
}
public function getFechainscripcion()
{
return $this->fechainscripcion;
}
public function setFechainscripcion($fechainscripcion): self
{
$this->fechainscripcion = $fechainscripcion;
return $this;
}
public function __construct()
{
$this->createdAt= new \DateTime('now');
}
}
My controller update.
public function asignarUsuario(Request $request, Evento $evento, Userhasevento $userhasevento){
$user_repo = $this->getDoctrine()->getRepository(User::class);
$users = $user_repo->findAll();
$evento = $this->getDoctrine()->getRepository(Evento::class)->findOneById($evento);
$form = $this->createForm(AsignarEventoFormType::class, $evento);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$users = $evento->GetUsers();
foreach($users as $user){
$user->addEvento($evento);
$evento->addUser($user);
$userhasevento->SetTipoinvitado('normal');
$userhasevento->setFechainscripcion(new \DateTime('now'));
$em = $this->getDoctrine()->getManager();
$em->persist($evento);
$em->persist($userhasevento);
$em->flush();
}
return $this->redirect($this->generateUrl('evento_detalle', ['id' => $evento->getId()]));
}
return $this->render('evento/AsignarEvento.html.twig',[
'form' => $form->createView()
]);
}
Is your field $tipoinvitado disappear?
You might have changed your relation and the null boolean of $tipoinvitado at some point. You made your migration after changing any relation?
This can happen when you change your relations and had already data stored in you DB in a field that disappear because of you relation change.
I might be able to help you, don't hesitate giving more info
You have the answer in the error, the $tipoinvitado field has the annotation nullable=false which is a not null constraint (prevent storing null value), so it should absolutely have a value.
So you either give it a default value, give a value or change it to nullable=true.
I have several classes using a Taggable trait to set up a tag system common to several doctrine entities (Project, Note, ...).
The relationship between these entities and these tags is a ManyToMany relationship that I can not make multi-directional.
My problem: When I delete a Project entity, it is removed from the project table, but the relationships in the project_tag table between this project and the tags are not deleted. Then, if I create a new Project entity, an exception is thrown.
An exception exists while executing 'INSERT INTO project_tag (project_id, tag_id) VALUES (?,?)' With params [2, 4]:
SQLSTATE [23000]: Integrity constraint violation: 19 UNIQUE constraint failed: project_tag.project_id, project_tag.tag_id
Entities :
Tag
/**
* Tag
*
* #ORM\Table(name="tag")
* #ORM\Entity(repositoryClass="AppBundle\Repository\TagRepository")
*/
class Tag
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* #ORM\Column(name="last_use_at", type="datetime", nullable=false)
* #var \DateTime
*/
private $lastUseAt;
public function __construct()
{
$this->lastUseAt = new \DateTime();
}
public function __toString()
{
return $this->name;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Tag
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName(): string
{
return $this->name;
}
/**
* #return \DateTime
*/
public function getLastUseAt(): \DateTime
{
return $this->lastUseAt;
}
/**
* #param \DateTime $lastUseAt
*/
public function setLastUseAt(\DateTime $lastUseAt)
{
$this->lastUseAt = $lastUseAt;
}
}
Taggable
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Tag", cascade={"persist"})
*/
protected $tags;
/**
* Add tag
*
* #param Tag $tag
*
* #return $this
*/
public function addTag(Tag $tag)
{
$tag->setLastUseAt(new \DateTime());
$this->tags[] = $tag;
return $this;
}
/**
* Remove tag
*
* #param Tag $tag
*/
public function removeTag(Tag $tag)
{
$this->tags->removeElement($tag);
}
/**
* Get tags
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getTags()
{
return $this->tags;
}
}
Project
/**
* Project
*
* #ORM\Table(name="project")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ProjectRepository")
*/
class Project
{
use Taggable;
}
Note
class Note
{
use Taggable;
}
Is this the only solution or is my annotation incomplete / incorrect?
I tried with JoinColumns, JoinTable and onDelete = "cascade" but nothing works.
In the meantime, I dodged the problem with this instruction placed before the suppresion.
$project->getTags()->clear();
Full code of the action in the controller :
/**
* #Route("/project/{id}/delete", name="project_delete")
*/
public function deleteAction($id) {
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository('AppBundle:Project')->find($id);
if(!$project) {
return $this->redirectToRoute('index');
}
$project->getTags()->clear();
$em->remove($project);
$em->flush();
return $this->redirectToRoute('index');
}
I think I found a better solution: you can set the PRAGMA within Doctrine configuration. Like:
doctrine:
dbal:
# configure these for your database server
driver: 'pdo_sqlite'
#server_version: '5.7'
#charset: utf8mb4
#default_table_options:
#charset: utf8mb4
#collate: utf8mb4_unicode_ci
url: '%env(resolve:DATABASE_URL)%'
options:
'PRAGMA foreign_keys': 'ON'
I just tried it on my Symfony 4 application, re-created the database and tested using DB Browser for SQLite and it works as I expected.
Hope this helps
I managed to fix the problem. Here's my solution working for SQLite conections.
Create an eventListener listening on the kernel.request event :
namespace AppBundle\EventListener;
use Doctrine\Bundle\DoctrineBundle\Registry;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RequestListener
{
/**
* #var Registry
*/
private $doctrine;
public function __construct(Registry $doctrine)
{
$this->doctrine = $doctrine;
}
public function onKernelRequest(GetResponseEvent $event)
{
$this->doctrine->getConnection()->exec('PRAGMA foreign_keys = ON');
}
}
Service declaration
app.event_listener.request_listener:
class: AppBundle\EventListener\RequestListener
arguments:
- '#doctrine'
tags:
- { name: kernel.event_listener, event: kernel.request }
I think the problem is that you have your trait Taggable set as the owning side of the ManyToMany relationship but your are deleting the inverse side and expecting something to happen as a result. Doctrine will only check the owning side of the relationship in order to persist any changes. See here for docs on this.
You can solve by making the Taggable the inverse side of each of your relationships, or by manually telling doctrine to delete the owning side.
The first solution will probably not work for you since you won't (easily) specify multiple inverse sides. (Are you sure a trait is the right way to go for this??)
The second solution is easy. In your entities like Project for your deleteTag($tag) function, call a delete function on the owning side (e.g., deleteProject($project). You will have to create if one does not exist.
class Project
{
use Taggable;
public function deleteTag($tag)
{
$this->tags->removeElement($tag);
// persist on the owning side
$tag->deleteProject($this);
}
}
EDIT:
After seeing full code, it looks like you are deleting correctly. Now you need to tell doctrine to carry that through. See this post for full details, but basically you can change your trait to this:
trait Taggable
{
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(
* targetEntity="AppBundle\Entity\Tag",
* cascade={"persist"},
* onDelete="CASCADE"
* )
*/
protected $tags;
// ...
}
I'm in a situation that need to update a Doctrine2 Entity and exclude some fields.
With ZF2 i have an action to handle update using Zend\Form and validation filter. In particular Dish Entity have a blob column called photo that is required. During an update i want to replace the photo only if a new file is provided.
Here there are the source code for the entity and the controller action that update dish.
Dishes\Entity\Dish.php
<?php
namespace Dishes\Entity;
use Doctrine\ORM\Mapping as ORM;
/** #ORM\Entity **/
class Dish
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
**/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $name;
/**
* #ORM\Column(type="text")
*/
protected $description;
/**
* #ORM\Column(type="integer")
*/
protected $time;
/**
* #ORM\Column(type="integer")
*/
protected $complexity;
/**
* #ORM\Column(type="blob")
*/
protected $photo;
/**
* Magic getter to expose protected properties.
*
* #param string $property
* #return mixed
*/
public function __get($property)
{
return $this->$property;
}
/**
* Magic setter to save protected properties.
*
* #param string $property
* #param mixed $value
*/
public function __set($property, $value)
{
$this->$property = $value;
}
}
Dishes\Controller\AdminController.php
public function editDishAction()
{
//Params from url
$id = (int) $this->params()->fromRoute('id', 0);
$objectManager = $this->objectManager;
$hydrator = new DoctrineObject($objectManager, false);
$form = new DishForm();
$existingDish = $objectManager->find('Dishes\Entity\Dish', $id);
if ($existingDish === NULL)
$this->notFoundAction();
$request = $this->getRequest();
if ($request->isPost())
{
$filter = new DishFilter();
$filter->get('photo')->setRequired(false);
$form->setHydrator($hydrator)
->setObject($existingDish)
->setInputFilter($filter);
$post = array_merge_recursive(
$request->getPost()->toArray(),
$request->getFiles()->toArray()
);
//Backup photo stream
$imageData = $existingDish->photo;
$form->setData($post);
if ($form->isValid())
{
//If user upload a new image read it.
if(!empty($existingDish->photo['tmp_name']))
$imageData = file_get_contents($existingDish->photo['tmp_name']);
$existingDish->photo = $imageData;
$objectManager->flush();
$this->redirect()->toRoute('zfcadmin/dishes');
}
}
else
{
$data = $hydrator->extract($existingDish);
unset($data['photo']);
$form->setData($data);
}
return new ViewModel(array('form' => $form));
}
Actually i set $dish->photo property to NULL but this violate DB NOT NULL constraint.
How can I tell Doctrine to exclude a particular entity field from update at runtime?
Doctrine maps every column's nullable property in database level to false by default since you don't set any nullable flag in your annotation:
/**
* #ORM\Column(type="blob")
*/
protected $photo;
This means, "Photo is required, you can't insert or update row's photo column with a null value".
If you want to have null values in your database, use the following annotation:
/**
* #ORM\Column(type="blob", nullable=true)
*/
protected $photo;
and in it's setter don't forget the null default argument value:
public function setPhoto($photo = null)
{
$this->photo = $photo;
}
For the question; seems like you're setting a new Dish object on every edit operation in the action:
$form->setHydrator($hydrator)
->setObject(new Dish)
->setInputFilter($filter);
This is correct when creating new Dish objects. When editing, you have to set an already persisted Dish instance to the form:
// I'm just writing to explain the flow.
// Accessing the object repository in action level is not a good practice.
// Use a DishService for example.
$id = 32; // Grab it from route or elsewhere
$repo = $entityManager->getRepository('Dishes\Entity\Dish');
$existingDish = $repo->findOne((int) $id);
$form->setHydrator($hydrator)
->setObject($existingDish)
->setInputFilter($filter);
I'm assuming this is edit action for an existing Dish.
So, the hydrator will correctly handle both changed and untouched fields on next call since you give an already populated Dish instance via the form.
I also recommend fetching the DishFilter from the InputFilterManager instead of creating it manually in action level:
// $filter = new DishFilter();
$filter = $serviceLocator->get('InputFilterManager')->get(DishFilter::class);
// Exclude the photo on validation:
$filter->setValidationGroup('name', 'description', 'time', 'complexity');
Hope it helps.
I create Event Listener for preUpdate of Post entity, that triggered fine, but when I try to update the related entity Category, it thrown an error:
Field "category" is not a valid field of the entity "BW\BlogBundle\Entity\Post" in PreUpdateEventArgs.
My Event Listener code is:
public function preUpdate(PreUpdateEventArgs $args) {
$entity = $args->getEntity();
$em = $args->getEntityManager();
if ($entity instanceof Post) {
$args->setNewValue('slug', $this->toSlug($entity->getHeading())); // work fine
$args->setNewValue('category', NULL); // throw an error
// other code...
My Post entity code is:
/**
* Post
*
* #ORM\Table(name="posts")
* #ORM\Entity(repositoryClass="BW\BlogBundle\Entity\PostRepository")
*/
class Post
{
/**
* #var string
*
* #ORM\Column(name="slug", type="string", length=255)
*/
private $slug;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="\BW\BlogBundle\Entity\Category")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
// other code
How can I update this Category entity in this EvenetListener together with Post entity like in my example?
This answer work, but only for Post changes. But I also need change some values of Category entity, for example:
$entity->getCategory()->setSlug('some-category-slug'); // changes not apply, nothing happens with Category entity.
I guess the method setNewValue only works for a field that has changed. Maybe your category is already NULL. That's why it's throw the error. Here's the sample code from documentation.
/**
* Set the new value of this field.
*
* #param string $field
* #param mixed $value
*/
public function setNewValue($field, $value)
{
$this->assertValidField($field);
$this->entityChangeSet[$field][1] = $value;
}
/**
* Assert the field exists in changeset.
*
* #param string $field
*/
private function assertValidField($field)
{
if ( ! isset($this->entityChangeSet[$field])) {
throw new \InvalidArgumentException(sprintf(
'Field "%s" is not a valid field of the entity "%s" in PreUpdateEventArgs.',
$field,
get_class($this->getEntity())
));
}