2

This causes an error:

$em = $this->getDoctrine()->getManager();
$courses = $em->getRepository(Course::class)->findBy(['id' => $ids]);

foreach ($courses as $course) {
    $data = $form->getData();
    $course->setProperties($data);
    $em->persist($course);
}

$em->flush();

The followin error is thown:

Type error: 
Argument 3 passed to Doctrine\ORM\Event\PreUpdateEventArgs::
 __construct() must be of the type array, null given, called in:

/var/www/bib/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 1064

But when I insert $em->flush(); In a cycle - everything works. What wrong?

Course entity:

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

use CoreBundle\Entity\GuidTrait;
use CoreBundle\Entity\Typo3Trait;
use Knp\DoctrineBehaviors\Model\Timestampable\Timestampable;
use CoreBundle\Entity\LoggableTrait;

/**
 * @ORM\Entity(repositoryClass="AppBundle\Repository\CourseRepository")
 * @ORM\Table(name="courses")
 * @ORM\HasLifecycleCallbacks()
 */
class Course
{
    use GuidTrait, Typo3Trait, Timestampable, LoggableTrait;

    const STATUS_INACTIVE = 0;
    const STATUS_ACTIVE = 1;

    /**
     * @ORM\Column(type="string", length=150, nullable=true)
     */
    protected $title;

    /**
     * @Assert\NotBlank()
     * @ORM\Column(type="string")
     */
    protected $code;

    /**
     * @Assert\NotBlank()
     * @ORM\Column(name="`order`", type="integer")
     */
    protected $order;

    /**
     * @Assert\NotBlank()
     * @ORM\Column(type="smallint")
     */
    protected $status;

    /**
     * @Assert\NotBlank()
     * @Assert\DateTime()
     * @ORM\Column(type="datetime", nullable=false)
     */
    protected $date;

    /**
     * @Assert\NotBlank()
     * @ORM\Column(type="text", nullable=false)
     */
    protected $enrolmentDetails;

    /**
     * @Assert\NotBlank()
     * @ORM\Column(type="text", nullable=false)
     */
    protected $durationDetails;

    /**
     * @Assert\NotBlank()
     * @ORM\Column(type="text", nullable=false)
     */
    protected $timetable;

    /**
     * @Assert\NotBlank()
     * @ORM\Column(type="string", nullable=false)
     */
    protected $contactName;

    /**
     * @Assert\NotBlank()
     * @Assert\Email
     * @ORM\Column(type="string", nullable=false)
     */
    protected $contactEmail;

    /**
     * @Assert\NotBlank()
     * @ORM\Column(type="string", nullable=false)
     */
    protected $contactPhone;

    /**
     * @ORM\Column(type="string", nullable=true)
     */
    protected $contactFax;

    /**
     * @Assert\NotBlank()
     * @Assert\Type(type="float")
     * @Assert\GreaterThanOrEqual(0)
     *
     * @ORM\Column(type="decimal", precision=8, scale=2, nullable=false)
     */
    protected $price;

    /**
     * @Assert\NotBlank()
     * @Assert\GreaterThanOrEqual(0)
     * @Assert\Type(type="integer")
     *
     * @ORM\Column(type="integer", nullable=false)
     */
    protected $availability;

    /**
     * @ORM\Column(type="text", nullable=true)
     */
    protected $courseNotes;

    /**
     * @ORM\ManyToOne(targetEntity="Centre", inversedBy="courses")
     * @ORM\JoinColumn(name="centre_id", referencedColumnName="uid")
     */
    protected $centre;

    /**
     * @Assert\NotBlank()
     * @ORM\ManyToOne(targetEntity="Qualification", inversedBy="courses")
     * @ORM\JoinColumn(name="qualification_id", referencedColumnName="uid",onDelete="CASCADE")
     */
    protected $qualification;

    /**
     * @Assert\NotBlank()
     * @ORM\ManyToOne(targetEntity="Venue", inversedBy="courses")
     * @ORM\JoinColumn(name="venue_id", referencedColumnName="uid")
     */
    protected $venue;

    /**
     * @ORM\OneToMany(targetEntity="Booking", mappedBy="course", cascade={"remove"})
     */
    protected $bookings;

