PluginProbe
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler / 1.3.25
FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler v1.3.25
1.6.5 1.6.4 1.6.3 1.6.2 1.6.1 1.6.0 1.5.4 1.5.5 1.5.3 1.5.2 1.5.1 1.5.0 1.4.2 1.4.1 1.4.0 1.3.28 1.3.27 1.3.26 1.3.25 1.3.23 1.3.22 1.3.21 1.3.20 1.3.19 trunk All 48 releases
fluent-cart / vendor / wpfluent / framework / src / WPFluent / Database / Query / WPDBConnection.php

WPDBConnection.php in FluentCart A New Era of eCommerce – Faster, Lighter, and Simpler 1.3.25, at vendor/wpfluent/framework/src/WPFluent/Database/Query/WPDBConnection.php

868 lines 21.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 * WPDB Connection
5 */
6
7 namespace FluentCart\Framework\Database\Query;
8
9 use Closure;
10 use Exception;
11 use DateTimeInterface;
12 use FluentCart\Framework\Foundation\App;
13 use FluentCart\Framework\Database\Schema;
14 use FluentCart\Framework\Database\QueryException;
15 use FluentCart\Framework\Database\ConnectionInterface;
16 use FluentCart\Framework\Database\MultipleColumnsSelectedException;
17 use FluentCart\Framework\Database\Events\QueryExecuted;
18 use FluentCart\Framework\Database\Query\Expression;
19 use FluentCart\Framework\Database\Query\Processors\Processor;
20 use FluentCart\Framework\Database\Query\Processors\MySqlProcessor;
21 use FluentCart\Framework\Database\Query\Processors\SQLiteProcessor;
22 use FluentCart\Framework\Database\Query\Builder as QueryBuilder;
23 use FluentCart\Framework\Database\Query\Grammars\Grammar;
24 use FluentCart\Framework\Database\Query\Grammars\MySqlGrammar;
25 use FluentCart\Framework\Database\Query\Grammars\SQLiteGrammar;
26 use FluentCart\Framework\Database\Concerns\ManagesTransactions;
27 use FluentCart\Framework\Database\DetectsLostConnections;
28
29 use FluentCart\Framework\Database\Events\TransactionBeginning;
30 use FluentCart\Framework\Database\Events\TransactionCommitted;
31 use FluentCart\Framework\Database\Events\TransactionCommitting;
32 use FluentCart\Framework\Database\Events\TransactionRolledBack;
33
34 class WPDBConnection implements ConnectionInterface
35 {
36 use DetectsLostConnections, ManagesTransactions;
37
38 /**
39 * $wpdb Global $wpdb instance
40 * @var Object
41 */
42 protected $wpdb;
43
44 /**
45 * The name of the connected database.
46 *
47 * @var string
48 */
49 protected $database;
50
51 /**
52 * The table prefix for the connection.
53 *
54 * @var string
55 */
56 protected $tablePrefix = '';
57
58 /**
59 * The database connection configuration options.
60 *
61 * @var array
62 */
63 protected $config = [];
64
65 /**
66 * The query grammar implementation.
67 *
68 * @var \FluentCart\Framework\Database\Query\Grammars\Grammar
69 */
70 protected $queryGrammar;
71
72 /**
73 * The query post processor implementation.
74 *
75 * @var \FluentCart\Framework\Database\Query\Processors\Processor
76 */
77 protected $postProcessor;
78
79 /**
80 * The number of active transactions.
81 *
82 * @var int
83 */
84 protected $transactions = 0;
85
86 /**
87 * The transaction manager instance.
88 *
89 * @var \FluentCart\Framework\Database\DatabaseTransactionsManager|null
90 */
91 protected $transactionsManager;
92
93 /**
94 * All of the callbacks that should be invoked before a transaction is started.
95 *
96 * @var \Closure[]
97 */
98 protected $beforeStartingTransaction = [];
99
100 /**
101 * The event dispatcher.
102 *
103 * @var \FluentCart\Framework\Events\Dispatcher
104 */
105 protected $event = null;
106
107 /**
108 * Create a new database connection instance.
109 *
110 * @param \wpdb $wpdb The WordPress database instance.
111 * @return void
112 */
113 public function __construct($wpdb)
114 {
115 $this->setupWpdbInstance($wpdb);
116
117 $this->useDefaultQueryGrammar();
118
119 $this->useDefaultPostProcessor();
120
121 $this->event = App::make('events');
122 }
123
124 /**
125 * Populate $wpdb instance & turn off db errors
126 *
127 * @param $wpdb Global $wpdb instance
128 * @return Null
129 */
130 protected function setupWpdbInstance($wpdb)
131 {
132 $this->wpdb = $wpdb;
133
134 $this->wpdb->show_errors(
135 $this->shouldShowErrors()
136 );
137 }
138
139 /**
140 * Determine if database errors should be shown.
141 *
142 * @return bool
143 */
144 protected function shouldShowErrors()
145 {
146 return strpos(App::env(), 'prod') === false;
147 }
148
149 /**
150 * Set the query grammar to the default implementation.
151 *
152 * @return void
153 */
154 public function useDefaultQueryGrammar()
155 {
156 $this->queryGrammar = $this->getDefaultQueryGrammar();
157 }
158
159 /**
160 * Get the default query grammar instance.
161 *
162 * @return \FluentCart\Framework\Database\Query\Grammars\Grammar
163 */
164 protected function getDefaultQueryGrammar()
165 {
166 return $this->isSqlite() ? new SQLiteGrammar : new MySqlGrammar;
167 }
168
169 /**
170 * Set the query post processor to the default implementation.
171 *
172 * @return void
173 */
174 public function useDefaultPostProcessor()
175 {
176 $this->postProcessor = $this->getDefaultPostProcessor();
177 }
178
179 /**
180 * Get the default post processor instance.
181 *
182 * @return \FluentCart\Framework\Database\Query\Processors\Processor
183 */
184 protected function getDefaultPostProcessor()
185 {
186 return $this->isSqlite() ? new SQLiteProcessor : new MySqlProcessor;
187 }
188
189 /**
190 * Begin a fluent query against a database table.
191 *
192 * @param \Closure|\FluentCart\Framework\Database\Query\Builder|string $table
193 * @param string|null $as
194 * @return \FluentCart\Framework\Database\Query\Builder
195 */
196 public function table($table, $as = null)
197 {
198 return $this->query()->from($table, $as);
199 }
200
201 /**
202 * Get a new query builder instance.
203 *
204 * @return \FluentCart\Framework\Database\Query\Builder
205 */
206 public function query()
207 {
208 return new QueryBuilder(
209 $this, $this->getQueryGrammar(), $this->getPostProcessor()
210 );
211 }
212
213 /**
214 * Run a select statement and return a single result.
215 *
216 * @param string $query
217 * @param array $bindings
218 * @return mixed
219 */
220 public function selectOne($query, $bindings = [])
221 {
222 return $this->run($query, $bindings, function ($query, $bindings) {
223 $query = $this->bindParams($query, $bindings);
224
225 $result = $this->wpdb->get_row($query);
226
227 if ($result === false || $this->wpdb->last_error) {
228 throw new QueryException(
229 $query, $bindings, new Exception($this->wpdb->last_error)
230 );
231 }
232
233 return $result;
234 });
235 }
236
237 /**
238 * Run a select statement and return the first column of the first row.
239 *
240 * @param string $query
241 * @param array $bindings
242 * @return mixed
243 *
244 * @throws \FluentCart\Framework\Database\MultipleColumnsSelectedException
245 */
246 public function scalar($query, $bindings = [])
247 {
248 $record = $this->selectOne($query, $bindings);
249
250 if (is_null($record)) {
251 return null;
252 }
253
254 $record = (array)$record;
255
256 if (count($record) > 1) {
257 throw new MultipleColumnsSelectedException(
258 'The query returned more than one column.'
259 );
260 }
261
262 return reset($record);
263 }
264
265 /**
266 * Run a select statement against the database.
267 *
268 * @param string $query
269 * @param array $bindings
270 * @return array
271 */
272 public function select($query, $bindings = [])
273 {
274 return $this->run($query, $bindings, function ($query, $bindings) {
275 $query = $this->bindParams($query, $bindings);
276
277 $result = $this->wpdb->get_results($query);
278
279 if ($result === false || $this->wpdb->last_error) {
280 throw new QueryException(
281 $query, $bindings, new Exception($this->wpdb->last_error)
282 );
283 }
284
285 return $result;
286 });
287 }
288
289 /**
290 * Bind the parameters into SQL query
291 *
292 * @param $query
293 * @param $bindings
294 * @return string
295 */
296 protected function bindParams(string $query, array $bindings)
297 {
298 $query = str_replace('"', '`', $query);
299
300 $bindings = $this->prepareBindings($bindings);
301
302 if (empty($bindings)) {
303 return $query;
304 }
305
306 $query = str_replace(['%', '?'], ['%%', '%s'], $query);
307
308 if ($this->wpdb->dbh instanceof \mysqli) {
309 return $this->wpdb->prepare($query, ...$bindings);
310 }
311
312 $bindings = array_map(function ($value) {
313 if ($value === null) return 'NULL';
314 if (is_bool($value)) return $value ? '1' : '0';
315 if (is_string($value)) return "'" . esc_sql($value) . "'";
316 return (string) $value;
317 }, $bindings);
318
319 return vsprintf($query, $bindings);
320 }
321
322 /**
323 * Run a select statement against the database and returns a generator.
324 *
325 * @param string $query
326 * @param array $bindings
327 * @return \Generator
328 * @throws \FluentAccount\Framework\Database\QueryException
329 */
330 public function cursor($query, $bindings = [])
331 {
332 // If not mysqli (e.g., SQLite), fallback to standard select
333 if (!$this->wpdb->dbh instanceof \mysqli) {
334 foreach ($this->select($query, $bindings) as $row) {
335 yield $row;
336 }
337 return;
338 }
339
340 $preparedQuery = str_replace('"', '`', $query);
341
342 $preparedQuery = str_replace('%', '%%', $preparedQuery);
343
344 $bindings = $this->prepareBindings($bindings);
345
346 $this->wpdb->flush();
347 $this->wpdb->insert_id = 0;
348 $this->wpdb->check_current_query = true;
349
350 if (!$this->wpdb->check_connection()) {
351 throw new QueryException(
352 $query, $bindings, new Exception(
353 $this->wpdb->last_error ?: 'Error reconnecting to database.'
354 )
355 );
356 }
357
358 if (defined('SAVEQUERIES') && SAVEQUERIES) {
359 $this->wpdb->timer_start();
360 }
361
362 $statement = $this->wpdb->dbh->prepare($preparedQuery);
363
364 if ($statement === false) {
365 throw new QueryException(
366 $query, $bindings, new Exception(
367 'Failed to prepare statement: ' . $this->wpdb->dbh->error
368 )
369 );
370 }
371
372 if (!empty($bindings)) {
373 $types = '';
374 foreach ($bindings as $binding) {
375 if (is_int($binding)) {
376 $types .= 'i';
377 } elseif (is_double($binding)) {
378 $types .= 'd';
379 } else {
380 $types .= 's';
381 }
382 }
383
384 $statement->bind_param($types, ...$bindings);
385 }
386
387 if ($statement->execute()) {
388 $result = $statement->get_result();
389
390 if ($result) {
391 while ($row = $result->fetch_assoc()) {
392 yield (object) $row;
393 }
394 $result->free();
395 } else {
396 if ($statement->errno) {
397 throw new QueryException(
398 $query, $bindings, new Exception($statement->error)
399 );
400 }
401 }
402 $statement->close();
403 return;
404 }
405
406 // Error handling if statement execution fails
407 if ($statement->error || $statement->errno) {
408 $err = $statement->error
409 ? $statement->error
410 : 'Mysqli Error No: ' . $statement->errno;
411
412 $this->wpdb->last_error = $err;
413
414 throw new QueryException(
415 $query, $bindings, new Exception($err)
416 );
417 }
418 }
419
420 /**
421 * Raw cursor query for MySQLi (non-prepared).
422 *
423 * @param string $query
424 * @param array $bindings
425 * @return \Generator
426 * @throws \FluentAccount\Framework\Database\QueryException
427 */
428 public function rawCursor($query, $bindings = [])
429 {
430 if (!empty($bindings)) {
431 $query = str_replace(['%', '?'], ['%%', '%s'], $query);
432 $query = $this->wpdb->prepare($query, ...$bindings);
433 }
434
435 if (!$this->wpdb->dbh instanceof \mysqli) {
436 foreach ($this->select($query) as $row) { yield $row; }
437 return;
438 }
439
440 $stmt = $this->wpdb->dbh->query($query, MYSQLI_USE_RESULT);
441
442 if ($stmt instanceof \mysqli_result) {
443 try {
444 while ($row = $stmt->fetch_assoc()) {
445 yield (object) $row;
446 }
447 } finally {
448 $stmt->free();
449 }
450 } elseif ($this->wpdb->dbh->error) {
451 throw new QueryException(
452 $query, $bindings, new Exception($this->wpdb->dbh->error)
453 );
454 }
455 }
456
457 /**
458 * Run an insert statement against the database.
459 *
460 * @param string $query
461 * @param array $bindings
462 * @return bool
463 */
464 public function insert($query, $bindings = [])
465 {
466 return $this->statement($query, $bindings);
467 }
468
469 /**
470 * Run an update statement against the database.
471 *
472 * @param string $query
473 * @param array $bindings
474 * @return int
475 */
476 public function update($query, $bindings = [])
477 {
478 return $this->affectingStatement($query, $bindings);
479 }
480
481 /**
482 * Run a delete statement against the database.
483 *
484 * @param string $query
485 * @param array $bindings
486 * @return int
487 */
488 public function delete($query, $bindings = [])
489 {
490 return $this->affectingStatement($query, $bindings);
491 }
492
493 /**
494 * Execute an SQL statement and return the boolean result.
495 *
496 * @param string $query
497 * @param array $bindings
498 * @return bool
499 */
500 public function statement($query, $bindings = [])
501 {
502 return $this->run($query, $bindings, function ($query, $bindings) {
503 $query = $this->bindParams($query, $bindings, true);
504
505 $result = $this->unprepared($query);
506
507 if ($result === false || $this->wpdb->last_error) {
508 throw new QueryException(
509 $query, $bindings, new Exception($this->wpdb->last_error)
510 );
511 }
512
513 return $result;
514 });
515 }
516
517 /**
518 * Run an SQL statement and get the number of rows affected.
519 *
520 * @param string $query
521 * @param array $bindings
522 * @return int
523 */
524 public function affectingStatement($query, $bindings = [])
525 {
526 return $this->run($query, $bindings, function ($query, $bindings) {
527 $query = $this->bindParams($query, $bindings, true);
528
529 $result = $this->wpdb->query($query);
530
531 if ($result === false || $this->wpdb->last_error) {
532 throw new QueryException(
533 $query, $bindings, new Exception($this->wpdb->last_error)
534 );
535 }
536
537 return intval($result);
538 });
539 }
540
541 /**
542 * Run a raw, unprepared query against the PDO connection.
543 *
544 * @param string $query
545 * @return bool
546 */
547 public function unprepared($query)
548 {
549 return $this->wpdb->query($query);
550 }
551
552 /**
553 * Execute the given callback in "dry run" mode.
554 *
555 * @param \Closure $callback
556 * @return array
557 */
558 public function pretend(Closure $callback)
559 {
560 // ...
561 }
562
563 /**
564 * Prepare the query bindings for execution.
565 *
566 * @param array $bindings
567 * @return array
568 */
569 public function prepareBindings(array $bindings)
570 {
571 $grammar = $this->getQueryGrammar();
572
573 foreach ($bindings as $key => $value) {
574 // We need to transform all instances of DateTimeInterface into
575 // the actual date string. Each query grammar maintains its
576 // own date string format so we'll just ask the grammar
577 // for the format to get from the date.
578 if ($value instanceof DateTimeInterface) {
579 $bindings[$key] = $value->format($grammar->getDateFormat());
580 } elseif (is_bool($value)) {
581 $bindings[$key] = (int)$value;
582 }
583 }
584
585 return $bindings;
586 }
587
588 /**
589 * Run a SQL statement and log its execution context.
590 *
591 * @param string $query
592 * @param array $bindings
593 * @param \Closure $callback
594 * @return mixed
595 */
596 public function run($query, $bindings, $callback)
597 {
598 $start = microtime(true);
599
600 try {
601 return $callback($query, $bindings);
602 } finally {
603 $time = $this->getElapsedTime($start);
604 $this->event->dispatch(
605 new QueryExecuted($query, $bindings, $time, $this)
606 );
607 }
608 }
609
610 /**
611 * Get a new raw query expression.
612 *
613 * @param mixed $value
614 * @return \FluentCart\Framework\Database\Query\Expression
615 */
616 public function raw($value)
617 {
618 return new Expression($value);
619 }
620
621 /**
622 * Get the query grammar used by the connection.
623 *
624 * @return \FluentCart\Framework\Database\Query\Grammars\Grammar
625 */
626 public function getQueryGrammar()
627 {
628 $this->queryGrammar->setTablePrefix($this->wpdb);
629
630 return $this->queryGrammar;
631 }
632
633 /**
634 * Set the query grammar used by the connection.
635 *
636 * @param \FluentCart\Framework\Database\Query\Grammars\Grammar $grammar
637 * @return $this
638 */
639 public function setQueryGrammar(Grammar $grammar)
640 {
641 $this->queryGrammar = $grammar;
642
643 return $this;
644 }
645
646 /**
647 * Get the query post processor used by the connection.
648 *
649 * @return \FluentCart\Framework\Database\Query\Processors\Processor
650 */
651 public function getPostProcessor()
652 {
653 return $this->postProcessor;
654 }
655
656 /**
657 * Set the query post processor used by the connection.
658 *
659 * @param \FluentCart\Framework\Database\Query\Processors\Processor $processor
660 * @return $this
661 */
662 public function setPostProcessor(Processor $processor)
663 {
664 $this->postProcessor = $processor;
665
666 return $this;
667 }
668
669 /**
670 * Return the last insert id
671 *
672 * @param string $args
673 *
674 * @return int
675 */
676 public function lastInsertId($args)
677 {
678 return $this->wpdb->insert_id;
679 }
680
681 /**
682 * Return self as PDO, the Processor instance uses it.
683 *
684 * @return \FluentCart\Framework\Database\Query\WPDBConnection
685 */
686 public function getPdo()
687 {
688 return $this;
689 }
690
691 /**
692 * Returns the $wpdb object.
693 *
694 * @return Object $wpdb
695 */
696 public function getWPDB()
697 {
698 return $this->wpdb;
699 }
700
701 /**
702 * Get the database connection name.
703 *
704 * @return string|null
705 */
706 public function getName()
707 {
708 return $this->isSqlite() ? 'sqlite' : 'mysql';
709 }
710
711 /**
712 * Get the name of the connected database.
713 *
714 * @return string
715 */
716 public function getDatabaseName()
717 {
718 return $this->wpdb->dbname;
719 }
720
721 /**
722 * Get the server version for the connection.
723 *
724 * @return string
725 */
726 public function getServerVersion(): string
727 {
728 return $this->getWPDB()->db_version();
729 }
730
731 /**
732 * Get the column listing for a given table.
733 *
734 * @param string $table
735 * @return array
736 */
737 public function getColumnListing($table)
738 {
739 return Schema::getColumns($table);
740 }
741
742 /**
743 * Alias for getColumnListing.
744 *
745 * @param string $t
746 * @return array
747 */
748 public function getColumns($t)
749 {
750 return $this->getColumnListing($t);
751 }
752
753 /**
754 * Determine if the connected database is a sqlite database.
755 *
756 * @return bool
757 */
758 public function isSqlite()
759 {
760 return Schema::isSqlite();
761 }
762
763 /**
764 * Determine if the connected database is a mariadb database.
765 *
766 * @return bool
767 */
768 public function isMaria()
769 {
770 return Schema::isMaria();
771 }
772
773 /**
774 * Register a hook to be run just before a database transaction is started.
775 *
776 * @param \Closure $callback
777 * @return $this
778 */
779 public function beforeStartingTransaction(Closure $callback)
780 {
781 $this->beforeStartingTransaction[] = $callback;
782
783 return $this;
784 }
785
786 /**
787 * Register a database query listener with the connection.
788 *
789 * @param \Closure $callback
790 * @return void
791 */
792 public function listen(Closure $callback)
793 {
794 $this->event->listen(QueryExecuted::class, $callback);
795 }
796
797 /**
798 * Fire an event for this connection.
799 *
800 * @param string $event
801 * @return array|null
802 */
803 protected function fireConnectionEvent($event)
804 {
805 if (!$this->event) {
806 return;
807 }
808
809 switch ($event) {
810 case 'beganTransaction':
811 $payload = new TransactionBeginning($this);
812 break;
813 case 'committed':
814 $payload = new TransactionCommitted($this);
815 break;
816 case 'committing':
817 $payload = new TransactionCommitting($this);
818 break;
819 case 'rollingBack':
820 $payload = new TransactionRolledBack($this);
821 break;
822 default:
823 $payload = null;
824 break;
825 }
826
827 if ($payload !== null) {
828 return $this->event->dispatch($payload);
829 }
830 }
831
832 /**
833 * Get the elapsed time since a given starting point.
834 *
835 * @param int $start
836 * @return float
837 */
838 protected function getElapsedTime($start)
839 {
840 return round((microtime(true) - $start) * 1000, 2);
841 }
842
843 /**
844 * Get the table prefix for the connection.
845 *
846 * @return [type] [description]
847 */
848 public function getTablePrefix()
849 {
850 if (!$this->tablePrefix) {
851 $this->tablePrefix = $this->queryGrammar->getTablePrefix();
852 }
853
854 return $this->tablePrefix;
855 }
856
857 /**
858 * Get the table name with the table prefix.
859 *
860 * @param string $table
861 * @return string
862 */
863 public function getTableName($table)
864 {
865 return $this->getTablePrefix() . $table;
866 }
867 }
868