Doctrine 2 relationship is not working - php

I have Item entity:
/**
* Item
*
* #ORM\Table(name="item")
* #ORM\Entity
*/
class Item {
...
/**
* #var Unit
* #ORM\Column(name="unitId", type="integer")
* #ORM\ManyToOne(targetEntity="Unit", inversedBy="items")
* #ORM\JoinColumn(name="unitId", referencedColumnName="id")
*/
private $unit;
...
}
And Unit entity:
/**
* Unit
*
* #ORM\Table(name="unit")
* #ORM\Entity
*/
class Unit {
...
/**
* #var Item[]
* #ORM\OneToMany(targetEntity="Item", mappedBy="unit")
*/
private $items;
...
}
And a code like:
$item = $this->objectManager->getRepository('Application\Main\Entity\Item')->find($id);
$unitName = $item->getUnit()->getName();
Which produces error Fatal error: Call to a member function getName() on a non-object, what means that doctrine handles this field as simple field, not a relation field. What should I do to force Doctrine to use this field as a relationship? I have few entities with such issue, while other are working just fine. What is the reason?

vendor/doctrine/orm/lib/Doctrine/ORM/Mapping/Driver/AnnotationDrive.php, lines 262-263:
// Field can only be annotated with one of:
// #Column, #OneToOne, #OneToMany, #ManyToOne, #ManyToMany
So, it seems that you cannot use relation annotations when using #Column and visa versa. I had to remove #Column annotation and add #JoinColumn annotation and it started to work as expected.

Related

Retrieve nested object with OneToMany/ManyToOne doctrine relationship

I want to do a one to many / many to one relationship between two DAO.
After annoting properties, I have an unexpected and unlimited object in the result.
/**
* TicketSponsorDAO
*
* #ORM\Table(name="ticket_sponsor")
* #ORM\Entity
*/
class TicketSponsorDAO {
/**
* #var int
*
* #ORM\Column(name="ticket_id", type="integer")
*/
private $ticketId;
/**
* #ORM\ManyToOne(targetEntity="TicketDAO", inversedBy="sponsors")
* #ORM\JoinColumn(name="ticket_id", referencedColumnName="id")
*/
private $ticket;
...
}
And
/**
* TicketDAO
*
* #ORM\Table(name="ticket")
* #ORM\Entity
*/
class TicketDAO
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="TicketSponsorDAO", mappedBy="ticket")
*/
private $sponsors;
public function __construct() {
$this->sponsors = new ArrayCollection();
}
...
}
When I execute:
$sponsorEm = $em->getRepository(TicketDAO::class);
$spo = $sponsorEm->find("2");
var_dump($spo);
I have good properties about the ticket DAO, but the relation doesn't work and I have an unlimited object which is returned.
So in the MySQL database, I have the foreign key, the FK index and the primary key which are here.
The var_dump:
I follow this documentation: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-many-bidirectional
Use symfony dumper as you are having a circular reference.
dump($spo);
Hi var_dump() will return your the type of objects and classes.
you should try to fetch the propertes like this
$spo->getSponsers();
it will return you an array or may be an collection;
For me it looks like one ticket have many sponsors and in this case I see only one problem. There is no auto_increment id in the TicketSponsorDao table, but there is a ticket_id column and I don't understand the purpose of that.
/**
* TicketSponsorDAO
*
* #ORM\Table(name="ticket_sponsor")
* #ORM\Entity
*/
class TicketSponsorDAO {
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
}
There is a recursion between the objets it's logic with OneToMany/ManyToOne relationship because the object of One is referred in the object of Many, ...
The var_dump doesn't manage it correctly.
To display the object I use dump from Symfony and it's good!
We can add a __toString with return serialize($this) into DAO class if the dump is displayed in browser.
Otherwise, we have an error:
... ENTITY could not be converted to string ...

Doctrine Filters: filtering only directly hydrated entities, not relation ones

