Doctrine not generating entity ID - symfony

I'm trying to create an entity, but i get a null value on the id of the entity, which is a generated value.
Controller :
if($buRepository->findOneBy(['buName' => $row['Bu1'], 'dateDeleted' => null]) > null)
elseif($buRepository->findOneBy(['buName' => $row['Bu1'], 'dateDeleted' => null]) === null)
{
$bu1 = new Bu();
$bu1->setBuName($row['Bu1']);
$bu1->setLastUpdated(new \DateTime('now'));
$bu1->setDateDeleted(null);
$bu1->setSrcId(3);
$bu1->setIdInSource($bu1->getBuId());
}
Entity :
/**
* #var int
*
* #ORM\Column(name="bu_id", type="bigint", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $buId;
Once my entity tries to flush i get an error on the buid, saying it can't be null.

The Buid isn't created and assign if the entity isn't flushed, the best way to go around it, is to put a trigger in place.
Since IdInSource can't be of NULL value but is suppose to be the BuId (no relation just assigning the INT value), i assigned it a value of 0.
For each entity with a IdInSource of 0 my database trigger replaces it with the BuId.

Try AUTO strategy which fall to AUTO_INCREMENT for MySQL

Related

Doctrine: set foreign relation

This is regarding a problem with Doctrine when I try to insert a record into a associative entity. Below is a simplified description of the problem.
I have two tables, let's call them One and Two. Table One has a foreign key to table Two, called twoId with a column two_id. Field two_id happens to be part of the primary key.
* #ORM\Id
* #ORM\Column(name="user_id", type="string", length=40)
*/
private $twoId;
/**
* #ManyToOne(targetEntity="[...]", inversedBy="[...]", fetch="EAGER")
* #JoinColumn(name="two_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $two;
I am trying to insert a new record into table A. This works:
$two = [.. read from DB ..];
$one = new One();
$one->setTwo($two);
$one->setTwoId($two->getId());
$em->persist($one);
$em->flush();
I don't like to call both setTwo and setTwoId. Furthermore, I don't like reading the $two record before referencing it.
If I skip setTwoId call, I get the error: Entity of type [..] is missing an assigned ID for field 'twoId'. The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called.
If I skip setTwo call, I get the error: Integrity constraint violation: 1048 Column 'two_id' cannot be null
My problems are:
How can I avoid calling both setTwo() and setTwoId()?
What if I want to reference a entity from Two without reading it? Should I use $em->getReference()? (PhpStorm doesn't even recognize it)
In case someone makes the same mistake:
As pointed out by #lordrhodos, declaring the field $twoId was wrong because Doctrine will create it automatically without having a definition.
Definition:
/**
* #ManyToOne(targetEntity="[...]", inversedBy="[...]", fetch="EAGER")
* #JoinColumn(name="two_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $two;
Usage:
$two = [.. read from DB ..];
$one = new One();
$one->setTwo($two);
$em->persist($one);
$em->flush();

Get element from ArrayCollection

I want to get 1 element out an ArrayCollection. I'm using Symfony 2.7.
For example i have an collection of the entity Activity:
$activities = $em->getRepository('AppBundle:Activity')->findAll();
Next i want to get 1 activity out of this ArrayCollection, based on a many-to-one relation.
The entity 'Activity':
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\ActivityRepository")
* #ORM\Table(name="activity")
*/
class Activity {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="ObjectElementTask", inversedBy="activities", cascade={"persist"})
* #ORM\JoinColumn(name="objectelementtask_id", referencedColumnName="id", onDelete="CASCADE")
*/
private $objectelementtask;
What did i try:
$objectElementTask = $em->getRepository('AppBundle:ObjectElementTask')->findOneBy(["active" => 1, "object" => (int)$objectId]);
$activity = $activities->findBy(['objectelementtask' => $objectElementTask]);
I get the following exception:
"Call to a member function findBy() on array"
I want to prevent querying the database foreach.
I also tried:
$activity = array_search($objectElementTask, array_column($activities, 'objectelementtask'));
But this has no result...
Thanks in advance!
There are a couple things that I can see here.
1) Doctrine queries return an array not an ArrayCollection. (https://stackoverflow.com/a/8237943/7745506). That's why you're getting the error on findBy. (I don't even think ArrayCollection has a findBy method. Here's the API and I don't see it: http://www.doctrine-project.org/api/common/2.3/class-Doctrine.Common.Collections.ArrayCollection.html) If this were an ArrayCollection, you might be able to use the filter method: http://www.doctrine-project.org/api/common/2.3/source-class-Doctrine.Common.Collections.ArrayCollection.html#377-387
2) If your Activity class has a ManyToOne relationship with the ObjectElementTask class then by definition, any search for an Activity by ObjectElementTask has the potential to return many Activities.
3) You say you don't want to query the DB in a foreach and you don't need to.
What you can do in this situation is query the DB for all Activities (it has the potential to be multiple because of item 2 above) for a specific ObjectElementTask.
$objectElementTask = $em->getRepository('AppBundle:ObjectElementTask')->findOneBy(["active" => 1, "object" => (int)$objectId]);
$activities = $em->getRepository('AppBundle:Activity')->findBy(['objectelementtask' => $objectElementTask]);
This will return all Activities that have that ObjectElementTask as their objectelementtask. You'll have to figure out WHICH Activity you want after that because this will be an array.

