I am using symfony 3.4.1, with doctrine/orm 2.5.13. I have 2 tables. store and store_product.
In Store entity I have:
/**
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Store\Product", mappedBy="store")
* #ORM\JoinColumn(name="store_id")
*/
private $products;
and in Store\Product entity I have composite index with (store_id,product_id). I have:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Store", inversedBy="products")
* #ORM\JoinColumn(name="store_id",referencedColumnName="id",nullable=false,onDelete="CASCADE")
* #ORM\Id()
* #ORM\GeneratedValue("NONE")
* #var $store \AppBundle\Entity\Store
*/
protected $store;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Product", inversedBy="stores")
* #ORM\JoinColumn(name="product_id",referencedColumnName="id",nullable=false,onDelete="CASCADE")
* #ORM\Id()
* #ORM\GeneratedValue("NONE")
* #var $product \AppBundle\Entity\Product
*/
protected $product;
Earlier I produced a native query using the guide. It was only fetching entries from store table and it worked perfectly.
Now I am trying to join store_product table and it is not going so well. I am using the following query which returns 1 result.
SELECT st.id, st.name, stp.store_id, stp.product_id, stp.price FROM store st LEFT JOIN store_product stp ON st.id = stp.store_id WHERE st.id=1 LIMIT 1;
returns something like:
id | name | store_id | product_id | price
----+------------+----------+------------+-------
1 | Store Name | 1 | 1234567890 | 129
I setup the result set mapping as follows:
$rsm = new ResultSetMapping();
$rsm->addEntityResult('AppBundle\Entity\Store', 'st');
$rsm->addFieldResult('st', 'id', 'id');
$rsm->addFieldResult('st', 'name', 'name');
$rsm->addJoinedEntityResult('AppBundle\Entity\Store\Product', 'stp',
'st', 'products');
$rsm->addFieldResult('stp','store_id', 'store');
$rsm->addFieldResult('stp','product_id','product');
$rsm->addFieldResult('stp','price','price');
I am getting error: Notice: Undefined index: store Can anybody see the reason of the error?
I managed to get this working at last after 3 months.
Apparently I made a mistake of trusting documentation when I added foreign keys with addFieldResult() for joined table.
I needed to remove addFieldResult() lines for store_id and product_id` and add them like:
$rsm->addMetaResult('stp', 'store_id', 'store_id', true);
$rsm->addMetaResult('stp', 'product_id', 'product_id', true);
It wasn't enough to just add them with addMetaResult() I had to set 3rd parameter true also.
Even though documentation says Consequently, associations that are fetch-joined do not require the foreign keys to be present in the SQL result set, only associations that are lazy.
What worked for me was using addJoinedEntityFromClassMetadata from the ResultSetMappingBuilder instead of addJoinedEntityResult. Using the example from the documentation:
/**
* #param string $class The class name of the joined entity.
* #param string $alias The unique alias to use for the joined entity.
* #param string $parentAlias The alias of the entity result that is the parent of this joined result.
* #param string $relation The association field that connects the parent entity result
* with the joined entity result.
*/
$rsm->addJoinedEntityFromClassMetadata(Address::class, 'a', 'u', 'address');
Related
This question already has answers here:
Doctrine query builder using inner join with conditions
(2 answers)
Closed 5 years ago.
From Symfony 4, I have a ManyToOne relation between entities A and B (several B for only one A). I want select all id's A rows only if an 'child' B have the value 1 in its specificfield.
The table 'A' :
---
id
---
0
1
2
The table 'B' :
----------------------------------
id parent_id specificfield
----------------------------------
0 1 1
1 1 0
2 2 0
Result expected :
---
id
---
1
// because, only the A row with the id 1 have at least one 'child' in the table B with the specificfield set to 1
I tried build a query like this :
$res = $this->em->createQueryBuilder()
->select('A.id')
->from(Parent::class,'A')
->innerJoin(Child::class,'B',Join::ON,"A.id = B.parent_id")
->where('B.specificfield = 1')
->distinct()
->getQuery()
->getResult();
But I get the error : "Expected end of string, got 'ON'" .
The sql query equivalent works in my phpmyadmin ..
SELECT DISTINCT A.id
FROM parent A
INNER JOIN child B ON A.id = B.parent_id
WHERE e.specificfield = 1
I don't see where is my mistake in my dql query ..
When using the ORM you are working on the entities not the tables directly. This requires you to think a bit differently, especially in regards to assocations. You are joining the properties of the entities, not between the tables/objects.
The query therefore look like this:
$builder
->select('a.id')
->from(A::class, 'a')
->innerJoin('a.children', 'b')
->where('b.active = 1')
->getQuery()
->getResult();
So you just specify on which property you want to join and because your mapping already specified which entity belongs there it should work. For reference this is what my entities look like:
/**
* #ORM\Entity()
*/
class A
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\B", mappedBy="parent")
*/
private $children;
...
}
/**
* #ORM\Entity()
*/
class B
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\A", inversedBy="children")
* #ORM\JoinColumn(nullable=false)
*/
private $parent;
/**
* #ORM\Column(type="boolean", options={"default": false})
*/
private $active;
...
}
edit for clarification: When you join B::class instead of a.children. Then you just tell the query builder that you want to fetch multiple entities at once unrelatedly. In case you want to quickly collect all As and active Bs with one query, but then you also have to select b.id.
I think you need to use "with" instead of "on" in the join...
I have an entity with a column type array
/**
* Report entity
*
* #ORM\Table(name="report")
* #ORM\Entity(repositoryClass="AppBundle\Repository\ReportRepository")
*/
class Report
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var array
*
* #ORM\Column(name="selectedCountries", type="array", nullable=true)
*/
private $selectedCountries;
In the database I have a a record which is stored as:
+-------------------------+
| selectedCountries |
+-------------------------+
| a:0:{} |
+-------------------------+
When I do:
$report = $this->reportRepository->findOneBy(array('selectedCountries' => []));
I get null and I have no idea why.
The db connection, entity, repository etc.. is working fine: just when I include this array in the findOneBy() it does not find the result.
With the help of the Symfony profiler, I debugged the queries doctrine makes and found that I need to use simple_array instead of array for the #ORM\Column type.
When I use array as a column type: doctrine serializes this array whenever it saves data to the database like a:0:{}. The problem with this approach is when I try to run a findby:
If I use an array as a parameter, doctrine querie looks like this: ..AND t0.selectedCountries IN ('de')..
If I use a serialized array, doctrine escapes it like this: ..AND
t0.selectedCountries = 's:19:\"a:1:{i:0;s:2:\"de\";}\";' ..
So in both cases the record will not be found!
But by using simple_array doctrine query looks like: .. AND t0.selectedCountries IN ('de') .. which is perfect since the data in the table is saved now as:
+-------------------------+
| selectedCountries |
+-------------------------+
| de |
+-------------------------+
The only issue with this approach is when you have more elements in the array: selectedCountries => de,fr,uk and want to search by this. For this to work you should do extra work:
Add #ORM\HasLifecycleCallbacks() to your entity and sort the array before inserting
/**
* #ORM\PrePersist
*/
public function setSelectedCountriesValue()
{
sort($this->selectedCountries);
}
In your entity's repository use Query Builder and sort the array when trying to find results.
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('r')
->from('AppBundle:Report', 'r')
...
if ($creteria['selectedCountries']) {
sort($creteria['selectedCountries']);
$qb->andWhere('r.selectedCountries= :selectedCountries')
->setParameter('selectedCountries', implode(',', $creteria['selectedCountries']));
}
I want select a column (which has a foreign key constraint) without creating joins on tables. I have two tables named eventupdate and eventcategory. The event column is common in both tables. whenever I try the following code it gives an error.
Please give some suggestion. I don't want to create a join.
$qb2 = $this->em->createQueryBuilder();
$from = 'Entities\EventCategory cat';
$qb2->add('from',$from)
->select('cat.event')
->Where('cat.id=3);
$query=$qb2->getQuery();
There are two options that I can see:
HINT_INCLUDE_META_COLUMNS together with ArrayHydrator
$query = $queryBuilder->getQuery();
$query->setHint(\Doctrine\ORM\Query::HINT_INCLUDE_META_COLUMNS, true);
var_dump($query->getArrayResult()); // Will return array with raw foreign key column name => value, e.g. user_id => 5
Create separate property in Entities\EventCategory which has the foreign key as primitive type
/**
* #var User
*
* #ManyToOne(targetEntity="User")
* #JoinColumn(name="user_id", referencedColumnName="user_id")
*/
private $user;
/**
* #var int
*
* #Column(name="user_id", type="integer", nullable=false)
*/
private $userId;
I'm working with a legacy database (that means no schema changes!) and I need to create a associations between the the Doctrine entities involved. I'll describe the data structure first and then explain what I've tried.
The database has a user table with various other tables also storing user related info. Eg:
siteUser has:
contentId (PK)
firstName
lastName
username
password
...
and siteUser entities have metadata in this system which is along the lines of:
metadataId (PK)
title
description
keywords
createDate
publishDate
contentId
contentTable (discriminator)
...
Almost everything in the database can have Metadata by storing it's PK in the metadata.contentId field and the table name in the metadata.contentTable field. Note that metadata.contentId is not a foreign key, these must have been alien to the DBA as I'm yet to see a single one.
Users on the system can save information they find relevant to them so that they can come back to the system later and don't have to go hunting for the same information again.
This is done with content types called conLink, conVideo, conLeaflet stored as database entities (which have metadata).
For example a conVideo looks like this:
contentId (PK)
embedCode
The way users can store mark this information as being relevant to them is by the system storing it in a link table called userSavedContent:
userSavedContentId (PK)
userId
metadataId
Note that userSavedContent.userId and userSavedContent.metadataId are also not foreign key constraints.
THE APPROACH!
I need to get user's saved content. In SQL this is no problem!
SELECT
metadata.title,
conVideo.embedCode
FROM
userSavedContent
INNER JOIN
metadata ON userSavedContent.metadataId = metadata.metadataId
INNER JOIN
conVideo ON conVideo.contentId = metadata.contentId
WHERE userSavedContent.userId = 193745
AND metadata.contentTable = 'conVideo'
However doing this in Doctrine is more complicated because the value of metadata.contentTable could potentially be any of the conLink, conVideo, conLeaflet entities.
So my application is built using Symfony2 (and Doctrine) and I have models defined for all of the above entities.
In this Metadata is an abstract class with a discriminator on metadata.contentTable:
/**
*
* #ORM\Table(name="metadata")
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="contentTable", type="string")
* #ORM\DiscriminatorMap(
* {
* "conLink" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\ConLinkMetadata",
* "conVideo" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\ConVideoMetadata",
* "siteUser" = "MyApp\Bundle\DataApiBundle\Entity\Metadata\SiteUserMetadata"
* }
* )
*/
abstract class Metadata
The ConVideoMetadata class extends Metadata and adds a content property that associates the ConVideo entity to it:
/**
* #var ContentType $content
*
* #ORM\OneToOne(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\ContentType\ConVideo",
* inversedBy="metadata",
* cascade={"persist", "remove"}
* )
* #ORM\JoinColumn(name="contentId", referencedColumnName="contentId")
*/
protected $content;
Now the userSavedContent entity has metadata property to associated it to an item of metadata.
/**
* #var Metadata $metadata
*
* #ORM\ManyToOne(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\Metadata",
* inversedBy="userSavedContent"
* )
* #ORM\JoinColumn(name="id", referencedColumnName="metadataId")
*/
protected $metadata;
And finally the siteUser is related to userSavedContent by the following property on it's entity:
/**
* #ORM\OneToMany(
* targetEntity="MyApp\Bundle\DataApiBundle\Entity\UserSavedContent",
* mappedBy="siteUser",
* cascade={"persist", "remove"},
* orphanRemoval=true
* )
* #ORM\JoinColumn(name="contentId", referencedColumnName="userId")
*/
private $userSavedContentItems;
THE PROBLEM!
In my siteUserRepository class I now need to query for a siteUser and all it's saved content items:
$builder = $this->createQueryBuilder('s')
->select('s', 'm', 'usc', 'uscm', 'uscc')
->innerJoin('s.metadata', 'm')
->leftJoin('s.userSavedContentItems', 'usc')
->leftJoin('usc.metadata', 'uscm')
->leftJoin('uscm.content', 'uscc');
return $builder;
This doesn't work!
"[Semantical Error] Error: Class MyApp\Bundle\DataApiBundle\Entity\Metadata has no association named content"
This makes sense of course since MyApp\Bundle\DataApiBundle\Entity\Metadata doesn't have the content property, it's child MyApp\Bundle\DataApiBundle\Entity\Metadata\ConVideoMetadata is the one with that association. I thought Doctrine would have been able to work this out but apparently not.
So my question is:
Is this approach very wrong? And if not what can I do to make that association/query work?
The fix for this issue was to get Doctrine to eagerly fetch the concrete metadata->content entities. I could declare these explicitly but used Doctrine's MetadataFactory to get the Metadata entity's discriminator for the list of all possible content types.
$metadataFactory = $this->getEntityManager()->getMetadataFactory();
$metadataMetadata = $metadataFactory->getMetadataFor('MyApp\Bundle\DataApiBundle\Entity\Metadata');
foreach ($metadataMetadata->discriminatorMap as $contentEntity) {
$builder->getQuery()
->setFetchMode(
$contentEntity,
'content',
ClassMetadata::FETCH_EAGER
);
}
I have an Item entity that has a ManyToOne relationship to a Category entity. I want them to be joined by a field other than Category's id (in this case, a field called id2). My schema is listed below.
class Item {
/**
* #ORM\Id
* #ORM\Column(name = "id", type = "integer")
* #ORM\GeneratedValue(strategy = "AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity = "Category")
* #ORM\JoinColumn(name = "category_id", referencedColumnName = "id2")
*/
protected $category;
}
class Category {
/**
* #ORM\Id
* #ORM\Column(name = "id", type = "integer")
* #ORM\GeneratedValue(strategy = "AUTO")
*/
protected $id;
/**
* #ORM\Column(name = "id2", type = "string", length = "255", unique = "true")
*/
protected $id2;
When I try saving an Item I get this error:
Notice: Undefined index: id2 in vendor/doctrine/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php line 511
Sure enough, if I change id2 to id in the JoinColumn annotation, everything works fine, but I need the entities to be connected through id2. Is this possible?
Edit
What I want to achieve is impossible according to the official Doctrine 2 docs.
It is not possible to use join columns pointing to non-primary keys.
Doctrine will think these are the primary keys and create lazy-loading
proxies with the data, which can lead to unexpected results. Doctrine
can for performance reasons not validate the correctness of this
settings at runtime but only through the Validate Schema command.
source: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/limitations-and-known-issues.html#join-columns-with-non-primary-keys
I think Doctrine wants these to be primary keys, from the docs:
name: Column name that holds the foreign key identifier for this relation.
Another thing that jumps out at me from your code sample is category.id2 being type string, I would at least expect it to be an integer, but it may also need to be for #JoinColumn to work properly.
You may be able to get away with just #Index on category.id2 and leave it as a string though; worth a shot anyway.
Just to report. I was able to join non-PKs in Many2One (undirectional) relation, BUT my object can't be loaded the normal way. It must be loaded with DQL like:
SELECT d,u FROM DEntity d
JOIN d.userAccount u
this way I stopped getting error: Missing value for primary key id on ....