PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 1.5.21
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v1.5.21
2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 1.7.2 All 33 releases
fluent-booking / vendor / wpfluent / framework / src / WPFluent / Database / Query / WPDBConnection.php

WPDBConnection.php in Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution 1.5.21, at vendor/wpfluent/framework/src/WPFluent/Database/Query/WPDBConnection.php

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