From a39a5b812b3bac20dec295d36f197bc3d6bc34cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?ANDRE=20s=C3=A9bastien?= <sandre@afi-sa.fr> Date: Tue, 2 Aug 2022 15:58:30 +0200 Subject: [PATCH] operators_match * add order from clause * add order match for clause * add operator group by * add readme * zend adapter mocking activable * RT fix --- README.md | 48 +- src/Storm/Model/Loader.php | 90 +- .../Model/PersistenceStrategy/Abstract.php | 10 +- .../Model/PersistenceStrategy/Clause.php | 234 ----- src/Storm/Model/PersistenceStrategy/Db.php | 82 +- .../Model/PersistenceStrategy/Volatile.php | 257 +++-- .../Volatile/OrderClause.php | 53 +- src/Storm/Query.php | 360 +++++++ src/Storm/Query/Clause.php | 507 ++++++++++ src/Storm/Query/Clause/Match.php | 110 +++ src/Storm/Query/Clause/MatchTerms.php | 91 ++ src/Storm/Query/Clause/OrderBy.php | 84 ++ src/Storm/Query/Criteria.php | 233 +++++ src/Storm/Query/CriteriaInterface.php | 100 ++ src/Storm/Query/MatchBoolean.php | 39 + src/Storm/Query/MatchRating.php | 78 ++ src/Storm/Query/Sql.php | 41 + src/Storm/Test/ModelTestCase.php | 32 +- tests/Storm/Test/LoaderQueryTest.php | 886 ++++++++++++++++++ tests/Storm/Test/LoaderTest.php | 97 +- tests/Storm/Test/LoaderVolatileTest.php | 73 -- 21 files changed, 2912 insertions(+), 593 deletions(-) delete mode 100644 src/Storm/Model/PersistenceStrategy/Clause.php create mode 100644 src/Storm/Query.php create mode 100644 src/Storm/Query/Clause.php create mode 100644 src/Storm/Query/Clause/Match.php create mode 100644 src/Storm/Query/Clause/MatchTerms.php create mode 100644 src/Storm/Query/Clause/OrderBy.php create mode 100644 src/Storm/Query/Criteria.php create mode 100644 src/Storm/Query/CriteriaInterface.php create mode 100644 src/Storm/Query/MatchBoolean.php create mode 100644 src/Storm/Query/MatchRating.php create mode 100644 src/Storm/Query/Sql.php create mode 100644 tests/Storm/Test/LoaderQueryTest.php diff --git a/README.md b/README.md index 0bc20845..ad998f82 100644 --- a/README.md +++ b/README.md @@ -772,4 +772,50 @@ PHP Warning: Uncaught exception 'Storm_Test_ObjectWrapperException' with messag [1] => string(6) "juliet" } -``` \ No newline at end of file +``` + + +# Objects Query + +Represents an object to collect all clauses, like where order ... +From Storm_Model_Abstract, displays result on format DB or volatile. + +## Storm_Query_Clause + +It's represent an sql criteria like "column" = "value" +With 3 attributes + - a key for Storm_Model_Abstract attribut + - an operator like "=", ">" ... + - a value who can be a string, an array, an other Storm_Query_Clause or null + +Examples + - equal clause : +```php +public static function equal(string $key, string $value) : self +``` + - is null clause : +```php +public static function isNull(string $key) : self +``` + +All clauses must respond to two methods : +```php +public function getFormatDb() : string + +public function containAttibuteInVolatile(array $model) : bool +``` + +All clauses can be created with a static call, that manages the operator value + +## Storm_Query + +Collect all clauses and can assemble the result on DB format or in Volatile mode + +### Storm_Query_CriteriaInterface + +Display all singleton basic method to collect clauses + +## Testings + +All the tests are in Class tests/Storm/Test/LoaderQueryTest.php +Any new Clause must implements an Db Test and an Volatile Test \ No newline at end of file diff --git a/src/Storm/Model/Loader.php b/src/Storm/Model/Loader.php index 0d7ba1dd..ac40c5ae 100644 --- a/src/Storm/Model/Loader.php +++ b/src/Storm/Model/Loader.php @@ -90,7 +90,6 @@ class Storm_Model_Loader { } - public static function resetCache() { static::$_loader_cache = null; } @@ -144,9 +143,8 @@ class Storm_Model_Loader { * @param mixed $select * @return array */ - public function findAll($select=null) { + public function findAll($select = null) : array { return $this->getPersistenceStrategy()->findAll($select); - } @@ -161,6 +159,7 @@ class Storm_Model_Loader { if (isset($this->_persistence_strategy)) return $this->_persistence_strategy; + // Storm_Model_PersistenceStrategy_Db | Storm_Model_PersistenceStrategy_Volatile $class_name = 'Storm_Model_PersistenceStrategy_' . static::$_default_strategy; return $this->_persistence_strategy = new $class_name($this); } @@ -453,98 +452,55 @@ class Storm_Model_Loader { } - /** * @param array $args * @return Storm_Model_Abstract */ public function findFirstBy($args) { - $args['limit'] = 1; - $instances = $this->findAllBy($args); - - if (count($instances) == 0) { - return null; - } - $first_instance = reset($instances); - $this->cacheInstance($first_instance); - return $first_instance; - } - - - public function fetchAllBy($fields, $params) { - return $this->getPersistenceStrategy()->fetchAllBy($fields, $params); - } - - - public function clauseLike(string $key, string $value) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseLike($key, $value); - } - + $instances = $this->findAllBy($this->_prepareFirstBy($args)); - public function clauseNotLike(string $key, string $value) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseNotLike($key, $value); + return $this->_findFirstFrom($instances); } - public function clauseGreater(string $key, string $value) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseGreater($key, $value); - } - - - public function clauseGreaterEqual(string $key, string $value) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseGreaterEqual($key, $value); - } - - - public function clauseLesser(string $key, string $value) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseLesser($key, $value); - } - - - public function clauseLesserEqual(string $key, string $value) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseLesserEqual($key, $value); - } - - - public function clauseIs(string $key) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseIs($key); - } + protected function _findFirstFrom(array $instances) : ?Storm_Model_Abstract { + if (0 === count($instances)) + return null; + $first_instance = reset($instances); + $this->cacheInstance($first_instance); - public function clauseNotIs(string $key) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseNotIs($key); + return $first_instance; } - public function clauseIn(string $key, array $array) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseIn($key, $array); + protected function _prepareFirstBy($args) { + $args['limit'] = 1; + return $args; } - public function clauseNotIn(string $key, array $array) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseNotIn($key, $array); + public function fetchAllBy($fields, $params) { + return $this->getPersistenceStrategy()->fetchAllBy($fields, $params); } - public function clauseEqual(string $key, - string $value) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseEqual($key, $value); + public function fetchForQuery(Storm_Query $query) : array { + return $this->getPersistenceStrategy()->fetchForQuery($query); } - public function clauseNotEqual(string $key, string $value) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseNotEqual($key, $value); + public function countForQuery(Storm_Query $query) : int { + return $this->getPersistenceStrategy()->countForQuery($query); } - public function clauseStart(string $key, - string $value) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseStart($key, $value); + public function fetchFirstForQuery(Storm_Query $query) : ?Storm_Model_Abstract { + return $this->_findFirstFrom($this->fetchForQuery($query->limit(1))); } - public function clauseEnd(string $key, - string $value) : Storm_Model_PersistenceStrategy_Clause { - return Storm_Model_PersistenceStrategy_Clause::clauseEnd($key, $value); + public function greater(string $key, string $value) : Storm_Query_Clause { + return Storm_Query_Clause::greater($key, $value); } } diff --git a/src/Storm/Model/PersistenceStrategy/Abstract.php b/src/Storm/Model/PersistenceStrategy/Abstract.php index c99ba05c..e0d0395f 100644 --- a/src/Storm/Model/PersistenceStrategy/Abstract.php +++ b/src/Storm/Model/PersistenceStrategy/Abstract.php @@ -26,20 +26,20 @@ THE SOFTWARE. class Storm_Model_PersistenceStrategy_Abstract { - protected $_loader; + protected Storm_Model_Loader $_loader; - public function __construct($loader){ - $this->_loader=$loader; + public function __construct(Storm_Model_Loader $loader) { + $this->_loader = $loader; } - public function isVolatile() { + public function isVolatile() : bool { return false; } - public function fetchAllBy($fields, $params) { + public function fetchAllBy($fields, $params) : array { return []; } diff --git a/src/Storm/Model/PersistenceStrategy/Clause.php b/src/Storm/Model/PersistenceStrategy/Clause.php deleted file mode 100644 index b1db63ec..00000000 --- a/src/Storm/Model/PersistenceStrategy/Clause.php +++ /dev/null @@ -1,234 +0,0 @@ -<?php -/* -STORM is under the MIT License (MIT) - -Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - -*/ - -class Storm_Model_PersistenceStrategy_Clause { - - protected - $_key, - $_operator, - $_value, - $_array, - $_negated, - $_is_array; - - const - CLAUSE_WHERE = 'where', - CLAUSE_LIKE = 'like', - CLAUSE_EQUAL = '=', - CLAUSE_IN = 'in', - CLAUSE_IS = 'is', - CLAUSE_GREATER = '>', - CLAUSE_GREATER_EQUAL = '>=', - CLAUSE_LESSER = '<', - CLAUSE_LESSER_EQUAL = '<=', - PERCENT = '%'; - - - public function __construct(string $key, string $operator, $value_or_array) { - $this->_negated = false; - $this->_key = $key; - $this->_operator = $operator; - $this->_is_array = is_array($value_or_array); - - if ($this->_is_array) - $this->_array = $value_or_array; - - if (!$this->_is_array) - $this->_value = $value_or_array; - } - - - public static function clauseIs(string $key) : self { - return new static($key, static::CLAUSE_IS, null); - } - - - public static function clauseNotIs(string $key) : self { - return static::clauseIs($key)->_setNegated(true); - } - - - public static function clauseIn(string $key, array $array) : self { - return new static($key, static::CLAUSE_IN, $array); - } - - - public static function clauseNotIn(string $key, array $array) : self { - return static::clauseIn($key, $array)->_setNegated(true); - } - - - public static function clauseEqual(string $key, string $value) : self { - return new static($key, static::CLAUSE_EQUAL, $value); - } - - - public static function clauseNotEqual(string $key, string $value) : self { - return static::clauseEqual($key, $value)->_setNegated(true); - } - - - public static function clauseLike(string $key, string $value) : self { - return new static($key, static::CLAUSE_LIKE, $value); - } - - - public static function clauseNotLike(string $key, string $value) : self { - return static::clauseLike($key, $value)->_setNegated(true); - } - - - public static function clauseStart(string $key, string $value) : self { - return new static($key, static::CLAUSE_LIKE, $value . static::PERCENT); - } - - - public static function clauseEnd(string $key, string $value) : self { - return new static($key, static::CLAUSE_LIKE, static::PERCENT . $value); - } - - - public static function clauseGreater(string $key, string $value) : self { - return new static($key, static::CLAUSE_GREATER, $value); - } - - - public static function clauseGreaterEqual(string $key, string $value) : self { - return new static($key, static::CLAUSE_GREATER_EQUAL, $value); - } - - - public static function clauseLesser(string $key, string $value) : self { - return new static($key, static::CLAUSE_LESSER, $value); - } - - - public static function clauseLesserEqual(string $key, string $value) : self { - return new static($key, static::CLAUSE_LESSER_EQUAL, $value); - } - - - public static function newWith(string $key, $value_or_array) : self { - $operator = static::CLAUSE_EQUAL; - if (static::CLAUSE_WHERE === $key) - $operator = static::CLAUSE_WHERE; - - $is_like = ' like' === substr($key, -5); - $key = $is_like ? substr($key, 0, strlen($key) - 5) : $key; - $negated = (' not' === substr($key, -4)); - $key = $negated ? substr($key, 0, strlen($key) - 4) : $key; - - if ($is_like) - $operator = static::CLAUSE_LIKE; - - if (null === $value_or_array) - $operator = static::CLAUSE_IS; - - if (is_array($value_or_array)) - $operator = static::CLAUSE_IN; - - return (new static($key, $operator, $value_or_array))->_setNegated($negated); - } - - - protected function _setNegated(bool $negated) : self { - $this->_negated = $negated; - return $this; - } - - - protected function _getValueOrArray() { - return $this->_is_array - ? ($this->_array ?? []) - : ($this->_value ?? ''); - } - - - public function getFormatDb($table) : string { - if (static::CLAUSE_WHERE === $this->_operator) - return '(' . $this->_getValueOrArray() . ')'; - if (static::CLAUSE_IS === $this->_operator) - return $this->_key . ' ' . $this->_getOperator() . ' null'; - - return $table->getAdapter() - ->quoteInto($this->_clauseFormatDb(), - $this->_getValueOrArray(), null, null); - } - - - protected function _clauseFormatDb() : string { - if (static::CLAUSE_IN === $this->_operator) - return $this->_key . ' ' . $this->_getOperator() . ' (?)'; - if (static::CLAUSE_LIKE === $this->_operator) - return $this->_key . ' ' . $this->_getOperator() . ' ?'; - - return $this->_key . $this->_getOperator() . '?'; - } - - - protected function _getOperator() : string { - if (static::CLAUSE_EQUAL === $this->_operator) - return ($this->_negated ? '!' : '') . $this->_operator; - if (static::CLAUSE_IS === $this->_operator) - return $this->_operator . ($this->_negated ? ' not' : ''); - - return ($this->_negated ? 'not ' : '') . $this->_operator; - } - - - public function containAttibuteInVolatile($model) : bool { - if (!array_key_exists($this->_key, $model)) - return $this->_negated; - - if ($this->_is_array && 0 === count($this->_getValueOrArray())) - throw new Storm_Model_Exception(sprintf('array given for %s is empty', - $this->_key)); - - if ($this->_is_array && in_array($model[$this->_key], $this->_getValueOrArray())) - return !$this->_negated; - - if (static::CLAUSE_LIKE === $this->_operator) { - $matches = preg_match(('/^' . str_replace('%', '.*', - $this->_getValueOrArray()) . '$/i'), - $model[$this->_key]); - return $this->_negated ? !$matches : $matches; - } - - if (static::CLAUSE_GREATER === $this->_operator) - return $model[$this->_key] > $this->_getValueOrArray(); - - if (static::CLAUSE_GREATER_EQUAL === $this->_operator) - return $model[$this->_key] >= $this->_getValueOrArray(); - - if (static::CLAUSE_LESSER === $this->_operator) - return $model[$this->_key] < $this->_getValueOrArray(); - - if (static::CLAUSE_LESSER_EQUAL === $this->_operator) - return $model[$this->_key] <= $this->_getValueOrArray(); - - return ($this->_negated !== ($model[$this->_key] == $this->_getValueOrArray())); - } -} diff --git a/src/Storm/Model/PersistenceStrategy/Db.php b/src/Storm/Model/PersistenceStrategy/Db.php index cc5b23a8..bbc88d46 100644 --- a/src/Storm/Model/PersistenceStrategy/Db.php +++ b/src/Storm/Model/PersistenceStrategy/Db.php @@ -62,6 +62,7 @@ class Storm_Model_PersistenceStrategy_Db return null; } + public function update($data, $id) { return $this->getTable()->update($data, $this->_loader->getIdField(). "='" . $id . "'"); } @@ -96,10 +97,11 @@ class Storm_Model_PersistenceStrategy_Db } - protected function _generateWhereClauseForKeyAndValue(string $key, $value) : string { - $clause = ($value instanceof Storm_Model_PersistenceStrategy_Clause) + protected function _generateWhereClauseForKeyAndValue(string $key, + $value) : string { + $clause = ($value instanceof Storm_Query_Clause) ? $value - : Storm_Model_PersistenceStrategy_Clause::newWith($key, $value); + : Storm_Query_Clause::newWith($key, $value); return $clause->getFormatDb($this->getTable()); } @@ -111,7 +113,25 @@ class Storm_Model_PersistenceStrategy_Db } - public function findAll($select=null) { + public function fetchForQuery(Storm_Query $query) : array { + return $this->findAll($this->_assembleQuery($query)); + } + + + public function countForQuery(Storm_Query $query) : int { + return $this->_count($this->_assembleQuery($query)); + } + + + protected function _assembleQuery(Storm_Query $query) { + $select = $this->getTable()->select(); + $query->assemble($select); + + return $select; + } + + + public function findAll($select = null) : array { if (is_string($select)) return $this->_findAllByString($select); @@ -127,14 +147,14 @@ class Storm_Model_PersistenceStrategy_Db } - public function findAllBy($args) { + public function findAllBy(array $args = []) { if ($select = $this->_generateSelectFor($args)) return $this->findAll($select); return []; } - public function fetchAllBy($fields, $params) { + public function fetchAllBy($fields, $params) : array { if (!$select = $this->_generateSelectFor($params)) return []; @@ -145,13 +165,20 @@ class Storm_Model_PersistenceStrategy_Db } - public function countBy($args) { + public function countBy(array $args = []) : int { if (!$select = $this->_generateSelectFor($args)) return 0; + return $this->_count($select); + } + + + protected function _count($select) : int { $select->from($this->getTable(), [sprintf('count(*) as numberof')]); + $rows = $this->getTable()->fetchAll($select)->toArray(); + return $rows[0]['numberof']; } @@ -169,8 +196,9 @@ class Storm_Model_PersistenceStrategy_Db /** * @see findAllBy */ - public function _generateSelectFor($args) { - if (array_key_exists('role', $args) && array_key_exists('model', $args)) { + public function _generateSelectFor(array $args = []) { + if (array_key_exists('role', $args) + && array_key_exists('model', $args)) { $model = $args['model']; $role = $args['role']; unset($args['model']); @@ -185,26 +213,32 @@ class Storm_Model_PersistenceStrategy_Db else $select = $this->getTable()->select(); - foreach ($args as $field => $value) { - if (in_array($field, ['order', 'limit', 'where'], true)) { - $select->$field($value); - continue; - } + foreach ($args as $field => $value) + $this->_addInSelect($select, $field, $value); + + return $select; + } + - if (in_array($field, ['limitPage', 'scope'], true)) { - $this->$field($select, $value); - continue; - } + protected function _addInSelect($select, string $field, $value_or_clause) : self { + if (in_array($field, ['order', 'limit', 'where'], true)) { + $select->$field($value_or_clause); + return $this; + } - if ('group_by' === $field) { - $select->group($value); - continue; - } + if (in_array($field, ['limitPage', 'scope'], true)) { + $this->$field($select, $value_or_clause); + return $this; + } - $select->where($this->_generateWhereClauseForKeyAndValue($field, $value)); + if ('group_by' === $field) { + $select->group($value_or_clause); + return $this; } - return $select; + $select->where($this->_generateWhereClauseForKeyAndValue($field, + $value_or_clause)); + return $this; } diff --git a/src/Storm/Model/PersistenceStrategy/Volatile.php b/src/Storm/Model/PersistenceStrategy/Volatile.php index 30344a66..260c04e9 100644 --- a/src/Storm/Model/PersistenceStrategy/Volatile.php +++ b/src/Storm/Model/PersistenceStrategy/Volatile.php @@ -1,37 +1,37 @@ <?php /* -STORM is under the MIT License (MIT) + STORM is under the MIT License (MIT) -Copyright (c) 2010-2011 Agence Française Informatique http://www.afi-sa.fr + Copyright (c) 2010-2011 Agence Française Informatique http://www.afi-sa.fr -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. */ -class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceStrategy_Abstract { +class Storm_Model_PersistenceStrategy_Volatile + extends Storm_Model_PersistenceStrategy_Abstract { + protected $_instances = [], - $desc_order=false, - $special_select_fields = ['order', 'limit', 'limitpage', 'where', 'group_by'], + $desc_order = false, $_table; - public function getTable() { return $this->_table; } @@ -43,36 +43,46 @@ class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceS } - public function findAll($select = null) { - if (null === $filtered_select = $this->extractRoleAndModel(is_array($select) - ? array_change_key_case($select) - : [])) - return []; + /** + * @param $select null | string | array + */ + public function findAll($select = null) : array { + $select = $this->_extractRoleAndModel($select); - $group_by = $filtered_select['group_by'] ?? ''; - $order = $filtered_select['order'] ?? ''; - $limit = $filtered_select['limit'] ?? ''; + return null === $select ? [] : $this->_findAll($select, null); + } - $page_size=0; - if (isset($filtered_select['limitpage'])) { - list($page, $page_size) = $filtered_select['limitpage']; - if ($page > 0) $page -= 1; - } - foreach($this->special_select_fields as $field) - unset($filtered_select[$field]); + /** + * @param $select null | string | array + */ + protected function _findAll($select, ?Storm_Query $query) : array { + $group_by = $this->_getGroupBy($select, $query); + $order = $this->_getOrder($select); + $limit = $this->_getLimit($select, $query); + $limit_page = $this->_getLimitPage($select, $query); + + $page_size = 0; + if ($limit_page) { + list($page, $page_size) = $limit_page; + if ($page > 0) + $page -= 1; + } $values = []; - $this->_allMatchingInstancesDo($filtered_select, + $this->_allMatchingInstancesDo($select, + $query, function($model) use (&$values) { $values []= $model; }); $values = $this->groupBy($values, $group_by); - $values = $this->ordered($values, $order); + $values = $query + ? $query->orderVolatile($values) + : $this->ordered($values, $order); $values = $this->limited($values, $limit); - if ($page_size>0) + if ($page_size > 0) $values = array_slice($values, $page * $page_size, $page_size); return array_map([$this->_loader, 'newFromRow'], @@ -80,74 +90,84 @@ class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceS } - public function fetchAllBy($fields, $params) { + public function fetchAllBy($fields, $params) : array { return array_map(function($model) use($fields) - { - $keys = array_map(function($field) - { - $parts = explode(' as ', $field); - return current($parts); - }, - $fields); - - $aliases = array_map(function($field) - { - $parts = explode(' as ', $field); - return isset($parts[1]) ? $parts[1] : current($parts); - }, - $fields); - - $map = array_combine($keys, $aliases); - - $filtered = array_filter($model->getRawAttributes(), - function($key) use ($keys) - { - return in_array($key, $keys); - }, - ARRAY_FILTER_USE_KEY); - - $row = []; - foreach($filtered as $k => $v) - $row[$map[$k]] = $v; - - return $row; - }, + { + $keys = array_map(fn($field) => current(explode(' as ', $field)), + $fields); + + $aliases = array_map(function($field) + { + $parts = explode(' as ', $field); + return isset($parts[1]) ? $parts[1] : current($parts); + }, + $fields); + + $map = array_combine($keys, $aliases); + + $filtered = array_filter($model->getRawAttributes(), + fn($key) => in_array($key, $keys), + ARRAY_FILTER_USE_KEY); + + $row = []; + foreach($filtered as $k => $v) + $row[$map[$k]] = $v; + + return $row; + }, $this->findAll($params)); } - protected function extractRoleAndModel($select) { - if (array_key_exists('role', $select) && array_key_exists('model', $select)) { + public function fetchForQuery(Storm_Query $query) : array { + return $this->_findAll([], $query); + } + + + public function countForQuery(Storm_Query $query) : int { + return sizeof($this->fetchForQuery($query)); + } + + + protected function _extractRoleAndModel($select) : ?array { + $select = is_array($select) + ? array_change_key_case($select) + : []; + + if (array_key_exists('role', $select) + && array_key_exists('model', $select)) { $model = $select['model']; $role = $select['role']; unset($select['model']); unset($select['role']); - if ($model->isNew()) return null; + // Warning, this return null is necessary to early exit search mode + if ($model->isNew()) + return null; $field = $this->_loader->getIdFieldForDependent($role); - $select[$field]=$model->getId(); + $select[$field] = $model->getId(); } if (array_key_exists('scope', $select)) { - $select = array_merge($select, array_change_key_case($select['scope'])); + $select = array_merge($select, + array_change_key_case($select['scope'])); unset($select['scope']); } + return $select; } - protected function getInstancesArray() : array { return array_values( - array_map( - function($model) {return $model->getRawAttributes();}, - $this->_instances)); + array_map(fn($model) => $model->getRawAttributes(), + $this->_instances)); } - public function findAllBy($args) : array { + public function findAllBy(array $args = []) : array { return $this->findAll($args); } @@ -155,13 +175,11 @@ class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceS protected function compareFunction($order) { $first_clause = Storm_Model_PersistenceStrategy_Volatile_OrderClause::parseOrderClause($order); - return function($a, $b) use ($first_clause) { - return $first_clause->compare($a, $b); - }; + return fn($a, $b) => $first_clause->compare($a, $b); } - public function ordered($result, $select) { + public function ordered(array $result, $select) : array { if (! ($select && $result) ) return $result; @@ -173,8 +191,8 @@ class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceS } - public function limited($result, $limit) { - if ('' == $limit) + public function limited(array $result, string $limit) : array { + if (!$limit) return $result; if (false === strpos($limit, ',')) @@ -211,16 +229,16 @@ class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceS public function containsAttribute(array $model, string $key, $value) : bool { - $clause = ($value instanceof Storm_Model_PersistenceStrategy_Clause) + $clause = ($value instanceof Storm_Query_Clause) ? $value : null; if (!$clause && preg_match('/left\((.+),(.+)\)/', $key, $matches)) - $clause = Storm_Model_PersistenceStrategy_Clause::clauseLike(trim($matches[1]), - (substr($value, 0, (int)$matches[2]) . '%')); + $clause = Storm_Query_Clause::like(trim($matches[1]), + (substr($value, 0, (int)$matches[2]) . '%')); if (!$clause) - $clause = Storm_Model_PersistenceStrategy_Clause::newWith($key, $value); + $clause = Storm_Query_Clause::newWith($key, $value); return $clause->containAttibuteInVolatile($model); } @@ -276,6 +294,7 @@ class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceS public function updateAll(array $where, array $set_values) : int { return $this->_allMatchingInstancesDo($where, + null, function($model) use ($set_values) { $this->_instances[$model['id']]->initializeAttributes($set_values); }); @@ -284,6 +303,7 @@ class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceS public function deleteBy(array $clauses) : int { return $this->_allMatchingInstancesDo($clauses, + null, function($model) { unset($this->_instances[$model['id']]); }, @@ -291,11 +311,14 @@ class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceS } - protected function _allMatchingInstancesDo(array $clauses, callable $callback) : int { - $matching_instances = array_filter($this->getInstancesArray(), - function ($model) use ($clauses) { - return $this->_modelMatchesClauses($model, $clauses); - }); + protected function _allMatchingInstancesDo(array $clauses, + ?Storm_Query $query, + callable $callback) : int { + $filter_callback = $query + ? fn($model) => $this->_modelMatchesQuery($model, $query) + : fn($model) => $this->_modelMatchesClauses($model, $clauses); + + $matching_instances = array_filter($this->getInstancesArray(), $filter_callback); return count(array_map($callback, $matching_instances)); } @@ -306,6 +329,11 @@ class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceS } + protected function _modelMatchesQuery(array $model, Storm_Query $query) : bool { + return $query->containsAllAttributes($model); + } + + public function countBy(array $args) : int { return sizeof($this->findAll($args)); } @@ -314,4 +342,43 @@ class Storm_Model_PersistenceStrategy_Volatile extends Storm_Model_PersistenceS public function isVolatile() : bool { return true; } + + + protected function _getLimit(array &$select, ?Storm_Query $query) : string { + if ($query) + return $query->getLimitValue(); + + $clause = Storm_Query_Clause::limit($select['limit'] ?? 0); + + unset($select['limit']); + return $clause->getValue(); + } + + + protected function _getLimitPage(array &$select, ?Storm_Query $query) : array { + if ($query) + return $query->getLimitPageValue(); + + $clause = Storm_Query_Clause::limitPage($select['limitpage'] ?? []); + + unset($select['limitpage']); + return $clause->getValue(); + } + + + protected function _getOrder(array &$select) { + $order = $select['order'] ?? ''; + unset($select['order']); + return $order; + } + + + protected function _getGroupBy(array &$select, ?Storm_Query $query) : string { + if ($query) + return $query->getGroupByValue(); + + $group_by = $select['group_by'] ?? ''; + unset($select['group_by']); + return $group_by; + } } diff --git a/src/Storm/Model/PersistenceStrategy/Volatile/OrderClause.php b/src/Storm/Model/PersistenceStrategy/Volatile/OrderClause.php index 7f65cbf5..4fb49279 100644 --- a/src/Storm/Model/PersistenceStrategy/Volatile/OrderClause.php +++ b/src/Storm/Model/PersistenceStrategy/Volatile/OrderClause.php @@ -1,26 +1,26 @@ <?php /* -STORM is under the MIT License (MIT) + STORM is under the MIT License (MIT) -Copyright (c) 2010-2011 Agence Française Informatique http://www.afi-sa.fr + Copyright (c) 2010-2011 Agence Française Informatique http://www.afi-sa.fr -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. */ @@ -34,12 +34,13 @@ class Storm_Model_PersistenceStrategy_Volatile_OrderClause { $clauses = array_reverse(array_filter(explode(',', strtolower($order)))); $first_clause = new static(array_shift($clauses)); - foreach($clauses as $clause) { + foreach($clauses as $clause) $first_clause = new static($clause, $first_clause); - } + return $first_clause; } + public function __construct($order, $next_clause = null) { $description = explode(' ', trim($order)); @@ -60,21 +61,11 @@ class Storm_Model_PersistenceStrategy_Volatile_OrderClause { } $result = (is_int($first)) - ? $this->int_compare((int)$first, (int)$last) - : $this->string_compare($first, $last); + ? ($first - (int)$last) + : strcmp($first, $last); return (($result === 0) && $this->_next_clause) ? $this->_next_clause->compare($a, $b) : $result; } - - - public function int_compare($a , $b) { - return ($a == $b) ? 0 : (($a < $b) ? -1 : 1); - } - - - public function string_compare($a, $b){ - return strcmp($a, $b); - } } diff --git a/src/Storm/Query.php b/src/Storm/Query.php new file mode 100644 index 00000000..bac1b191 --- /dev/null +++ b/src/Storm/Query.php @@ -0,0 +1,360 @@ +<?php +/* + STORM is under the MIT License (MIT) + + Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + + +class Storm_Query implements Storm_Query_CriteriaInterface { + + protected array $_orders; + protected Storm_Model_Loader $_loader; + protected Storm_Query_CriteriaInterface $_criteria; + protected ?Storm_Query_Clause $_clause_limit; + protected ?Storm_Query_Clause $_clause_limit_page; + protected ?Storm_Query_Clause $_clause_group_by; + + protected function __construct(Storm_Model_Loader $loader) { + $this->_loader = $loader; + $this->_orders = []; + $this->_clause_limit = null; + $this->_clause_limit_page = null; + $this->_clause_group_by = null; + $this->_criteria = new Storm_Query_Criteria; + } + + + public static function from(string $loader_class) : self { + return new static($loader_class::getLoader()); + } + + + /** + * @param $limit int | string + */ + public function limit($limit) : self { + $this->_clause_limit = Storm_Query_Clause::limit($limit); + return $this; + } + + + /** + * @param $range array | int | string + */ + public function limit_page($range) : self { + $this->_clause_limit_page = Storm_Query_Clause::limitPage($range); + return $this; + } + + + public function group(string $value) : self { + $this->_clause_group_by = Storm_Query_Clause::group($value); + return $this; + } + + + /** + * @param $key_or_clause string|Storm_Query_MatchRating + */ + public function order($key_or_clause) : self { + $this->_orders [] = $this->_order($key_or_clause) + ->setOrder(false); + return $this; + } + + + /** + * @param $key_or_clause string|Storm_Query_MatchRating + */ + public function order_desc($key_or_clause) : self { + $this->_orders [] = $this->_order($key_or_clause) + ->setOrder(true); + return $this; + } + + + /** + * @param $key_or_clause string|Storm_Query_MatchRating + */ + protected function _order($key_or_clause) : Storm_Query_Clause { + if ($key_or_clause instanceof Storm_Query_MatchRating) + return Storm_Query_Clause::order($key_or_clause->getKey(), + Storm_Query_Clause::match($key_or_clause)); + + return Storm_Query_Clause::order($key_or_clause); + } + + + public function fetchAll() : array { + return $this->_loader->fetchForQuery($this); + } + + + public function count() : int { + return $this->_loader->countForQuery($this); + } + + + public function fetchFirst() : ?Storm_Model_Abstract { + return $this->_loader->fetchFirstForQuery($this); + } + + + /** interface Storm_Query_CriteriaInterface */ + + public function beOr() : self { + $this->_criteria->beOr(); + return $this; + } + + + public function or(Storm_Query_CriteriaInterface $criteria) : self { + $this->_criteria->or($criteria); + return $this; + } + + + public function and(Storm_Query_CriteriaInterface $criteria) : self { + $this->_criteria->and($criteria); + return $this; + } + + + /** + * @param $value int | string + */ + public function eq(string $key, $value) : self { + $this->_criteria->eq($key, $value); + return $this; + } + + + /** + * @param $value int | string + */ + public function not_eq(string $key, $value) : self { + $this->_criteria->not_eq($key, $value); + return $this; + } + + + public function like(string $key, string $value) : self { + $this->_criteria->like($key, $value); + return $this; + } + + + public function not_like(string $key, string $value) : self { + $this->_criteria->not_like($key, $value); + return $this; + } + + + /** + * @param $value int | string + */ + public function gt(string $key, $value) : self { + $this->_criteria->gt($key, $value); + return $this; + } + + + /** + * @param $value int | string + */ + public function gt_eq(string $key, $value) : self { + $this->_criteria->gt_eq($key, $value); + return $this; + } + + + /** + * @param $value int | string + */ + public function lt(string $key, $value) : self { + $this->_criteria->lt($key, $value); + return $this; + } + + + /** + * @param $value int | string + */ + public function lt_eq(string $key, $value) : self { + $this->_criteria->lt_eq($key, $value); + return $this; + } + + + public function is_null(string $key) : self { + $this->_criteria->is_null($key); + return $this; + } + + + public function not_is_null(string $key) : self { + $this->_criteria->not_is_null($key); + return $this; + } + + + public function in(string $key, array $array) : self { + $this->_criteria->in($key, $array); + return $this; + } + + + public function not_in(string $key, array $array) : self { + $this->_criteria->not_in($key, $array); + return $this; + } + + + public function start(string $key, string $value) : self { + $this->_criteria->start($key, $value); + return $this; + } + + + public function end(string $key, string $value) : self { + $this->_criteria->end($key, $value); + return $this; + } + + + public function match(Storm_Query_MatchBoolean $match) : self { + $this->_criteria->match($match); + return $this; + } + + + /** DB management */ + + /** + * @param $select Zend_Db_Table_Select|Storm_Test_ObjectWrapper + */ + public function assemble(object $select) : self { + $sql = new Storm_Query_Sql; + $this->_criteria->assemble($sql); + if ($where = $sql->implode($this->_criteria->separator())) + $select->where($where); + + foreach ($this->_orders as $order) + $order->assemble($select); + + if ($this->_clause_limit) + $this->_clause_limit->assemble($select); + + if ($this->_clause_limit_page) + $this->_clause_limit_page->assemble($select); + + if ($this->_clause_group_by) + $this->_clause_group_by->assemble($select); + + return $this; + } + + + /** Volatile management */ + + public function orderVolatile(array $models) : array { + return (new Storm_Query_Order(array_reverse($this->_orders))) + ->compareVolatile($models); + } + + + public function containsAllAttributes(array $model) : bool { + return $this->_criteria->containsAllAttributes($model); + } + + + public function getLimitValue() : string { + return $this->_clause_limit + ? $this->_clause_limit->getValue() + : ''; + } + + + public function getLimitPageValue() : array { + return $this->_clause_limit_page + ? $this->_clause_limit_page->getValue() + : []; + } + + + public function getGroupByValue() : string { + return $this->_clause_group_by + ? $this->_clause_group_by->getValue() + : ''; + } +} + + + + +class Storm_Query_Order { + + protected ?Storm_Query_Order $_next_query; + protected ?Storm_Query_Clause $_clause; + protected int $_position; + + public function __construct(array $orders, ?int $position = 0) { + $this->_position = $position++; + $this->_clause = array_shift($orders); + + $this->_next_query = ($orders + ? (new Storm_Query_Order($orders, $position)) + : null); + } + + + public function getNextQuery() : ?Storm_Query_Order { + if (!($next_query = $this->_next_query)) + return null; + + return $next_query->getClause() + ? $next_query + : null; + } + + + public function getClause() : ?Storm_Query_Clause { + return $this->_clause; + } + + + public function getPosition() : int { + return 10 ** $this->_position; + } + + + public function compareVolatile(array $models) : array { + if (!$models) + return $models; + + if ($clause = $this->getClause()) + usort($models, fn($a, $b) => $clause->compare($this, $a, $b)); + + return $models; + } +} diff --git a/src/Storm/Query/Clause.php b/src/Storm/Query/Clause.php new file mode 100644 index 00000000..442b5653 --- /dev/null +++ b/src/Storm/Query/Clause.php @@ -0,0 +1,507 @@ +<?php +/* +STORM is under the MIT License (MIT) + +Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + + +class Storm_Query_Clause { + + protected string $_key; + protected string $_operator; + protected bool $_negated; + protected $_value; + + const + CLAUSE_WHERE = 'where', + CLAUSE_LIKE = 'like', + CLAUSE_EQUAL = '=', + CLAUSE_IN = 'in', + CLAUSE_IS = 'is', + CLAUSE_GREATER = '>', + CLAUSE_GREATER_EQUAL = '>=', + CLAUSE_LESSER = '<', + CLAUSE_LESSER_EQUAL = '<=', + CLAUSE_MATCH = 'MATCH', + CLAUSE_LIMIT = 'limit', + CLAUSE_LIMIT_PAGE = 'limitPage', + CLAUSE_ORDER_BY = 'order by', + CLAUSE_GROUP_BY = 'group by', + PERCENT = '%'; + + const CLAUSE_IMPLEMENTATIONS = + [ + self::CLAUSE_LIKE => Storm_Query_ClauseLike::class, + self::CLAUSE_EQUAL => Storm_Query_ClauseEqual::class, + self::CLAUSE_IN => Storm_Query_ClauseIn::class, + self::CLAUSE_IS => Storm_Query_ClauseIsNull::class, + self::CLAUSE_GREATER => Storm_Query_ClauseGreater::class, + self::CLAUSE_GREATER_EQUAL => Storm_Query_ClauseGreaterEqual::class, + self::CLAUSE_LESSER => Storm_Query_ClauseLesser::class, + self::CLAUSE_LESSER_EQUAL => Storm_Query_ClauseLesserEqual::class, + self::CLAUSE_MATCH => Storm_Query_Clause_Match::class, + self::CLAUSE_LIMIT => Storm_Query_ClauseLimit::class, + self::CLAUSE_LIMIT_PAGE => Storm_Query_ClauseLimitPage::class, + self::CLAUSE_ORDER_BY => Storm_Query_Clause_OrderBy::class, + self::CLAUSE_GROUP_BY => Storm_Query_ClauseGroupBy::class, + self::CLAUSE_WHERE => Storm_Query_ClauseWhere::class + ]; + + /** + * @param $value null | string | Storm_Query_Clause + */ + public function __construct(string $key, string $operator, $value) { + $this->_negated = false; + $this->_key = $key; + $this->_operator = $operator; + $this->_value = $value; + + $this->_prepareValue(); + } + + + protected function _prepareValue() : self { + return $this; + } + + + public static function newFor(string $key, + string $operator, + $value) : self { + $class = static::CLAUSE_IMPLEMENTATIONS[$operator]; + return new $class($key, $operator, $value); + } + + + public static function newWith(string $key, $value) : self { + $operator = static::CLAUSE_EQUAL; + if (static::CLAUSE_WHERE === $key) + $operator = static::CLAUSE_WHERE; + + $is_like = ' like' === substr($key, -5); + $key = $is_like ? substr($key, 0, strlen($key) - 5) : $key; + $negated = (' not' === substr($key, -4)); + $key = $negated ? substr($key, 0, strlen($key) - 4) : $key; + + if ($is_like) + $operator = static::CLAUSE_LIKE; + + if (null === $value) + $operator = static::CLAUSE_IS; + + if (is_array($value)) + $operator = static::CLAUSE_IN; + + return (static::newFor($key, $operator, $value))->setNegated($negated); + } + + + public static function isNull(string $key) : self { + return static::newFor($key, static::CLAUSE_IS, null); + } + + + public static function in(string $key, array $array) : self { + return static::newFor($key, static::CLAUSE_IN, $array); + } + + + public static function equal(string $key, $value) : self { + return static::newFor($key, static::CLAUSE_EQUAL, $value); + } + + + public static function like(string $key, string $value) : self { + return static::newFor($key, static::CLAUSE_LIKE, $value); + } + + + public static function start(string $key, string $value) : self { + return static::newFor($key, static::CLAUSE_LIKE, $value . static::PERCENT); + } + + + public static function end(string $key, string $value) : self { + return static::newFor($key, static::CLAUSE_LIKE, static::PERCENT . $value); + } + + + public static function greater(string $key, $value) : self { + return static::newFor($key, static::CLAUSE_GREATER, $value); + } + + + public static function greaterEqual(string $key, $value) : self { + return static::newFor($key, static::CLAUSE_GREATER_EQUAL, $value); + } + + + public static function lesser(string $key, $value) : self { + return static::newFor($key, static::CLAUSE_LESSER, $value); + } + + + public static function lesserEqual(string $key, $value) : self { + return static::newFor($key, static::CLAUSE_LESSER_EQUAL, $value); + } + + + public static function match(Storm_Query_MatchRating $match) : self { + return static::newFor($match->getKey(), static::CLAUSE_MATCH, $match); + } + + + /** + * @param $limit int | string + */ + public static function limit($limit) : self { + return static::newFor(static::CLAUSE_LIMIT, static::CLAUSE_LIMIT, $limit); + } + + + /** + * @param $range array | int | string + */ + public static function limitPage($range) : self { + return static::newFor(static::CLAUSE_LIMIT_PAGE, + static::CLAUSE_LIMIT_PAGE, + $range); + } + + + public static function group(string $value) : self { + return static::newFor(static::CLAUSE_GROUP_BY, static::CLAUSE_GROUP_BY, $value); + } + + + public static function order(string $key, + ?Storm_Query_Clause $clause = null) : self { + return static::newFor($key, static::CLAUSE_ORDER_BY, $clause); + } + + + public function setNegated(bool $negated) : self { + $this->_negated = $negated; + return $this; + } + + + public function getValue() { + return $this->_value; + } + + + public function assemble($select) : self { + return $this; + } + + + public function getFormatDb() : string { + if (static::CLAUSE_WHERE === $this->_operator) + return '(' . $this->getValue() . ')'; + + return Zend_Db_Table_Abstract::getDefaultAdapter() + ->quoteInto($this->_clauseFormatDb(), + $this->getValue(), null, null); + } + + + public function containAttibuteInVolatile(array $model) : bool { + if (!$this->_existKeyInModel($model)) + return $this->_negated; + + return ($this->_negated !== ($model[$this->_key] == $this->getValue())); + } + + + protected function _clauseFormatDb() : string { + return $this->_key . $this->_getOperator() . '?'; + } + + + protected function _getOperator() : string { + return ($this->_negated ? 'not ' : '') . $this->_operator; + } + + + protected function _existKeyInModel(array $model) : bool { + return array_key_exists($this->_key, $model); + } +} + + + + +class Storm_Query_ClauseLike extends Storm_Query_Clause { + + protected function _prepareValue() : self { + $this->_value = (string)$this->_value; + + return $this; + } + + + protected function _clauseFormatDb() : string { + return $this->_key . ' ' . $this->_getOperator() . ' ?'; + } + + + public function containAttibuteInVolatile(array $model) : bool { + if (!$this->_existKeyInModel($model)) + return $this->_negated; + + $matches = preg_match(('/^' . str_replace('%', '.*', + $this->getValue()) . '$/i'), + $model[$this->_key]); + + return $this->_negated ? !$matches : $matches; + } +} + + + + +class Storm_Query_ClauseEqual extends Storm_Query_Clause { + + protected function _getOperator() : string { + return ($this->_negated ? '!' : '') . $this->_operator; + } +} + + + + +class Storm_Query_ClauseIn extends Storm_Query_Clause { + + protected function _prepareValue() : self { + if (!is_array($this->_value)) + $this->_value = []; + + return $this; + } + + + public function containAttibuteInVolatile(array $model) : bool { + if (!$this->_existKeyInModel($model)) + return $this->_negated; + + if (0 === count($this->getValue())) + throw new Storm_Model_Exception(sprintf('array given for %s is empty', + $this->_key)); + + return in_array($model[$this->_key], $this->getValue()) + ? !$this->_negated + : $this->_negated; + } + + + protected function _clauseFormatDb() : string { + return $this->_key . ' ' . $this->_getOperator() . ' (?)'; + } +} + + + + +class Storm_Query_ClauseIsNull extends Storm_Query_Clause { + + protected function _prepareValue() : self { + $this->_value = null; + + return $this; + } + + + public function getFormatDb() : string { + return $this->_key . ' ' . $this->_getOperator() . ' null'; + } + + + protected function _getOperator() : string { + return $this->_operator . ($this->_negated ? ' not' : ''); + } +} + + + + +class Storm_Query_ClauseGreater extends Storm_Query_Clause { + + public function containAttibuteInVolatile(array $model) : bool { + if (!$this->_existKeyInModel($model)) + return false; + + return $model[$this->_key] > $this->getValue(); + } +} + + + + +class Storm_Query_ClauseGreaterEqual extends Storm_Query_Clause { + + public function containAttibuteInVolatile(array $model) : bool { + if (!$this->_existKeyInModel($model)) + return false; + + return $model[$this->_key] >= $this->getValue(); + } +} + + + + +class Storm_Query_ClauseLesser extends Storm_Query_Clause { + + public function containAttibuteInVolatile(array $model) : bool { + if (!$this->_existKeyInModel($model)) + return false; + + return $model[$this->_key] < $this->getValue(); + } +} + + + + +class Storm_Query_ClauseLesserEqual extends Storm_Query_Clause { + + public function containAttibuteInVolatile(array $model) : bool { + if (!$this->_existKeyInModel($model)) + return false; + + return $model[$this->_key] <= $this->getValue(); + } +} + + + + +class Storm_Query_ClauseLimit extends Storm_Query_Clause { + + protected function _prepareValue() : self { + $this->_value = (string)$this->_value; + + return $this; + } + + + public function assemble($select) : self { + if ($this->getValue()) + $select->limit($this->getValue()); + + return $this; + } + + + public function getFormatDb() : string { + return ''; + } + + + public function containAttibuteInVolatile(array $model) : bool { + return true; + } +} + + + + +class Storm_Query_ClauseLimitPage extends Storm_Query_Clause { + + protected function _prepareValue() : self { + if (!is_array($this->_value)) + $this->_value = [(int)$this->_value]; + + $limit = (int)($this->_value[0] ?? 0); + $offset = (int)($this->_value[1] ?? 0); + $this->_value = [$limit, $offset]; + + return $this; + } + + + public function assemble($select) : self { + $range = $this->getValue(); + + if ($range[1] > 0) + $select->limitPage($range[0], $range[1]); + + return $this; + } + + + public function getFormatDb() : string { + return ''; + } + + + public function containAttibuteInVolatile(array $model) : bool { + return true; + } +} + + + + +class Storm_Query_ClauseGroupBy extends Storm_Query_Clause { + + protected function _prepareValue() : self { + $this->_value = (string)$this->_value; + + return $this; + } + + + public function assemble($select) : self { + $select->group($this->getValue()); + return $this; + } + + + public function getFormatDb() : string { + return ''; + } + + + public function containAttibuteInVolatile(array $model) : bool { + return true; + } +} + + + + +class Storm_Query_ClauseWhere extends Storm_Query_Clause { + + protected function _prepareValue() : self { + $this->_value = (string)$this->_value; + + return $this; + } + + + public function containAttibuteInVolatile(array $model) : bool { + return true; + } +} diff --git a/src/Storm/Query/Clause/Match.php b/src/Storm/Query/Clause/Match.php new file mode 100644 index 00000000..f7ce65cb --- /dev/null +++ b/src/Storm/Query/Clause/Match.php @@ -0,0 +1,110 @@ +<?php +/* +STORM is under the MIT License (MIT) + +Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + + +class Storm_Query_Clause_Match extends Storm_Query_Clause { + + public function getFormatDb() : string { + if (!$value = $this->getValue()) + return ''; + + $content = ''; + foreach ($value->getTerms() as $key => $term) + $content = implode(' ', + array_filter([$content, + $term->getFormatDb($value->isStrict())])); + + $against = sprintf('AGAINST(%s%s)', + Zend_Db_Table_Abstract::getDefaultAdapter() + ->quoteInto('?', $content), + ($value->isBooleanMode() ? ' IN BOOLEAN MODE' : '')); + + return $this->_getOperator() . ' ' . $against; + } + + + public function compare(array $a, array $b) : int { + return ($this->_compare($this->_getContents($a)) + - $this->_compare($this->_getContents($b))); + } + + + protected function _compare(string $contents) : int { + if (!$value = $this->getValue()) + return 0; + + $compare = 0; + + foreach ($value->getTerms() as $term) { + $new_compare = $term->getCompareValue($contents); + if ($value->isStrict() && 0 === $new_compare) + return 0; + + $compare += $term->getCompareValue($contents); + } + + return $value->isBooleanMode() + ? min($compare, 1) + : ceil(($compare / count(explode(' ', $contents))) * 10); + } + + + public function containAttibuteInVolatile(array $model) : bool { + if (!($value = $this->getValue()) + || !($contents = $this->_getContents($model))) + return false; + + foreach ($value->getTerms() as $term) { + if ($value->isStrict() && !$term->containVolatile($contents)) + return false; + + if (!$value->isStrict() && $term->containVolatile($contents)) + return true; + } + + return $value->isStrict(); + } + + + protected function _getOperator() : string { + return $this->_operator . '(' . $this->_key . ')'; + } + + + protected function _clauseFormatDb() : string { + return $this->_getOperator(); + } + + + protected function _getContents(array $model) : string { + $contents = ''; + foreach (explode(',', $this->_key) as $key) + if (array_key_exists($key, $model)) + $contents = implode(' ', array_filter([$contents, $model[$key]])); + + return $contents; + } +} diff --git a/src/Storm/Query/Clause/MatchTerms.php b/src/Storm/Query/Clause/MatchTerms.php new file mode 100644 index 00000000..d255ac6b --- /dev/null +++ b/src/Storm/Query/Clause/MatchTerms.php @@ -0,0 +1,91 @@ +<?php +/* +STORM is under the MIT License (MIT) + +Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + +Permission is hereby granted, free of charge, to any person obtaining a copy +gof this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + + +class Storm_Query_Clause_MatchTerms { + + protected array $_values; + protected bool $_exact; + + public function __construct(array $values, bool $exact) { + $this->_values = $values; + $this->_exact = $exact; + } + + + public function getFormatDb(bool $strict) : string { + if (!($count = count($this->_values))) + return ''; + + $format_db = $strict ? '+' : ''; + $values = $this->_getValues(); + + if ($this->_exact) + return $format_db . '"' . $values . '"'; + + $format_db .= (!$strict || 1 === $count) + ? '%s' + : '(%s)'; + + return sprintf($format_db, $values); + } + + + public function containVolatile(string $contents) : bool { + if ($this->_exact) + return $this->_containValue($contents, $this->_getValues()); + + foreach ($this->_values as $value) + if ($this->_containValue($contents, $value)) + return true; + + return false; + } + + + public function getCompareValue(string $contents) : int { + if ($this->_exact) + return preg_match_all('/\b' . $this->_getValues() . '\b/', $contents); + + $compare = 0; + foreach ($this->_values as $value) + $compare += ($new_compare = preg_match_all('/\b' . $value . '\b/', $contents)) + ? $new_compare + : 0; + + return $compare; + } + + + protected function _containValue(string $contents, string $value) : bool { + return preg_match('/\b' . $value . '\b/', $contents); + } + + + protected function _getValues() : string { + return implode(' ', $this->_values); + } +} diff --git a/src/Storm/Query/Clause/OrderBy.php b/src/Storm/Query/Clause/OrderBy.php new file mode 100644 index 00000000..72deff1b --- /dev/null +++ b/src/Storm/Query/Clause/OrderBy.php @@ -0,0 +1,84 @@ +<?php +/* +STORM is under the MIT License (MIT) + +Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + + +class Storm_Query_Clause_OrderBy extends Storm_Query_Clause { + + const ORDER_DESC = ' desc'; + + protected ?string $_order_mode = null; + + public function assemble($select) : self { + $select->order((($clause = $this->getValue()) + ? $clause->getFormatDb($select->getTable()) + : $this->_key) + . $this->_order_mode); + return $this; + } + + + public function getFormatDb() : string { + return ''; + } + + + public function containAttibuteInVolatile(array $model) : bool { + return true; + } + + + public function setOrder(bool $mode) : self { + $this->_order_mode = $mode ? static::ORDER_DESC : ''; + return $this; + } + + + public function compare(Storm_Query_Order $query_order, array $a, array $b) : int { + $compare = $this->_compareValues($a, $b) + * $this->_direction() + * $query_order->getPosition(); + + return ($next_order = $query_order->getNextQuery()) + ? ($compare + $next_order->getClause()->compare($next_order, $a, $b)) + : $compare; + } + + + protected function _compareValues(array $a, array $b) : int { + if ($match_clause = $this->getValue()) + return $match_clause->compare($a, $b); + + $first = $this->_existKeyInModel($a) ? $a[$this->_key] : ''; + $second = $this->_existKeyInModel($b) ? $b[$this->_key] : ''; + + return ($first <=> $second); + } + + + protected function _direction() : int { + return static::ORDER_DESC === $this->_order_mode ? -1 : 1; + } +} diff --git a/src/Storm/Query/Criteria.php b/src/Storm/Query/Criteria.php new file mode 100644 index 00000000..48ba0961 --- /dev/null +++ b/src/Storm/Query/Criteria.php @@ -0,0 +1,233 @@ +<?php +/* + STORM is under the MIT License (MIT) + + Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + + +class Storm_Query_Criteria implements Storm_Query_CriteriaInterface { + + const + SEPARATOR_AND = 'and', + SEPARATOR_OR = 'or'; + + protected array $_clauses; + protected string $_separator; + + + public function __construct() { + $this->_clauses = []; + $this->_separator = static::SEPARATOR_AND; + } + + + public function beOr() : self { + $this->_separator = static::SEPARATOR_OR; + return $this; + } + + + public function separator() : string { + return ' ' . $this->_separator . ' '; + } + + + public function assemble(Storm_Query_Sql $sql) : self { + foreach ($this->_clauses as $clause) + $sql->write($this->_assembleOne($clause)); + + return $this; + } + + + /** + * @param $clause Storm_Query_CriteriaInterface|Storm_Query_Clause + */ + protected function _assembleOne(object $clause) : string { + if ($clause instanceof Storm_Query_CriteriaInterface) { + $sql = new Storm_Query_Sql; + $clause->assemble($sql); + + return '(' . $sql->implode($clause->separator()) . ')'; + } + + return $clause->getFormatDb(); + } + + + public function containsAllAttributes(array $model) : bool { + return static::SEPARATOR_AND === $this->_separator + ? $this->_containsAllAttributesForAnd($model) + : $this->_containsAllAttributesForOr($model); + } + + + protected function _containsAllAttributesForAnd(array $model) : bool { + foreach ($this->_clauses as $clause) + if (!$this->_containsOne($clause, $model)) + return false; + + return true; + } + + + protected function _containsAllAttributesForOr(array $model) : bool { + foreach ($this->_clauses as $clause) + if ($this->_containsOne($clause, $model)) + return true; + + return false; + } + + + protected function _containsOne($clause, array $model) : bool { + if ($clause instanceof Storm_Query_CriteriaInterface) + return $clause->containsAllAttributes($model); + + return $clause->containAttibuteInVolatile($model); + } + + + /** + * @param $value int | string + */ + public function eq(string $key, $value) : self { + $this->_clauses [] = Storm_Query_Clause::equal($key, $value); + return $this; + } + + + /** + * @param $value int | string + */ + public function not_eq(string $key, $value) : self { + $this->_clauses [] = Storm_Query_Clause::equal($key, $value) + ->setNegated(true); + return $this; + } + + + public function like(string $key, string $value) : self { + $this->_clauses [] = Storm_Query_Clause::like($key, $value); + return $this; + } + + + public function not_like(string $key, string $value) : self { + $this->_clauses [] = Storm_Query_Clause::like($key, $value) + ->setNegated(true); + return $this; + } + + + /** + * @param $value int | string + */ + public function gt(string $key, $value) : self { + $this->_clauses [] = Storm_Query_Clause::greater($key, $value); + return $this; + } + + + /** + * @param $value int | string + */ + public function gt_eq(string $key, $value) : self { + $this->_clauses [] = Storm_Query_Clause::greaterEqual($key, $value); + return $this; + } + + + /** + * @param $value int | string + */ + public function lt(string $key, $value) : self { + $this->_clauses [] = Storm_Query_Clause::lesser($key, $value); + return $this; + } + + + /** + * @param $value int | string + */ + public function lt_eq(string $key, $value) : self { + $this->_clauses [] = Storm_Query_Clause::lesserEqual($key, $value); + return $this; + } + + + public function is_null(string $key) : self { + $this->_clauses [] = Storm_Query_Clause::isNull($key); + return $this; + } + + + public function not_is_null(string $key) : self { + $this->_clauses [] = Storm_Query_Clause::isNull($key) + ->setNegated(true); + return $this; + } + + + public function in(string $key, array $array) : self { + $this->_clauses [] = Storm_Query_Clause::in($key, $array); + return $this; + } + + + public function not_in(string $key, array $array) : self { + $this->_clauses [] = Storm_Query_Clause::in($key, $array) + ->setNegated(true); + return $this; + } + + + public function start(string $key, string $value) : self { + $this->_clauses [] = Storm_Query_Clause::start($key, $value); + return $this; + } + + + public function end(string $key, string $value) : self { + $this->_clauses [] = Storm_Query_Clause::end($key, $value); + return $this; + } + + + public function or(Storm_Query_CriteriaInterface $criteria) : self { + $this->_clauses [] = $criteria->beOr(); + return $this; + } + + + public function and(Storm_Query_CriteriaInterface $criteria) : self { + $this->_clauses [] = $criteria; + return $this; + } + + + public function match(Storm_Query_MatchBoolean $match) : self { + $this->_clauses [] = Storm_Query_Clause::match($match); + return $this; + } +} diff --git a/src/Storm/Query/CriteriaInterface.php b/src/Storm/Query/CriteriaInterface.php new file mode 100644 index 00000000..4f44e298 --- /dev/null +++ b/src/Storm/Query/CriteriaInterface.php @@ -0,0 +1,100 @@ +<?php +/* + STORM is under the MIT License (MIT) + + Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + + +interface Storm_Query_CriteriaInterface { + + public function beOr() : self; + + + /** + * @param $value int | string + */ + public function eq(string $key, $value) : self; + + + /** + * @param $value int | string + */ + public function not_eq(string $key, $value) : self; + + + public function like(string $key, string $value) : self; + + + public function not_like(string $key, string $value) : self; + + + /** + * @param $value int | string + */ + public function gt(string $key, $value) : self; + + + /** + * @param $value int | string + */ + public function gt_eq(string $key, $value) : self; + + + /** + * @param $value int | string + */ + public function lt(string $key, $value) : self; + + + /** + * @param $value int | string + */ + public function lt_eq(string $key, $value) : self; + + + public function is_null(string $key) : self; + + + public function not_is_null(string $key) : self; + + + public function in(string $key, array $array) : self; + + + public function not_in(string $key, array $array) : self; + + + public function start(string $key, string $value) : self; + + + public function end(string $key, string $value) : self; + + + public function or(Storm_Query_CriteriaInterface $criteria) : self; + + + public function and(Storm_Query_CriteriaInterface $criteria) : self; + + + public function match(Storm_Query_MatchBoolean $match) : self; +} diff --git a/src/Storm/Query/MatchBoolean.php b/src/Storm/Query/MatchBoolean.php new file mode 100644 index 00000000..15ab7d81 --- /dev/null +++ b/src/Storm/Query/MatchBoolean.php @@ -0,0 +1,39 @@ +<?php +/* + STORM is under the MIT License (MIT) + + Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + + +class Storm_Query_MatchBoolean extends Storm_Query_MatchRating { + + public function __construct($array_or_key, bool $strict = false) { + parent::__construct($array_or_key, $strict); + $this->_boolean_mode = true; + } + + + public function exact($array_or_value) : self { + return $this->_addTerm($array_or_value, true); + } +} diff --git a/src/Storm/Query/MatchRating.php b/src/Storm/Query/MatchRating.php new file mode 100644 index 00000000..8dfcb720 --- /dev/null +++ b/src/Storm/Query/MatchRating.php @@ -0,0 +1,78 @@ +<?php +/* + STORM is under the MIT License (MIT) + + Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + +*/ + + +class Storm_Query_MatchRating { + + protected string $_key; + protected array $_terms; + protected bool $_boolean_mode; + protected bool $_strict; + + public function __construct($array_or_key, bool $strict = false) { + $this->_key = is_array($array_or_key) + ? implode(', ', $array_or_key) + : $array_or_key; + $this->_terms = []; + $this->_boolean_mode = false; + $this->_strict = $strict; + } + + + public function isBooleanMode() : bool { + return $this->_boolean_mode; + } + + + public function isStrict() : bool { + return $this->_strict; + } + + + public function getKey() : string { + return $this->_key; + } + + + public function getTerms() : array { + return $this->_terms; + } + + + public function against($array_or_value) : self { + return $this->_addTerm($array_or_value); + } + + + protected function _addTerm($array_or_value, bool $exact = false) : self { + if (!is_array($array_or_value)) + $array_or_value = explode(' ', $array_or_value); + + $this->_terms [] = (new Storm_Query_Clause_MatchTerms($array_or_value, + $exact)); + return $this; + } +} diff --git a/src/Storm/Query/Sql.php b/src/Storm/Query/Sql.php new file mode 100644 index 00000000..53b34797 --- /dev/null +++ b/src/Storm/Query/Sql.php @@ -0,0 +1,41 @@ +<?php +/* +STORM is under the MIT License (MIT) + +Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + + +class Storm_Query_Sql { + + protected array $_sql = []; + + public function write(string $sql) : self { + $this->_sql [] = $sql; + return $this; + } + + + public function implode(string $glue) : string { + return implode($glue, $this->_sql); + } +} diff --git a/src/Storm/Test/ModelTestCase.php b/src/Storm/Test/ModelTestCase.php index 70fd6937..bf7534a2 100644 --- a/src/Storm/Test/ModelTestCase.php +++ b/src/Storm/Test/ModelTestCase.php @@ -27,13 +27,43 @@ THE SOFTWARE. abstract class Storm_Test_ModelTestCase extends PHPUnit_Framework_TestCase { use Storm_Test_THelpers; + protected $_old_adapter; + protected bool $_storm_mock_zend_adapter = true; + protected function setUp() { Storm_Model_Abstract::unsetLoaders(); + + $this->_old_adapter = Zend_Db_Table_Abstract::getDefaultAdapter(); + + if (!$this->_storm_mock_zend_adapter) + return; + + $adapter = new class() extends Zend_Db_Adapter_Mysqli + { + public function __construct($config=[]) { } + + + public function getConfig() { + return ['dbname' => 'mockdb']; + } + + + protected function _connect() { + $this->_connection = Storm_Test_ObjectWrapper::mock() + ->whenCalled('real_escape_string') + ->willDo(fn($value) => $value); + + return true; + } + }; + + Zend_Db_Table_Abstract::setDefaultAdapter($adapter); } protected function tearDown() { Storm_Model_Abstract::unsetLoaders(); + Zend_Db_Table_Abstract::setDefaultAdapter($this->_old_adapter); } @@ -43,5 +73,3 @@ abstract class Storm_Test_ModelTestCase extends PHPUnit_Framework_TestCase { $message); } } - -?> \ No newline at end of file diff --git a/tests/Storm/Test/LoaderQueryTest.php b/tests/Storm/Test/LoaderQueryTest.php new file mode 100644 index 00000000..297c8bdb --- /dev/null +++ b/tests/Storm/Test/LoaderQueryTest.php @@ -0,0 +1,886 @@ +<?php +/* + STORM is under the MIT License (MIT) + + Copyright (c) 2010-2022 Agence Française Informatique http://www.afi-sa.fr + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + + +abstract class Storm_Test_LoaderQueryTestCase extends Storm_Test_ModelTestCase { + + protected + $_select, + $_table, + $_loader; + + public function setUp() { + parent::setUp(); + + $this->_table = $this->mock(); + $this->_loader = Storm_Test_Mock_User::getLoader()->setTable($this->_table); + } +} + + + + +class Storm_Test_LoaderQueryDbTest extends Storm_Test_LoaderQueryTestCase { + + public function setUp() { + parent::setUp(); + + $this->_table + ->whenCalled('select')->answers($this->_select = $this->mock()) + ->whenCalled('fetchAll')->with($this->_select) + ->answers(new Zend_Db_Table_Rowset([])); + + $this->_select->whenCalled('where')->answers($this->_select) + ->whenCalled('getTable')->answers($this->_table); + } + + + /** @test */ + public function withClauseGreaterThanShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->gt('id', '30') + ->fetchAll(); + + $this->assertEquals(['id>\'30\''], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseGreaterThanEqualShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->gt_eq('id', '30') + ->fetchAll(); + + $this->assertEquals(['id>=\'30\''], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseLesserThanShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->lt('id', '30') + ->fetchAll(); + + $this->assertEquals(['id<\'30\''], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseLesserThanEqualShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->lt_eq('id', '30') + ->fetchAll(); + + $this->assertEquals(['id<=\'30\''], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseIsShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->is_null('name') + ->fetchAll(); + + $this->assertEquals(['name is null'], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseIsNotShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->not_is_null('name') + ->fetchAll(); + + $this->assertEquals(['name is not null'], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseInShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->in('name', ['Harlock', 'Nausicaa']) + ->fetchAll(); + + $this->assertEquals(['name in (\'Harlock\', \'Nausicaa\')'], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseNotInShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->not_in('name', ['Harlock', 'Nausicaa']) + ->fetchAll(); + + $this->assertEquals(['name not in (\'Harlock\', \'Nausicaa\')'], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseEqualShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->eq('left(name, 5)', 'ganda') + ->fetchAll(); + + $this->assertEquals(['left(name, 5)=\'ganda\''], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseNotEqualShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->not_eq('name', 'Harlock') + ->fetchAll(); + + $this->assertEquals(['name!=\'Harlock\''], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseLikeShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->like('login', '%aus%') + ->fetchAll(); + + $this->assertEquals(['login like \'%aus%\''], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseNotLikeShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->not_like('login', '%aus%') + ->fetchAll(); + + $this->assertEquals(['login not like \'%aus%\''], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseStartShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->start('login', 'aus') + ->fetchAll(); + + $this->assertEquals(['login like \'aus%\''], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseEndShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->end('login', 'aus') + ->fetchAll(); + + $this->assertEquals(['login like \'%aus\''], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseOrShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->or((new Storm_Query_Criteria) + ->eq('login', 'aus') + ->in('role', ['admin', 'invite'])) + ->fetchAll(); + + $this->assertEquals(['(login=\'aus\' or role in (\'admin\', \'invite\'))'], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseAndShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->and((new Storm_Query_Criteria) + ->eq('login', 'aus') + ->in('role', ['admin', 'invite'])) + ->fetchAll(); + + $this->assertEquals(['(login=\'aus\' and role in (\'admin\', \'invite\'))'], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseMatchShouldReturnQueryInBooleanMode() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->match((new Storm_Query_MatchBoolean('login, role')) + ->against(['ADMIN', 'INVITE'])) + ->fetchAll(); + + $this->assertEquals(['MATCH(login, role) AGAINST(\'ADMIN INVITE\' IN BOOLEAN MODE)'], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseMatchMultipleTermsShouldReturnQueryInBooleanMode() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->match((new Storm_Query_MatchBoolean('login,role')) + ->against('ADMIN INVITE') + ->against('HUGO ADRIEN')) + ->fetchAll(); + + $this->assertEquals(['MATCH(login,role) AGAINST(\'ADMIN INVITE HUGO ADRIEN\' IN BOOLEAN MODE)'], + $this->_select->getAttributesForLastCallOn('where')); + } + + + /** @test */ + public function withClauseMatchStrictAndOneExactExpressionWithNoBooleanModeShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->match((new Storm_Query_MatchBoolean(['login', 'role'], true)) + ->exact('ADMIN INVITE') + ->against('HUGO')) + ->fetchAll(); + + $this->assertEquals(['MATCH(login, role) AGAINST(\'+"ADMIN INVITE" +HUGO\' IN BOOLEAN MODE)'], + $this->_select->getAttributesForLastCallOn('where')); + } +} + + + + +class Storm_Test_LoaderQueryLimitTest extends Storm_Test_LoaderQueryTestCase { + + public function setUp() { + parent::setUp(); + + $this->_table + ->whenCalled('select')->answers($this->_select = $this->mock()) + ->whenCalled('fetchAll')->with($this->_select) + ->answers(new Zend_Db_Table_Rowset([])); + + $this->_select->whenCalled('limit')->answers($this->_select); + } + + + /** @test */ + public function withClauseLimit30ShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->limit(30) + ->fetchAll(); + + $this->assertEquals([30], + $this->_select->getAttributesForLastCallOn('limit')); + } + + + /** @test */ + public function withClauseLimit30Offset10ShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->limit('10, 30') + ->fetchAll(); + + $this->assertEquals(['10, 30'], + $this->_select->getAttributesForLastCallOn('limit')); + } +} + + + + +class Storm_Test_LoaderQueryLimitPageTest extends Storm_Test_LoaderQueryTestCase { + + public function setUp() { + parent::setUp(); + + $this->_table + ->whenCalled('select')->answers($this->_select = $this->mock()) + ->whenCalled('fetchAll')->with($this->_select) + ->answers(new Zend_Db_Table_Rowset([])); + + $this->_select->whenCalled('limitPage')->answers($this->_select); + } + + + /** @test */ + public function withClauseLimitPageShouldReturnQuery() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->limit_page([1, 100]) + ->fetchAll(); + + $this->assertEquals([1, 100], + $this->_select->getAttributesForLastCallOn('limitPage')); + } +} + + + + +class Storm_Test_LoaderQueryOrderTest extends Storm_Test_LoaderQueryTestCase { + + public function setUp() { + parent::setUp(); + + $this->_table + ->whenCalled('select')->answers($this->_select = $this->mock()) + ->whenCalled('fetchAll')->with($this->_select) + ->answers(new Zend_Db_Table_Rowset([])); + + $this->_select->whenCalled('order')->answers($this->_select) + ->whenCalled('getTable')->answers($this->_table); + } + + + /** @test */ + public function withClauseOrderIdShouldReturnOrderId() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->order('id') + ->fetchAll(); + + $this->assertEquals(['id'], + $this->_select->getAttributesForLastCallOn('order')); + } + + + /** @test */ + public function withClauseOrderIdDescShouldReturnOrderIdDesc() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->order_desc('id') + ->fetchAll(); + + $this->assertEquals(['id desc'], + $this->_select->getAttributesForLastCallOn('order')); + } + + + /** @test */ + public function withMultipleClauseOrderShouldReturnAllOrders() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->order_desc('id') + ->order('name') + ->order_desc('role') + ->fetchAll(); + + $this->assertEquals(['id desc', 'name', 'role desc'], + [$this->_select->getFirstAttributeForMethodCallAt('order', 0), + $this->_select->getFirstAttributeForMethodCallAt('order', 1), + $this->_select->getFirstAttributeForMethodCallAt('order', 2)]); + } + + + /** @test */ + public function withMatchClauseOrderShouldReturnMatchOrder() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->order((new Storm_Query_MatchRating('login, role')) + ->against(['ADMIN', 'INVITE'])) + ->fetchAll(); + + $this->assertEquals(['MATCH(login, role) AGAINST(\'ADMIN INVITE\')'], + $this->_select->getAttributesForLastCallOn('order')); + } + + + /** @test */ + public function withMatchClauseOrderDescShouldReturnMatchOrder() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->order_desc((new Storm_Query_MatchBoolean('login, role')) + ->against(['ADMIN', 'INVITE'])) + ->fetchAll(); + + $this->assertEquals(['MATCH(login, role) AGAINST(\'ADMIN INVITE\' IN BOOLEAN MODE) desc'], + $this->_select->getAttributesForLastCallOn('order')); + } +} + + + + +class Storm_Test_LoaderQueryGroupByTest extends Storm_Test_LoaderQueryTestCase { + + public function setUp() { + parent::setUp(); + + $this->_table + ->whenCalled('select')->answers($this->_select = $this->mock()) + ->whenCalled('fetchAll')->with($this->_select) + ->answers(new Zend_Db_Table_Rowset([])); + + $this->_select->whenCalled('group')->answers($this->_select); + } + + + /** @test */ + public function withGroupByLoaderShouldCallGroupOnDbSelect() { + Storm_Query::from(Storm_Test_Mock_User::class) + ->group('name') + ->fetchAll(); + + $this->assertEquals(['name'], + $this->_select->getAttributesForLastCallOn('group')); + } +} + + + + +class Storm_Test_LoaderQueryUser extends Storm_Model_Abstract { + + protected $_table_name = 'users'; +} + + + + +class Storm_Test_LoaderQueryVolatileClauseWhereTest extends Storm_Test_ModelTestCase { + + public function setUp() { + parent::setUp(); + + $this->fixture(Storm_Test_LoaderQueryUser::class, + ['id' => 100, + 'login' => 'user_admin', + 'level' => 'invite admin redacteur', + 'foo' => 'premier deuxieme troisieme', + 'prefs' => null]); + + $this->fixture(Storm_Test_LoaderQueryUser::class, + ['id' => 101, + 'login' => 'user_administrateur', + 'level' => 'administrateur', + 'foo' => 'deuxieme redacteur', + 'prefs' => '{"best":true}']); + + $this->fixture(Storm_Test_LoaderQueryUser::class, + ['id' => 102, + 'login' => 'user_invite', + 'level' => 'invite', + 'foo' => 'premier deuxieme']); + } + + + /** @test */ + public function whereId_GT_100_ShouldAnswersUserAdministrateurAndUserInvite() { + $this->assertEquals(['user_administrateur', 'user_invite'], + (new Storm_Model_Collection(Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->gt('id', 100) + ->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function whereId_GT_EQ_101_ShouldAnswersUserAdministrateurAndUserInvite() { + $this->assertEquals(['user_administrateur', 'user_invite'], + (new Storm_Model_Collection(Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->gt_eq('id', 101) + ->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function whereId_LT_102_ShouldAnswersUserAdminAndUserAdministrateur() { + $this->assertEquals(['user_admin', 'user_administrateur'], + (new Storm_Model_Collection(Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->lt('id', 102) + ->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function whereId_LT_EQ_101_ShouldAnswersUserAdminAndUserAdministrateur() { + $this->assertEquals(['user_admin', 'user_administrateur'], + (new Storm_Model_Collection(Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->lt_eq('id', 101) + ->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function whereLogin_NOT_EQ_UserAdministrateurShouldReturnUserAdminAndUserInvite() { + $this->assertEquals(['user_admin', 'user_invite'], + (new Storm_Model_Collection(Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->not_eq('login', 'user_administrateur') + ->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function whereId_IN_100And102_AndFoo_NOT_LIKE_TroisieShouldReturnId_102() { + $this->assertEquals([102], + (new Storm_Model_Collection(Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->in('id', [100, 102]) + ->not_like('foo', '%troisie%') + ->fetchAll())) + ->collect('id') + ->getArrayCopy()); + } + + + /** @test */ + public function whereId_IN_100And101_AndFoo_START_DeuxShouldReturnId_101() { + $this->assertEquals([101], + (new Storm_Model_Collection(Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->in('id', [100, 101]) + ->start('foo', 'deux') + ->fetchAll())) + ->collect('id') + ->getArrayCopy()); + } + + + /** @test */ + public function whereFoo_END_ActeurShouldReturnId_101() { + $this->assertEquals([101], + (new Storm_Model_Collection(Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->end('foo', 'acteur') + ->fetchAll())) + ->collect('id') + ->getArrayCopy()); + } + + + /** @test */ + public function withMatchLevelAgainstAdminShouldAnswersOnlyUser_Admin() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->match((new Storm_Query_MatchBoolean('level')) + ->against(['admin'])); + $this->assertEquals(['user_admin'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function withMatchLevelAgainstAdminDeuxiemeShouldAnswersUser_AdminAndUser_AdministrateurAndUser_Invite() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->match((new Storm_Query_MatchBoolean('level,foo')) + ->against(['admin', 'deuxieme'])); + $this->assertEquals(['user_admin', 'user_administrateur', 'user_invite'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function withMatchLevelAgainstStrictAdminRedacteur_DeuxiemeShouldAnswersUser_AdminAndUser_Administrateur() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->match((new Storm_Query_MatchBoolean('level,foo', true)) + ->against(['admin', 'redacteur']) + ->against(['deuxieme'])); + $this->assertEquals(['user_admin', 'user_administrateur'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function withMatchLevelAgainstExactDeuxiemeTroisiemeShouldAnswersOnlyUser_Admin() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->match((new Storm_Query_MatchBoolean('level,foo')) + ->exact('deuxieme redacteur')); + $this->assertEquals(['user_administrateur'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function withOrClause_LevelInviteOrFooLikePremierShouldAnswersUser_AdminAndUser_Invite() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->or((new Storm_Query_Criteria) + ->eq('level', 'invite') + ->like('foo', '%premier%')); + $this->assertEquals(['user_admin', 'user_invite'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function withAndClause_LevelInviteOrFooLikePremierShouldAnswersOnlyUser_Invite() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->and((new Storm_Query_Criteria) + ->eq('level', 'invite') + ->like('foo', '%premier%')); + $this->assertEquals(['user_invite'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + + /** @test */ + public function withIsNullPrefsShouldAnswersUser_100_Only() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->and((new Storm_Query_Criteria)->is_null('prefs')); + + $this->assertEquals([Storm_Test_LoaderQueryUser::find(100)], + $query->fetchAll()); + } + + + /** @test */ + public function withIsNotNullPrefsShouldAnswersUsers_101_and_102() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->and((new Storm_Query_Criteria)->not_is_null('prefs')); + + $this->assertEquals([Storm_Test_LoaderQueryUser::find(101), + Storm_Test_LoaderQueryUser::find(102)], + $query->fetchAll()); + } + + + /** @test */ + public function withNotInInviteAdministrateurShouldAnswerUser_100_Only() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->not_in('level', ['invite', 'administrateur']); + + $this->assertEquals([Storm_Test_LoaderQueryUser::find(100)], + $query->fetchAll()); + } + + + /** @test */ + public function withClauseLimitOneShouldReturnOnlyUser_100() { + $this->assertEquals([Storm_Test_LoaderQueryUser::find(100)], + Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->limit(1) + ->fetchAll()); + } + + + /** @test */ + public function withClauseLimitOneTwoShouldReturnTwoUser_101_102() { + $this->assertEquals([Storm_Test_LoaderQueryUser::find(101), + Storm_Test_LoaderQueryUser::find(102)], + Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->limit('1, 2') + ->fetchAll()); + } + + + /** @test */ + public function withClauseLimitPageShouldPaginate() { + $this->assertEquals([Storm_Test_LoaderQueryUser::find(100), + Storm_Test_LoaderQueryUser::find(101)], + Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->limit_page([0, 2]) + ->fetchAll()); + $this->assertEquals([Storm_Test_LoaderQueryUser::find(100), + Storm_Test_LoaderQueryUser::find(101)], + Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->limit_page([1, 2]) + ->fetchAll()); + $this->assertEquals([Storm_Test_LoaderQueryUser::find(102)], + Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->limit_page([2, 2]) + ->fetchAll()); + } + + + /** @test */ + public function withClauseLimitPageAndAttributeShouldWork() { + $this->assertEquals([Storm_Test_LoaderQueryUser::find(100), + Storm_Test_LoaderQueryUser::find(102)], + Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->start('foo', 'premier') + ->limit_page([1, 2]) + ->fetchAll()); + } +} + + + + +class Storm_Test_LoaderQueryVolatileClauseOrderTest + extends Storm_Test_ModelTestCase { + + public function setUp() { + parent::setUp(); + + $this->fixture(Storm_Test_LoaderQueryUser::class, + ['id' => 100, + 'login' => 'user_admin', + 'level' => 'invite admin redacteur', + 'foo' => 'first second third', + 'order1' => 'abcd', + 'order2' => 2, + 'order3' => 'defg']); + + $this->fixture(Storm_Test_LoaderQueryUser::class, + ['id' => 101, + 'login' => 'user_administrateur', + 'level' => 'administrateur', + 'foo' => 'third second forth', + 'order1' => 'abcdef', + 'order2' => 3, + 'order3' => 'defg']); + + $this->fixture(Storm_Test_LoaderQueryUser::class, + ['id' => 102, + 'login' => 'user_invite', + 'level' => 'invite', + 'foo' => 'forth fifth seven', + 'order1' => 'cdef', + 'order2' => 1, + 'order3' => 'klmn']); + } + + + /** @test */ + public function with_Order1Desc_ShouldAnswersInOrdered() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->order_desc('order1'); + $this->assertEquals(['user_invite', 'user_administrateur', 'user_admin'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function with_Order3Asc_Order2Desc_ShouldAnswersOrderedUser() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->order('order3') + ->order_desc('order2'); + $this->assertEquals(['user_administrateur', 'user_admin', 'user_invite'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function with_Order3Desc_Order2Desc_ShouldAnswersOrderedUser() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->order_desc('order3') + ->order_desc('order2'); + $this->assertEquals(['user_invite', 'user_administrateur', 'user_admin'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function withMatch_Level_Administrateur_ShouldAnswersOrderedUser() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->order((new Storm_Query_MatchRating('level')) + ->against('administrateur')); + $this->assertEquals(['user_admin', 'user_invite', 'user_administrateur'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function withMatch_Level_Administrateur_Redacteur_Desc_ShouldAnswersOrderedUser() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->order_desc((new Storm_Query_MatchRating('level')) + ->against('administrateur redacteur')); + $this->assertEquals(['user_administrateur', 'user_admin', 'user_invite'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function withMatchInBooleanMode_Level_Administrateur_Redacteur_Desc_ShouldAnswersOrderedUser() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->order_desc((new Storm_Query_MatchBoolean('level')) + ->against('administrateur redacteur')); + $this->assertEquals(['user_admin', 'user_administrateur', 'user_invite'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function withMatch_FooDescExacteInBooleanMode_Forth_Fifth_Level_Administrateur_Redacteur_ShouldAnswersOrderedUser() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->order_desc((new Storm_Query_MatchBoolean('foo')) + ->exact('forth fifth')) + ->order((new Storm_Query_MatchRating('level')) + ->against('administrateur redacteur')); + $this->assertEquals(['user_invite', 'user_admin', 'user_administrateur'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } + + + /** @test */ + public function withMatch_FooLevelStrict_InviteRedacteur_SecondThird_ShouldAnswersOrderedUser() { + // https://www.php.net/manual/fr/function.usort + // If two members compare as equal, they retain their original order. Prior to PHP 8.0.0, their relative order in the sorted array was undefined. + if (version_compare(PHP_VERSION, '8.0.0', '<')) + return; + + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->order((new Storm_Query_MatchBoolean('foo,level', true)) + ->against('invite redacteur') + ->against('second third')); + $this->assertEquals([Storm_Test_LoaderQueryUser::find(101), + Storm_Test_LoaderQueryUser::find(102), + Storm_Test_LoaderQueryUser::find(100)], + $query->fetchAll()); + } + + + /** @test */ + public function selectUsersGroupByOrder3ShouldAnswersUserAdminAndUserInvite() { + $query = Storm_Query::from(Storm_Test_LoaderQueryUser::class) + ->group('order3'); + $this->assertEquals([0 => 'user_admin', 2 => 'user_invite'], + (new Storm_Model_Collection($query->fetchAll())) + ->collect('login') + ->getArrayCopy()); + } +} diff --git a/tests/Storm/Test/LoaderTest.php b/tests/Storm/Test/LoaderTest.php index e7ab7352..50eec16e 100644 --- a/tests/Storm/Test/LoaderTest.php +++ b/tests/Storm/Test/LoaderTest.php @@ -1,26 +1,26 @@ <?php /* -STORM is under the MIT License (MIT) - -Copyright (c) 2010-2011 Agence Française Informatique http://www.afi-sa.fr - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. + STORM is under the MIT License (MIT) + + Copyright (c) 2010-2011 Agence Française Informatique http://www.afi-sa.fr + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. */ @@ -38,15 +38,15 @@ abstract class Storm_Test_LoaderTestCase extends Storm_Test_ModelTestCase { ->whenCalled('quoteInto') ->willDo(function($clause, $value) - { - return str_replace('?', - is_array($value) - ? implode(',', - array_map(function($item){ return "'" . $item . "'"; }, - $value)) - : "'" . $value . "'", - $clause); - }); + { + return str_replace('?', + is_array($value) + ? implode(',', + array_map(function($item){ return "'" . $item . "'"; }, + $value)) + : "'" . $value . "'", + $clause); + }); $this->_loader = Storm_Test_Mock_User::getLoader()->setTable($this->_table); } @@ -158,41 +158,17 @@ class Storm_Test_LoaderBasicTest extends Storm_Test_LoaderTestCase { return [ [ ['name' => null] , ['name is null'] ], - [ ['name' => ['Harlock', 'Nausicaa']], ['name in (\'Harlock\',\'Nausicaa\')'] ], + [ ['name' => ['Harlock', 'Nausicaa']], ['name in (\'Harlock\', \'Nausicaa\')'] ], [ ['name not' => 'Harlock'], ['name!=\'Harlock\''] ], [ ['name not' => ['Harlock', 'Nausicaa']], - ['name not in (\'Harlock\',\'Nausicaa\')'] ], + ['name not in (\'Harlock\', \'Nausicaa\')'] ], [ ['name not' => null], ['name is not null'] ], [ ['login like' => '%aus%'], ['login like \'%aus%\''] ], [ ['login not like' => '%aus%'], ['login not like \'%aus%\''] ], [ ['where' => 'id=\'29\' and name=\'lp\''], ['id=\'29\' and name=\'lp\''] ], [ ['left(name, 5)' => 'ganda'], ['left(name, 5)=\'ganda\''] ], [ ['id' => '29'], ['id=\'29\''] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseGreater('id', '30') ], - ['id>\'30\''] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseGreaterEqual('id', '30') ], - ['id>=\'30\''] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseLesser('id', '30') ], - ['id<\'30\''] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseLesserEqual('id', '30') ], - ['id<=\'30\''] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseIs('name') ], ['name is null'] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseIn('name', ['Harlock', 'Nausicaa']) ], - ['name in (\'Harlock\',\'Nausicaa\')'] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseNotEqual('name', 'Harlock') ], - ['name!=\'Harlock\''] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseNotIn('name', ['Harlock', 'Nausicaa']) ], - ['name not in (\'Harlock\',\'Nausicaa\')'] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseLike('login', '%aus%') ], - ['login like \'%aus%\''] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseNotLike('login', '%aus%') ], - ['login not like \'%aus%\''] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseEqual('left(name, 5)', 'ganda') ], - ['left(name, 5)=\'ganda\''] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseStart('login', 'aus') ], - ['login like \'aus%\''] ], - [ [ Storm_Model_PersistenceStrategy_Clause::clauseEnd('login', 'aus') ], - ['login like \'%aus\''] ], + [ [ Storm_Query_Clause::greater('id', '30') ], ['id>\'30\''] ], ]; } @@ -243,7 +219,7 @@ class Storm_Test_LoaderBasicTest extends Storm_Test_LoaderTestCase { $this->_table->whenCalled('delete')->answers(2); $this->_loader->basicDeleteBy(['nom' => ['bond', 'l\'upin']]); - $this->assertEquals("nom in ('bond','l'upin')", + $this->assertEquals("nom in ('bond', 'l'upin')", $this->_table->getFirstAttributeForLastCallOn('delete')); } @@ -286,7 +262,7 @@ class Storm_Test_LoaderBasicTest extends Storm_Test_LoaderTestCase { ['type' => 'fictive']); $this->assertEquals([['type' => 'fictive'], - "nom in ('bond','lupin') and type='real'"], + "nom in ('bond', 'lupin') and type='real'"], $this->_table->getAttributesForLastCallOn('update')); } @@ -317,7 +293,7 @@ class Storm_Test_LoaderSaveWithIdTest extends Storm_Test_LoaderTestCase { ->answers($this->_select = $this->mock()); $this->_select - ->whenCalled('where')->with('id=\'4\'')->answers($this->_select) + ->whenCalled('where')->with('id=4')->answers($this->_select) ->whenCalled('from')->with($this->_table, ['count(*) as numberof'])->answers($this->_select); } @@ -353,7 +329,6 @@ class Storm_Test_LoaderSaveWithIdTest extends Storm_Test_LoaderTestCase { ->whenCalled('update') ->answers(1); - Storm_Test_Mock_User::newInstanceWithId(4, [])->save($force_primary_key = true); $this->assertArraySubset(['id' => 4], diff --git a/tests/Storm/Test/LoaderVolatileTest.php b/tests/Storm/Test/LoaderVolatileTest.php index 3e91fdf9..b7f46605 100644 --- a/tests/Storm/Test/LoaderVolatileTest.php +++ b/tests/Storm/Test/LoaderVolatileTest.php @@ -148,7 +148,6 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase { 'level' => 'invite', 'brain_id' => null, 'id_mouth' => null]); - } @@ -779,78 +778,6 @@ class Storm_Test_LoaderVolatileTest extends Storm_Test_ModelTestCase { } - /** @test */ - public function selectAllCatsWhereIdMoreThanThreeShouldAnswersFifiAndLoulou() { - $this->assertEquals(['fifi', 'loulou'], - (new Storm_Model_Collection(Storm_Test_VolatileCat::findAllBy([Storm_Test_VolatileCat::clauseGreater('id', 3)]))) - ->collect('name') - ->getArrayCopy()); - } - - - /** @test */ - public function selectAllCatsWhereIdMoreOrEqualThanForShouldAnswersFifiAndLoulou() { - $this->assertEquals(['fifi', 'loulou'], - (new Storm_Model_Collection(Storm_Test_VolatileCat::findAllBy([Storm_Test_VolatileCat::clauseGreaterEqual('id', 4)]))) - ->collect('name') - ->getArrayCopy()); - } - - - /** @test */ - public function selectAllCatsWhereIdLesserThanFiveShouldAnswersRiriAndFifi() { - $this->assertEquals(['riri', 'fifi'], - (new Storm_Model_Collection(Storm_Test_VolatileCat::findAllBy([Storm_Test_VolatileCat::clauseLesser('id', 5)]))) - ->collect('name') - ->getArrayCopy()); - } - - - /** @test */ - public function selectAllCatsWhereIdLesserOrEqualThanForShouldAnswersRiriAndFifi() { - $this->assertEquals(['riri', 'fifi'], - (new Storm_Model_Collection(Storm_Test_VolatileCat::findAllBy([Storm_Test_VolatileCat::clauseLesserEqual('id', 4)]))) - ->collect('name') - ->getArrayCopy()); - } - - - /** @test */ - public function selectUserLoginNotHubertShouldReturnAlbertAndZoe() { - $this->assertEquals(['albert', 'zoe'], - (new Storm_Model_Collection(Storm_Test_VolatileUser::findAllBy([Storm_Test_VolatileUser::clauseNotEqual('login', 'hubert')]))) - ->collect('login') - ->getArrayCopy()); - } - - - /** @test */ - public function selectUserLoginInAlbertZoeAndFooNotLikeSnafuShouldReturnAlbert() { - $this->assertEquals(['albert'], - (new Storm_Model_Collection(Storm_Test_VolatileUser::findAllBy([Storm_Test_VolatileUser::clauseIn('login', ['albert', 'zoe']), Storm_Test_VolatileUser::clauseNotLike('foo', '%naf%')]))) - ->collect('login') - ->getArrayCopy()); - } - - - /** @test */ - public function selectUserLoginInAlbertZoeAndFooStartSnaShouldReturnZoe() { - $this->assertEquals(['zoe'], - (new Storm_Model_Collection(Storm_Test_VolatileUser::findAllBy([Storm_Test_VolatileUser::clauseIn('login', ['albert', 'zoe']), Storm_Test_VolatileUser::clauseStart('foo', 'sna')]))) - ->collect('login') - ->getArrayCopy()); - } - - - /** @test */ - public function selectUserLoginInAlbertZoeAndFooEndAfuShouldReturnZoe() { - $this->assertEquals(['zoe'], - (new Storm_Model_Collection(Storm_Test_VolatileUser::findAllBy([Storm_Test_VolatileUser::clauseIn('login', ['albert', 'zoe']), Storm_Test_VolatileUser::clauseEnd('foo', 'afu')]))) - ->collect('login') - ->getArrayCopy()); - } - - /** @test */ public function selectUsersGroupByFooShouldAnswersAlbertAndHubert() { $this->assertEquals(['albert', 'hubert'], -- GitLab