ModelManagerException: Failed to create object

I'm using Sonata Admin bundle. When I submit a "Create entity" form the ModelManagerException is thrown if the form has some empty fields.
I've tracked this down to the PDOException that is thrown first. The exception is thrown because my empty fields get null values, but my table does not allow null values. I don't want to have null fields, I want empty string instead. How do I tell Sonata Admin class to create new entity with empty strings instead of nulls for fields without values?
This is the query that throws the exception:
INSERT INTO some_table (name, username, email) VALUES (?, ?, ?)' with params [null, "my name", "test#mailinator.com"]
These are the exceptions:
ModelManagerException: Failed to create object: Acme\DemoBundle\Entity\SomeEntity
SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
PDOException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'name' cannot be null
I tried setting $this->name = ''; explicitly in SomeEntity constructor but it didn't help. The query still had null instead of ''. I tried overriding getNewInstance() on my Admin class to set name to '' value, but it didn't help either - query still had null instead.
The weird thing is that I can have empty fields on entity edit form - the entity updates fine and empty fields are saved correctly to the database. Why is this happening?
Example code:
<?php
namespace Acme\DemoBundle\Entity
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="some_table")
*/
class SomeEntity
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*
* We can't use Assert\NotNull()
* because Admin bundle uses null values for empty fields!
*/
protected $name;
// Other fields and methods.
}
you have two choices:
1- if you want the name field can be null, you need to edit the name annotation:
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $name;
2- if you want the name field be just an empty string when you create new instance, you just have to add the prePersist in your entityAdmin:
public function prePersist($object)
{
$object->setName('');
}
I hope it helps :)
There is some DBAL exception associated with this. Scroll lower on the symfony exception page and you'll see it.

Unable to retrieve records using a variable in Symfony2, but works if I don't use a variable?

