├── .gitignore
├── LICENSE
├── composer.json
├── readme.md
└── src
├── Collection.php
├── Interfaces
├── Arrayable.php
├── JSONable.php
└── Stringable.php
└── Query.php
/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /vendor
3 |
4 | # Generated
5 | /tests/.tmp
6 | composer.lock
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Sultan Nasir Uddin
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sultann/wp-query-builder",
3 | "description": "WP Query Builder is a lightweight and efficient SQL Query Builder based on wpdb for WordPress. It supports complicated query generation.",
4 | "license": "MIT",
5 | "type": "library",
6 | "keywords": [
7 | "wordpress",
8 | "query builder",
9 | "database"
10 | ],
11 | "authors": [
12 | {
13 | "name": "MD Sultan Nasir Uddin",
14 | "email": "manikdrmc@gmail.com"
15 | }
16 | ],
17 | "require": {
18 | "php": ">=5.4"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "PluginEver\\QueryBuilder\\": "src"
23 | }
24 | },
25 | "minimum-stability": "dev"
26 | }
27 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | # WP Query Builder
8 | WP Query Builder is a lightweight and efficient SQL Query Builder based on wpdb for WordPress. It supports complicated query generation.
9 |
10 | [](https://travis-ci.org/sultann/wp-query-builder)
11 | [](https://packagist.org/packages/sultann/wp-query-builder)
12 | [](https://github.com/sultann/wp-query-builder/blob/master/LICENSE)
13 | [](https://github.com/sultann/wp-query-builder/releases)
14 |
15 | ## Installation
16 | WP Query Builder follows `PSR-4` autoloading and can be installed using composer:
17 |
18 | ```
19 | $ composer require sultann/wp-query-builder
20 | ```
21 |
22 | ## Documentation 💡
23 | ### Initialize
24 | Initialize query builder. init method takes a string argument using that later you can do action/filter based on your requirement.
25 | without argument.
26 | ```php
27 | $query = \PluginEver\QueryBuilder\Query::init();
28 | ```
29 | with argument
30 | ```php
31 | $query = \PluginEver\QueryBuilder\Query::init('query_users');
32 | ```
33 |
34 | ### Select
35 | This will build the query, execute and returns all users from users table with applying table prefix automatically.
36 | by default it select all(*) but you can define what to select from the query; If you are selecting all then you can omit the select statement.
37 | ```php
38 | $results = $query->select('*')
39 | ->from('users')
40 | ->get();
41 | ```
42 | Select specific column
43 | ```php
44 | $results = $query->select('ID')
45 | ->from('users')
46 | ->get();
47 | ```
48 | Select multiple column
49 | ```php
50 | $results = $query->select('user_login, user_email')
51 | ->from('users')
52 | ->get();
53 | ```
54 |
55 |
56 | ### Where conditions
57 | For the next few examples, lets assume a larger dataset so that the queries make sense.
58 | ```php
59 | $results = $query->select('*')
60 | ->from('users')
61 | ->where('user_email', 'like', '%gmail.com')
62 | ->get();
63 | ```
64 | Notice how omitting the operator in the first condition ->where('user_url', '') makes this default to =.
65 | By default all where conditions are defined with the and operator.
66 |
67 | Different where operators:
68 |
69 | ```php
70 | $results = $query->select('*')
71 | ->from('users')
72 | ->where('user_email', 'like', '%gmail.com')
73 | ->orWhere('user_email', 'like', '%yahoo.com')
74 | ->get();
75 | ```
76 |
77 | There are few more builtin Where conditions available
78 | - `andWhere()`
79 | - `whereIn()`
80 | - `whereNotIn()`
81 | - `whereNull()`
82 | - `whereNotNull()`
83 | - `orWhereNull()`
84 | - `orWhereNotNull()`
85 | - `whereBetween()`
86 | - `whereNotBetween()`
87 | - `whereDateBetween()`
88 | - `whereRaw()`
89 |
90 | #### Where scopes
91 | Allow you to group conditions:
92 |
93 | ```php
94 | $results = $query->select('*')
95 | ->from('posts')
96 | ->where('post_status', 'publish')
97 | ->where(function($q)
98 | {
99 | $q->where('menu_order', '>', 21);
100 | $q->where('menu_order', '<', 99);
101 | })
102 | ->orWhere('post_type', 'page')
103 | ->get();
104 | ```
105 |
106 | Where Between
107 | ```php
108 | $results = $query->select('*')
109 | ->from('posts')
110 | ->whereBetween('menu_order', 1, 20)
111 | ->get();
112 | ```
113 |
114 | Where Not Between
115 | ```php
116 | $results = $query->select('*')
117 | ->from('posts')
118 | ->whereNotBetween('menu_order', 20, 30)
119 | ->get();
120 | ```
121 |
122 | Where Date Between
123 | ```php
124 | $results = $query->select('*')
125 | ->from('posts')
126 | ->whereDateBetween('post_date', '2010-04-22 10:16:21', '2020-05-04')
127 | ->get();
128 | ```
129 |
130 | ### Joins
131 | By default, all joins are Left Join. Available join types 'LEFT', 'RIGHT', 'INNER', 'CROSS', 'LEFT OUTER', 'RIGHT OUTER'
132 | Joining tables:
133 | ```php
134 | $results = $query->select( '*' )
135 | ->from( 'posts p' )
136 | ->join( 'users u', 'u.ID', '=','p.post_author' )
137 | ->get();
138 | ```
139 |
140 | #### Joins scopes
141 | Allow you to group conditions:
142 | ```php
143 | $results = $query->select( '*' )
144 | ->from( 'posts p' )
145 | ->join( 'users u', 'u.ID', '=','p.post_author' )
146 | ->join('usermeta um', function($q) {
147 | $q->where('um.meta_key', 'first_name');
148 | $q->where('um.met_value', 'like', '%sultan%');
149 | })
150 | ->get();
151 | ```
152 | There are few more builtin join conditions available
153 | - `leftJoin()`
154 | - `rightJoin()`
155 | - `innerJoin()`
156 | - `outerJoin()`
157 |
158 | ### Grouping
159 |
160 | Grouping data:
161 | ```php
162 | $results = $query->select('*')
163 | ->from('posts')
164 | ->group_by('post_status')
165 | ->get();
166 | ```
167 |
168 | ### Having
169 | Group by with having data:
170 | ```php
171 | $results = $query->select('*')
172 | ->from('posts')
173 | ->group_by('post_status')
174 | ->having('count(ID)>1')
175 | ->get();
176 | ```
177 |
178 | ### Ordering
179 | Ordering data:
180 | ```php
181 | $results = $query->select('*')
182 | ->from('posts')
183 | ->order_by('post_title', 'DESC')
184 | ->get();
185 | ```
186 |
187 | ### Limiting data
188 | Limit and offset:
189 | ```php
190 | $results = $query->select('*')
191 | ->from('posts')
192 | ->limit(20, 10)
193 | ->get();
194 | ```
195 | Only limit
196 | ```php
197 | $results = $query->select('*')
198 | ->from('posts')
199 | ->limit(20)
200 | ->get();
201 | ```
202 | Offset as separate
203 | ```php
204 | $results = $query->select('*')
205 | ->from('posts')
206 | ->limit(20)
207 | ->offset(10)
208 | ->get();
209 | ```
210 |
211 | ### Pagination
212 | shortcut of limit and offset
213 | ```php
214 | $results = $query->select('*')
215 | ->from('posts')
216 | ->page(1, 20)//page number & page size
217 | ->get();
218 | ```
219 |
220 | ### Find
221 | find item with column value
222 | ```php
223 | $results = $query->select('*')
224 | ->from('posts')
225 | ->find(1, 'ID');
226 | ```
227 |
228 |
229 | ### First
230 | Get first item from the posts table
231 | ```php
232 | $results = $query->select('*')
233 | ->from('posts')
234 | ->first();
235 | ```
236 |
237 | ### Last
238 | Get last item from the posts table
239 | ```php
240 | $results = $query->select('*')
241 | ->from('posts')
242 | ->last();
243 | ```
244 |
245 | ### Counting
246 | count total rows
247 | ```php
248 | $results = $query->select('*')
249 | ->from('posts')
250 | ->count();
251 | ```
252 |
253 | ### toSql
254 | Out the query instead of executing
255 | ```php
256 | $results = $query->from('posts as p')
257 | ->join('users as u', 'p.post_author', 'u.ID')
258 | ->join('usermeta um', function($q) {
259 | $q->where('um.meta_key', 'first_name');
260 | $q->where('um.met_value', 'like', '%sultan%');
261 | })
262 | ->toSql();
263 | ```
264 |
265 | ### Update
266 | Update a row
267 | ```php
268 | $results = $query->table('posts')
269 | ->where('ID', 20)
270 | ->update(['post_title' => 'updated']);
271 | ```
272 |
273 | ### Delete
274 | Delete a row
275 | ```php
276 | $results = $query->from('posts')
277 | ->where('ID', 20)
278 | ->delete();
279 | ```
280 |
281 | ### Search
282 | Search a value from columns
283 | ```php
284 | $results = $query->from('posts')
285 | ->search('Hello Word', array('post_title', 'post_content')) // it will search Hello & World both
286 | ->delete();
287 | ```
288 |
289 | ## License
290 |
291 | The MIT License (MIT). Please see [License File](https://github.com/sultann/wp-query-builder/blob/master/LICENSE) for more information.
--------------------------------------------------------------------------------
/src/Collection.php:
--------------------------------------------------------------------------------
1 | getArrayableItems( $items );
29 |
30 | $this->items = (array) $items;
31 | }
32 |
33 | /**
34 | * Create a new collection instance if the value isn't one already.
35 | *
36 | * @param mixed $items
37 | *
38 | * @return static
39 | */
40 | public static function make( $items = null ) {
41 | return new static( $items );
42 | }
43 |
44 | /**
45 | * Get all of the items in the collection.
46 | *
47 | * @return array
48 | */
49 | public function all() {
50 | return $this->items;
51 | }
52 |
53 |
54 | /**
55 | * Diff the collection with the given items.
56 | *
57 | * @param Arrayable|array $items
58 | *
59 | * @return static
60 | */
61 | public function diff( $items ) {
62 | return new static( array_diff( $this->items, $this->getArrayableItems( $items ) ) );
63 | }
64 |
65 | /**
66 | * Execute a callback over each item.
67 | *
68 | * @param callable $callback
69 | *
70 | * @return static
71 | */
72 | public function each( callable $callback ) {
73 | return new static( array_map( $callback, $this->items ) );
74 | }
75 |
76 | /**
77 | * Run a filter over each of the items.
78 | *
79 | * @param callable $callback
80 | *
81 | * @return static
82 | */
83 | public function filter( callable $callback ) {
84 | return new static( array_filter( $this->items, $callback ) );
85 | }
86 |
87 | /**
88 | * Filter items by the given key value pair.
89 | *
90 | * @param string $key
91 | * @param mixed $value
92 | * @param bool $strict
93 | *
94 | * @return static
95 | */
96 | public function where( $key, $value, $strict = true ) {
97 | return $this->filter( function ( $item ) use ( $key, $value, $strict ) {
98 | return $strict ? self::data_get( $item, $key ) === $value
99 | : self::data_get( $item, $key ) == $value;
100 | } );
101 | }
102 |
103 | /**
104 | * Filter items by the given key value pair using loose comparison.
105 | *
106 | * @param string $key
107 | * @param mixed $value
108 | *
109 | * @return static
110 | */
111 | public function whereLoose( $key, $value ) {
112 | return $this->where( $key, $value, false );
113 | }
114 |
115 | /**
116 | * Flip the items in the collection.
117 | *
118 | * @return static
119 | */
120 | public function flip() {
121 | return new static( array_flip( $this->items ) );
122 | }
123 |
124 | /**
125 | * Remove an item from the collection by key.
126 | *
127 | * @param mixed $key
128 | *
129 | * @return void
130 | */
131 | public function forget( $key ) {
132 | $this->offsetUnset( $key );
133 | }
134 |
135 | /**
136 | * Get an item from the collection by key.
137 | *
138 | * @param mixed $key
139 | * @param mixed $default
140 | *
141 | * @return mixed
142 | */
143 | public function get( $key, $default = null ) {
144 | if ( $this->offsetExists( $key ) ) {
145 | return $this->items[ $key ];
146 | }
147 |
148 | return $default instanceof \Closure ? $default() : $default;
149 | }
150 |
151 | /**
152 | * Determine if an item exists in the collection by key.
153 | *
154 | * @param mixed $key
155 | *
156 | * @return bool
157 | */
158 | public function has( $key ) {
159 | return $this->offsetExists( $key );
160 | }
161 |
162 | /**
163 | * Intersect the collection with the given items.
164 | *
165 | * @param Arrayable|array $items
166 | *
167 | * @return static
168 | */
169 | public function intersect( $items ) {
170 | return new static( array_intersect( $this->items, $this->getArrayableItems( $items ) ) );
171 | }
172 |
173 | /**
174 | * Determine if the collection is empty or not.
175 | *
176 | * @return bool
177 | */
178 | public function isEmpty() {
179 | return empty( $this->items );
180 | }
181 |
182 | /**
183 | * Determine if the given value is callable, but not a string.
184 | *
185 | * @param mixed $value
186 | *
187 | * @return bool
188 | */
189 | protected function useAsCallable( $value ) {
190 | return ! is_string( $value ) && is_callable( $value );
191 | }
192 |
193 | /**
194 | * Get the keys of the collection items.
195 | *
196 | * @return static
197 | */
198 | public function keys() {
199 | return new static( array_keys( $this->items ) );
200 | }
201 |
202 | /**
203 | * Get the last item from the collection.
204 | *
205 | * @return mixed|null
206 | */
207 | public function last() {
208 | return count( $this->items ) > 0 ? end( $this->items ) : null;
209 | }
210 |
211 | /**
212 | * Run a map over each of the items.
213 | *
214 | * @param callable $callback
215 | *
216 | * @return static
217 | */
218 | public function map( callable $callback ) {
219 | return new static( array_map( $callback, $this->items, array_keys( $this->items ) ) );
220 | }
221 |
222 | /**
223 | * Merge the collection with the given items.
224 | *
225 | * @param Arrayable|array $items
226 | *
227 | * @return static
228 | */
229 | public function merge( $items ) {
230 | return new static( array_merge( $this->items, $this->getArrayableItems( $items ) ) );
231 | }
232 |
233 | /**
234 | * Get and remove the last item from the collection.
235 | *
236 | * @return mixed|null
237 | */
238 | public function pop() {
239 | return array_pop( $this->items );
240 | }
241 |
242 | /**
243 | * Push an item onto the beginning of the collection.
244 | *
245 | * @param mixed $value
246 | *
247 | * @return void
248 | */
249 | public function prepend( $value ) {
250 | array_unshift( $this->items, $value );
251 | }
252 |
253 | /**
254 | * Push an item onto the end of the collection.
255 | *
256 | * @param mixed $value
257 | *
258 | * @return void
259 | */
260 | public function push( $value ) {
261 | $this->offsetSet( null, $value );
262 | }
263 |
264 | /**
265 | * Put an item in the collection by key.
266 | *
267 | * @param mixed $key
268 | * @param mixed $value
269 | *
270 | * @return void
271 | */
272 | public function put( $key, $value ) {
273 | $this->offsetSet( $key, $value );
274 | }
275 |
276 | /**
277 | * Get one or more items randomly from the collection.
278 | *
279 | * @param int $amount
280 | *
281 | * @return mixed
282 | */
283 | public function random( $amount = 1 ) {
284 | if ( $this->isEmpty() ) {
285 | return;
286 | }
287 |
288 | $keys = array_rand( $this->items, $amount );
289 |
290 | return is_array( $keys ) ? array_intersect_key( $this->items, array_flip( $keys ) ) : $this->items[ $keys ];
291 | }
292 |
293 | /**
294 | * Reduce the collection to a single value.
295 | *
296 | * @param callable $callback
297 | * @param mixed $initial
298 | *
299 | * @return mixed
300 | */
301 | public function reduce( callable $callback, $initial = null ) {
302 | return array_reduce( $this->items, $callback, $initial );
303 | }
304 |
305 | /**
306 | * Create a collection of all elements that do not pass a given truth test.
307 | *
308 | * @param callable|mixed $callback
309 | *
310 | * @return static
311 | */
312 | public function reject( $callback ) {
313 | if ( $this->useAsCallable( $callback ) ) {
314 | return $this->filter( function ( $item ) use ( $callback ) {
315 | return ! $callback( $item );
316 | } );
317 | }
318 |
319 | return $this->filter( function ( $item ) use ( $callback ) {
320 | return $item != $callback;
321 | } );
322 | }
323 |
324 | /**
325 | * Reverse items order.
326 | *
327 | * @return static
328 | */
329 | public function reverse() {
330 | return new static( array_reverse( $this->items ) );
331 | }
332 |
333 | /**
334 | * Search the collection for a given value and return the corresponding key if successful.
335 | *
336 | * @param mixed $value
337 | * @param bool $strict
338 | *
339 | * @return mixed
340 | */
341 | public function search( $value, $strict = false ) {
342 | if ( ! $this->useAsCallable( $value ) ) {
343 | return array_search( $value, $this->items, $strict );
344 | }
345 |
346 | foreach ( $this->items as $key => $item ) {
347 | if ( $value( $item, $key ) ) {
348 | return $key;
349 | }
350 | }
351 |
352 | return false;
353 | }
354 |
355 | /**
356 | * Get and remove the first item from the collection.
357 | *
358 | * @return mixed|null
359 | */
360 | public function shift() {
361 | return array_shift( $this->items );
362 | }
363 |
364 | /**
365 | * Shuffle the items in the collection.
366 | *
367 | * @return $this
368 | */
369 | public function shuffle() {
370 | shuffle( $this->items );
371 |
372 | return $this;
373 | }
374 |
375 | /**
376 | * Slice the underlying collection array.
377 | *
378 | * @param int $offset
379 | * @param int $length
380 | * @param bool $preserveKeys
381 | *
382 | * @return static
383 | */
384 | public function slice( $offset, $length = null, $preserveKeys = false ) {
385 | return new static( array_slice( $this->items, $offset, $length, $preserveKeys ) );
386 | }
387 |
388 | /**
389 | * Chunk the underlying collection array.
390 | *
391 | * @param int $size
392 | * @param bool $preserveKeys
393 | *
394 | * @return static
395 | */
396 | public function chunk( $size, $preserveKeys = false ) {
397 | $chunks = [];
398 |
399 | foreach ( array_chunk( $this->items, $size, $preserveKeys ) as $chunk ) {
400 | $chunks[] = new static( $chunk );
401 | }
402 |
403 | return new static( $chunks );
404 | }
405 |
406 | /**
407 | * Sort through each item with a callback.
408 | *
409 | * @param callable $callback
410 | *
411 | * @return $this
412 | */
413 | public function sort( callable $callback ) {
414 | uasort( $this->items, $callback );
415 |
416 | return $this;
417 | }
418 |
419 | /**
420 | * Splice portion of the underlying collection array.
421 | *
422 | * @param int $offset
423 | * @param int $length
424 | * @param mixed $replacement
425 | *
426 | * @return static
427 | */
428 | public function splice( $offset, $length = 0, $replacement = [] ) {
429 | return new static( array_splice( $this->items, $offset, $length, $replacement ) );
430 | }
431 |
432 | /**
433 | * Take the first or last {$limit} items.
434 | *
435 | * @param int $limit
436 | *
437 | * @return static
438 | */
439 | public function take( $limit = null ) {
440 | if ( $limit < 0 ) {
441 | return $this->slice( $limit, abs( $limit ) );
442 | }
443 |
444 | return $this->slice( 0, $limit );
445 | }
446 |
447 | /**
448 | * Transform each item in the collection using a callback.
449 | *
450 | * @param callable $callback
451 | *
452 | * @return $this
453 | */
454 | public function transform( callable $callback ) {
455 | $this->items = array_map( $callback, $this->items );
456 |
457 | return $this;
458 | }
459 |
460 | /**
461 | * Return only unique items from the collection array.
462 | *
463 | * @return static
464 | */
465 | public function unique() {
466 | return new static( array_unique( $this->items ) );
467 | }
468 |
469 | /**
470 | * Reset the keys on the underlying array.
471 | *
472 | * @return static
473 | */
474 | public function values() {
475 | return new static( array_values( $this->items ) );
476 | }
477 |
478 | /**
479 | * Count the number of items in the collection.
480 | *
481 | * @return int
482 | */
483 | public function count() {
484 | return count( $this->items );
485 | }
486 |
487 | /**
488 | * Determine if an item exists at an offset.
489 | *
490 | * @param mixed $key
491 | *
492 | * @return bool
493 | */
494 | public function offsetExists( $key ) {
495 | return array_key_exists( $key, $this->items );
496 | }
497 |
498 | /**
499 | * Get an item at a given offset.
500 | *
501 | * @param mixed $key
502 | *
503 | * @return mixed
504 | */
505 | public function offsetGet( $key ) {
506 | return $this->items[ $key ];
507 | }
508 |
509 | /**
510 | * Set the item at a given offset.
511 | *
512 | * @param mixed $key
513 | * @param mixed $value
514 | *
515 | * @return void
516 | */
517 | public function offsetSet( $key, $value ) {
518 | if ( is_null( $key ) ) {
519 | $this->items[] = $value;
520 | } else {
521 | $this->items[ $key ] = $value;
522 | }
523 | }
524 |
525 | /**
526 | * Unset the item at a given offset.
527 | *
528 | * @param string $key
529 | *
530 | * @return void
531 | */
532 | public function offsetUnset( $key ) {
533 | unset( $this->items[ $key ] );
534 | }
535 |
536 |
537 | /**
538 | * Get an iterator for the items.
539 | *
540 | * @return \ArrayIterator
541 | */
542 | public function getIterator() {
543 | return new \ArrayIterator( $this->items );
544 | }
545 |
546 | /**
547 | * Returns collection as pure array.
548 | * Does depth array casting.
549 | * @return array
550 | * @since 1.0.2
551 | *
552 | */
553 | public function __toArray() {
554 | $output = [];
555 | $value = null;
556 | foreach ( $this as $key => $value ) {
557 | $output[ $key ] = ! is_object( $value )
558 | ? $value
559 | : ( method_exists( $value, '__toArray' )
560 | ? $value->__toArray()
561 | : (array) $value
562 | );
563 | }
564 |
565 | return $output;
566 | }
567 |
568 | /**
569 | * Returns collection as pure array.
570 | * Does depth array casting.
571 | * @return array
572 | * @since 1.0.2
573 | *
574 | */
575 | public function toArray() {
576 | return $this->__toArray();
577 | }
578 |
579 | /**
580 | * Returns collection as a string.
581 | *
582 | * @param string
583 | *
584 | * @since 1.0.2
585 | *
586 | */
587 | public function __toString() {
588 | return json_encode( $this->__toArray() );
589 | }
590 |
591 | /**
592 | * Returns object as JSON string.
593 | *
594 | * @param int $options JSON encoding options. See @link.
595 | * @param int $depth JSON encoding depth. See @link.
596 | *
597 | * @return string
598 | * @link http://php.net/manual/en/function.json-encode.php
599 | *
600 | * @since 1.0.2
601 | *
602 | */
603 | public function __toJSON( $options = 0, $depth = 512 ) {
604 | return json_encode( $this->__toArray(), $options, $depth );
605 | }
606 |
607 | /**
608 | * Returns object as JSON string.
609 | *
610 | * @param int $options JSON encoding options. See @link.
611 | * @param int $depth JSON encoding depth. See @link.
612 | *
613 | * @return string
614 | * @link http://php.net/manual/en/function.json-encode.php
615 | *
616 | * @since 1.0.2
617 | *
618 | */
619 | public function toJSON( $options = 0, $depth = 512 ) {
620 | return $this->__toJSON( $options, $depth );
621 | }
622 |
623 | /**
624 | * @param $target
625 | * @param $key
626 | * @param null $default
627 | *
628 | * @return array
629 | * @since
630 | */
631 | public static function data_get( $target, $key, $default = null ) {
632 | if ( is_null( $key ) ) {
633 | return $target;
634 | }
635 |
636 | $key = is_array( $key ) ? $key : explode( '.', $key );
637 |
638 | foreach ( $key as $i => $segment ) {
639 | unset( $key[ $i ] );
640 |
641 | if ( is_null( $segment ) ) {
642 | return $target;
643 | }
644 |
645 | if ( $segment === '*' ) {
646 | if ( $target instanceof Collection ) {
647 | $target = $target->all();
648 | } elseif ( ! is_array( $target ) ) {
649 | return $default instanceof \Closure ? $default() : $default;
650 | }
651 | $result = [];
652 |
653 | foreach ( $target as $item ) {
654 | $result[] = self::data_get( $item, $key );
655 | }
656 |
657 | return in_array( '*', $key ) ? $result : $result;
658 | }
659 |
660 | if ( array_key_exists( $segment, $target ) ) {
661 | $target = $target[ $segment ];
662 | } elseif ( is_object( $target ) && isset( $target->{$segment} ) ) {
663 | $target = $target->{$segment};
664 | } else {
665 | return $default instanceof \Closure ? $default() : $default;
666 | }
667 | }
668 |
669 | return $target;
670 | }
671 |
672 | /**
673 | * Results array of items from Collection or Arrayable.
674 | *
675 | * @param $items
676 | *
677 | * @return mixed
678 | * @since 1.0.0
679 | */
680 | protected function getArrayableItems( $items ) {
681 | if ( $items instanceof Collection ) {
682 | $items = $items->all();
683 | } elseif ( $items instanceof Arrayable ) {
684 | $items = $items->toArray();
685 | }
686 |
687 | return $items;
688 | }
689 | }
--------------------------------------------------------------------------------
/src/Interfaces/Arrayable.php:
--------------------------------------------------------------------------------
1 | id = ! empty( $id ) ? $id : uniqid();
68 |
69 | return $builder;
70 | }
71 |
72 | /**
73 | * Adds select statement.
74 | *
75 | * @param $statement
76 | *
77 | * @return $this
78 | * @since 1.0.0
79 | */
80 | public function select( $statement ) {
81 | $this->select[] = $statement;
82 |
83 | return $this;
84 | }
85 |
86 | /**
87 | * Adds from statement.
88 | *
89 | * @param string $name
90 | * @param bool $add_prefix
91 | *
92 | * @since 1.0.0
93 | *
94 | */
95 | public function table( $name, $add_prefix = true ) {
96 | global $wpdb;
97 | $table = ( $add_prefix ? $wpdb->prefix : '' ) . $name;
98 | $this->from = $table;
99 |
100 | return $this;
101 | }
102 |
103 | /**
104 | * Adds from statement.
105 | *
106 | * @param string $from
107 | * @param bool $add_prefix Should DB prefix be added.
108 | *
109 | * @return Query this for chaining.
110 | * @global object $wpdb
111 | *
112 | * @since 1.0.0
113 | *
114 | */
115 | public function from( $from, $add_prefix = true ) {
116 | global $wpdb;
117 | $this->from = $this->from . ' ' . ( $add_prefix ? $wpdb->prefix : '' ) . $from;
118 |
119 | return $this;
120 | }
121 |
122 | /**
123 | * Adds search statement.
124 | *
125 | * @param $search
126 | * @param $columns
127 | * @param $joint
128 | *
129 | * @since 1.0.0
130 | */
131 | public function search( $search, $columns, $joint = 'AND' ) {
132 | if ( ! empty( $search ) ) {
133 | global $wpdb;
134 | foreach ( explode( ' ', $search ) as $word ) {
135 | $word = '%' . $this->sanitize_value( true, $word ) . '%';
136 | $this->where[] = [
137 | 'joint' => $joint,
138 | 'condition' => '(' . implode( ' OR ', array_map( function ( $column ) use ( &$wpdb, &$word ) {
139 | return $wpdb->prepare( $column . ' LIKE %s', $word );
140 | }, $columns ) ) . ')',
141 | ];
142 | }
143 | }
144 |
145 | return $this;
146 | }
147 |
148 | /**
149 | * Create a where statement.
150 | *
151 | * ->where('name', 'sultan')
152 | * ->where('age', '>', 18)
153 | * ->where('name', 'in', array('ayaan', 'ayaash', 'anaan'))
154 | * ->where(function($q){
155 | * $q->where('ID', '>', 21);
156 | * })
157 | *
158 | * @param string $column The SQL column
159 | * @param mixed $param1 Operator or value depending if $param2 isset.
160 | * @param mixed $param2 The value if $param1 is an operator.
161 | * @param string $joint the where type ( and, or )
162 | *
163 | * @return Query The current query builder.
164 | */
165 | public function where( $column, $param1 = null, $param2 = null, $joint = 'and' ) {
166 | global $wpdb;
167 | if ( ! in_array( strtolower( $joint ), [ 'and', 'or', 'where' ] ) ) {
168 | $this->exception( 'Invalid where type "' . $joint . '"' );
169 | }
170 |
171 | // when column is an array we assume to make a bulk and where.
172 | if ( is_array( $column ) ) {
173 | // create new query object
174 | $subquery = new Query();
175 | foreach ( $column as $key => $val ) {
176 | $subquery->where( $key, $val, null, $joint );
177 | }
178 |
179 | $this->where = array_merge( $this->where, $subquery->where );
180 |
181 | return $this;
182 | }
183 |
184 | if ( is_object( $column ) && ( $column instanceof \Closure ) ) {
185 | // create new query object
186 | $subquery = new Query();
187 |
188 | // run the closure callback on the sub query
189 | call_user_func_array( $column, array( &$subquery ) );
190 | $condition = '';
191 | for ( $i = 0; $i < count( $subquery->where ); ++ $i ) {
192 | $condition .= ( $i === 0 ? ' ' : ' ' . $subquery->where[ $i ]['joint'] . ' ' )
193 | . $subquery->where[ $i ]['condition'];
194 | }
195 |
196 | $this->where = array_merge( $this->where, array(
197 | array(
198 | 'joint' => $joint,
199 | 'condition' => "($condition)"
200 | )
201 | ) );
202 |
203 | return $this;
204 | }
205 |
206 | // when param2 is null we replace param2 with param one as the
207 | // value holder and make param1 to the = operator.
208 | // However, if param1 is either 'is' or 'is not' we need to leave param2 as null.
209 | // This is used for the whereNull() and whereNotNull() methods.
210 | if ( is_null( $param2 ) && ! in_array( $param1, [ 'is', 'is not' ], true ) ) {
211 | $param2 = $param1;
212 | $param1 = '=';
213 | }
214 |
215 | // if the param2 is an array we filter it. when param2 is an array we probably
216 | // have an "in" or "between" statement which has no need for duplicates.
217 | if ( is_array( $param2 ) ) {
218 | $param2 = array_unique( $param2 );
219 | }
220 |
221 | // Between?
222 | if ( is_array( $param2 ) && strpos( $param1, 'BETWEEN' ) !== false ) {
223 | $min = isset( $param2[0] ) ? $param2[0] : false;
224 | $max = isset( $param2[1] ) ? $param2[1] : false;
225 | if ( ! $min || ! $max ) {
226 | $this->exception( "BETWEEN min or max is missing" );
227 | }
228 |
229 | $min = $wpdb->prepare( is_numeric( $min ) ? '%d' : '%s', $min );
230 | $max = $wpdb->prepare( is_numeric( $max ) ? '%d' : '%s', $max );
231 |
232 | $this->where[] = [
233 | 'joint' => $joint,
234 | 'condition' => "($column BETWEEN $min AND $max)",
235 | ];
236 |
237 | return $this;
238 | }
239 |
240 | // Not Between?
241 | if ( is_array( $param2 ) && strpos( $param1, 'NOT BETWEEN' ) !== false ) {
242 | $min = isset( $param2[0] ) ? $param2[0] : false;
243 | $max = isset( $param2[1] ) ? $param2[1] : false;
244 | if ( ! $min || ! $max ) {
245 | $this->exception( "NOT BETWEEN min or max is missing" );
246 | }
247 |
248 | $min = $wpdb->prepare( is_numeric( $min ) ? '%d' : '%s', $min );
249 | $max = $wpdb->prepare( is_numeric( $max ) ? '%d' : '%s', $max );
250 |
251 | $this->where[] = [
252 | 'joint' => $joint,
253 | 'condition' => "($column NOT BETWEEN $min AND $max)",
254 | ];
255 |
256 | return $this;
257 | }
258 |
259 |
260 | //first check if is array if so then make a string out of array
261 | //if not array but null then set value as null
262 | //if not null does it contains . it could be column so dont parse as string
263 | //If not column then use wpdb prepare
264 | //if contains $prefix
265 | $contain_join = preg_replace( '/^(\s?AND ?|\s?OR ?)|\s$/i', '', $param2 );
266 |
267 | $param2 = is_array( $param2 ) ? ( '("' . implode( '","', $param2 ) . '")' ) : ( $param2 === null
268 | ? 'null'
269 | : ( strpos( $param2, '.' ) !== false || strpos( $param2, $wpdb->prefix ) !== false ? $param2 : $wpdb->prepare( is_numeric( $param2 ) ? '%d' : '%s', $param2 ) )
270 | );
271 |
272 | $this->where[] = [
273 | 'joint' => $joint,
274 | 'condition' => implode( ' ', [ $column, $param1, $param2 ] ),
275 | ];
276 |
277 | return $this;
278 | }
279 |
280 |
281 | /**
282 | * Create an or where statement
283 | *
284 | * This is the same as the normal where just with a fixed type
285 | *
286 | * @param string $column The SQL column
287 | * @param mixed $param1
288 | * @param mixed $param2
289 | *
290 | * @return Query The current query builder.
291 | */
292 | public function orWhere( $column, $param1 = null, $param2 = null ) {
293 | return $this->where( $column, $param1, $param2, 'or' );
294 | }
295 |
296 | /**
297 | * Create an and where statement
298 | *
299 | * This is the same as the normal where just with a fixed type
300 | *
301 | * @param string $column The SQL column
302 | * @param mixed $param1
303 | * @param mixed $param2
304 | *
305 | * @return Query The current query builder.
306 | */
307 | public function andWhere( $column, $param1 = null, $param2 = null ) {
308 | return $this->where( $column, $param1, $param2, 'and' );
309 | }
310 |
311 | /**
312 | * Creates a where in statement
313 | *
314 | * ->whereIn('id', [42, 38, 12])
315 | *
316 | * @param string $column
317 | * @param array $options
318 | *
319 | * @return Query The current query builder.
320 | */
321 | public function whereIn( $column, array $options = array() ) {
322 | // when the options are empty we skip
323 | if ( empty( $options ) ) {
324 | return $this;
325 | }
326 |
327 | return $this->where( $column, 'in', $options );
328 | }
329 |
330 | /**
331 | * Creates a where not in statement
332 | *
333 | * ->whereNotIn('id', [42, 38, 12])
334 | *
335 | * @param string $column
336 | * @param array $options
337 | *
338 | * @return Query The current query builder.
339 | */
340 | public function whereNotIn( $column, array $options = array() ) {
341 | // when the options are empty we skip
342 | if ( empty( $options ) ) {
343 | return $this;
344 | }
345 |
346 | return $this->where( $column, 'not in', $options );
347 | }
348 |
349 | /**
350 | * Creates a where something is null statement
351 | *
352 | * ->whereNull('modified_at')
353 | *
354 | * @param string $column
355 | *
356 | * @return Query The current query builder.
357 | */
358 | public function whereNull( $column ) {
359 | return $this->where( $column, 'is', null );
360 | }
361 |
362 | /**
363 | * Creates a where something is not null statement
364 | *
365 | * ->whereNotNull('created_at')
366 | *
367 | * @param string $column
368 | *
369 | * @return Query The current query builder.
370 | */
371 | public function whereNotNull( $column ) {
372 | return $this->where( $column, 'is not', null );
373 | }
374 |
375 | /**
376 | * Creates a or where something is null statement
377 | *
378 | * ->orWhereNull('modified_at')
379 | *
380 | * @param string $column
381 | *
382 | * @return Query The current query builder.
383 | */
384 | public function orWhereNull( $column ) {
385 | return $this->orWhere( $column, 'is', null );
386 | }
387 |
388 | /**
389 | * Creates a or where something is not null statement
390 | *
391 | * ->orWhereNotNull('modified_at')
392 | *
393 | * @param string $column
394 | *
395 | * @return Query The current query builder.
396 | */
397 | public function orWhereNotNull( $column ) {
398 | return $this->orWhere( $column, 'is not', null );
399 | }
400 |
401 |
402 | /**
403 | * Creates a where between statement
404 | *
405 | * ->whereBetween('user_id', 1, 2000)
406 | *
407 | * @param string $column
408 | *
409 | * @return Query The current query builder.
410 | */
411 | public function whereBetween( $column, $min, $max ) {
412 | return $this->where( $column, 'BETWEEN', array( $min, $max ) );
413 | }
414 |
415 | /**
416 | * Creates a where not between statement
417 | *
418 | * ->whereNotBetween('user_id', 1, 2000)
419 | *
420 | * @param string $column
421 | *
422 | * @return Query The current query builder.
423 | */
424 | public function whereNotBetween( $column, $min, $max ) {
425 | return $this->where( $column, 'NOT BETWEEN', array( $min, $max ) );
426 | }
427 |
428 | /**
429 | * Creates a where date between statement
430 | *
431 | * ->whereDateBetween('date', '2014-02-01', '2014-02-28')
432 | *
433 | * @param string $column
434 | *
435 | * @return Query The current query builder.
436 | */
437 | public function whereDateBetween( $column, $start, $end ) {
438 | global $wpdb;
439 | $stat_date = $wpdb->get_var( $wpdb->prepare( 'SELECT CAST(%s as DATE)', $start ) );
440 | $end_date = $wpdb->get_var( $wpdb->prepare( 'SELECT CAST(%s as DATE)', $end ) );
441 |
442 | return $this->where( $column, 'BETWEEN', array( $stat_date, $end_date ) );
443 | }
444 |
445 |
446 | /**
447 | *
448 | * @param $query
449 | * @param string $joint
450 | *
451 | * @since 1.0.1
452 | */
453 | public function whereRaw( $query, $joint = 'AND' ) {
454 | $this->where[] = [
455 | 'joint' => $joint,
456 | 'condition' => $query,
457 | ];
458 |
459 | return $this;
460 | }
461 |
462 |
463 | /**
464 | * Add a join statement to the current query
465 | *
466 | * ->join('avatars', 'users.id', '=', 'avatars.user_id')
467 | *
468 | * @param array|string $table The table to join. (can contain an alias definition.)
469 | * @param string $localKey
470 | * @param string $operator The operator (=, !=, <, > etc.)
471 | * @param string $referenceKey
472 | * @param string $type The join type (inner, left, right, outer)
473 | * @param string $joint The join AND or Or
474 | * @param bool $add_prefix Add table prefix or not
475 | *
476 | * @return Query The current query builder.
477 | */
478 | public function join( $table, $localKey, $operator = null, $referenceKey = null, $type = 'left', $joint = 'AND', $add_prefix = true ) {
479 | global $wpdb;
480 | $type = is_string( $type ) ? strtoupper( trim( $type ) ) : ( $type ? 'LEFT' : '' );
481 | if ( ! in_array( $type, [ '', 'LEFT', 'RIGHT', 'INNER', 'CROSS', 'LEFT OUTER', 'RIGHT OUTER' ] ) ) {
482 | $this->exception( "Invalid join type." );
483 | }
484 |
485 | $join = [
486 | 'table' => ( $add_prefix ? $wpdb->prefix : '' ) . $table,
487 | 'type' => $type,
488 | 'on' => [],
489 | ];
490 |
491 | // to make nested joins possible you can pass an closure
492 | // which will create a new query where you can add your nested where
493 | if ( is_object( $localKey ) && ( $localKey instanceof \Closure ) ) {
494 | //create new query object
495 | $subquery = new Query();
496 | // run the closure callback on the sub query
497 | call_user_func_array( $localKey, array( &$subquery ) );
498 |
499 | $join['on'] = array_merge( $join['on'], $subquery->where );
500 | $this->join = array_merge( $this->join, array( $join ) );
501 |
502 | return $this;
503 | }
504 |
505 | // when param2 is null we replace param2 with param one as the
506 | // value holder and make param1 to the = operator.
507 | if ( is_null( $referenceKey ) ) {
508 | $referenceKey = $operator;
509 | $operator = '=';
510 | }
511 |
512 | $referenceKey = is_array( $referenceKey ) ? ( '(\'' . implode( '\',\'', $referenceKey ) . '\')' )
513 | : ( $referenceKey === null
514 | ? 'null'
515 | : ( strpos( $referenceKey, '.' ) !== false || strpos( $referenceKey, $wpdb->prefix ) !== false ? $referenceKey : $wpdb->prepare( is_numeric( $referenceKey ) ? '%d' : '%s', $referenceKey ) )
516 | );
517 |
518 | $join['on'][] = [
519 | 'joint' => $joint,
520 | 'condition' => implode( ' ', [ $localKey, $operator, $referenceKey ] ),
521 | ];
522 |
523 | $this->join[] = $join;
524 |
525 | return $this;
526 | }
527 |
528 | /**
529 | * Left join same as join with special type
530 | *
531 | * @param array|string $table The table to join. (can contain an alias definition.)
532 | * @param string $localKey
533 | * @param string $operator The operator (=, !=, <, > etc.)
534 | * @param string $referenceKey
535 | *
536 | * @return Query The current query builder.
537 | */
538 | public function leftJoin( $table, $localKey, $operator = null, $referenceKey = null ) {
539 | return $this->join( $table, $localKey, $operator, $referenceKey, 'left' );
540 | }
541 |
542 | /**
543 | * Alias of the `join` method with join type right.
544 | *
545 | * @param array|string $table The table to join. (can contain an alias definition.)
546 | * @param string $localKey
547 | * @param string $operator The operator (=, !=, <, > etc.)
548 | * @param string $referenceKey
549 | *
550 | * @return Query The current query builder.
551 | */
552 | public function rightJoin( $table, $localKey, $operator = null, $referenceKey = null ) {
553 | return $this->join( $table, $localKey, $operator, $referenceKey, 'right' );
554 | }
555 |
556 | /**
557 | * Alias of the `join` method with join type inner.
558 | *
559 | * @param array|string $table The table to join. (can contain an alias definition.)
560 | * @param string $localKey
561 | * @param string $operator The operator (=, !=, <, > etc.)
562 | * @param string $referenceKey
563 | *
564 | * @return Query The current query builder.
565 | */
566 | public function innerJoin( $table, $localKey, $operator = null, $referenceKey = null ) {
567 | return $this->join( $table, $localKey, $operator, $referenceKey, 'inner' );
568 | }
569 |
570 | /**
571 | * Alias of the `join` method with join type outer.
572 | *
573 | * @param array|string $table The table to join. (can contain an alias definition.)
574 | * @param string $localKey
575 | * @param string $operator The operator (=, !=, <, > etc.)
576 | * @param string $referenceKey
577 | *
578 | * @return Query The current query builder.
579 | */
580 | public function outerJoin( $table, $localKey, $operator = null, $referenceKey = null ) {
581 | return $this->join( $table, $localKey, $operator, $referenceKey, 'outer' );
582 | }
583 |
584 | /**
585 | *
586 | * @param $query
587 | * @param string $joint
588 | *
589 | * @since 1.0.1
590 | */
591 | public function joinRaw( $query, $joint = 'AND' ) {
592 | $this->join['on'][] = [
593 | 'joint' => $joint,
594 | 'condition' => $query,
595 | ];
596 |
597 | return $this;
598 | }
599 |
600 |
601 | /**
602 | * Adds group by statement.
603 | * ->groupBy('category')
604 | * ->gorupBy(['category', 'price'])
605 | *
606 | * @param string $field
607 | *
608 | * @return Query this for chaining.
609 | * @since 1.0.0
610 | *
611 | */
612 | public function group_by( $field ) {
613 | if ( empty( $field ) ) {
614 | return $this;
615 | }
616 |
617 | if ( is_array( $field ) ) {
618 | foreach ( $field as $groupby ) {
619 | $this->group[] = $groupby;
620 | }
621 | } else {
622 | $this->group[] = $field;
623 | }
624 |
625 | return $this;
626 | }
627 |
628 | /**
629 | * Adds having statement.
630 | *
631 | * ->group_by('user.id')
632 | * ->having('count(user.id)>1')
633 | *
634 | * @param string $statement
635 | *
636 | * @return Query this for chaining.
637 | * @since 1.0.0
638 | *
639 | */
640 | public function having( $statement ) {
641 | if ( ! empty( $statement ) ) {
642 | $this->having = $statement;
643 | }
644 |
645 | return $this;
646 | }
647 |
648 | /**
649 | * Adds order by statement.
650 | *
651 | * ->orderBy('created_at')
652 | * ->orderBy('modified_at', 'desc')
653 | *
654 | * @param string $key
655 | * @param string $direction
656 | *
657 | * @return Query this for chaining.
658 | * @throws Exception
659 | * @since 1.0.0
660 | *
661 | */
662 | public function order_by( $key, $direction = 'ASC' ) {
663 | $direction = trim( strtoupper( $direction ) );
664 | if ( $direction !== 'ASC' && $direction !== 'DESC' ) {
665 | $this->exception( 'Invalid direction value.' );
666 | }
667 | if ( ! empty( $key ) ) {
668 | $this->order[] = $key . ' ' . $direction;
669 | }
670 |
671 | return $this;
672 | }
673 |
674 | /**
675 | * Set the query limit
676 | *
677 | * // limit()
678 | * ->limit(20)
679 | *
680 | * // limit(, )
681 | * ->limit(60, 20)
682 | *
683 | * @param int $limit
684 | * @param int $limit2
685 | *
686 | * @return Query The current query builder.
687 | */
688 | public function limit( $limit, $limit2 = null ) {
689 | if ( ! is_null( $limit2 ) ) {
690 | $this->offset = (int) $limit;
691 | $this->limit = (int) $limit2;
692 | } else {
693 | $this->limit = (int) $limit;
694 | }
695 |
696 | return $this;
697 | }
698 |
699 | /**
700 | * Adds offset statement.
701 | *
702 | * ->offset(20)
703 | *
704 | * @param int $offset
705 | *
706 | * @return Query this for chaining.
707 | *
708 | */
709 | public function offset( $offset ) {
710 | $this->offset = $offset;
711 |
712 | return $this;
713 | }
714 |
715 | /**
716 | * Create a query limit based on a page and a page size
717 | *
718 | * //page(, )
719 | * ->page(2, 20)
720 | *
721 | * @param int $page
722 | * @param int $size
723 | *
724 | * @return Query The current query builder.
725 | * @since 1.0.0
726 | */
727 | public function page( $page, $size = 20 ) {
728 | if ( ( $page = (int) $page ) <= 1 ) {
729 | $page = 0;
730 | }
731 |
732 | $this->limit = (int) $size;
733 | $this->offset = (int) $size * $page;
734 |
735 | return $this;
736 | }
737 |
738 | /**
739 | * Find something, means select one item by key
740 | *
741 | * ->find('manikdrmc@gmail.com', 'email')
742 | *
743 | * @param int $id
744 | * @param string $key
745 | *
746 | * @return mixed
747 | */
748 | public function find( $id, $key = 'id' ) {
749 | return $this->where( $key, $id )->one();
750 | }
751 |
752 | /**
753 | * Get the first result ordered by the given key.
754 | *
755 | * @param string $key By what should the first item be selected? Default is: 'id'
756 | *
757 | * @return mixed The first result.
758 | */
759 | public function first( $key = 'id' ) {
760 | return $this->order_by( $key, 'asc' )->one();
761 | }
762 |
763 | /**
764 | * Get the last result by key
765 | *
766 | * @param string $key
767 | *
768 | * @return mixed the last result.
769 | */
770 | public function last( $key = 'id' ) {
771 | return $this->order_by( $key, 'desc' )->one();
772 | }
773 |
774 | /**
775 | * Pluck item.
776 | * ->find('post_title')
777 | * @return Object
778 | * @since 1.0.1
779 | */
780 | public function pluck() {
781 | $selects = func_get_args();
782 | $this->select = $selects;
783 |
784 | return $this->get();
785 | }
786 |
787 |
788 | /**
789 | * Returns results from builder statements.
790 | *
791 | * @param int $output WPDB output type.
792 | * @param callable $row_map Function callable to filter or map results to.
793 | * @param bool $calc_rows Flag that indicates to SQL if rows should be calculated or not.
794 | *
795 | * @return Object || Array
796 | * @since 1.0.0
797 | *
798 | * @global object $wpdb
799 | *
800 | */
801 | public function get( $output = OBJECT, $row_map = null, $calc_rows = false ) {
802 | global $wpdb;
803 | do_action( 'wp_query_builder_get_builder', $this );
804 | do_action( 'wp_query_builder_get_builder_' . $this->id, $this );
805 |
806 | $query = '';
807 | $this->_query_select( $query, $calc_rows );
808 | $this->_query_from( $query );
809 | $this->_query_join( $query );
810 | $this->_query_where( $query );
811 | $this->_query_group( $query );
812 | $this->_query_having( $query );
813 | $this->_query_order( $query );
814 | $this->_query_limit( $query );
815 | $this->_query_offset( $query );
816 |
817 | // Process
818 | $query = apply_filters( 'wp_query_builder_get_query', $query );
819 | $query = apply_filters( 'wp_query_builder_get_query_' . $this->id, $query );
820 |
821 | $results = $wpdb->get_results( $query, $output );
822 | if ( $row_map ) {
823 | $results = array_map( function ( $row ) use ( &$row_map ) {
824 | return call_user_func_array( $row_map, [ $row ] );
825 | }, $results );
826 | }
827 |
828 | return $results;
829 | }
830 |
831 | /**
832 | * Sets the limit to 1, executes and returns the first result using get.
833 | *
834 | * @param string $output
835 | *
836 | * @return mixed The single result.
837 | */
838 | public function one( $output = OBJECT ) {
839 | global $wpdb;
840 | do_action( 'wp_query_builder_one_builder', $this );
841 | do_action( 'wp_query_builder_one_builder_' . $this->id, $this );
842 |
843 | $this->_query_select( $query );
844 | $this->_query_from( $query );
845 | $this->_query_join( $query );
846 | $this->_query_where( $query );
847 | $this->_query_group( $query );
848 | $this->_query_having( $query );
849 | $this->_query_order( $query );
850 | $query .= ' LIMIT 1';
851 | $this->_query_offset( $query );
852 |
853 | $query = apply_filters( 'wp_query_builder_one_query', $query );
854 | $query = apply_filters( 'wp_query_builder_one_query_' . $this->id, $query );
855 |
856 | return $wpdb->get_row( $query, $output );
857 | }
858 |
859 | /**
860 | * Just return the number of results
861 | *
862 | * @param string|int $column
863 | *
864 | * @return int
865 | */
866 | public function count( $column = 1 ) {
867 | global $wpdb;
868 | do_action( 'wp_query_builder_count_builder', $this );
869 | do_action( 'wp_query_builder_count_builder_' . $this->id, $this );
870 |
871 | $query = 'SELECT count(' . $column . ') as `count`';
872 | $this->_query_from( $query );
873 | $this->_query_join( $query );
874 | $this->_query_where( $query );
875 | $this->_query_group( $query );
876 | $this->_query_having( $query );
877 |
878 | return intval( $wpdb->get_var( $query ) );
879 | }
880 |
881 | /**
882 | * Just get a single value from the result
883 | *
884 | * @param string $column The index of the column.
885 | * @param bool $calc_rows Flag that indicates to SQL if rows should be calculated or not.
886 | *
887 | * @return mixed The columns value
888 | */
889 | public function column( $column = 0, $calc_rows = false ) {
890 | global $wpdb;
891 | do_action( 'wp_query_builder_column_builder', $this );
892 | do_action( 'wp_query_builder_column_builder_' . $this->id, $this );
893 |
894 | $query = '';
895 | $this->_query_select( $query, $calc_rows );
896 | $this->_query_from( $query );
897 | $this->_query_join( $query );
898 | $this->_query_where( $query );
899 | $this->_query_group( $query );
900 | $this->_query_having( $query );
901 | $this->_query_order( $query );
902 | $this->_query_limit( $query );
903 | $this->_query_offset( $query );
904 |
905 | return $wpdb->get_col( $query, $column );
906 | }
907 |
908 | /**
909 | * Returns a value.
910 | *
911 | * @param int $x Column of value to return. Indexed from 0.
912 | * @param int $y Row of value to return. Indexed from 0.
913 | *
914 | * @return mixed
915 | * @global object $wpdb
916 | *
917 | * @since 1.0.0
918 | *
919 | */
920 | public function value( $x = 0, $y = 0 ) {
921 | global $wpdb;
922 | do_action( 'wp_query_builder_value_builder', $this );
923 | do_action( 'wp_query_builder_value_builder_' . $this->id, $this );
924 |
925 | // Build
926 | // Query
927 | $query = '';
928 | $this->_query_select( $query );
929 | $this->_query_from( $query );
930 | $this->_query_join( $query );
931 | $this->_query_where( $query );
932 | $this->_query_group( $query );
933 | $this->_query_having( $query );
934 | $this->_query_order( $query );
935 | $this->_query_limit( $query );
936 | $this->_query_offset( $query );
937 |
938 | return $wpdb->get_var( $query, $x, $y );
939 | }
940 |
941 |
942 | /**
943 | * Update or insert.
944 | *
945 | * @param $data
946 | *
947 | * @return array|string
948 | */
949 | public function updateOrInsert( $data ) {
950 | if ( $this->first() ) {
951 | return $this->update( $data );
952 | } else {
953 | return $this->insert( $data );
954 | }
955 | }
956 |
957 | /**
958 | * Find or insert.
959 | *
960 | * @param $data
961 | *
962 | * @return array|string
963 | */
964 | public function findOrInsert( $data ) {
965 | if ( $this->first() ) {
966 | return $this->update( $data );
967 | } else {
968 | return $this->insert( $data );
969 | }
970 | }
971 |
972 | /**
973 | * Get max value.
974 | *
975 | * @param $column
976 | *
977 | * @return int
978 | * @since 1.0.1
979 | */
980 | public function max( $column ) {
981 | global $wpdb;
982 | $query = 'SELECT MAX(' . $column . ')';
983 | $this->_query_from( $query );
984 | $this->_query_join( $query );
985 | $this->_query_where( $query );
986 | $this->_query_group( $query );
987 | $this->_query_having( $query );
988 |
989 | return intval( $wpdb->get_var( $query ) );
990 | }
991 |
992 | /**
993 | * Get min value.
994 | *
995 | * @param $column
996 | *
997 | * @return int
998 | * @since 1.0.1
999 | */
1000 | public function min( $column ) {
1001 | global $wpdb;
1002 | $query = 'SELECT MIN(' . $column . ')';
1003 | $this->_query_from( $query );
1004 | $this->_query_join( $query );
1005 | $this->_query_where( $query );
1006 | $this->_query_group( $query );
1007 | $this->_query_having( $query );
1008 |
1009 | return intval( $wpdb->get_var( $query ) );
1010 | }
1011 |
1012 | /**
1013 | * Get avg value.
1014 | *
1015 | * @param $column
1016 | *
1017 | * @return int
1018 | * @since 1.0.1
1019 | */
1020 | public function avg( $column ) {
1021 | global $wpdb;
1022 | $query = 'SELECT AVG(' . $column . ')';
1023 | $this->_query_from( $query );
1024 | $this->_query_join( $query );
1025 | $this->_query_where( $query );
1026 | $this->_query_group( $query );
1027 | $this->_query_having( $query );
1028 |
1029 | return intval( $wpdb->get_var( $query ) );
1030 | }
1031 |
1032 | /**
1033 | * Get sum value.
1034 | *
1035 | * @param $column
1036 | *
1037 | * @return int
1038 | * @since 1.0.1
1039 | */
1040 | public function sum( $column ) {
1041 | global $wpdb;
1042 | $query = 'SELECT SUM(' . $column . ')';
1043 | $this->_query_from( $query );
1044 | $this->_query_join( $query );
1045 | $this->_query_where( $query );
1046 | $this->_query_group( $query );
1047 | $this->_query_having( $query );
1048 |
1049 | return intval( $wpdb->get_var( $query ) );
1050 | }
1051 |
1052 | /**
1053 | * Returns flag indicating if query has been executed.
1054 | *
1055 | * @param string $sql
1056 | *
1057 | * @return bool
1058 | * @since 1.0.0
1059 | *
1060 | * @global object $wpdb
1061 | *
1062 | */
1063 | public function query( $sql = '' ) {
1064 | global $wpdb;
1065 | $query = $sql;
1066 | if ( empty( $query ) ) {
1067 | $this->_query_select( $query, false );
1068 | $this->_query_from( $query );
1069 | $this->_query_join( $query );
1070 | $this->_query_where( $query );
1071 | $this->_query_group( $query );
1072 | $this->_query_having( $query );
1073 | $this->_query_order( $query );
1074 | $this->_query_limit( $query );
1075 | $this->_query_offset( $query );
1076 | }
1077 |
1078 | return $wpdb->query( $query );
1079 | }
1080 |
1081 | /**
1082 | * Returns query from builder statements.
1083 | *
1084 | * @return string
1085 | * @since 1.0.0
1086 | */
1087 | public function toSql() {
1088 | $query = '';
1089 | $this->_query_select( $query );
1090 | $this->_query_from( $query );
1091 | $this->_query_join( $query );
1092 | $this->_query_where( $query );
1093 | $this->_query_group( $query );
1094 | $this->_query_having( $query );
1095 | $this->_query_order( $query );
1096 | $this->_query_limit( $query );
1097 | $this->_query_offset( $query );
1098 |
1099 | return $query;
1100 | }
1101 |
1102 | /**
1103 | * Returns found rows in last query, if SQL_CALC_FOUND_ROWS is used and is supported.
1104 | * @return array
1105 | * @global object $wpdb
1106 | *
1107 | * @since 1.0.0
1108 | *
1109 | */
1110 | public function rows_found() {
1111 | global $wpdb;
1112 | $query = 'SELECT FOUND_ROWS()';
1113 | // Process
1114 | $query = apply_filters( 'wp_query_builder_found_rows_query', $query );
1115 | $query = apply_filters( 'wp_query_builder_found_rows_query_' . $this->id, $query );
1116 |
1117 | return $wpdb->get_var( $query );
1118 | }
1119 |
1120 | /**
1121 | * Returns flag indicating if delete query has been executed.
1122 | * @return bool
1123 | * @global object $wpdb
1124 | *
1125 | * @since 1.0.0
1126 | *
1127 | */
1128 | public function delete() {
1129 | global $wpdb;
1130 | do_action( 'wp_query_builder_delete_builder', $this );
1131 | do_action( 'wp_query_builder_delete_builder_' . $this->id, $this );
1132 |
1133 | $query = '';
1134 | $this->_query_delete( $query );
1135 | $this->_query_from( $query );
1136 | $this->_query_join( $query );
1137 | $this->_query_where( $query );
1138 |
1139 | return $wpdb->query( $query );
1140 | }
1141 |
1142 | /**
1143 | * Update
1144 | * @return bool
1145 | * @global object $wpdb
1146 | *
1147 | * @since 1.0.0
1148 | *
1149 | */
1150 | public function update( $data ) {
1151 | global $wpdb;
1152 | $conditions = '';
1153 | $this->_query_where( $conditions );
1154 |
1155 | if ( empty( trim( $conditions ) ) ) {
1156 | return false;
1157 | }
1158 |
1159 | $fields = array();
1160 | foreach ( $data as $column => $value ) {
1161 |
1162 | if ( is_null( $value ) ) {
1163 | $fields[] = "`$column` = NULL";
1164 | continue;
1165 | }
1166 |
1167 | $fields[] = "`$column` = " . $wpdb->prepare( is_numeric( $value ) ? '%d' : '%s', $value );
1168 | }
1169 |
1170 | $table = trim( $this->from );
1171 | $fields = implode( ', ', $fields );
1172 |
1173 | $query = "UPDATE `$table` SET $fields $conditions";
1174 |
1175 | return $wpdb->query( $query );
1176 | }
1177 |
1178 | /**
1179 | * Insert data.
1180 | *
1181 | * @param $data
1182 | * @param array $format
1183 | *
1184 | * @return bool|int
1185 | * @since 1.0.1
1186 | */
1187 | public function insert( $data, $format = array() ) {
1188 | global $wpdb;
1189 |
1190 | if ( false !== $wpdb->insert( trim( $this->from ), $data, $format ) ) {
1191 | return $wpdb->insert_id;
1192 | };
1193 |
1194 | return false;
1195 | }
1196 |
1197 | /**
1198 | * Return a cloned object from current builder.
1199 | *
1200 | * @return Query
1201 | * @since 1.0.0
1202 | */
1203 | public function copy() {
1204 | return clone( $this );
1205 | }
1206 |
1207 | /**
1208 | * Builds query's select statement.
1209 | *
1210 | * @param string &$query
1211 | * @param bool $calc_rows
1212 | *
1213 | * @since 1.0.0
1214 | *
1215 | */
1216 | private function _query_select( &$query, $calc_rows = false ) {
1217 | $query = 'SELECT ' . ( $calc_rows ? 'SQL_CALC_FOUND_ROWS ' : '' ) . (
1218 | is_array( $this->select ) && count( $this->select )
1219 | ? implode( ',', $this->select )
1220 | : '*'
1221 | );
1222 | }
1223 |
1224 | /**
1225 | * Builds query's from statement.
1226 | *
1227 | * @param string &$query
1228 | *
1229 | * @since 1.0.0
1230 | *
1231 | */
1232 | private function _query_from( &$query ) {
1233 | $query .= ' FROM ' . $this->from;
1234 | }
1235 |
1236 | /**
1237 | * Builds query's join statement.
1238 | *
1239 | * @param string &$query
1240 | *
1241 | * @since 1.0.0
1242 | *
1243 | */
1244 | private function _query_join( &$query ) {
1245 | foreach ( $this->join as $join ) {
1246 | $query .= ( ! empty( $join['type'] ) ? ' ' . $join['type'] . ' JOIN ' : ' JOIN ' ) . $join['table'];
1247 | for ( $i = 0; $i < count( $join['on'] ); ++ $i ) {
1248 | $query .= ( $i === 0 ? ' ON ' : ' ' . $join['on'][ $i ]['joint'] . ' ' )
1249 | . $join['on'][ $i ]['condition'];
1250 | }
1251 | }
1252 | }
1253 |
1254 | /**
1255 | * Builds query's where statement.
1256 | *
1257 | * @param string &$query
1258 | *
1259 | * @since 1.0.0
1260 | *
1261 | */
1262 | public function _query_where( &$query ) {
1263 | for ( $i = 0; $i < count( $this->where ); ++ $i ) {
1264 | $query .= ( $i === 0 ? ' WHERE ' : ' ' . $this->where[ $i ]['joint'] . ' ' )
1265 | . $this->where[ $i ]['condition'];
1266 | }
1267 | }
1268 |
1269 | /**
1270 | * Builds query's group by statement.
1271 | *
1272 | * @param string &$query
1273 | *
1274 | * @since 1.0.0
1275 | *
1276 | */
1277 | private function _query_group( &$query ) {
1278 | if ( count( $this->group ) ) {
1279 | $query .= ' GROUP BY ' . implode( ',', $this->group );
1280 | }
1281 | }
1282 |
1283 | /**
1284 | * Builds query's having statement.
1285 | *
1286 | * @param string &$query
1287 | *
1288 | * @since 1.0.0
1289 | *
1290 | */
1291 | private function _query_having( &$query ) {
1292 | if ( $this->having ) {
1293 | $query .= ' HAVING ' . $this->having;
1294 | }
1295 | }
1296 |
1297 | /**
1298 | * Builds query's order by statement.
1299 | *
1300 | * @param string &$query
1301 | *
1302 | * @since 1.0.0
1303 | *
1304 | */
1305 | private function _query_order( &$query ) {
1306 | if ( count( $this->order ) ) {
1307 | $query .= ' ORDER BY ' . implode( ',', $this->order );
1308 | }
1309 | }
1310 |
1311 | /**
1312 | * Builds query's limit statement.
1313 | *
1314 | * @param string &$query
1315 | *
1316 | * @global object $wpdb
1317 | *
1318 | * @since 1.0.0
1319 | *
1320 | */
1321 | private function _query_limit( &$query ) {
1322 | global $wpdb;
1323 | if ( $this->limit ) {
1324 | $query .= $wpdb->prepare( ' LIMIT %d', $this->limit );
1325 | }
1326 | }
1327 |
1328 | /**
1329 | * Builds query's offset statement.
1330 | *
1331 | * @param string &$query
1332 | *
1333 | * @global object $wpdb
1334 | *
1335 | * @since 1.0.0
1336 | *
1337 | */
1338 | private function _query_offset( &$query ) {
1339 | global $wpdb;
1340 | if ( $this->offset ) {
1341 | $query .= $wpdb->prepare( ' OFFSET %d', $this->offset );
1342 | }
1343 | }
1344 |
1345 | /**
1346 | * Builds query's delete statement.
1347 | *
1348 | * @param string &$query
1349 | *
1350 | * @since 1.0.0
1351 | *
1352 | */
1353 | private function _query_delete( &$query ) {
1354 | $query .= trim( 'DELETE ' . ( count( $this->join )
1355 | ? preg_replace( '/\s[aA][sS][\s\S]+.*?/', '', $this->from )
1356 | : ''
1357 | ) );
1358 | }
1359 |
1360 | /**
1361 | * Sanitize value.
1362 | *
1363 | * @param string|bool $callback Sanitize callback.
1364 | * @param mixed $value
1365 | *
1366 | * @return mixed
1367 | * @since 1.0.0
1368 | *
1369 | */
1370 | private function sanitize_value( $callback, $value ) {
1371 | if ( $callback === true ) {
1372 | $callback = ( is_numeric( $value ) && strpos( $value, '.' ) !== false )
1373 | ? 'floatval'
1374 | : ( is_numeric( $value )
1375 | ? 'intval'
1376 | : ( is_string( $value )
1377 | ? 'sanitize_text_field'
1378 | : null
1379 | )
1380 | );
1381 | }
1382 | if ( strpos( $callback, '_builder' ) !== false ) {
1383 | $callback = [ &$this, $callback ];
1384 | }
1385 | if ( is_array( $value ) ) {
1386 | for ( $i = count( $value ) - 1; $i >= 0; -- $i ) {
1387 | $value[ $i ] = $this->sanitize_value( true, $value[ $i ] );
1388 | }
1389 | }
1390 |
1391 | return $callback && is_callable( $callback ) ? call_user_func_array( $callback, [ $value ] ) : $value;
1392 | }
1393 |
1394 | /**
1395 | * @param $message
1396 | *
1397 | * @throws \Exception
1398 | * @since 1.0.0
1399 | */
1400 | private function exception( $message ) {
1401 | throw new \Exception( $message );
1402 | }
1403 | }
1404 |
--------------------------------------------------------------------------------