Doctrine2 EntityNotFoundException when getting an entity from relation - symfony

In a Symfony2 project using Doctrine2. I have a Lead entity related with Promotion entity 1-N. A Lead con have a related Promotion or not.
//Lead.php
...
/**
* #var string $promotionCode
* #ORM\Column(name="promotion_code", type="string", length=16)
*/
private $promotionCode;
/**
* #var Promotion $promotion
* #ORM\ManyToOne(targetEntity="Promotion")
* #ORM\JoinColumn(name="promotion_code", referencedColumnName="id")
*/
private $promotion;
...
public function setPromotionCode($promotionCode) {
$this->promotionCode = $promotionCode;
}
public function getPromotionCode() {
return $this->promotionCode;
}
public function setPromotion($promotion) {
$this->promotion = $promotion;
}
public function getPromotion() {
return $this->promotion;
}
When I want to obtain the related promotion (if any) y do
$lead = $em->getRepository('BuvMarketplaceBundle:Lead')->find($id);
$promotion = $lead->getPromotion();
If the lead has a promotion this is OK. But if not this code returns a Promotion "entity", but when I try to use if I get an EntityNotFoundException.
So I have to test if the related promotion exists like this:
if (is_object($promotion) && method_exists($promotion, 'getDiscount')) {
try {
$promotion->getDiscount();
} catch(EntityNotFoundException $e) {
$promotion = null;
}
} else {
$promotion = null;
}
I know that I can use a findBy in the Promotion Repository, and may be another methods to check this.
But the question is if this is a bug or a feature in Doctrine2, so I'm getting a "false entity" when I think it may be a null.

Related

Type error with ArrayCollection / OneToMany relationship in Symfony 3.4

For the past couple of days I have been trying to create a bidirectionnal ManyToOne-OneToMany relationship in Symfony 3.4
I have two entities. One is Contribution and the other is Source. A Contribution can have several sources. So the relationship should be
Contribution – ManyToOne – Source – OneToMany – Contribution
But I keep getting the following error during $em→flush(); in my controller:
Type error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given, called in /var/www/html/Edebate/vendor/doctrine/orm/lib/Doctrine/ORM/UnitOfWork.php on line 605
I do not have any set method related to the Array Collection in my Entity Contribution as I could see in other posts here:
Type error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given
Symfony-Catchable Fatal Error: Argument 1 passed to Doctrine\Common\Collections\ArrayCollection::__construct() must be of the type array, object given
And the annotations are ok as mentionned here:
Doctrine OneToMany relationship error
Any help would be appreciate ! :)
Here is my Entity Contribution
use Doctrine\Common\Collections\ArrayCollection;
//annotations
abstract class Contribution
{
/**
* #ORM\OneToMany(targetEntity="Shaker\DebateBundle\Entity\Source", mappedBy="parent")
*/
protected $sources;
//Other attributes and methods
public function __construct() {
$this->sources = new ArrayCollection();
}
/**
* Add source
*
* #param \Shaker\DebateBundle\Entity\Source $source
*
* #return Contribution
*/
public function addSource(\Shaker\DebateBundle\Entity\Source $source)
{
$this->sources[] = $source;
return $this;
}
/**
* Remove source
*
* #param \Shaker\DebateBundle\Entity\Source $source
*/
public function removeSource(\Shaker\DebateBundle\Entity\Source $source)
{
$this->sources->removeElement($source);
}
/**
* Get sources
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getSources()
{
return $this->sources;
}
}
And this is in my Entity Source:
/**
* #ORM\ManyToOne(targetEntity="Shaker\DebateBundle\Entity\Contribution", inversedBy="sources")
*/
protected $parent;
/**
* Set parent
*
* #param \Shaker\DebateBundle\Entity\Contribution $parent
*
* #return Contribution
*/
public function setParent(\Shaker\DebateBundle\Entity\Contribution $parent = null)
{
$this->parent = $parent;
$parent->addSource($this);
return $this;
}
/**
* Get parent
*
* #return \Shaker\JRQBundle\Entity\Contribution
*/
public function getParent()
{
return $this->parent;
}
And in my Controller, the problem arises with flush:
$formsourcebook->handleRequest($request);
$contributionid=$formsourcebook->get('ContributionId')->getData();
if ($formsourcebook->isValid()) {
$topicargtarget=$this->getContribution($contributionid);
$sourcebook->setUser($user);
$sourcebook->setContribution($topicargtarget);
$em->persist($sourcebook);
$em->flush();
}
I don't know your question very well. However, did you try with this syntax in the Source entity?
private $parent;
// ...
public function __construct() {
$this->parent = new ArrayCollection();
// or new \Doctrine\Common\Collections\ArrayCollection();
}
I think you're forgetting the constructor in the class.
I think you "switched" some logic when working with collections. Here's how I think your "add" method should look like:
public function addSource(\Shaker\DebateBundle\Entity\Source $source)
{
$this->sources[] = $source;
$source->setParent($this);
return $this;
}
And in the other entity:
public function setParent(\Shaker\DebateBundle\Entity\Contribution $parent = null)
{
$this->parent = $parent;
return $this;
}
There are missing variables in your controller snippet, together with the form fields definitions, so you shouldn't work that much after submitting the form. Try to directly map as many fields as you can (even via autoguessing), and even if it looks ugly, but works, but then you can beautify later. Just my two cents with several months of delay.

Reference setting value in entity symfony

I have a doubt about code organization using symfony3 and doctrine: I'll try to explain as clear as I can. Let's say I have a FootballClub entity:
class FootballClub
{
// other code
private $memberships;
public function addMembership(Membership $membership) : FootballClub
{
$this->memberships[] = $membership;
return $this;
}
public function removeMembership(Membership $membership) : bool
{
return $this->memberships->removeElement($membership);
}
}
The entity is in a many-to-one relationship with another entity, Membership, which represents the contract a player has with the club. Let's say each club
has only a limited number of membership it can acquire, number that is represented as a setting, for example, as a property in a Setting entity.
The question is: how should I reference that setting when removing a membership from the club and check that is respected? Entities should not have any dependency, so what would be the correct way to implement this? A service? can you provide an example? Thank you for your time.
You could create a Settings entity, linked in OneToOne relation with FootballCluc entity.
Define Settings like this and instanciate it in the FootballClub's constructor
Settings entity
/** #Entity */
class Settings
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer")
*/
private $maxMembership;
// Other configurable properties ...
__constructor($maxMembership = 50)
{
$this->maxMembership = $maxMembership;
}
public function getMaxMembership()
{
return $this->maxMembership;
}
public function setMaxMembership($maxMembership)
{
$this->maxMembership = $maxMembership;
}
}
Football Entity
class FootballClub
{
/**
* One FootballClub has One Settings.
* #OneToOne(targetEntity="Settings")
* #JoinColumn(name="settings_id", referencedColumnName="id")
*/
private $settings;
// other code
private $memberships;
__constructor(Settings $settings = null)
{
if (null === $settings) {
$settings = new Settings();
}
$this->settings = $settings;
}
public function addMembership(Membership $membership) : FootballClub
{
if ($this->settings->getMaxMembership() <= count($this->memberships)) {
// throw new Exception("Max number of membership reached"); Strict mode
// return false // soft mode
}
$this->memberships-> = $membership;
return $this;
}
public function removeMembership(Membership $membership) : bool
{
return $this->memberships->removeElement($membership);
}
}

