PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 1.0.92
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v1.0.92
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 1.1.0 All 77 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.0.92, at vendor/wpfluent/framework/src/WPFluent/Database/Query/WPDBConnection.php

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