    /**
     * @ORM\Column(type="string", nullable=false)
     */
    protected $reference;

    public function __construct()
    {
        $this->status = self::STATUS_ACTIVE;
        $this->code = 'CODE';
        $this->order = 1;
    }

    /**
     * @ORM\PreFlush
     */
    public function updateReference()
    {
        $q = $this->getQualification()->getCode();
        $c = $this->getCentre()->getCode();
        $v = $this->getVenue()->getCode();
        $d = $this->getDate()->format('d/m/Y');
        $this->setReference("$q - $c - $v - $d");
    }

     /**
     * @return mixed
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * @param $title
     * @return $this
     */
    public function setTitle($title)
    {
        $this->title = $title;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getCode()
    {
        return $this->code;
    }

    /**
     * @param $code
     * @return $this
     */
    public function setCode($code)
    {
        $this->code = $code;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getOrder()
    {
        return $this->order;
    }

    /**
     * @param $order
     * @return $this
     */
    public function setOrder($order)
    {
        $this->order = $order;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getStatus()
    {
        return $this->status;
    }

    /**
     * @param $status
     * @return $this
     */
    public function setStatus($status)
    {
        $this->status = $status;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getDate()
    {
        return $this->date;
    }

    /**
     * @param $date
     * @return $this
     */
    public function setDate($date)
    {
        $this->date = $date;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getEnrolmentDetails()
    {
        return $this->enrolmentDetails;
    }

    /**
     * @param $enrolmentDetails
     * @return $this
     */
    public function setEnrolmentDetails($enrolmentDetails)
    {
        $this->enrolmentDetails = $enrolmentDetails;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getDurationDetails()
    {
        return $this->durationDetails;
    }

    /**
     * @param $durationDetails
     * @return $this
     */
    public function setDurationDetails($durationDetails)
    {
        $this->durationDetails = $durationDetails;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getTimetable()
    {
        return $this->timetable;
    }

    /**
     * @param $timetable
     * @return $this
     */
    public function setTimetable($timetable)
    {
        $this->timetable = $timetable;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getContactName()
    {
        return $this->contactName;
    }

    /**
     * @param $contactName
     * @return $this
     */
    public function setContactName($contactName)
    {
        $this->contactName = $contactName;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getContactEmail()
    {
        return $this->contactEmail;
    }

    /**
     * @param $contactEmail
     * @return $this
     */
    public function setContactEmail($contactEmail)
    {
        $this->contactEmail = $contactEmail;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getContactPhone()
    {
        return $this->contactPhone;
    }

    /**
     * @param $contactPhone
     * @return $this
     */
    public function setContactPhone($contactPhone)
    {
        $this->contactPhone = $contactPhone;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getContactFax()
    {
        return $this->contactFax;
    }

    /**
     * @param $contactFax
     * @return $this
     */
    public function setContactFax($contactFax)
    {
        $this->contactFax = $contactFax;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getPrice()
    {
        return $this->price;
    }

    /**
     * @param $price
     * @return $this
     */
    public function setPrice($price)
    {
        $this->price = $price;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getAvailability()
    {
        return $this->availability;
    }

    /**
     * @param $availability
     * @return $this
     */
    public function setAvailability($availability)
    {
        $this->availability = $availability;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getCourseNotes()
    {
        return $this->courseNotes;
    }

    /**
     * @param $courseNotes
     * @return $this
     */
    public function setCourseNotes($courseNotes)
    {
        $this->courseNotes = $courseNotes;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getCentre()
    {
        return $this->centre;
    }

    /**
     * @param $centre
     * @return $this
     */
    public function setCentre($centre)
    {
        $this->centre = $centre;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getQualification()
    {
        return $this->qualification;
    }

    /**
     * @param $qualification
     * @return $this
     */
    public function setQualification($qualification)
    {
        $this->qualification = $qualification;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getVenue()
    {
        return $this->venue;
    }

    /**
     * @param $venue
     * @return $this
     */
    public function setVenue($venue)
    {
        $this->venue = $venue;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getReference()
    {
        return $this->reference;
    }

    /**
     * @param $reference
     * @return $this
     */
    public function setReference($reference)
    {
        $this->reference = $reference;
        return $this;
    }

    /**
     * @return mixed
     */
    public function getBookings()
    {
        return $this->bookings;
    }

    /**
     * @param mixed $bookings
     */
    public function setBookings($bookings)
    {
        $this->bookings = $bookings;
    }

    public function getVat( $amount = 0 )
    {
        if (empty($amount)) {
            return round( $this->price * $this->centre->getVat()->getRate()/100, 2 );
        } else {
            return round( $amount * $this->centre->getVat()->getRate()/100, 2 );
        }
    }

    public function setProperties(Course $course)
    {
        foreach ($this as $key=>$value) {
            if ($course->$key) {
                $this->$key = $course->$key;
            }
        }
    }
}

There is nothing supernatural in this entity. Does anyone have an answer to the question I asked?

Oleg Shleif
  • 710
  • 7
  • 24
  • 1
    Can you show us your Course entity content ? – LugiHaue May 18 '17 at 12:52
  • You can move $data = outside of the loop. And remove the persist line as there is no need to persist existing entities. Your Course::setProperties is potentially very dangerous. Perhaps you could post the code? Any doctrine listeners or unusual 3rd party bundles being used? – Cerad May 18 '17 at 12:55
  • I added the used entity – Oleg Shleif May 18 '17 at 13:01
  • What's the point setProperties? It probably overwrites the id field aswell? – ccKep May 18 '17 at 13:03
  • You use `@ORM\HasLifecycleCallbacks()`, do you have custom EventSubscriber implemented? – Łukasz D. Tulikowski May 18 '17 at 13:04
  • No, the id is taken into account and comes tested. In entity, only a few fields change – Oleg Shleif May 18 '17 at 13:05
  • Where is it taken into account? As far as I can see, you call setProperties and it just iterates over all fields and sets them? You might want to post your PreUpdate Subscriber aswell. – ccKep May 18 '17 at 13:08
  • ccKep: You understood correctly, but I did not quite understand about the subscriber – Oleg Shleif May 18 '17 at 13:15
  • Also: Remember that `findBy` might give you proxy entities. – ccKep May 18 '17 at 13:41
  • @OlegShleif See [Link](https://3v4l.org/nC0g4), your code overwrites all internal fields - including any id fields that are probably set. – ccKep May 18 '17 at 13:59
  • @ccKep No, the data that comes from the form does not have an id. If they were overwritten - I would not have `flush()` inside the loop – Oleg Shleif May 18 '17 at 14:04
  • Fair point on that one, I still think it's kind of hacky/lazy way to do that. Maybe you'll be using that on some edit form in the future, in that case the entity would have an id already and overwrite those - who knows. On your question though: You're using `@ORM\HasLifecycleCallbacks` so you're probably using an EventSubscriber of some sort? – ccKep May 18 '17 at 14:06
  • Keep in mind that there is nothing stopping some jolly joker from submitting an id (or any other field) just to mess with you. The fact that your form does not have one means nothing. It's actually bit confusing why you are even doing this at all. Use a Symfony form and the mapping takes care of itself. – Cerad May 18 '17 at 18:07

1 Answers1

1

There are many issues in Symfony and Doctrine when you execute the flush() method. For example, calling flush() inside a Doctrine listener is not a supported Doctrine usage. it means you are trying to nest several flushes inside each other, which can indeed break the unit of work.

There is a really complete examplample in this StackOverflow Answer, but I think it maybe not be enaugh to understand your problem, so if you want you can check all the posible scenarios where flush should fail here:

https://github.com/doctrine/doctrine2/issues/4004

Community
  • 1
  • 1
developer_hatch
  • 15,898
  • 3
  • 42
  • 75
  • Yep. While the entity in question may not be supernatural, there is a lot of magic going on. Doctrine events interact in so many unexpected ways that it's a bit of a miracle that things work even when you flush each entity individually. – Cerad May 18 '17 at 13:11
  • Absolutely @Cerad, sometimes it's necesary some little knowledge on the magical stuff that is goin on behind the pretty code is viewed – developer_hatch May 18 '17 at 13:14