I have a bi-directional OneToMany relationship using Doctrine 2 as an ORM within the ZendFramework 1.11.2.
Note: Doctrine did not create the database tables. The database is MySQL.
For some reason, when I persist and flush a new link entity to the link table (see below) the foreign key field (container_id) gets set to NULL. However, if the '#' symbol is removed from the 'ManyToOne(targetEntity="Shepherd\Navigation\Domain\Container\Model", inversedBy="links")' line, the foreign key field is populated properly.
Since the entity is added properly to the database when the '#' symbol is removed, there is something wrong with the OneToMany relationship somewhere.
For example, if I have a link model named $link (see pseudo-code below)...
$link (Shepherd\Navigation\Domain\Link\Model)
{
id: '' // auto generated value
cid: 23 // the foreign key value
label: test
uri: test.com
... // other values not listed here for brevity
}
...when the new link model is persisted and the entity manager is flushed, the container_id (foreign key) value from the newly inserted row in the link (shepherd_navigation_link) table is NULL.
$em // Assume $em is the Entity Manager
$em->persist($link);
$em->flush();
// The container_id in the newly added row in the
// link table (shepherd_navigation_link) is NULL
The link table schema:
CREATE TABLE `shepherd_navigation_link` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`container_id` int(10) unsigned DEFAULT NULL,
`node_id` int(10) unsigned DEFAULT NULL,
`parent_id` int(10) unsigned DEFAULT NULL,
`label` varchar(100) NOT NULL,
`options` text,
`events` text,
`privilege` varchar(100) NOT NULL,
`resource` varchar(100) DEFAULT NULL,
`uri` varchar(300) NOT NULL,
`visible` int(10) unsigned DEFAULT '1',
PRIMARY KEY (`id`),
KEY `container_id` (`container_id`)
) ENGINE=InnoDB
ALTER TABLE `shepherd_navigation_link` ADD FOREIGN KEY (container_id) REFERENCES shepherd_navigation_container(id)
Link entity model:
/**
* #Entity
* #Table(name="shepherd_navigation_link")
*/
class
{
/**
* #Id
* #Column(type="integer")
* #GeneratedValue
*/
protected $id;
/**
* #Column(name="container_id", type="integer", nullable=false)
*/
protected $cid;
/**
* #Column(name="node_id", type="integer")
*/
protected $nid;
/**
* #Column(name="parent_id", type="integer", nullable=false)
*/
protected $pid;
/**
* #Column
*/
protected $label;
/**
* #Column(nullable=true)
*/
protected $options;
/**
* #Column(nullable=true)
*/
protected $events;
/**
* #Column
*/
protected $privilege;
/**
* #Column(nullable=true)
*/
protected $resource;
/**
* #Column
*/
protected $uri;
/**
* #Column(type="integer", nullable=true)
*/
protected $visible;
/**
* #OneToMany(targetEntity="Model", mappedBy="parent")
*/
private $children;
/**
* #ManyToOne(targetEntity="Model", inversedBy="children")
*/
private $parent;
/**
*) #ManyToOne(targetEntity="Shepherd\Navigation\Domain\Container\Model", inversedBy="links"
*/
private $container;
/**
* #OneToOne(targetEntity="Shepherd\Navigation\Domain\Link\Position", inversedBy="link")
*/
private $node;
public function __construct()
{
$this->children = new \Doctrine\Common\Collections\ArrayCollection();
}
/** Accessors and Mutators excluded for brevity **/
}
Note: the protected property $cid maps to the container_id column above.
The container table schema:
CREATE TABLE `shepherd_navigation_container` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`description` text,
PRIMARY KEY (`id`)
) ENGINE=InnoDB
The container entity model:
/**
* #Entity
* #Table(name="shepherd_navigation_container")
*/
class Model
{
/**
* #Id
* #Column(type="integer")
* #GeneratedValue
*/
protected $id;
/**
* #Column
*/
protected $name;
/**
* #Column(nullable=true)
*/
protected $description;
/**
* #OneToMany(targetEntity="Shepherd\Navigation\Domain\Link\Model", mappedBy="container")
*/
private $links;
/**
* Constructor
*/
public function __construct()
{
$this->links = new \Doctrine\Common\Collections\ArrayCollection();
}
/** Accessors and Mutators excluded for brevity **/
}
What am I missing? What am I doing wrong?
I figured out the problem (by reading the documentation http://www.doctrine-project.org/docs/orm/2.0/en/tutorials/getting-started-xml-edition.html). It turns out there were actually a few problems.
Problem 1 => I did not provide a method to set the container variable.
// Inside the Link Entity class...
public function setContainer($container)
{
$this->container = $container;
}
Problem 2 => I did not set the container value. In error, I thought Doctrine 2 did this internally, but I found out the container variable needs to be set prior to flushing.
Foolish oversight on my part.
$link = new Link();
$link->setContainer($container);
// $em is the Entity Manager
$em->persist($link);
$em->flush();
Problem 3 => The container ($container) needed to either be persisted prior to flushing or the #OneToMany definition on the container entity needed to change. I chose to update the container entity definition. Take a look here (http://www.doctrine-project.org/docs/orm/2.0/en/reference/working-with-associations.html#transitive-persistence-cascade-operations) for more information.
// Inside the Container Entity class...
/**
* #OneToMany(targetEntity="Shepherd\Navigation\Domain\Link\Model", mappedBy="container", cascade={"persist"})
*/
After making these changes and removing the #OneToOne node relationship in the link entity class (turns out I didn't need it), everything worked fine. I hope this helps someone.
Related
I have two fields : account_type_id and account_id.
How can i manually map doctrine TargetEntity to join Company Entity if accountTypeId = 1 OR join User Entity if account_type_id = 2 ?
<?php
/** #Entity */
class Accounts
{
// 1= Company, 2 = User
private $accountType;
/**
* #ManyToOne(targetEntity="Companies")
*/
private $company;
/**
* #ManyToOne(targetEntity="Users")
*/
private $user;
//...
}
Unfortunately, joining different columns on the fly cannot be done automatically, but you can have both fields set as nullable and only set the correct one when persisting the Account entity.
This would be the annotation:
/**
* #ORM\ManyToOne(targetEntity="Users", inversedBy="users")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
*/
private $user;
Keep in mind that nullable=true is the default anyway, I'm just being specific here.
If you want to go defensively about this, you can have an additional check in getter
/**
* #return User
* #throws \Exception
*/
public function getUser()
{
if ($this->accountType !== 2) {
throw new \Exception("Entity is not of type 'user'");
}
return $this->user;
}
How can I set the task_id on the TaskLine entity?
Similar Questions:
Doctrine: How to insert foreign key value
Best practice for inserting objects with foreign keys in symfony2
Doctrine 2 entity association does not set value for foreign key
I get this error:
Neither the property "task_id" nor one of the methods "getTaskId()", "taskId()", "isTaskId()", "hasTaskId()", "__get()" exist and have public access in class "AppBundle\Entity\TaskLine"
NOTE: Normally I work with PropelORM.
I'm trying to save a new TaskLine entity which is related to Task entity. I'm posting JSON payload which look something like this.
{
"id": null,
"task_id": 1,
"note" : "new note"
}
In the controller I json_decode the request payload and submit that to $form->submit($note_data), $form is an instance of:
class TaskNoteType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add( 'task_id', NumberType::class )
->add( 'note', TextType::class )
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\TaskLine'
));
}
}
Here is my Task entity
class Task
{
/**
* #var string
*
* #ORM\Column(name="description", type="string", length=150, nullable=true)
*/
private $description;
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
}
TaskLine entity
class TaskLine
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var \AppBundle\Entity\Task
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Task")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="task_id", referencedColumnName="id")
* })
*/
private $task;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set task
*
* #param \AppBundle\Entity\Task $task
*
* #return TaskLine
*/
public function setTask(\AppBundle\Entity\Task $task = null)
{
$this->task = $task;
return $this;
}
/**
* Get task
*
* #return \AppBundle\Entity\Task
*/
public function getTask()
{
return $this->task;
}
}
I found my answer in here:
Best Practice for inserting objects with foreign keys in Symfony2
Answered by: Tuan nguyen
In ORM you have to set Foreign key by an object which your entity
associated with. You could use EntityManager#getReference to get a
reference to category entity without loading it from DB. Like this
$category = $entityManager->getReference('YourProject\Entity\Category', $categoryId);
$product = new Product();
$product->setCategory($category);
Similar questions:
Doctrine: How to insert foreign key value
Following function you should have already in your Slider entity (or
similar).
public function addImage(Image $image) {
$image->setSlider($this); // This is the line you're probably looking for
$this->images[] = $image;
return $this; }
What it does is if you persist the entity it writes the ID of the Slider (sid) into your Image.
Doctrine 2 entity
association does not set value for foreign key
I found something in the Doctrine 2 documentation:
Changes made only to the inverse side of an association are ignored.
Make sure to update both sides of a bidirectional association (or at
least the owning side, from Doctrine’s point of view) As in my case
the owning side is the User I must update it. Doctrine 1 was able to
manage it automatically... too bad.
I have two simple entities connected to each other with a OneToMany association.
/**
* #ORM\Entity(repositoryClass="Shopware\CustomModels\JoeKaffeeAbo\Client")
* #ORM\Table(name="joe_kaffee_abo_client")
*/
class Client extends ModelEntity
{
public function __construct() {
$this->abos = new ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="Shopware\CustomModels\JoeKaffeeAbo\Abo", mappedBy="client", cascade= "all")
* #var \Doctrine\Common\Collections\ArrayCollection
*/
protected $abos;
public function addAbo($abo)
{
//$this->abos[] = $abo;
$this->abos->add($abo);
}
The second one is Abo:
/**
* #ORM\Entity(repositoryClass="Shopware\CustomModels\JoeKaffeeAbo\Abo")
* #ORM\Table(name="joe_kaffee_abo_abos")
*/
class Abo extends ModelEntity
{
/**
* #ORM\ManyToOne(targetEntity="Shopware\CustomModels\JoeKaffeeAbo\Client",inversedBy="abos")
* #ORM\JoinColumn(name="clientId", referencedColumnName="id")
*/
protected $client;
}
Somehow the join column gets not filled - it seems that doctrine ignores the association I set up. And if I call $client->addAbo($abo) there gets not relation created...and I cant receive the added abos via a getter function which returns the abo array collection.
I have two tables/entities client and site that have a many to many relationship combined by a join table client_site. Here are how my entities are setup.
Here is the client table entity
/**
* #Entity
* #Table(name="client")
*/
class Client
{
/**
* #Id #Column(type="bigint")
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ManyToMany(targetEntity="Site", inversedBy="clients")
* #JoinTable(name="client_site",
* joinColumns={#JoinColumn(name="c_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="s_id", referencedColumnName="id")}
* )
*/
private $sites;
And the site table entity
/**
* #Entity
* #Table(name="site")
*/
class Site
{
/**
* #Id #Column(type="bigint")
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ManyToMany(targetEntity="Client", mappedBy="sites")
*/
private $clients;
This is the client_site table entity
/**
* #Entity
* #Table(name="client_site",indexes={#index(name="FK_client_site",columns={"c_id"}),#index(name="FK_client_site_2",columns={"s_id"})})
*/
class ClientSite
{
/**
* #Id
* #ManyToOne(targetEntity="Client", inversedBy="ClientSite")
*/
private $client;
/**
* #Id
* #ManyToOne(targetEntity="Site", inversedBy="ClientSite")
*/
private $site;
This is the query I am trying to run
$query = Zend_Registry::get('em')
->createQuery('SELECT c, s
FROM Application\Models\Client c
JOIN c.sites s
WHERE c.is_active = 1');
$clients = $query->getResult();
And this is my error
No identifier/primary key specified for Entity 'Application\Models\ClientSite'. Every Entity must have an identifier/primary key.
I put the #Id on both fields in the ClientSite entity, as they are the composite primary keys for my joiner table. Can this not be done in Doctrine2? If it can't, what are my alternative options.
If you CAN do this, what have I done incorrectly?
This isn't currently supported by Doctrine 2.
However, they are working on adding support for this in an experimental branch. Apparently this might be included in the 2.1 release if it works without issues.
I'm trying to make something very simple.. but I do wrong, and I don't know what is the problem. Just I'm trying to insert new item to database with Doctrine 2:
$favouriteBook = new UserFavouriteBook;
$favouriteBook->user_id = 5;
$favouriteBook->book_id = 8;
$favouriteBook->created_at = new DateTime("now");
$this->_em->persist($favouriteBook);
$this->_em->flush();
As you can see.. is very simple, but that, give me next error:
Error: Message: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'user_id' cannot be null
Obviosly, if I make a "dump" before "persist" and "flush" of $favouriteBook, all looks be correct..
This is my "favouriteBook" entity:
/** #Column(type="integer")
* #Id
*/
private $user_id;
/** #Column(type="integer")
* #Id
*/
private $book_id;
/**
* #ManyToOne(targetEntity="Book", inversedBy="usersFavourite")
* #JoinColumn(name="book_id", referencedColumnName="id")
*/
private $book;
/**
* #ManyToOne(targetEntity="User", inversedBy="favouriteBooks")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/** #Column(type="datetime") */
private $created_at;
public function __get($property) {
return $this->$property;
}
public function __set($property, $value) {
$this->$property = $value;
}
Anyone can image what is the problem? .. I don't know what else try.. Thanks
I think what beberlei is saying is that within your favouriteBook entity, you don't need to define the user_id and book_id as class properties, b/c the book and user properties you have set already recognize these as the relevant join columns. Also, your attempt to persist the favouriteBook entity failed because you need to set the book and user entity associations within the favouriteBook entity, not the foreign keys. So it would be:
$favouriteBook = new UserFavouriteBook;
$favouriteBook->book = $book;
$favouriteBook->user = $user;
$favouriteBook->created_at = new DateTime("now");
$this->_em->persist($favouriteBook);
$this->_em->flush();
You are mapping foreign keys and the associations. You have to modify the association not the foreign key field. Its bad-practice to map them both, you should remove $book_id and $user_id completly.