Does Doctrine represented a missing relation only with `NULL`? - php

I am learning to use Doctrine on Symfony 5.4.
My understanding is that if you have an entity with a nullable ManyToOne relation
/**
* #ORM\Table(name="tblCustomer")
* #ORM\Entity(repositoryClass=CustomerRepository::class)
*/
class Customer implements \JsonSerializable
{
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="customers", fetch="EAGER")
* #ORM\JoinColumn(nullable=true, name="id_user_default", referencedColumnName="id")
*/
private $dev_user;
}
on a insert without specifying something for dev_user relation, Doctrine try to run a query like
INSERT INTO tblCustomer (id_user_default) VALUES (NULL);
it seems that the "missing relation" is always represented with null. Am I right? Or for example I can make Doctrine set 0 for a missing relation, running then the query
INSERT INTO tblCustomer (id_user_default) VALUES (0);
?

Related

Doctrine launching too many querys witn an innerJoin

Given this code, Doctrine launchs as many querys as rows I have in table2
$qb = $this->getModelManager()->createQuery($er->getClassName(), 't1')->getQueryBuilder();
$qb->select('t1, t2, t3')
->innerJoin('table1.table2', 't2');
->innerJoin('table2.table3', 't3')
->where('t3.id = :foo')
->setParameter('foo', $foo);
each query is like this one:
SELECT t0.id AS id_1,
t0.name AS name_2,
t0.slug AS slug_3,
t0.description AS description_4,
t0.visible AS visible_5
FROM table2 t0
WHERE t0.id = ?
and those are the entities:
TABLE1: The main entity, witch is related to table 2 by manyToOne if I make the innerJoin with table 2, Doctrine act as expected (1 query)
/**
* #ORM\Entity(repositoryClass = "Table1Repo")
* #ORM\Table(name="table1")
*/
class table1 extends BaseTable1 implements table1Interface
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
* #Gedmo\Versioned
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity="table2", inversedBy="tables1")
* #ORM\JoinColumn(name="table2_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $table2;
}
TABLE2, related with table one by OneToMany, and with table 3 by ManyToMany.
/**
* #ORM\Entity(repositoryClass="table2Repository")
* #ORM\Table(name="table2")
*/
class table2 extends Basetable2
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="table3", inversedBy="table2s")
* #ORM\JoinTable(name="table3_table2")
*/
protected $table3;
/**
* #ORM\OneToMany(targetEntity="table1", mappedBy="table2")
* #Accessor(getter="getTables1")
*/
protected $tables1;
}
TABLE3: oly related with table 2 by a ManyToMany relation. When I make the innerJoin with table 2, Doctrine still acts as expected, making only one query
/**
* #ORM\Entity(repositoryClass = "table3Repo")
* #ORM\Table(name="table3")
* #Gedmo\Loggable
*/
class table3 extends Basetable3
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\ManyToMany(targetEntity="table2", mappedBy="tables3")
* #ORM\JoinTable(name="table3_table2")
*/
protected $tables2;
}
So, as I add both innerJoins to the query Builder, Doctrine makes only one query, but is when I add the WHERE clause, when Doctrine makes the 279 querys, one per row in table2, witch is related with table1 by oneToMany and with table3 by ManyToMany.
Other relevant point is that the querybuilder is beeing executed under SonataAdmin query_builder fiel option.
I can't find why I'm getting this behaviour, any clue?
When you run a query, you always need to have a root entity which joins to others. In your case, that is table1. After fetching, based on query and entity metadata, Doctrine will attempt to create an object for each instance of root, but unless told otherwise, it will stop there. In fact, for each sub-objects (e.g. table2), it will create dummy "proxy" objects which are barely shallow representations, and are to be resolved from DB whenever to try to dereference them. The process of converting DB results to objects is known as object hydration.
In order to do perform hydration of sub-objects, you need to "select" sub-entity as well:
$qb->select('t1', 't2', 't3')
->innerJoin('t1.table2', 't2');
->innerJoin('t2.table3', 't3')
->where('t3.id = :foo')
->setParameter('foo', $foo);
Pay attention not to go crazy with fetching everything, as it takes longer and consumes reasonably more RAM. Fine-tune your query, until you reach what you want (logic and performance-wise).
Hope this helps...