Symfony: clone object with Datetime field and add days to cloned object dates

I have an apparently simple problem but after several hours of try-and-error I only get wrong results.
The goal is to duplicate an object Convention with a OneToMany relation to HallReservation and add some days to this reservations. The result I get is, the days are added in the cloned objects and in the original objects, I want the original objects to remain unaltered.
This is the significant code:
/**
* #ORM\Entity()
*/
class Convention
{
/**
* #ORM\OneToMany(targetEntity="HallReservation", mappedBy="convention", cascade={"all"})
*/
private $hallReservation;
/**
* Clone object
*
*/
public function __clone()
{
if ($this->id)
{
$this->id=null;
$this->reference = null;
$this->registrationDate = new \Datetime('now');
foreach ($this->hallReservation as $hallR)
{
$clonedR = clone $hallR;
$clonedR->setConvention($this);
$newRDate = clone $hallR->getDate();
$clonedR->setDate($newRDate);
$this->hallReservation->add($clonedR);
}
}
}
/**
* #ORM\Entity()
*/
class HallReservation
{
/**
* #var \Date
*
* #ORM\Column(name="date", type="date")
*/
private $date;
/**
* Clone object
*
*/
public function __clone()
{
if ($this->id)
{
$this->id=null;
$clonedDate = clone $this->date;
$this->date = $clonedDate;
}
Controller code:
$jumpInterval = $originalConventionBeginDate->diff($newDate);
foreach ($newConvention->getHallReservation() as $newHallR)
{
$prevDate = clone $newHallR->getDate();
$prevDate->add($jumpInterval);
$newHallR->setDate($prevDate);
}
$em->persist($newConvention);
$em->flush();
As you see I clone datetime object everywhere but still the original convention hall reservations dates are also being added.
When you clone the Convention object you do not set the hallReservation property to a new Collection. A OneToMany relation in Doctrine is mapped to a Collection object, so when you clone the Convention object, you get a reference to the original hallReservation collection in the cloned object.
You could try something like this:
<?php
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Entity()
*/
class Convention
{
/**
* #ORM\OneToMany(targetEntity="HallReservation", mappedBy="convention", cascade={"all"})
*/
private $hallReservation;
/**
* Clone object
*
*/
public function __clone()
{
if ($this->id)
{
$this->id=null;
$this->reference = null;
$this->registrationDate = new \Datetime('now');
$reservations = new ArrayCollection();
foreach ($this->hallReservation as $hallR)
{
$clonedR = clone $hallR;
$clonedR->setConvention($this);
$newRDate = clone $hallR->getDate();
$clonedR->setDate($newRDate);
$reservations->add($clonedR);
}
$this->hallReservation = $reservations;
}
}

Symfony don't update date field

In my Controller, I want change two values in my entity (only for index "3) :
$cle->getVersions()[0]->getLots()[3]->setTantieme(97);
$cle->getVersions()[0]->getLots()[3]->setDateSuppression(new \DateTime);
dump($cle);
$em->flush();
But, only "Tantieme" value is changed. I don't understand. In my entity, I have :
/**
* #var string
*
* #ORM\Column(name="date_suppression", type="datetime", nullable=true)
*/
protected $date_suppression;
public function setDateSuppression($date_suppression)
{
$this->date_suppression = $date_suppression;
}
public function getDateSuppression()
{
return $this->date_supppression;
}
It is a trait . And it works great with others entities.
Dump result juste before flush :
Image
Tantieme is always updated, but date_suppression never ...
Try to return "something" in your setDateSuppression() method:
public function setDateSuppression($date_suppression)
{
$this->date_suppression = $date_suppression;
return $this;
}
EDIT:
Try like that :
$cle->getVersions()[0]->getLots()[3]->setDateSuppression(new \DateTime());

Applying a correct logic after successful validation in Symfony

First of I all, I created the whole example below specifically for this question because the actual example is very big so if it looks stupid then assume that it is not for now!
I'm trying to come up with a solution so that I can call a correct private method (bankA() or bankB()) in controller if the validation successfully passes. As you can see in the custom validation constraint, I only check the $bank->code property however the condition is not actually that simple (there is repository checks so on) - (as I said above, it is trimmed down version). So, could please someone tell me, how will I know that which private method I should call in controller after successful validation? I'm happy to create dedicated validators if necessary so open for suggestions and examples.
Note: I looked into symfony group validation documentation but didn't really get the picture how I could apply to my scenario.
EXAMPLE REQUEST
{ "id": 66, "code": "A" }
{ "id": 34, "code": "B" }
CONTROLLER
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* #Route("bank", service="application_frontend.controller.bank")
*/
class BankController extends Controller
{
private $validator;
public function __construct(
ValidatorInterface $validator
) {
$this->validator = $validator;
}
/**
* #param Request $request
*
* #Route("")
* #Method({"POST"})
*
* #throws Exception
*/
public function indexAction(Request $request)
{
$content = $request->getContent();
$content = json_decode($content, true);
$bank = new Bank();
$bank->id = $content['id'];
$bank->code = $content['code'];
$errors = $this->validator->validate($bank);
if (count($errors)) {
throw new Exception($errors[0]->getMessage());
}
// OK, validation has passed so which one do I call now ?!?!
$this->bankA($bank);
$this->bankB($bank);
}
private function bankA(Bank $bank)
{
// Do something nice with Bank
}
private function bankB(Bank $bank)
{
// Do something bad with Bank
}
}
BANK MODEL
use Application\FrontendBundle\Validator\Constraint as BankAssert;
/**
* #BankAssert\Bank
*/
class Bank
{
/**
* #var int
*/
public $id;
/**
* #var string
*/
public $code;
}
CUSTOM VALIDATOR
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class Bank extends Constraint
{
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
public function validatedBy()
{
return get_class($this).'Validator';
}
}
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class BankValidator extends ConstraintValidator
{
public function validate($bank, Constraint $constraint)
{
if ($bank->code == 'A') {
return;
}
if ($bank->code == 'B') {
return;
}
$this->context->buildViolation('Bank error')->addViolation();
}
}
Depending on how many codes there are you could either do...
if ('A' === $bank->getCode()) {
$this->bankA($bank);
} else {
$this->bankB($bank);
}
Or..
$method = 'bank'.$bank->getCode();
if (!method_exists($this, $method)) {
throw new \Exception('Method "'.$method.'" does not exist');
}
$this->$method();
All of that being said, it would be advisable to move all of this work into a dedicated service rather than in your controller. Then in your controller use something like...
$this->container->get('do_something_to_bank.service')->processAction($bank);

Resources