├── src ├── KeyNotExists.php ├── Exceptions │ ├── InvalidJsonException.php │ ├── NullValueException.php │ ├── FileNotFoundException.php │ ├── InvalidNodeException.php │ ├── KeyNotPresentException.php │ └── ConditionNotAllowedException.php ├── Utilities.php ├── ArrayQuery.php ├── ConditionFactory.php ├── QueryEngine.php └── Clause.php ├── changelog.txt ├── helpers └── qarray.php ├── composer.json ├── .github └── workflows │ └── workflow.yml └── LICENSE /src/KeyNotExists.php: -------------------------------------------------------------------------------- 1 | time_ago 8 | 6. And improve some performance -------------------------------------------------------------------------------- /helpers/qarray.php: -------------------------------------------------------------------------------- 1 | $map) { 15 | if ($map instanceof QueryEngine) { 16 | $new_data[$key] = self::toArray($map); 17 | } else { 18 | $new_data[$key] = $map; 19 | } 20 | } 21 | 22 | return $new_data; 23 | } 24 | 25 | public static function qarray($data = []) 26 | { 27 | if (!is_array($data)) { 28 | $data = []; 29 | } 30 | 31 | $instance = ArrayQuery::getInstance(); 32 | 33 | return $instance->collect($data); 34 | } 35 | } -------------------------------------------------------------------------------- /src/ArrayQuery.php: -------------------------------------------------------------------------------- 1 | collect($data); 18 | } else { 19 | parent::__construct($data); 20 | } 21 | } 22 | 23 | public static function getInstance() 24 | { 25 | if (is_null(static::$instance)) { 26 | static::$instance = new static(); 27 | } 28 | 29 | return static::$instance; 30 | } 31 | 32 | public function readPath($file) 33 | { 34 | return '{}'; 35 | } 36 | 37 | public function parseData($data) 38 | { 39 | 40 | return $this->collect([]); 41 | } 42 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nahid/qarray", 3 | "description": "QArray is a PHP abstraction for querying array", 4 | "keywords": ["array", "query", "php"], 5 | "homepage": "https://github.com/nahid/qarray", 6 | "type": "library", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Nahid Bin Azhar", 11 | "email": "nahid.dns@gmail.com" 12 | } 13 | ], 14 | "require": { 15 | "php": ">=7.4", 16 | "myclabs/deep-copy": "^1.8", 17 | "ext-json": "*" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "^5.0 || ^6.0 || ^7.1", 21 | "symfony/var-dumper": "^6.3" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Nahid\\QArray\\": "src/" 26 | }, 27 | "files":[ 28 | "helpers/qarray.php" 29 | ] 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Nahid\\JsonQ\\Tests\\": "tests/" 34 | } 35 | }, 36 | "minimum-stability": "dev", 37 | "prefer-stable": true 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | tests: 11 | strategy: 12 | matrix: 13 | php-versions: [ '5.6', '7.0', '7.1', '7.2', '7.3', '7.4' ] 14 | fail-fast: false 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout latest revision 20 | uses: actions/checkout@v2 21 | 22 | - name: Install PHP with extensions 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: ${{ matrix.php-versions }} 26 | 27 | - name: Validate composer.json and composer.lock 28 | run: composer validate --strict 29 | 30 | - name: Cache Composer packages 31 | id: composer-cachez 32 | uses: actions/cache@v2 33 | with: 34 | path: vendor 35 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 36 | restore-keys: | 37 | ${{ runner.os }}-php- 38 | 39 | - name: Install dependencies 40 | run: composer install --prefer-dist --no-progress 41 | 42 | - name: Run PHPUnit tests 43 | run: ./vendor/bin/phpunit 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/ConditionFactory.php: -------------------------------------------------------------------------------- 1 | $comparable; 72 | } 73 | 74 | /** 75 | * Strict less than 76 | * 77 | * @param mixed $value 78 | * @param mixed $comparable 79 | * 80 | * @return bool 81 | */ 82 | public static function lessThan($value, $comparable) 83 | { 84 | return $value < $comparable; 85 | } 86 | 87 | /** 88 | * Greater or equal 89 | * 90 | * @param mixed $value 91 | * @param mixed $comparable 92 | * 93 | * @return bool 94 | */ 95 | public static function greaterThanOrEqual($value, $comparable) 96 | { 97 | return $value >= $comparable; 98 | } 99 | 100 | /** 101 | * Less or equal 102 | * 103 | * @param mixed $value 104 | * @param mixed $comparable 105 | * 106 | * @return bool 107 | */ 108 | public static function lessThanOrEqual($value, $comparable) 109 | { 110 | return $value <= $comparable; 111 | } 112 | 113 | /** 114 | * In array 115 | * 116 | * @param mixed $value 117 | * @param array $comparable 118 | * 119 | * @return bool 120 | */ 121 | public static function in($value, $comparable) 122 | { 123 | return (is_array($comparable) && in_array($value, $comparable)); 124 | } 125 | 126 | /** 127 | * Not in array 128 | * 129 | * @param mixed $value 130 | * @param array $comparable 131 | * 132 | * @return bool 133 | */ 134 | public static function notIn($value, $comparable) 135 | { 136 | return (is_array($comparable) && !in_array($value, $comparable)); 137 | } 138 | 139 | public static function inArray($value, $comparable) 140 | { 141 | if (!is_array($value)) return false; 142 | 143 | return in_array($comparable, $value); 144 | } 145 | 146 | public static function inNotArray($value, $comparable) 147 | { 148 | return !static::inArray($value, $comparable); 149 | } 150 | 151 | /** 152 | * Is null equal 153 | * 154 | * @param mixed $value 155 | * 156 | * @return bool 157 | */ 158 | public static function isNull($value, $comparable) 159 | { 160 | return is_null($value); 161 | } 162 | 163 | /** 164 | * Is not null equal 165 | * 166 | * @param mixed $value 167 | * 168 | * @return bool 169 | */ 170 | public static function isNotNull($value, $comparable) 171 | { 172 | return !$value instanceof KeyNotExists && !is_null($value); 173 | } 174 | 175 | public static function notExists($value, $comparable) 176 | { 177 | return $value instanceof KeyNotExists; 178 | } 179 | 180 | public static function exists($value, $comparable) 181 | { 182 | return !static::notExists($value, $comparable); 183 | } 184 | 185 | /** 186 | * Start With 187 | * 188 | * @param mixed $value 189 | * @param string $comparable 190 | * 191 | * @return bool 192 | */ 193 | public static function startWith($value, $comparable) 194 | { 195 | if (is_array($comparable) || is_array($value) || is_object($comparable) || is_object($value)) { 196 | return false; 197 | } 198 | 199 | if (preg_match("/^$comparable/", $value)) { 200 | return true; 201 | } 202 | 203 | return false; 204 | } 205 | 206 | /** 207 | * End with 208 | * 209 | * @param mixed $value 210 | * @param string $comparable 211 | * 212 | * @return bool 213 | */ 214 | public static function endWith($value, $comparable) 215 | { 216 | if (is_array($comparable) || is_array($value) || is_object($comparable) || is_object($value)) { 217 | return false; 218 | } 219 | 220 | if (preg_match("/$comparable$/", $value)) { 221 | return true; 222 | } 223 | 224 | return false; 225 | } 226 | 227 | /** 228 | * Match with pattern 229 | * 230 | * @param mixed $value 231 | * @param string $comparable 232 | * 233 | * @return bool 234 | */ 235 | public static function match($value, $comparable) 236 | { 237 | if (is_array($comparable) || is_array($value) || is_object($comparable) || is_object($value)) { 238 | return false; 239 | } 240 | 241 | $comparable = trim($comparable); 242 | 243 | if (preg_match("/^$comparable$/", $value)) { 244 | return true; 245 | } 246 | 247 | return false; 248 | } 249 | 250 | /** 251 | * Contains substring in string 252 | * 253 | * @param string $value 254 | * @param string $comparable 255 | * 256 | * @return bool 257 | */ 258 | public static function contains($value, $comparable) 259 | { 260 | return (strpos($value, $comparable) !== false); 261 | } 262 | 263 | /** 264 | * Dates equal 265 | * 266 | * @param string $value 267 | * @param string $comparable 268 | * 269 | * @return bool 270 | */ 271 | public static function dateEqual($value, $comparable, $format = 'Y-m-d') 272 | { 273 | $date = date($format, strtotime($value)); 274 | return $date == $comparable; 275 | } 276 | 277 | 278 | /** 279 | * is given value instance of value 280 | * 281 | * @param string $value 282 | * @param string $comparable 283 | * 284 | * @return bool 285 | */ 286 | public static function instance($value, $comparable) 287 | { 288 | return $value instanceof $comparable; 289 | } 290 | 291 | /** 292 | * is given value data type of value 293 | * 294 | * @param string $value 295 | * @param string $comparable 296 | * 297 | * @return bool 298 | */ 299 | public static function type($value, $comparable) 300 | { 301 | return gettype($value) === $comparable; 302 | } 303 | 304 | /** 305 | * is given value exits in given key of array 306 | * 307 | * @param string $value 308 | * @param string $comparable 309 | * 310 | * @return bool 311 | */ 312 | public static function any($value, $comparable) 313 | { 314 | if (is_array($value)) { 315 | return in_array($comparable, $value); 316 | } 317 | 318 | return false; 319 | } 320 | 321 | /** 322 | * is given value exits in given key of array 323 | * 324 | * @param string $value 325 | * @param string $comparable 326 | * 327 | * @return bool 328 | */ 329 | public static function execFunction($value, $comparable) 330 | { 331 | if (is_array($value)) { 332 | return in_array($comparable, $value); 333 | } 334 | 335 | return false; 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/QueryEngine.php: -------------------------------------------------------------------------------- 1 | collect($this->readPath($data)); 21 | 22 | } else { 23 | $this->collect($this->parseData($data)); 24 | } 25 | } 26 | 27 | /** 28 | * return json string when echoing the instance 29 | * 30 | * @return string 31 | * @throws ConditionNotAllowedException 32 | */ 33 | public function __toString(): string 34 | { 35 | return $this->toJson(); 36 | } 37 | 38 | /** 39 | * @param string $path 40 | * @return array 41 | */ 42 | public abstract function readPath(string $path); 43 | 44 | /** 45 | * @param string $data 46 | * @return array 47 | */ 48 | public abstract function parseData(string $data); 49 | 50 | /** 51 | * @param mixed $key 52 | * @return mixed 53 | * @throws KeyNotPresentException 54 | */ 55 | public function __get(mixed $key): mixed 56 | { 57 | if (isset($this->_data[$key]) or is_null($this->_data[$key])) { 58 | return $this->_data[$key]; 59 | } 60 | 61 | throw new KeyNotPresentException(); 62 | } 63 | 64 | /** 65 | * Property override for current object 66 | * 67 | * @param mixed $key 68 | * @param mixed $val 69 | */ 70 | public function __set(mixed $key, mixed $val): void 71 | { 72 | if (is_array($this->_data)) { 73 | $this->_data[$key] = $val; 74 | } 75 | } 76 | 77 | /** 78 | * @return array 79 | * @throws ConditionNotAllowedException 80 | */ 81 | public function __invoke(): array 82 | { 83 | return $this->toArray(); 84 | } 85 | 86 | /** 87 | * Implementation of ArrayAccess : check existence of the target offset 88 | * 89 | * @param mixed $offset 90 | * @return bool 91 | */ 92 | public function offsetExists(mixed $offset): bool 93 | { 94 | return isset($this->_data[$offset]); 95 | } 96 | 97 | /** 98 | * Implementation of ArrayAccess : Get the target offset 99 | * 100 | * @param mixed $offset 101 | * @return mixed|KeyNotExists 102 | */ 103 | public function offsetGet(mixed $offset): mixed 104 | { 105 | if ($this->offsetExists($offset)) { 106 | return $this->_data[$offset]; 107 | } 108 | 109 | return new KeyNotExists(); 110 | } 111 | 112 | /** 113 | * Implementation of ArrayAccess : Set the target offset 114 | * 115 | * @param mixed $offset 116 | * @param mixed $value 117 | */ 118 | public function offsetSet(mixed $offset, mixed $value): void 119 | { 120 | $this->_data[$offset] = $value; 121 | } 122 | 123 | /** 124 | * Implementation of ArrayAccess : Unset the target offset 125 | * 126 | * @param mixed $offset 127 | */ 128 | public function offsetUnset(mixed $offset): void 129 | { 130 | if ($this->offsetExists($offset)) { 131 | unset($this->_data[$offset]); 132 | } 133 | } 134 | 135 | /** 136 | * Implementation of Iterator : Rewind the Iterator to the first element 137 | * 138 | * @return void 139 | */ 140 | public function rewind(): void 141 | { 142 | reset($this->_data); 143 | } 144 | 145 | /** 146 | * Implementation of Iterator : Return the current element 147 | * @return mixed 148 | */ 149 | public function current(): mixed 150 | { 151 | $data = current($this->_data); 152 | if (!is_array($data)) { 153 | return $data; 154 | } 155 | 156 | $instance = new static(); 157 | 158 | return $instance->collect($data); 159 | } 160 | 161 | /** 162 | * Implementation of Iterator : Return the key of the current element 163 | * 164 | * @return mixed 165 | */ 166 | public function key(): mixed 167 | { 168 | return key($this->_data); 169 | } 170 | 171 | /** 172 | * Implementation of Iterator : Move forward to next element 173 | * 174 | * @return |void 175 | */ 176 | public function next(): void 177 | { 178 | next($this->_data); 179 | } 180 | 181 | /** 182 | * Implementation of Iterator : Checks if current position is valid 183 | * 184 | * @return bool 185 | */ 186 | public function valid(): bool 187 | { 188 | return key($this->_data) !== null; 189 | } 190 | 191 | /** 192 | * Deep copy current instance 193 | * 194 | * @param bool $fresh 195 | * @return QueryEngine 196 | */ 197 | public function copy($fresh = false) 198 | { 199 | if ($fresh) { 200 | $this->fresh([ 201 | '_data' => $this->_data, 202 | '_original' => $this->_original, 203 | '_traveler' => $this->_traveler, 204 | ]); 205 | } 206 | 207 | return deep_copy($this); 208 | } 209 | 210 | /** 211 | * Alias of from() method 212 | * 213 | * @param null $node 214 | * @return $this 215 | * @throws InvalidNodeException 216 | */ 217 | public function at($node = null) 218 | { 219 | return $this->from($node); 220 | } 221 | 222 | /** 223 | * getting prepared data 224 | * 225 | * @param array $column 226 | * @return QueryEngine 227 | * @throws ConditionNotAllowedException 228 | */ 229 | public function get($column = []) 230 | { 231 | if (!is_array($column)) { 232 | $column = func_get_args(); 233 | } 234 | 235 | $this->setSelect($column); 236 | $this->prepare(); 237 | return $this->makeResult($this->_data); 238 | } 239 | 240 | /** 241 | * alias of get method 242 | * 243 | * @param array $column 244 | * @return array|object 245 | * @throws ConditionNotAllowedException 246 | */ 247 | public function fetch($column = []) 248 | { 249 | if (!is_array($column)) { 250 | $column = func_get_args(); 251 | } 252 | 253 | return $this->get($column); 254 | } 255 | 256 | /** 257 | * check exists data from the query 258 | * 259 | * @return bool 260 | * @throws ConditionNotAllowedException 261 | */ 262 | public function exists() 263 | { 264 | $this->prepare(); 265 | 266 | return (!empty($this->_data) && !is_null($this->_data)); 267 | } 268 | 269 | /** 270 | * reset given data to the $_data 271 | * 272 | * @param mixed $data 273 | * @param bool $fresh 274 | * @return QueryEngine 275 | */ 276 | public function reset($data = null, $fresh = false) 277 | { 278 | if (is_null($data)) { 279 | $data = deep_copy($this->_original); 280 | } 281 | 282 | if ($fresh) { 283 | $self = new static(); 284 | $self->collect($data); 285 | 286 | return $self; 287 | } 288 | 289 | $this->collect($data); 290 | 291 | return $this; 292 | } 293 | 294 | /** 295 | * getting group data from specific column 296 | * 297 | * @param string $column 298 | * @return $this 299 | * @throws ConditionNotAllowedException 300 | */ 301 | public function groupBy($column) 302 | { 303 | $this->prepare(); 304 | 305 | $data = []; 306 | foreach ($this->_data as $map) { 307 | $value = $this->arrayGet($map, $column); 308 | if ($value) { 309 | $data[$value][] = $map; 310 | } 311 | } 312 | 313 | $this->_data = $data; 314 | return $this; 315 | } 316 | 317 | /** 318 | * Group by count from array value 319 | * 320 | * @param $column 321 | * @return $this 322 | */ 323 | public function countGroupBy($column): self 324 | { 325 | 326 | $this->prepare(); 327 | 328 | $data = []; 329 | foreach ($this->_data as $map) { 330 | $value = $this->arrayGet($map, $column); 331 | if (!$value) { 332 | continue; 333 | } 334 | 335 | if (isset($data[$value])) { 336 | $data[$value] ++; 337 | } else { 338 | $data[$value] = 1; 339 | } 340 | } 341 | 342 | $this->_data = $data; 343 | return $this; 344 | } 345 | 346 | 347 | /** 348 | * getting distinct data from specific column 349 | * 350 | * @param string $column 351 | * @return $this 352 | * @throws ConditionNotAllowedException 353 | */ 354 | public function distinct($column) 355 | { 356 | $this->prepare(); 357 | 358 | $data = []; 359 | foreach ($this->_data as $map) { 360 | $value = $this->arrayGet($map, $column); 361 | if ($value && !array_key_exists($value, $data)) { 362 | $data[$value] = $map; 363 | } 364 | } 365 | 366 | $this->_data = array_values($data); 367 | return $this; 368 | } 369 | 370 | 371 | /** 372 | * count prepared data 373 | * 374 | * @return int 375 | */ 376 | public function count(): int 377 | { 378 | $this->prepare(); 379 | 380 | return count($this->_data); 381 | } 382 | 383 | /** 384 | * size is an alias of count 385 | * 386 | * @return int 387 | * @throws ConditionNotAllowedException 388 | */ 389 | public function size(): int 390 | { 391 | return $this->count(); 392 | } 393 | 394 | /** 395 | * sum prepared data 396 | * @param int $column 397 | * @return number 398 | * @throws ConditionNotAllowedException 399 | */ 400 | public function sum($column = null) 401 | { 402 | $this->prepare(); 403 | $data = $this->_data; 404 | 405 | $sum = 0; 406 | if (is_null($column)) { 407 | $sum = array_sum($data); 408 | } else { 409 | foreach ($data as $key => $val) { 410 | $value = $this->arrayGet($val, $column); 411 | if (is_scalar($value)) { 412 | $sum += $value; 413 | } 414 | 415 | } 416 | } 417 | 418 | return $sum; 419 | } 420 | 421 | /** 422 | * getting max value from prepared data 423 | * 424 | * @param int $column 425 | * @return number 426 | * @throws ConditionNotAllowedException 427 | */ 428 | public function max($column = null) 429 | { 430 | $this->prepare(); 431 | $data = $this->_data; 432 | if (!is_null($column)) { 433 | $values = []; 434 | foreach ($data as $val) { 435 | $values[] = $this->arrayGet($val, $column); 436 | } 437 | 438 | $data = $values; 439 | } 440 | 441 | return max($data); 442 | } 443 | 444 | /** 445 | * getting min value from prepared data 446 | * 447 | * @param int $column 448 | * @return number 449 | * @throws ConditionNotAllowedException 450 | */ 451 | public function min($column = null) 452 | { 453 | $this->prepare(); 454 | $data = $this->_data; 455 | 456 | if (!is_null($column)) { 457 | $values = []; 458 | foreach ($data as $val) { 459 | $values[] = $this->arrayGet($val, $column); 460 | } 461 | 462 | $data = $values; 463 | } 464 | 465 | return min($data); 466 | } 467 | 468 | /** 469 | * getting average value from prepared data 470 | * 471 | * @param int $column 472 | * @return number 473 | * @throws ConditionNotAllowedException 474 | */ 475 | public function avg($column = null) 476 | { 477 | $this->prepare(); 478 | 479 | $count = $this->count(); 480 | $total = $this->sum($column); 481 | 482 | return ($total/$count); 483 | } 484 | 485 | /** 486 | * getting first element of prepared data 487 | * 488 | * @param array $column 489 | * @return object|array|null 490 | * @throws ConditionNotAllowedException 491 | */ 492 | public function first($column = []) 493 | { 494 | $this->prepare(); 495 | 496 | $data = $this->_data; 497 | $this->setSelect($column); 498 | 499 | if (!is_array($data)) return null; 500 | 501 | if (count($data) > 0) { 502 | $data = $this->toArray(); 503 | $this->_data = reset($data); 504 | return $this; 505 | } 506 | 507 | return null; 508 | } 509 | 510 | /** 511 | * getting last element of prepared data 512 | * 513 | * @param array $column 514 | * @return object|array|null 515 | * @throws ConditionNotAllowedException 516 | */ 517 | public function last($column = []) 518 | { 519 | $this->prepare(); 520 | 521 | $data = $this->_data; 522 | $this->setSelect($column); 523 | 524 | if (!is_array($data)) return null; 525 | 526 | if (count($data) > 0) { 527 | return $this->makeResult(end($data)); 528 | } 529 | 530 | return null; 531 | } 532 | 533 | /** 534 | * getting nth number of element of prepared data 535 | * 536 | * @param int $index 537 | * @param array $column 538 | * @return object|array|null 539 | * @throws ConditionNotAllowedException 540 | */ 541 | public function nth($index, $column = []) 542 | { 543 | $this->prepare(); 544 | 545 | $data = $this->_data; 546 | $this->setSelect($column); 547 | 548 | if (!is_array($data)) return null; 549 | 550 | $total_elm = count($data); 551 | $idx = abs($index); 552 | 553 | if (!is_integer($index) || $total_elm < $idx || $index == 0 || !is_array($this->_data)) { 554 | return null; 555 | } 556 | 557 | if ($index > 0) { 558 | $result = $data[$index - 1]; 559 | } else { 560 | $result = $data[$this->count() + $index]; 561 | } 562 | 563 | return $this->makeResult($result); 564 | } 565 | 566 | /** 567 | * sorting from prepared data 568 | * 569 | * @param string $column 570 | * @param string $order 571 | * @return object|array|null 572 | * @throws ConditionNotAllowedException 573 | */ 574 | public function sortBy($column, $order = 'asc') 575 | { 576 | $this->prepare(); 577 | 578 | if (!is_array($this->_data)) { 579 | return $this; 580 | } 581 | 582 | usort($this->_data, function ($a, $b) use ($column, $order) { 583 | $val1 = $this->arrayGet($a, $column); 584 | $val2 = $this->arrayGet($b, $column); 585 | if (is_string($val1)) { 586 | $val1 = strtolower($val1); 587 | } 588 | 589 | if (is_string($val2)) { 590 | $val2 = strtolower($val2); 591 | } 592 | 593 | if ($val1 == $val2) { 594 | return 0; 595 | } 596 | $order = strtolower(trim($order)); 597 | 598 | if ($order == 'desc') { 599 | return ($val1 > $val2) ? -1 : 1; 600 | } else { 601 | return ($val1 < $val2) ? -1 : 1; 602 | } 603 | }); 604 | 605 | return $this; 606 | } 607 | 608 | /** 609 | * Sort an array value 610 | * 611 | * @param string $order 612 | * @return QueryEngine 613 | */ 614 | public function sort($order = 'asc') 615 | { 616 | $this->_data = Utilities::toArray($this->_data); 617 | 618 | if ($order == 'desc') { 619 | rsort($this->_data); 620 | }else{ 621 | sort($this->_data); 622 | } 623 | 624 | return $this->makeResult($this->_data); 625 | 626 | } 627 | 628 | /** 629 | * getting data from desire path 630 | * 631 | * @param string $path 632 | * @param array $column 633 | * @return mixed 634 | * @throws InvalidNodeException 635 | * @throws ConditionNotAllowedException 636 | */ 637 | public function find($path, $column = []) 638 | { 639 | return $this->from($path)->prepare()->get($column); 640 | } 641 | 642 | /** 643 | * Get the raw data of result 644 | * 645 | * @return mixed 646 | */ 647 | public function result() 648 | { 649 | return $this->_data; 650 | } 651 | 652 | /** 653 | * take action of each element of prepared data 654 | * 655 | * @param callable $fn 656 | * @throws ConditionNotAllowedException 657 | */ 658 | public function each(callable $fn) 659 | { 660 | $this->prepare(); 661 | 662 | foreach ($this->_data as $key => $val) { 663 | $fn($key, $val); 664 | } 665 | } 666 | 667 | /** 668 | * transform prepared data by using callable function 669 | * 670 | * @param callable $fn 671 | * @return self 672 | * @throws ConditionNotAllowedException 673 | */ 674 | public function transform(callable $fn) 675 | { 676 | $this->prepare(); 677 | $data = []; 678 | 679 | foreach ($this->_data as $key => $val) { 680 | $data[$key] = $fn($val); 681 | } 682 | 683 | return $this->makeResult($data); 684 | } 685 | 686 | 687 | /** 688 | * map prepared data by using callable function for each entity 689 | * 690 | * @param callable $fn 691 | * @return object|array 692 | * @throws ConditionNotAllowedException 693 | */ 694 | public function map(callable $fn) 695 | { 696 | $this->prepare(); 697 | $data = []; 698 | 699 | foreach ($this->_data as $key => $val) { 700 | $data[] = $fn($key, $val); 701 | } 702 | 703 | return $this->makeResult($data); 704 | } 705 | 706 | /** 707 | * filtered each element of prepared data 708 | * 709 | * @param callable $fn 710 | * @param bool $key 711 | * @return mixed|array 712 | * @throws ConditionNotAllowedException 713 | */ 714 | public function filter(callable $fn, $key = false) 715 | { 716 | $this->prepare(); 717 | 718 | $data = []; 719 | foreach ($this->_data as $k => $val) { 720 | if ($fn($val)) { 721 | if ($key) { 722 | $data[$k] = $val; 723 | } else { 724 | $data[] = $val; 725 | } 726 | } 727 | } 728 | 729 | return $this->makeResult($data); 730 | } 731 | 732 | /** 733 | * then method set position of working data 734 | * 735 | * @param string $node 736 | * @return QueryEngine 737 | * @throws InvalidNodeException 738 | * @throws ConditionNotAllowedException 739 | */ 740 | public function then($node) 741 | { 742 | $this->prepare(); 743 | $this->from($node); 744 | 745 | return $this; 746 | } 747 | 748 | /** 749 | * implode resulting data from desire key and delimeter 750 | * 751 | * @param string|array $key 752 | * @param string $delimiter 753 | * @return self 754 | * @throws ConditionNotAllowedException 755 | */ 756 | public function implode($key, $delimiter = ',') 757 | { 758 | $this->prepare(); 759 | 760 | $implode = []; 761 | if (is_string($key)) { 762 | $implodedData[$key] = $this->makeImplode($key, $delimiter); 763 | return $this->makeResult($implodedData); 764 | } 765 | 766 | if (is_array($key)) { 767 | foreach ($key as $k) { 768 | $imp = $this->makeImplode($k, $delimiter); 769 | $implode[$k] = $imp; 770 | } 771 | 772 | return $this->makeResult($implode); 773 | } 774 | 775 | $implodedData[$key] = ''; 776 | return $this->makeResult($implodedData); 777 | } 778 | 779 | /** 780 | * process implode from resulting data 781 | * 782 | * @param string $key 783 | * @param string $delimiter 784 | * @return string|null 785 | * @throws \Exception 786 | */ 787 | protected function makeImplode($key, $delimiter = ',') 788 | { 789 | $data = array_column($this->toArray(), $key); 790 | 791 | if (is_array($data)) { 792 | return implode($delimiter, $data); 793 | } 794 | 795 | return ''; 796 | } 797 | 798 | /** 799 | * getting specific key's value from prepared data 800 | * 801 | * @param string $column 802 | * @param string|null $index 803 | * @return self 804 | * @throws ConditionNotAllowedException 805 | */ 806 | public function column($column, $index = null) 807 | { 808 | $this->prepare(); 809 | 810 | $data = array_column($this->_data, $column, $index); 811 | return $this->makeResult($data); 812 | } 813 | 814 | /** 815 | * getting raw JSON from prepared data 816 | * 817 | * @return string 818 | * @throws ConditionNotAllowedException 819 | */ 820 | public function toJson() 821 | { 822 | $this->prepare(); 823 | 824 | return json_encode($this->toArray()); 825 | } 826 | 827 | /** 828 | * @return mixed 829 | * @throws ConditionNotAllowedException 830 | */ 831 | public function toArray() 832 | { 833 | $this->prepare(); 834 | $maps = $this->_data; 835 | return Utilities::toArray($maps); 836 | } 837 | 838 | /** 839 | * getting all keys from prepared data 840 | * 841 | * @return object|array 842 | * @throws ConditionNotAllowedException 843 | */ 844 | public function keys() 845 | { 846 | $this->prepare(); 847 | 848 | return $this->makeResult(array_keys($this->_data)); 849 | } 850 | 851 | /** 852 | * getting all values from prepared data 853 | * 854 | * @return object|array 855 | * @throws ConditionNotAllowedException 856 | */ 857 | public function values() 858 | { 859 | $this->prepare(); 860 | 861 | return $this->makeResult(array_values($this->_data)); 862 | } 863 | 864 | /** 865 | * getting chunk values from prepared data 866 | * 867 | * @param int $amount 868 | * @param callable $fn 869 | * @return object|array|bool 870 | * @throws ConditionNotAllowedException 871 | */ 872 | public function chunk($amount, callable $fn = null) 873 | { 874 | $this->prepare(); 875 | 876 | $chunk_value = array_chunk($this->_data, $amount); 877 | $chunks = []; 878 | 879 | if (!is_null($fn) && is_callable($fn)) { 880 | foreach ($chunk_value as $chunk) { 881 | $return = $fn($chunk); 882 | if (!is_null($return)) { 883 | $chunks[] = $return; 884 | } 885 | } 886 | return count($chunks) > 0 ? $chunks : null; 887 | } 888 | 889 | return $chunk_value; 890 | } 891 | 892 | /** 893 | * Pluck is the alias of column 894 | * 895 | * @param $column 896 | * @param null $key 897 | * @return array|mixed|QueryEngine 898 | */ 899 | public function pluck($column, $key = null) 900 | { 901 | return $this->column($column, $key); 902 | } 903 | 904 | /** 905 | * Array pop from current result set 906 | * 907 | * @return array|mixed|QueryEngine 908 | */ 909 | public function pop() 910 | { 911 | $this->prepare(); 912 | 913 | $data = array_pop($this->_data); 914 | return $this->makeResult($data); 915 | } 916 | 917 | /** 918 | * Array shift from current result set 919 | * 920 | * @return array|mixed|QueryEngine 921 | */ 922 | public function shift() 923 | { 924 | $this->prepare(); 925 | 926 | $data = array_shift($this->_data); 927 | return $this->makeResult($data); 928 | } 929 | 930 | /** 931 | * Push the given data in current result set 932 | * 933 | * @param $data 934 | * @param null $key 935 | * @return $this 936 | */ 937 | public function push($data, $key = null) 938 | { 939 | $this->prepare(); 940 | 941 | if (is_null($key)) { 942 | $this->_data[] = $data; 943 | } else { 944 | $this->_data[$key] = $data; 945 | } 946 | 947 | return $this; 948 | } 949 | } 950 | -------------------------------------------------------------------------------- /src/Clause.php: -------------------------------------------------------------------------------- 1 | 'equal', 77 | 'eq' => 'equal', 78 | '==' => 'strictEqual', 79 | 'seq' => 'strictEqual', 80 | '!=' => 'notEqual', 81 | 'neq' => 'notEqual', 82 | '<>' => 'notEqual', 83 | '!==' => 'strictNotEqual', 84 | 'sneq' => 'strictNotEqual', 85 | '>' => 'greaterThan', 86 | 'gt' => 'greaterThan', 87 | '<' => 'lessThan', 88 | 'lt' => 'lessThan', 89 | '>=' => 'greaterThanOrEqual', 90 | 'gte' => 'greaterThanOrEqual', 91 | '<=' => 'lessThanOrEqual', 92 | 'lte' => 'lessThanOrEqual', 93 | 'in' => 'in', 94 | 'notin' => 'notIn', 95 | 'inarray' => 'inArray', 96 | 'notinarray' => 'notInArray', 97 | 'null' => 'isNull', 98 | 'notnull' => 'isNotNull', 99 | 'exists' => 'exists', 100 | 'notexists' => 'notExists', 101 | 'startswith' => 'startWith', 102 | 'endswith' => 'endWith', 103 | 'match' => 'match', 104 | 'contains' => 'contains', 105 | 'dates' => 'dateEqual', 106 | 'instance' => 'instance', 107 | 'type' => 'type', 108 | 'any' => 'any', 109 | ]; 110 | 111 | /** 112 | * @param array $props 113 | * @return $this 114 | */ 115 | public function fresh($props = []) 116 | { 117 | $properties = [ 118 | '_data' => [], 119 | '_original' => [], 120 | '_select' => [], 121 | '_isProcessed' => false, 122 | '_node' => '', 123 | '_except' => [], 124 | '_conditions' => [], 125 | '_take' => null, 126 | '_offset' => 0, 127 | '_traveler' => '.', 128 | ]; 129 | 130 | foreach ($properties as $property=>$value) { 131 | if (isset($props[$property])) { 132 | $value = $props[$property]; 133 | } 134 | 135 | $this->$property = $value; 136 | } 137 | 138 | return $this; 139 | } 140 | 141 | 142 | /** 143 | * import parsed data from raw json 144 | * 145 | * @param array|object $data 146 | * @return self 147 | */ 148 | public function collect($data) 149 | { 150 | $this->reProcess(); 151 | $this->fresh(); 152 | 153 | $this->_data = deep_copy($data); 154 | $this->_original = deep_copy($data); 155 | 156 | return $this; 157 | } 158 | 159 | 160 | /** 161 | * Prepare data from desire conditions 162 | * 163 | * @return $this 164 | */ 165 | protected function prepare() 166 | { 167 | if ($this->_isProcessed) { 168 | return $this; 169 | } 170 | 171 | if (count($this->_conditions) > 0) { 172 | $calculatedData = $this->processQuery(); 173 | if (!is_null($this->_take)) { 174 | $calculatedData = array_slice($calculatedData, $this->_offset, $this->_take); 175 | } 176 | 177 | $this->_data = $calculatedData; 178 | 179 | $this->_isProcessed = true; 180 | return $this; 181 | } 182 | 183 | $this->_isProcessed = true; 184 | if (!is_null($this->_take)) { 185 | $this->_data = array_slice($this->_data, $this->_offset, $this->_take); 186 | } 187 | 188 | $this->_data = $this->getData(); 189 | 190 | return $this; 191 | } 192 | 193 | /** 194 | * Our system will cache processed data and prevend multiple time processing. If 195 | * you want to reprocess this method can help you 196 | * 197 | * @return $this 198 | */ 199 | public function reProcess() 200 | { 201 | $this->_isProcessed = false; 202 | return $this; 203 | } 204 | 205 | /** 206 | * Parse object to array 207 | * 208 | * @param object $obj 209 | * @return array|mixed 210 | */ 211 | protected function objectToArray($obj) 212 | { 213 | if (!is_array($obj) && !is_object($obj)) { 214 | return $obj; 215 | } 216 | 217 | if (is_array($obj)) { 218 | return $obj; 219 | } 220 | 221 | if (is_object($obj)) { 222 | $obj = get_object_vars($obj); 223 | } 224 | 225 | return array_map([$this, 'objectToArray'], $obj); 226 | } 227 | 228 | /** 229 | * Check given value is multidimensional array 230 | * 231 | * @param array $arr 232 | * @return bool 233 | */ 234 | protected function isMultiArray($arr) 235 | { 236 | if (!is_array($arr)) { 237 | return false; 238 | } 239 | 240 | rsort($arr); 241 | 242 | return isset($arr[0]) && is_array($arr[0]); 243 | } 244 | 245 | 246 | /** 247 | * Check the given array is a collection 248 | * 249 | * @param $array 250 | * @return bool 251 | */ 252 | protected function isCollection($array) 253 | { 254 | if (!is_array($array)) return false; 255 | 256 | return array_keys($array) === range(0, count($array) - 1); 257 | } 258 | 259 | /** 260 | * Set node path, where QArray start to prepare 261 | * 262 | * @param null $node 263 | * @return $this 264 | * @throws InvalidNodeException 265 | */ 266 | public function from($node = null) 267 | { 268 | $this->_isProcessed = false; 269 | 270 | if (is_null($node) || $node == '') { 271 | throw new InvalidNodeException(); 272 | } 273 | 274 | $this->_node = $node; 275 | 276 | return $this; 277 | } 278 | 279 | /** 280 | * Taking desire columns from result 281 | * 282 | * @param $array 283 | * @return array 284 | */ 285 | public function takeColumn($array) 286 | { 287 | return $this->selectColumn($this->exceptColumn($array)); 288 | } 289 | 290 | /** 291 | * selecting specific column 292 | * 293 | * @param $array 294 | * @return array 295 | */ 296 | protected function selectColumn($array) 297 | { 298 | $keys = $this->_select; 299 | if (count($keys) == 0) { 300 | return $array; 301 | } 302 | 303 | $select = array_keys($keys); 304 | $columns = array_intersect_key($array, array_flip((array) $select)); 305 | $row = []; 306 | foreach ($columns as $column => $val) { 307 | $fn = null; 308 | if (array_key_exists($column, $keys)) { 309 | $fn = $keys[$column]; 310 | } 311 | 312 | if (is_callable($fn)) { 313 | $val = call_user_func_array($fn, [$val, $array]); 314 | } 315 | 316 | $row[$column] = $val; 317 | } 318 | 319 | return $row; 320 | } 321 | 322 | 323 | /** 324 | * selecting specific column 325 | * 326 | * @param $array 327 | * @return array 328 | */ 329 | protected function exceptColumn($array) 330 | { 331 | $keys = $this->_except; 332 | 333 | if (count($keys) == 0) { 334 | return $array; 335 | } 336 | 337 | return array_diff_key($array, array_flip((array) $keys)); 338 | } 339 | 340 | 341 | /** 342 | * select desired column 343 | * 344 | * @param array $columns 345 | * @return $this 346 | */ 347 | public function select($columns = []) 348 | { 349 | if (!is_array($columns)) { 350 | $columns = func_get_args(); 351 | } 352 | 353 | $this->setSelect($columns); 354 | 355 | return $this; 356 | } 357 | 358 | /** 359 | * setter for select columns 360 | * 361 | * @param array $columns 362 | */ 363 | protected function setSelect($columns = []) 364 | { 365 | if (count($columns) <= 0 ) { 366 | return; 367 | } 368 | 369 | foreach ($columns as $key => $column) { 370 | if (is_string($column)) { 371 | $this->_select[$column] = $key; 372 | } elseif(is_callable($column)) { 373 | $this->_select[$key] = $column; 374 | } else { 375 | $this->_select[$column] = $key; 376 | } 377 | } 378 | } 379 | 380 | /** 381 | * Set offset value for slice of array 382 | * 383 | * @param $offset 384 | * @return $this 385 | */ 386 | public function offset($offset) 387 | { 388 | $this->_offset = $offset; 389 | 390 | return $this; 391 | } 392 | 393 | /** 394 | * Set taken value for slice of array 395 | * 396 | * @param $take 397 | * @return $this 398 | */ 399 | public function take($take) 400 | { 401 | $this->_take = $take; 402 | 403 | return $this; 404 | } 405 | 406 | 407 | /** 408 | * select desired column for except 409 | * 410 | * @param array $columns 411 | * @return $this 412 | */ 413 | public function except($columns = []) 414 | { 415 | if (!is_array($columns)) { 416 | $columns = func_get_args(); 417 | } 418 | 419 | if (count($columns) > 0 ){ 420 | $this->_except = $columns; 421 | } 422 | 423 | return $this; 424 | } 425 | 426 | 427 | /** 428 | * Prepare data for result 429 | * 430 | * @param mixed $data 431 | * @param bool $newInstance 432 | * @return array|mixed 433 | */ 434 | protected function makeResult($data, $newInstance = false) 435 | { 436 | if (!$newInstance || is_null($data) || is_scalar($data) || !is_array($data)) { 437 | $this->_data = $data; 438 | return $this; 439 | } 440 | 441 | /* 442 | foreach ($data as $key => $val) { 443 | $output[$key] = $this->generateResultData($val); 444 | }*/ 445 | 446 | 447 | return $this->instanceWithValue($data, ['_select' => $this->_select, '_except' => $this->_except]); 448 | } 449 | 450 | /** 451 | * Create/Copy new instance with given value 452 | * 453 | * @param $value 454 | * @param array $meta 455 | * @return mixed 456 | */ 457 | protected function instanceWithValue($value, $meta = []) 458 | { 459 | $instance = new static(); 460 | $instance = $instance->collect($value); 461 | $instance->fresh($meta); 462 | 463 | return $instance; 464 | } 465 | 466 | /** 467 | * Set traveler delimiter 468 | * 469 | * @param $delimiter 470 | * @return $this 471 | */ 472 | public function setTraveler($delimiter) 473 | { 474 | $this->_traveler = $delimiter; 475 | 476 | return $this; 477 | } 478 | 479 | /** 480 | * Get data from nested array 481 | * 482 | * @param array $data 483 | * @param string $node 484 | * @param mixed $default 485 | * @return mixed 486 | */ 487 | protected function arrayGet($data, $node, $default = null) 488 | { 489 | if (empty($node) || $node == $this->_traveler) { 490 | return $data; 491 | } 492 | 493 | if (!$node) return new KeyNotExists(); 494 | 495 | if (isset($data[$node])) { 496 | return $data[$node]; 497 | } 498 | 499 | if (strpos($node, $this->_traveler) === false) { 500 | return $default; 501 | } 502 | 503 | $items = $data; 504 | 505 | foreach (explode($this->_traveler, $node) as $segment) { 506 | if (!is_array($items) || !isset($items[$segment])) { 507 | return $default; 508 | } 509 | 510 | $items = &$items[$segment]; 511 | } 512 | 513 | return $items; 514 | } 515 | /** 516 | * get data from node path 517 | * 518 | * @return mixed 519 | */ 520 | protected function getData() 521 | { 522 | return $this->arrayGet($this->_data, $this->_node); 523 | } 524 | 525 | /** 526 | * Process the given queries 527 | * 528 | * @return array 529 | * @throws ConditionNotAllowedException 530 | */ 531 | protected function processQuery() 532 | { 533 | $_data = $this->getData(); 534 | $conditions = $this->_conditions; 535 | 536 | /*return array_filter($data, function ($data) use ($conditions) { 537 | return $this->applyConditions($conditions, $data); 538 | });*/ 539 | 540 | $result = []; 541 | if (!is_array($_data)) return null; 542 | 543 | foreach ($_data as $key => $data) { 544 | $keep = $this->applyConditions($conditions, $data); 545 | if ($keep) { 546 | $result[$key] = $this->takeColumn($data); 547 | } 548 | } 549 | 550 | return $result; 551 | } 552 | 553 | /** 554 | * All the given conditions applied here 555 | * 556 | * @param $conditions 557 | * @param $data 558 | * @return bool 559 | * @throws ConditionNotAllowedException 560 | */ 561 | protected function applyConditions($conditions, $data) 562 | { 563 | $decision = false; 564 | foreach ($conditions as $cond) { 565 | $orDecision = true; 566 | $this->processEachCondition($cond, $data, $orDecision); 567 | $decision |= $orDecision; 568 | } 569 | 570 | return $decision; 571 | } 572 | 573 | /** 574 | * Apply every conditions for each row 575 | * 576 | * @param $rules 577 | * @param $data 578 | * @param $orDecision 579 | * @return bool|mixed 580 | * @throws ConditionNotAllowedException 581 | */ 582 | protected function processEachCondition($rules, $data, &$orDecision) 583 | { 584 | if (!is_array($rules)) return false; 585 | 586 | $andDecision = true; 587 | 588 | foreach ($rules as $rule) { 589 | $params = []; 590 | $function = null; 591 | 592 | $value = $this->arrayGet($data, $rule['key']); 593 | 594 | if (!is_callable($rule['condition'])) { 595 | $function = $this->makeConditionalFunctionFromOperator($rule['condition']); 596 | $params = [$value, $rule['value']]; 597 | } 598 | 599 | if (is_callable($rule['condition'])) { 600 | $function = $rule['condition']; 601 | $params = [$data]; 602 | } 603 | 604 | if ($value instanceof KeyNotExists) { 605 | $andDecision = false; 606 | } 607 | 608 | $andDecision = call_user_func_array($function, $params); 609 | 610 | /* 611 | if (! $value instanceof KeyNotExists) { 612 | $andDecision = call_user_func_array($function, $params); 613 | }*/ 614 | 615 | //$andDecision = $value instanceof KeyNotExists ? false : call_user_func_array($function, [$value, $rule['value']]); 616 | $orDecision &= $andDecision; 617 | } 618 | 619 | return $orDecision; 620 | 621 | } 622 | 623 | /** 624 | * Build or generate a function for applies condition from operator 625 | * @param $condition 626 | * @return array 627 | * @throws ConditionNotAllowedException 628 | */ 629 | protected function makeConditionalFunctionFromOperator($condition) 630 | { 631 | if (!isset(self::$_conditionsMap[$condition])) { 632 | throw new ConditionNotAllowedException("Exception: {$condition} condition not allowed"); 633 | } 634 | 635 | $function = self::$_conditionsMap[$condition]; 636 | if (!is_callable($function)) { 637 | if (!method_exists(ConditionFactory::class, $function)) { 638 | throw new ConditionNotAllowedException("Exception: {$condition} condition not allowed"); 639 | } 640 | 641 | $function = [ConditionFactory::class, $function]; 642 | } 643 | 644 | return $function; 645 | } 646 | 647 | /** 648 | * make WHERE clause 649 | * 650 | * @param string $key 651 | * @param string $condition 652 | * @param mixed $value 653 | * @return $this 654 | */ 655 | public function where($key, $condition = null, $value = null) 656 | { 657 | if (!is_null($condition) && is_null($value)) { 658 | $value = $condition; 659 | $condition = '='; 660 | } 661 | 662 | if (count($this->_conditions) < 1) { 663 | array_push($this->_conditions, []); 664 | } 665 | 666 | if (is_callable($key)) { 667 | $key($this); 668 | 669 | return $this; 670 | } 671 | 672 | return $this->makeWhere($key, $condition, $value); 673 | } 674 | 675 | /** 676 | * make WHERE clause with OR 677 | * 678 | * @param string $key 679 | * @param string $condition 680 | * @param mixed $value 681 | * @return $this 682 | */ 683 | public function orWhere($key = null, $condition = null, $value = null) 684 | { 685 | if (!is_null($condition) && is_null($value)) { 686 | $value = $condition; 687 | $condition = '='; 688 | } 689 | 690 | array_push($this->_conditions, []); 691 | 692 | if (is_callable($key)) { 693 | $key($this); 694 | 695 | return $this; 696 | } 697 | 698 | return $this->makeWhere($key, $condition, $value); 699 | } 700 | 701 | /** 702 | * make a callable where condition for custom logic implementation 703 | * 704 | * @param callable $fn 705 | * @return $this 706 | */ 707 | public function callableWhere(callable $fn) 708 | { 709 | if (count($this->_conditions) < 1) { 710 | array_push($this->_conditions, []); 711 | } 712 | 713 | return $this->makeWhere(null, $fn, null); 714 | } 715 | 716 | /** 717 | * make a callable orwhere condition for custom logic implementation 718 | * 719 | * @param callable $fn 720 | * @return $this\ 721 | */ 722 | public function orCallableWhere(callable $fn) 723 | { 724 | array_push($this->_conditions, []); 725 | $fn($this); 726 | return $this; 727 | //return $this->makeWhere(null, $fn, null); 728 | } 729 | 730 | /** 731 | * generator for AND and OR where 732 | * 733 | * @param string $key 734 | * @param string $condition 735 | * @param mixed $value 736 | * @return $this 737 | */ 738 | protected function makeWhere($key, $condition = null, $value = null) 739 | { 740 | $current = end($this->_conditions); 741 | $index = key($this->_conditions); 742 | 743 | array_push($current, [ 744 | 'key' => $key, 745 | 'condition' => $condition, 746 | 'value' => $value 747 | ]); 748 | 749 | $this->_conditions[$index] = $current; 750 | $this->_isProcessed = false; 751 | 752 | return $this; 753 | } 754 | 755 | /** 756 | * make WHERE IN clause 757 | * 758 | * @param string $key 759 | * @param array $value 760 | * @return $this 761 | */ 762 | public function whereIn($key = null, $value = []) 763 | { 764 | $this->where($key, 'in', $value); 765 | 766 | return $this; 767 | } 768 | 769 | /** 770 | * make WHERE DATA TYPE clause 771 | * 772 | * @param null $key 773 | * @param $value 774 | * @return $this 775 | */ 776 | public function whereDataType($key, $value) 777 | { 778 | $this->where($key, 'type', $value); 779 | 780 | return $this; 781 | } 782 | 783 | /** 784 | * make WHERE NOT IN clause 785 | * 786 | * @param string $key 787 | * @param mixed $value 788 | * @return $this 789 | */ 790 | public function whereNotIn($key = null, $value = []) 791 | { 792 | $this->where($key, 'notin', $value); 793 | return $this; 794 | } 795 | 796 | /** 797 | * check the given value are contains in the given array key 798 | * 799 | * @param $key 800 | * @param $value 801 | * @return $this 802 | */ 803 | public function whereInArray($key, $value) 804 | { 805 | $this->where($key, 'inarray', $value); 806 | return $this; 807 | } 808 | 809 | /** 810 | * make a callable wherenot condition for custom logic implementation 811 | * 812 | * @param $key 813 | * @param $value 814 | * @return $this 815 | */ 816 | public function whereNotInArray($key, $value) 817 | { 818 | $this->where($key, 'notinarray', $value); 819 | return $this; 820 | } 821 | 822 | /** 823 | * make WHERE NULL clause 824 | * 825 | * @param string $key 826 | * @return $this 827 | */ 828 | public function whereNull($key = null) 829 | { 830 | $this->where($key, 'null', 'null'); 831 | return $this; 832 | } 833 | 834 | 835 | /** 836 | * make WHERE Boolean clause 837 | * 838 | * @param string $key 839 | * @param mixed $value 840 | * @return $this 841 | */ 842 | public function whereBool($key, $value) 843 | { 844 | if (is_bool($value)) { 845 | $this->where($key, '==', $value); 846 | } 847 | return $this; 848 | } 849 | 850 | /** 851 | * make WHERE NOT NULL clause 852 | * 853 | * @param string $key 854 | * @return $this 855 | */ 856 | public function whereNotNull($key) 857 | { 858 | $this->where($key, 'notnull', 'null'); 859 | 860 | return $this; 861 | } 862 | 863 | /** 864 | * Check the given key is exists in row 865 | * 866 | * @param $key 867 | * @return $this 868 | */ 869 | public function whereExists($key) 870 | { 871 | $this->where($key, 'exists', 'null'); 872 | 873 | return $this; 874 | } 875 | 876 | /** 877 | * Check the given key is not exists in row 878 | * 879 | * @param $key 880 | * @return $this 881 | */ 882 | public function whereNotExists($key) 883 | { 884 | $this->where($key, 'notexists', 'null'); 885 | 886 | return $this; 887 | } 888 | 889 | /** 890 | * make WHERE START WITH clause 891 | * 892 | * @param string $key 893 | * @param string $value 894 | * @return $this 895 | */ 896 | public function whereStartsWith($key, $value) 897 | { 898 | $this->where($key, 'startswith', $value); 899 | 900 | return $this; 901 | } 902 | 903 | /** 904 | * make WHERE ENDS WITH clause 905 | * 906 | * @param string $key 907 | * @param string $value 908 | * @return $this 909 | */ 910 | public function whereEndsWith($key, $value) 911 | { 912 | $this->where($key, 'endswith', $value); 913 | 914 | return $this; 915 | } 916 | 917 | /** 918 | * make WHERE MATCH clause 919 | * 920 | * @param string $key 921 | * @param string $value 922 | * @return $this 923 | */ 924 | public function whereMatch($key, $value) 925 | { 926 | $this->where($key, 'match', $value); 927 | 928 | return $this; 929 | } 930 | 931 | /** 932 | * make WHERE CONTAINS clause 933 | * 934 | * @param string $key 935 | * @param string $value 936 | * @return $this 937 | */ 938 | public function whereContains($key, $value) 939 | { 940 | $this->where($key, 'contains', $value); 941 | 942 | return $this; 943 | } 944 | 945 | /** 946 | * make WHERE LIKE clause 947 | * 948 | * @param string $key 949 | * @param string $value 950 | * @return $this 951 | */ 952 | public function whereLike($key, $value) 953 | { 954 | $this->where($key, 'contains', strtolower($value)); 955 | 956 | return $this; 957 | } 958 | 959 | /** 960 | * make WHERE DATE clause 961 | * 962 | * @param string $key 963 | * @param string $condition 964 | * @param string $value 965 | * @return $this 966 | */ 967 | public function whereDate($key, $condition, $value = null) 968 | { 969 | return $this->callableWhere(function($row) use($key, $condition, $value) { 970 | $haystack = isset($row[$key]) ? $row[$key] : null; 971 | $haystack = date('Y-m-d', strtotime($haystack)); 972 | 973 | $function = $this->makeConditionalFunctionFromOperator($condition); 974 | 975 | return call_user_func_array($function, [$haystack, $value]); 976 | }); 977 | } 978 | 979 | /** 980 | * make WHERE Instance clause 981 | * 982 | * @param string $key 983 | * @param object|string $object 984 | * @return $this 985 | */ 986 | public function whereInstance($key, $object) 987 | { 988 | $this->where($key, 'instance', $object); 989 | 990 | return $this; 991 | } 992 | 993 | /** 994 | * make WHERE any clause 995 | * 996 | * @param string $key 997 | * @param mixed 998 | * @return $this 999 | */ 1000 | public function whereAny($key, $value) 1001 | { 1002 | $this->where($key, 'any', $value); 1003 | 1004 | return $this; 1005 | } 1006 | /** 1007 | * make WHERE any clause 1008 | * 1009 | * @param string $key 1010 | * @param mixed 1011 | * @param mixed 1012 | * @return $this 1013 | */ 1014 | public function whereCount($key, $condition, $value = null) 1015 | { 1016 | return $this->where($key, function($columnValue, $row) use ($value, $condition) { 1017 | $count = 0; 1018 | if (is_array($columnValue)) { 1019 | $count = count($columnValue); 1020 | } 1021 | 1022 | $function = $this->makeConditionalFunctionFromOperator($condition); 1023 | 1024 | return call_user_func_array($function, [$count, $value]); 1025 | }); 1026 | } 1027 | 1028 | /** 1029 | * make macro for custom where clause 1030 | * 1031 | * @param string $name 1032 | * @param callable $fn 1033 | * @return bool 1034 | */ 1035 | public static function macro($name, callable $fn) 1036 | { 1037 | if (!array_key_exists($name, self::$_conditionsMap)) { 1038 | self::$_conditionsMap[$name] = $fn; 1039 | return true; 1040 | } 1041 | 1042 | return false; 1043 | } 1044 | } 1045 | --------------------------------------------------------------------------------