Doctrine 2 joining table, ManyToOne unidirectional, where tbl2.value = :value

I'm trying to figure out how to join two tables, while querying the second table. I thought it was as simple as:
// Within ServerServiceRepository
return $this->createQueryBuilder('ss')
->join(ServiceType::class, 't')
->where('t.serviceTypeName = :name')
->getQuery()
->execute(['name' => $name]);
But turns out, not so much...
The issue is the query is NOT joining the keys (service_type_id on both tables). What's going on here? I have all the OneToMany relationships setup correctly:
/**
* ServerService
*
* #ORM\Table(name="server_services")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ServerServiceRepository")
*/
class ServerService extends AbstractEntity
{
/**
* #var ServiceType
*
* #ORM\Column(name="service_type_id", type="integer", nullable=false)
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Supportal\ServiceType", fetch="LAZY", inversedBy="serviceTypeId")
* #ORM\JoinColumn(name="service_type_id", referencedColumnName="service_type_id")
*/
private $serviceType;
// [...]
}
/**
* ServiceType
*
* #ORM\Table(name="service_types")
* #ORM\Entity
*/
class ServiceType extends \AppBundle\Entity\AbstractEntity
{
/**
* #var integer
*
* #ORM\Column(name="service_type_id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
* #ORM\OneToMany(targetEntity="AppBundle\Entity\ServerService", fetch="EXTRA_LAZY", mappedBy="serviceType")
*/
private $serviceTypeId;
/**
* #var string
*
* #ORM\Column(name="service_type_name", type="string", length=255, nullable=true)
*/
private $serviceTypeName;
// [...]
}
I've added / removed the OneToMany relationship from ServiceType to no change. This is really a unidirectional relationship. Per Doctrine's own docs (Chapter 5), ServerType does not require a relationship mapping.
The SQL query is generating a JOIN that's missing the actual keys:
INNER JOIN service_types s1_ ON (s1_.service_type_name = ?)
What am I missing here on Doctrine to get this working right? I've looked at the tutorials. Symofny's example is almost exact what I'm after, except I need to select by "category name" not Product id: https://symfony.com/doc/2.8/doctrine/associations.html
I've got to be missing something so super simple. But I can't for the life of me peg it...
Edit:
I've removed the OneToMany from ServiceType in my code. It's optional. Not needed for this anyway. This is a unidirectional relationship.
I've tried this:
return $this->createQueryBuilder('ss')
->join('ss.serviceType', 't')
->where('t.serviceTypeName = :name')
->getQuery()
->execute(['name' => $name]);
Resulting in this error:
[Semantical Error] line 0, col 85 near 't WHERE t.serviceTypeName': Error: Class AppBundle\Entity\ServerService has no association named serviceType
Solution
The solution was removing the #ORM Column definition. Looks like it's a conflict in the relationship definitions.
First of all, change the ManyToOne docblock definition to:
/**
* #var ServiceType
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Supportal\ServiceType", fetch="LAZY", inversedBy="serverService")
* #ORM\JoinColumn(name="service_type_id", referencedColumnName="service_type_id")
*/
private $serviceType;
Also change the OneToMany, remove line #ORM\OneToMany(targetEntity="AppBundle\Entity\ServerService", fetch="EXTRA_LAZY", mappedBy="serviceType")
Create new field serverService for the OneToMany relation:
/**
*
* #ORM\OneToMany(targetEntity="ServerService", mappedBy="serviceType")
*/
private $serverService;
You should join on the relation field, in this case serviceType. The way you defined the join is like selecting both tables.
Change to:
return $this->createQueryBuilder('ss')
->join('ss.serviceType', 't')
->where('t.serviceTypeName = :name')
->getQuery()
->execute(['name' => $name]);
Since you are applying a condition on the joined result here, using a LEFT JOIN or simply JOIN is the same.
References:
How to Work with Doctrine Associations / Relations
how to do left join in doctrine
Left join ON condition AND other condition syntax in Doctrine

How to complete an entity with relations which cannot be mapped by doctrine

