Last active
January 3, 2016 07:09
-
-
Save khuppenbauer/8427244 to your computer and use it in GitHub Desktop.
add group by to TYPO3Flow Persistence
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace TYPO3\Flow\Tests\Functional\Persistence\Doctrine; | |
/* * | |
* This script belongs to the TYPO3 Flow framework. * | |
* * | |
* It is free software; you can redistribute it and/or modify it under * | |
* the terms of the GNU Lesser General Public License, either version 3 * | |
* of the License, or (at your option) any later version. * | |
* * | |
* The TYPO3 project - inspiring people to share! * | |
* */ | |
use TYPO3\Flow\Persistence\Doctrine\Query; | |
/** | |
* Testcase for query | |
* | |
*/ | |
class QueryTest extends \TYPO3\Flow\Tests\FunctionalTestCase { | |
/** | |
* @var boolean | |
*/ | |
static protected $testablePersistenceEnabled = TRUE; | |
/** | |
* @return void | |
*/ | |
public function setUp() { | |
parent::setUp(); | |
if (!$this->persistenceManager instanceof \TYPO3\Flow\Persistence\Doctrine\PersistenceManager) { | |
$this->markTestSkipped('Doctrine persistence is not enabled'); | |
} | |
} | |
/** | |
* @test | |
*/ | |
public function simpleQueryCanBeSerializedAndDeserialized() { | |
$query = new Query('TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity'); | |
$serializedQuery = serialize($query); | |
$unserializedQuery = unserialize($serializedQuery); | |
$this->assertQueryEquals($query, $unserializedQuery); | |
} | |
/** | |
* @test | |
*/ | |
public function simpleQueryCanBeExecutedAfterDeserialization() { | |
$testEntityRepository = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntityRepository(); | |
$testEntityRepository->removeAll(); | |
$testEntity1 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity1->setName('Flow'); | |
$testEntityRepository->add($testEntity1); | |
$this->persistenceManager->persistAll(); | |
$query = new Query('TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity'); | |
$serializedQuery = serialize($query); | |
$unserializedQuery = unserialize($serializedQuery); | |
$this->assertEquals(1, $unserializedQuery->execute()->count()); | |
$this->assertEquals(array($testEntity1), $unserializedQuery->execute()->toArray()); | |
} | |
/** | |
* @test | |
*/ | |
public function moreComplexQueryCanBeSerializedAndDeserialized() { | |
$query = new Query('TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity'); | |
$query->matching($query->equals('name', 'some')); | |
$serializedQuery = serialize($query); | |
$unserializedQuery = unserialize($serializedQuery); | |
$this->assertQueryEquals($query, $unserializedQuery); | |
} | |
/** | |
* @test | |
*/ | |
public function moreComplexQueryCanBeExecutedAfterDeserialization() { | |
$testEntityRepository = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntityRepository(); | |
$testEntityRepository->removeAll(); | |
$testEntity1 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity1->setName('Flow'); | |
$testEntityRepository->add($testEntity1); | |
$testEntity2 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity2->setName('some'); | |
$testEntityRepository->add($testEntity2); | |
$this->persistenceManager->persistAll(); | |
$query = new Query('TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity'); | |
$query->matching($query->equals('name', 'Flow')); | |
$serializedQuery = serialize($query); | |
$unserializedQuery = unserialize($serializedQuery); | |
$this->assertEquals(1, $unserializedQuery->execute()->count()); | |
$this->assertEquals(array($testEntity1), $unserializedQuery->execute()->toArray()); | |
} | |
/** | |
* @test | |
*/ | |
public function countIncludesAllResultsByDefault() { | |
$testEntityRepository = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntityRepository(); | |
$testEntityRepository->removeAll(); | |
$testEntity1 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity1->setName('Flow'); | |
$testEntityRepository->add($testEntity1); | |
$testEntity2 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity2->setName('some'); | |
$testEntityRepository->add($testEntity2); | |
$testEntity3 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity3->setName('more'); | |
$testEntityRepository->add($testEntity3); | |
$this->persistenceManager->persistAll(); | |
$query = new Query('TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity'); | |
$this->assertEquals(3, $query->execute()->count()); | |
} | |
/** | |
* @test | |
*/ | |
public function countRespectsLimitConstraint() { | |
$testEntityRepository = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntityRepository(); | |
$testEntityRepository->removeAll(); | |
$testEntity1 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity1->setName('Flow'); | |
$testEntityRepository->add($testEntity1); | |
$testEntity2 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity2->setName('some'); | |
$testEntityRepository->add($testEntity2); | |
$testEntity3 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity3->setName('more'); | |
$testEntityRepository->add($testEntity3); | |
$this->persistenceManager->persistAll(); | |
$query = new Query('TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity'); | |
$this->assertEquals(2, $query->setLimit(2)->execute()->count()); | |
} | |
/** | |
* @test | |
*/ | |
public function countRespectsOffsetConstraint() { | |
$testEntityRepository = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntityRepository(); | |
$testEntityRepository->removeAll(); | |
$testEntity1 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity1->setName('Flow'); | |
$testEntityRepository->add($testEntity1); | |
$testEntity2 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity2->setName('some'); | |
$testEntityRepository->add($testEntity2); | |
$testEntity3 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity3->setName('more'); | |
$testEntityRepository->add($testEntity3); | |
$this->persistenceManager->persistAll(); | |
$query = new Query('TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity'); | |
$this->assertEquals(1, $query->setOffset(2)->execute()->count()); | |
} | |
/** | |
* @test | |
*/ | |
public function countRespectsGroupByConstraint() { | |
$testEntityRepository = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntityRepository(); | |
$testEntityRepository->removeAll(); | |
$testEntity1 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity1->setName('Flow'); | |
$testEntityRepository->add($testEntity1); | |
$testEntity2 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity2->setName('some'); | |
$testEntityRepository->add($testEntity2); | |
$testEntity3 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity; | |
$testEntity3->setName('some'); | |
$testEntityRepository->add($testEntity3); | |
$this->persistenceManager->persistAll(); | |
$query = new Query('TYPO3\Flow\Tests\Functional\Persistence\Fixtures\TestEntity'); | |
$this->assertEquals(2, $query->setGroupBy(array('name'))->execute()->count()); | |
} | |
/** | |
* @test | |
*/ | |
public function comlexQueryWithJoinsCanBeExecutedAfterDeserialization() { | |
$postEntityRepository = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\PostRepository; | |
$postEntityRepository->removeAll(); | |
$commentRepository = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\CommentRepository; | |
$commentRepository->removeAll(); | |
$testEntity1 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\Post; | |
$testEntity1->setTitle('Flow'); | |
$postEntityRepository->add($testEntity1); | |
$testEntity2 = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\Post; | |
$testEntity2->setTitle('Flow with comment'); | |
$comment = new \TYPO3\Flow\Tests\Functional\Persistence\Fixtures\Comment; | |
$comment->setContent('Flow'); | |
$testEntity2->setComment($comment); | |
$postEntityRepository->add($testEntity2); | |
$commentRepository->add($comment); | |
$this->persistenceManager->persistAll(); | |
$query = new Query('TYPO3\Flow\Tests\Functional\Persistence\Fixtures\Post'); | |
$query->matching($query->equals('comment.content', 'Flow')); | |
$serializedQuery = serialize($query); | |
$unserializedQuery = unserialize($serializedQuery); | |
$this->assertEquals(1, $unserializedQuery->execute()->count()); | |
$this->assertEquals(array($testEntity2), $unserializedQuery->execute()->toArray()); | |
} | |
protected function assertQueryEquals(Query $expected, Query $actual) { | |
$this->assertEquals($expected->getConstraint(), $actual->getConstraint()); | |
$this->assertEquals($expected->getOrderings(), $actual->getOrderings()); | |
$this->assertEquals($expected->getOffset(), $actual->getOffset()); | |
$this->assertEquals($expected->getLimit(), $actual->getLimit()); | |
$this->assertEquals($expected->getGroupBy(), $actual->getGroupBy()); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace TYPO3\Flow\Persistence\Doctrine; | |
/* * | |
* This script belongs to the TYPO3 Flow framework. * | |
* * | |
* It is free software; you can redistribute it and/or modify it under * | |
* the terms of the GNU Lesser General Public License, either version 3 * | |
* of the License, or (at your option) any later version. * | |
* * | |
* The TYPO3 project - inspiring people to share! * | |
* */ | |
use TYPO3\Flow\Annotations as Flow; | |
/** | |
* A Query class for Doctrine 2 | |
* | |
* @api | |
*/ | |
class Query implements \TYPO3\Flow\Persistence\QueryInterface { | |
/** | |
* @var string | |
*/ | |
protected $entityClassName; | |
/** | |
* @Flow\Inject | |
* @var \TYPO3\Flow\Log\SystemLoggerInterface | |
*/ | |
protected $systemLogger; | |
/** | |
* @var \Doctrine\ORM\QueryBuilder | |
*/ | |
protected $queryBuilder; | |
/** | |
* @var \Doctrine\ORM\EntityManager | |
*/ | |
protected $entityManager; | |
/** | |
* @var mixed | |
*/ | |
protected $constraint; | |
/** | |
* @var array | |
*/ | |
protected $orderings; | |
/** | |
* @var integer | |
*/ | |
protected $limit; | |
/** | |
* @var integer | |
*/ | |
protected $offset; | |
/** | |
* @var integer | |
*/ | |
protected $parameterIndex = 1; | |
/** | |
* @var array | |
*/ | |
protected $parameters; | |
/** | |
* @var array | |
*/ | |
protected $joins; | |
/** | |
* @var integer | |
*/ | |
protected $joinAliasCounter = 0; | |
/** | |
* @var array | |
*/ | |
protected $groupBy; | |
/** | |
* @param string $entityClassName | |
*/ | |
public function __construct($entityClassName) { | |
$this->entityClassName = $entityClassName; | |
} | |
/** | |
* @param \Doctrine\Common\Persistence\ObjectManager $entityManager | |
* @return void | |
*/ | |
public function injectEntityManager(\Doctrine\Common\Persistence\ObjectManager $entityManager) { | |
$this->entityManager = $entityManager; | |
$this->queryBuilder = $entityManager->createQueryBuilder()->select('e')->from($this->entityClassName, 'e'); | |
} | |
/** | |
* Returns the type this query cares for. | |
* | |
* @return string | |
* @api | |
*/ | |
public function getType() { | |
return $this->entityClassName; | |
} | |
/** | |
* Executes the query and returns the result. | |
* | |
* @return \TYPO3\Flow\Persistence\QueryResultInterface The query result | |
* @api | |
*/ | |
public function execute() { | |
return new \TYPO3\Flow\Persistence\Doctrine\QueryResult($this); | |
} | |
/** | |
* Gets the results of this query as array. | |
* | |
* Really executes the query on the database. | |
* This should only ever be executed from the QueryResult class. | |
* | |
* @return array result set | |
* @throws \TYPO3\Flow\Persistence\Doctrine\Exception\DatabaseException | |
* @throws \TYPO3\Flow\Persistence\Doctrine\Exception\DatabaseConnectionException | |
* @throws \TYPO3\Flow\Persistence\Doctrine\Exception\DatabaseStructureException | |
*/ | |
public function getResult() { | |
try { | |
return $this->queryBuilder->getQuery()->getResult(); | |
} catch (\Doctrine\ORM\ORMException $ormException) { | |
$this->systemLogger->logException($ormException); | |
return array(); | |
} catch (\Doctrine\DBAL\DBALException $dbalException) { | |
$this->systemLogger->logException($dbalException); | |
if (stripos($dbalException->getMessage(), 'no database selected') !== FALSE) { | |
$message = 'No database name was specified in the configuration.'; | |
$exception = new Exception\DatabaseConnectionException($message, $dbalException->getCode()); | |
} elseif (stripos($dbalException->getMessage(), 'table') !== FALSE && stripos($dbalException->getMessage(), 'not') !== FALSE && stripos($dbalException->getMessage(), 'exist') !== FALSE) { | |
$message = 'A table or view seems to be missing from the database.'; | |
$exception = new Exception\DatabaseStructureException($message, $dbalException->getCode()); | |
} else { | |
$message = 'An error occurred in the Database Abstraction Layer.'; | |
$exception = new Exception\DatabaseException($message, $dbalException->getCode()); | |
} | |
throw $exception; | |
} catch (\PDOException $pdoException) { | |
$this->systemLogger->logException($pdoException); | |
$message = 'An error occurred while using the PDO Driver.'; | |
if (stripos($pdoException->getMessage(), 'unknown database') !== FALSE | |
|| (stripos($pdoException->getMessage(), 'database') !== FALSE && strpos($pdoException->getMessage(), 'not') !== FALSE && strpos($pdoException->getMessage(), 'exist') !== FALSE)) { | |
$message = 'The database which was specified in the configuration does not exist.'; | |
$exception = new Exception\DatabaseConnectionException($message, $pdoException->getCode()); | |
} elseif (stripos($pdoException->getMessage(), 'access denied') !== FALSE | |
|| stripos($pdoException->getMessage(), 'connection refused') !== FALSE) { | |
$message = 'The database username / password specified in the configuration seem to be wrong.'; | |
$exception = new Exception\DatabaseConnectionException($message, $pdoException->getCode()); | |
} | |
throw $exception; | |
} | |
} | |
/** | |
* Returns the query result count | |
* | |
* @return integer The query result count | |
* @throws Exception\DatabaseConnectionException | |
* @api | |
*/ | |
public function count() { | |
try { | |
$originalQuery = $this->queryBuilder->getQuery(); | |
$dqlQuery = clone $originalQuery; | |
$dqlQuery->setParameters($originalQuery->getParameters()); | |
$dqlQuery->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_TREE_WALKERS, array('TYPO3\Flow\Persistence\Doctrine\CountWalker')); | |
$offset = $dqlQuery->getFirstResult(); | |
$limit = $dqlQuery->getMaxResults(); | |
if ($offset !== NULL) { | |
$dqlQuery->setFirstResult(NULL); | |
} | |
if ($this->getGroupBy() !== NULL) { | |
$numberOfResults = count($dqlQuery->getResult()); | |
} else { | |
$numberOfResults = (int)$dqlQuery->getSingleScalarResult(); | |
} | |
if ($offset !== NULL) { | |
$numberOfResults = max(0, $numberOfResults - $offset); | |
} | |
if ($limit !== NULL) { | |
$numberOfResults = min($numberOfResults, $limit); | |
} | |
return $numberOfResults; | |
} catch (\Doctrine\ORM\ORMException $ormException) { | |
$this->systemLogger->logException($ormException); | |
return 0; | |
} catch (\PDOException $pdoException) { | |
throw new Exception\DatabaseConnectionException($pdoException->getMessage(), $pdoException->getCode()); | |
} | |
} | |
/** | |
* Sets the property names to order the result by. Expected like this: | |
* array( | |
* 'foo' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_ASCENDING, | |
* 'bar' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_DESCENDING | |
* ) | |
* | |
* @param array $orderings The property names to order by | |
* @return \TYPO3\Flow\Persistence\QueryInterface | |
* @api | |
*/ | |
public function setOrderings(array $orderings) { | |
$this->orderings = $orderings; | |
$this->queryBuilder->resetDQLPart('orderBy'); | |
foreach ($this->orderings AS $propertyName => $order) { | |
$this->queryBuilder->addOrderBy($this->getPropertyNameWithAlias($propertyName), $order); | |
} | |
return $this; | |
} | |
/** | |
* Returns the property names to order the result by, like this: | |
* array( | |
* 'foo' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_ASCENDING, | |
* 'bar' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_DESCENDING | |
* ) | |
* | |
* @return array | |
* @api | |
*/ | |
public function getOrderings() { | |
return $this->orderings; | |
} | |
/** | |
* Sets the maximum size of the result set to limit. Returns $this to allow | |
* for chaining (fluid interface) | |
* | |
* @param integer $limit | |
* @return \TYPO3\Flow\Persistence\QueryInterface | |
* @api | |
*/ | |
public function setLimit($limit) { | |
$this->limit = $limit; | |
$this->queryBuilder->setMaxResults($limit); | |
return $this; | |
} | |
/** | |
* Returns the maximum size of the result set to limit. | |
* | |
* @return integer | |
* @api | |
*/ | |
public function getLimit() { | |
return $this->limit; | |
} | |
/** | |
* Sets the start offset of the result set to offset. Returns $this to | |
* allow for chaining (fluid interface) | |
* | |
* @param integer $offset | |
* @return \TYPO3\Flow\Persistence\QueryInterface | |
* @api | |
*/ | |
public function setOffset($offset) { | |
$this->offset = $offset; | |
$this->queryBuilder->setFirstResult($offset); | |
return $this; | |
} | |
/** | |
* Returns the start offset of the result set. | |
* | |
* @return integer | |
* @api | |
*/ | |
public function getOffset() { | |
return $this->offset; | |
} | |
/** | |
* Sets the property names to group the result by. Returns $this to allow | |
* for chaining (fluid interface) | |
* | |
* @param array $groupBy | |
* @return \TYPO3\Flow\Persistence\QueryInterface | |
* @api | |
*/ | |
public function setGroupBy($groupBy) { | |
$this->groupBy = $groupBy; | |
$this->queryBuilder->resetDQLPart('groupBy'); | |
foreach ($this->groupBy as $propertyName) { | |
$this->queryBuilder->addGroupBy($this->getPropertyNameWithAlias($propertyName)); | |
} | |
return $this; | |
} | |
/** | |
* Returns the property names to order the result by. | |
* | |
* @return array | |
* @api | |
*/ | |
public function getGroupBy() { | |
return $this->groupBy; | |
} | |
/** | |
* The constraint used to limit the result set. Returns $this to allow | |
* for chaining (fluid interface) | |
* | |
* @param object $constraint Some constraint, depending on the backend | |
* @return \TYPO3\Flow\Persistence\QueryInterface | |
* @api | |
*/ | |
public function matching($constraint) { | |
$this->constraint = $constraint; | |
$this->queryBuilder->where($constraint); | |
return $this; | |
} | |
/** | |
* Gets the constraint for this query. | |
* | |
* @return \TYPO3\Flow\Persistence\Generic\Qom\Constraint the constraint, or null if none | |
* @api | |
*/ | |
public function getConstraint() { | |
return $this->constraint; | |
} | |
/** | |
* Performs a logical conjunction of the two given constraints. The method | |
* takes one or more constraints and concatenates them with a boolean AND. | |
* It also accepts a single array of constraints to be concatenated. | |
* | |
* @param mixed $constraint1 The first of multiple constraints or an array of constraints. | |
* @return object | |
* @api | |
*/ | |
public function logicalAnd($constraint1) { | |
if (is_array($constraint1)) { | |
$constraints = $constraint1; | |
} else { | |
$constraints = func_get_args(); | |
} | |
return call_user_func_array(array($this->queryBuilder->expr(), 'andX'), $constraints); | |
} | |
/** | |
* Performs a logical disjunction of the two given constraints. The method | |
* takes one or more constraints and concatenates them with a boolean OR. | |
* It also accepts a single array of constraints to be concatenated. | |
* | |
* @param mixed $constraint1 The first of multiple constraints or an array of constraints. | |
* @return object | |
* @api | |
*/ | |
public function logicalOr($constraint1) { | |
if (is_array($constraint1)) { | |
$constraints = $constraint1; | |
} else { | |
$constraints = func_get_args(); | |
} | |
return call_user_func_array(array($this->queryBuilder->expr(), 'orX'), $constraints); | |
} | |
/** | |
* Performs a logical negation of the given constraint | |
* | |
* @param object $constraint Constraint to negate | |
* @return object | |
* @api | |
*/ | |
public function logicalNot($constraint) { | |
return $this->queryBuilder->expr()->not($constraint); | |
} | |
/** | |
* Returns an equals criterion used for matching objects against a query. | |
* | |
* It matches if the $operand equals the value of the property named | |
* $propertyName. If $operand is NULL a strict check for NULL is done. For | |
* strings the comparison can be done with or without case-sensitivity. | |
* | |
* Note: case-sensitivity is only possible if the database supports it. E.g. | |
* if you are using MySQL with a case-insensitive collation you will not be able | |
* to test for case-sensitive equality (the other way around works, because we | |
* compare lowercased values). | |
* | |
* @param string $propertyName The name of the property to compare against | |
* @param mixed $operand The value to compare with | |
* @param boolean $caseSensitive Whether the equality test should be done case-sensitive for strings | |
* @return object | |
* @api | |
*/ | |
public function equals($propertyName, $operand, $caseSensitive = TRUE) { | |
$aliasedPropertyName = $this->getPropertyNameWithAlias($propertyName); | |
if ($operand === NULL) { | |
return $this->queryBuilder->expr()->isNull($aliasedPropertyName); | |
} | |
if ($caseSensitive === TRUE) { | |
return $this->queryBuilder->expr()->eq($aliasedPropertyName, $this->getParamNeedle($operand)); | |
} | |
return $this->queryBuilder->expr()->eq($this->queryBuilder->expr()->lower($aliasedPropertyName), $this->getParamNeedle(strtolower($operand))); | |
} | |
/** | |
* Returns a like criterion used for matching objects against a query. | |
* Matches if the property named $propertyName is like the $operand, using | |
* standard SQL wildcards. | |
* | |
* @param string $propertyName The name of the property to compare against | |
* @param string $operand The value to compare with | |
* @param boolean $caseSensitive Whether the matching should be done case-sensitive | |
* @return object | |
* @throws \TYPO3\Flow\Persistence\Exception\InvalidQueryException if used on a non-string property | |
* @todo implement case-sensitivity switch | |
* @api | |
*/ | |
public function like($propertyName, $operand, $caseSensitive = TRUE) { | |
return $this->queryBuilder->expr()->like($this->getPropertyNameWithAlias($propertyName), $this->getParamNeedle($operand)); | |
} | |
/** | |
* Returns a "contains" criterion used for matching objects against a query. | |
* It matches if the multivalued property contains the given operand. | |
* | |
* If NULL is given as $operand, there will never be a match! | |
* | |
* @param string $propertyName The name of the multivalued property to compare against | |
* @param mixed $operand The value to compare with | |
* @return object | |
* @throws \TYPO3\Flow\Persistence\Exception\InvalidQueryException if used on a single-valued property | |
* @api | |
*/ | |
public function contains($propertyName, $operand) { | |
return '(' . $this->getParamNeedle($operand) . ' MEMBER OF ' . $this->getPropertyNameWithAlias($propertyName) . ')'; | |
} | |
/** | |
* Returns an "isEmpty" criterion used for matching objects against a query. | |
* It matches if the multivalued property contains no values or is NULL. | |
* | |
* @param string $propertyName The name of the multivalued property to compare against | |
* @return boolean | |
* @throws \TYPO3\Flow\Persistence\Exception\InvalidQueryException if used on a single-valued property | |
* @api | |
*/ | |
public function isEmpty($propertyName) { | |
return '(' . $this->getPropertyNameWithAlias($propertyName) . ' IS EMPTY)'; | |
} | |
/** | |
* Returns an "in" criterion used for matching objects against a query. It | |
* matches if the property's value is contained in the multivalued operand. | |
* | |
* @param string $propertyName The name of the property to compare against | |
* @param mixed $operand The value to compare with, multivalued | |
* @return object | |
* @throws \TYPO3\Flow\Persistence\Exception\InvalidQueryException if used on a multi-valued property | |
* @api | |
*/ | |
public function in($propertyName, $operand) { | |
// Take care: In cannot be needled at the moment! DQL escapes it, but only as literals, making caching a bit harder. | |
// This is a todo for Doctrine 2.1 | |
return $this->queryBuilder->expr()->in($this->getPropertyNameWithAlias($propertyName), $operand); | |
} | |
/** | |
* Returns a less than criterion used for matching objects against a query | |
* | |
* @param string $propertyName The name of the property to compare against | |
* @param mixed $operand The value to compare with | |
* @return object | |
* @throws \TYPO3\Flow\Persistence\Exception\InvalidQueryException if used on a multi-valued property or with a non-literal/non-DateTime operand | |
* @api | |
*/ | |
public function lessThan($propertyName, $operand) { | |
return $this->queryBuilder->expr()->lt($this->getPropertyNameWithAlias($propertyName), $this->getParamNeedle($operand)); | |
} | |
/** | |
* Returns a less or equal than criterion used for matching objects against a query | |
* | |
* @param string $propertyName The name of the property to compare against | |
* @param mixed $operand The value to compare with | |
* @return object | |
* @throws \TYPO3\Flow\Persistence\Exception\InvalidQueryException if used on a multi-valued property or with a non-literal/non-DateTime operand | |
* @api | |
*/ | |
public function lessThanOrEqual($propertyName, $operand) { | |
return $this->queryBuilder->expr()->lte($this->getPropertyNameWithAlias($propertyName), $this->getParamNeedle($operand)); | |
} | |
/** | |
* Returns a greater than criterion used for matching objects against a query | |
* | |
* @param string $propertyName The name of the property to compare against | |
* @param mixed $operand The value to compare with | |
* @return object | |
* @throws \TYPO3\Flow\Persistence\Exception\InvalidQueryException if used on a multi-valued property or with a non-literal/non-DateTime operand | |
* @api | |
*/ | |
public function greaterThan($propertyName, $operand) { | |
return $this->queryBuilder->expr()->gt($this->getPropertyNameWithAlias($propertyName), $this->getParamNeedle($operand)); | |
} | |
/** | |
* Returns a greater than or equal criterion used for matching objects against a query | |
* | |
* @param string $propertyName The name of the property to compare against | |
* @param mixed $operand The value to compare with | |
* @return object | |
* @throws \TYPO3\Flow\Persistence\Exception\InvalidQueryException if used on a multi-valued property or with a non-literal/non-DateTime operand | |
* @api | |
*/ | |
public function greaterThanOrEqual($propertyName, $operand) { | |
return $this->queryBuilder->expr()->gte($this->getPropertyNameWithAlias($propertyName), $this->getParamNeedle($operand)); | |
} | |
/** | |
* Get a needle for parameter binding. | |
* | |
* @param mixed $operand | |
* @return string | |
*/ | |
protected function getParamNeedle($operand) { | |
$index = $this->parameterIndex++; | |
$this->queryBuilder->setParameter($index, $operand); | |
return '?' . $index; | |
} | |
/** | |
* Adds left join clauses along the given property path to the query, if needed. | |
* This enables us to set conditions on related objects. | |
* | |
* @param string $propertyPath The path to a sub property, e.g. property.subProperty.foo, or a simple property name | |
* @return string The last part of the property name prefixed by the used join alias, if joins have been added | |
*/ | |
protected function getPropertyNameWithAlias($propertyPath) { | |
$aliases = $this->queryBuilder->getRootAliases(); | |
$previousJoinAlias = $aliases[0]; | |
if (strpos($propertyPath, '.') === FALSE) { | |
return $previousJoinAlias . '.' . $propertyPath; | |
} | |
$propertyPathParts = explode('.', $propertyPath); | |
$conditionPartsCount = count($propertyPathParts); | |
for ($i = 0; $i < $conditionPartsCount - 1; $i++) { | |
$joinAlias = $propertyPathParts[$i] . $this->joinAliasCounter++; | |
$this->queryBuilder->leftJoin($previousJoinAlias . '.' . $propertyPathParts[$i], $joinAlias); | |
$this->joins[$joinAlias] = $previousJoinAlias . '.' . $propertyPathParts[$i]; | |
$previousJoinAlias = $joinAlias; | |
} | |
return $previousJoinAlias . '.' . $propertyPathParts[$i]; | |
} | |
/** | |
* We need to drop the query builder, as it contains a PDO instance deep inside. | |
* | |
* @return array | |
*/ | |
public function __sleep() { | |
$this->parameters = $this->queryBuilder->getParameters(); | |
return array('entityClassName', 'constraint', 'orderings', 'parameterIndex', 'limit', 'offset', 'parameters', 'joins'); | |
} | |
/** | |
* Recreate query builder and set state again. | |
* | |
* @return void | |
*/ | |
public function __wakeup() { | |
if ($this->constraint !== NULL) { | |
$this->queryBuilder->where($this->constraint); | |
} | |
if (is_array($this->orderings)) { | |
$aliases = $this->queryBuilder->getRootAliases(); | |
foreach ($this->orderings AS $propertyName => $order) { | |
$this->queryBuilder->addOrderBy($aliases[0] . '.' . $propertyName, $order); | |
} | |
} | |
if (is_array($this->groupBy)) { | |
$aliases = $this->queryBuilder->getRootAliases(); | |
foreach ($this->groupBy AS $propertyName) { | |
$this->queryBuilder->addGroupBy($aliases[0] . '.' . $propertyName); | |
} | |
} | |
if (is_array($this->joins)) { | |
foreach ($this->joins as $joinAlias => $join) { | |
$this->queryBuilder->leftJoin($join, $joinAlias); | |
} | |
} | |
$this->queryBuilder->setFirstResult($this->offset); | |
$this->queryBuilder->setMaxResults($this->limit); | |
$this->queryBuilder->setParameters($this->parameters); | |
unset($this->parameters); | |
} | |
/** | |
* Cloning the query clones also the internal QueryBuilder, | |
* as they are tightly coupled. | |
*/ | |
public function __clone() { | |
$this->queryBuilder = clone $this->queryBuilder; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment