PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 1.1.0
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v1.1.0
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
fluent-community / vendor / wpfluent / framework / src / WPFluent / Database / Query / WPDBConnection.php

WPDBConnection.php in FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses 1.1.0, at vendor/wpfluent/framework/src/WPFluent/Database/Query/WPDBConnection.php

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