I have an entity person, which is linked to an person_groups table.
As this link is not done through person.id primary key, but through an index person.matricule, I cannot map this attribute with a many to one relation
However, I'd really like to have a groups property in my entity. How can this be achieved ?
I was thinking of using the doctrine event lifecycle, to dynamically update my entity at load time. Is it a good idea ? Are there other solutions ?
What is the reason you can't map it by doctrine?
/**
* #ORM\OneToMany(
* targetEntity="YourNamespace\YourEntityGroups",
* mappedBy="person"
* )
*/
protected $groups;
and
/**
* #ORM\ManyToOne(
* targetEntity="YourNamespace\YourEntityPerson",
* inversedBy="groups"
* )
* #ORM\JoinColumn(name="person_matricule", referencedColumnName="matricule")
*/
protected $person;

doctrine query builder join adding table twice

UPDATED at bottom:
I am trying to do what should be a simple join between two tables. I have a Gig table and a Venue table for a simple band site that I am building using Symfony2 (2.2). It's my first time with Symfony2 and doctrine so it is possible I am going completely in the wrong direction. I have created and populated the tables with DataFixtures and have verified that the ID relationships are correct. The problem I am getting is that the resulting DQL query has the Gig table referenced twice in the FROM section and that is causing me to get back several instances of the same record instead of the x number of records I am expecting. I don't know what I am doing wrong for that to be happening. Also, there may be an easier way of doing this but I am exploring all of my options since I am teaching myself Symfony2 in the process of building the site.
The Gig table contains a venue_id pointing to a Venue table that is defined in the Gig entity as a ManyToOne relationship (shown below). Using a doctrine findAll everything seems to work fine with the Venue class in the Gig Entity being populated correctly. I am trying to create a flat view of a few of the most recent Gigs to be displayed on the front page so I figured I would try to use a Join and include only the fields I need.
Here is the Repository Query:
public function getGigsWithLimit($maxGigs)
{
$qb = $this->createQueryBuilder('b')
->select('
g.gigDate,
g.startTime,
g.endTime,
g.message1 as gig_message1,
g.message2 as gig_message2,
g.url,
v.name,
v.address1,
v.address2,
v.city,
v.state,
v.zip,
v.phone,
v.url as venue_url,
v.message1 as venue_message1,
v.message2 as venue_message2,
v.message3 as venue_message3'
)
->from('WieldingBassBundle:Gig', 'g')
->leftJoin('g.venue', 'v')
->orderBy('g.gigDate', 'DESC')
->setMaxResults($maxGigs);
return $qb->getQuery()->getResult();
}
Here is the DQL it creates:
SELECT
g0_.id AS id0,
g0_.gig_date AS gig_date1,
g0_.start_time AS start_time2,
g0_.end_time AS end_time3,
g0_.message1 AS message14,
g0_.message2 AS message25,
g0_.url AS url6,
v1_.name AS name7,
v1_.address1 AS address18,
v1_.address2 AS address29,
v1_.city AS city10,
v1_.state AS state11,
v1_.zip AS zip12,
v1_.phone AS phone13,
v1_.url AS url14,
v1_.message1 AS message115,
v1_.message2 AS message216,
v1_.message3 AS message317
FROM
Gig g2_,
Gig g0_
LEFT JOIN
Venue v1_ ON g0_.venue_id = v1_.id
LIMIT
6
The Gig g2_ is my problem. If I delete it and execute the query everything is as expected. I don't know what is generating that.
The first table Gigs Entity looks like this (I am leaving out the getters and setters):
/**
* Gig
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Wielding\BassBundle\Entity\GigRepository")
* #ORM\HasLifecycleCallbacks
*/
class Gig
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="gig_date", type="date")
*/
private $gigDate;
/**
* #var \DateTime
*
* #ORM\Column(name="start_time", type="datetime")
*/
private $startTime;
/**
* #var \DateTime
*
* #ORM\Column(name="end_time", type="datetime")
*/
private $endTime;
/**
* #var string
*
* #ORM\Column(name="message1", type="string", length=50, nullable=true)
*/
private $message1;
/**
* #var string
*
* #ORM\Column(name="message2", type="string", length=50, nullable=true)
*/
private $message2;
/**
* #var string
*
* #ORM\Column(name="url", type="string", length=128, nullable=true)
*/
private $url;
/**
* #var integer
*
* #ORM\Column(name="venue_id", type="integer")
*/
private $venueId;
/**
* #ORM\Column(type="datetime")
*/
protected $created;
/**
* #ORM\Column(type="datetime")
*/
protected $updated;
/**
* #ORM\ManyToOne(targetEntity="Venue", cascade="persist")
* #ORM\JoinColumn(name="venue_id", referencedColumnName="id")
*/
protected $venue;
The Venue table is simple and does not have any relationships defined so I will leave it out unless it is asked for.
Any ideas? Thanks for any help.
Andrew
I removed everything except what would recreate the problem and here is what I was left with:
I simplified the repository method to:
public function getGigsWithLimit2($maxGigs)
{
$qb = $this->createQueryBuilder('a')
->select('g.id')
->from('WieldingBassBundle:Gig', 'g')
->setMaxResults($maxGigs);
return $qb->getQuery()->getResult();
}
This now generates:
SELECT
g0_.id AS id0
FROM
Gig g1_,
Gig g0_
LIMIT
6
There is that darn Gig g1_ problem again. I got the "Explain Query" from the Symfony profiler and it shows:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE g1_ index IDX_ED7D664240A73EBA 4 9 Using index
1 SIMPLE g0_ index IDX_ED7D664240A73EBA 4 9 Using index; Using join buffer
I don't pretend to know what that means but it shows both table entries with different information about how it was used.
whats the use of "venueId" if you already got "venue" which contains the foreign key?
I found the problem. I am not used to Doctrine and was using the ->from in a repository that did not need it since the entity was automatically related through the annotations. My earlier query that worked was in the controller and not a repository so the ->from was necessary.
You are trying to do SQL. Doctrine is different. Your query fetches every field. Doctrine prefers to fetch entities. I think you probably want this:
public function getGigsWithLimit($maxGigs)
{
$qb = $this->createQueryBuilder('g')
->leftJoin('g.venue', 'v')
->orderBy('g.gigDate', 'DESC')
->setMaxResults($maxGigs);
return $qb->getQuery()->getResult();
}
The return result is a list of entities which you can call all the specific methods directly. You are still welcome to specify fields with doctrine, if you want partial objects, but I've found the normal method of fetching the entire entity covers most of my needs.
This is a fundamentally different paradigm than SQL and will take some getting used to.