How can I determine (within Doctrine filter) if entity is directly hydrated or was only joined or is fetched from other's entity relation?
I know how can it be filtered globally, this works (filter is enabled in Symfony's kernel.request listener):
Entities
/**
* Period
*
* #ORM\Table(name="period")
* #ORM\Entity(repositoryClass="Acme\DemoBundle\Entity\PeriodRepository")
*/
class Period {
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Task", inversedBy="periods")
*/
private $task;
}
/**
* Task
*
* #ORM\Table(name = "task")
* #ORM\Entity(repositoryClass="Acme\DemoBundle\Entity\TaskRepository")
*/
class Task
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="type", type="integer")
*/
private $type;
/**
* #ORM\OneToMany(targetEntity="Period", mappedBy="task")
*/
private $periods;
}
Filter
namespace Acme\DemoBundle\Doctrine\Filter;
use Acme\DemoBundle\Entity\Task;
use Doctrine\ORM\Query\Filter\SQLFilter;
use Doctrine\ORM\Mapping\ClassMetadata;
/**
* DisableTasksFilter excludes tasks from Doctrine results.
*/
class DisableTasksFilter extends SQLFilter
{
const NAME = 'acme_disable_tasks';
/**
* #inheritdoc
*/
public function addFilterConstraint(ClassMetadata $entity, $alias)
{
// Ignore tasks with type = 1
if ($entity->getReflectionClass()->name === Task::class) {
return "$alias.type != 1";
}
return '';
}
}
BUT... (THE PROBLEM)
Filter should work only for direct fetching Task entities and SHOULD NOT filter Task relations in Period entities, nor joined entities in queries (it could break some logic).
In our app there are few places where Tasks are fetched:
TaskManager methods
TaskRepository used in TaskManager and directly
User->tasks relation (via helper methods which filter collection with ArrayCollection::filter())
Period->task relation
It would be much easier if it could be possible to filter it with globally registered filter instead of adding conditions to each place where Tasks are fetched.
Is it even possible?

Doctrine 2 many-to-many with MappedSuperclass in Zend framework 2

I am new to Doctrine2 and trying to create entities for the following DB structure:
I want to have all machine parts as an array in one attribute of the machine class. I tried this:
class Machine {
....
/**
* #var array
* #ORM\OneToMany(targetEntity="MachineHasPart", mappedBy="machine", cascade={"persist", "remove"}, orphanRemoval=TRUE)
*/
private $parts;
....
public function getParts () {
return array_map(
function ($machineHasPart) {
return $machineHasPart->getPart();
},
$this->parts->toArray()
);
}
}
Where MachineHasPart is a #MappedSuperclass for the intermediate entities/tables (like machineHasCylinder etc), but it failed with:
An exception occurred while executing 'SELECT FROM machineHasPart t0'.
Should I restructure my database to use ORM here? Or there is a solution for my case?
You cannot query a #MappedSuperClass. This is also mentioned in the Doctrine2 documentation in chapter 6.1. Mapped Superclasses:
A mapped superclass cannot be an entity, it is not query-able and persistent
This means you have to either change the target entity to something queryable or you have to make MachineHasPart to a entity and change to single table inheritance.
When I look at your database structure I would suggest changing your Machine entity to have three independent relationships for the parts. One for Belt, one for Cylinder and one for Gear.
Then instead of a generic getParts you will have three methods getBelts, getCylinders and getGears.
If that is really not what you want then you can leave a comment.
UPDATE
You can solve it also with class inheritance. First make a base class Part that is also an entity and use it in the other classes Belt, Cylinder and Gear:
Part:
<?php
namespace Machine\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Part
*
* #ORM\Entity
* #ORM\Table("part")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="discriminator", type="string")
* #ORM\DiscriminatorMap({
* "part" = "Part",
* "gear" = "Gear",
* "cylinder" = "Cylinder",
* "belt" = "Belt",
* })
* #property int $id
*/
class Part
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var Machine
* #ORM\ManyToOne(targetEntity="Machine\Entity\Machine", inversedBy="parts")
* #ORM\JoinColumn(name="machine_id", referencedColumnName="id", nullable=true)
*/
protected $machine;
/**
* Get id.
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set id.
*
* #param int $id
* #return self
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
//... add setters and getters for machine as normal ...
}
Extend this class in your other parts:
Belt:
<?php
namespace Machine\Entity;
/**
* Belt
*
* #ORM\Entity
*/
class Belt extends Part
{
}
Cylinder:
<?php
namespace Machine\Entity;
/**
* Cylinder
*
* #ORM\Entity
*/
class Cylinder extends Part
{
}
Gear:
<?php
namespace Machine\Entity;
/**
* Gear
*
* #ORM\Entity
*/
class Gear extends Part
{
}
Now in your machine relate to the parts like as follows.
Machine:
<?php
namespace Machine\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Machine
*
* #ORM\Entity
* #ORM\Table("machine")
* #property int $id
*/
class Machine
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* Get id.
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set id.
*
* #param int $id
* #return self
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
/**
* #var Collection
* #ORM\OneToMany(targetEntity="Machine\Entity\Part", mappedBy="machine")
*/
protected $parts;
public function __constuct()
{
$parts = new ArrayCollection();
}
/**
*
* #return Collection
*/
public function getParts()
{
return $this->parts;
}
//... add setters and getters for parts as normal ...
}
Extend this class in your other parts:
Reading further in the Doctrine2 documentation in chapter 6.1. Mapped Superclasses (referred to by #Wilt):
... Furthermore Many-To-Many associations are only possible if the mapped superclass is only used in exactly one entity at the moment...
This means in this case the ORM mapping doesn't help. I cannot gather the data of all three entities MachineHasCylinder, MachineHasBelt and MachineHasGear through a MappedSupperclass at the same time.
I think using DQL or Native SQL is the only solution for this problem.

Doctrine one-to-many relation - "No identifier/primary key specified"

Doctrine fails with a simple bi-directional many-to-one relationship between FoodDes (many) and FoodGroup (one). Both entities are shown here:
/**
* #ORM\Entity
* #ORM\Table(name="FOOD_DES")
*/
class FoodDes
{
public function __construct()
{
$this->foodGroup = new ArrayCollection();
}
/**
* #ORM\Id
* #ORM\Column(name="NDB_No", type="string", length=10)
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="FoodGroup", inversedBy="fdGroupCode")
* #ORM\JoinColumn(name="FdGrp_Cd", referencedColumnName="FdGrp_CD")
*/
protected $foodGroup;
}
>
/**
* #ORM\Entity
* #ORM\Table(name="FD_GROUP")
*/
class FoodGroup
{
/**
* #ORM\Id();
* #ORM\GeneratedValue(strategy="NONE");
* #ORM\OneToMany(targetEntity="FoodDes", mappedBy="foodGroup")
*/
protected $fdGroupCode;
When I run doctrine orm:schema-tool:create, it fails with error:
No identifier/primary key specified for Entity
'Acme\Entities\FoodGroup'. Every Entity must have an
identifier/primary key.
However, I labeled $fdGroupCode as my only identifier.
Next approach
I've also tried creating a new primary key $id on the FoodGroup entity and removing the primary key label from $fdGroupCode on FoodGroup. Below is the new FoodGroup entity.
/**
* #ORM\Entity
* #ORM\Table(name="FD_GROUP")
*/
class FoodGroup
{
/**
* #ORM\Id
* #ORM\Column(name="id", type="integer", nullable=false)
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="FoodDes", mappedBy="foodGroup")
*/
protected $fdGroupCode;
When I run doctrine orm:schema-tool:create again, it results with a new error:
[Doctrine\ORM\ORMException]
Column name FdGrp_CD referenced for relation from
Acme\Entities\FoodDes towards Acme\Entities\FoodGroup does not exist.
This error doesn't make any sense. Of course it wouldn't exist. I am running it against an empty database!
These error occur running from the command line, but they also occur when querying the entities against a database. Can somebody please help me?
I'd rather give you working example of OneToMany from one of my projects, so you can see the difference and format code in proper way. If it does not work, then try to get a new Symfony dist and start over.
<?php
// SomeBundle/Entity/Shop/Product.php
namespace SomeBundle\Entity\Shop;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="shop_products")
*/
class Product
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="bigint")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="ProductItem", mappedBy="product")
*/
protected $productItem;
}
Related entity:
<?php
// SomeBundle/Entity/Shop/ProductItem.php
namespace SomeBundle\Entity\Shop;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="shop_products_items")
*/
class ProductItem
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="bigint")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Product", inversedBy="productItem")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
protected $product;
}
For reasons why your code does not work could be many (namespaces, folder structure, column names, etc.). This example works and tested. Give it a try : )

