I have a few different doctrine entities implementing a common interface and I want to (ideally) create a repository in doctrine that allows me to query with ordering/pagination across all those entities (a union in plain SQL). The entities don't inherit from a common base class.
Specifically the interface allows an object to be used as a tag:
interface My\TaggableInterface
{
// get object UUID
public function getObjectIdentity(): string
// get the tag text
public function getTagString(): string
}
class My\Entity implements My\TaggableInterface
class My\Other\Entity implements My\TaggableInterface
I was hoping to create a custom repository that managed the union, so I could write:
$entityManager()->getRepository('My\\TaggableInterface')
->findBy(
//criteria
)`
But there doesn't seem to be a way to create a temporary table from an interface or support for union in the doctrine query builder. I want to avoid using native (My)SQL if possible, but I can't see how to achieve this using Doctrine?
Many thanks.
You can't do it with a single query. Even if you could somehow construct this query, doctrine wouldn't know how to hydrate the results.
You can however find and query all entities implementing your interface without having to list them all somewhere in your code:
$result = array();
$criteria = array(
// criteria
);
foreach ($em->getMetadataFactory()->getAllMetadata() as $m) {
$class = $m->getName();
$reflClass = new \ReflectionClass($class);
if ($reflClass->implementsInterface('My\TaggableInterface')) {
$result = array_merge($result, $em->getRepository($class)->findBy($criteria));
}
}
// results of various entity classes are now all in $result
An outline of the solution I used is below. Since I'm hydrating the results from each table into a tag entity (which can be persisted using the ORM if the tag is selected by the user) there's no need to hydrate multiple entity classes from the same result set.
$sql = <<<EOF
SELECT `id`, `text`
FROM tag_table
GROUP BY `text`
UNION
SELECT UUID() as `id`, `some_text_field` as `text`
FROM another_table
GROUP BY `some_text_field`
EOF;
$resultMapping = new ResultSetMapping();
$resultMapping->addEntityResult('My\Tag\Entity', 'tag');
$resultMapping->addFieldResult('tag', 'id', 'id');
$resultMapping->addFieldResult('tag', 'text', 'text');
$nativeQuery = $entityManager->createNativeQuery($sql, $resultMapping);
$result = $nativeQuery->getResult();
The SQL can then be extended to handle pagination, and to make sure the tag entity table is used (instead of a newly generated uuid for the same tag text) when duplicate entries across the tables are removed by the union.
Related
This is my query, I tried this query it works.
SELECT *
FROM conference_venue
WHERE id_venue NOT IN (SELECT id_venue FROM submission_data WHERE id_submission = 1);
i want to display data in conference_venue. but I don't want to display data whose id_venue is the same as the submission_data table (same as id_venue whose id_submission is mentioned).
I'm trying to make a query for the laravel version, but it's a blank white screen with no errors.
DB::table('conference_venue')
->whereNotIn('id_venue', function($q){
$q->select('id_venue')
->from('submission_data')
->where('id_submission', '=', 1);
})->select('*')->get();
This query works when I try it in sql query console but fails when I try it with Laravel query builder.
You can try this:
DB::table('conference_venue')
->select('*')
->whereRaw(
'conference_venue.id_venue NOT IN (SELECT submission_data.id_venue FROM submission_data WHERE id_submission = 1)'
);
Or better yet, create a Model for conference_venue and submission_data (ie: ConferenceVenue, SubmissionData) and you can add Eloquent relationships for ConferenceVenue and SubmissionData.
Eloquent relationships, which supports a variety of common
relationships (One To One, One To Many, Many To Many, etc.), are
defined as methods on your Eloquent model classes. Since relationships
also serve as powerful query builders, defining relationships as
methods provides powerful method chaining and querying capabilities.
Eloquent: Relationships
On you ConferenceVenue Class, you can add a method something similar to the following:
public function available() {
return this->hasMany(SubmissionData, 'id_venue')
->select('*') // You can also specify relevant columns ONLY
->whereRaw(
'conference_venue.id_venue NOT IN (SELECT submission_data.id_venue FROM submission_data WHERE id_submission = 1)'
);
}
Where you can use the relationship method as follows:
$available = ConferenceVenue::with('available')->get();
I'm building a product management tool where the product can have an arbitrary number of attributes, documents, features, images, videos as well as a single type, brand, and category. There are a few other related tables, but this is enough to demonstrate the problem.
There's a Model class called ProductModel that contains a method like this (reduced for clarity):
public function loadValues() {
//Product entity data
$this->id = $this->entity->getId();
$this->slug = $this->entity->getSlug();
// One of each of these
$this->loadType();
$this->loadBrand();
$this->loadCategory();
// Arbitrary number of each of these
$this->loadAttributes();
$this->loadDocuments();
$this->loadFeatures();
$this->loadImages();
$this->loadVideos();
...
}
Each of the load methods does some boiler plate that eventually executes this method:
public function loadEntitiesByProductId($productId=0) {
// Get all the entities of this type that are associated with the product.
$entities = $this->entityManager
->getRepository($this->entityName)
->findByProduct($productId);
$instances = array();
// Create a Model for each entity and load the data.
foreach ($entities as $entity) {
$id = $entity->getId();
$instances[$id] = new $this->childClass();
$instances[$id]->entity = $entity;
$instances[$id]->loadValues();
}
return $instances;
}
This is OK for cases where the related entity is a single table, but usually it's a mapper. In those cases, I get all the mapper entities in the first query then I have to query for the related entity within the loadValues() method (via Doctrine's get<Entity>() method). The result of this process is a huge number of queries (often >100). I need to get rid of the extraneous queries, but I'd like to do so without losing the idioms I'm using across my data models.
Is there a way to get the entityManager to do a better job at using joins to group these queries?
There were a couple problems with my previous approach:
First, I was getting the entities from the repository instead of loading them from the existing entity:
$entities = $this->entityManager
->getRepository($this->entityName)
->findByProduct($productId);
Better is:
$method = $this->deriveGetMethod($this->entityName);
$entities = $productEntity->$method()
Second, I was retrieving the product entity using $this->entityManager->getRespository... which works fine for loading small data sets (a single table or one or two relations), but there's no way to get the repository's findBy methods to load relations in a single query. The solution is to use the queryBuilder.
$qb = $this->entityManger->createQueryBuilder();
$query = $this->select('product',/*related tables*/)->/*joins etc.*/
$productEntity = $query->getSingleResult();
let me at first state that we use php and postgre database. In our project we have decided not to use any ORM due to its overload of sql queries and we are taking the oposite way.
Imagine you have a select from several tables, lets say joined on id columns. For instance:
tables: users(id, name), items(id, name, description), comments(user_id, item_id, text, rating)
So basically you have a table of users, a table of some items and a table of comments which are related to one user and one item.
You create two objects - user and item representing their table row. And then you want to create a comment object. In an ORM it would contain objects user and item and they would load themselves with their queries, but that would be two queries and you re thinking...hm but I can select that data with a single query...but how?
Imagine that you have this select:
SELECT * FROM comments JOIN users ON comments.user_id = users.id JOIN items ON comments.item_id = items.id
(you can also imagine a WHERE clause with specified item id or user id etc.)
So how would you split the result of such a select into this class structure, lets say you want a list of comment objects:
user
item
comment (contains references to user and item object)
So far our theoretical solution was to prefix name of the columns with fixed prefixes :) and then propagating the result into the object structure and each objects takes what it needs from the select. Any other solutions? Lets say more sophisticated?
Thanks for any ideas
PS: obviously I have used a very simple example, but try to imagine that the problem is far larger and the structure far more complex
First of all, you might benefit from looking at the Data Mapper pattern. A simple use-case with would look like this:
$user = new User;
$mapper = new UserMapper( $db );
$user->setName('foobar');
$mapper->fetch( $user );
if ( $user->isBanned() )
{
throw new Exception('get out !');
}
$user->setLastActive( time() );
$mapper->store( $user );
As for the single query with data: that's not the important part. You just ALIAS it as required (oh .. and i hope you are not using the * for selecting rows). The important bit is creating an object graph from selected data. That where you use builders/factories.
//the rest of PDO-related code
$data = $statement->fecth(PDO::FETCH_ASSOC);
$comment = $commentFactory->build($data);
Where $commentFactory is instance of CommentFactory:
class CommentFactory
{
public function build( $params )
{
$author = new User;
$subject = new Item;
$comment = new Comment( $author, $subject );
$author->setId( $params['user_id']);
$author->setName( $params['user_name']);
$subject->setId( $param['item_id']);
$comment->setContent( $param['content']);
return $comment;
}
}
Additionally with setup like this, you can easily change how $comment is made, just by changing what class is the $commentFactory an instance of.
I have a simple entity with many-to-many and one-to-many associations. I'm aware of 'Joins' for fetching related associations which is a manual solution for my problem.
How can I fetch an entity with all of its associations using EntityManager in Doctrine2? e.g.:
$this->em
->getRepository('Entities\Patientprofile')
->findOneByuserid('555555557')
->fetchAllAssociations();
from http://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html#temporarily-change-fetch-mode-in-dql
you can set eager fetch mode temporarily:
$query = $em->createQuery("SELECT u FROM MyProject\User u");
$query->setFetchMode("MyProject\User", "address", "EAGER");
$query->execute();
If you want do load dynamically all associations with this fetch mode, you can use the getAssociationMappings() method of the Doctrine\ORM\Mapping\ClassMetadataInfo, passing your entity name as parameter to the constructor of ClassMetadataInfo and then iterate over the returned array as $assoc and call:
$query->setFetchMode("MyProject\User", $assoc, "EAGER");
Doc: ClassMetadataInfo#getAssociationMappings()
Doctrine2 setFetchMode not working with "EAGER"
I tried also to fetch the associating entities "eagerly" using setFetchMode in my query, but the following didn't seem to work:
$query->setFetchMode("MyProject\User", "address", "EAGER");
When I jumped into the files I found out that the third parameter $fetchMode should be an integer. The constants are defined in Doctrine\ORM\Mapping:ClassMetadataInfo. When passing a string it will default to Mapping\ClassMetadata::FETCH_LAZY because of this if clause.
/**
* Specifies that an association is to be fetched when it is first accessed.
*/
const FETCH_LAZY = 2;
/**
* Specifies that an association is to be fetched when the owner of the
* association is fetched.
*/
const FETCH_EAGER = 3;
/**
* Specifies that an association is to be fetched lazy (on first access) and that
* commands such as Collection#count, Collection#slice are issued directly against
* the database if the collection is not yet initialized.
*/
const FETCH_EXTRA_LAZY = 4;
So setting the corresponding integer solved the problem:
$query->setFetchMode("MyProject\User", "address", 3);
Or declare the class use Doctrine\ORM\Mapping\ClassMetadata at the top and then use the constant:
$query->setFetchMode("MyProject\User", "address", ClassMetadata::FETCH_EAGER);
EDIT:
Since there seems to be a lot of confusion here on how to fetch associations the right way I will edit my answer and add some additional information on how you can fetch join using your repository.
According to the Doctrine documentation there are 2 types of joins:
Regular Joins: Used to limit the results and/or compute aggregate values.
Fetch Joins: In addition to the uses of regular joins: Used to fetch related entities and include them in the hydrated result of a
query.
So to get an entity including its associations you will need to "fetch-join" all these associations to make sure they are loaded eagerly.
I usually don't use DQL queries for getting entities and solving my fetch joins, instead I add a custom method to a repository where I use a query builder. This is more flexible and much more readable then using DQL. The correct DQL query will be created by the query builder when we call the createQuery method. You can check the created DQL query of course for debug purposes.
An example for such a custom method inside the Patientprofile entity repository from the question above:
public function findPatientByIdWithAssociations($id)(
// create a query builder for patient with alias 'p'
$qb = $this->createQueryBuilder('p')
->where('p.id = :patient_id')
->addSelect('pd')
->leftJoin('p.documentation', 'pd')
->addSelect('pa')
->leftJoin('p.address', 'pa')
->setParameter('patient_id', $id);
$query = $queryBuilder->getQuery();
return $query->getSingleResult();
}
And now you can use your custom repository method to get the patient by id (for example '555555557') including associations to the patient documentation and address:
$repository = $this->em->getRepository('Entities\Patientprofile');
$patient = $repository->findPatientByIdWithAssociations('555555557');
Make sure you use both addSelect and leftJoin to do eager loading.
Doctrine 2 uses Proxy classes for lazy loading, so you don't actually need to have the associations' data fetched until you use the objects. Since the Proxy classes inherit from your association classes, you're able to use the proxies exactly as you would use the fretch association classes.
but, if you really need to fetch the actual association classes, you need to tell the query to set the fetch mode to Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER. If you're using the annotations, you can achieve this with:
e.g.
/**
* #ManyToMany(targetEntity="Item", fetch="EAGER")
*/
private $items;
You can use a DQL query:
$query = $em->createQuery("SELECT p, f FROM Entities\\Patientprofile p JOIN p.Foo f WHERE p.id = ?1");
$query->setParameter(1, 321);
$patient = $query->getSingleResult();
Faced the same problem.
It was necessary to pull out all chain of parents of an element.
$query->setFetchMode(EntityClass, "alias_in_entity", 3) gets only 1 lvl deep, other parents are just proxy.
This can be fixed by changed in entity class fetch mode to eager. But if it`s not if this is not possible for some reason (performance etc), this can be made as #wormhit mentioned by changing entity metadata "on fly"
Example:
$query = $this->entityManager->createQueryBuilder()->select('fields')
->from(FormField::class, 'fields');
$metadata = $this->entityManager->getClassMetadata(FormField::class);
$metadata->setAssociationOverride('parent', ['fetch' => \Doctrine\ORM\Mapping\ClassMetadata::FETCH_EAGER]);
return $query->getOneOrNullResult();
I am wondering about using the abstraction in Zend Db RowSet instead of joins, is it possible
for instance I am able to get some info from parent table as in here
/**
* Get default photo info (path , description)
*/
public function getDefaultPhotoInfo($userdId){
$select = $this->select($this)
->where('id=?', $userdId);
$rowset = $this->fetchAll($select);
$current = $rowset->current();
$res = $current->findParentRow('UserPhotos', 'Avatar');
if(isset($res)){
return $res->toArray();
}
}
How can I use Rowset abstraction to get this logic working
table( id, pic_path,) table_translation(id, table_id, lang_id, pic_title);
the above is representation of two tables , the idea is to get the info from both table specifying the lang_id , it is easy with joins but can I do it with the abstraction of Db Rowset ?
Just for clarification: when doing fetchAll on a Zend_Db_Table instance, you get a Zend_Db_Table_Rowset, which implements the Iterator interface. Thus, calling current() on the Rowset instance, will return a Zend_Db_Table_Row instance.
As of ZF1.10, you define relationships between tables in a Zend_Db_Table_Defintion instance or on a concrete table instance like described in the reference guide for Zend_Db_Table Relationships. Since the guide is rather detailed, I won't reproduce this here.
Once you defined relationships, you can fetch them from a row with (example 1 from guide)
$accountsTable = new Accounts();
$accountsRowset = $accountsTable->find(1234);
$user1234 = $accountsRowset->current();
$bugsReportedByUser = $user1234->findDependentRowset('Bugs');
or by the magic finder methods.
The findParentRow() method is somewhat different to that, as it return the full row of a dependent rowset from it's parent row.
Ex5: This example shows getting a Row object from the table Bugs (for example one of those bugs with status 'NEW'), and finding the row in the Accounts table for the user who reported the bug.
$bugsTable = new Bugs();
$bugsRowset = $bugsTable->fetchAll(array('bug_status = ?' => 'NEW'));
$bug1 = $bugsRowset->current();
$reporter = $bug1->findParentRow('Accounts');
When using table relations, keep in mind that these will result in one additional query per fetched dependent table, whereas a Join does it all in one.
Also see this related questions:
Modeling objects with multiple table relationships in Zend Framework