I have three SQL statements in Symfony2, and they all use a variable that contains the record ID (which is passed through using the URL). The first SQL statement works correctly, however, the other two don't. They often result in errors like this:
An exception occurred while executing 'SELECT m0_.name AS name0,
m0_.created AS created1, m0_.event_date AS event_date2,
m0_.description AS description3, m0_.event_type AS event_type4,
m1_.surname AS surname5, m1_.first_name AS first_name6 FROM map_lists
m0_ LEFT JOIN map_list_members m2_ ON (m2_.list_id = m0_.id) LEFT JOIN
map_contacts m1_ ON (m1_.id = m2_.contact_id) WHERE m0_.branch_id = ?
AND m0_.event_type IS NOT NULL AND m1_.id = ? ORDER BY m0_.event_date
DESC' with params {"1":"0","2":{}}:
Catchable Fatal Error: Object of class Doctrine\ORM\Query could not be
converted to string in
F:\wamp\www\centredb\vendor\doctrine\dbal\lib\Doctrine\DBAL\Connection.php
line 1211
The two SQL statements in question are:
// Retrieve Test For That Member
$membertests = $dm->createQuery('
SELECT mt.id, mt.taken, mt.result, mtd.test, mtd.description
FROM InstructorBundle:MapTests mt
LEFT JOIN InstructorBundle:MapTestDescriptions mtd WHERE mtd.id = mt.testDescription
WHERE mt.contact = :member'
)->setParameter('member', '40264');
$memtest = $membertests->getResult();
// Retrieve Events For That Member
$memberevents = $dm->createQuery('
SELECT mli.name, mli.created, mli.eventDate, mli.description, mli.eventType, mc.surname, mc.firstName
FROM InstructorBundle:MapLists mli
LEFT JOIN InstructorBundle:MapListMembers mlm WHERE mlm.list = mli.id
LEFT JOIN InstructorBundle:MapContacts mc WHERE mc.id = mlm.contact
WHERE mli.branch = :centre
AND mli.eventType IS NOT NULL
AND mc.id = :member
ORDER BY mli.eventDate DESC'
)->setParameters(array(
'centre' => $centreid,
'member' => $member
));
$memevent = $memberevents->getResult();
Now, if I remove $member from the Parameters and replace it with the record ID that I'm using during development these SQL statements work. Obviously this isn't ideal, so to find out why these SQL statements fail when using the same variable that the 3rd uses is vital.
The 3rd SQL statement, for reference, is:
// Retrieve Member Details
$member = $dm->createQuery('
SELECT mc.id, mc.surname, mc.firstName
FROM InstructorBundle:MapSubscriptions msu
LEFT JOIN InstructorBundle:MapContacts mc WHERE msu.contact = mc.id
LEFT JOIN InstructorBundle:MapFamilies mf WHERE mc.family = mf.id
WHERE mc.id = :member'
)->setParameter('member', $member);
I've looked at the Entity's of the two tables and the two fields that $member is used to recover the data from. They look like this:
MapTests mt
/**
* #var \MapContacts
*
* #ORM\ManyToOne(targetEntity="MapContacts")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="contact_id", referencedColumnName="id")
* })
*/
private $contact;
/**
* Set contact
*
* #param \Acme\InstructorBundle\Entity\MapContacts $contact
* #return MapTests
*/
public function setContact(\Acme\InstructorBundle\Entity\MapContacts $contact = null)
{
$this->contact = $contact;
return $this;
}
/**
* Get contact
*
* #return \Acme\InstructorBundle\Entity\MapContacts
*/
public function getContact()
{
return $this->contact;
}
MapContacts mc
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
I can't figure it out, it seems fine to me. But obviously something is stopping this from working.
Is your $member variable an object? You need to use integer id as parameter in your query, so replace $member with $member->getId()
This is a really weird issue, as the $member variable works with one query but wouldn't with the other two. However, adding this code seemed to have fixed the issue:
$memberint = intval($member);
This seemed to fix it. The errors dissappeared. However, it doesn't explain really why $member will work with one query and not the other two.
I recommend use dsl, the query language provided by Doctrine. Your second query looks like:
$dm->createQuery()
->select('mli.name, mli.created, mli.eventDate, mli.description, mli.eventType, mc.surname, mc.firstName')
->from('InstructorBundle:MapLists', 'mli')
->leftJoin('mli.list', 'mlm')
->leftJoin('mlm.contact', 'mc')
->where('mli.branch = :centre')
->andWhere('mli.eventType IS NOT NULL')
->andWhere('mc.id = :member')
->orderBy('mli.eventDate', 'DESC')
->setParameters(array(
'centre' => $centreid,
'member' => $member
));
Look at examples here - http://docs.doctrine-project.org/en/latest/reference/dql-doctrine-query-language.html
Everywhere where id is used as a parameter, it is used as integer. And in database, id is always an integer. Doctrine needs your $member as an integer, whereas you are passing a numeric string. I assume that the portion of the code where Doctrine verifies parameters has some type of bug. I suggest that you report this bug at #doctrine channel on freenode.
As a solution for your code, just do typecasting for your $member variable either with intval($member) or (int)$member right in your query.

Composite key and form

