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
Related
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.
I have a MySQL database and a table tobjects where each record has its id, parameter, value (something like XML) and one can say that this parameter column determines the "type" of an object.
The objects are used in some other tables, depending on their types, so each of them should be handled in specific way.
Because "handling" is somewhat common (I use the same function) I created a TObject class (not abstract but could be) from which I inherit other classes; this inheritance method is very useful and that's the very reason I use object oriented programming. For example TObject has retrieve() method that gets from db all the necessary data, not those in tobjects table but others too, which are type dependent, so I override it in some classes.
The problem I encountered is that when I create an object I do not know what class should it be. Of course, I can SELECT Parameter FROM tobjects WHERE id=$id, and then (with switch) create object of the proper class, and use its retrieve() method (each class retrieves different data, only those from tobjects are common) to get data from the db, that causes me to run query two times and some part of work outside the class, which works, but is not gentle.
The best solution would be if I can create a TObject and then, upon retrieving, change the class of the object to the one I need and it would be TObject's descendant, but I'm almost sure it's not possible.
Is my solution, that I run the first query just to select one field from tobjects only to determine object's class right? Or is there a trick to change object's class in runtime?
If understand what you are doing correctly, here is the way I would approach this:
Passing PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE to the first argument of PDOStatement::fetch() will return an object of class PDOStatement::fetchColumn(0) - in other words, it determines the class name to instantiate from the value of the first column of the result set.
To leverage this, you would JOIN tobjects ON targetTable.objectType = tobjects.id and select tobjects.Parameter as the first column in the result set. If the Parameter column already holds a 1:1 mapping of database object types to class names, this is all you need to do, however I'm not sure whether this is the case, and it probably shouldn't be, because it makes it more difficult to substitute another class at a later date.
To overcome this limitation, I suggest you create a temporary table when you first connect the database, which maps Parameter values to class names, which you can JOIN onto the query to obtain the target class name.
So the flow would go something like this:
// Set up the connection
$db = new PDO('mysql:yourDSNhere');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// Create a temp table to store the mapping
$db->query("
CREATE TEMPORARY TABLE `objectMappings` (
`Parameter` INT NOT NULL PRIMARY KEY,
`ClassName` VARCHAR(255)
) ENGINE=MEMORY
");
// A mapping of Parameter IDs to class names
$classMap = array(
1 => 'Class1',
2 => 'Class2',
3 => 'Class3',
// ...
);
// Build a query string and insert
$rows = array();
foreach ($classMap as $paramId => $className) {
// this data is hard-coded so it shouldn't need further sanitization
$rows[] = "($paramId, '$className')";
}
$db->query("
INSERT INTO `objectMappings`
(`Parameter`, `ClassName`)
VALUES
".implode(',
', $rows)."
");
// ...
// When you want to retrieve some data
$result = $db->query("
SELECT m.ClassName, t.*
FROM targetTable t
JOIN tobjects o ON t.objectType = o.id
JOIN objectMappings m ON o.Parameter = m.Parameter
WHERE t.someCol = 'some value'
");
while ($obj = $result->fetch(PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE)) {
// $obj now has the correct type, do stuff with it here
}
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();
could some one please explain this snippet of magento code found in loadByCustomerId() in the class Mage_Sales_Model_Mysql4_Quote.
$read = $this->_getReadAdapter();
$select = $this->_getLoadSelect('customer_id', $customerId, $quote)
->where('is_active=1')
->order('updated_at desc')
->limit(1);
$data = $read->fetchRow($select);
When i var_dump($data) i see that its an array of customer data. What is model associated with this $data array? Thanks.
Magento "Models" (meaning the entities that allow you to interact with the database, not general purpose server/domain models) have two layers. The first is the "Model" layer. This contains methods for logical interactive with an model. (get me a customer's address, place the order, etc). The second layer is the "Resource Model" layer. Resource Models handle any interactive with the database (or,more generally, the data-store,or the persistance layer, or etc.).
The way a Resource Model interacts with the database is via adapter objects. One for reading information, another for writing information.
So, you're in the class Mage_Sales_Model_Mysql4_Quote. This is a Resource Model. It's the backend for the Mage_Sales_Model_Quote object, instantiated with
$model = Mage::getModel('sales/quote');
With this line
$read = $this->_getReadAdapter();
you're getting a reference to the model's read adapter. This will let you make queries to the database.
With this line
$select = $this->_getLoadSelect('customer_id', $customerId, $quote)
->where('is_active=1')
->order('updated_at desc')
->limit(1);
You're getting a reference to the SQL statement (also an object) that this Resource Model will use to load a sales/quote object.
//gets a reference
$this->_getLoadSelect('customer_id', $customerId, $quote)
Then, you're calling methods on that object to alter it with additional logic
->where('is_active=1')
->order('updated_at desc')
->limit(1);
In pseudo sql, a query might look like this normally
SELECT * FROM quote_table;
But after you call those methods, the query will look something like
SELECT * FROM quote_table
WHERE is_active = 1
ORDER BY updated_at desc
LIMIT 1;
Finally,
$data = $read->fetchRow($select);
here you're using the read adapter you fetched earlier to make a query into the database for the specific quote item row that your query will fetch.
_getReadAdapter() gets the read-only database connection. _getLoadSelect creates a select query on the model's (Mage_Sales_Model_Mysql4_Quote) main table. The data returned is just raw data from the SQL query not associated with any particular backend model.