NULL inserted while Persisting new Entity, Mapping table column to 2 tables. doctrine 2

I need to map the same column to 2 differences tables (lets say normal and extended).
/**
* #var ItemValue
*
* #OneToOne(targetEntity="ItemValue")
* #JoinColumn(name="id_value", referencedColumnName="id_value")
*/
private $value;
/**
* #var ItemValueExtended
*
* #OneToOne(targetEntity="ItemValueExtended")
* #JoinColumn(name="id_value", referencedColumnName="id_value")
*/
private $valueExtended;
/**
* #var string $isExtended
*
* #Column(name="is_extended", type="string", nullable=false)
*/
private $isExtended = 'YES';
I have no problem with joining data based on the isExtended attribute using DQL:
"SELECT id,idv FROM ItemData id
JOIN id.value idv WHERE id.isExtended='NO'";
and
"SELECT id,idv FROM ItemData id
JOIN id.valueExtended idv WHERE id.isExtended='YES'";
but when ever I want to persist a new object, NULL is inserted in id_value column ?!!
$oValue = ItemValue();
.
.
$oData = new ItemData();
$oData->setValue($oValue);
.
.
.
$em->persist($oData);
$em->flush();
Any Idea ?
From Doctrine2 documentation:
In the case of bi-directional associations you have to update the
fields on both sides.
One possible solution would be:
$oData = new ItemData();
$oData->setValue($oValue);
$oValue->setData($oData);
but it's tedious. Another better one is set the cascade option on both sides of the one-to-one association:
#OneToOne(targetEntity="ItemValue"), cascade={"persist", "remove"})
This way your code will work. You can choose the appropriate cascade options looking at here.
In the case where both the parent and child entity are new entities (neither have been persisted) a PrePersist lifecycle event on the parent can help:
/**
* ....
*
* #ORM\HasLifecycleCallbacks
*/
class ParentEntity {...
/**
* #ORM\PrePersist()
*/
public function prePersist() {
foreach($this->getChildEntities() as $childEntity) {
$childEntity->setParent($this);
}
}
-
class ChildEntity {
....
This will automatically create the child -> parent relationship upon saving the parent.
In many instances Doctrine will then be able to work out the rest at the SQL level.

Categories