Why association is not creating in Doctrine2?

I have 2 entities — NewsItem and Category. It is unidirectional association between this entities: 1 NewsItem has 1 Category, 1 Category has many NewsItem's.
I am trying to create association mapping like in THIS example. What I've tried? My code:
class NewsItem {
// other fields
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\ManyToOne(targetEntity="News\Entity\Category")
*/
protected $category_id;
// other fiels, getters, setters etc
}
After that, I deleted tables in the database manually and run command orm:schema-tool:update --force in command line. It says that some queries are executed without errors — it's ok. But when I open table Category in HeidiSQL there are no FOREIGN KEYS there. That means that tables are not linked.
What I did wrong?
You can watch full code of this News\Entity\NewsItem entity here: click me. News\Entity\Category entity is here: click me.
you should remove * #ORM\Column(type="integer") as it is conflicting with the many-to-one relation.
Even if it is not the cause of the bug, you should also rename protected $category_id; to protected $category;
Also, your two entities are under the same namespace so it's not necessary to add the related entity's full path. targetEntity="Category" is enough.
You have incorrect mapping information for the Category entity.
Your NewsItem.php file should look like this:
namespace Your\Bundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\Mapping\JoinColumn;
/**
* #ORM\Table(name="news_item")
* #ORM\Entity
*/
class NewsItem {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var Category
*
* #ORM\ManyToOne(targetEntity="Category", inversedBy="news_items")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $category;
// Rest of code omitted.
}
And your Category.php should look like this:
namespace Your\Bundle\Entity;
/**
* #ORM\Table(name="category")
* #ORM\Entity
*/
class Category {
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="NewsItem", mappedBy="category")
*/
private $news_items;
public function __construct(){
$this->news_items = new ArrayCollection();
}
// Rest of code omitted.
}

Categories