How to test custom DQL function? - php

I wrote a custom DQL function based on this tutorial.
I would like to write some tests, but the functionality of DQL is quite complex.
Is there any way how to test it?
As I see, even doctrine project not tests own DQL functions :(

I know it's an old quesion but for the folks who goggled it out as I did.
Testing is simple in fact. The general idea is to generate a query using your custom funciton then compare it against the expected result. Here is the example.
Let's say you have a custom DQL function which translates MY_FUNC(field) to MySQL_INTERNAL_FUNC(field). Then you test it as the following.
/* Somewhere in the PHPUnit test */
/* Assuming $em represents your EntityManager instance */
$query = $em->createQuery('
SELECT MY_FUNC(entity.someField)
FROM MyBundle:MyEntity entity
');
self::assertEquals('
SELECT MySQL_INTERNAL_FUNC(v0_.some_field) AS sclr_0
FROM my_entity v0_
', $query->getSQL());
That does the trick. Note the indents. They are there just to make the code clear. In practice Doctrine will remove them making SQL. So the expected SQL code should be in one line.

Related

Missing insert() method on Doctrine DBAL Query Builder

I feel like I'm having a moment where I'm missing something small here; I've been having issues using the insert() method on the QueryBuilder component on Dotrine DBAL 2.2.x / 2.3.x.
I did some investigation and here's the snippet from the QueryBuilder page from the DBAL Documantation
The \Doctrine\DBAL\Query\QueryBuilder supports building SELECT, INSERT, UPDATE and DELETE queries. Which sort of query you are building depends on the methods you are using.
It goes on further to explain code examples, such that I can simply do:
$builder = $connection->createQueryBuilder();
$result = $builder
->insert('table_name')
// ...
To use the query builder in Insert Mode. Except when I do I get a complaint here from PHP:
Fatal error: Call to undefined method Doctrine\DBAL\Query\QueryBuilder::insert()
On further inspection of The QueryBuilder.php Source Code
I see no reference to any method insert(...), no class to inherit this from, no traits added to the QueryBuilder that could expose the insert mechanism. In addition I see this right at the top:
/* The query types. */
const SELECT = 0;
const DELETE = 1;
const UPDATE = 2;
There's no insert query type; there is however this interesting method comment for execute():
/**
* Execute this query using the bound parameters and their types.
*
* Uses {#see Connection::executeQuery} for select statements and {#see Connection::executeUpdate}
* for insert, update and delete statements.
*
* #return mixed
*/
Bottom Line:
This is a massive project with 100's of maintainers, I'm more likely to find my interpretation suspect here than a screwup on something so fundamental over numerous versions, but I cannot for the life of me figure out what I'm missing. Please help me see the obvious.
It depends on your version. Insert has been added since v2.5.0-BETA3.
Viz https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Query/QueryBuilder.php#L563
and commit
You can decide to update package version or check this alternative solution

Does the Yii CActiveRecord class have a first() method?

I need to just get the first record from a Yii CActiveRecord derived class. In Rails I would just be able to do this:
post = Post.first
I thought I could do the same thing with Yii like this:
$post = Post::model()->first();
But that method doesn't exist. Do I have to just do find with a condition to get the first record?
I don't see first() in the docs for CActiveRecord so I assume the answer is no, it doesn't have a first method. So how would one go about querying just the first record?
This works but sure is an ugly hack. Surely there's a better way.
$first = Post::model()->findAll(array('order'=>id, 'limit'=>1));
Yii isn't going to make any assumptions about how your data should be ordered. Good database design requires that if you use a surrogate key, that key should have a meaningless value. That means NOT using it for ordering.
That issue aside, here is probably the best way to do your query:
$first = Post::model()->find(array('order'=>'id ASC'));
By using find instead of findAll you automatically apply a LIMIT 1 to your result. I would not skip the inclusion of the order by clause, as that insures that the database will order the results consistently.
If you use this query a lot, you can create the following method. UPDATE: Modified it to throw an exception when the primaryKey is composite or missing. We could add more error checking as well, but we leave that as an exercise for the reader. ;)
public function first($orderBy = null){
if(!$orderBy){
$orderBy = self::model()->tableSchema->primaryKey;
}
if(!is_string($orderBy)){
throw new CException('Order by statement must be a string.');
}
return self::model()->find(array('order'=>$orderBy));
}
Then include this method in a class which extends CActiveRecord, and then extend all your models form that class.
The wrapper I wrote will by default order results by the primary key, but you could optionally pass a different column and direction (ASC OR DESC) if you wish.
Then if you do this for the post class, you can access the first model like so:
$first = Post::model()->first();
CActiveRecord::find() returns only one model.
$first=Post::model()->find();
Yii2 asks for a condition when doing a findOne().
You could do a find() following with no conditions and just return one()
$first= Post::find()->one();
To really be sure you could just add a orderBy clause to it:
$first= Post::find()->orderBy(['id' => SORT_ASC])->one();
Same goes for the command function:
$first= \Yii::$app->myDatabase->createCommand('SELECT * FROM Post ORDER BY id ASC')->queryOne();

How to properly write Doctrine 2 DQL queries?

I have upgraded to Doctrine 2.2.2 now. I have managed to successfully connect my database to my application and was able to generate proxies and repositories. I have no problem with those generation. I am just confused with regards to using the DQL of doctrine 2.2.2.
The case is this: I currently have a repository class responsible for user registration, authentication, etc. I have managed to execute the DQL on it but I just felt weird about this stuff (in my repository class).
$query = $em->createQuery("SELECT u FROM MyProject\\Entity\\AdminUsers u");
I tried also:
$query = $em->createQuery("SELECT u FROM AdminUsers u");
The last did not work but the first one works fine but it seems weird. Is it really the right way of executing DQL in doctrine 2? or am I missing something important.
NOTE: on the above declaration of this repository class is:
namespace MyProject\Repository;
use Doctrine\ORM\EntityRepository,
MyProject\Entity\AdminUsers;
It almost is the right way to do it. If you would use single quotes ', you could just use a single backslash \ instead of a double backslash \\.
Doctrine cant find out (or it would be extremely expensive to do so) which classes you imported via use statements.
But you can use a typed repository which you retrieve from the entity manager via:
$repo = $em->getRepository('MyDomain\Model\User');
$res = $repo->findSomeone();
And in the findSomeone() function you can do this:
$qb = $this->createQueryBuilder('u');
$dql = $qb->where('u.id = 1')->getDQL();
return $this->_em->createQuery($dql)->getSingleResult();
Meaning, the repository is already typed on your entity and knows which class to select from.
Some documentation:
Querying with doctrine
Querybuilder
10 step get started guide (which covers the basics including repositories)

Lazy Loading with Doctrine2 and Symfony2 using DQL

I have a tree structure with a parent field. Currently I am trying to get all parent nodes to display the path to the current node.
Basically I am doing a while-loop to process all nodes.
$current = $node->getParent();
while($current) {
// do something
$current = $current->getParent();
}
Using the default findById method works. Because the entity has some aggregated fields, I am using a custom repository method, to load all basic fields with one query.
public function findNodeByIdWithMeta($id) {
return $this->getEntityManager()
->createQuery('
SELECT p, a, c, cc, ca, pp FROM
TestingNestedObjectBundle:NestedObject p
JOIN p.actions a
LEFT JOIN p.children c
LEFT JOIN c.children cc
LEFT JOIN c.actions ca
LEFT JOIN p.parent pp
WHERE p.id = :id
')
->setParameter('id', $id)
->setHint(
\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER,
'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
)
->getOneOrNullResult();
}
With that code, loading the parents fails. I only get the immediate parent (addressed by LEFT JOIN p.parent pp) but not the parents above. E.g. $node->getParent()->getParent() returns null.
Whats wrong with my code? Did I misunderstood the lazy loading thing?
Thanks a lot,
Hacksteak
It looks like your are using the adjacency model for storing trees in a relational database. Which in turn means, that you will need a join for every level to get all ancestors with a single query.
As you are already using the Doctrine Extension Library I recommend to have a look at the Tree component.
My Answer involves not using DQL and instead creating a NestedSetManager which has access to your DBAL connection so you can use SQL. I never felt like the ORM's did a good job with NestedSets query logic.
With a NestedSetManager, you can then write a bunch of clean methods and it's really simple because all these queries are well documented. See this link. Some of the method in my NestedSetManager are:
setNode();
setRoot();
loadNestedSet();
moveNodeUp();
modeNodeDown();
getRootNode();
addNodeSibling();
getNodesByDepth();
getParents();
getNodePath();
childExists();
addChildToNode();
renameNode();
deleteNode();
// And many more
You can have a ball and create a lot of create NestedSet functionality if you're not tied down by an ORM's somewhat complex functionality.
Also -- Symfony2 makes all this really really easy. You create your NestedSetManager class file and reference it in your Services.yml and pass in your Dbal connection. Mine looks like this:
services:
manager.nestedset:
class: Acme\CoreBundle\Manager\NestedSetManager
arguments: [ #database_connection ]
you can then access your nestedsets with:
$path = $this->get('manager.nestedset')->setNode(4)->getNodePath(); // in your controller
Moral of the story, ORM/NestedSets drove me bonkers and this solution work really well. If you're being forced to use DQL and have no other options, this answer probably wont be acceptable.

How to test across multiple mysql schemas with PHPUnit?

I would like to test a process that queries across multiple schemas using PHPUnit's PHPUnit_Extensions_Database_TestCase class. I've dug through the documentation, SO and the source code, but it seems like I have to set up my database using something like:
protected function getConnection()
{
$this->pdo = new PDO('mysql:host=localhost;dbname=unit_test_schema', 'xxxx', 'yyyy');
return $this->createDefaultDBConnection($this->pdo, 'unit_test_schema');
}
protected function getDataSet()
{
return $this->createXMLDataSet(DB_SETUP_DIR.'/schema.xml');
}
Is there a way to somehow use more than one schema so I can test a query like:
SELECT *
FROM schema1.tableA
JOIN schema2.tableB
USING (id)
EDIT: To be clear, the issue I'm trying to resolve is that I can only figure out how to pass schema setup files to one database. I'd like to find a way to say "create tables1.xml in schema1 and tables2.xml in schema2." The tables referenced in the different xml files would be filled-and-killed for each test.
One thing I've done is created a definition for each schema, then overrode the definition when unit testing
define('schema1', 'schema1_prod');
define('schema2', 'schema2_prod');
Then for unit testing
define('schema1', 'unit_tests');
define('schema2', 'unit_tests');
If you have like table names across multiple schemas this will still break, but it should help if you don't.

Categories