I have the following associations in my database (simplified version):
This is a Many-To-Many association but with an attribute on the joining table, so I have to use One-To-Many/Many-To-One associations.
I have a form where I can add as many relations as I want to one order item and create it at the same time (mainly inspired by the How to Embed a Collection of Forms tutorial from the documentation.
When I post the form, I get the following error:
Entity of type TEST\MyBundle\Entity\Relation has identity through
a foreign entity TEST\MyBundle\Entity\Order, however this entity
has no identity itself. You have to call EntityManager#persist() on
the related entity and make sure that an identifier was generated
before trying to persist 'TEST\MyBundle\Entity\Relation'. In case
of Post Insert ID Generation (such as MySQL Auto-Increment or
PostgreSQL SERIAL) this means you have to call EntityManager#flush()
between both persist operations.
I understand this error because Doctrine tries to persist the Relation object(s) related to the order since I have the cascade={"persist"} option on the OneToMany relation. But how can I avoid this behavior?
I have tried to remove cascade={"persist"} and manually persist the entity, but I get the same error (because I need to flush() order to get the ID and when I do so, I have the same error message).
I also tried to detach() the Relation objects before the flush() but with no luck.
This problem seems unique if 1) you are using a join table with composite keys, 2) forms component, and 3) the join table is an entity that is being built by the form component's 'collection' field. I saw a lot of people having problems but not a lot of solutions, so I thought I'd share mine.
I wanted to keep my composite primary key, as I wanted to ensure that only one instance of the two foreign keys would persist in the database. Using
this entity setup as an example
/** #Entity */
class Order
{
/** #OneToMany(targetEntity="OrderItem", mappedBy="order") */
private $items;
public function __construct(Customer $customer)
{
$this->items = new Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class Product
{
/** #OneToMany(targetEntity="OrderItem", mappedBy="product") */
private $orders;
.....
public function __construct(Customer $customer)
{
$this->orders = new Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class OrderItem
{
/** #Id #ManyToOne(targetEntity="Order") */
private $order;
/** #Id #ManyToOne(targetEntity="Product") */
private $product;
/** #Column(type="integer") */
private $amount = 1;
}
The problem I was facing, if I were building an Order object in a form, that had a collection field of OrderItems, I wouldn't be able to save OrderItem entity without having saved the Order Entity first (as doctrine/SQL needs the order id for the composite key), but the Doctrine EntityManager wasn't allowing me to save the Order object that has OrderItem attributes (because it insists on saving them en mass together). You can't turn off cascade as it will complain that you haven't saved the associated entities first, and you cant save the associated entities before saving Order. What a conundrum. My solution was to remove the associated entities, save Order and then reintroduce the associated entities to the Order object and save it again. So first I created a mass assignment function of the ArrayCollection attribute $items
class Order
{
.....
public function setItemsArray(Doctrine\Common\Collections\ArrayCollection $itemsArray = null){
if(null){
$this->items->clear();
}else{
$this->items = $itemsArray;
}
....
}
And then in my Controller where I process the form for Order.
//get entity manager
$em = $this->getDoctrine()->getManager();
//get order information (with items)
$order = $form->getData();
//pull out items array from order
$items = $order->getItems();
//clear the items from the order
$order->setItemsArray(null);
//persist and flush the Order object
$em->persist($order);
$em->flush();
//reintroduce the order items to the order object
$order->setItemsArray($items);
//persist and flush the Order object again ):
$em->persist($order);
$em->flush();
It sucks that you have to persist and flush twice (see more here Persist object with two foreign identities in doctrine). But that is doctrine for you, with all of it's power, it sure can put you in a bind. But thankfully you will only have to do this when creating a new object, not editing, because the object is already in the database.
You need to persist and flush the original before you can persist and flush the relationship records. You are 100% correct in the reason for the error.
I assume from the diagram that you are trying to add and order and the relation to the contact at the same time? If so you need to persist and flush the order before you can persist and flush the relationship. Or you can add a primary key to the Relation table.
I ended up creating a separated primary key on my Relation table (instead of having the composite one).
It looks like it is a dirty fix, and I am sure there is a better way to handle this situation but it works for now.
Here is my Relations entity:
/**
* Relation
*
* #ORM\Entity
*/
class Relation
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Contact", inversedBy="relation")
*/
protected $contact;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="relation")
*/
protected $order;
/**
* #var integer
*
* #ORM\Column(name="invoice", type="integer", nullable=true)
*/
private $invoice;
//Rest of the entity...
I then added the cascade={"persist"} option on the OneToMany relation with Order:
/**
* Orders
*
* #ORM\Entity
*/
class Order
{
/**
* #ORM\OneToMany(targetEntity="Relation", mappedBy="order", cascade={"persist"})
*/
protected $relation;
//Rest of the entity...
Et voilĂ !

Resources