diff --git a/lib/Mapper.php b/lib/Mapper.php index 7cf9595..2bf1e52 100644 --- a/lib/Mapper.php +++ b/lib/Mapper.php @@ -91,12 +91,14 @@ public function collectionClass() /** * Entity manager class for storing information and meta-data about entities - * + * @param string $entityName * @return \Spot\Entity\Manager */ - public function entityManager() + public function entityManager($entityName = null) { - $entityName = $this->entity(); + if (!$entityName) { + $entityName = $this->entity(); + } if (!isset(self::$entityManager[$entityName])) { self::$entityManager[$entityName] = new Entity\Manager($entityName); } diff --git a/lib/Query.php b/lib/Query.php index c971d18..f80968b 100644 --- a/lib/Query.php +++ b/lib/Query.php @@ -2,8 +2,6 @@ namespace Spot; -use Doctrine\DBAL\Types\Type; - /** * Query Object - Used to build adapter-independent queries PHP-style * @@ -95,6 +93,13 @@ class Query implements \Countable, \IteratorAggregate, \ArrayAccess, \JsonSerial */ protected static $_whereOperatorObjects = []; + /** + * For future use + * Store the type of relation with the selected mapper + * @var + */ + protected $mapping; + /** * Constructor Method * @@ -209,6 +214,59 @@ public function select() return $this; } + /** + * Join (passthrough to DBAL QueryBuilder) + * @param string $fromAlias + * @param string $entityName + * @param string $alias + * @param null $condition + * @return $this + */ + public function join($fromAlias, $entityName, $alias, $condition = null) + { + return $this->makeJoin(__FUNCTION__, $fromAlias, $entityName, $alias, $condition); + + } + /** + * Inner Join (passthrough to DBAL QueryBuilder) + * @param string $fromAlias + * @param string $entityName + * @param string $alias + * @param null $condition + * @return $this + */ + public function innerJoin($fromAlias, $entityName, $alias, $condition = null) + { + return $this->makeJoin(__FUNCTION__, $fromAlias, $entityName, $alias, $condition); + + } + /** + * Left Join (passthrough to DBAL QueryBuilder) + * @param string $fromAlias + * @param string $entityName + * @param string $alias + * @param null $condition + * @return $this + */ + public function leftJoin($fromAlias, $entityName, $alias, $condition = null) + { + return $this->makeJoin(__FUNCTION__, $fromAlias, $entityName, $alias, $condition); + + } + /** + * Right Join (passthrough to DBAL QueryBuilder) + * @param string $fromAlias + * @param string $entityName + * @param string $alias + * @param null $condition + * @return $this + */ + public function rightJoin($fromAlias, $entityName, $alias, $condition = null) + { + return $this->makeJoin(__FUNCTION__, $fromAlias, $entityName, $alias, $condition); + + } + /** * Delete (passthrough to DBAL QueryBuilder) * @@ -347,10 +405,11 @@ public function whereSql($sql) * * @param array $where Array of conditions for this clause * @param bool $useAlias + * @param string $entityName * @return array SQL fragment strings for WHERE clause * @throws Exception */ - private function parseWhereToSQLFragments(array $where, $useAlias = true) + private function parseWhereToSQLFragments(array $where, $useAlias = true, $entityName=null) { $builder = $this->builder(); @@ -383,9 +442,14 @@ private function parseWhereToSQLFragments(array $where, $useAlias = true) // Prefix column name with alias if ($useAlias === true) { - $col = $this->fieldWithAlias($col); + $col = $this->fieldWithAlias($col, true, $entityName); } + if ( $this->stringIsExistingField($entityName, $value) ){ + $value = function () use ($value){ + return $value; + }; + } $sqlFragments[] = $operatorCallable($builder, $col, $value); } @@ -708,18 +772,25 @@ public function escapeIdentifier($identifier) * Get field name with table alias appended * @param string $field * @param bool $escaped + * @param string $entityName * @return string */ - public function fieldWithAlias($field, $escaped = true) + public function fieldWithAlias($field, $escaped = true, $entityName = null) { - $fieldInfo = $this->_mapper->entityManager()->fields(); + $fieldInfo = $this->_mapper->entityManager($entityName)->fields(); + //extract table alias if present + list($field, $table) = $this->extractTableAndFieldFromString($field); // Determine real field name (column alias support) if (isset($fieldInfo[$field])) { $field = $fieldInfo[$field]['column']; } - $field = $this->_tableName . '.' . $field; + if (!$table) { + $table = $this->_mapper->entityManager($entityName)->table(); + } + + $field = $table . '.' . $field; return $escaped ? $this->escapeIdentifier($field) : $field; } @@ -810,4 +881,102 @@ public function __call($method, $args) throw new \BadMethodCallException("Method '" . __CLASS__ . "::" . $method . "' not found"); } } + + /** + * Store the mapping of tables and mapper + * @param string $type + * @param array $data + */ + private function addMapping($type, array $data) + { + $type = (string)$type; + $this->mapping[$type] = $data; + } + + /** + * Add a join of type $type + * @param string $type + * @param string $fromAlias + * @param string $entityName + * @param string $alias + * @param string $condition + * @return $this + */ + public function makeJoin($type, $fromAlias, $entityName, $alias, $condition) + { + $joinTable = $this->mapper()->getMapper($entityName)->table(); +// $conditionString = (string)$condition; + + $this->addMapping( + 'join', + array( + $fromAlias => array( + 'joinTable' => $joinTable, + 'joinAlias' => $alias, + 'joinEntity' => $entityName, + + ), + ) + ); +// $testCondition = explode('=', $condition); + $conditionString = implode(' AND ', $this->parseWhereToSQLFragments($condition, true, $entityName)); +// $conditionString = implode(' =', $condition); + //@FIXME: now parameters are double escaped, because the initial are double escaped also :( + $this->builder()->$type( + $this->escapeIdentifier($fromAlias), + $this->escapeIdentifier($joinTable), + $this->escapeIdentifier($alias), + $conditionString + ); + + return $this; + } + + /** + * Extract data from string, for strings which contains "point" + * @Example: table.field + * @param $string + * @return array + */ + public function extractTableAndFieldFromString($string) + { + $pointPosition = strpos($string, '.'); + if ($pointPosition !== false) { + $table = substr($string, 0, $pointPosition); + $field = substr($string, $pointPosition + 1); + } else { + $table = null; + $field = $string; + } + + return [$field, $table]; + + } + + /** + * Determine if the value is a existing field + * @param string $entityName + * @param string $value + * @return string + */ + public function stringIsExistingField($entityName, $value) + { + $field = ''; + if (is_array($value)) { + return $field; + } + $fieldInfo = array_merge( + $this->_mapper->entityManager($entityName)->fields(), + $this->_mapper->entityManager()->fields() + ); + $field = null; + //extract table alias if present + list($extractedField, $table) = $this->extractTableAndFieldFromString($value); + // Determine real field name (column alias support) + if (isset($fieldInfo[$extractedField])) { + $field = $fieldInfo[$extractedField]['column']; + } + + return $field; + } } diff --git a/lib/Query/Operator/Equals.php b/lib/Query/Operator/Equals.php index 4548b1b..8ea1a89 100644 --- a/lib/Query/Operator/Equals.php +++ b/lib/Query/Operator/Equals.php @@ -28,6 +28,10 @@ public function __invoke(QueryBuilder $builder, $column, $value) return $column . ' IS NULL'; } + if ($value instanceof \Closure) { + return $column . ' = ' . $value(); + } + return $column . ' = ' . $builder->createPositionalParameter($value); } } diff --git a/lib/Query/Operator/FullText.php b/lib/Query/Operator/FullText.php index 0ea6661..c069292 100644 --- a/lib/Query/Operator/FullText.php +++ b/lib/Query/Operator/FullText.php @@ -17,6 +17,10 @@ class FullText */ public function __invoke(QueryBuilder $builder, $column, $value) { + if ($value instanceof \Closure) { + return $column . ' = ' . $value(); + } + return 'MATCH(' . $column . ') AGAINST (' . $builder->createPositionalParameter($value) . ')'; } } diff --git a/lib/Query/Operator/FullTextBoolean.php b/lib/Query/Operator/FullTextBoolean.php index 2b5e1b5..383ceb1 100644 --- a/lib/Query/Operator/FullTextBoolean.php +++ b/lib/Query/Operator/FullTextBoolean.php @@ -17,6 +17,10 @@ class FullTextBoolean */ public function __invoke(QueryBuilder $builder, $column, $value) { + if ($value instanceof \Closure) { + return $column . ' = ' . $value(); + } + return 'MATCH(' . $column . ') AGAINST (' . $builder->createPositionalParameter($value) . ' IN BOOLEAN MODE)'; } } diff --git a/lib/Query/Operator/GreaterThan.php b/lib/Query/Operator/GreaterThan.php index af9e8be..1dd7799 100644 --- a/lib/Query/Operator/GreaterThan.php +++ b/lib/Query/Operator/GreaterThan.php @@ -17,6 +17,10 @@ class GreaterThan */ public function __invoke(QueryBuilder $builder, $column, $value) { + if ($value instanceof \Closure) { + return $column . ' = ' . $value(); + } + return $column . ' > ' . $builder->createPositionalParameter($value); } } diff --git a/lib/Query/Operator/GreaterThanOrEqual.php b/lib/Query/Operator/GreaterThanOrEqual.php index 8003674..db9e522 100644 --- a/lib/Query/Operator/GreaterThanOrEqual.php +++ b/lib/Query/Operator/GreaterThanOrEqual.php @@ -17,6 +17,10 @@ class GreaterThanOrEqual */ public function __invoke(QueryBuilder $builder, $column, $value) { + if ($value instanceof \Closure) { + return $column . ' = ' . $value(); + } + return $column . ' >= ' . $builder->createPositionalParameter($value); } } diff --git a/lib/Query/Operator/In.php b/lib/Query/Operator/In.php index 1985044..05c9ac9 100644 --- a/lib/Query/Operator/In.php +++ b/lib/Query/Operator/In.php @@ -24,6 +24,10 @@ public function __invoke(QueryBuilder $builder, $column, $value) throw new Exception("Use of IN operator expects value to be array. Got " . gettype($value) . "."); } + if ($value instanceof \Closure) { + return $column . ' = ' . $value(); + } + return $column . ' IN (' . $builder->createPositionalParameter($value, Connection::PARAM_STR_ARRAY) . ')'; } } diff --git a/lib/Query/Operator/LessThan.php b/lib/Query/Operator/LessThan.php index a888704..e57a7c7 100644 --- a/lib/Query/Operator/LessThan.php +++ b/lib/Query/Operator/LessThan.php @@ -17,6 +17,10 @@ class LessThan */ public function __invoke(QueryBuilder $builder, $column, $value) { + if ($value instanceof \Closure) { + return $column . ' < ' . $value(); + } + return $column . ' < ' . $builder->createPositionalParameter($value); } } diff --git a/lib/Query/Operator/LessThanOrEqual.php b/lib/Query/Operator/LessThanOrEqual.php index 59b0789..1828504 100644 --- a/lib/Query/Operator/LessThanOrEqual.php +++ b/lib/Query/Operator/LessThanOrEqual.php @@ -17,6 +17,10 @@ class LessThanOrEqual */ public function __invoke(QueryBuilder $builder, $column, $value) { + if ($value instanceof \Closure) { + return $column . ' <= ' . $value(); + } + return $column . ' <= ' . $builder->createPositionalParameter($value); } } diff --git a/lib/Query/Operator/Like.php b/lib/Query/Operator/Like.php index d2c1fc5..019c68a 100644 --- a/lib/Query/Operator/Like.php +++ b/lib/Query/Operator/Like.php @@ -17,6 +17,10 @@ class Like */ public function __invoke(QueryBuilder $builder, $column, $value) { + if ($value instanceof \Closure) { + return $column . ' = ' . $value(); + } + return $column . ' LIKE ' . $builder->createPositionalParameter($value); } } diff --git a/lib/Query/Operator/Not.php b/lib/Query/Operator/Not.php index b0ce871..0044ad8 100644 --- a/lib/Query/Operator/Not.php +++ b/lib/Query/Operator/Not.php @@ -21,13 +21,18 @@ class Not public function __invoke(QueryBuilder $builder, $column, $value) { if (is_array($value) && !empty($value)) { - return $column . ' NOT IN (' . $builder->createPositionalParameter($value, Connection::PARAM_STR_ARRAY) . ')'; + return $column . ' NOT IN (' . $builder->createPositionalParameter($value, + Connection::PARAM_STR_ARRAY) . ')'; } if ($value === null || (is_array($value) && empty($value))) { return $column . ' IS NOT NULL'; } + if ($value instanceof \Closure) { + return $column . ' = ' . $value(); + } + return $column . ' != ' . $builder->createPositionalParameter($value); } } diff --git a/lib/Query/Operator/RegExp.php b/lib/Query/Operator/RegExp.php index e33cfc2..e66eb39 100644 --- a/lib/Query/Operator/RegExp.php +++ b/lib/Query/Operator/RegExp.php @@ -17,6 +17,10 @@ class RegExp */ public function __invoke(QueryBuilder $builder, $column, $value) { + if ($value instanceof \Closure) { + return $column . ' = ' . $value(); + } + return $column . ' REGEXP ' . $builder->createPositionalParameter($value); } } diff --git a/test b/test new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/test @@ -0,0 +1 @@ +test diff --git a/tests/Mapper.php b/tests/Mapper.php index 4bd1f3e..af20423 100644 --- a/tests/Mapper.php +++ b/tests/Mapper.php @@ -20,4 +20,67 @@ public function testGetCustomEntityMapper() $query = $mapper->testQuery(); $this->assertInstanceOf('Spot\Query', $query); } + + /** + * entityManager() return the current entity if $entityName is empty + */ + public function testEntityManagerReturnTheCurrentEntityIfEntityNameIsEmpty() + { + $mapper = test_spot_mapper('SpotTest\Entity\NotNullOverride'); + $manager = $mapper->entityManager(); + $currentEntity = $mapper->entity(); + $reflection = new \ReflectionClass(get_class($manager)); + $privateProperty = $reflection->getProperty('entityName'); + $privateProperty->setAccessible(true); + $privateValue = $privateProperty->getValue($manager); + $this->assertEquals($currentEntity, $privateValue); + } + + + /** + * entityManager() return the same entity if we passed as parameter + */ + public function testEntityManagerReturnTheSameEntityIfWePassedAsParameter() + { + $mapper = test_spot_mapper('SpotTest\Entity\NotNullOverride'); + test_spot_mapper('\SpotTest\Entity\Author'); + $manager = $mapper->entityManager('\SpotTest\Entity\Author'); + $currentEntity = $mapper->entity(); + $reflection = new \ReflectionClass(get_class($manager)); + $privateProperty = $reflection->getProperty('entityName'); + $privateProperty->setAccessible(true); + $privateValue = $privateProperty->getValue($manager); + $this->assertNotEquals($currentEntity, $privateValue); + } + + + /** + * entityManager() return will return different entity from current entity if we passed as parameter + */ + public function testEntityManagerReturnWillReturnDifferentEntityFromCurrentEntityIfWePassedAsParameter() + { + $mapper = test_spot_mapper('SpotTest\Entity\NotNullOverride'); + test_spot_mapper('\SpotTest\Entity\Author'); + $manager = $mapper->entityManager('\SpotTest\Entity\Author'); + $reflection = new \ReflectionClass(get_class($manager)); + $privateProperty = $reflection->getProperty('entityName'); + $privateProperty->setAccessible(true); + $privateValue = $privateProperty->getValue($manager); + $this->assertEquals('\SpotTest\Entity\Author', $privateValue); + } + + + /** + * entityManager() always return instance of Entity\Manager + */ + public function testEntityManagerAlwaysReturnInstanceOfEntityManager() + { + $mapper = test_spot_mapper('\SpotTest\Entity\NotNullOverride'); + test_spot_mapper('\SpotTest\Entity\Author'); + $manager = $mapper->entityManager('\SpotTest\Entity\Author'); + $managerCurrent = $mapper->entityManager(); + $this->assertInstanceOf('\Spot\Entity\Manager', $manager); + $this->assertInstanceOf('\Spot\Entity\Manager', $managerCurrent); + } + }