· 9 years ago · Oct 21, 2016, 08:16 AM
1<?php
2
3namespace RedBeanPHP {
4
5/**
6 * RedBean Logging interface.
7 * Provides a uniform and convenient logging
8 * interface throughout RedBeanPHP.
9 *
10 * @file RedBean/Logging.php
11 * @author Gabor de Mooij and the RedBeanPHP Community
12 * @license BSD/GPLv2
13 *
14 * @copyright
15 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
16 * This source file is subject to the BSD/GPLv2 License that is bundled
17 * with this source code in the file license.txt.
18 */
19interface Logger
20{
21 /**
22 * A logger (for PDO or OCI driver) needs to implement the log method.
23 * The log method will receive logging data. Note that the number of parameters is 0, this means
24 * all parameters are optional and the number may vary. This way the logger can be used in a very
25 * flexible way. Sometimes the logger is used to log a simple error message and in other
26 * situations sql and bindings are passed.
27 * The log method should be able to accept all kinds of parameters and data by using
28 * functions like func_num_args/func_get_args.
29 *
30 * @param string $message, ...
31 * @return void
32 */
33 public function log();
34}
35}
36
37namespace RedBeanPHP\Logger {
38
39use RedBeanPHP\Logger as Logger;
40use RedBeanPHP\RedException as RedException;
41
42/**
43 * Logger. Provides a basic logging function for RedBeanPHP.
44 *
45 * @file RedBeanPHP/Logger.php
46 * @author Gabor de Mooij and the RedBeanPHP Community
47 * @license BSD/GPLv2
48 *
49 * @copyright
50 * copyright (c) G.J.G.T. (Gabor) de Mooij
51 * This source file is subject to the BSD/GPLv2 License that is bundled
52 * with this source code in the file license.txt.
53 */
54class RDefault implements Logger
55{
56 /**
57 * Logger modes
58 */
59 const C_LOGGER_ECHO = 0;
60 const C_LOGGER_ARRAY = 1;
61
62 /**
63 * @var integer
64 */
65 protected $mode = 0;
66
67 /**
68 * @var array
69 */
70 protected $logs = array();
71
72 /**
73 * Default logger method logging to STDOUT.
74 * This is the default/reference implementation of a logger.
75 * This method will write the message value to STDOUT (screen) unless
76 * you have changed the mode of operation to C_LOGGER_ARRAY.
77 *
78 * @param $message (optional) message to log (might also be data or output)
79 *
80 * @return void
81 */
82 public function log()
83 {
84 if ( func_num_args() < 1 ) return;
85
86 foreach ( func_get_args() as $argument ) {
87 if ( is_array( $argument ) ) {
88 $log = print_r( $argument, TRUE );
89 if ( $this->mode === self::C_LOGGER_ECHO ) {
90 echo $log;
91 } else {
92 $this->logs[] = $log;
93 }
94 } else {
95 if ( $this->mode === self::C_LOGGER_ECHO ) {
96 echo $argument;
97 } else {
98 $this->logs[] = $argument;
99 }
100 }
101
102 if ( $this->mode === self::C_LOGGER_ECHO ) echo "<br>" . PHP_EOL;
103 }
104 }
105
106 /**
107 * Returns the internal log array.
108 * The internal log array is where all log messages are stored.
109 *
110 * @return array
111 */
112 public function getLogs()
113 {
114 return $this->logs;
115 }
116
117 /**
118 * Clears the internal log array, removing all
119 * previously stored entries.
120 *
121 * @return self
122 */
123 public function clear()
124 {
125 $this->logs = array();
126 return $this;
127 }
128
129 /**
130 * Selects a logging mode.
131 * There are several options available.
132 *
133 * * C_LOGGER_ARRAY - log silently, stores entries in internal log array only
134 * * C_LOGGER_ECHO - also forward log messages directly to STDOUT
135 *
136 * @param integer $mode mode of operation for logging object
137 *
138 * @return self
139 */
140 public function setMode( $mode )
141 {
142 if ($mode !== self::C_LOGGER_ARRAY && $mode !== self::C_LOGGER_ECHO ) {
143 throw new RedException( 'Invalid mode selected for logger, use C_LOGGER_ARRAY or C_LOGGER_ECHO.' );
144 }
145 $this->mode = $mode;
146 return $this;
147 }
148
149 /**
150 * Searches for all log entries in internal log array
151 * for $needle and returns those entries.
152 * This method will return an array containing all matches for your
153 * search query.
154 *
155 * @param string $needle phrase to look for in internal log array
156 *
157 * @return array
158 */
159 public function grep( $needle )
160 {
161 $found = array();
162 foreach( $this->logs as $logEntry ) {
163 if ( strpos( $logEntry, $needle ) !== FALSE ) $found[] = $logEntry;
164 }
165 return $found;
166 }
167}
168}
169
170namespace RedBeanPHP\Logger\RDefault {
171
172use RedBeanPHP\Logger as Logger;
173use RedBeanPHP\Logger\RDefault as RDefault;
174use RedBeanPHP\RedException as RedException;
175
176/**
177 * Debug logger.
178 * A special logger for debugging purposes.
179 * Provides debugging logging functions for RedBeanPHP.
180 *
181 * @file RedBeanPHP/Logger/RDefault/Debug.php
182 * @author Gabor de Mooij and the RedBeanPHP Community
183 * @license BSD/GPLv2
184 *
185 * @copyright
186 * copyright (c) G.J.G.T. (Gabor) de Mooij
187 * This source file is subject to the BSD/GPLv2 License that is bundled
188 * with this source code in the file license.txt.
189 */
190class Debug extends RDefault implements Logger
191{
192 /**
193 * @var integer
194 */
195 private $strLen = 40;
196
197 /**
198 * Writes a query for logging with all bindings / params filled
199 * in.
200 *
201 * @param string $newSql the query
202 * @param array $bindings the bindings to process (key-value pairs)
203 *
204 * @return string
205 */
206 private function writeQuery( $newSql, $newBindings )
207 {
208 //avoid str_replace collisions: slot1 and slot10 (issue 407).
209 uksort( $newBindings, function( $a, $b ) {
210 return ( strlen( $b ) - strlen( $a ) );
211 } );
212
213 $newStr = $newSql;
214 foreach( $newBindings as $slot => $value ) {
215 if ( strpos( $slot, ':' ) === 0 ) {
216 $newStr = str_replace( $slot, $this->fillInValue( $value ), $newStr );
217 }
218 }
219 return $newStr;
220 }
221
222 /**
223 * Fills in a value of a binding and truncates the
224 * resulting string if necessary.
225 *
226 * @param mixed $value bound value
227 *
228 * @return string
229 */
230 protected function fillInValue( $value )
231 {
232 if ( is_null( $value ) ) $value = 'NULL';
233
234 $value = strval( $value );
235 if ( strlen( $value ) > ( $this->strLen ) ) {
236 $value = substr( $value, 0, ( $this->strLen ) ).'... ';
237 }
238
239 if ( !is_numeric( $value ) && $value !== 'NULL') {
240 $value = '\''.$value.'\'';
241 }
242
243 return $value;
244 }
245
246 /**
247 * Dependending on the current mode of operation,
248 * this method will either log and output to STDIN or
249 * just log.
250 *
251 * Depending on the value of constant PHP_SAPI this function
252 * will format output for console or HTML.
253 *
254 * @param string $str string to log or output and log
255 *
256 * @return void
257 */
258 protected function output( $str )
259 {
260 $this->logs[] = $str;
261 if ( !$this->mode ) {
262 $highlight = FALSE;
263 /* just a quick heuritsic to highlight schema changes */
264 if ( strpos( $str, 'CREATE' ) === 0
265 || strpos( $str, 'ALTER' ) === 0
266 || strpos( $str, 'DROP' ) === 0) {
267 $highlight = TRUE;
268 }
269 if (PHP_SAPI === 'cli') {
270 if ($highlight) echo "\e[91m";
271 echo $str, PHP_EOL;
272 echo "\e[39m";
273 } else {
274 if ($highlight) {
275 echo "<b style=\"color:red\">{$str}</b>";
276 } else {
277 echo $str;
278 }
279 echo '<br />';
280 }
281 }
282 }
283
284 /**
285 * Normalizes the slots in an SQL string.
286 * Replaces question mark slots with :slot1 :slot2 etc.
287 *
288 * @param string $sql sql to normalize
289 *
290 * @return string
291 */
292 protected function normalizeSlots( $sql )
293 {
294 $i = 0;
295 $newSql = $sql;
296 while($i < 20 && strpos($newSql, '?') !== FALSE ){
297 $pos = strpos( $newSql, '?' );
298 $slot = ':slot'.$i;
299 $begin = substr( $newSql, 0, $pos );
300 $end = substr( $newSql, $pos+1 );
301 if (PHP_SAPI === 'cli') {
302 $newSql = "{$begin}\e[32m{$slot}\e[39m{$end}";
303 } else {
304 $newSql = "{$begin}<b style=\"color:green\">$slot</b>{$end}";
305 }
306 $i++;
307 }
308 return $newSql;
309 }
310
311 /**
312 * Normalizes the bindings.
313 * Replaces numeric binding keys with :slot1 :slot2 etc.
314 *
315 * @param array $bindings bindings to normalize
316 *
317 * @return array
318 */
319 protected function normalizeBindings( $bindings )
320 {
321 $i = 0;
322 $newBindings = array();
323 foreach( $bindings as $key => $value ) {
324 if ( is_numeric($key) ) {
325 $newKey = ':slot'.$i;
326 $newBindings[$newKey] = $value;
327 $i++;
328 } else {
329 $newBindings[$key] = $value;
330 }
331 }
332 return $newBindings;
333 }
334
335 /**
336 * Logger method.
337 *
338 * Takes a number of arguments tries to create
339 * a proper debug log based on the available data.
340 *
341 * @return void
342 */
343 public function log()
344 {
345 if ( func_num_args() < 1 ) return;
346
347 $sql = func_get_arg( 0 );
348
349 if ( func_num_args() < 2) {
350 $bindings = array();
351 } else {
352 $bindings = func_get_arg( 1 );
353 }
354
355 if ( !is_array( $bindings ) ) {
356 return $this->output( $sql );
357 }
358
359 $newSql = $this->normalizeSlots( $sql );
360 $newBindings = $this->normalizeBindings( $bindings );
361 $newStr = $this->writeQuery( $newSql, $newBindings );
362 $this->output( $newStr );
363 }
364
365 /**
366 * Sets the max string length for the parameter output in
367 * SQL queries. Set this value to a reasonable number to
368 * keep you SQL queries readable.
369 *
370 * @param integer $len string length
371 *
372 * @return self
373 */
374 public function setParamStringLength( $len = 20 )
375 {
376 $this->strLen = max(0, $len);
377 return $this;
378 }
379}
380}
381
382namespace RedBeanPHP {
383
384/**
385 * Interface for database drivers.
386 * The Driver API conforms to the ADODB pseudo standard
387 * for database drivers.
388 *
389 * @file RedBeanPHP/Driver.php
390 * @author Gabor de Mooij and the RedBeanPHP Community
391 * @license BSD/GPLv2
392 *
393 * @copyright
394 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
395 * This source file is subject to the BSD/GPLv2 License that is bundled
396 * with this source code in the file license.txt.
397 */
398interface Driver
399{
400 /**
401 * Runs a query and fetches results as a multi dimensional array.
402 *
403 * @param string $sql SQL query to execute
404 * @param array $bindings list of values to bind to SQL snippet
405 *
406 * @return array
407 */
408 public function GetAll( $sql, $bindings = array() );
409
410 /**
411 * Runs a query and fetches results as a column.
412 *
413 * @param string $sql SQL query to execute
414 * @param array $bindings list of values to bind to SQL snippet
415 *
416 * @return array
417 */
418 public function GetCol( $sql, $bindings = array() );
419
420 /**
421 * Runs a query and returns results as a single cell.
422 *
423 * @param string $sql SQL query to execute
424 * @param array $bindings list of values to bind to SQL snippet
425 *
426 * @return mixed
427 */
428 public function GetOne( $sql, $bindings = array() );
429
430 /**
431 * Runs a query and returns results as an associative array
432 * indexed by the first column.
433 *
434 * @param string $sql SQL query to execute
435 * @param array $bindings list of values to bind to SQL snippet
436 *
437 * @return mixed
438 */
439 public function GetAssocRow( $sql, $bindings = array() );
440
441 /**
442 * Runs a query and returns a flat array containing the values of
443 * one row.
444 *
445 * @param string $sql SQL query to execute
446 * @param array $bindings list of values to bind to SQL snippet
447 *
448 * @return array
449 */
450 public function GetRow( $sql, $bindings = array() );
451
452 /**
453 * Executes SQL code and allows key-value binding.
454 * This function allows you to provide an array with values to bind
455 * to query parameters. For instance you can bind values to question
456 * marks in the query. Each value in the array corresponds to the
457 * question mark in the query that matches the position of the value in the
458 * array. You can also bind values using explicit keys, for instance
459 * array(":key"=>123) will bind the integer 123 to the key :key in the
460 * SQL. This method has no return value.
461 *
462 * @param string $sql SQL query to execute
463 * @param array $bindings list of values to bind to SQL snippet
464 *
465 * @return array Affected Rows
466 */
467 public function Execute( $sql, $bindings = array() );
468
469 /**
470 * Returns the latest insert ID if driver does support this
471 * feature.
472 *
473 * @return integer
474 */
475 public function GetInsertID();
476
477 /**
478 * Returns the number of rows affected by the most recent query
479 * if the currently selected driver driver supports this feature.
480 *
481 * @return integer
482 */
483 public function Affected_Rows();
484
485 /**
486 * Returns a cursor-like object from the database.
487 *
488 * @param string $sql SQL query to execute
489 * @param array $bindings list of values to bind to SQL snippet
490 *
491 * @return mixed
492 */
493 public function GetCursor( $sql, $bindings = array() );
494
495 /**
496 * Toggles debug mode. In debug mode the driver will print all
497 * SQL to the screen together with some information about the
498 * results. All SQL code that passes through the driver will be
499 * passes on to the screen for inspection.
500 * This method has no return value.
501 *
502 * @param boolean $tf TRUE = debug mode ON
503 * @param Logger $customLogger
504 *
505 * @return void
506 */
507 public function setDebugMode( $tf, $customLogger );
508
509 /**
510 * Starts a transaction.
511 *
512 * @return void
513 */
514 public function CommitTrans();
515
516 /**
517 * Commits a transaction.
518 *
519 * @return void
520 */
521 public function StartTrans();
522
523 /**
524 * Rolls back a transaction.
525 *
526 * @return void
527 */
528 public function FailTrans();
529
530 /**
531 * Resets the internal Query Counter.
532 *
533 * @return self
534 */
535 public function resetCounter();
536
537 /**
538 * Returns the number of SQL queries processed.
539 *
540 * @return integer
541 */
542 public function getQueryCount();
543}
544}
545
546namespace RedBeanPHP\Driver {
547
548use RedBeanPHP\Driver as Driver;
549use RedBeanPHP\Logger as Logger;
550use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
551use RedBeanPHP\RedException as RedException;
552use RedBeanPHP\RedException\SQL as SQL;
553use RedBeanPHP\Logger\RDefault as RDefault;
554use RedBeanPHP\PDOCompatible as PDOCompatible;
555use RedBeanPHP\Cursor\PDOCursor as PDOCursor;
556
557/**
558 * PDO Driver
559 * This Driver implements the RedBean Driver API.
560 * for RedBeanPHP. This is the standard / default database driver
561 * for RedBeanPHP.
562 *
563 * @file RedBeanPHP/PDO.php
564 * @author Gabor de Mooij and the RedBeanPHP Community, Desfrenes
565 * @license BSD/GPLv2
566 *
567 * @copyright
568 * copyright (c) Desfrenes & Gabor de Mooij and the RedBeanPHP community
569 * This source file is subject to the BSD/GPLv2 License that is bundled
570 * with this source code in the file license.txt.
571 */
572class RPDO implements Driver
573{
574 /**
575 * @var integer
576 */
577 protected $max;
578
579 /**
580 * @var string
581 */
582 protected $dsn;
583
584 /**
585 * @var boolean
586 */
587 protected $loggingEnabled = FALSE;
588
589 /**
590 * @var Logger
591 */
592 protected $logger = NULL;
593
594 /**
595 * @var PDO
596 */
597 protected $pdo;
598
599 /**
600 * @var integer
601 */
602 protected $affectedRows;
603
604 /**
605 * @var integer
606 */
607 protected $resultArray;
608
609 /**
610 * @var array
611 */
612 protected $connectInfo = array();
613
614 /**
615 * @var boolean
616 */
617 protected $isConnected = FALSE;
618
619 /**
620 * @var bool
621 */
622 protected $flagUseStringOnlyBinding = FALSE;
623
624 /**
625 * @var integer
626 */
627 protected $queryCounter = 0;
628
629 /**
630 * @var string
631 */
632 protected $mysqlEncoding = '';
633
634 /**
635 * Binds parameters. This method binds parameters to a PDOStatement for
636 * Query Execution. This method binds parameters as NULL, INTEGER or STRING
637 * and supports both named keys and question mark keys.
638 *
639 * @param PDOStatement $statement PDO Statement instance
640 * @param array $bindings values that need to get bound to the statement
641 *
642 * @return void
643 */
644 protected function bindParams( $statement, $bindings )
645 {
646 foreach ( $bindings as $key => &$value ) {
647 if ( is_integer( $key ) ) {
648 if ( is_null( $value ) ) {
649 $statement->bindValue( $key + 1, NULL, \PDO::PARAM_NULL );
650 } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
651 $statement->bindParam( $key + 1, $value, \PDO::PARAM_INT );
652 } else {
653 $statement->bindParam( $key + 1, $value, \PDO::PARAM_STR );
654 }
655 } else {
656 if ( is_null( $value ) ) {
657 $statement->bindValue( $key, NULL, \PDO::PARAM_NULL );
658 } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
659 $statement->bindParam( $key, $value, \PDO::PARAM_INT );
660 } else {
661 $statement->bindParam( $key, $value, \PDO::PARAM_STR );
662 }
663 }
664 }
665 }
666
667 /**
668 * This method runs the actual SQL query and binds a list of parameters to the query.
669 * slots. The result of the query will be stored in the protected property
670 * $rs (always array). The number of rows affected (result of rowcount, if supported by database)
671 * is stored in protected property $affectedRows. If the debug flag is set
672 * this function will send debugging output to screen buffer.
673 *
674 * @param string $sql the SQL string to be send to database server
675 * @param array $bindings the values that need to get bound to the query slots
676 * @param array $options
677 *
678 * @return mixed
679 * @throws SQL
680 */
681 protected function runQuery( $sql, $bindings, $options = array() )
682 {
683 $this->connect();
684 if ( $this->loggingEnabled && $this->logger ) {
685 $this->logger->log( $sql, $bindings );
686 }
687 try {
688 if ( strpos( 'pgsql', $this->dsn ) === 0 ) {
689 if ( defined( '\PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT' ) ) {
690 $statement = $this->pdo->prepare( $sql, array( \PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => TRUE ) );
691 } else {
692 $statement = $this->pdo->prepare( $sql );
693 }
694 } else {
695 $statement = $this->pdo->prepare( $sql );
696 }
697 $this->bindParams( $statement, $bindings );
698 $statement->execute();
699 $this->queryCounter ++;
700 $this->affectedRows = $statement->rowCount();
701 if ( $statement->columnCount() ) {
702 $fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL;
703 if ( isset( $options['noFetch'] ) && $options['noFetch'] ) {
704 $this->resultArray = array();
705 return $statement;
706 }
707 $this->resultArray = $statement->fetchAll( $fetchStyle );
708 if ( $this->loggingEnabled && $this->logger ) {
709 $this->logger->log( 'resultset: ' . count( $this->resultArray ) . ' rows' );
710 }
711 } else {
712 $this->resultArray = array();
713 }
714 } catch ( \PDOException $e ) {
715 //Unfortunately the code field is supposed to be int by default (php)
716 //So we need a property to convey the SQL State code.
717 $err = $e->getMessage();
718 if ( $this->loggingEnabled && $this->logger ) $this->logger->log( 'An error occurred: ' . $err );
719 $exception = new SQL( $err, 0 );
720 $exception->setSQLState( $e->getCode() );
721 throw $exception;
722 }
723 }
724
725 /**
726 * Try to fix MySQL character encoding problems.
727 * MySQL < 5.5 does not support proper 4 byte unicode but they
728 * seem to have added it with version 5.5 under a different label: utf8mb4.
729 * We try to select the best possible charset based on your version data.
730 *
731 * @return void
732 */
733 protected function setEncoding()
734 {
735 $driver = $this->pdo->getAttribute( \PDO::ATTR_DRIVER_NAME );
736 $version = floatval( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) );
737 if ($driver === 'mysql') {
738 $encoding = ($version >= 5.5) ? 'utf8mb4' : 'utf8';
739 $this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '.$encoding ); //on every re-connect
740 $this->pdo->exec(' SET NAMES '. $encoding); //also for current connection
741 $this->mysqlEncoding = $encoding;
742 }
743 }
744
745 /**
746 * Constructor. You may either specify dsn, user and password or
747 * just give an existing PDO connection.
748 *
749 * Examples:
750 * $driver = new RPDO($dsn, $user, $password);
751 * $driver = new RPDO($existingConnection);
752 *
753 * @param string|object $dsn database connection string
754 * @param string $user optional, usename to sign in
755 * @param string $pass optional, password for connection login
756 *
757 * @return void
758 */
759 public function __construct( $dsn, $user = NULL, $pass = NULL )
760 {
761 if ( is_object( $dsn ) ) {
762 $this->pdo = $dsn;
763 $this->isConnected = TRUE;
764 $this->setEncoding();
765 $this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION );
766 $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC );
767 // make sure that the dsn at least contains the type
768 $this->dsn = $this->getDatabaseType();
769 } else {
770 $this->dsn = $dsn;
771 $this->connectInfo = array( 'pass' => $pass, 'user' => $user );
772 }
773
774 //PHP 5.3 PDO SQLite has a bug with large numbers:
775 if ( ( strpos( $this->dsn, 'sqlite' ) === 0 && PHP_MAJOR_VERSION === 5 && PHP_MINOR_VERSION === 3 ) || defined('HHVM_VERSION') || $this->dsn === 'test-sqlite-53' ) {
776 $this->max = 2147483647; //otherwise you get -2147483648 ?! demonstrated in build #603 on Travis.
777 } elseif ( strpos( $this->dsn, 'cubrid' ) === 0 ) {
778 $this->max = 2147483647; //bindParam in pdo_cubrid also fails...
779 } else {
780 $this->max = PHP_INT_MAX; //the normal value of course (makes it possible to use large numbers in LIMIT clause)
781 }
782 }
783
784 /**
785 * Returns the best possible encoding for MySQL based on version data.
786 *
787 * @return string
788 */
789 public function getMysqlEncoding()
790 {
791 return $this->mysqlEncoding;
792 }
793
794 /**
795 * Whether to bind all parameters as strings.
796 * If set to TRUE this will cause all integers to be bound as STRINGS.
797 * This will NOT affect NULL values.
798 *
799 * @param boolean $yesNo pass TRUE to bind all parameters as strings.
800 *
801 * @return void
802 */
803 public function setUseStringOnlyBinding( $yesNo )
804 {
805 $this->flagUseStringOnlyBinding = (boolean) $yesNo;
806 }
807
808 /**
809 * Sets the maximum value to be bound as integer, normally
810 * this value equals PHP's MAX INT constant, however sometimes
811 * PDO driver bindings cannot bind large integers as integers.
812 * This method allows you to manually set the max integer binding
813 * value to manage portability/compatibility issues among different
814 * PHP builds. This method will return the old value.
815 *
816 * @param integer $max maximum value for integer bindings
817 *
818 * @return integer
819 */
820 public function setMaxIntBind( $max )
821 {
822 if ( !is_integer( $max ) ) throw new RedException( 'Parameter has to be integer.' );
823 $oldMax = $this->max;
824 $this->max = $max;
825 return $oldMax;
826 }
827
828 /**
829 * Establishes a connection to the database using PHP\PDO
830 * functionality. If a connection has already been established this
831 * method will simply return directly. This method also turns on
832 * UTF8 for the database and PDO-ERRMODE-EXCEPTION as well as
833 * PDO-FETCH-ASSOC.
834 *
835 * @return void
836 */
837 public function connect()
838 {
839 if ( $this->isConnected ) return;
840 try {
841 $user = $this->connectInfo['user'];
842 $pass = $this->connectInfo['pass'];
843 $this->pdo = new \PDO(
844 $this->dsn,
845 $user,
846 $pass
847 );
848 $this->setEncoding();
849 $this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, TRUE );
850 //cant pass these as argument to constructor, CUBRID driver does not understand...
851 $this->pdo->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION );
852 $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC );
853 $this->isConnected = TRUE;
854 } catch ( \PDOException $exception ) {
855 $matches = array();
856 $dbname = ( preg_match( '/dbname=(\w+)/', $this->dsn, $matches ) ) ? $matches[1] : '?';
857 throw new \PDOException( 'Could not connect to database (' . $dbname . ').', $exception->getCode() );
858 }
859 }
860
861 /**
862 * Directly sets PDO instance into driver.
863 * This method might improve performance, however since the driver does
864 * not configure this instance terrible things may happen... only use
865 * this method if you are an expert on RedBeanPHP, PDO and UTF8 connections and
866 * you know your database server VERY WELL.
867 *
868 * @param PDO $pdo PDO instance
869 *
870 * @return void
871 */
872 public function setPDO( \PDO $pdo ) {
873 $this->pdo = $pdo;
874 }
875
876 /**
877 * @see Driver::GetAll
878 */
879 public function GetAll( $sql, $bindings = array() )
880 {
881 $this->runQuery( $sql, $bindings );
882 return $this->resultArray;
883 }
884
885 /**
886 * @see Driver::GetAssocRow
887 */
888 public function GetAssocRow( $sql, $bindings = array() )
889 {
890 $this->runQuery( $sql, $bindings, array(
891 'fetchStyle' => \PDO::FETCH_ASSOC
892 )
893 );
894 return $this->resultArray;
895 }
896
897 /**
898 * @see Driver::GetCol
899 */
900 public function GetCol( $sql, $bindings = array() )
901 {
902 $rows = $this->GetAll( $sql, $bindings );
903 $cols = array();
904 if ( $rows && is_array( $rows ) && count( $rows ) > 0 ) {
905 foreach ( $rows as $row ) {
906 $cols[] = array_shift( $row );
907 }
908 }
909
910 return $cols;
911 }
912
913 /**
914 * @see Driver::GetOne
915 */
916 public function GetOne( $sql, $bindings = array() )
917 {
918 $arr = $this->GetAll( $sql, $bindings );
919 $res = NULL;
920 if ( !is_array( $arr ) ) return NULL;
921 if ( count( $arr ) === 0 ) return NULL;
922 $row1 = array_shift( $arr );
923 if ( !is_array( $row1 ) ) return NULL;
924 if ( count( $row1 ) === 0 ) return NULL;
925 $col1 = array_shift( $row1 );
926 return $col1;
927 }
928
929 /**
930 * Alias for getOne().
931 * Backward compatibility.
932 *
933 * @param string $sql SQL
934 * @param array $bindings bindings
935 *
936 * @return mixed
937 */
938 public function GetCell( $sql, $bindings = array() )
939 {
940 return $this->GetOne( $sql, $bindings );
941 }
942
943 /**
944 * @see Driver::GetRow
945 */
946 public function GetRow( $sql, $bindings = array() )
947 {
948 $arr = $this->GetAll( $sql, $bindings );
949 return array_shift( $arr );
950 }
951
952 /**
953 * @see Driver::Excecute
954 */
955 public function Execute( $sql, $bindings = array() )
956 {
957 $this->runQuery( $sql, $bindings );
958 return $this->affectedRows;
959 }
960
961 /**
962 * @see Driver::GetInsertID
963 */
964 public function GetInsertID()
965 {
966 $this->connect();
967
968 return (int) $this->pdo->lastInsertId();
969 }
970
971 /**
972 * @see Driver::GetCursor
973 */
974 public function GetCursor( $sql, $bindings = array() )
975 {
976 $statement = $this->runQuery( $sql, $bindings, array( 'noFetch' => TRUE ) );
977 $cursor = new PDOCursor( $statement, \PDO::FETCH_ASSOC );
978 return $cursor;
979 }
980
981 /**
982 * @see Driver::Affected_Rows
983 */
984 public function Affected_Rows()
985 {
986 $this->connect();
987 return (int) $this->affectedRows;
988 }
989
990 /**
991 * Toggles debug mode. In debug mode the driver will print all
992 * SQL to the screen together with some information about the
993 * results.
994 *
995 * @param boolean $trueFalse turn on/off
996 * @param Logger $logger logger instance
997 *
998 * @return void
999 */
1000 public function setDebugMode( $tf, $logger = NULL )
1001 {
1002 $this->connect();
1003 $this->loggingEnabled = (bool) $tf;
1004 if ( $this->loggingEnabled and !$logger ) {
1005 $logger = new RDefault();
1006 }
1007 $this->setLogger( $logger );
1008 }
1009
1010 /**
1011 * Injects Logger object.
1012 * Sets the logger instance you wish to use.
1013 *
1014 * @param Logger $logger the logger instance to be used for logging
1015 *
1016 * @return void
1017 */
1018 public function setLogger( Logger $logger )
1019 {
1020 $this->logger = $logger;
1021 }
1022
1023 /**
1024 * Gets Logger object.
1025 * Returns the currently active Logger instance.
1026 *
1027 * @return Logger
1028 */
1029 public function getLogger()
1030 {
1031 return $this->logger;
1032 }
1033
1034 /**
1035 * @see Driver::StartTrans
1036 */
1037 public function StartTrans()
1038 {
1039 $this->connect();
1040 $this->pdo->beginTransaction();
1041 }
1042
1043 /**
1044 * @see Driver::CommitTrans
1045 */
1046 public function CommitTrans()
1047 {
1048 $this->connect();
1049 $this->pdo->commit();
1050 }
1051
1052 /**
1053 * @see Driver::FailTrans
1054 */
1055 public function FailTrans()
1056 {
1057 $this->connect();
1058 $this->pdo->rollback();
1059 }
1060
1061 /**
1062 * Returns the name of database driver for PDO.
1063 * Uses the PDO attribute DRIVER NAME to obtain the name of the
1064 * PDO driver.
1065 *
1066 * @return string
1067 */
1068 public function getDatabaseType()
1069 {
1070 $this->connect();
1071
1072 return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME );
1073 }
1074
1075 /**
1076 * Returns the version number of the database.
1077 *
1078 * @return mixed
1079 */
1080 public function getDatabaseVersion()
1081 {
1082 $this->connect();
1083 return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
1084 }
1085
1086 /**
1087 * Returns the underlying PHP PDO instance.
1088 *
1089 * @return PDO
1090 */
1091 public function getPDO()
1092 {
1093 $this->connect();
1094 return $this->pdo;
1095 }
1096
1097 /**
1098 * Closes database connection by destructing PDO.
1099 *
1100 * @return void
1101 */
1102 public function close()
1103 {
1104 $this->pdo = NULL;
1105 $this->isConnected = FALSE;
1106 }
1107
1108 /**
1109 * Returns TRUE if the current PDO instance is connected.
1110 *
1111 * @return boolean
1112 */
1113 public function isConnected()
1114 {
1115 return $this->isConnected && $this->pdo;
1116 }
1117
1118 /**
1119 * Toggles logging, enables or disables logging.
1120 *
1121 * @param boolean $enable TRUE to enable logging
1122 *
1123 * @return self
1124 */
1125 public function setEnableLogging( $enable )
1126 {
1127 $this->loggingEnabled = (boolean) $enable;
1128 }
1129
1130 /**
1131 * Resets the internal Query Counter.
1132 *
1133 * @return self
1134 */
1135 public function resetCounter()
1136 {
1137 $this->queryCounter = 0;
1138 return $this;
1139 }
1140
1141 /**
1142 * Returns the number of SQL queries processed.
1143 *
1144 * @return integer
1145 */
1146 public function getQueryCount()
1147 {
1148 return $this->queryCounter;
1149 }
1150
1151 /**
1152 * Returns the maximum value treated as integer parameter
1153 * binding.
1154 *
1155 * This method is mainly for testing purposes but it can help
1156 * you solve some issues relating to integer bindings.
1157 *
1158 * @return integer
1159 */
1160 public function getIntegerBindingMax()
1161 {
1162 return $this->max;
1163 }
1164}
1165}
1166
1167namespace RedBeanPHP {
1168
1169use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
1170use RedBeanPHP\BeanHelper as BeanHelper;
1171use RedBeanPHP\RedException as RedException;
1172
1173/* PHP 5.3 compatibility */
1174if (interface_exists('\JsonSerializable')) {
1175 /* We extend JsonSerializable to avoid namespace conflicts,
1176 can't define interface with special namespace in PHP */
1177 interface Jsonable extends \JsonSerializable {};
1178} else {
1179 interface Jsonable {};
1180}
1181
1182/**
1183 * OODBBean (Object Oriented DataBase Bean).
1184 *
1185 * to exchange information with the database. A bean represents
1186 * a single table row and offers generic services for interaction
1187 * with databases systems as well as some meta-data.
1188 *
1189 * @file RedBeanPHP/OODBBean.php
1190 * @author Gabor de Mooij and the RedBeanPHP community
1191 * @license BSD/GPLv2
1192 * @desc OODBBean represents a bean. RedBeanPHP uses beans
1193 *
1194 * @copyright
1195 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
1196 * This source file is subject to the BSD/GPLv2 License that is bundled
1197 * with this source code in the file license.txt.
1198 */
1199class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable,Jsonable
1200{
1201 /**
1202 * FUSE error modes.
1203 */
1204 const C_ERR_IGNORE = FALSE;
1205 const C_ERR_LOG = 1;
1206 const C_ERR_NOTICE = 2;
1207 const C_ERR_WARN = 3;
1208 const C_ERR_EXCEPTION = 4;
1209 const C_ERR_FUNC = 5;
1210 const C_ERR_FATAL = 6;
1211
1212 /**
1213 * @var boolean
1214 */
1215 protected static $errorHandlingFUSE = FALSE;
1216
1217 /**
1218 * @var callable|NULL
1219 */
1220 protected static $errorHandler = NULL;
1221
1222 /**
1223 * @var array
1224 */
1225 protected static $aliases = array();
1226
1227 /**
1228 * @var boolean
1229 */
1230 protected static $autoResolve = FALSE;
1231
1232 /**
1233 * This is where the real properties of the bean live. They are stored and retrieved
1234 * by the magic getter and setter (__get and __set).
1235 *
1236 * @var array $properties
1237 */
1238 protected $properties = array();
1239
1240 /**
1241 * Here we keep the meta data of a bean.
1242 *
1243 * @var array
1244 */
1245 protected $__info = array();
1246
1247 /**
1248 * The BeanHelper allows the bean to access the toolbox objects to implement
1249 * rich functionality, otherwise you would have to do everything with R or
1250 * external objects.
1251 *
1252 * @var BeanHelper
1253 */
1254 protected $beanHelper = NULL;
1255
1256 /**
1257 * @var null
1258 */
1259 protected $fetchType = NULL;
1260
1261 /**
1262 * @var string
1263 */
1264 protected $withSql = '';
1265
1266 /**
1267 * @var array
1268 */
1269 protected $withParams = array();
1270
1271 /**
1272 * @var string
1273 */
1274 protected $aliasName = NULL;
1275
1276 /**
1277 * @var string
1278 */
1279 protected $via = NULL;
1280
1281 /**
1282 * @var boolean
1283 */
1284 protected $noLoad = FALSE;
1285
1286 /**
1287 * @var boolean
1288 */
1289 protected $all = FALSE;
1290
1291 /**
1292 * Sets the error mode for FUSE.
1293 * What to do if a FUSE model method does not exist?
1294 * You can set the following options:
1295 *
1296 * * OODBBean::C_ERR_IGNORE (default), ignores the call, returns NULL
1297 * * OODBBean::C_ERR_LOG, logs the incident using error_log
1298 * * OODBBean::C_ERR_NOTICE, triggers a E_USER_NOTICE
1299 * * OODBBean::C_ERR_WARN, triggers a E_USER_WARNING
1300 * * OODBBean::C_ERR_EXCEPTION, throws an exception
1301 * * OODBBean::C_ERR_FUNC, allows you to specify a custom handler (function)
1302 * * OODBBean::C_ERR_FATAL, triggers a E_USER_ERROR
1303 *
1304 * <code>
1305 * Custom handler method signature: handler( array (
1306 * 'message' => string
1307 * 'bean' => OODBBean
1308 * 'method' => string
1309 * ) )
1310 * </code>
1311 *
1312 * This method returns the old mode and handler as an array.
1313 *
1314 * @param integer $mode error handling mode
1315 * @param callable|NULL $func custom handler
1316 *
1317 * @return array
1318 */
1319 public static function setErrorHandlingFUSE($mode, $func = NULL) {
1320 if (
1321 $mode !== self::C_ERR_IGNORE
1322 && $mode !== self::C_ERR_LOG
1323 && $mode !== self::C_ERR_NOTICE
1324 && $mode !== self::C_ERR_WARN
1325 && $mode !== self::C_ERR_EXCEPTION
1326 && $mode !== self::C_ERR_FUNC
1327 && $mode !== self::C_ERR_FATAL
1328 ) throw new \Exception( 'Invalid error mode selected' );
1329
1330 if ( $mode === self::C_ERR_FUNC && !is_callable( $func ) ) {
1331 throw new \Exception( 'Invalid error handler' );
1332 }
1333
1334 $old = array( self::$errorHandlingFUSE, self::$errorHandler );
1335 self::$errorHandlingFUSE = $mode;
1336 if ( is_callable( $func ) ) {
1337 self::$errorHandler = $func;
1338 } else {
1339 self::$errorHandler = NULL;
1340 }
1341 return $old;
1342 }
1343
1344 /**
1345 * Sets global aliases.
1346 * Registers a batch of aliases in one go. This works the same as
1347 * fetchAs and setAutoResolve but explicitly. For instance if you register
1348 * the alias 'cover' for 'page' a property containing a reference to a
1349 * page bean called 'cover' will correctly return the page bean and not
1350 * a (non-existant) cover bean.
1351 *
1352 * <code>
1353 * R::aliases( array( 'cover' => 'page' ) );
1354 * $book = R::dispense( 'book' );
1355 * $page = R::dispense( 'page' );
1356 * $book->cover = $page;
1357 * R::store( $book );
1358 * $book = $book->fresh();
1359 * $cover = $book->cover;
1360 * echo $cover->getMeta( 'type' ); //page
1361 * </code>
1362 *
1363 * The format of the aliases registration array is:
1364 *
1365 * {alias} => {actual type}
1366 *
1367 * In the example above we use:
1368 *
1369 * cover => page
1370 *
1371 * From that point on, every bean reference to a cover
1372 * will return a 'page' bean. Note that with autoResolve this
1373 * feature along with fetchAs() is no longer very important, although
1374 * relying on explicit aliases can be a bit faster.
1375 *
1376 * @param array $list list of global aliases to use
1377 *
1378 * @return void
1379 */
1380 public static function aliases( $list )
1381 {
1382 self::$aliases = $list;
1383 }
1384
1385 /**
1386 * Enables or disables auto-resolving fetch types.
1387 * Auto-resolving aliased parent beans is convenient but can
1388 * be slower and can create infinite recursion if you
1389 * used aliases to break cyclic relations in your domain.
1390 *
1391 * @param boolean $automatic TRUE to enable automatic resolving aliased parents
1392 *
1393 * @return void
1394 */
1395 public static function setAutoResolve( $automatic = TRUE )
1396 {
1397 self::$autoResolve = (boolean) $automatic;
1398 }
1399
1400 /**
1401 * Sets a meta property for all beans. This is a quicker way to set
1402 * the meta properties for a collection of beans because this method
1403 * can directly access the property arrays of the beans.
1404 * This method returns the beans.
1405 *
1406 * @param array $beans beans to set the meta property of
1407 * @param string $property property to set
1408 * @param mixed $value value
1409 *
1410 * @return array
1411 */
1412 public static function setMetaAll( $beans, $property, $value )
1413 {
1414 foreach( $beans as $bean ) {
1415 if ( $bean instanceof OODBBean ) $bean->__info[ $property ] = $value;
1416 }
1417
1418 return $beans;
1419 }
1420
1421 /**
1422 * Parses the join in the with-snippet.
1423 * For instance:
1424 *
1425 * <code>
1426 * $author
1427 * ->withCondition(' @joined.detail.title LIKE ? ')
1428 * ->ownBookList;
1429 * </code>
1430 *
1431 * will automatically join 'detail' on book to
1432 * access the title field.
1433 *
1434 * @note this feature requires Narrow Field Mode and Join Feature
1435 * to be both activated (default).
1436 *
1437 * @param string $type the source type for the join
1438 *
1439 * @return string
1440 */
1441 private function parseJoin( $type )
1442 {
1443 $joinSql = '';
1444 $joins = array();
1445 if ( strpos($this->withSql, '@joined.' ) !== FALSE ) {
1446 $writer = $this->beanHelper->getToolBox()->getWriter();
1447 $oldParts = $parts = explode( '@joined.', $this->withSql );
1448 array_shift( $parts );
1449 foreach($parts as $part) {
1450 $explosion = explode( '.', $part );
1451 $joinInfo = array_shift( $explosion );
1452 //Dont join more than once..
1453 if ( !isset( $joins[$joinInfo] ) ) {
1454 $joins[ $joinInfo ] = true;
1455 $joinSql .= $writer->writeJoin( $type, $joinInfo, 'LEFT' );
1456 }
1457 }
1458 $this->withSql = implode( '', $oldParts );
1459 $joinSql .= ' WHERE ';
1460 }
1461 return $joinSql;
1462 }
1463
1464 /**
1465 * Internal method.
1466 * Obtains a shared list for a certain type.
1467 *
1468 * @param string $type the name of the list you want to retrieve.
1469 *
1470 * @return array
1471 */
1472 private function getSharedList( $type, $redbean, $toolbox )
1473 {
1474 $writer = $toolbox->getWriter();
1475
1476 if ( $this->via ) {
1477 $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
1478 if ( $oldName !== $this->via ) {
1479 //set the new renaming rule
1480 $writer->renameAssocTable( $oldName, $this->via );
1481 }
1482 $this->via = NULL;
1483 }
1484
1485 $beans = array();
1486 if ($this->getID()) {
1487 $type = $this->beau( $type );
1488 $assocManager = $redbean->getAssociationManager();
1489 $beans = $assocManager->related( $this, $type, $this->withSql, $this->withParams );
1490 }
1491
1492 $this->withSql = '';
1493 $this->withParams = array();
1494
1495 return $beans;
1496 }
1497
1498 /**
1499 * Internal method.
1500 * Obtains the own list of a certain type.
1501 *
1502 * @param string $type name of the list you want to retrieve
1503 * @param OODB $oodb The RB OODB object database instance
1504 *
1505 * @return array
1506 */
1507 private function getOwnList( $type, $redbean )
1508 {
1509 $type = $this->beau( $type );
1510
1511 if ( $this->aliasName ) {
1512 $parentField = $this->aliasName;
1513 $myFieldLink = $parentField . '_id';
1514
1515 $this->__info['sys.alias.' . $type] = $this->aliasName;
1516
1517 $this->aliasName = NULL;
1518 } else {
1519 $parentField = $this->__info['type'];
1520 $myFieldLink = $parentField . '_id';
1521 }
1522
1523 $beans = array();
1524
1525 if ( $this->getID() ) {
1526
1527 $firstKey = NULL;
1528 if ( count( $this->withParams ) > 0 ) {
1529 reset( $this->withParams );
1530
1531 $firstKey = key( $this->withParams );
1532 }
1533
1534 $joinSql = $this->parseJoin( $type );
1535
1536 if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
1537 $bindings = $this->withParams;
1538 $bindings[':slot0'] = $this->getID();
1539
1540 $beans = $redbean->find( $type, array(), " {$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
1541 } else {
1542 $bindings = array_merge( array( $this->getID() ), $this->withParams );
1543
1544 $beans = $redbean->find( $type, array(), " {$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
1545 }
1546 }
1547
1548 $this->withSql = '';
1549 $this->withParams = array();
1550
1551 foreach ( $beans as $beanFromList ) {
1552 $beanFromList->__info['sys.parentcache.' . $parentField] = $this;
1553 }
1554
1555 return $beans;
1556 }
1557
1558 /**
1559 * Initializes a bean. Used by OODB for dispensing beans.
1560 * It is not recommended to use this method to initialize beans. Instead
1561 * use the OODB object to dispense new beans. You can use this method
1562 * if you build your own bean dispensing mechanism.
1563 *
1564 * @param string $type type of the new bean
1565 * @param BeanHelper $beanhelper bean helper to obtain a toolbox and a model
1566 *
1567 * @return void
1568 */
1569 public function initializeForDispense( $type, BeanHelper $beanhelper )
1570 {
1571 $this->beanHelper = $beanhelper;
1572 $this->__info['type'] = $type;
1573 $this->__info['sys.id'] = 'id';
1574 $this->__info['sys.orig'] = array( 'id' => 0 );
1575 $this->__info['tainted'] = TRUE;
1576 $this->__info['changed'] = TRUE;
1577 $this->properties['id'] = 0;
1578 }
1579
1580 /**
1581 * Sets the Bean Helper. Normally the Bean Helper is set by OODB.
1582 * Here you can change the Bean Helper. The Bean Helper is an object
1583 * providing access to a toolbox for the bean necessary to retrieve
1584 * nested beans (bean lists: ownBean, sharedBean) without the need to
1585 * rely on static calls to the facade (or make this class dep. on OODB).
1586 *
1587 * @param BeanHelper $helper helper to use for this bean
1588 *
1589 * @return void
1590 */
1591 public function setBeanHelper( BeanHelper $helper )
1592 {
1593 $this->beanHelper = $helper;
1594 }
1595
1596 /**
1597 * Returns an ArrayIterator so you can treat the bean like
1598 * an array with the properties container as its contents.
1599 * This method is meant for PHP and allows you to access beans as if
1600 * they were arrays, i.e. using array notation:
1601 *
1602 * $bean[$key] = $value;
1603 *
1604 * Note that not all PHP functions work with the array interface.
1605 *
1606 * @return ArrayIterator
1607 */
1608 public function getIterator()
1609 {
1610 return new \ArrayIterator( $this->properties );
1611 }
1612
1613 /**
1614 * Imports all values from an associative array $array. Chainable.
1615 * This method imports the values in the first argument as bean
1616 * propery and value pairs. Use the second parameter to provide a
1617 * selection. If a selection array is passed, only the entries
1618 * having keys mentioned in the selection array will be imported.
1619 * Set the third parameter to TRUE to preserve spaces in selection keys.
1620 *
1621 * @param array $array what you want to import
1622 * @param string|array $selection selection of values
1623 * @param boolean $notrim if TRUE selection keys will NOT be trimmed
1624 *
1625 * @return OODBBean
1626 */
1627 public function import( $array, $selection = FALSE, $notrim = FALSE )
1628 {
1629 if ( is_string( $selection ) ) {
1630 $selection = explode( ',', $selection );
1631 }
1632
1633 if ( !$notrim && is_array( $selection ) ) {
1634 foreach ( $selection as $key => $selected ) {
1635 $selection[$key] = trim( $selected );
1636 }
1637 }
1638
1639 foreach ( $array as $key => $value ) {
1640 if ( $key != '__info' ) {
1641 if ( !$selection || ( $selection && in_array( $key, $selection ) ) ) {
1642 if ( is_array($value ) ) {
1643 if ( isset( $value['_type'] ) ) {
1644 $bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $value['_type'] );
1645 unset( $value['_type'] );
1646 $bean->import($value);
1647 $this->$key = $bean;
1648 } else {
1649 $listBeans = array();
1650 foreach( $value as $listKey => $listItem ) {
1651 $bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $listItem['_type'] );
1652 unset( $listItem['_type'] );
1653 $bean->import($listItem);
1654 $list = &$this->$key;
1655 $list[ $listKey ] = $bean;
1656 }
1657 }
1658 } else {
1659 $this->$key = $value;
1660 }
1661 }
1662 }
1663 }
1664
1665 return $this;
1666 }
1667
1668 /**
1669 * Fast way to import a row.
1670 * Does not perform any checks.
1671 *
1672 * @param array $row a database row
1673 *
1674 * @return self
1675 */
1676 public function importRow( $row )
1677 {
1678 $this->properties = $row;
1679 $this->__info['sys.orig'] = $row;
1680 $this->__info['changed'] = FALSE;
1681 return $this;
1682 }
1683
1684 /**
1685 * Imports data from another bean. Chainable.
1686 * Copies the properties from the source bean to the internal
1687 * property list.
1688 *
1689 * @param OODBBean $sourceBean the source bean to take properties from
1690 *
1691 * @return OODBBean
1692 */
1693 public function importFrom( OODBBean $sourceBean )
1694 {
1695 $this->__info['tainted'] = TRUE;
1696 $this->__info['changed'] = TRUE;
1697 $this->properties = $sourceBean->properties;
1698
1699 return $this;
1700 }
1701
1702 /**
1703 * Injects the properties of another bean but keeps the original ID.
1704 * Just like import() but keeps the original ID.
1705 * Chainable.
1706 *
1707 * @param OODBBean $otherBean the bean whose properties you would like to copy
1708 *
1709 * @return OODBBean
1710 */
1711 public function inject( OODBBean $otherBean )
1712 {
1713 $myID = $this->properties['id'];
1714
1715 $this->import( $otherBean->export() );
1716
1717 $this->id = $myID;
1718
1719 return $this;
1720 }
1721
1722 /**
1723 * Exports the bean as an array.
1724 * This function exports the contents of a bean to an array and returns
1725 * the resulting array.
1726 *
1727 * @param boolean $meta set to TRUE if you want to export meta data as well
1728 * @param boolean $parents set to TRUE if you want to export parents as well
1729 * @param boolean $onlyMe set to TRUE if you want to export only this bean
1730 * @param array $filters optional whitelist for export
1731 *
1732 * @return array
1733 */
1734 public function export( $meta = FALSE, $parents = FALSE, $onlyMe = FALSE, $filters = array() )
1735 {
1736 $arr = array();
1737
1738 if ( $parents ) {
1739 foreach ( $this as $key => $value ) {
1740 if ( substr( $key, -3 ) != '_id' ) continue;
1741
1742 $prop = substr( $key, 0, strlen( $key ) - 3 );
1743 $this->$prop;
1744 }
1745 }
1746
1747 $hasFilters = is_array( $filters ) && count( $filters );
1748
1749 foreach ( $this as $key => $value ) {
1750 if ( !$onlyMe && is_array( $value ) ) {
1751 $vn = array();
1752
1753 foreach ( $value as $i => $b ) {
1754 if ( !( $b instanceof OODBBean ) ) continue;
1755 $vn[] = $b->export( $meta, FALSE, FALSE, $filters );
1756 $value = $vn;
1757 }
1758 } elseif ( $value instanceof OODBBean ) {
1759 if ( $hasFilters ) {
1760 if ( !in_array( strtolower( $value->getMeta( 'type' ) ), $filters ) ) continue;
1761 }
1762
1763 $value = $value->export( $meta, $parents, FALSE, $filters );
1764 }
1765
1766 $arr[$key] = $value;
1767 }
1768
1769 if ( $meta ) {
1770 $arr['__info'] = $this->__info;
1771 }
1772
1773 return $arr;
1774 }
1775
1776 /**
1777 * Implements isset() function for use as an array.
1778 *
1779 * @param string $property name of the property you want to check
1780 *
1781 * @return boolean
1782 */
1783 public function __isset( $property )
1784 {
1785 $property = $this->beau( $property );
1786
1787 if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
1788 $property = substr($property, 1);
1789 }
1790 return isset( $this->properties[$property] );
1791 }
1792
1793 /**
1794 * Returns the ID of the bean no matter what the ID field is.
1795 *
1796 * @return string|null
1797 */
1798 public function getID()
1799 {
1800 return ( isset( $this->properties['id'] ) ) ? (string) $this->properties['id'] : NULL;
1801 }
1802
1803 /**
1804 * Unsets a property of a bean.
1805 * Magic method, gets called implicitly when performing the unset() operation
1806 * on a bean property.
1807 *
1808 * @param string $property property to unset
1809 *
1810 * @return void
1811 */
1812 public function __unset( $property )
1813 {
1814 $property = $this->beau( $property );
1815
1816 if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
1817 $property = substr($property, 1);
1818 }
1819
1820 unset( $this->properties[$property] );
1821
1822 $shadowKey = 'sys.shadow.'.$property;
1823 if ( isset( $this->__info[ $shadowKey ] ) ) unset( $this->__info[$shadowKey] );
1824
1825 //also clear modifiers
1826 $this->withSql = '';
1827 $this->withParams = array();
1828 $this->aliasName = NULL;
1829 $this->fetchType = NULL;
1830 $this->noLoad = FALSE;
1831 $this->all = FALSE;
1832 $this->via = NULL;
1833
1834 return;
1835 }
1836
1837 /**
1838 * Adds WHERE clause conditions to ownList retrieval.
1839 * For instance to get the pages that belong to a book you would
1840 * issue the following command: $book->ownPage
1841 * However, to order these pages by number use:
1842 *
1843 * <code>
1844 * $book->with(' ORDER BY `number` ASC ')->ownPage
1845 * </code>
1846 *
1847 * the additional SQL snippet will be merged into the final
1848 * query.
1849 *
1850 * @param string $sql SQL to be added to retrieval query.
1851 * @param array $bindings array with parameters to bind to SQL snippet
1852 *
1853 * @return OODBBean
1854 */
1855 public function with( $sql, $bindings = array() )
1856 {
1857 $this->withSql = $sql;
1858 $this->withParams = $bindings;
1859 return $this;
1860 }
1861
1862 /**
1863 * Just like with(). Except that this method prepends the SQL query snippet
1864 * with AND which makes it slightly more comfortable to use a conditional
1865 * SQL snippet. For instance to filter an own-list with pages (belonging to
1866 * a book) on specific chapters you can use:
1867 *
1868 * $book->withCondition(' chapter = 3 ')->ownPage
1869 *
1870 * This will return in the own list only the pages having 'chapter == 3'.
1871 *
1872 * @param string $sql SQL to be added to retrieval query (prefixed by AND)
1873 * @param array $bindings array with parameters to bind to SQL snippet
1874 *
1875 * @return OODBBean
1876 */
1877 public function withCondition( $sql, $bindings = array() )
1878 {
1879 $this->withSql = ' AND ' . $sql;
1880 $this->withParams = $bindings;
1881 return $this;
1882 }
1883
1884 /**
1885 * Tells the bean to (re)load the following list without any
1886 * conditions. If you have an ownList or sharedList with a
1887 * condition you can use this method to reload the entire list.
1888 *
1889 * Usage:
1890 *
1891 * <code>
1892 * $bean->with( ' LIMIT 3 ' )->ownPage; //Just 3
1893 * $bean->all()->ownPage; //Reload all pages
1894 * </code>
1895 *
1896 * @return self
1897 */
1898 public function all()
1899 {
1900 $this->all = TRUE;
1901 return $this;
1902 }
1903
1904 /**
1905 * Tells the bean to only access the list but not load
1906 * its contents. Use this if you only want to add something to a list
1907 * and you have no interest in retrieving its contents from the database.
1908 *
1909 * @return self
1910 */
1911 public function noLoad()
1912 {
1913 $this->noLoad = TRUE;
1914 return $this;
1915 }
1916
1917 /**
1918 * Prepares an own-list to use an alias. This is best explained using
1919 * an example. Imagine a project and a person. The project always involves
1920 * two persons: a teacher and a student. The person beans have been aliased in this
1921 * case, so to the project has a teacher_id pointing to a person, and a student_id
1922 * also pointing to a person. Given a project, we obtain the teacher like this:
1923 *
1924 * <code>
1925 * $project->fetchAs('person')->teacher;
1926 * </code>
1927 *
1928 * Now, if we want all projects of a teacher we cant say:
1929 *
1930 * <code>
1931 * $teacher->ownProject
1932 * </code>
1933 *
1934 * because the $teacher is a bean of type 'person' and no project has been
1935 * assigned to a person. Instead we use the alias() method like this:
1936 *
1937 * <code>
1938 * $teacher->alias('teacher')->ownProject
1939 * </code>
1940 *
1941 * now we get the projects associated with the person bean aliased as
1942 * a teacher.
1943 *
1944 * @param string $aliasName the alias name to use
1945 *
1946 * @return OODBBean
1947 */
1948 public function alias( $aliasName )
1949 {
1950 $this->aliasName = $this->beau( $aliasName );
1951
1952 return $this;
1953 }
1954
1955 /**
1956 * Returns properties of bean as an array.
1957 * This method returns the raw internal property list of the
1958 * bean. Only use this method for optimization purposes. Otherwise
1959 * use the export() method to export bean data to arrays.
1960 *
1961 * @return array
1962 */
1963 public function getProperties()
1964 {
1965 return $this->properties;
1966 }
1967
1968 /**
1969 * Returns properties of bean as an array.
1970 * This method returns the raw internal property list of the
1971 * bean. Only use this method for optimization purposes. Otherwise
1972 * use the export() method to export bean data to arrays.
1973 * This method returns an array with the properties array and
1974 * the type (string).
1975 *
1976 * @return array
1977 */
1978 public function getPropertiesAndType()
1979 {
1980 return array( $this->properties, $this->__info['type'] );
1981 }
1982
1983 /**
1984 * Turns a camelcase property name into an underscored property name.
1985 *
1986 * Examples:
1987 *
1988 * * oneACLRoute -> one_acl_route
1989 * * camelCase -> camel_case
1990 *
1991 * Also caches the result to improve performance.
1992 *
1993 * @param string $property property to un-beautify
1994 *
1995 * @return string
1996 */
1997 public function beau( $property )
1998 {
1999 static $beautifulColumns = array();
2000
2001 if ( ctype_lower( $property ) ) return $property;
2002
2003 if (
2004 ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) )
2005 || ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) )
2006 || ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) )
2007 ) {
2008
2009 $property = preg_replace( '/List$/', '', $property );
2010 return $property;
2011 }
2012
2013 if ( !isset( $beautifulColumns[$property] ) ) {
2014 $beautifulColumns[$property] = AQueryWriter::camelsSnake( $property );
2015 }
2016
2017 return $beautifulColumns[$property];
2018 }
2019
2020 /**
2021 * Clears all modifiers.
2022 *
2023 * @return self
2024 */
2025 public function clearModifiers()
2026 {
2027 $this->withSql = '';
2028 $this->withParams = array();
2029 $this->aliasName = NULL;
2030 $this->fetchType = NULL;
2031 $this->noLoad = FALSE;
2032 $this->all = FALSE;
2033 $this->via = NULL;
2034 return $this;
2035 }
2036
2037 /**
2038 * Determines whether a list is opened in exclusive mode or not.
2039 * If a list has been opened in exclusive mode this method will return TRUE,
2040 * othwerwise it will return FALSE.
2041 *
2042 * @param string $listName name of the list to check
2043 *
2044 * @return boolean
2045 */
2046 public function isListInExclusiveMode( $listName )
2047 {
2048 $listName = $this->beau( $listName );
2049
2050 if ( strpos( $listName, 'xown' ) === 0 && ctype_upper( substr( $listName, 4, 1 ) ) ) {
2051 $listName = substr($listName, 1);
2052 }
2053
2054 $listName = lcfirst( substr( $listName, 3 ) );
2055
2056 return ( isset( $this->__info['sys.exclusive-'.$listName] ) && $this->__info['sys.exclusive-'.$listName] );
2057 }
2058
2059 /**
2060 * Magic Getter. Gets the value for a specific property in the bean.
2061 * If the property does not exist this getter will make sure no error
2062 * occurs. This is because RedBean allows you to query (probe) for
2063 * properties. If the property can not be found this method will
2064 * return NULL instead.
2065 *
2066 * @param string $property name of the property you wish to obtain the value of
2067 *
2068 * @return mixed
2069 */
2070 public function &__get( $property )
2071 {
2072 $isEx = FALSE;
2073 $isOwn = FALSE;
2074 $isShared = FALSE;
2075
2076 if ( !ctype_lower( $property ) ) {
2077 $property = $this->beau( $property );
2078 if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
2079 $property = substr($property, 1);
2080 $listName = lcfirst( substr( $property, 3 ) );
2081 $isEx = TRUE;
2082 $isOwn = TRUE;
2083 $this->__info['sys.exclusive-'.$listName] = TRUE;
2084 } elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) ) {
2085 $isOwn = TRUE;
2086 $listName = lcfirst( substr( $property, 3 ) );
2087 } elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
2088 $isShared = TRUE;
2089 }
2090 }
2091
2092 $fieldLink = $property . '_id';
2093 $exists = isset( $this->properties[$property] );
2094
2095 //If not exists and no field link and no list, bail out.
2096 if ( !$exists && !isset($this->$fieldLink) && (!$isOwn && !$isShared )) {
2097
2098 $this->withSql = '';
2099 $this->withParams = array();
2100 $this->aliasName = NULL;
2101 $this->fetchType = NULL;
2102 $this->noLoad = FALSE;
2103 $this->all = FALSE;
2104 $this->via = NULL;
2105
2106 $NULL = NULL;
2107 return $NULL;
2108 }
2109
2110 $hasAlias = (!is_null($this->aliasName));
2111 $differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
2112 ($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
2113 $hasSQL = ($this->withSql !== '' || $this->via !== NULL);
2114 $hasAll = (boolean) ($this->all);
2115
2116 //If exists and no list or exits and list not changed, bail out.
2117 if ( $exists && ((!$isOwn && !$isShared ) || (!$hasSQL && !$differentAlias && !$hasAll)) ) {
2118
2119 $this->withSql = '';
2120 $this->withParams = array();
2121 $this->aliasName = NULL;
2122 $this->fetchType = NULL;
2123 $this->noLoad = FALSE;
2124 $this->all = FALSE;
2125 $this->via = NULL;
2126 return $this->properties[$property];
2127 }
2128
2129 list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
2130
2131 if ( isset( $this->$fieldLink ) ) {
2132 $this->__info['tainted'] = TRUE;
2133
2134 if ( isset( $this->__info["sys.parentcache.$property"] ) ) {
2135 $bean = $this->__info["sys.parentcache.$property"];
2136 } else {
2137 if ( isset( self::$aliases[$property] ) ) {
2138 $type = self::$aliases[$property];
2139 } elseif ( $this->fetchType ) {
2140 $type = $this->fetchType;
2141 $this->fetchType = NULL;
2142 } else {
2143 $type = $property;
2144 }
2145 $bean = NULL;
2146 if ( !is_null( $this->properties[$fieldLink] ) ) {
2147 $bean = $redbean->load( $type, $this->properties[$fieldLink] );
2148 //If the IDs dont match, we failed to load, so try autoresolv in that case...
2149 if ( $bean->id !== $this->properties[$fieldLink] && self::$autoResolve ) {
2150 $type = $this->beanHelper->getToolbox()->getWriter()->inferFetchType( $this->__info['type'], $property );
2151 if ( !is_null( $type) ) {
2152 $bean = $redbean->load( $type, $this->properties[$fieldLink] );
2153 $this->__info["sys.autoresolved.{$property}"] = $type;
2154 }
2155 }
2156 }
2157 }
2158
2159 $this->properties[$property] = $bean;
2160 $this->withSql = '';
2161 $this->withParams = array();
2162 $this->aliasName = NULL;
2163 $this->fetchType = NULL;
2164 $this->noLoad = FALSE;
2165 $this->all = FALSE;
2166 $this->via = NULL;
2167
2168 return $this->properties[$property];
2169
2170 }
2171 //Implicit: elseif ( $isOwn || $isShared ) {
2172 if ( $this->noLoad ) {
2173 $beans = array();
2174 } elseif ( $isOwn ) {
2175 $beans = $this->getOwnList( $listName, $redbean );
2176 } else {
2177 $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
2178 }
2179
2180 $this->properties[$property] = $beans;
2181 $this->__info["sys.shadow.$property"] = $beans;
2182 $this->__info['tainted'] = TRUE;
2183
2184 $this->withSql = '';
2185 $this->withParams = array();
2186 $this->aliasName = NULL;
2187 $this->fetchType = NULL;
2188 $this->noLoad = FALSE;
2189 $this->all = FALSE;
2190 $this->via = NULL;
2191
2192 return $this->properties[$property];
2193 }
2194
2195 /**
2196 * Magic Setter. Sets the value for a specific property.
2197 * This setter acts as a hook for OODB to mark beans as tainted.
2198 * The tainted meta property can be retrieved using getMeta("tainted").
2199 * The tainted meta property indicates whether a bean has been modified and
2200 * can be used in various caching mechanisms.
2201 *
2202 * @param string $property name of the property you wish to assign a value to
2203 * @param mixed $value the value you want to assign
2204 *
2205 * @return void
2206 */
2207 public function __set( $property, $value )
2208 {
2209 $isEx = FALSE;
2210 $isOwn = FALSE;
2211 $isShared = FALSE;
2212
2213 if ( !ctype_lower( $property ) ) {
2214 $property = $this->beau( $property );
2215 if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
2216 $property = substr($property, 1);
2217 $listName = lcfirst( substr( $property, 3 ) );
2218 $isEx = TRUE;
2219 $isOwn = TRUE;
2220 $this->__info['sys.exclusive-'.$listName] = TRUE;
2221 } elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) ) {
2222 $isOwn = TRUE;
2223 $listName = lcfirst( substr( $property, 3 ) );
2224 } elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
2225 $isShared = TRUE;
2226 }
2227 }
2228
2229 $hasAlias = (!is_null($this->aliasName));
2230 $differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
2231 ($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
2232 $hasSQL = ($this->withSql !== '' || $this->via !== NULL);
2233 $exists = isset( $this->properties[$property] );
2234 $fieldLink = $property . '_id';
2235
2236 if ( ($isOwn || $isShared) && (!$exists || $hasSQL || $differentAlias) ) {
2237
2238 if ( !$this->noLoad ) {
2239 list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
2240 if ( $isOwn ) {
2241 $beans = $this->getOwnList( $listName, $redbean );
2242 } else {
2243 $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
2244 }
2245 $this->__info["sys.shadow.$property"] = $beans;
2246 }
2247 }
2248
2249 $this->withSql = '';
2250 $this->withParams = array();
2251 $this->aliasName = NULL;
2252 $this->fetchType = NULL;
2253 $this->noLoad = FALSE;
2254 $this->all = FALSE;
2255 $this->via = NULL;
2256
2257 $this->__info['tainted'] = TRUE;
2258 $this->__info['changed'] = TRUE;
2259
2260 if ( array_key_exists( $fieldLink, $this->properties ) && !( $value instanceof OODBBean ) ) {
2261 if ( is_null( $value ) || $value === FALSE ) {
2262
2263 unset( $this->properties[ $property ]);
2264 $this->properties[ $fieldLink ] = NULL;
2265
2266 return;
2267 } else {
2268 throw new RedException( 'Cannot cast to bean.' );
2269 }
2270 }
2271
2272 if ( $value === FALSE ) {
2273 $value = '0';
2274 } elseif ( $value === TRUE ) {
2275 $value = '1';
2276 } elseif ( $value instanceof \DateTime ) {
2277 $value = $value->format( 'Y-m-d H:i:s' );
2278 }
2279
2280 $this->properties[$property] = $value;
2281 }
2282
2283 /**
2284 * Sets a property directly, for internal use only.
2285 *
2286 * @param string $property property
2287 * @param mixed $value value
2288 * @param boolean $updateShadow whether you want to update the shadow
2289 * @param boolean $taint whether you want to mark the bean as tainted
2290 *
2291 * @return void
2292 */
2293 public function setProperty( $property, $value, $updateShadow = FALSE, $taint = FALSE )
2294 {
2295 $this->properties[$property] = $value;
2296
2297 if ( $updateShadow ) {
2298 $this->__info['sys.shadow.' . $property] = $value;
2299 }
2300
2301 if ( $taint ) {
2302 $this->__info['tainted'] = TRUE;
2303 $this->__info['changed'] = TRUE;
2304 }
2305 }
2306
2307 /**
2308 * Returns the value of a meta property. A meta property
2309 * contains additional information about the bean object that will not
2310 * be stored in the database. Meta information is used to instruct
2311 * RedBeanPHP as well as other systems how to deal with the bean.
2312 * If the property cannot be found this getter will return NULL instead.
2313 *
2314 * Example:
2315 *
2316 * <code>
2317 * $bean->setMeta( 'flush-cache', TRUE );
2318 * </code>
2319 *
2320 * RedBeanPHP also stores meta data in beans, this meta data uses
2321 * keys prefixed with 'sys.' (system).
2322 *
2323 * @param string $path path to property in meta data
2324 * @param mixed $default default value
2325 *
2326 * @return mixed
2327 */
2328 public function getMeta( $path, $default = NULL )
2329 {
2330 return ( isset( $this->__info[$path] ) ) ? $this->__info[$path] : $default;
2331 }
2332
2333 /**
2334 * Gets and unsets a meta property.
2335 * Moves a meta property out of the bean.
2336 * This is a short-cut method that can be used instead
2337 * of combining a get/unset.
2338 *
2339 * @param string $path path to property in meta data
2340 * @param mixed $default default value
2341 *
2342 * @return mixed
2343 */
2344 public function moveMeta( $path, $value = NULL )
2345 {
2346 if ( isset( $this->__info[$path] ) ) {
2347 $value = $this->__info[ $path ];
2348 unset( $this->__info[ $path ] );
2349 }
2350 return $value;
2351 }
2352
2353 /**
2354 * Stores a value in the specified Meta information property.
2355 * The first argument should be the key to store the value under,
2356 * the second argument should be the value. It is common to use
2357 * a path-like notation for meta data in RedBeanPHP like:
2358 * 'my.meta.data', however the dots are purely for readability, the
2359 * meta data methods do not store nested structures or hierarchies.
2360 *
2361 * @param string $path path / key to store value under
2362 * @param mixed $value value to store in bean (not in database) as meta data
2363 *
2364 * @return OODBBean
2365 */
2366 public function setMeta( $path, $value )
2367 {
2368 $this->__info[$path] = $value;
2369
2370 return $this;
2371 }
2372
2373 /**
2374 * Copies the meta information of the specified bean
2375 * This is a convenience method to enable you to
2376 * exchange meta information easily.
2377 *
2378 * @param OODBBean $bean bean to copy meta data of
2379 *
2380 * @return OODBBean
2381 */
2382 public function copyMetaFrom( OODBBean $bean )
2383 {
2384 $this->__info = $bean->__info;
2385
2386 return $this;
2387 }
2388
2389 /**
2390 * Sends the call to the registered model.
2391 *
2392 * @param string $method name of the method
2393 * @param array $args argument list
2394 *
2395 * @return mixed
2396 */
2397 public function __call( $method, $args )
2398 {
2399 if ( !isset( $this->__info['model'] ) ) {
2400 $model = $this->beanHelper->getModelForBean( $this );
2401
2402 if ( !$model ) {
2403 return NULL;
2404 }
2405
2406 $this->__info['model'] = $model;
2407 }
2408 if ( !method_exists( $this->__info['model'], $method ) ) {
2409
2410 if ( self::$errorHandlingFUSE === FALSE ) {
2411 return NULL;
2412 }
2413
2414 if ( in_array( $method, array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ), TRUE ) ) {
2415 return NULL;
2416 }
2417
2418 $message = "FUSE: method does not exist in model: $method";
2419 if ( self::$errorHandlingFUSE === self::C_ERR_LOG ) {
2420 error_log( $message );
2421 return NULL;
2422 } elseif ( self::$errorHandlingFUSE === self::C_ERR_NOTICE ) {
2423 trigger_error( $message, E_USER_NOTICE );
2424 return NULL;
2425 } elseif ( self::$errorHandlingFUSE === self::C_ERR_WARN ) {
2426 trigger_error( $message, E_USER_WARNING );
2427 return NULL;
2428 } elseif ( self::$errorHandlingFUSE === self::C_ERR_EXCEPTION ) {
2429 throw new \Exception( $message );
2430 } elseif ( self::$errorHandlingFUSE === self::C_ERR_FUNC ) {
2431 $func = self::$errorHandler;
2432 return $func(array(
2433 'message' => $message,
2434 'method' => $method,
2435 'args' => $args,
2436 'bean' => $this
2437 ));
2438 }
2439 trigger_error( $message, E_USER_ERROR );
2440 return NULL;
2441 }
2442
2443 return call_user_func_array( array( $this->__info['model'], $method ), $args );
2444 }
2445
2446 /**
2447 * Implementation of __toString Method
2448 * Routes call to Model. If the model implements a __toString() method this
2449 * method will be called and the result will be returned. In case of an
2450 * echo-statement this result will be printed. If the model does not
2451 * implement a __toString method, this method will return a JSON
2452 * representation of the current bean.
2453 *
2454 * @return string
2455 */
2456 public function __toString()
2457 {
2458 $string = $this->__call( '__toString', array() );
2459
2460 if ( $string === NULL ) {
2461 $list = array();
2462 foreach($this->properties as $property => $value) {
2463 if (is_scalar($value)) {
2464 $list[$property] = $value;
2465 }
2466 }
2467 return json_encode( $list );
2468 } else {
2469 return $string;
2470 }
2471 }
2472
2473 /**
2474 * Implementation of Array Access Interface, you can access bean objects
2475 * like an array.
2476 * Call gets routed to __set.
2477 *
2478 * @param mixed $offset offset string
2479 * @param mixed $value value
2480 *
2481 * @return void
2482 */
2483 public function offsetSet( $offset, $value )
2484 {
2485 $this->__set( $offset, $value );
2486 }
2487
2488 /**
2489 * Implementation of Array Access Interface, you can access bean objects
2490 * like an array.
2491 *
2492 * Array functions do not reveal x-own-lists and list-alias because
2493 * you dont want duplicate entries in foreach-loops.
2494 * Also offers a slight performance improvement for array access.
2495 *
2496 * @param mixed $offset property
2497 *
2498 * @return boolean
2499 */
2500 public function offsetExists( $offset )
2501 {
2502 return $this->__isset( $offset );
2503 }
2504
2505 /**
2506 * Implementation of Array Access Interface, you can access bean objects
2507 * like an array.
2508 * Unsets a value from the array/bean.
2509 *
2510 * Array functions do not reveal x-own-lists and list-alias because
2511 * you dont want duplicate entries in foreach-loops.
2512 * Also offers a slight performance improvement for array access.
2513 *
2514 * @param mixed $offset property
2515 *
2516 * @return void
2517 */
2518 public function offsetUnset( $offset )
2519 {
2520 $this->__unset( $offset );
2521 }
2522
2523 /**
2524 * Implementation of Array Access Interface, you can access bean objects
2525 * like an array.
2526 * Returns value of a property.
2527 *
2528 * Array functions do not reveal x-own-lists and list-alias because
2529 * you dont want duplicate entries in foreach-loops.
2530 * Also offers a slight performance improvement for array access.
2531 *
2532 * @param mixed $offset property
2533 *
2534 * @return mixed
2535 */
2536 public function &offsetGet( $offset )
2537 {
2538 return $this->__get( $offset );
2539 }
2540
2541 /**
2542 * Chainable method to cast a certain ID to a bean; for instance:
2543 * $person = $club->fetchAs('person')->member;
2544 * This will load a bean of type person using member_id as ID.
2545 *
2546 * @param string $type preferred fetch type
2547 *
2548 * @return OODBBean
2549 */
2550 public function fetchAs( $type )
2551 {
2552 $this->fetchType = $type;
2553
2554 return $this;
2555 }
2556
2557 /**
2558 * For polymorphic bean relations.
2559 * Same as fetchAs but uses a column instead of a direct value.
2560 *
2561 * @param string $field field name to use for mapping
2562 *
2563 * @return OODBBean
2564 */
2565 public function poly( $field )
2566 {
2567 return $this->fetchAs( $this->$field );
2568 }
2569
2570 /**
2571 * Traverses a bean property with the specified function.
2572 * Recursively iterates through the property invoking the
2573 * function for each bean along the way passing the bean to it.
2574 *
2575 * Can be used together with with, withCondition, alias and fetchAs.
2576 *
2577 * @param string $property property
2578 * @param callable $function function
2579 * @param integer $maxDepth maximum depth for traversal
2580 *
2581 * @return OODBBean
2582 * @throws RedException
2583 */
2584 public function traverse( $property, $function, $maxDepth = NULL )
2585 {
2586 $this->via = NULL;
2587 if ( strpos( $property, 'shared' ) !== FALSE ) {
2588 throw new RedException( 'Traverse only works with (x)own-lists.' );
2589 }
2590
2591 if ( !is_null( $maxDepth ) ) {
2592 if ( !$maxDepth-- ) return $this;
2593 }
2594
2595 $oldFetchType = $this->fetchType;
2596 $oldAliasName = $this->aliasName;
2597 $oldWith = $this->withSql;
2598 $oldBindings = $this->withParams;
2599
2600 $beans = $this->$property;
2601
2602 if ( $beans === NULL ) return $this;
2603
2604 if ( !is_array( $beans ) ) $beans = array( $beans );
2605
2606 foreach( $beans as $bean ) {
2607 /** @var OODBBean $bean */
2608 $function( $bean );
2609
2610 $bean->fetchType = $oldFetchType;
2611 $bean->aliasName = $oldAliasName;
2612 $bean->withSql = $oldWith;
2613 $bean->withParams = $oldBindings;
2614
2615 $bean->traverse( $property, $function, $maxDepth );
2616 }
2617
2618 return $this;
2619 }
2620
2621 /**
2622 * Implementation of Countable interface. Makes it possible to use
2623 * count() function on a bean.
2624 *
2625 * @return integer
2626 */
2627 public function count()
2628 {
2629 return count( $this->properties );
2630 }
2631
2632 /**
2633 * Checks whether a bean is empty or not.
2634 * A bean is empty if it has no other properties than the id field OR
2635 * if all the other property are empty().
2636 *
2637 * @return boolean
2638 */
2639 public function isEmpty()
2640 {
2641 $empty = TRUE;
2642 foreach ( $this->properties as $key => $value ) {
2643 if ( $key == 'id' ) {
2644 continue;
2645 }
2646 if ( !empty( $value ) ) {
2647 $empty = FALSE;
2648 }
2649 }
2650
2651 return $empty;
2652 }
2653
2654 /**
2655 * Chainable setter.
2656 *
2657 * @param string $property the property of the bean
2658 * @param mixed $value the value you want to set
2659 *
2660 * @return OODBBean
2661 */
2662 public function setAttr( $property, $value )
2663 {
2664 $this->$property = $value;
2665
2666 return $this;
2667 }
2668
2669 /**
2670 * Comfort method.
2671 * Unsets all properties in array.
2672 *
2673 * @param array $properties properties you want to unset.
2674 *
2675 * @return OODBBean
2676 */
2677 public function unsetAll( $properties )
2678 {
2679 foreach ( $properties as $prop ) {
2680 if ( isset( $this->properties[$prop] ) ) {
2681 unset( $this->properties[$prop] );
2682 }
2683 }
2684
2685 return $this;
2686 }
2687
2688 /**
2689 * Returns original (old) value of a property.
2690 * You can use this method to see what has changed in a
2691 * bean.
2692 *
2693 * @param string $property name of the property you want the old value of
2694 *
2695 * @return mixed
2696 */
2697 public function old( $property )
2698 {
2699 $old = $this->getMeta( 'sys.orig', array() );
2700
2701 if ( array_key_exists( $property, $old ) ) {
2702 return $old[$property];
2703 }
2704
2705 return NULL;
2706 }
2707
2708 /**
2709 * Convenience method.
2710 * Returns TRUE if the bean has been changed, or FALSE otherwise.
2711 * Same as $bean->getMeta('tainted');
2712 * Note that a bean becomes tainted as soon as you retrieve a list from
2713 * the bean. This is because the bean lists are arrays and the bean cannot
2714 * determine whether you have made modifications to a list so RedBeanPHP
2715 * will mark the whole bean as tainted.
2716 *
2717 * @return boolean
2718 */
2719 public function isTainted()
2720 {
2721 return $this->getMeta( 'tainted' );
2722 }
2723
2724 /**
2725 * Returns TRUE if the value of a certain property of the bean has been changed and
2726 * FALSE otherwise.
2727 *
2728 * Note that this method will return TRUE if applied to a loaded list.
2729 * Also note that this method keeps track of the bean's history regardless whether
2730 * it has been stored or not. Storing a bean does not undo it's history,
2731 * to clean the history of a bean use: clearHistory().
2732 *
2733 * @param string $property name of the property you want the change-status of
2734 *
2735 * @return boolean
2736 */
2737 public function hasChanged( $property )
2738 {
2739 return ( array_key_exists( $property, $this->properties ) ) ?
2740 $this->old( $property ) != $this->properties[$property] : FALSE;
2741 }
2742
2743 /**
2744 * Returns TRUE if the specified list exists, has been loaded and has been changed:
2745 * beans have been added or deleted. This method will not tell you anything about
2746 * the state of the beans in the list.
2747 *
2748 * @param string $property name of the list to check
2749 *
2750 * @return boolean
2751 */
2752 public function hasListChanged( $property )
2753 {
2754 if ( !array_key_exists( $property, $this->properties ) ) return FALSE;
2755 $diffAdded = array_diff_assoc( $this->properties[$property], $this->__info['sys.shadow.'.$property] );
2756 if ( count( $diffAdded ) ) return TRUE;
2757 $diffMissing = array_diff_assoc( $this->__info['sys.shadow.'.$property], $this->properties[$property] );
2758 if ( count( $diffMissing ) ) return TRUE;
2759 return FALSE;
2760 }
2761
2762 /**
2763 * Clears (syncs) the history of the bean.
2764 * Resets all shadow values of the bean to their current value.
2765 *
2766 * @return self
2767 */
2768 public function clearHistory()
2769 {
2770 $this->__info['sys.orig'] = array();
2771 foreach( $this->properties as $key => $value ) {
2772 if ( is_scalar($value) ) {
2773 $this->__info['sys.orig'][$key] = $value;
2774 } else {
2775 $this->__info['sys.shadow.'.$key] = $value;
2776 }
2777 }
2778 return $this;
2779 }
2780
2781 /**
2782 * Creates a N-M relation by linking an intermediate bean.
2783 * This method can be used to quickly connect beans using indirect
2784 * relations. For instance, given an album and a song you can connect the two
2785 * using a track with a number like this:
2786 *
2787 * Usage:
2788 *
2789 * <code>
2790 * $album->link('track', array('number'=>1))->song = $song;
2791 * </code>
2792 *
2793 * or:
2794 *
2795 * <code>
2796 * $album->link($trackBean)->song = $song;
2797 * </code>
2798 *
2799 * What this method does is adding the link bean to the own-list, in this case
2800 * ownTrack. If the first argument is a string and the second is an array or
2801 * a JSON string then the linking bean gets dispensed on-the-fly as seen in
2802 * example #1. After preparing the linking bean, the bean is returned thus
2803 * allowing the chained setter: ->song = $song.
2804 *
2805 * @param string|OODBBean $type type of bean to dispense or the full bean
2806 * @param string|array $qualification JSON string or array (optional)
2807 *
2808 * @return OODBBean
2809 */
2810 public function link( $typeOrBean, $qualification = array() )
2811 {
2812 if ( is_string( $typeOrBean ) ) {
2813
2814 $typeOrBean = AQueryWriter::camelsSnake( $typeOrBean );
2815
2816 $bean = $this->beanHelper->getToolBox()->getRedBean()->dispense( $typeOrBean );
2817
2818 if ( is_string( $qualification ) ) {
2819 $data = json_decode( $qualification, TRUE );
2820 } else {
2821 $data = $qualification;
2822 }
2823
2824 foreach ( $data as $key => $value ) {
2825 $bean->$key = $value;
2826 }
2827 } else {
2828 $bean = $typeOrBean;
2829 }
2830
2831 $list = 'own' . ucfirst( $bean->getMeta( 'type' ) );
2832
2833 array_push( $this->$list, $bean );
2834
2835 return $bean;
2836 }
2837
2838 /**
2839 * Returns a bean of the given type with the same ID of as
2840 * the current one. This only happens in a one-to-one relation.
2841 * This is as far as support for 1-1 goes in RedBeanPHP. This
2842 * method will only return a reference to the bean, changing it
2843 * and storing the bean will not update the related one-bean.
2844 *
2845 * @return OODBBean
2846 */
2847 public function one( $type ) {
2848 return $this->beanHelper->getToolBox()->getRedBean()->load( $type, $this->id );
2849 }
2850
2851 /**
2852 * Returns the same bean freshly loaded from the database.
2853 *
2854 * @return OODBBean
2855 */
2856 public function fresh()
2857 {
2858 return $this->beanHelper->getToolbox()->getRedBean()->load( $this->getMeta( 'type' ), $this->properties['id'] );
2859 }
2860
2861 /**
2862 * Registers a association renaming globally.
2863 *
2864 * @param string $via type you wish to use for shared lists
2865 *
2866 * @return OODBBean
2867 */
2868 public function via( $via )
2869 {
2870 $this->via = AQueryWriter::camelsSnake( $via );
2871
2872 return $this;
2873 }
2874
2875 /**
2876 * Counts all own beans of type $type.
2877 * Also works with alias(), with() and withCondition().
2878 *
2879 * @param string $type the type of bean you want to count
2880 *
2881 * @return integer
2882 */
2883 public function countOwn( $type )
2884 {
2885 $type = $this->beau( $type );
2886
2887 if ( $this->aliasName ) {
2888 $myFieldLink = $this->aliasName . '_id';
2889
2890 $this->aliasName = NULL;
2891 } else {
2892 $myFieldLink = $this->__info['type'] . '_id';
2893 }
2894
2895 $count = 0;
2896
2897 if ( $this->getID() ) {
2898
2899 $firstKey = NULL;
2900 if ( count( $this->withParams ) > 0 ) {
2901 reset( $this->withParams );
2902 $firstKey = key( $this->withParams );
2903 }
2904
2905 $joinSql = $this->parseJoin( $type );
2906
2907 if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
2908 $bindings = $this->withParams;
2909 $bindings[':slot0'] = $this->getID();
2910 $count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " {$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
2911 } else {
2912 $bindings = array_merge( array( $this->getID() ), $this->withParams );
2913 $count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " {$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
2914 }
2915
2916 }
2917
2918 $this->clearModifiers();
2919 return (int) $count;
2920 }
2921
2922 /**
2923 * Counts all shared beans of type $type.
2924 * Also works with via(), with() and withCondition().
2925 *
2926 * @param string $type type of bean you wish to count
2927 *
2928 * @return integer
2929 */
2930 public function countShared( $type )
2931 {
2932 $toolbox = $this->beanHelper->getToolbox();
2933 $redbean = $toolbox->getRedBean();
2934 $writer = $toolbox->getWriter();
2935
2936 if ( $this->via ) {
2937 $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
2938
2939 if ( $oldName !== $this->via ) {
2940 //set the new renaming rule
2941 $writer->renameAssocTable( $oldName, $this->via );
2942 $this->via = NULL;
2943 }
2944 }
2945
2946 $type = $this->beau( $type );
2947 $count = 0;
2948
2949 if ( $this->getID() ) {
2950 $count = $redbean->getAssociationManager()->relatedCount( $this, $type, $this->withSql, $this->withParams );
2951 }
2952
2953 $this->clearModifiers();
2954 return (integer) $count;
2955 }
2956
2957 /**
2958 * Iterates through the specified own-list and
2959 * fetches all properties (with their type) and
2960 * returns the references.
2961 * Use this method to quickly load indirectly related
2962 * beans in an own-list. Whenever you cannot use a
2963 * shared-list this method offers the same convenience
2964 * by aggregating the parent beans of all children in
2965 * the specified own-list.
2966 *
2967 * Example:
2968 *
2969 * <code>
2970 * $quest->aggr( 'xownQuestTarget', 'target', 'quest' );
2971 * </code>
2972 *
2973 * Loads (in batch) and returns references to all
2974 * quest beans residing in the $questTarget->target properties
2975 * of each element in the xownQuestTargetList.
2976 *
2977 * @param string $list the list you wish to process
2978 * @param string $property the property to load
2979 * @param string $type the type of bean residing in this property (optional)
2980 *
2981 * @return array
2982 */
2983 public function &aggr( $list, $property, $type = NULL )
2984 {
2985 $this->via = NULL;
2986 $ids = $beanIndex = $references = array();
2987
2988 if ( strlen( $list ) < 4 ) throw new RedException('Invalid own-list.');
2989 if ( strpos( $list, 'own') !== 0 ) throw new RedException('Only own-lists can be aggregated.');
2990 if ( !ctype_upper( substr( $list, 3, 1 ) ) ) throw new RedException('Invalid own-list.');
2991
2992 if ( is_null( $type ) ) $type = $property;
2993
2994 foreach( $this->$list as $bean ) {
2995 $field = $property . '_id';
2996 if ( isset( $bean->$field) ) {
2997 $ids[] = $bean->$field;
2998 $beanIndex[$bean->$field] = $bean;
2999 }
3000 }
3001
3002 $beans = $this->beanHelper->getToolBox()->getRedBean()->batch( $type, $ids );
3003
3004 //now preload the beans as well
3005 foreach( $beans as $bean ) {
3006 $beanIndex[$bean->id]->setProperty( $property, $bean );
3007 }
3008
3009 foreach( $beanIndex as $indexedBean ) {
3010 $references[] = $indexedBean->$property;
3011 }
3012
3013 return $references;
3014 }
3015
3016 /**
3017 * Tests whether the database identities of two beans are equal.
3018 *
3019 * @param OODBBean $bean other bean
3020 *
3021 * @return boolean
3022 */
3023 public function equals(OODBBean $bean)
3024 {
3025 return (bool) (
3026 ( (string) $this->properties['id'] === (string) $bean->properties['id'] )
3027 && ( (string) $this->__info['type'] === (string) $bean->__info['type'] )
3028 );
3029 }
3030
3031 /**
3032 * Magic method jsonSerialize, implementation for the \JsonSerializable interface,
3033 * this method gets called by json_encode and facilitates a better JSON representation
3034 * of the bean. Exports the bean on JSON serialization, for the JSON fans.
3035 *
3036 * @see http://php.net/manual/en/class.jsonserializable.php
3037 *
3038 * @return array
3039 */
3040 public function jsonSerialize()
3041 {
3042 return $this->export();
3043 }
3044}
3045}
3046
3047namespace RedBeanPHP {
3048
3049use RedBeanPHP\Observer as Observer;
3050
3051/**
3052 * Observable
3053 * Base class for Observables
3054 *
3055 * @file RedBeanPHP/Observable.php
3056 * @author Gabor de Mooij and the RedBeanPHP community
3057 * @license BSD/GPLv2
3058 *
3059 * @copyright
3060 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
3061 * This source file is subject to the BSD/GPLv2 License that is bundled
3062 * with this source code in the file license.txt.
3063 */
3064abstract class Observable { //bracket must be here - otherwise coverage software does not understand.
3065
3066 /**
3067 * @var array
3068 */
3069 private $observers = array();
3070
3071 /**
3072 * Implementation of the Observer Pattern.
3073 * Adds an event listener to the observable object.
3074 * First argument should be the name of the event you wish to listen for.
3075 * Second argument should be the object that wants to be notified in case
3076 * the event occurs.
3077 *
3078 * @param string $eventname event identifier
3079 * @param Observer $observer observer instance
3080 *
3081 * @return void
3082 */
3083 public function addEventListener( $eventname, Observer $observer )
3084 {
3085 if ( !isset( $this->observers[$eventname] ) ) {
3086 $this->observers[$eventname] = array();
3087 }
3088
3089 foreach ( $this->observers[$eventname] as $o ) {
3090 if ( $o == $observer ) {
3091 return;
3092 }
3093 }
3094
3095 $this->observers[$eventname][] = $observer;
3096 }
3097
3098 /**
3099 * Notifies listeners.
3100 * Sends the signal $eventname, the event identifier and a message object
3101 * to all observers that have been registered to receive notification for
3102 * this event. Part of the observer pattern implementation in RedBeanPHP.
3103 *
3104 * @param string $eventname event you want signal
3105 * @param mixed $info message object to send along
3106 *
3107 * @return void
3108 */
3109 public function signal( $eventname, $info )
3110 {
3111 if ( !isset( $this->observers[$eventname] ) ) {
3112 $this->observers[$eventname] = array();
3113 }
3114
3115 foreach ( $this->observers[$eventname] as $observer ) {
3116 $observer->onEvent( $eventname, $info );
3117 }
3118 }
3119}
3120}
3121
3122namespace RedBeanPHP {
3123
3124/**
3125 * Observer.
3126 *
3127 * Interface for Observer object. Implementation of the
3128 * observer pattern.
3129 *
3130 * @file RedBeanPHP/Observer.php
3131 * @author Gabor de Mooij and the RedBeanPHP community
3132 * @license BSD/GPLv2
3133 * @desc Part of the observer pattern in RedBean
3134 *
3135 * @copyright
3136 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
3137 * This source file is subject to the BSD/GPLv2 License that is bundled
3138 * with this source code in the file license.txt.
3139 */
3140interface Observer
3141{
3142 /**
3143 * An observer object needs to be capable of receiving
3144 * notifications. Therefore the observer needs to implement the
3145 * onEvent method with two parameters: the event identifier specifying the
3146 * current event and a message object (in RedBeanPHP this can also be a bean).
3147 *
3148 * @param string $eventname event identifier
3149 * @param mixed $bean a message sent along with the notification
3150 *
3151 * @return void
3152 */
3153 public function onEvent( $eventname, $bean );
3154}
3155}
3156
3157namespace RedBeanPHP {
3158
3159/**
3160 * Adapter Interface.
3161 * Describes the API for a RedBeanPHP Database Adapter.
3162 * This interface defines the API contract for
3163 * a RedBeanPHP Database Adapter.
3164 *
3165 * @file RedBeanPHP/Adapter.php
3166 * @author Gabor de Mooij and the RedBeanPHP Community
3167 * @license BSD/GPLv2
3168 *
3169 * @copyright
3170 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
3171 * This source file is subject to the BSD/GPLv2 License that is bundled
3172 * with this source code in the file license.txt.
3173 */
3174interface Adapter
3175{
3176 /**
3177 * Returns the latest SQL statement.
3178 *
3179 * @return string
3180 */
3181 public function getSQL();
3182
3183 /**
3184 * Executes an SQL Statement using an array of values to bind
3185 * If $noevent is TRUE then this function will not signal its
3186 * observers to notify about the SQL execution; this to prevent
3187 * infinite recursion when using observers.
3188 *
3189 * @param string $sql string containing SQL code for database
3190 * @param array $bindings array of values to bind to parameters in query string
3191 * @param boolean $noevent no event firing
3192 *
3193 * @return void
3194 */
3195 public function exec( $sql, $bindings = array(), $noevent = FALSE );
3196
3197 /**
3198 * Executes an SQL Query and returns a resultset.
3199 * This method returns a multi dimensional resultset similar to getAll
3200 * The values array can be used to bind values to the place holders in the
3201 * SQL query.
3202 *
3203 * @param string $sql string containing SQL code for database
3204 * @param array $bindings array of values to bind to parameters in query string
3205 *
3206 * @return array
3207 */
3208 public function get( $sql, $bindings = array() );
3209
3210 /**
3211 * Executes an SQL Query and returns a resultset.
3212 * This method returns a single row (one array) resultset.
3213 * The values array can be used to bind values to the place holders in the
3214 * SQL query.
3215 *
3216 * @param string $sql string containing SQL code for database
3217 * @param array $bindings array of values to bind to parameters in query string
3218 *
3219 * @return array
3220 */
3221 public function getRow( $sql, $bindings = array() );
3222
3223 /**
3224 * Executes an SQL Query and returns a resultset.
3225 * This method returns a single column (one array) resultset.
3226 * The values array can be used to bind values to the place holders in the
3227 * SQL query.
3228 *
3229 * @param string $sql string containing SQL code for database
3230 * @param array $bindings array of values to bind to parameters in query string
3231 *
3232 * @return array
3233 */
3234 public function getCol( $sql, $bindings = array() );
3235
3236 /**
3237 * Executes an SQL Query and returns a resultset.
3238 * This method returns a single cell, a scalar value as the resultset.
3239 * The values array can be used to bind values to the place holders in the
3240 * SQL query.
3241 *
3242 * @param string $sql string containing SQL code for database
3243 * @param array $bindings array of values to bind to parameters in query string
3244 *
3245 * @return string
3246 */
3247 public function getCell( $sql, $bindings = array() );
3248
3249 /**
3250 * Executes the SQL query specified in $sql and takes
3251 * the first two columns of the resultset. This function transforms the
3252 * resultset into an associative array. Values from the the first column will
3253 * serve as keys while the values of the second column will be used as values.
3254 * The values array can be used to bind values to the place holders in the
3255 * SQL query.
3256 *
3257 * @param string $sql string containing SQL code for database
3258 * @param array $bindings array of values to bind to parameters in query string
3259 *
3260 * @return array
3261 */
3262 public function getAssoc( $sql, $bindings = array() );
3263
3264 /**
3265 * Executes the SQL query specified in $sql and indexes
3266 * the row by the first column.
3267 *
3268 * @param string $sql Sstring containing SQL code for databaseQL
3269 * @param array $bindings values to bind
3270 *
3271 * @return array
3272 */
3273 public function getAssocRow( $sql, $bindings = array() );
3274
3275 /**
3276 * Returns the latest insert ID.
3277 *
3278 * @return integer
3279 */
3280 public function getInsertID();
3281
3282 /**
3283 * Returns the number of rows that have been
3284 * affected by the last update statement.
3285 *
3286 * @return integer
3287 */
3288 public function getAffectedRows();
3289
3290 /**
3291 * Returns a database agnostic Cursor object.
3292 *
3293 * @param string $sql string containing SQL code for database
3294 * @param array $bindings array of values to bind to parameters in query string
3295 *
3296 * @return Cursor
3297 */
3298 public function getCursor( $sql, $bindings = array() );
3299
3300 /**
3301 * Returns the original database resource. This is useful if you want to
3302 * perform operations on the driver directly instead of working with the
3303 * adapter. RedBean will only access the adapter and never to talk
3304 * directly to the driver though.
3305 *
3306 * @return mixed
3307 */
3308 public function getDatabase();
3309
3310 /**
3311 * This method is part of the RedBean Transaction Management
3312 * mechanisms.
3313 * Starts a transaction.
3314 *
3315 * @return void
3316 */
3317 public function startTransaction();
3318
3319 /**
3320 * This method is part of the RedBean Transaction Management
3321 * mechanisms.
3322 * Commits the transaction.
3323 *
3324 * @return void
3325 */
3326 public function commit();
3327
3328 /**
3329 * This method is part of the RedBean Transaction Management
3330 * mechanisms.
3331 * Rolls back the transaction.
3332 *
3333 * @return void
3334 */
3335 public function rollback();
3336
3337 /**
3338 * Closes database connection.
3339 *
3340 * @return void
3341 */
3342 public function close();
3343}
3344}
3345
3346namespace RedBeanPHP\Adapter {
3347
3348use RedBeanPHP\Observable as Observable;
3349use RedBeanPHP\Adapter as Adapter;
3350use RedBeanPHP\Driver as Driver;
3351
3352/**
3353 * DBAdapter (Database Adapter)
3354 *
3355 * An adapter class to connect various database systems to RedBean
3356 * Database Adapter Class. The task of the database adapter class is to
3357 * communicate with the database driver. You can use all sorts of database
3358 * drivers with RedBeanPHP. The default database drivers that ships with
3359 * the RedBeanPHP library is the RPDO driver ( which uses the PHP Data Objects
3360 * Architecture aka PDO ).
3361 *
3362 * @file RedBeanPHP/Adapter/DBAdapter.php
3363 * @author Gabor de Mooij and the RedBeanPHP Community.
3364 * @license BSD/GPLv2
3365 *
3366 * @copyright
3367 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP community.
3368 * This source file is subject to the BSD/GPLv2 License that is bundled
3369 * with this source code in the file license.txt.
3370 */
3371class DBAdapter extends Observable implements Adapter
3372{
3373 /**
3374 * @var Driver
3375 */
3376 private $db = NULL;
3377
3378 /**
3379 * @var string
3380 */
3381 private $sql = '';
3382
3383 /**
3384 * Constructor.
3385 *
3386 * Creates an instance of the RedBean Adapter Class.
3387 * This class provides an interface for RedBean to work
3388 * with ADO compatible DB instances.
3389 *
3390 * @param Driver $database ADO Compatible DB Instance
3391 */
3392 public function __construct( $database )
3393 {
3394 $this->db = $database;
3395 }
3396
3397 /**
3398 * @see Adapter::getSQL
3399 */
3400 public function getSQL()
3401 {
3402 return $this->sql;
3403 }
3404
3405 /**
3406 * @see Adapter::exec
3407 */
3408 public function exec( $sql, $bindings = array(), $noevent = FALSE )
3409 {
3410 if ( !$noevent ) {
3411 $this->sql = $sql;
3412 $this->signal( 'sql_exec', $this );
3413 }
3414
3415 return $this->db->Execute( $sql, $bindings );
3416 }
3417
3418 /**
3419 * @see Adapter::get
3420 */
3421 public function get( $sql, $bindings = array() )
3422 {
3423 $this->sql = $sql;
3424 $this->signal( 'sql_exec', $this );
3425
3426 return $this->db->GetAll( $sql, $bindings );
3427 }
3428
3429 /**
3430 * @see Adapter::getRow
3431 */
3432 public function getRow( $sql, $bindings = array() )
3433 {
3434 $this->sql = $sql;
3435 $this->signal( 'sql_exec', $this );
3436
3437 return $this->db->GetRow( $sql, $bindings );
3438 }
3439
3440 /**
3441 * @see Adapter::getCol
3442 */
3443 public function getCol( $sql, $bindings = array() )
3444 {
3445 $this->sql = $sql;
3446 $this->signal( 'sql_exec', $this );
3447
3448 return $this->db->GetCol( $sql, $bindings );
3449 }
3450
3451 /**
3452 * @see Adapter::getAssoc
3453 */
3454 public function getAssoc( $sql, $bindings = array() )
3455 {
3456 $this->sql = $sql;
3457
3458 $this->signal( 'sql_exec', $this );
3459
3460 $rows = $this->db->GetAll( $sql, $bindings );
3461
3462 $assoc = array();
3463 if ( !$rows ) {
3464 return $assoc;
3465 }
3466
3467 foreach ( $rows as $row ) {
3468 if ( empty( $row ) ) continue;
3469
3470 if ( count( $row ) > 2 ) {
3471 $key = array_shift( $row );
3472 $value = $row;
3473 } elseif ( count( $row ) > 1 ) {
3474 $key = array_shift( $row );
3475 $value = array_shift( $row );
3476 } else {
3477 $key = array_shift( $row );
3478 $value = $key;
3479 }
3480
3481 $assoc[$key] = $value;
3482 }
3483
3484 return $assoc;
3485 }
3486
3487 /**
3488 * @see Adapter::getAssocRow
3489 */
3490 public function getAssocRow($sql, $bindings = array())
3491 {
3492 $this->sql = $sql;
3493 $this->signal( 'sql_exec', $this );
3494
3495 return $this->db->GetAssocRow( $sql, $bindings );
3496 }
3497
3498 /**
3499 * @see Adapter::getCell
3500 */
3501 public function getCell( $sql, $bindings = array(), $noSignal = NULL )
3502 {
3503 $this->sql = $sql;
3504
3505 if ( !$noSignal ) $this->signal( 'sql_exec', $this );
3506
3507 return $this->db->GetOne( $sql, $bindings );
3508 }
3509
3510 /**
3511 * @see Adapter::getCursor
3512 */
3513 public function getCursor( $sql, $bindings = array() )
3514 {
3515 return $this->db->GetCursor( $sql, $bindings );
3516 }
3517
3518 /**
3519 * @see Adapter::getInsertID
3520 */
3521 public function getInsertID()
3522 {
3523 return $this->db->getInsertID();
3524 }
3525
3526 /**
3527 * @see Adapter::getAffectedRows
3528 */
3529 public function getAffectedRows()
3530 {
3531 return $this->db->Affected_Rows();
3532 }
3533
3534 /**
3535 * @see Adapter::getDatabase
3536 */
3537 public function getDatabase()
3538 {
3539 return $this->db;
3540 }
3541
3542 /**
3543 * @see Adapter::startTransaction
3544 */
3545 public function startTransaction()
3546 {
3547 $this->db->StartTrans();
3548 }
3549
3550 /**
3551 * @see Adapter::commit
3552 */
3553 public function commit()
3554 {
3555 $this->db->CommitTrans();
3556 }
3557
3558 /**
3559 * @see Adapter::rollback
3560 */
3561 public function rollback()
3562 {
3563 $this->db->FailTrans();
3564 }
3565
3566 /**
3567 * @see Adapter::close.
3568 */
3569 public function close()
3570 {
3571 $this->db->close();
3572 }
3573}
3574}
3575
3576namespace RedBeanPHP {
3577
3578/**
3579 * Database Cursor Interface.
3580 * Represents a simple database cursor.
3581 * Cursors make it possible to create lightweight BeanCollections.
3582 *
3583 * @file RedBeanPHP/Cursor.php
3584 * @author Gabor de Mooij and the RedBeanPHP Community
3585 * @license BSD/GPLv2
3586 *
3587 * @copyright
3588 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
3589 * This source file is subject to the BSD/GPLv2 License that is bundled
3590 * with this source code in the file license.txt.
3591 */
3592interface Cursor
3593{
3594 /**
3595 * Retrieves the next row from the result set.
3596 *
3597 * @return array
3598 */
3599 public function getNextItem();
3600
3601 /**
3602 * Closes the database cursor.
3603 * Some databases require a cursor to be closed before executing
3604 * another statement/opening a new cursor.
3605 *
3606 * @return void
3607 */
3608 public function close();
3609}
3610}
3611
3612namespace RedBeanPHP\Cursor {
3613
3614use RedBeanPHP\Cursor as Cursor;
3615
3616/**
3617 * PDO Database Cursor
3618 * Implementation of PDO Database Cursor.
3619 * Used by the BeanCollection to fetch one bean at a time.
3620 *
3621 * @file RedBeanPHP/Cursor/PDOCursor.php
3622 * @author Gabor de Mooij and the RedBeanPHP Community
3623 * @license BSD/GPLv2
3624 *
3625 * @copyright
3626 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
3627 * This source file is subject to the BSD/GPLv2 License that is bundled
3628 * with this source code in the file license.txt.
3629 */
3630class PDOCursor implements Cursor
3631{
3632 /**
3633 * @var PDOStatement
3634 */
3635 protected $res;
3636
3637 /**
3638 * @var string
3639 */
3640 protected $fetchStyle;
3641
3642 /**
3643 * Constructor, creates a new instance of a PDO Database Cursor.
3644 *
3645 * @param PDOStatement $res the PDO statement
3646 * @param string $fetchStyle fetch style constant to use
3647 *
3648 * @return void
3649 */
3650 public function __construct( \PDOStatement $res, $fetchStyle )
3651 {
3652 $this->res = $res;
3653 $this->fetchStyle = $fetchStyle;
3654 }
3655
3656 /**
3657 * @see Cursor::getNextItem
3658 */
3659 public function getNextItem()
3660 {
3661 return $this->res->fetch();
3662 }
3663
3664 /**
3665 * @see Cursor::close
3666 */
3667 public function close()
3668 {
3669 $this->res->closeCursor();
3670 }
3671}
3672}
3673
3674namespace RedBeanPHP\Cursor {
3675
3676use RedBeanPHP\Cursor as Cursor;
3677
3678/**
3679 * NULL Database Cursor
3680 * Implementation of the NULL Cursor.
3681 * Used for an empty BeanCollection.
3682 *
3683 * @file RedBeanPHP/Cursor/NULLCursor.php
3684 * @author Gabor de Mooij and the RedBeanPHP Community
3685 * @license BSD/GPLv2
3686 *
3687 * @copyright
3688 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
3689 * This source file is subject to the BSD/GPLv2 License that is bundled
3690 * with this source code in the file license.txt.
3691 */
3692class NullCursor implements Cursor
3693{
3694 /**
3695 * @see Cursor::getNextItem
3696 */
3697 public function getNextItem()
3698 {
3699 return NULL;
3700 }
3701
3702 /**
3703 * @see Cursor::close
3704 */
3705 public function close()
3706 {
3707 return NULL;
3708 }
3709}
3710}
3711
3712namespace RedBeanPHP {
3713
3714use RedBeanPHP\Cursor as Cursor;
3715use RedBeanPHP\Repository as Repository;
3716
3717/**
3718 * BeanCollection.
3719 *
3720 * The BeanCollection represents a collection of beans and
3721 * makes it possible to use database cursors. The BeanCollection
3722 * has a method next() to obtain the first, next and last bean
3723 * in the collection. The BeanCollection does not implement the array
3724 * interface nor does it try to act like an array because it cannot go
3725 * backward or rewind itself.
3726 *
3727 * Use the BeanCollection for large datasets where skip/limit is not an
3728 * option. Keep in mind that ID-marking (querying a start ID) is a decent
3729 * alternative though.
3730 *
3731 * @file RedBeanPHP/BeanCollection.php
3732 * @author Gabor de Mooij and the RedBeanPHP community
3733 * @license BSD/GPLv2
3734 *
3735 * @copyright
3736 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
3737 * This source file is subject to the BSD/GPLv2 License that is bundled
3738 * with this source code in the file license.txt.
3739 */
3740class BeanCollection
3741{
3742 /**
3743 * @var Cursor
3744 */
3745 protected $cursor = NULL;
3746
3747 /**
3748 * @var Repository
3749 */
3750 protected $repository = NULL;
3751
3752 /**
3753 * @var string
3754 */
3755 protected $type = NULL;
3756
3757 /**
3758 * Constructor, creates a new instance of the BeanCollection.
3759 *
3760 * @param string $type type of beans in this collection
3761 * @param Repository $repository repository to use to generate bean objects
3762 * @param Cursor $cursor cursor object to use
3763 *
3764 * @return void
3765 */
3766 public function __construct( $type, Repository $repository, Cursor $cursor )
3767 {
3768 $this->type = $type;
3769 $this->cursor = $cursor;
3770 $this->repository = $repository;
3771 }
3772
3773 /**
3774 * Returns the next bean in the collection.
3775 * If called the first time, this will return the first bean in the collection.
3776 * If there are no more beans left in the collection, this method
3777 * will return NULL.
3778 *
3779 * @return OODBBean|NULL
3780 */
3781 public function next()
3782 {
3783 $row = $this->cursor->getNextItem();
3784 if ( $row ) {
3785 $beans = $this->repository->convertToBeans( $this->type, array( $row ) );
3786 $bean = array_shift( $beans );
3787 return $bean;
3788 }
3789 return NULL;
3790 }
3791
3792 /**
3793 * Closes the underlying cursor (needed for some databases).
3794 *
3795 * @return void
3796 */
3797 public function close()
3798 {
3799 $this->cursor->close();
3800 }
3801}
3802}
3803
3804namespace RedBeanPHP {
3805
3806/**
3807 * QueryWriter
3808 * Interface for QueryWriters.
3809 * Describes the API for a QueryWriter.
3810 *
3811 * Terminology:
3812 *
3813 * - beautified property (a camelCased property, has to be converted first)
3814 * - beautified type (a camelCased type, has to be converted first)
3815 * - type (a bean type, corresponds directly to a table)
3816 * - property (a bean property, corresponds directly to a column)
3817 * - table (a checked and quoted type, ready for use in a query)
3818 * - column (a checked and quoted property, ready for use in query)
3819 * - tableNoQ (same as type, but in context of a database operation)
3820 * - columnNoQ (same as property, but in context of a database operation)
3821 *
3822 * @file RedBeanPHP/QueryWriter.php
3823 * @author Gabor de Mooij and the RedBeanPHP community
3824 * @license BSD/GPLv2
3825 *
3826 * @copyright
3827 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
3828 * This source file is subject to the BSD/GPLv2 License that is bundled
3829 * with this source code in the file license.txt.
3830 */
3831interface QueryWriter
3832{
3833 /**
3834 * SQL filter constants
3835 */
3836 const C_SQLFILTER_READ = 'r';
3837 const C_SQLFILTER_WRITE = 'w';
3838
3839 /**
3840 * Query Writer constants.
3841 */
3842 const C_SQLSTATE_NO_SUCH_TABLE = 1;
3843 const C_SQLSTATE_NO_SUCH_COLUMN = 2;
3844 const C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION = 3;
3845
3846 /**
3847 * Define data type regions
3848 *
3849 * 00 - 80: normal data types
3850 * 80 - 99: special data types, only scan/code if requested
3851 * 99 : specified by user, don't change
3852 */
3853 const C_DATATYPE_RANGE_SPECIAL = 80;
3854 const C_DATATYPE_RANGE_SPECIFIED = 99;
3855
3856 /**
3857 * Define GLUE types for use with glueSQLCondition methods.
3858 * Determines how to prefix a snippet of SQL before appending it
3859 * to other SQL (or integrating it, mixing it otherwise).
3860 *
3861 * WHERE - glue as WHERE condition
3862 * AND - glue as AND condition
3863 */
3864 const C_GLUE_WHERE = 1;
3865 const C_GLUE_AND = 2;
3866
3867 /**
3868 * Writes an SQL Snippet for a JOIN, returns the
3869 * SQL snippet string.
3870 *
3871 * @note A default implementation is available in AQueryWriter
3872 * unless a database uses very different SQL this should suffice.
3873 *
3874 * @param string $type source type
3875 * @param string $targetType target type (type to join)
3876 * @param string $leftRight type of join (possible: 'LEFT', 'RIGHT' or 'INNER').
3877 *
3878 * @return string $joinSQLSnippet
3879 */
3880 public function writeJoin( $type, $targetType, $joinType );
3881
3882 /**
3883 * Glues an SQL snippet to the beginning of a WHERE clause.
3884 * This ensures users don't have to add WHERE to their query snippets.
3885 *
3886 * The snippet gets prefixed with WHERE or AND
3887 * if it starts with a condition.
3888 *
3889 * If the snippet does NOT start with a condition (or this function thinks so)
3890 * the snippet is returned as-is.
3891 *
3892 * The GLUE type determines the prefix:
3893 *
3894 * * NONE prefixes with WHERE
3895 * * WHERE prefixes with WHERE and replaces AND if snippets starts with AND
3896 * * AND prefixes with AND
3897 *
3898 * This method will never replace WHERE with AND since a snippet should never
3899 * begin with WHERE in the first place. OR is not supported.
3900 *
3901 * Only a limited set of clauses will be recognized as non-conditions.
3902 * For instance beginning a snippet with complex statements like JOIN or UNION
3903 * will not work. This is too complex for use in a snippet.
3904 *
3905 * @note A default implementation is available in AQueryWriter
3906 * unless a database uses very different SQL this should suffice.
3907 *
3908 * @param string $sql SQL Snippet
3909 * @param integer $glue the GLUE type - how to glue (C_GLUE_WHERE or C_GLUE_AND)
3910 *
3911 * @return string
3912 */
3913 public function glueSQLCondition( $sql, $glue = NULL );
3914
3915 /**
3916 * Determines if there is a LIMIT 1 clause in the SQL.
3917 * If not, it will add a LIMIT 1. (used for findOne).
3918 *
3919 * @note A default implementation is available in AQueryWriter
3920 * unless a database uses very different SQL this should suffice.
3921 *
3922 * @param string $sql query to scan and adjust
3923 *
3924 * @return string
3925 */
3926 public function glueLimitOne( $sql );
3927
3928 /**
3929 * Returns the tables that are in the database.
3930 *
3931 * @return array
3932 */
3933 public function getTables();
3934
3935 /**
3936 * This method will create a table for the bean.
3937 * This methods accepts a type and infers the corresponding table name.
3938 *
3939 * @param string $type type of bean you want to create a table for
3940 *
3941 * @return void
3942 */
3943 public function createTable( $type );
3944
3945 /**
3946 * Returns an array containing all the columns of the specified type.
3947 * The format of the return array looks like this:
3948 * $field => $type where $field is the name of the column and $type
3949 * is a database specific description of the datatype.
3950 *
3951 * This methods accepts a type and infers the corresponding table name.
3952 *
3953 * @param string $type type of bean you want to obtain a column list of
3954 *
3955 * @return array
3956 */
3957 public function getColumns( $type );
3958
3959 /**
3960 * Returns the Column Type Code (integer) that corresponds
3961 * to the given value type. This method is used to determine the minimum
3962 * column type required to represent the given value.
3963 *
3964 * @param string $value value
3965 *
3966 * @return integer
3967 */
3968 public function scanType( $value, $alsoScanSpecialForTypes = FALSE );
3969
3970 /**
3971 * This method will add a column to a table.
3972 * This methods accepts a type and infers the corresponding table name.
3973 *
3974 * @param string $type name of the table
3975 * @param string $column name of the column
3976 * @param integer $field data type for field
3977 *
3978 * @return void
3979 */
3980 public function addColumn( $type, $column, $field );
3981
3982 /**
3983 * Returns the Type Code for a Column Description.
3984 * Given an SQL column description this method will return the corresponding
3985 * code for the writer. If the include specials flag is set it will also
3986 * return codes for special columns. Otherwise special columns will be identified
3987 * as specified columns.
3988 *
3989 * @param string $typedescription description
3990 * @param boolean $includeSpecials whether you want to get codes for special columns as well
3991 *
3992 * @return integer
3993 */
3994 public function code( $typedescription, $includeSpecials = FALSE );
3995
3996 /**
3997 * This method will widen the column to the specified data type.
3998 * This methods accepts a type and infers the corresponding table name.
3999 *
4000 * @param string $type type / table that needs to be adjusted
4001 * @param string $column column that needs to be altered
4002 * @param integer $datatype target data type
4003 *
4004 * @return void
4005 */
4006 public function widenColumn( $type, $column, $datatype );
4007
4008 /**
4009 * Selects records from the database.
4010 * This methods selects the records from the database that match the specified
4011 * type, conditions (optional) and additional SQL snippet (optional).
4012 *
4013 * @param string $type name of the table you want to query
4014 * @param array $conditions criteria ( $column => array( $values ) )
4015 * @param string $addSQL additional SQL snippet
4016 * @param array $bindings bindings for SQL snippet
4017 *
4018 * @return array
4019 */
4020 public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
4021
4022 /**
4023 * Selects records from the database and returns a cursor.
4024 * This methods selects the records from the database that match the specified
4025 * type, conditions (optional) and additional SQL snippet (optional).
4026 *
4027 * @param string $type name of the table you want to query
4028 * @param array $conditions criteria ( $column => array( $values ) )
4029 * @param string $addSQL additional SQL snippet
4030 * @param array $bindings bindings for SQL snippet
4031 *
4032 * @return Cursor
4033 */
4034 public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() );
4035
4036 /**
4037 * Returns records through an intermediate type. This method is used to obtain records using a link table and
4038 * allows the SQL snippets to reference columns in the link table for additional filtering or ordering.
4039 *
4040 * @param string $sourceType source type, the reference type you want to use to fetch related items on the other side
4041 * @param string $destType destination type, the target type you want to get beans of
4042 * @param mixed $linkID ID to use for the link table
4043 * @param string $addSql Additional SQL snippet
4044 * @param array $bindings Bindings for SQL snippet
4045 *
4046 * @return array
4047 */
4048 public function queryRecordRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() );
4049
4050 /**
4051 * Returns the row that links $sourceType $sourcID to $destType $destID in an N-M relation.
4052 *
4053 * @param string $sourceType source type, the first part of the link you're looking for
4054 * @param string $destType destination type, the second part of the link you're looking for
4055 * @param string $sourceID ID for the source
4056 * @param string $destID ID for the destination
4057 *
4058 * @return array|null
4059 */
4060 public function queryRecordLink( $sourceType, $destType, $sourceID, $destID );
4061
4062 /**
4063 * Counts the number of records in the database that match the
4064 * conditions and additional SQL.
4065 *
4066 * @param string $type name of the table you want to query
4067 * @param array $conditions criteria ( $column => array( $values ) )
4068 * @param string $addSQL additional SQL snippet
4069 * @param array $bindings bindings for SQL snippet
4070 *
4071 * @return integer
4072 */
4073 public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
4074
4075 /**
4076 * Returns the number of records linked through $linkType and satisfying the SQL in $addSQL/$bindings.
4077 *
4078 * @param string $sourceType source type
4079 * @param string $targetType the thing you want to count
4080 * @param mixed $linkID the of the source type
4081 * @param string $addSQL additional SQL snippet
4082 * @param array $bindings bindings for SQL snippet
4083 *
4084 * @return integer
4085 */
4086 public function queryRecordCountRelated( $sourceType, $targetType, $linkID, $addSQL = '', $bindings = array() );
4087
4088 /**
4089 * Returns all rows of specified type that have been tagged with one of the
4090 * strings in the specified tag list array.
4091 *
4092 * Note that the additional SQL snippet can only be used for pagination,
4093 * the SQL snippet will be appended to the end of the query.
4094 *
4095 * @param string $type the bean type you want to query
4096 * @param array $tagList an array of strings, each string containing a tag title
4097 * @param boolean $all if TRUE only return records that have been associated with ALL the tags in the list
4098 * @param string $addSql addition SQL snippet, for pagination
4099 * @param array $bindings parameter bindings for additional SQL snippet
4100 *
4101 * @return array
4102 */
4103 public function queryTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() );
4104
4105 /**
4106 * This method should update (or insert a record), it takes
4107 * a table name, a list of update values ( $field => $value ) and an
4108 * primary key ID (optional). If no primary key ID is provided, an
4109 * INSERT will take place.
4110 * Returns the new ID.
4111 * This methods accepts a type and infers the corresponding table name.
4112 *
4113 * @param string $type name of the table to update
4114 * @param array $updatevalues list of update values
4115 * @param integer $id optional primary key ID value
4116 *
4117 * @return integer
4118 */
4119 public function updateRecord( $type, $updatevalues, $id = NULL );
4120
4121 /**
4122 * Deletes records from the database.
4123 * @note $addSql is always prefixed with ' WHERE ' or ' AND .'
4124 *
4125 * @param string $type name of the table you want to query
4126 * @param array $conditions criteria ( $column => array( $values ) )
4127 * @param string $sql additional SQL
4128 * @param array $bindings bindings
4129 *
4130 * @return void
4131 */
4132 public function deleteRecord( $type, $conditions = array(), $addSql = '', $bindings = array() );
4133
4134 /**
4135 * Deletes all links between $sourceType and $destType in an N-M relation.
4136 *
4137 * @param string $sourceType source type
4138 * @param string $destType destination type
4139 * @param string $sourceID source ID
4140 *
4141 * @return void
4142 */
4143 public function deleteRelations( $sourceType, $destType, $sourceID );
4144
4145 /**
4146 * @see QueryWriter::addUniqueConstaint
4147 */
4148 public function addUniqueIndex( $type, $columns );
4149
4150 /**
4151 * This method will add a UNIQUE constraint index to a table on columns $columns.
4152 * This methods accepts a type and infers the corresponding table name.
4153 *
4154 * @param string $type target bean type
4155 * @param array $columnsPartOfIndex columns to include in index
4156 *
4157 * @return void
4158 */
4159 public function addUniqueConstraint( $type, $columns );
4160
4161 /**
4162 * This method will check whether the SQL state is in the list of specified states
4163 * and returns TRUE if it does appear in this list or FALSE if it
4164 * does not. The purpose of this method is to translate the database specific state to
4165 * a one of the constants defined in this class and then check whether it is in the list
4166 * of standard states provided.
4167 *
4168 * @param string $state SQL state to consider
4169 * @param array $list list of standardized SQL state constants to check against
4170 *
4171 * @return boolean
4172 */
4173 public function sqlStateIn( $state, $list );
4174
4175 /**
4176 * This method will remove all beans of a certain type.
4177 * This methods accepts a type and infers the corresponding table name.
4178 *
4179 * @param string $type bean type
4180 *
4181 * @return void
4182 */
4183 public function wipe( $type );
4184
4185 /**
4186 * This method will add a foreign key from type and field to
4187 * target type and target field.
4188 * The foreign key is created without an action. On delete/update
4189 * no action will be triggered. The FK is only used to allow database
4190 * tools to generate pretty diagrams and to make it easy to add actions
4191 * later on.
4192 * This methods accepts a type and infers the corresponding table name.
4193 *
4194 *
4195 * @param string $type type that will have a foreign key field
4196 * @param string $targetType points to this type
4197 * @param string $property field that contains the foreign key value
4198 * @param string $targetProperty field where the fk points to
4199 * @param string $isDep whether target is dependent and should cascade on update/delete
4200 *
4201 * @return void
4202 */
4203 public function addFK( $type, $targetType, $property, $targetProperty, $isDep = false );
4204
4205 /**
4206 * This method will add an index to a type and field with name
4207 * $name.
4208 * This methods accepts a type and infers the corresponding table name.
4209 *
4210 * @param string $type type to add index to
4211 * @param string $name name of the new index
4212 * @param string $property field to index
4213 *
4214 * @return void
4215 */
4216 public function addIndex( $type, $name, $property );
4217
4218 /**
4219 * Checks and filters a database structure element like a table of column
4220 * for safe use in a query. A database structure has to conform to the
4221 * RedBeanPHP DB security policy which basically means only alphanumeric
4222 * symbols are allowed. This security policy is more strict than conventional
4223 * SQL policies and does therefore not require database specific escaping rules.
4224 *
4225 * @param string $databaseStructure name of the column/table to check
4226 * @param boolean $noQuotes TRUE to NOT put backticks or quotes around the string
4227 *
4228 * @return string
4229 */
4230 public function esc( $databaseStructure, $dontQuote = FALSE );
4231
4232 /**
4233 * Removes all tables and views from the database.
4234 *
4235 * @return void
4236 */
4237 public function wipeAll();
4238
4239 /**
4240 * Renames an association. For instance if you would like to refer to
4241 * album_song as: track you can specify this by calling this method like:
4242 *
4243 * <code>
4244 * renameAssociation('album_song','track')
4245 * </code>
4246 *
4247 * This allows:
4248 *
4249 * <code>
4250 * $album->sharedSong
4251 * </code>
4252 *
4253 * to add/retrieve beans from track instead of album_song.
4254 * Also works for exportAll().
4255 *
4256 * This method also accepts a single associative array as
4257 * its first argument.
4258 *
4259 * @param string|array $fromType original type name, or array
4260 * @param string $toType new type name (only if 1st argument is string)
4261 *
4262 * @return void
4263 */
4264 public function renameAssocTable( $fromType, $toType = NULL );
4265
4266 /**
4267 * Returns the format for link tables.
4268 * Given an array containing two type names this method returns the
4269 * name of the link table to be used to store and retrieve
4270 * association records. For instance, given two types: person and
4271 * project, the corresponding link table might be: 'person_project'.
4272 *
4273 * @param array $types two types array($type1, $type2)
4274 *
4275 * @return string
4276 */
4277 public function getAssocTable( $types );
4278
4279 /**
4280 * Given a bean type and a property, this method
4281 * tries to infer the fetch type using the foreign key
4282 * definitions in the database.
4283 * For instance: project, student -> person.
4284 * If no fetchType can be inferred, this method will return NULL.
4285 *
4286 * @note QueryWriters do not have to implement this method,
4287 * it's optional. A default version is available in AQueryWriter.
4288 *
4289 * @param $type the source type to fetch a target type for
4290 * @param $property the property to fetch the type of
4291 *
4292 * @return string|NULL
4293 */
4294 public function inferFetchType( $type, $property );
4295}
4296}
4297
4298namespace RedBeanPHP\QueryWriter {
4299
4300use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
4301use RedBeanPHP\RedException as RedException;
4302use RedBeanPHP\QueryWriter as QueryWriter;
4303use RedBeanPHP\OODBBean as OODBBean;
4304use RedBeanPHP\RedException\SQL as SQLException;
4305
4306/**
4307 * RedBeanPHP Abstract Query Writer.
4308 * Represents an abstract Database to RedBean
4309 * To write a driver for a different database for RedBean
4310 * Contains a number of functions all implementors can
4311 * inherit or override.
4312 *
4313 * @file RedBeanPHP/QueryWriter/AQueryWriter.php
4314 * @author Gabor de Mooij and the RedBeanPHP Community
4315 * @license BSD/GPLv2
4316 *
4317 * @copyright
4318 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
4319 * This source file is subject to the BSD/GPLv2 License that is bundled
4320 * with this source code in the file license.txt.
4321 */
4322abstract class AQueryWriter
4323{
4324 /**
4325 * @var array
4326 */
4327 private static $sqlFilters = array();
4328
4329 /**
4330 * @var boolean
4331 */
4332 private static $flagSQLFilterSafeMode = false;
4333
4334 /**
4335 * @var boolean
4336 */
4337 private static $flagNarrowFieldMode = true;
4338
4339 /**
4340 * @var array
4341 */
4342 public static $renames = array();
4343
4344 /**
4345 * @var DBAdapter
4346 */
4347 protected $adapter;
4348
4349 /**
4350 * @var string
4351 */
4352 protected $defaultValue = 'NULL';
4353
4354 /**
4355 * @var string
4356 */
4357 protected $quoteCharacter = '';
4358
4359 /**
4360 * @var boolean
4361 */
4362 protected $flagUseCache = TRUE;
4363
4364 /**
4365 * @var array
4366 */
4367 protected $cache = array();
4368
4369 /**
4370 * @var integer
4371 */
4372 protected $maxCacheSizePerType = 20;
4373
4374 /**
4375 * @var array
4376 */
4377 public $typeno_sqltype = array();
4378
4379 /**
4380 * Checks whether a number can be treated like an int.
4381 *
4382 * @param string $value string representation of a certain value
4383 *
4384 * @return boolean
4385 */
4386 public static function canBeTreatedAsInt( $value )
4387 {
4388 return (bool) ( strval( $value ) === strval( intval( $value ) ) );
4389 }
4390
4391 /**
4392 * @see QueryWriter::getAssocTableFormat
4393 */
4394 public static function getAssocTableFormat( $types )
4395 {
4396 sort( $types );
4397
4398 $assoc = implode( '_', $types );
4399
4400 return ( isset( self::$renames[$assoc] ) ) ? self::$renames[$assoc] : $assoc;
4401 }
4402
4403 /**
4404 * @see QueryWriter::renameAssociation
4405 */
4406 public static function renameAssociation( $from, $to = NULL )
4407 {
4408 if ( is_array( $from ) ) {
4409 foreach ( $from as $key => $value ) self::$renames[$key] = $value;
4410
4411 return;
4412 }
4413
4414 self::$renames[$from] = $to;
4415 }
4416
4417 /**
4418 * Globally available service method for RedBeanPHP.
4419 * Converts a camel cased string to a snake cased string.
4420 *
4421 * @param string $camel camelCased string to converty to snake case
4422 *
4423 * @return string
4424 */
4425 public static function camelsSnake( $camel )
4426 {
4427 return strtolower( preg_replace( '/(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])/', '_$1$2', $camel ) );
4428 }
4429
4430 /**
4431 * Clears renames.
4432 *
4433 * @return void
4434 */
4435 public static function clearRenames()
4436 {
4437 self::$renames = array();
4438 }
4439
4440 /**
4441 * Toggles 'Narrow Field Mode'.
4442 * In Narrow Field mode the queryRecord method will
4443 * narrow its selection field to
4444 *
4445 * SELECT {table}.*
4446 *
4447 * instead of
4448 *
4449 * SELECT *
4450 *
4451 * This is a better way of querying because it allows
4452 * more flexibility (for instance joins). However if you need
4453 * the wide selector for backward compatibility; use this method
4454 * to turn OFF Narrow Field Mode by passing FALSE.
4455 *
4456 * @param boolean $narrowField TRUE = Narrow Field FALSE = Wide Field
4457 *
4458 * @return void
4459 */
4460 public static function setNarrowFieldMode( $narrowField )
4461 {
4462 self::$flagNarrowFieldMode = (boolean) $narrowField;
4463 }
4464
4465 /**
4466 * Sets SQL filters.
4467 * This is a lowlevel method to set the SQL filter array.
4468 * The format of this array is:
4469 *
4470 * <code>
4471 * array(
4472 * '<MODE, i.e. 'r' for read, 'w' for write>' => array(
4473 * '<TABLE NAME>' => array(
4474 * '<COLUMN NAME>' => '<SQL>'
4475 * )
4476 * )
4477 * )
4478 * </code>
4479 *
4480 * Example:
4481 *
4482 * <code>
4483 * array(
4484 * QueryWriter::C_SQLFILTER_READ => array(
4485 * 'book' => array(
4486 * 'title' => ' LOWER(book.title) '
4487 * )
4488 * )
4489 * </code>
4490 *
4491 * Note that you can use constants instead of magical chars
4492 * as keys for the uppermost array.
4493 * This is a lowlevel method. For a more friendly method
4494 * please take a look at the facade: R::bindFunc().
4495 *
4496 * @param array list of filters to set
4497 *
4498 * @return void
4499 */
4500 public static function setSQLFilters( $sqlFilters, $safeMode = false )
4501 {
4502 self::$flagSQLFilterSafeMode = (boolean) $safeMode;
4503 self::$sqlFilters = $sqlFilters;
4504 }
4505
4506 /**
4507 * Returns current SQL Filters.
4508 * This method returns the raw SQL filter array.
4509 * This is a lowlevel method. For a more friendly method
4510 * please take a look at the facade: R::bindFunc().
4511 *
4512 * @return array
4513 */
4514 public static function getSQLFilters()
4515 {
4516 return self::$sqlFilters;
4517 }
4518
4519 /**
4520 * Returns a cache key for the cache values passed.
4521 * This method returns a fingerprint string to be used as a key to store
4522 * data in the writer cache.
4523 *
4524 * @param array $keyValues key-value to generate key for
4525 *
4526 * @return string
4527 */
4528 private function getCacheKey( $keyValues )
4529 {
4530 return json_encode( $keyValues );
4531 }
4532
4533 /**
4534 * Returns the values associated with the provided cache tag and key.
4535 *
4536 * @param string $cacheTag cache tag to use for lookup
4537 * @param string $key key to use for lookup
4538 *
4539 * @return mixed
4540 */
4541 private function getCached( $cacheTag, $key )
4542 {
4543 $sql = $this->adapter->getSQL();
4544
4545 if ($this->updateCache()) {
4546 if ( isset( $this->cache[$cacheTag][$key] ) ) {
4547 return $this->cache[$cacheTag][$key];
4548 }
4549 }
4550
4551 return NULL;
4552 }
4553
4554 /**
4555 * Checks if the previous query had a keep-cache tag.
4556 * If so, the cache will persist, otherwise the cache will be flushed.
4557 *
4558 * Returns TRUE if the cache will remain and FALSE if a flush has
4559 * been performed.
4560 *
4561 * @return boolean
4562 */
4563 private function updateCache()
4564 {
4565 $sql = $this->adapter->getSQL();
4566 if ( strpos( $sql, '-- keep-cache' ) !== strlen( $sql ) - 13 ) {
4567 // If SQL has been taken place outside of this method then something else then
4568 // a select query might have happened! (or instruct to keep cache)
4569 $this->cache = array();
4570 return FALSE;
4571 }
4572 return TRUE;
4573 }
4574
4575 /**
4576 * Stores data from the writer in the cache under a specific key and cache tag.
4577 * A cache tag is used to make sure the cache remains consistent. In most cases the cache tag
4578 * will be the bean type, this makes sure queries associated with a certain reference type will
4579 * never contain conflicting data.
4580 * Why not use the cache tag as a key? Well
4581 * we need to make sure the cache contents fits the key (and key is based on the cache values).
4582 * Otherwise it would be possible to store two different result sets under the same key (the cache tag).
4583 *
4584 * In previous versions you could only store one key-entry, I have changed this to
4585 * improve caching efficiency (issue #400).
4586 *
4587 * @param string $cacheTag cache tag (secondary key)
4588 * @param string $key key to store values under
4589 * @param array $values content to be stored
4590 *
4591 * @return void
4592 */
4593 private function putResultInCache( $cacheTag, $key, $values )
4594 {
4595 if ( isset( $this->cache[$cacheTag] ) ) {
4596 if ( count( $this->cache[$cacheTag] ) > $this->maxCacheSizePerType ) array_shift( $this->cache[$cacheTag] );
4597 } else {
4598 $this->cache[$cacheTag] = array();
4599 }
4600
4601 $this->cache[$cacheTag][$key] = $values;
4602 }
4603
4604 /**
4605 * Creates an SQL snippet from a list of conditions of format:
4606 *
4607 * <code>
4608 * array(
4609 * key => array(
4610 * value1, value2, value3 ....
4611 * )
4612 * )
4613 * </code>
4614 *
4615 * @param array $conditions list of conditions
4616 * @param array $bindings parameter bindings for SQL snippet
4617 * @param string $addSql additional SQL snippet to append to result
4618 *
4619 * @return string
4620 */
4621 private function makeSQLFromConditions( $conditions, &$bindings, $addSql = '' )
4622 {
4623 reset( $bindings );
4624 $firstKey = key( $bindings );
4625 $paramTypeIsNum = ( is_numeric( $firstKey ) );
4626 $counter = 0;
4627
4628 $sqlConditions = array();
4629 foreach ( $conditions as $column => $values ) {
4630 if ( !count( $values ) ) continue;
4631
4632 $sql = $this->esc( $column );
4633 $sql .= ' IN ( ';
4634
4635 if ( !is_array( $values ) ) $values = array( $values );
4636
4637 // If it's safe to skip bindings, do so...
4638 if ( ctype_digit( implode( '', $values ) ) ) {
4639 $sql .= implode( ',', $values ) . ' ) ';
4640
4641 // only numeric, cant do much harm
4642 $sqlConditions[] = $sql;
4643 } else {
4644
4645 if ( $paramTypeIsNum ) {
4646 $sql .= implode( ',', array_fill( 0, count( $values ), '?' ) ) . ' ) ';
4647
4648 array_unshift($sqlConditions, $sql);
4649
4650 foreach ( $values as $k => $v ) {
4651 $values[$k] = strval( $v );
4652
4653 array_unshift( $bindings, $v );
4654 }
4655 } else {
4656
4657 $slots = array();
4658
4659 foreach( $values as $k => $v ) {
4660 $slot = ':slot'.$counter++;
4661 $slots[] = $slot;
4662 $bindings[$slot] = strval( $v );
4663 }
4664
4665 $sql .= implode( ',', $slots ).' ) ';
4666 $sqlConditions[] = $sql;
4667 }
4668 }
4669 }
4670
4671 $sql = '';
4672 if ( is_array( $sqlConditions ) && count( $sqlConditions ) > 0 ) {
4673 $sql = implode( ' AND ', $sqlConditions );
4674 $sql = " WHERE ( $sql ) ";
4675
4676 if ( $addSql ) $sql .= $addSql;
4677 } elseif ( $addSql ) {
4678 $sql = $addSql;
4679 }
4680
4681 return $sql;
4682 }
4683
4684 /**
4685 * Returns the table names and column names for a relational query.
4686 *
4687 * @param string $sourceType type of the source bean
4688 * @param string $destType type of the bean you want to obtain using the relation
4689 * @param boolean $noQuote TRUE if you want to omit quotes
4690 *
4691 * @return array
4692 */
4693 private function getRelationalTablesAndColumns( $sourceType, $destType, $noQuote = FALSE )
4694 {
4695 $linkTable = $this->esc( $this->getAssocTable( array( $sourceType, $destType ) ), $noQuote );
4696 $sourceCol = $this->esc( $sourceType . '_id', $noQuote );
4697
4698 if ( $sourceType === $destType ) {
4699 $destCol = $this->esc( $destType . '2_id', $noQuote );
4700 } else {
4701 $destCol = $this->esc( $destType . '_id', $noQuote );
4702 }
4703
4704 $sourceTable = $this->esc( $sourceType, $noQuote );
4705 $destTable = $this->esc( $destType, $noQuote );
4706
4707 return array( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol );
4708 }
4709
4710 /**
4711 * Given a type and a property name this method
4712 * returns the foreign key map section associated with this pair.
4713 *
4714 * @param string $type name of the type
4715 * @param string $property name of the property
4716 *
4717 * @return array|NULL
4718 */
4719 protected function getForeignKeyForTypeProperty( $type, $property )
4720 {
4721 $property = $this->esc( $property, TRUE );
4722
4723 try {
4724 $map = $this->getKeyMapForType( $type );
4725 } catch ( SQLException $e ) {
4726 return NULL;
4727 }
4728
4729 foreach( $map as $key ) {
4730 if ( $key['from'] === $property ) return $key;
4731 }
4732 return NULL;
4733 }
4734
4735 /**
4736 * Returns the foreign key map (FKM) for a type.
4737 * A foreign key map describes the foreign keys in a table.
4738 * A FKM always has the same structure:
4739 *
4740 * <code>
4741 * array(
4742 * 'name' => <name of the foreign key>
4743 * 'from' => <name of the column on the source table>
4744 * 'table' => <name of the target table>
4745 * 'to' => <name of the target column> (most of the time 'id')
4746 * 'on_update' => <update rule: 'SET NULL','CASCADE' or 'RESTRICT'>
4747 * 'on_delete' => <delete rule: 'SET NULL','CASCADE' or 'RESTRICT'>
4748 * )
4749 * </code>
4750 *
4751 * @note the keys in the result array are FKDLs, i.e. descriptive unique
4752 * keys per source table. Also see: AQueryWriter::makeFKLabel for details.
4753 *
4754 * @param string $type the bean type you wish to obtain a key map of
4755 *
4756 * @return array
4757 */
4758 protected function getKeyMapForType( $type )
4759 {
4760 return array();
4761 }
4762
4763 /**
4764 * This method makes a key for a foreign key description array.
4765 * This key is a readable string unique for every source table.
4766 * This uniform key is called the FKDL Foreign Key Description Label.
4767 * Note that the source table is not part of the FKDL because
4768 * this key is supposed to be 'per source table'. If you wish to
4769 * include a source table, prefix the key with 'on_table_<SOURCE>_'.
4770 *
4771 * @param string $from the column of the key in the source table
4772 * @param string $type the type (table) where the key points to
4773 * @param string $to the target column of the foreign key (mostly just 'id')
4774 *
4775 * @return string
4776 */
4777 protected function makeFKLabel($from, $type, $to)
4778 {
4779 return "from_{$from}_to_table_{$type}_col_{$to}";
4780 }
4781
4782 /**
4783 * Returns an SQL Filter snippet for reading.
4784 *
4785 * @param string $type type of bean
4786 *
4787 * @return string
4788 */
4789 protected function getSQLFilterSnippet( $type )
4790 {
4791 $existingCols = array();
4792 if (self::$flagSQLFilterSafeMode) {
4793 $existingCols = $this->getColumns( $type );
4794 }
4795
4796 $sqlFilters = array();
4797 if ( isset( self::$sqlFilters[QueryWriter::C_SQLFILTER_READ][$type] ) ) {
4798 foreach( self::$sqlFilters[QueryWriter::C_SQLFILTER_READ][$type] as $property => $sqlFilter ) {
4799 if ( !self::$flagSQLFilterSafeMode || isset( $existingCols[$property] ) ) {
4800 $sqlFilters[] = $sqlFilter.' AS '.$property.' ';
4801 }
4802 }
4803 }
4804 $sqlFilterStr = ( count($sqlFilters) ) ? ( ','.implode( ',', $sqlFilters ) ) : '';
4805 return $sqlFilterStr;
4806 }
4807
4808 /**
4809 * Generates a list of parameters (slots) for an SQL snippet.
4810 * This method calculates the correct number of slots to insert in the
4811 * SQL snippet and determines the correct type of slot. If the bindings
4812 * array contains named parameters this method will return named ones and
4813 * update the keys in the value list accordingly (that's why we use the &).
4814 *
4815 * If you pass an offset the bindings will be re-added to the value list.
4816 * Some databases cant handle duplicate parameter names in queries.
4817 *
4818 * @param array &$valueList list of values to generate slots for (gets modified if needed)
4819 * @param array $otherBindings list of additional bindings
4820 * @param integer $offset start counter at...
4821 *
4822 * @return string
4823 */
4824 protected function getParametersForInClause( &$valueList, $otherBindings, $offset = 0 )
4825 {
4826 if ( is_array( $otherBindings ) && count( $otherBindings ) > 0 ) {
4827 reset( $otherBindings );
4828
4829 $key = key( $otherBindings );
4830
4831 if ( !is_numeric($key) ) {
4832 $filler = array();
4833 $newList = (!$offset) ? array() : $valueList;
4834 $counter = $offset;
4835
4836 foreach( $valueList as $value ) {
4837 $slot = ':slot' . ( $counter++ );
4838 $filler[] = $slot;
4839 $newList[$slot] = $value;
4840 }
4841
4842 // Change the keys!
4843 $valueList = $newList;
4844
4845 return implode( ',', $filler );
4846 }
4847 }
4848
4849 return implode( ',', array_fill( 0, count( $valueList ), '?' ) );
4850 }
4851
4852 /**
4853 * Adds a data type to the list of data types.
4854 * Use this method to add a new column type definition to the writer.
4855 * Used for UUID support.
4856 *
4857 * @param integer $dataTypeID magic number constant assigned to this data type
4858 * @param string $SQLDefinition SQL column definition (i.e. INT(11))
4859 *
4860 * @return self
4861 */
4862 protected function addDataType( $dataTypeID, $SQLDefinition )
4863 {
4864 $this->typeno_sqltype[ $dataTypeID ] = $SQLDefinition;
4865 $this->sqltype_typeno[ $SQLDefinition ] = $dataTypeID;
4866 return $this;
4867 }
4868
4869 /**
4870 * Returns the sql that should follow an insert statement.
4871 *
4872 * @param string $table name
4873 *
4874 * @return string
4875 */
4876 protected function getInsertSuffix( $table )
4877 {
4878 return '';
4879 }
4880
4881 /**
4882 * Checks whether a value starts with zeros. In this case
4883 * the value should probably be stored using a text datatype instead of a
4884 * numerical type in order to preserve the zeros.
4885 *
4886 * @param string $value value to be checked.
4887 *
4888 * @return boolean
4889 */
4890 protected function startsWithZeros( $value )
4891 {
4892 $value = strval( $value );
4893
4894 if ( strlen( $value ) > 1 && strpos( $value, '0' ) === 0 && strpos( $value, '0.' ) !== 0 ) {
4895 return TRUE;
4896 } else {
4897 return FALSE;
4898 }
4899 }
4900
4901 /**
4902 * Inserts a record into the database using a series of insert columns
4903 * and corresponding insertvalues. Returns the insert id.
4904 *
4905 * @param string $table table to perform query on
4906 * @param array $insertcolumns columns to be inserted
4907 * @param array $insertvalues values to be inserted
4908 *
4909 * @return integer
4910 */
4911 protected function insertRecord( $type, $insertcolumns, $insertvalues )
4912 {
4913 $default = $this->defaultValue;
4914 $suffix = $this->getInsertSuffix( $type );
4915 $table = $this->esc( $type );
4916
4917 if ( count( $insertvalues ) > 0 && is_array( $insertvalues[0] ) && count( $insertvalues[0] ) > 0 ) {
4918
4919 $insertSlots = array();
4920 foreach ( $insertcolumns as $k => $v ) {
4921 $insertcolumns[$k] = $this->esc( $v );
4922
4923 if (isset(self::$sqlFilters['w'][$type][$v])) {
4924 $insertSlots[] = self::$sqlFilters['w'][$type][$v];
4925 } else {
4926 $insertSlots[] = '?';
4927 }
4928 }
4929
4930 $insertSQL = "INSERT INTO $table ( id, " . implode( ',', $insertcolumns ) . " ) VALUES
4931 ( $default, " . implode( ',', $insertSlots ) . " ) $suffix";
4932
4933 $ids = array();
4934 foreach ( $insertvalues as $i => $insertvalue ) {
4935 $ids[] = $this->adapter->getCell( $insertSQL, $insertvalue, $i );
4936 }
4937
4938 $result = count( $ids ) === 1 ? array_pop( $ids ) : $ids;
4939 } else {
4940 $result = $this->adapter->getCell( "INSERT INTO $table (id) VALUES($default) $suffix" );
4941 }
4942
4943 if ( $suffix ) return $result;
4944
4945 $last_id = $this->adapter->getInsertID();
4946
4947 return $last_id;
4948 }
4949
4950 /**
4951 * Checks table name or column name.
4952 *
4953 * @param string $table table string
4954 *
4955 * @return string
4956 */
4957 protected function check( $struct )
4958 {
4959 if ( !is_string( $struct ) || !preg_match( '/^[a-zA-Z0-9_]+$/', $struct ) ) {
4960 throw new RedException( 'Identifier does not conform to RedBeanPHP security policies.' );
4961 }
4962
4963 return $struct;
4964 }
4965
4966 /**
4967 * Checks whether the specified type (i.e. table) already exists in the database.
4968 * Not part of the Object Database interface!
4969 *
4970 * @param string $table table name
4971 *
4972 * @return boolean
4973 */
4974 public function tableExists( $table )
4975 {
4976 $tables = $this->getTables();
4977
4978 return in_array( $table, $tables );
4979 }
4980
4981 /**
4982 * @see QueryWriter::glueSQLCondition
4983 */
4984 public function glueSQLCondition( $sql, $glue = NULL )
4985 {
4986 static $snippetCache = array();
4987
4988 if ( trim( $sql ) === '' ) {
4989 return $sql;
4990 }
4991
4992 $key = $glue . '|' . $sql;
4993
4994 if ( isset( $snippetCache[$key] ) ) {
4995 return $snippetCache[$key];
4996 }
4997
4998 $lsql = ltrim( $sql );
4999
5000 if ( preg_match( '/^(INNER|LEFT|RIGHT|JOIN|AND|OR|WHERE|ORDER|GROUP|HAVING|LIMIT|OFFSET)\s+/i', $lsql ) ) {
5001 if ( $glue === QueryWriter::C_GLUE_WHERE && stripos( $lsql, 'AND' ) === 0 ) {
5002 $snippetCache[$key] = ' WHERE ' . substr( $lsql, 3 );
5003 } else {
5004 $snippetCache[$key] = $sql;
5005 }
5006 } else {
5007 $snippetCache[$key] = ( ( $glue === QueryWriter::C_GLUE_AND ) ? ' AND ' : ' WHERE ') . $sql;
5008 }
5009
5010 return $snippetCache[$key];
5011 }
5012
5013 /**
5014 * @see QueryWriter::glueLimitOne
5015 */
5016 public function glueLimitOne( $sql = '')
5017 {
5018 return ( strpos( strtoupper( $sql ), 'LIMIT' ) === FALSE ) ? ( $sql . ' LIMIT 1 ' ) : $sql;
5019 }
5020
5021 /**
5022 * @see QueryWriter::esc
5023 */
5024 public function esc( $dbStructure, $dontQuote = FALSE )
5025 {
5026 $this->check( $dbStructure );
5027
5028 return ( $dontQuote ) ? $dbStructure : $this->quoteCharacter . $dbStructure . $this->quoteCharacter;
5029 }
5030
5031 /**
5032 * @see QueryWriter::addColumn
5033 */
5034 public function addColumn( $type, $column, $field )
5035 {
5036 $table = $type;
5037 $type = $field;
5038 $table = $this->esc( $table );
5039 $column = $this->esc( $column );
5040
5041 $type = ( isset( $this->typeno_sqltype[$type] ) ) ? $this->typeno_sqltype[$type] : '';
5042
5043 $this->adapter->exec( "ALTER TABLE $table ADD $column $type " );
5044 }
5045
5046 /**
5047 * @see QueryWriter::updateRecord
5048 */
5049 public function updateRecord( $type, $updatevalues, $id = NULL )
5050 {
5051 $table = $type;
5052
5053 if ( !$id ) {
5054 $insertcolumns = $insertvalues = array();
5055
5056 foreach ( $updatevalues as $pair ) {
5057 $insertcolumns[] = $pair['property'];
5058 $insertvalues[] = $pair['value'];
5059 }
5060
5061 //Otherwise psql returns string while MySQL/SQLite return numeric causing problems with additions (array_diff)
5062 return (string) $this->insertRecord( $table, $insertcolumns, array( $insertvalues ) );
5063 }
5064
5065 if ( $id && !count( $updatevalues ) ) {
5066 return $id;
5067 }
5068
5069 $table = $this->esc( $table );
5070 $sql = "UPDATE $table SET ";
5071
5072 $p = $v = array();
5073
5074 foreach ( $updatevalues as $uv ) {
5075
5076 if ( isset( self::$sqlFilters['w'][$type][$uv['property']] ) ) {
5077 $p[] = " {$this->esc( $uv["property"] )} = ". self::$sqlFilters['w'][$type][$uv['property']];
5078 } else {
5079 $p[] = " {$this->esc( $uv["property"] )} = ? ";
5080 }
5081
5082 $v[] = $uv['value'];
5083 }
5084
5085 $sql .= implode( ',', $p ) . ' WHERE id = ? ';
5086
5087 $v[] = $id;
5088
5089 $this->adapter->exec( $sql, $v );
5090
5091 return $id;
5092 }
5093
5094 /**
5095 * @see QueryWriter::writeJoin
5096 */
5097 public function writeJoin( $type, $targetType, $leftRight = 'LEFT' )
5098 {
5099 if ( $leftRight !== 'LEFT' && $leftRight !== 'RIGHT' && $leftRight !== 'INNER' )
5100 throw new RedException( 'Invalid JOIN.' );
5101
5102 $table = $this->esc( $type );
5103 $targetTable = $this->esc( $targetType );
5104 $field = $this->esc( $targetType, TRUE );
5105 return " {$leftRight} JOIN {$targetTable} ON {$targetTable}.id = {$table}.{$field}_id ";
5106 }
5107
5108 /**
5109 * @see QueryWriter::queryRecord
5110 */
5111 public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
5112 {
5113 $addSql = $this->glueSQLCondition( $addSql, ( count($conditions) > 0) ? QueryWriter::C_GLUE_AND : NULL );
5114
5115 $key = NULL;
5116 if ( $this->flagUseCache ) {
5117 $key = $this->getCacheKey( array( $conditions, $addSql, $bindings, 'select' ) );
5118
5119 if ( $cached = $this->getCached( $type, $key ) ) {
5120 return $cached;
5121 }
5122 }
5123
5124 $table = $this->esc( $type );
5125
5126 $sqlFilterStr = '';
5127 if ( count( self::$sqlFilters ) ) {
5128 $sqlFilterStr = $this->getSQLFilterSnippet( $type );
5129 }
5130
5131 $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
5132
5133 $fieldSelection = ( self::$flagNarrowFieldMode ) ? "{$table}.*" : '*';
5134 $sql = "SELECT {$fieldSelection} {$sqlFilterStr} FROM {$table} {$sql} -- keep-cache";
5135
5136 $rows = $this->adapter->get( $sql, $bindings );
5137
5138 if ( $this->flagUseCache && $key ) {
5139 $this->putResultInCache( $type, $key, $rows );
5140 }
5141
5142 return $rows;
5143 }
5144
5145 /**
5146 * @see QueryWriter::queryRecordWithCursor
5147 */
5148 public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() )
5149 {
5150 $sql = $this->glueSQLCondition( $addSql, NULL );
5151 $table = $this->esc( $type );
5152 $sql = "SELECT {$table}.* FROM {$table} {$sql}";
5153 return $this->adapter->getCursor( $sql, $bindings );
5154 }
5155
5156 /**
5157 * @see QueryWriter::queryRecordRelated
5158 */
5159 public function queryRecordRelated( $sourceType, $destType, $linkIDs, $addSql = '', $bindings = array() )
5160 {
5161 $addSql = $this->glueSQLCondition( $addSql, QueryWriter::C_GLUE_WHERE );
5162
5163 list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
5164
5165 $key = $this->getCacheKey( array( $sourceType, $destType, implode( ',', $linkIDs ), $addSql, $bindings ) );
5166
5167 if ( $this->flagUseCache && $cached = $this->getCached( $destType, $key ) ) {
5168 return $cached;
5169 }
5170
5171 $inClause = $this->getParametersForInClause( $linkIDs, $bindings );
5172
5173 $sqlFilterStr = '';
5174 if ( count( self::$sqlFilters ) ) {
5175 $sqlFilterStr = $this->getSQLFilterSnippet( $destType );
5176 }
5177
5178 if ( $sourceType === $destType ) {
5179 $inClause2 = $this->getParametersForInClause( $linkIDs, $bindings, count( $bindings ) ); //for some databases
5180 $sql = "
5181 SELECT
5182 {$destTable}.* {$sqlFilterStr} ,
5183 COALESCE(
5184 NULLIF({$linkTable}.{$sourceCol}, {$destTable}.id),
5185 NULLIF({$linkTable}.{$destCol}, {$destTable}.id)) AS linked_by
5186 FROM {$linkTable}
5187 INNER JOIN {$destTable} ON
5188 ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) ) OR
5189 ( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} IN ($inClause2) )
5190 {$addSql}
5191 -- keep-cache";
5192
5193 $linkIDs = array_merge( $linkIDs, $linkIDs );
5194 } else {
5195 $sql = "
5196 SELECT
5197 {$destTable}.* {$sqlFilterStr},
5198 {$linkTable}.{$sourceCol} AS linked_by
5199 FROM {$linkTable}
5200 INNER JOIN {$destTable} ON
5201 ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) )
5202 {$addSql}
5203 -- keep-cache";
5204 }
5205
5206 $bindings = array_merge( $linkIDs, $bindings );
5207
5208 $rows = $this->adapter->get( $sql, $bindings );
5209
5210 $this->putResultInCache( $destType, $key, $rows );
5211
5212 return $rows;
5213 }
5214
5215 /**
5216 * @see QueryWriter::queryRecordLink
5217 */
5218 public function queryRecordLink( $sourceType, $destType, $sourceID, $destID )
5219 {
5220 list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
5221
5222 $key = $this->getCacheKey( array( $sourceType, $destType, $sourceID, $destID ) );
5223
5224 if ( $this->flagUseCache && $cached = $this->getCached( $linkTable, $key ) ) {
5225 return $cached;
5226 }
5227
5228 $sqlFilterStr = '';
5229 if ( count( self::$sqlFilters ) ) {
5230 $sqlFilterStr = $this->getSQLFilterSnippet( $destType );
5231 }
5232
5233 if ( $sourceTable === $destTable ) {
5234 $sql = "SELECT {$linkTable}.* {$sqlFilterStr} FROM {$linkTable}
5235 WHERE ( {$sourceCol} = ? AND {$destCol} = ? ) OR
5236 ( {$destCol} = ? AND {$sourceCol} = ? ) -- keep-cache";
5237 $row = $this->adapter->getRow( $sql, array( $sourceID, $destID, $sourceID, $destID ) );
5238 } else {
5239 $sql = "SELECT {$linkTable}.* {$sqlFilterStr} FROM {$linkTable}
5240 WHERE {$sourceCol} = ? AND {$destCol} = ? -- keep-cache";
5241 $row = $this->adapter->getRow( $sql, array( $sourceID, $destID ) );
5242 }
5243
5244 $this->putResultInCache( $linkTable, $key, $row );
5245
5246 return $row;
5247 }
5248
5249 /**
5250 * @see QueryWriter::queryTagged
5251 */
5252 public function queryTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() )
5253 {
5254 $assocType = $this->getAssocTable( array( $type, 'tag' ) );
5255 $assocTable = $this->esc( $assocType );
5256 $assocField = $type . '_id';
5257 $table = $this->esc( $type );
5258 $slots = implode( ',', array_fill( 0, count( $tagList ), '?' ) );
5259 $score = ( $all ) ? count( $tagList ) : 1;
5260
5261 $sql = "
5262 SELECT {$table}.*, count({$table}.id) FROM {$table}
5263 INNER JOIN {$assocTable} ON {$assocField} = {$table}.id
5264 INNER JOIN tag ON {$assocTable}.tag_id = tag.id
5265 WHERE tag.title IN ({$slots})
5266 GROUP BY {$table}.id
5267 HAVING count({$table}.id) >= ?
5268 {$addSql}
5269 ";
5270
5271 $bindings = array_merge( $tagList, array( $score ), $bindings );
5272 $rows = $this->adapter->get( $sql, $bindings );
5273 return $rows;
5274 }
5275
5276 /**
5277 * @see QueryWriter::queryRecordCount
5278 */
5279 public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
5280 {
5281 $addSql = $this->glueSQLCondition( $addSql );
5282
5283 $table = $this->esc( $type );
5284
5285 $this->updateCache(); //check if cache chain has been broken
5286
5287 $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
5288 $sql = "SELECT COUNT(*) FROM {$table} {$sql} -- keep-cache";
5289
5290 return (int) $this->adapter->getCell( $sql, $bindings );
5291 }
5292
5293 /**
5294 * @see QueryWriter::queryRecordCountRelated
5295 */
5296 public function queryRecordCountRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() )
5297 {
5298 list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
5299
5300 $this->updateCache(); //check if cache chain has been broken
5301
5302 if ( $sourceType === $destType ) {
5303 $sql = "
5304 SELECT COUNT(*) FROM {$linkTable}
5305 INNER JOIN {$destTable} ON
5306 ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? ) OR
5307 ( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} = ? )
5308 {$addSql}
5309 -- keep-cache";
5310
5311 $bindings = array_merge( array( $linkID, $linkID ), $bindings );
5312 } else {
5313 $sql = "
5314 SELECT COUNT(*) FROM {$linkTable}
5315 INNER JOIN {$destTable} ON
5316 ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? )
5317 {$addSql}
5318 -- keep-cache";
5319
5320 $bindings = array_merge( array( $linkID ), $bindings );
5321 }
5322
5323 return (int) $this->adapter->getCell( $sql, $bindings );
5324 }
5325
5326 /**
5327 * @see QueryWriter::deleteRecord
5328 */
5329 public function deleteRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
5330 {
5331 $addSql = $this->glueSQLCondition( $addSql );
5332
5333 $table = $this->esc( $type );
5334
5335 $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
5336 $sql = "DELETE FROM {$table} {$sql}";
5337
5338 $this->adapter->exec( $sql, $bindings );
5339 }
5340
5341 /**
5342 * @see QueryWriter::deleteRelations
5343 */
5344 public function deleteRelations( $sourceType, $destType, $sourceID )
5345 {
5346 list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
5347
5348 if ( $sourceTable === $destTable ) {
5349 $sql = "DELETE FROM {$linkTable}
5350 WHERE ( {$sourceCol} = ? ) OR
5351 ( {$destCol} = ? )
5352 ";
5353
5354 $this->adapter->exec( $sql, array( $sourceID, $sourceID ) );
5355 } else {
5356 $sql = "DELETE FROM {$linkTable}
5357 WHERE {$sourceCol} = ? ";
5358
5359 $this->adapter->exec( $sql, array( $sourceID ) );
5360 }
5361 }
5362
5363 /**
5364 * @see QueryWriter::widenColumn
5365 */
5366 public function widenColumn( $type, $property, $dataType )
5367 {
5368 if ( !isset($this->typeno_sqltype[$dataType]) ) return FALSE;
5369
5370 $table = $this->esc( $type );
5371 $column = $this->esc( $property );
5372
5373 $newType = $this->typeno_sqltype[$dataType];
5374
5375 $this->adapter->exec( "ALTER TABLE $table CHANGE $column $column $newType " );
5376
5377 return TRUE;
5378 }
5379
5380 /**
5381 * @see QueryWriter::wipe
5382 */
5383 public function wipe( $type )
5384 {
5385 $table = $this->esc( $type );
5386
5387 $this->adapter->exec( "TRUNCATE $table " );
5388 }
5389
5390 /**
5391 * @see QueryWriter::renameAssocTable
5392 */
5393 public function renameAssocTable( $from, $to = NULL )
5394 {
5395 self::renameAssociation( $from, $to );
5396 }
5397
5398 /**
5399 * @see QueryWriter::getAssocTable
5400 */
5401 public function getAssocTable( $types )
5402 {
5403 return self::getAssocTableFormat( $types );
5404 }
5405
5406 /**
5407 * Turns caching on or off. Default: off.
5408 * If caching is turned on retrieval queries fired after eachother will
5409 * use a result row cache.
5410 *
5411 * @param boolean
5412 *
5413 * @return void
5414 */
5415 public function setUseCache( $yesNo )
5416 {
5417 $this->flushCache();
5418
5419 $this->flagUseCache = (bool) $yesNo;
5420 }
5421
5422 /**
5423 * Flushes the Query Writer Cache.
5424 * Clears the internal query cache array and returns its overall
5425 * size.
5426 *
5427 * @return integer
5428 */
5429 public function flushCache( $newMaxCacheSizePerType = NULL )
5430 {
5431 if ( !is_null( $newMaxCacheSizePerType ) && $newMaxCacheSizePerType > 0 ) {
5432 $this->maxCacheSizePerType = $newMaxCacheSizePerType;
5433 }
5434 $count = count( $this->cache, COUNT_RECURSIVE );
5435 $this->cache = array();
5436 return $count;
5437 }
5438
5439 /**
5440 * @deprecated Use esc() instead.
5441 *
5442 * @param string $column column to be escaped
5443 * @param boolean $noQuotes omit quotes
5444 *
5445 * @return string
5446 */
5447 public function safeColumn( $column, $noQuotes = FALSE )
5448 {
5449 return $this->esc( $column, $noQuotes );
5450 }
5451
5452 /**
5453 * @deprecated Use esc() instead.
5454 *
5455 * @param string $table table to be escaped
5456 * @param boolean $noQuotes omit quotes
5457 *
5458 * @return string
5459 */
5460 public function safeTable( $table, $noQuotes = FALSE )
5461 {
5462 return $this->esc( $table, $noQuotes );
5463 }
5464
5465 /**
5466 * @see QueryWriter::inferFetchType
5467 */
5468 public function inferFetchType( $type, $property )
5469 {
5470 $type = $this->esc( $type, TRUE );
5471 $field = $this->esc( $property, TRUE ) . '_id';
5472 $keys = $this->getKeyMapForType( $type );
5473
5474 foreach( $keys as $key ) {
5475 if (
5476 $key['from'] === $field
5477 ) return $key['table'];
5478 }
5479 return NULL;
5480 }
5481
5482 /**
5483 * @see QueryWriter::addUniqueConstraint
5484 */
5485 public function addUniqueIndex( $type, $properties )
5486 {
5487 return $this->addUniqueConstraint( $type, $properties );
5488 }
5489}
5490}
5491
5492namespace RedBeanPHP\QueryWriter {
5493
5494use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
5495use RedBeanPHP\QueryWriter as QueryWriter;
5496use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
5497use RedBeanPHP\Adapter as Adapter;
5498use RedBeanPHP\RedException\SQL as SQLException;
5499
5500/**
5501 * RedBeanPHP MySQLWriter.
5502 * This is a QueryWriter class for RedBeanPHP.
5503 * This QueryWriter provides support for the MySQL/MariaDB database platform.
5504 *
5505 * @file RedBeanPHP/QueryWriter/MySQL.php
5506 * @author Gabor de Mooij and the RedBeanPHP Community
5507 * @license BSD/GPLv2
5508 *
5509 * @copyright
5510 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
5511 * This source file is subject to the BSD/GPLv2 License that is bundled
5512 * with this source code in the file license.txt.
5513 */
5514class MySQL extends AQueryWriter implements QueryWriter
5515{
5516 /**
5517 * Data types
5518 */
5519 const C_DATATYPE_BOOL = 0;
5520 const C_DATATYPE_UINT32 = 2;
5521 const C_DATATYPE_DOUBLE = 3;
5522 const C_DATATYPE_TEXT7 = 4; //InnoDB cant index varchar(255) utf8mb4 - so keep 191 as long as possible
5523 const C_DATATYPE_TEXT8 = 5;
5524 const C_DATATYPE_TEXT16 = 6;
5525 const C_DATATYPE_TEXT32 = 7;
5526 const C_DATATYPE_SPECIAL_DATE = 80;
5527 const C_DATATYPE_SPECIAL_DATETIME = 81;
5528 const C_DATATYPE_SPECIAL_POINT = 90;
5529 const C_DATATYPE_SPECIAL_LINESTRING = 91;
5530 const C_DATATYPE_SPECIAL_POLYGON = 92;
5531 const C_DATATYPE_SPECIAL_MONEY = 93;
5532
5533 const C_DATATYPE_SPECIFIED = 99;
5534
5535 /**
5536 * @var DBAdapter
5537 */
5538 protected $adapter;
5539
5540 /**
5541 * @var string
5542 */
5543 protected $quoteCharacter = '`';
5544
5545 /**
5546 * @see AQueryWriter::getKeyMapForType
5547 */
5548 protected function getKeyMapForType( $type )
5549 {
5550 $databaseName = $this->adapter->getCell('SELECT DATABASE()');
5551 $table = $this->esc( $type, TRUE );
5552 $keys = $this->adapter->get('
5553 SELECT
5554 information_schema.key_column_usage.constraint_name AS `name`,
5555 information_schema.key_column_usage.referenced_table_name AS `table`,
5556 information_schema.key_column_usage.column_name AS `from`,
5557 information_schema.key_column_usage.referenced_column_name AS `to`,
5558 information_schema.referential_constraints.update_rule AS `on_update`,
5559 information_schema.referential_constraints.delete_rule AS `on_delete`
5560 FROM information_schema.key_column_usage
5561 INNER JOIN information_schema.referential_constraints
5562 ON information_schema.referential_constraints.constraint_name = information_schema.key_column_usage.constraint_name
5563 WHERE
5564 information_schema.key_column_usage.table_schema = :database
5565 AND information_schema.referential_constraints.constraint_schema = :database
5566 AND information_schema.key_column_usage.constraint_schema = :database
5567 AND information_schema.key_column_usage.table_name = :table
5568 AND information_schema.key_column_usage.constraint_name != \'PRIMARY\'
5569 AND information_schema.key_column_usage.referenced_table_name IS NOT NULL
5570 ', array( ':database' => $databaseName, ':table' => $table ) );
5571 $keyInfoList = array();
5572 foreach ( $keys as $k ) {
5573 $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
5574 $keyInfoList[$label] = array(
5575 'name' => $k['name'],
5576 'from' => $k['from'],
5577 'table' => $k['table'],
5578 'to' => $k['to'],
5579 'on_update' => $k['on_update'],
5580 'on_delete' => $k['on_delete']
5581 );
5582 }
5583 return $keyInfoList;
5584 }
5585
5586 /**
5587 * Constructor
5588 *
5589 * @param Adapter $adapter Database Adapter
5590 */
5591 public function __construct( Adapter $adapter )
5592 {
5593 $this->typeno_sqltype = array(
5594 MySQL::C_DATATYPE_BOOL => ' TINYINT(1) UNSIGNED ',
5595 MySQL::C_DATATYPE_UINT32 => ' INT(11) UNSIGNED ',
5596 MySQL::C_DATATYPE_DOUBLE => ' DOUBLE ',
5597 MySQL::C_DATATYPE_TEXT7 => ' VARCHAR(191) ',
5598 MYSQL::C_DATATYPE_TEXT8 => ' VARCHAR(255) ',
5599 MySQL::C_DATATYPE_TEXT16 => ' TEXT ',
5600 MySQL::C_DATATYPE_TEXT32 => ' LONGTEXT ',
5601 MySQL::C_DATATYPE_SPECIAL_DATE => ' DATE ',
5602 MySQL::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
5603 MySQL::C_DATATYPE_SPECIAL_POINT => ' POINT ',
5604 MySQL::C_DATATYPE_SPECIAL_LINESTRING => ' LINESTRING ',
5605 MySQL::C_DATATYPE_SPECIAL_POLYGON => ' POLYGON ',
5606 MySQL::C_DATATYPE_SPECIAL_MONEY => ' DECIMAL(10,2) '
5607 );
5608
5609 $this->sqltype_typeno = array();
5610
5611 foreach ( $this->typeno_sqltype as $k => $v ) {
5612 $this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
5613 }
5614
5615 $this->adapter = $adapter;
5616
5617 $this->encoding = $this->adapter->getDatabase()->getMysqlEncoding();
5618 }
5619
5620 /**
5621 * This method returns the datatype to be used for primary key IDS and
5622 * foreign keys. Returns one if the data type constants.
5623 *
5624 * @return integer
5625 */
5626 public function getTypeForID()
5627 {
5628 return self::C_DATATYPE_UINT32;
5629 }
5630
5631 /**
5632 * @see QueryWriter::getTables
5633 */
5634 public function getTables()
5635 {
5636 return $this->adapter->getCol( 'show tables' );
5637 }
5638
5639 /**
5640 * @see QueryWriter::createTable
5641 */
5642 public function createTable( $table )
5643 {
5644 $table = $this->esc( $table );
5645
5646 $encoding = $this->adapter->getDatabase()->getMysqlEncoding();
5647 $sql = "CREATE TABLE $table (id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY ( id )) ENGINE = InnoDB DEFAULT CHARSET={$encoding} COLLATE={$encoding}_unicode_ci ";
5648
5649 $this->adapter->exec( $sql );
5650 }
5651
5652 /**
5653 * @see QueryWriter::getColumns
5654 */
5655 public function getColumns( $table )
5656 {
5657 $columnsRaw = $this->adapter->get( "DESCRIBE " . $this->esc( $table ) );
5658
5659 $columns = array();
5660 foreach ( $columnsRaw as $r ) {
5661 $columns[$r['Field']] = $r['Type'];
5662 }
5663
5664 return $columns;
5665 }
5666
5667 /**
5668 * @see QueryWriter::scanType
5669 */
5670 public function scanType( $value, $flagSpecial = FALSE )
5671 {
5672 $this->svalue = $value;
5673
5674 if ( is_null( $value ) ) return MySQL::C_DATATYPE_BOOL;
5675 if ( $value === INF ) return MySQL::C_DATATYPE_TEXT7;
5676
5677 if ( $flagSpecial ) {
5678 if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
5679 return MySQL::C_DATATYPE_SPECIAL_MONEY;
5680 }
5681 if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
5682 return MySQL::C_DATATYPE_SPECIAL_DATE;
5683 }
5684 if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
5685 return MySQL::C_DATATYPE_SPECIAL_DATETIME;
5686 }
5687 if ( preg_match( '/^POINT\(/', $value ) ) {
5688 return MySQL::C_DATATYPE_SPECIAL_POINT;
5689 }
5690 if ( preg_match( '/^LINESTRING\(/', $value ) ) {
5691 return MySQL::C_DATATYPE_SPECIAL_LINESTRING;
5692 }
5693 if ( preg_match( '/^POLYGON\(/', $value ) ) {
5694 return MySQL::C_DATATYPE_SPECIAL_POLYGON;
5695 }
5696 }
5697
5698 //setter turns TRUE FALSE into 0 and 1 because database has no real bools (TRUE and FALSE only for test?).
5699 if ( $value === FALSE || $value === TRUE || $value === '0' || $value === '1' ) {
5700 return MySQL::C_DATATYPE_BOOL;
5701 }
5702
5703 if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
5704
5705 if ( !$this->startsWithZeros( $value ) ) {
5706
5707 if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= 0 && $value <= 4294967295 ) {
5708 return MySQL::C_DATATYPE_UINT32;
5709 }
5710
5711 if ( is_numeric( $value ) ) {
5712 return MySQL::C_DATATYPE_DOUBLE;
5713 }
5714 }
5715
5716 if ( mb_strlen( $value, 'UTF-8' ) <= 191 ) {
5717 return MySQL::C_DATATYPE_TEXT7;
5718 }
5719
5720 if ( mb_strlen( $value, 'UTF-8' ) <= 255 ) {
5721 return MySQL::C_DATATYPE_TEXT8;
5722 }
5723
5724 if ( mb_strlen( $value, 'UTF-8' ) <= 65535 ) {
5725 return MySQL::C_DATATYPE_TEXT16;
5726 }
5727
5728 return MySQL::C_DATATYPE_TEXT32;
5729 }
5730
5731 /**
5732 * @see QueryWriter::code
5733 */
5734 public function code( $typedescription, $includeSpecials = FALSE )
5735 {
5736 if ( isset( $this->sqltype_typeno[$typedescription] ) ) {
5737 $r = $this->sqltype_typeno[$typedescription];
5738 } else {
5739 $r = self::C_DATATYPE_SPECIFIED;
5740 }
5741
5742 if ( $includeSpecials ) {
5743 return $r;
5744 }
5745
5746 if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
5747 return self::C_DATATYPE_SPECIFIED;
5748 }
5749
5750 return $r;
5751 }
5752
5753 /**
5754 * @see QueryWriter::addUniqueIndex
5755 */
5756 public function addUniqueConstraint( $type, $properties )
5757 {
5758 $tableNoQ = $this->esc( $type, TRUE );
5759 $columns = array();
5760 foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
5761 $table = $this->esc( $type );
5762 sort( $columns ); // Else we get multiple indexes due to order-effects
5763 $name = 'UQ_' . sha1( implode( ',', $columns ) );
5764 try {
5765 $sql = "ALTER TABLE $table
5766 ADD UNIQUE INDEX $name (" . implode( ',', $columns ) . ")";
5767 $this->adapter->exec( $sql );
5768 } catch ( SQLException $e ) {
5769 //do nothing, dont use alter table ignore, this will delete duplicate records in 3-ways!
5770 return FALSE;
5771 }
5772 return TRUE;
5773 }
5774
5775 /**
5776 * @see QueryWriter::addIndex
5777 */
5778 public function addIndex( $type, $name, $property )
5779 {
5780 try {
5781 $table = $this->esc( $type );
5782 $name = preg_replace( '/\W/', '', $name );
5783 $column = $this->esc( $property );
5784 $this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
5785 return TRUE;
5786 } catch ( SQLException $e ) {
5787 return FALSE;
5788 }
5789 }
5790
5791 /**
5792 * @see QueryWriter::addFK
5793 * @return bool
5794 */
5795 public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
5796 {
5797 $table = $this->esc( $type );
5798 $targetTable = $this->esc( $targetType );
5799 $targetTableNoQ = $this->esc( $targetType, TRUE );
5800 $field = $this->esc( $property );
5801 $fieldNoQ = $this->esc( $property, TRUE );
5802 $targetField = $this->esc( $targetProperty );
5803 $targetFieldNoQ = $this->esc( $targetProperty, TRUE );
5804 $tableNoQ = $this->esc( $type, TRUE );
5805 $fieldNoQ = $this->esc( $property, TRUE );
5806 if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
5807
5808 //Widen the column if it's incapable of representing a foreign key (at least INT).
5809 $columns = $this->getColumns( $tableNoQ );
5810 $idType = $this->getTypeForID();
5811 if ( $this->code( $columns[$fieldNoQ] ) !== $idType ) {
5812 $this->widenColumn( $type, $property, $idType );
5813 }
5814
5815 $fkName = 'fk_'.($tableNoQ.'_'.$fieldNoQ);
5816 $cName = 'c_'.$fkName;
5817 try {
5818 $this->adapter->exec( "
5819 ALTER TABLE {$table}
5820 ADD CONSTRAINT $cName
5821 FOREIGN KEY $fkName ( `{$fieldNoQ}` ) REFERENCES `{$targetTableNoQ}`
5822 (`{$targetFieldNoQ}`) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';');
5823 } catch ( SQLException $e ) {
5824 // Failure of fk-constraints is not a problem
5825 }
5826 return true;
5827 }
5828
5829 /**
5830 * @see QueryWriter::sqlStateIn
5831 */
5832 public function sqlStateIn( $state, $list )
5833 {
5834 $stateMap = array(
5835 '42S02' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
5836 '42S22' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
5837 '23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
5838 );
5839
5840 return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
5841 }
5842
5843 /**
5844 * @see QueryWriter::wipeAll
5845 */
5846 public function wipeAll()
5847 {
5848 $this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 0;' );
5849
5850 foreach ( $this->getTables() as $t ) {
5851 try {
5852 $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
5853 } catch ( SQLException $e ) {
5854 }
5855
5856 try {
5857 $this->adapter->exec( "DROP VIEW IF EXISTS `$t`" );
5858 } catch ( SQLException $e ) {
5859 }
5860 }
5861
5862 $this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 1;' );
5863 }
5864}
5865}
5866
5867namespace RedBeanPHP\QueryWriter {
5868
5869use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
5870use RedBeanPHP\QueryWriter as QueryWriter;
5871use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
5872use RedBeanPHP\Adapter as Adapter;
5873use RedBeanPHP\RedException\SQL as SQLException;
5874
5875/**
5876 * RedBeanPHP SQLiteWriter with support for SQLite types
5877 * This is a QueryWriter class for RedBeanPHP.
5878 * This QueryWriter provides support for the SQLite database platform.
5879 *
5880 * @file RedBeanPHP/QueryWriter/SQLiteT.php
5881 * @author Gabor de Mooij and the RedBeanPHP Community
5882 * @license BSD/GPLv2
5883 *
5884 * @copyright
5885 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
5886 * This source file is subject to the BSD/GPLv2 License that is bundled
5887 * with this source code in the file license.txt.
5888 */
5889class SQLiteT extends AQueryWriter implements QueryWriter
5890{
5891 /**
5892 * Data types
5893 */
5894 const C_DATATYPE_INTEGER = 0;
5895 const C_DATATYPE_NUMERIC = 1;
5896 const C_DATATYPE_TEXT = 2;
5897 const C_DATATYPE_SPECIFIED = 99;
5898
5899 /**
5900 * @var DBAdapter
5901 */
5902 protected $adapter;
5903
5904 /**
5905 * @var string
5906 */
5907 protected $quoteCharacter = '`';
5908
5909 /**
5910 * Gets all information about a table (from a type).
5911 *
5912 * Format:
5913 * array(
5914 * name => name of the table
5915 * columns => array( name => datatype )
5916 * indexes => array() raw index information rows from PRAGMA query
5917 * keys => array() raw key information rows from PRAGMA query
5918 * )
5919 *
5920 * @param string $type type you want to get info of
5921 *
5922 * @return array
5923 */
5924 protected function getTable( $type )
5925 {
5926 $tableName = $this->esc( $type, TRUE );
5927 $columns = $this->getColumns( $type );
5928 $indexes = $this->getIndexes( $type );
5929 $keys = $this->getKeyMapForType( $type );
5930
5931 $table = array(
5932 'columns' => $columns,
5933 'indexes' => $indexes,
5934 'keys' => $keys,
5935 'name' => $tableName
5936 );
5937
5938 $this->tableArchive[$tableName] = $table;
5939
5940 return $table;
5941 }
5942
5943 /**
5944 * Puts a table. Updates the table structure.
5945 * In SQLite we can't change columns, drop columns, change or add foreign keys so we
5946 * have a table-rebuild function. You simply load your table with getTable(), modify it and
5947 * then store it with putTable()...
5948 *
5949 * @param array $tableMap information array
5950 *
5951 * @return void
5952 */
5953 protected function putTable( $tableMap )
5954 {
5955 $table = $tableMap['name'];
5956 $q = array();
5957 $q[] = "DROP TABLE IF EXISTS tmp_backup;";
5958
5959 $oldColumnNames = array_keys( $this->getColumns( $table ) );
5960
5961 foreach ( $oldColumnNames as $k => $v ) $oldColumnNames[$k] = "`$v`";
5962
5963 $q[] = "CREATE TEMPORARY TABLE tmp_backup(" . implode( ",", $oldColumnNames ) . ");";
5964 $q[] = "INSERT INTO tmp_backup SELECT * FROM `$table`;";
5965 $q[] = "PRAGMA foreign_keys = 0 ";
5966 $q[] = "DROP TABLE `$table`;";
5967
5968 $newTableDefStr = '';
5969 foreach ( $tableMap['columns'] as $column => $type ) {
5970 if ( $column != 'id' ) {
5971 $newTableDefStr .= ",`$column` $type";
5972 }
5973 }
5974
5975 $fkDef = '';
5976 foreach ( $tableMap['keys'] as $key ) {
5977 $fkDef .= ", FOREIGN KEY(`{$key['from']}`)
5978 REFERENCES `{$key['table']}`(`{$key['to']}`)
5979 ON DELETE {$key['on_delete']} ON UPDATE {$key['on_update']}";
5980 }
5981
5982 $q[] = "CREATE TABLE `$table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT $newTableDefStr $fkDef );";
5983
5984 foreach ( $tableMap['indexes'] as $name => $index ) {
5985 if ( strpos( $name, 'UQ_' ) === 0 ) {
5986 $cols = explode( '__', substr( $name, strlen( 'UQ_' . $table ) ) );
5987 foreach ( $cols as $k => $v ) $cols[$k] = "`$v`";
5988 $q[] = "CREATE UNIQUE INDEX $name ON `$table` (" . implode( ',', $cols ) . ")";
5989 } else $q[] = "CREATE INDEX $name ON `$table` ({$index['name']}) ";
5990 }
5991
5992 $q[] = "INSERT INTO `$table` SELECT * FROM tmp_backup;";
5993 $q[] = "DROP TABLE tmp_backup;";
5994 $q[] = "PRAGMA foreign_keys = 1 ";
5995
5996 foreach ( $q as $sq ) $this->adapter->exec( $sq );
5997 }
5998
5999 /**
6000 * Returns the an array describing the indexes for type $type.
6001 *
6002 * @param string $type type to describe indexes of
6003 *
6004 * @return array
6005 */
6006 protected function getIndexes( $type )
6007 {
6008 $table = $this->esc( $type, TRUE );
6009 $indexes = $this->adapter->get( "PRAGMA index_list('$table')" );
6010
6011 $indexInfoList = array();
6012 foreach ( $indexes as $i ) {
6013 $indexInfoList[$i['name']] = $this->adapter->getRow( "PRAGMA index_info('{$i['name']}') " );
6014
6015 $indexInfoList[$i['name']]['unique'] = $i['unique'];
6016 }
6017
6018 return $indexInfoList;
6019 }
6020
6021 /**
6022 * Adds a foreign key to a type.
6023 * Note: cant put this in try-catch because that can hide the fact
6024 * that database has been damaged.
6025 *
6026 * @param string $type type you want to modify table of
6027 * @param string $targetType target type
6028 * @param string $field field of the type that needs to get the fk
6029 * @param string $targetField field where the fk needs to point to
6030 * @param integer $buildopt 0 = NO ACTION, 1 = ON DELETE CASCADE
6031 *
6032 * @return boolean
6033 */
6034 protected function buildFK( $type, $targetType, $property, $targetProperty, $constraint = FALSE )
6035 {
6036 $table = $this->esc( $type, TRUE );
6037 $targetTable = $this->esc( $targetType, TRUE );
6038 $column = $this->esc( $property, TRUE );
6039 $targetColumn = $this->esc( $targetProperty, TRUE );
6040
6041 $tables = $this->getTables();
6042 if ( !in_array( $targetTable, $tables ) ) return FALSE;
6043
6044 if ( !is_null( $this->getForeignKeyForTypeProperty( $table, $column ) ) ) return FALSE;
6045 $t = $this->getTable( $table );
6046 $consSQL = ( $constraint ? 'CASCADE' : 'SET NULL' );
6047 $label = 'from_' . $column . '_to_table_' . $targetTable . '_col_' . $targetColumn;
6048 $t['keys'][$label] = array(
6049 'table' => $targetTable,
6050 'from' => $column,
6051 'to' => $targetColumn,
6052 'on_update' => $consSQL,
6053 'on_delete' => $consSQL
6054 );
6055 $this->putTable( $t );
6056 return TRUE;
6057 }
6058
6059 /**
6060 * @see AQueryWriter::getKeyMapForType
6061 */
6062 protected function getKeyMapForType( $type )
6063 {
6064 $table = $this->esc( $type, TRUE );
6065 $keys = $this->adapter->get( "PRAGMA foreign_key_list('$table')" );
6066 $keyInfoList = array();
6067 foreach ( $keys as $k ) {
6068 $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
6069 $keyInfoList[$label] = array(
6070 'name' => $label,
6071 'from' => $k['from'],
6072 'table' => $k['table'],
6073 'to' => $k['to'],
6074 'on_update' => $k['on_update'],
6075 'on_delete' => $k['on_delete']
6076 );
6077 }
6078 return $keyInfoList;
6079 }
6080
6081 /**
6082 * Constructor
6083 *
6084 * @param Adapter $adapter Database Adapter
6085 */
6086 public function __construct( Adapter $adapter )
6087 {
6088 $this->typeno_sqltype = array(
6089 SQLiteT::C_DATATYPE_INTEGER => 'INTEGER',
6090 SQLiteT::C_DATATYPE_NUMERIC => 'NUMERIC',
6091 SQLiteT::C_DATATYPE_TEXT => 'TEXT',
6092 );
6093
6094 $this->sqltype_typeno = array();
6095
6096 foreach ( $this->typeno_sqltype as $k => $v ) {
6097 $this->sqltype_typeno[$v] = $k;
6098 }
6099
6100 $this->adapter = $adapter;
6101 }
6102
6103 /**
6104 * This method returns the datatype to be used for primary key IDS and
6105 * foreign keys. Returns one if the data type constants.
6106 *
6107 * @return integer $const data type to be used for IDS.
6108 */
6109 public function getTypeForID()
6110 {
6111 return self::C_DATATYPE_INTEGER;
6112 }
6113
6114 /**
6115 * @see QueryWriter::scanType
6116 */
6117 public function scanType( $value, $flagSpecial = FALSE )
6118 {
6119 $this->svalue = $value;
6120
6121 if ( $value === NULL ) return self::C_DATATYPE_INTEGER;
6122 if ( $value === INF ) return self::C_DATATYPE_TEXT;
6123
6124 if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
6125
6126 if ( $value === TRUE || $value === FALSE ) return self::C_DATATYPE_INTEGER;
6127
6128 if ( is_numeric( $value ) && ( intval( $value ) == $value ) && $value < 2147483648 && $value > -2147483648 ) return self::C_DATATYPE_INTEGER;
6129
6130 if ( ( is_numeric( $value ) && $value < 2147483648 && $value > -2147483648)
6131 || preg_match( '/\d{4}\-\d\d\-\d\d/', $value )
6132 || preg_match( '/\d{4}\-\d\d\-\d\d\s\d\d:\d\d:\d\d/', $value )
6133 ) {
6134 return self::C_DATATYPE_NUMERIC;
6135 }
6136
6137 return self::C_DATATYPE_TEXT;
6138 }
6139
6140 /**
6141 * @see QueryWriter::addColumn
6142 */
6143 public function addColumn( $table, $column, $type )
6144 {
6145 $column = $this->check( $column );
6146 $table = $this->check( $table );
6147 $type = $this->typeno_sqltype[$type];
6148
6149 $this->adapter->exec( "ALTER TABLE `$table` ADD `$column` $type " );
6150 }
6151
6152 /**
6153 * @see QueryWriter::code
6154 */
6155 public function code( $typedescription, $includeSpecials = FALSE )
6156 {
6157 $r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99 );
6158
6159 return $r;
6160 }
6161
6162 /**
6163 * @see QueryWriter::widenColumn
6164 */
6165 public function widenColumn( $type, $column, $datatype )
6166 {
6167 $t = $this->getTable( $type );
6168
6169 $t['columns'][$column] = $this->typeno_sqltype[$datatype];
6170
6171 $this->putTable( $t );
6172 }
6173
6174 /**
6175 * @see QueryWriter::getTables();
6176 */
6177 public function getTables()
6178 {
6179 return $this->adapter->getCol( "SELECT name FROM sqlite_master
6180 WHERE type='table' AND name!='sqlite_sequence';" );
6181 }
6182
6183 /**
6184 * @see QueryWriter::createTable
6185 */
6186 public function createTable( $table )
6187 {
6188 $table = $this->esc( $table );
6189
6190 $sql = "CREATE TABLE $table ( id INTEGER PRIMARY KEY AUTOINCREMENT ) ";
6191
6192 $this->adapter->exec( $sql );
6193 }
6194
6195 /**
6196 * @see QueryWriter::getColumns
6197 */
6198 public function getColumns( $table )
6199 {
6200 $table = $this->esc( $table, TRUE );
6201
6202 $columnsRaw = $this->adapter->get( "PRAGMA table_info('$table')" );
6203
6204 $columns = array();
6205 foreach ( $columnsRaw as $r ) $columns[$r['name']] = $r['type'];
6206
6207 return $columns;
6208 }
6209
6210 /**
6211 * @see QueryWriter::addUniqueIndex
6212 */
6213 public function addUniqueConstraint( $type, $properties )
6214 {
6215 $tableNoQ = $this->esc( $type, TRUE );
6216 $name = 'UQ_' . $this->esc( $type, TRUE ) . implode( '__', $properties );
6217 $t = $this->getTable( $type );
6218 $t['indexes'][$name] = array( 'name' => $name );
6219 try {
6220 $this->putTable( $t );
6221 } catch( SQLException $e ) {
6222 return FALSE;
6223 }
6224 return TRUE;
6225 }
6226
6227 /**
6228 * @see QueryWriter::sqlStateIn
6229 */
6230 public function sqlStateIn( $state, $list )
6231 {
6232 $stateMap = array(
6233 'HY000' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
6234 '23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
6235 );
6236
6237 return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
6238 }
6239
6240 /**
6241 * @see QueryWriter::addIndex
6242 */
6243 public function addIndex( $type, $name, $column )
6244 {
6245 $columns = $this->getColumns( $type );
6246 if ( !isset( $columns[$column] ) ) return FALSE;
6247
6248 $table = $this->esc( $type );
6249 $name = preg_replace( '/\W/', '', $name );
6250 $column = $this->esc( $column, TRUE );
6251
6252 try {
6253 $t = $this->getTable( $type );
6254 $t['indexes'][$name] = array( 'name' => $column );
6255 $this->putTable( $t );
6256 return TRUE;
6257 } catch( SQLException $exception ) {
6258 return FALSE;
6259 }
6260 }
6261
6262 /**
6263 * @see QueryWriter::wipe
6264 */
6265 public function wipe( $type )
6266 {
6267 $table = $this->esc( $type );
6268
6269 $this->adapter->exec( "DELETE FROM $table " );
6270 }
6271
6272 /**
6273 * @see QueryWriter::addFK
6274 */
6275 public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
6276 {
6277 return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDep );
6278 }
6279
6280 /**
6281 * @see QueryWriter::wipeAll
6282 */
6283 public function wipeAll()
6284 {
6285 $this->adapter->exec( 'PRAGMA foreign_keys = 0 ' );
6286
6287 foreach ( $this->getTables() as $t ) {
6288 try {
6289 $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
6290 } catch ( SQLException $e ) {
6291 }
6292
6293 try {
6294 $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" );
6295 } catch ( SQLException $e ) {
6296 }
6297 }
6298
6299 $this->adapter->exec( 'PRAGMA foreign_keys = 1 ' );
6300 }
6301}
6302}
6303
6304namespace RedBeanPHP\QueryWriter {
6305
6306use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
6307use RedBeanPHP\QueryWriter as QueryWriter;
6308use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
6309use RedBeanPHP\Adapter as Adapter;
6310use RedBeanPHP\RedException\SQL as SQLException;
6311
6312/**
6313 * RedBeanPHP PostgreSQL Query Writer.
6314 * This is a QueryWriter class for RedBeanPHP.
6315 * This QueryWriter provides support for the PostgreSQL database platform.
6316 *
6317 * @file RedBeanPHP/QueryWriter/PostgreSQL.php
6318 * @author Gabor de Mooij and the RedBeanPHP Community
6319 * @license BSD/GPLv2
6320 *
6321 * @copyright
6322 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
6323 * This source file is subject to the BSD/GPLv2 License that is bundled
6324 * with this source code in the file license.txt.
6325 */
6326class PostgreSQL extends AQueryWriter implements QueryWriter
6327{
6328 /**
6329 * Data types
6330 */
6331 const C_DATATYPE_INTEGER = 0;
6332 const C_DATATYPE_DOUBLE = 1;
6333 const C_DATATYPE_TEXT = 3;
6334 const C_DATATYPE_SPECIAL_DATE = 80;
6335 const C_DATATYPE_SPECIAL_DATETIME = 81;
6336 const C_DATATYPE_SPECIAL_POINT = 90;
6337 const C_DATATYPE_SPECIAL_LSEG = 91;
6338 const C_DATATYPE_SPECIAL_CIRCLE = 92;
6339 const C_DATATYPE_SPECIAL_MONEY = 93;
6340 const C_DATATYPE_SPECIAL_POLYGON = 94;
6341 const C_DATATYPE_SPECIAL_MONEY2 = 95; //Numbers only money, i.e. fixed point numeric
6342 const C_DATATYPE_SPECIAL_JSON = 96; //JSON support (only manual)
6343 const C_DATATYPE_SPECIFIED = 99;
6344
6345 /**
6346 * @var DBAdapter
6347 */
6348 protected $adapter;
6349
6350 /**
6351 * @var string
6352 */
6353 protected $quoteCharacter = '"';
6354
6355 /**
6356 * @var string
6357 */
6358 protected $defaultValue = 'DEFAULT';
6359
6360 /**
6361 * Returns the insert suffix SQL Snippet
6362 *
6363 * @param string $table table
6364 *
6365 * @return string $sql SQL Snippet
6366 */
6367 protected function getInsertSuffix( $table )
6368 {
6369 return 'RETURNING id ';
6370 }
6371
6372 /**
6373 * @see AQueryWriter::getKeyMapForType
6374 */
6375 protected function getKeyMapForType( $type )
6376 {
6377 $table = $this->esc( $type, TRUE );
6378 $keys = $this->adapter->get( '
6379 SELECT
6380 information_schema.key_column_usage.constraint_name AS "name",
6381 information_schema.key_column_usage.column_name AS "from",
6382 information_schema.constraint_table_usage.table_name AS "table",
6383 information_schema.constraint_column_usage.column_name AS "to",
6384 information_schema.referential_constraints.update_rule AS "on_update",
6385 information_schema.referential_constraints.delete_rule AS "on_delete"
6386 FROM information_schema.key_column_usage
6387 INNER JOIN information_schema.constraint_table_usage
6388 ON (
6389 information_schema.key_column_usage.constraint_name = information_schema.constraint_table_usage.constraint_name
6390 AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_table_usage.constraint_schema
6391 AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_table_usage.constraint_catalog
6392 )
6393 INNER JOIN information_schema.constraint_column_usage
6394 ON (
6395 information_schema.key_column_usage.constraint_name = information_schema.constraint_column_usage.constraint_name
6396 AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_column_usage.constraint_schema
6397 AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_column_usage.constraint_catalog
6398 )
6399 INNER JOIN information_schema.referential_constraints
6400 ON (
6401 information_schema.key_column_usage.constraint_name = information_schema.referential_constraints.constraint_name
6402 AND information_schema.key_column_usage.constraint_schema = information_schema.referential_constraints.constraint_schema
6403 AND information_schema.key_column_usage.constraint_catalog = information_schema.referential_constraints.constraint_catalog
6404 )
6405 WHERE
6406 information_schema.key_column_usage.table_catalog = current_database()
6407 AND information_schema.key_column_usage.table_schema = ANY( current_schemas( FALSE ) )
6408 AND information_schema.key_column_usage.table_name = ?
6409 ', array( $type ) );
6410 $keyInfoList = array();
6411 foreach ( $keys as $k ) {
6412 $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
6413 $keyInfoList[$label] = array(
6414 'name' => $k['name'],
6415 'from' => $k['from'],
6416 'table' => $k['table'],
6417 'to' => $k['to'],
6418 'on_update' => $k['on_update'],
6419 'on_delete' => $k['on_delete']
6420 );
6421 }
6422 return $keyInfoList;
6423 }
6424
6425 /**
6426 * Constructor
6427 *
6428 * @param Adapter $adapter Database Adapter
6429 */
6430 public function __construct( Adapter $adapter )
6431 {
6432 $this->typeno_sqltype = array(
6433 self::C_DATATYPE_INTEGER => ' integer ',
6434 self::C_DATATYPE_DOUBLE => ' double precision ',
6435 self::C_DATATYPE_TEXT => ' text ',
6436 self::C_DATATYPE_SPECIAL_DATE => ' date ',
6437 self::C_DATATYPE_SPECIAL_DATETIME => ' timestamp without time zone ',
6438 self::C_DATATYPE_SPECIAL_POINT => ' point ',
6439 self::C_DATATYPE_SPECIAL_LSEG => ' lseg ',
6440 self::C_DATATYPE_SPECIAL_CIRCLE => ' circle ',
6441 self::C_DATATYPE_SPECIAL_MONEY => ' money ',
6442 self::C_DATATYPE_SPECIAL_MONEY2 => ' numeric(10,2) ',
6443 self::C_DATATYPE_SPECIAL_POLYGON => ' polygon ',
6444 self::C_DATATYPE_SPECIAL_JSON => ' json ',
6445 );
6446
6447 $this->sqltype_typeno = array();
6448
6449 foreach ( $this->typeno_sqltype as $k => $v ) {
6450 $this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
6451 }
6452
6453 $this->adapter = $adapter;
6454 }
6455
6456 /**
6457 * This method returns the datatype to be used for primary key IDS and
6458 * foreign keys. Returns one if the data type constants.
6459 *
6460 * @return integer
6461 */
6462 public function getTypeForID()
6463 {
6464 return self::C_DATATYPE_INTEGER;
6465 }
6466
6467 /**
6468 * @see QueryWriter::getTables
6469 */
6470 public function getTables()
6471 {
6472 return $this->adapter->getCol( 'SELECT table_name FROM information_schema.tables WHERE table_schema = ANY( current_schemas( FALSE ) )' );
6473 }
6474
6475 /**
6476 * @see QueryWriter::createTable
6477 */
6478 public function createTable( $table )
6479 {
6480 $table = $this->esc( $table );
6481
6482 $this->adapter->exec( " CREATE TABLE $table (id SERIAL PRIMARY KEY); " );
6483 }
6484
6485 /**
6486 * @see QueryWriter::getColumns
6487 */
6488 public function getColumns( $table )
6489 {
6490 $table = $this->esc( $table, TRUE );
6491
6492 $columnsRaw = $this->adapter->get( "SELECT column_name, data_type FROM information_schema.columns WHERE table_name='$table' AND table_schema = ANY( current_schemas( FALSE ) )" );
6493
6494 $columns = array();
6495 foreach ( $columnsRaw as $r ) {
6496 $columns[$r['column_name']] = $r['data_type'];
6497 }
6498
6499 return $columns;
6500 }
6501
6502 /**
6503 * @see QueryWriter::scanType
6504 */
6505 public function scanType( $value, $flagSpecial = FALSE )
6506 {
6507 $this->svalue = $value;
6508
6509 if ( $value === INF ) return self::C_DATATYPE_TEXT;
6510
6511 if ( $flagSpecial && $value ) {
6512 if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
6513 return PostgreSQL::C_DATATYPE_SPECIAL_DATE;
6514 }
6515
6516 if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d(\.\d{1,6})?$/', $value ) ) {
6517 return PostgreSQL::C_DATATYPE_SPECIAL_DATETIME;
6518 }
6519
6520 if ( preg_match( '/^\([\d\.]+,[\d\.]+\)$/', $value ) ) {
6521 return PostgreSQL::C_DATATYPE_SPECIAL_POINT;
6522 }
6523
6524 if ( preg_match( '/^\[\([\d\.]+,[\d\.]+\),\([\d\.]+,[\d\.]+\)\]$/', $value ) ) {
6525 return PostgreSQL::C_DATATYPE_SPECIAL_LSEG;
6526 }
6527
6528 if ( preg_match( '/^\<\([\d\.]+,[\d\.]+\),[\d\.]+\>$/', $value ) ) {
6529 return PostgreSQL::C_DATATYPE_SPECIAL_CIRCLE;
6530 }
6531
6532 if ( preg_match( '/^\((\([\d\.]+,[\d\.]+\),?)+\)$/', $value ) ) {
6533 return PostgreSQL::C_DATATYPE_SPECIAL_POLYGON;
6534 }
6535
6536 if ( preg_match( '/^\-?(\$|€|¥|£)[\d,\.]+$/', $value ) ) {
6537 return PostgreSQL::C_DATATYPE_SPECIAL_MONEY;
6538 }
6539
6540 if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
6541 return PostgreSQL::C_DATATYPE_SPECIAL_MONEY2;
6542 }
6543 }
6544
6545 if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
6546
6547 if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
6548
6549 if ( $value === FALSE || $value === TRUE || $value === NULL || ( is_numeric( $value )
6550 && AQueryWriter::canBeTreatedAsInt( $value )
6551 && $value < 2147483648
6552 && $value > -2147483648 )
6553 ) {
6554 return self::C_DATATYPE_INTEGER;
6555 } elseif ( is_numeric( $value ) ) {
6556 return self::C_DATATYPE_DOUBLE;
6557 } else {
6558 return self::C_DATATYPE_TEXT;
6559 }
6560 }
6561
6562 /**
6563 * @see QueryWriter::code
6564 */
6565 public function code( $typedescription, $includeSpecials = FALSE )
6566 {
6567 $r = ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99;
6568
6569 if ( $includeSpecials ) return $r;
6570
6571 if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
6572 return self::C_DATATYPE_SPECIFIED;
6573 }
6574
6575 return $r;
6576 }
6577
6578 /**
6579 * @see QueryWriter::widenColumn
6580 */
6581 public function widenColumn( $type, $column, $datatype )
6582 {
6583 $table = $type;
6584 $type = $datatype;
6585
6586 $table = $this->esc( $table );
6587 $column = $this->esc( $column );
6588
6589 $newtype = $this->typeno_sqltype[$type];
6590
6591 $this->adapter->exec( "ALTER TABLE $table \n\t ALTER COLUMN $column TYPE $newtype " );
6592 }
6593
6594 /**
6595 * @see QueryWriter::addUniqueIndex
6596 */
6597 public function addUniqueConstraint( $type, $properties )
6598 {
6599 $tableNoQ = $this->esc( $type, TRUE );
6600 $columns = array();
6601 foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
6602 $table = $this->esc( $type );
6603 sort( $columns ); //else we get multiple indexes due to order-effects
6604 $name = "UQ_" . sha1( $table . implode( ',', $columns ) );
6605 $sql = "ALTER TABLE {$table}
6606 ADD CONSTRAINT $name UNIQUE (" . implode( ',', $columns ) . ")";
6607 try {
6608 $this->adapter->exec( $sql );
6609 } catch( SQLException $e ) {
6610 return FALSE;
6611 }
6612 return TRUE;
6613 }
6614
6615 /**
6616 * @see QueryWriter::sqlStateIn
6617 */
6618 public function sqlStateIn( $state, $list )
6619 {
6620 $stateMap = array(
6621 '42P01' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
6622 '42703' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
6623 '23505' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
6624 );
6625
6626 return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
6627 }
6628
6629 /**
6630 * @see QueryWriter::addIndex
6631 */
6632 public function addIndex( $type, $name, $property )
6633 {
6634 $table = $this->esc( $type );
6635 $name = preg_replace( '/\W/', '', $name );
6636 $column = $this->esc( $property );
6637
6638 try {
6639 $this->adapter->exec( "CREATE INDEX {$name} ON $table ({$column}) " );
6640 return TRUE;
6641 } catch ( SQLException $e ) {
6642 return FALSE;
6643 }
6644 }
6645
6646 /**
6647 * @see QueryWriter::addFK
6648 */
6649 public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
6650 {
6651 $table = $this->esc( $type );
6652 $targetTable = $this->esc( $targetType );
6653 $field = $this->esc( $property );
6654 $targetField = $this->esc( $targetProperty );
6655 $tableNoQ = $this->esc( $type, TRUE );
6656 $fieldNoQ = $this->esc( $property, TRUE );
6657 if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
6658 try{
6659 $delRule = ( $isDep ? 'CASCADE' : 'SET NULL' );
6660 $this->adapter->exec( "ALTER TABLE {$table}
6661 ADD FOREIGN KEY ( {$field} ) REFERENCES {$targetTable}
6662 ({$targetField}) ON DELETE {$delRule} ON UPDATE {$delRule} DEFERRABLE ;" );
6663 return TRUE;
6664 } catch ( SQLException $e ) {
6665 return FALSE;
6666 }
6667 }
6668
6669 /**
6670 * @see QueryWriter::wipeAll
6671 */
6672 public function wipeAll()
6673 {
6674 $this->adapter->exec( 'SET CONSTRAINTS ALL DEFERRED' );
6675
6676 foreach ( $this->getTables() as $t ) {
6677 $t = $this->esc( $t );
6678 //Some plugins (PostGIS have unremovable tables/views), avoid exceptions.
6679 try { $this->adapter->exec( "DROP TABLE IF EXISTS $t CASCADE " ); }catch( \Exception $e ) {}
6680 }
6681
6682 $this->adapter->exec( 'SET CONSTRAINTS ALL IMMEDIATE' );
6683 }
6684}
6685}
6686
6687namespace RedBeanPHP {
6688
6689/**
6690 * RedBean\Exception Base.
6691 * Represents the base class for RedBeanPHP\Exceptions.
6692 *
6693 * @file RedBeanPHP/Exception.php
6694 * @author Gabor de Mooij and the RedBeanPHP Community
6695 * @license BSD/GPLv2
6696 *
6697 * @copyright
6698 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
6699 * This source file is subject to the BSD/GPLv2 License that is bundled
6700 * with this source code in the file license.txt.
6701 */
6702class RedException extends \Exception
6703{
6704}
6705}
6706
6707namespace RedBeanPHP\RedException {
6708
6709use RedBeanPHP\RedException as RedException;
6710
6711/**
6712 * SQL Exception.
6713 * Represents a generic database exception independent of the underlying driver.
6714 *
6715 * @file RedBeanPHP/RedException/SQL.php
6716 * @author Gabor de Mooij and the RedBeanPHP Community
6717 * @license BSD/GPLv2
6718 *
6719 * @copyright
6720 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
6721 * This source file is subject to the BSD/GPLv2 License that is bundled
6722 * with this source code in the file license.txt.
6723 */
6724class SQL extends RedException
6725{
6726 /**
6727 * @var string
6728 */
6729 private $sqlState;
6730
6731 /**
6732 * Returns an ANSI-92 compliant SQL state.
6733 *
6734 * @return string
6735 */
6736 public function getSQLState()
6737 {
6738 return $this->sqlState;
6739 }
6740
6741 /**
6742 * Returns the raw SQL STATE, possibly compliant with
6743 * ANSI SQL error codes - but this depends on database driver.
6744 *
6745 * @param string $sqlState SQL state error code
6746 *
6747 * @return void
6748 */
6749 public function setSQLState( $sqlState )
6750 {
6751 $this->sqlState = $sqlState;
6752 }
6753
6754 /**
6755 * To String prints both code and SQL state.
6756 *
6757 * @return string
6758 */
6759 public function __toString()
6760 {
6761 return '[' . $this->getSQLState() . '] - ' . $this->getMessage()."\n".
6762 'trace: ' . $this->getTraceAsString();
6763 }
6764}
6765}
6766
6767namespace RedBeanPHP {
6768
6769use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
6770use RedBeanPHP\QueryWriter as QueryWriter;
6771use RedBeanPHP\BeanHelper as BeanHelper;
6772use RedBeanPHP\RedException\SQL as SQLException;
6773use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
6774use RedBeanPHP\Cursor as Cursor;
6775use RedBeanPHP\Cursor\NullCursor as NullCursor;
6776
6777/**
6778 * Abstract Repository.
6779 *
6780 * OODB manages two repositories, a fluid one that
6781 * adjust the database schema on-the-fly to accomodate for
6782 * new bean types (tables) and new properties (columns) and
6783 * a frozen one for use in a production environment. OODB
6784 * allows you to swap the repository instances using the freeze()
6785 * method.
6786 *
6787 * @file RedBeanPHP/Repository.php
6788 * @author Gabor de Mooij and the RedBeanPHP community
6789 * @license BSD/GPLv2
6790 *
6791 * @copyright
6792 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
6793 * This source file is subject to the BSD/GPLv2 License that is bundled
6794 * with this source code in the file license.txt.
6795 */
6796abstract class Repository
6797{
6798 /**
6799 * @var array
6800 */
6801 protected $stash = NULL;
6802
6803 /*
6804 * @var integer
6805 */
6806 protected $nesting = 0;
6807
6808 /**
6809 * @var DBAdapter
6810 */
6811 protected $writer;
6812
6813 /**
6814 * Stores a bean and its lists in one run.
6815 *
6816 * @param OODBBean $bean bean to process
6817 *
6818 * @return void
6819 */
6820 protected function storeBeanWithLists( OODBBean $bean )
6821 {
6822 $sharedAdditions = $sharedTrashcan = $sharedresidue = $sharedItems = $ownAdditions = $ownTrashcan = $ownresidue = $embeddedBeans = array(); //Define groups
6823 foreach ( $bean as $property => $value ) {
6824 $value = ( $value instanceof SimpleModel ) ? $value->unbox() : $value;
6825 if ( $value instanceof OODBBean ) {
6826 $this->processEmbeddedBean( $embeddedBeans, $bean, $property, $value );
6827 $bean->setMeta("sys.typeof.{$property}", $value->getMeta('type'));
6828 } elseif ( is_array( $value ) ) {
6829 $originals = $bean->moveMeta( 'sys.shadow.' . $property, array() );
6830 if ( strpos( $property, 'own' ) === 0 ) {
6831 list( $ownAdditions, $ownTrashcan, $ownresidue ) = $this->processGroups( $originals, $value, $ownAdditions, $ownTrashcan, $ownresidue );
6832 $listName = lcfirst( substr( $property, 3 ) );
6833 if ($bean->moveMeta( 'sys.exclusive-'. $listName ) ) {
6834 OODBBean::setMetaAll( $ownTrashcan, 'sys.garbage', TRUE );
6835 OODBBean::setMetaAll( $ownAdditions, 'sys.buildcommand.fkdependson', $bean->getMeta( 'type' ) );
6836 }
6837 unset( $bean->$property );
6838 } elseif ( strpos( $property, 'shared' ) === 0 ) {
6839 list( $sharedAdditions, $sharedTrashcan, $sharedresidue ) = $this->processGroups( $originals, $value, $sharedAdditions, $sharedTrashcan, $sharedresidue );
6840 unset( $bean->$property );
6841 }
6842 }
6843 }
6844 $this->storeBean( $bean );
6845 $this->processTrashcan( $bean, $ownTrashcan );
6846 $this->processAdditions( $bean, $ownAdditions );
6847 $this->processResidue( $ownresidue );
6848 $this->processSharedTrashcan( $bean, $sharedTrashcan );
6849 $this->processSharedAdditions( $bean, $sharedAdditions );
6850 $this->processSharedResidue( $bean, $sharedresidue );
6851 }
6852
6853 /**
6854 * Process groups. Internal function. Processes different kind of groups for
6855 * storage function. Given a list of original beans and a list of current beans,
6856 * this function calculates which beans remain in the list (residue), which
6857 * have been deleted (are in the trashcan) and which beans have been added
6858 * (additions).
6859 *
6860 * @param array $originals originals
6861 * @param array $current the current beans
6862 * @param array $additions beans that have been added
6863 * @param array $trashcan beans that have been deleted
6864 * @param array $residue beans that have been left untouched
6865 *
6866 * @return array
6867 */
6868 protected function processGroups( $originals, $current, $additions, $trashcan, $residue )
6869 {
6870 return array(
6871 array_merge( $additions, array_diff( $current, $originals ) ),
6872 array_merge( $trashcan, array_diff( $originals, $current ) ),
6873 array_merge( $residue, array_intersect( $current, $originals ) )
6874 );
6875 }
6876
6877 /**
6878 * Processes an embedded bean.
6879 *
6880 * @param OODBBean|SimpleModel $embeddedBean the bean or model
6881 *
6882 * @return integer
6883 */
6884 protected function prepareEmbeddedBean( $embeddedBean )
6885 {
6886 if ( !$embeddedBean->id || $embeddedBean->getMeta( 'tainted' ) ) {
6887 $this->store( $embeddedBean );
6888 }
6889
6890 return $embeddedBean->id;
6891 }
6892
6893 /**
6894 * Processes a list of beans from a bean. A bean may contain lists. This
6895 * method handles shared addition lists; i.e. the $bean->sharedObject properties.
6896 *
6897 * @param OODBBean $bean the bean
6898 * @param array $sharedAdditions list with shared additions
6899 *
6900 * @return void
6901 */
6902 protected function processSharedAdditions( $bean, $sharedAdditions )
6903 {
6904 foreach ( $sharedAdditions as $addition ) {
6905 if ( $addition instanceof OODBBean ) {
6906 $this->oodb->getAssociationManager()->associate( $addition, $bean );
6907 } else {
6908 throw new RedException( 'Array may only contain OODBBeans' );
6909 }
6910 }
6911 }
6912
6913 /**
6914 * Processes a list of beans from a bean. A bean may contain lists. This
6915 * method handles own lists; i.e. the $bean->ownObject properties.
6916 * A residue is a bean in an own-list that stays where it is. This method
6917 * checks if there have been any modification to this bean, in that case
6918 * the bean is stored once again, otherwise the bean will be left untouched.
6919 *
6920 * @param OODBBean $bean bean tor process
6921 * @param array $ownresidue list to process
6922 *
6923 * @return void
6924 */
6925 protected function processResidue( $ownresidue )
6926 {
6927 foreach ( $ownresidue as $residue ) {
6928 if ( $residue->getMeta( 'tainted' ) ) {
6929 $this->store( $residue );
6930 }
6931 }
6932 }
6933
6934 /**
6935 * Processes a list of beans from a bean. A bean may contain lists. This
6936 * method handles own lists; i.e. the $bean->ownObject properties.
6937 * A trash can bean is a bean in an own-list that has been removed
6938 * (when checked with the shadow). This method
6939 * checks if the bean is also in the dependency list. If it is the bean will be removed.
6940 * If not, the connection between the bean and the owner bean will be broken by
6941 * setting the ID to NULL.
6942 *
6943 * @param OODBBean $bean bean to process
6944 * @param array $ownTrashcan list to process
6945 *
6946 * @return void
6947 */
6948 protected function processTrashcan( $bean, $ownTrashcan )
6949 {
6950 foreach ( $ownTrashcan as $trash ) {
6951
6952 $myFieldLink = $bean->getMeta( 'type' ) . '_id';
6953 $alias = $bean->getMeta( 'sys.alias.' . $trash->getMeta( 'type' ) );
6954 if ( $alias ) $myFieldLink = $alias . '_id';
6955
6956 if ( $trash->getMeta( 'sys.garbage' ) === true ) {
6957 $this->trash( $trash );
6958 } else {
6959 $trash->$myFieldLink = NULL;
6960 $this->store( $trash );
6961 }
6962 }
6963 }
6964
6965 /**
6966 * Unassociates the list items in the trashcan.
6967 *
6968 * @param OODBBean $bean bean to process
6969 * @param array $sharedTrashcan list to process
6970 *
6971 * @return void
6972 */
6973 protected function processSharedTrashcan( $bean, $sharedTrashcan )
6974 {
6975 foreach ( $sharedTrashcan as $trash ) {
6976 $this->oodb->getAssociationManager()->unassociate( $trash, $bean );
6977 }
6978 }
6979
6980 /**
6981 * Stores all the beans in the residue group.
6982 *
6983 * @param OODBBean $bean bean to process
6984 * @param array $sharedresidue list to process
6985 *
6986 * @return void
6987 */
6988 protected function processSharedResidue( $bean, $sharedresidue )
6989 {
6990 foreach ( $sharedresidue as $residue ) {
6991 $this->store( $residue );
6992 }
6993 }
6994
6995 /**
6996 * Determines whether the bean has 'loaded lists' or
6997 * 'loaded embedded beans' that need to be processed
6998 * by the store() method.
6999 *
7000 * @param OODBBean $bean bean to be examined
7001 *
7002 * @return boolean
7003 */
7004 protected function hasListsOrObjects( OODBBean $bean )
7005 {
7006 $processLists = FALSE;
7007 foreach ( $bean as $value ) {
7008 if ( is_array( $value ) || is_object( $value ) ) {
7009 $processLists = TRUE;
7010 break;
7011 }
7012 }
7013
7014 return $processLists;
7015 }
7016
7017 /**
7018 * Converts an embedded bean to an ID, removed the bean property and
7019 * stores the bean in the embedded beans array.
7020 *
7021 * @param array $embeddedBeans destination array for embedded bean
7022 * @param OODBBean $bean target bean to process
7023 * @param string $property property that contains the embedded bean
7024 * @param OODBBean $value embedded bean itself
7025 *
7026 * @return void
7027 */
7028 protected function processEmbeddedBean( &$embeddedBeans, $bean, $property, OODBBean $value )
7029 {
7030 $linkField = $property . '_id';
7031 $id = $this->prepareEmbeddedBean( $value );
7032 if ($bean->$linkField != $id) $bean->$linkField = $id;
7033 $bean->setMeta( 'cast.' . $linkField, 'id' );
7034 $embeddedBeans[$linkField] = $value;
7035 unset( $bean->$property );
7036 }
7037
7038 /**
7039 * Constructor, requires a query writer.
7040 * Creates a new instance of the bean respository class.
7041 *
7042 * @param QueryWriter $writer the Query Writer to use for this repository
7043 *
7044 * @return void
7045 */
7046 public function __construct( OODB $oodb, QueryWriter $writer )
7047 {
7048 $this->writer = $writer;
7049 $this->oodb = $oodb;
7050 }
7051
7052 /**
7053 * Checks whether a OODBBean bean is valid.
7054 * If the type is not valid or the ID is not valid it will
7055 * throw an exception: Security.
7056 *
7057 * @param OODBBean $bean the bean that needs to be checked
7058 *
7059 * @return void
7060 */
7061 public function check( OODBBean $bean )
7062 {
7063 //Is all meta information present?
7064 if ( !isset( $bean->id ) ) {
7065 throw new RedException( 'Bean has incomplete Meta Information id ' );
7066 }
7067 if ( !( $bean->getMeta( 'type' ) ) ) {
7068 throw new RedException( 'Bean has incomplete Meta Information II' );
7069 }
7070 //Pattern of allowed characters
7071 $pattern = '/[^a-z0-9_]/i';
7072 //Does the type contain invalid characters?
7073 if ( preg_match( $pattern, $bean->getMeta( 'type' ) ) ) {
7074 throw new RedException( 'Bean Type is invalid' );
7075 }
7076 //Are the properties and values valid?
7077 foreach ( $bean as $prop => $value ) {
7078 if (
7079 is_array( $value )
7080 || ( is_object( $value ) )
7081 ) {
7082 throw new RedException( "Invalid Bean value: property $prop" );
7083 } else if (
7084 strlen( $prop ) < 1
7085 || preg_match( $pattern, $prop )
7086 ) {
7087 throw new RedException( "Invalid Bean property: property $prop" );
7088 }
7089 }
7090 }
7091
7092 /**
7093 * Searches the database for a bean that matches conditions $conditions and sql $addSQL
7094 * and returns an array containing all the beans that have been found.
7095 *
7096 * Conditions need to take form:
7097 *
7098 * <code>
7099 * array(
7100 * 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
7101 * 'PROPERTY' => array( POSSIBLE VALUES... )
7102 * );
7103 * </code>
7104 *
7105 * All conditions are glued together using the AND-operator, while all value lists
7106 * are glued using IN-operators thus acting as OR-conditions.
7107 *
7108 * Note that you can use property names; the columns will be extracted using the
7109 * appropriate bean formatter.
7110 *
7111 * @param string $type type of beans you are looking for
7112 * @param array $conditions list of conditions
7113 * @param string $addSQL SQL to be used in query
7114 * @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not)
7115 *
7116 * @return array
7117 */
7118 public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
7119 {
7120 //for backward compatibility, allow mismatch arguments:
7121 if ( is_array( $sql ) ) {
7122 if ( isset( $sql[1] ) ) {
7123 $bindings = $sql[1];
7124 }
7125 $sql = $sql[0];
7126 }
7127 try {
7128 $beans = $this->convertToBeans( $type, $this->writer->queryRecord( $type, $conditions, $sql, $bindings ) );
7129
7130 return $beans;
7131 } catch ( SQLException $exception ) {
7132 $this->handleException( $exception );
7133 }
7134
7135 return array();
7136 }
7137
7138 /**
7139 * Finds a BeanCollection.
7140 *
7141 * @param string $type type of beans you are looking for
7142 * @param string $sql SQL to be used in query
7143 * @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not)
7144 *
7145 * @return BeanCollection
7146 */
7147 public function findCollection( $type, $sql, $bindings = array() )
7148 {
7149 try {
7150 $cursor = $this->writer->queryRecordWithCursor( $type, $sql, $bindings );
7151 return new BeanCollection( $type, $this, $cursor );
7152 } catch ( SQLException $exception ) {
7153 $this->handleException( $exception );
7154 }
7155 return new BeanCollection( $type, $this, new NullCursor );
7156 }
7157
7158 /**
7159 * Stores a bean in the database. This method takes a
7160 * OODBBean Bean Object $bean and stores it
7161 * in the database. If the database schema is not compatible
7162 * with this bean and RedBean runs in fluid mode the schema
7163 * will be altered to store the bean correctly.
7164 * If the database schema is not compatible with this bean and
7165 * RedBean runs in frozen mode it will throw an exception.
7166 * This function returns the primary key ID of the inserted
7167 * bean.
7168 *
7169 * The return value is an integer if possible. If it is not possible to
7170 * represent the value as an integer a string will be returned. We use
7171 * explicit casts instead of functions to preserve performance
7172 * (0.13 vs 0.28 for 10000 iterations on Core i3).
7173 *
7174 * @param OODBBean|SimpleModel $bean bean to store
7175 *
7176 * @return integer|string
7177 */
7178 public function store( $bean )
7179 {
7180 $processLists = $this->hasListsOrObjects( $bean );
7181 if ( !$processLists && !$bean->getMeta( 'tainted' ) ) {
7182 return $bean->getID(); //bail out!
7183 }
7184 $this->oodb->signal( 'update', $bean );
7185 $processLists = $this->hasListsOrObjects( $bean ); //check again, might have changed by model!
7186 if ( $processLists ) {
7187 $this->storeBeanWithLists( $bean );
7188 } else {
7189 $this->storeBean( $bean );
7190 }
7191 $this->oodb->signal( 'after_update', $bean );
7192
7193 return ( (string) $bean->id === (string) (int) $bean->id ) ? (int) $bean->id : (string) $bean->id;
7194 }
7195
7196 /**
7197 * Returns an array of beans. Pass a type and a series of ids and
7198 * this method will bring you the corresponding beans.
7199 *
7200 * important note: Because this method loads beans using the load()
7201 * function (but faster) it will return empty beans with ID 0 for
7202 * every bean that could not be located. The resulting beans will have the
7203 * passed IDs as their keys.
7204 *
7205 * @param string $type type of beans
7206 * @param array $ids ids to load
7207 *
7208 * @return array
7209 */
7210 public function batch( $type, $ids )
7211 {
7212 if ( !$ids ) {
7213 return array();
7214 }
7215 $collection = array();
7216 try {
7217 $rows = $this->writer->queryRecord( $type, array( 'id' => $ids ) );
7218 } catch ( SQLException $e ) {
7219 $this->handleException( $e );
7220 $rows = FALSE;
7221 }
7222 $this->stash[$this->nesting] = array();
7223 if ( !$rows ) {
7224 return array();
7225 }
7226 foreach ( $rows as $row ) {
7227 $this->stash[$this->nesting][$row['id']] = $row;
7228 }
7229 foreach ( $ids as $id ) {
7230 $collection[$id] = $this->load( $type, $id );
7231 }
7232 $this->stash[$this->nesting] = NULL;
7233
7234 return $collection;
7235 }
7236
7237 /**
7238 * This is a convenience method; it converts database rows
7239 * (arrays) into beans. Given a type and a set of rows this method
7240 * will return an array of beans of the specified type loaded with
7241 * the data fields provided by the result set from the database.
7242 *
7243 * New in 4.3.2: meta mask. The meta mask is a special mask to send
7244 * data from raw result rows to the meta store of the bean. This is
7245 * useful for bundling additional information with custom queries.
7246 * Values of every column whos name starts with $mask will be
7247 * transferred to the meta section of the bean under key 'data.bundle'.
7248 *
7249 * @param string $type type of beans you would like to have
7250 * @param array $rows rows from the database result
7251 * @param string $mask meta mask to apply (optional)
7252 *
7253 * @return array
7254 */
7255 public function convertToBeans( $type, $rows, $mask = NULL )
7256 {
7257 $masklen = 0;
7258 if ( $mask !== NULL ) $masklen = mb_strlen( $mask );
7259
7260 $collection = array();
7261 $this->stash[$this->nesting] = array();
7262 foreach ( $rows as $row ) {
7263 $meta = array();
7264 if ( !is_null( $mask ) ) {
7265 foreach( $row as $key => $value ) {
7266 if ( strpos( $key, $mask ) === 0 ) {
7267 unset( $row[$key] );
7268 $meta[$key] = $value;
7269 }
7270 }
7271 }
7272
7273 $id = $row['id'];
7274 $this->stash[$this->nesting][$id] = $row;
7275 $collection[$id] = $this->load( $type, $id );
7276
7277 if ( $mask !== NULL ) {
7278 $collection[$id]->setMeta( 'data.bundle', $meta );
7279 }
7280 }
7281 $this->stash[$this->nesting] = NULL;
7282
7283 return $collection;
7284 }
7285
7286 /**
7287 * Counts the number of beans of type $type.
7288 * This method accepts a second argument to modify the count-query.
7289 * A third argument can be used to provide bindings for the SQL snippet.
7290 *
7291 * @param string $type type of bean we are looking for
7292 * @param string $addSQL additional SQL snippet
7293 * @param array $bindings parameters to bind to SQL
7294 *
7295 * @return integer
7296 */
7297 public function count( $type, $addSQL = '', $bindings = array() )
7298 {
7299 $type = AQueryWriter::camelsSnake( $type );
7300 if ( count( explode( '_', $type ) ) > 2 ) {
7301 throw new RedException( 'Invalid type for count.' );
7302 }
7303
7304 try {
7305 return (int) $this->writer->queryRecordCount( $type, array(), $addSQL, $bindings );
7306 } catch ( SQLException $exception ) {
7307 if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array(
7308 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
7309 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ) ) ) {
7310 throw $exception;
7311 }
7312 }
7313
7314 return 0;
7315 }
7316
7317 /**
7318 * Removes a bean from the database.
7319 * This function will remove the specified OODBBean
7320 * Bean Object from the database.
7321 *
7322 * @param OODBBean|SimpleModel $bean bean you want to remove from database
7323 *
7324 * @return void
7325 */
7326 public function trash( $bean )
7327 {
7328 $this->oodb->signal( 'delete', $bean );
7329 foreach ( $bean as $property => $value ) {
7330 if ( $value instanceof OODBBean ) {
7331 unset( $bean->$property );
7332 }
7333 if ( is_array( $value ) ) {
7334 if ( strpos( $property, 'own' ) === 0 ) {
7335 unset( $bean->$property );
7336 } elseif ( strpos( $property, 'shared' ) === 0 ) {
7337 unset( $bean->$property );
7338 }
7339 }
7340 }
7341 try {
7342 $this->writer->deleteRecord( $bean->getMeta( 'type' ), array( 'id' => array( $bean->id ) ), NULL );
7343 } catch ( SQLException $exception ) {
7344 $this->handleException( $exception );
7345 }
7346 $bean->id = 0;
7347 $this->oodb->signal( 'after_delete', $bean );
7348 }
7349
7350 /**
7351 * Checks whether the specified table already exists in the database.
7352 * Not part of the Object Database interface!
7353 *
7354 * @deprecated Use AQueryWriter::typeExists() instead.
7355 *
7356 * @param string $table table name
7357 *
7358 * @return boolean
7359 */
7360 public function tableExists( $table )
7361 {
7362 return $this->writer->tableExists( $table );
7363 }
7364
7365 /**
7366 * Trash all beans of a given type. Wipes an entire type of bean.
7367 *
7368 * @param string $type type of bean you wish to delete all instances of
7369 *
7370 * @return boolean
7371 */
7372 public function wipe( $type )
7373 {
7374 try {
7375 $this->writer->wipe( $type );
7376
7377 return TRUE;
7378 } catch ( SQLException $exception ) {
7379 if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE ) ) ) {
7380 throw $exception;
7381 }
7382
7383 return FALSE;
7384 }
7385 }
7386}
7387}
7388
7389namespace RedBeanPHP\Repository {
7390
7391use RedBeanPHP\OODBBean as OODBBean;
7392use RedBeanPHP\QueryWriter as QueryWriter;
7393use RedBeanPHP\RedException as RedException;
7394use RedBeanPHP\BeanHelper as BeanHelper;
7395use RedBeanPHP\RedException\SQL as SQLException;
7396use RedBeanPHP\Repository as Repository;
7397
7398/**
7399 * Fluid Repository.
7400 * OODB manages two repositories, a fluid one that
7401 * adjust the database schema on-the-fly to accomodate for
7402 * new bean types (tables) and new properties (columns) and
7403 * a frozen one for use in a production environment. OODB
7404 * allows you to swap the repository instances using the freeze()
7405 * method.
7406 *
7407 * @file RedBeanPHP/Repository/Fluid.php
7408 * @author Gabor de Mooij and the RedBeanPHP community
7409 * @license BSD/GPLv2
7410 *
7411 * @copyright
7412 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
7413 * This source file is subject to the BSD/GPLv2 License that is bundled
7414 * with this source code in the file license.txt.
7415 */
7416class Fluid extends Repository
7417{
7418 /**
7419 * Figures out the desired type given the cast string ID.
7420 *
7421 * @param string $cast cast identifier
7422 *
7423 * @return integer
7424 */
7425 private function getTypeFromCast( $cast )
7426 {
7427 if ( $cast == 'string' ) {
7428 $typeno = $this->writer->scanType( 'STRING' );
7429 } elseif ( $cast == 'id' ) {
7430 $typeno = $this->writer->getTypeForID();
7431 } elseif ( isset( $this->writer->sqltype_typeno[$cast] ) ) {
7432 $typeno = $this->writer->sqltype_typeno[$cast];
7433 } else {
7434 throw new RedException( 'Invalid Cast' );
7435 }
7436
7437 return $typeno;
7438 }
7439
7440 /**
7441 * Orders the Query Writer to create a table if it does not exist already and
7442 * adds a note in the build report about the creation.
7443 *
7444 * @param OODBBean $bean bean to update report of
7445 * @param string $table table to check and create if not exists
7446 *
7447 * @return void
7448 */
7449 private function createTableIfNotExists( OODBBean $bean, $table )
7450 {
7451 //Does table exist? If not, create
7452 if ( !$this->tableExists( $this->writer->esc( $table, TRUE ) ) ) {
7453 $this->writer->createTable( $table );
7454 $bean->setMeta( 'buildreport.flags.created', TRUE );
7455 }
7456 }
7457
7458 /**
7459 * Modifies the table to fit the bean data.
7460 * Given a property and a value and the bean, this method will
7461 * adjust the table structure to fit the requirements of the property and value.
7462 * This may include adding a new column or widening an existing column to hold a larger
7463 * or different kind of value. This method employs the writer to adjust the table
7464 * structure in the database. Schema updates are recorded in meta properties of the bean.
7465 *
7466 * This method will also apply indexes, unique constraints and foreign keys.
7467 *
7468 * @param OODBBean $bean bean to get cast data from and store meta in
7469 * @param string $property property to store
7470 * @param mixed $value value to store
7471 *
7472 * @return void
7473 */
7474 private function modifySchema( OODBBean $bean, $property, $value )
7475 {
7476 $doFKStuff = FALSE;
7477 $table = $bean->getMeta( 'type' );
7478 $columns = $this->writer->getColumns( $table );
7479 $columnNoQ = $this->writer->esc( $property, TRUE );
7480 if ( !$this->oodb->isChilled( $bean->getMeta( 'type' ) ) ) {
7481 if ( $bean->getMeta( "cast.$property", -1 ) !== -1 ) { //check for explicitly specified types
7482 $cast = $bean->getMeta( "cast.$property" );
7483 $typeno = $this->getTypeFromCast( $cast );
7484 } else {
7485 $cast = FALSE;
7486 $typeno = $this->writer->scanType( $value, TRUE );
7487 }
7488 if ( isset( $columns[$this->writer->esc( $property, TRUE )] ) ) { //Is this property represented in the table ?
7489 if ( !$cast ) { //rescan without taking into account special types >80
7490 $typeno = $this->writer->scanType( $value, FALSE );
7491 }
7492 $sqlt = $this->writer->code( $columns[$this->writer->esc( $property, TRUE )] );
7493 if ( $typeno > $sqlt ) { //no, we have to widen the database column type
7494 $this->writer->widenColumn( $table, $property, $typeno );
7495 $bean->setMeta( 'buildreport.flags.widen', TRUE );
7496 $doFKStuff = TRUE;
7497 }
7498 } else {
7499 $this->writer->addColumn( $table, $property, $typeno );
7500 $bean->setMeta( 'buildreport.flags.addcolumn', TRUE );
7501 $doFKStuff = TRUE;
7502 }
7503 if ($doFKStuff) {
7504 if (strrpos($columnNoQ, '_id')===(strlen($columnNoQ)-3)) {
7505 $destinationColumnNoQ = substr($columnNoQ, 0, strlen($columnNoQ)-3);
7506 $indexName = "index_foreignkey_{$table}_{$destinationColumnNoQ}";
7507 $this->writer->addIndex($table, $indexName, $columnNoQ);
7508 $typeof = $bean->getMeta("sys.typeof.{$destinationColumnNoQ}", $destinationColumnNoQ);
7509 $isLink = $bean->getMeta( 'sys.buildcommand.unique', FALSE );
7510 //Make FK CASCADING if part of exclusive list (dependson=typeof) or if link bean
7511 $isDep = ( $bean->moveMeta( 'sys.buildcommand.fkdependson' ) === $typeof || is_array( $isLink ) );
7512 $result = $this->writer->addFK( $table, $typeof, $columnNoQ, 'id', $isDep );
7513 //If this is a link bean and all unique columns have been added already, then apply unique constraint
7514 if ( is_array( $isLink ) && !count( array_diff( $isLink, array_keys( $this->writer->getColumns( $table ) ) ) ) ) {
7515 $this->writer->addUniqueConstraint( $table, $bean->moveMeta('sys.buildcommand.unique') );
7516 $bean->setMeta("sys.typeof.{$destinationColumnNoQ}", NULL);
7517 }
7518 }
7519 }
7520 }
7521 }
7522
7523 /**
7524 * Part of the store() functionality.
7525 * Handles all new additions after the bean has been saved.
7526 * Stores addition bean in own-list, extracts the id and
7527 * adds a foreign key. Also adds a constraint in case the type is
7528 * in the dependent list.
7529 *
7530 * Note that this method raises a custom exception if the bean
7531 * is not an instance of OODBBean. Therefore it does not use
7532 * a type hint. This allows the user to take action in case
7533 * invalid objects are passed in the list.
7534 *
7535 * @param OODBBean $bean bean to process
7536 * @param array $ownAdditions list of addition beans in own-list
7537 *
7538 * @return void
7539 */
7540 protected function processAdditions( $bean, $ownAdditions )
7541 {
7542 $beanType = $bean->getMeta( 'type' );
7543
7544 foreach ( $ownAdditions as $addition ) {
7545 if ( $addition instanceof OODBBean ) {
7546
7547 $myFieldLink = $beanType . '_id';
7548 $alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
7549 if ( $alias ) $myFieldLink = $alias . '_id';
7550
7551 $addition->$myFieldLink = $bean->id;
7552 $addition->setMeta( 'cast.' . $myFieldLink, 'id' );
7553
7554 if ($alias) {
7555 $addition->setMeta( "sys.typeof.{$alias}", $beanType );
7556 } else {
7557 $addition->setMeta( "sys.typeof.{$beanType}", $beanType );
7558 }
7559
7560 $this->store( $addition );
7561 } else {
7562 throw new RedException( 'Array may only contain OODBBeans' );
7563 }
7564 }
7565 }
7566
7567 /**
7568 * Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
7569 * method. When all lists and embedded beans (parent objects) have been processed and
7570 * removed from the original bean the bean is passed to this method to be stored
7571 * in the database.
7572 *
7573 * @param OODBBean $bean the clean bean
7574 *
7575 * @return void
7576 */
7577 protected function storeBean( OODBBean $bean )
7578 {
7579 if ( $bean->getMeta( 'changed' ) ) {
7580 $this->check( $bean );
7581 $table = $bean->getMeta( 'type' );
7582 $this->createTableIfNotExists( $bean, $table );
7583
7584 $updateValues = array();
7585 foreach ( $bean as $property => $value ) {
7586 if ( $property !== 'id' ) {
7587 $this->modifySchema( $bean, $property, $value );
7588 }
7589 if ( $property !== 'id' ) {
7590 $updateValues[] = array( 'property' => $property, 'value' => $value );
7591 }
7592 }
7593
7594 $bean->id = $this->writer->updateRecord( $table, $updateValues, $bean->id );
7595 $bean->setMeta( 'changed', FALSE );
7596 }
7597 $bean->setMeta( 'tainted', FALSE );
7598 }
7599
7600 /**
7601 * Handles exceptions. Suppresses exceptions caused by missing structures.
7602 *
7603 * @param Exception $exception exception
7604 *
7605 * @return void
7606 */
7607 protected function handleException( \Exception $exception )
7608 {
7609 if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
7610 array(
7611 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
7612 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ) )
7613 ) {
7614 throw $exception;
7615 }
7616 }
7617
7618 /**
7619 * Dispenses a new bean (a OODBBean Bean Object)
7620 * of the specified type. Always
7621 * use this function to get an empty bean object. Never
7622 * instantiate a OODBBean yourself because it needs
7623 * to be configured before you can use it with RedBean. This
7624 * function applies the appropriate initialization /
7625 * configuration for you.
7626 *
7627 * @param string $type type of bean you want to dispense
7628 * @param string $number number of beans you would like to get
7629 * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
7630 *
7631 * @return OODBBean
7632 */
7633 public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
7634 {
7635 $OODBBEAN = defined( 'REDBEAN_OODBBEAN_CLASS' ) ? REDBEAN_OODBBEAN_CLASS : '\RedBeanPHP\OODBBean';
7636 $beans = array();
7637 for ( $i = 0; $i < $number; $i++ ) {
7638 $bean = new $OODBBEAN;
7639 $bean->initializeForDispense( $type, $this->oodb->getBeanHelper() );
7640 $this->check( $bean );
7641 $this->oodb->signal( 'dispense', $bean );
7642 $beans[] = $bean;
7643 }
7644
7645 return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans;
7646 }
7647
7648 /**
7649 * Loads a bean from the object database.
7650 * It searches for a OODBBean Bean Object in the
7651 * database. It does not matter how this bean has been stored.
7652 * RedBean uses the primary key ID $id and the string $type
7653 * to find the bean. The $type specifies what kind of bean you
7654 * are looking for; this is the same type as used with the
7655 * dispense() function. If RedBean finds the bean it will return
7656 * the OODB Bean object; if it cannot find the bean
7657 * RedBean will return a new bean of type $type and with
7658 * primary key ID 0. In the latter case it acts basically the
7659 * same as dispense().
7660 *
7661 * Important note:
7662 * If the bean cannot be found in the database a new bean of
7663 * the specified type will be generated and returned.
7664 *
7665 * @param string $type type of bean you want to load
7666 * @param integer $id ID of the bean you want to load
7667 *
7668 * @return OODBBean
7669 */
7670 public function load( $type, $id )
7671 {
7672 $bean = $this->dispense( $type );
7673 if ( isset( $this->stash[$this->nesting][$id] ) ) {
7674 $row = $this->stash[$this->nesting][$id];
7675 } else {
7676 try {
7677 $rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
7678 } catch ( SQLException $exception ) {
7679 if ( $this->writer->sqlStateIn( $exception->getSQLState(),
7680 array(
7681 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
7682 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
7683 )
7684 ) {
7685 $rows = 0;
7686 }
7687 }
7688 if ( empty( $rows ) ) {
7689 return $bean;
7690 }
7691 $row = array_pop( $rows );
7692 }
7693 $bean->importRow( $row );
7694 $this->nesting++;
7695 $this->oodb->signal( 'open', $bean );
7696 $this->nesting--;
7697
7698 return $bean->setMeta( 'tainted', FALSE );
7699 }
7700}
7701}
7702
7703namespace RedBeanPHP\Repository {
7704
7705use RedBeanPHP\OODBBean as OODBBean;
7706use RedBeanPHP\QueryWriter as QueryWriter;
7707use RedBeanPHP\RedException as RedException;
7708use RedBeanPHP\BeanHelper as BeanHelper;
7709use RedBeanPHP\RedException\SQL as SQLException;
7710use RedBeanPHP\Repository as Repository;
7711
7712/**
7713 * Frozen Repository.
7714 * OODB manages two repositories, a fluid one that
7715 * adjust the database schema on-the-fly to accomodate for
7716 * new bean types (tables) and new properties (columns) and
7717 * a frozen one for use in a production environment. OODB
7718 * allows you to swap the repository instances using the freeze()
7719 * method.
7720 *
7721 * @file RedBeanPHP/Repository/Frozen.php
7722 * @author Gabor de Mooij and the RedBeanPHP community
7723 * @license BSD/GPLv2
7724 *
7725 * @copyright
7726 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
7727 * This source file is subject to the BSD/GPLv2 License that is bundled
7728 * with this source code in the file license.txt.
7729 */
7730class Frozen extends Repository
7731{
7732 /**
7733 * Handles exceptions. Suppresses exceptions caused by missing structures.
7734 *
7735 * @param \Exception $exception exception to handle
7736 *
7737 * @return void
7738 * @throws \Exception
7739 */
7740 protected function handleException( \Exception $exception )
7741 {
7742 throw $exception;
7743 }
7744
7745 /**
7746 * Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
7747 * method. When all lists and embedded beans (parent objects) have been processed and
7748 * removed from the original bean the bean is passed to this method to be stored
7749 * in the database.
7750 *
7751 * @param OODBBean $bean the clean bean
7752 *
7753 * @return void
7754 */
7755 protected function storeBean( OODBBean $bean )
7756 {
7757 if ( $bean->getMeta( 'changed' ) ) {
7758
7759 list( $properties, $table ) = $bean->getPropertiesAndType();
7760 $id = $properties['id'];
7761 unset($properties['id']);
7762 $updateValues = array();
7763 $k1 = 'property';
7764 $k2 = 'value';
7765 foreach( $properties as $key => $value ) {
7766 $updateValues[] = array( $k1 => $key, $k2 => $value );
7767 }
7768 $bean->id = $this->writer->updateRecord( $table, $updateValues, $id );
7769 $bean->setMeta( 'changed', FALSE );
7770 }
7771 $bean->setMeta( 'tainted', FALSE );
7772 }
7773
7774 /**
7775 * Part of the store() functionality.
7776 * Handles all new additions after the bean has been saved.
7777 * Stores addition bean in own-list, extracts the id and
7778 * adds a foreign key. Also adds a constraint in case the type is
7779 * in the dependent list.
7780 *
7781 * Note that this method raises a custom exception if the bean
7782 * is not an instance of OODBBean. Therefore it does not use
7783 * a type hint. This allows the user to take action in case
7784 * invalid objects are passed in the list.
7785 *
7786 * @param OODBBean $bean bean to process
7787 * @param array $ownAdditions list of addition beans in own-list
7788 *
7789 * @return void
7790 * @throws RedException
7791 */
7792 protected function processAdditions( $bean, $ownAdditions )
7793 {
7794 $beanType = $bean->getMeta( 'type' );
7795
7796 $cachedIndex = array();
7797 foreach ( $ownAdditions as $addition ) {
7798 if ( $addition instanceof OODBBean ) {
7799
7800 $myFieldLink = $beanType . '_id';
7801 $alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
7802 if ( $alias ) $myFieldLink = $alias . '_id';
7803
7804 $addition->$myFieldLink = $bean->id;
7805 $addition->setMeta( 'cast.' . $myFieldLink, 'id' );
7806 $this->store( $addition );
7807
7808 } else {
7809 throw new RedException( 'Array may only contain OODBBeans' );
7810 }
7811 }
7812 }
7813
7814 /**
7815 * Dispenses a new bean (a OODBBean Bean Object)
7816 * of the specified type. Always
7817 * use this function to get an empty bean object. Never
7818 * instantiate a OODBBean yourself because it needs
7819 * to be configured before you can use it with RedBean. This
7820 * function applies the appropriate initialization /
7821 * configuration for you.
7822 *
7823 * @param string $type type of bean you want to dispense
7824 * @param int $number number of beans you would like to get
7825 * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
7826 *
7827 * @return OODBBean
7828 */
7829 public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
7830 {
7831 $OODBBEAN = defined( 'REDBEAN_OODBBEAN_CLASS' ) ? REDBEAN_OODBBEAN_CLASS : '\RedBeanPHP\OODBBean';
7832 $beans = array();
7833 for ( $i = 0; $i < $number; $i++ ) {
7834 /** @var \RedBeanPHP\OODBBean $bean */
7835 $bean = new $OODBBEAN;
7836 $bean->initializeForDispense( $type, $this->oodb->getBeanHelper() );
7837 $this->oodb->signal( 'dispense', $bean );
7838 $beans[] = $bean;
7839 }
7840
7841 return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans;
7842 }
7843
7844 /**
7845 * Loads a bean from the object database.
7846 * It searches for a OODBBean Bean Object in the
7847 * database. It does not matter how this bean has been stored.
7848 * RedBean uses the primary key ID $id and the string $type
7849 * to find the bean. The $type specifies what kind of bean you
7850 * are looking for; this is the same type as used with the
7851 * dispense() function. If RedBean finds the bean it will return
7852 * the OODB Bean object; if it cannot find the bean
7853 * RedBean will return a new bean of type $type and with
7854 * primary key ID 0. In the latter case it acts basically the
7855 * same as dispense().
7856 *
7857 * Important note:
7858 * If the bean cannot be found in the database a new bean of
7859 * the specified type will be generated and returned.
7860 *
7861 * @param string $type type of bean you want to load
7862 * @param integer $id ID of the bean you want to load
7863 *
7864 * @return OODBBean
7865 * @throws SQLException
7866 */
7867 public function load( $type, $id )
7868 {
7869 $bean = $this->dispense( $type );
7870 if ( isset( $this->stash[$this->nesting][$id] ) ) {
7871 $row = $this->stash[$this->nesting][$id];
7872 } else {
7873 try {
7874 $rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
7875 } catch ( SQLException $exception ) {
7876 if ( $this->writer->sqlStateIn( $exception->getSQLState(),
7877 array(
7878 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
7879 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
7880 )
7881 ) {
7882 throw $exception; //only throw if frozen
7883 }
7884 }
7885 if ( empty( $rows ) ) {
7886 return $bean;
7887 }
7888 $row = array_pop( $rows );
7889 }
7890 $bean->importRow( $row );
7891 $this->nesting++;
7892 $this->oodb->signal( 'open', $bean );
7893 $this->nesting--;
7894
7895 return $bean->setMeta( 'tainted', FALSE );
7896 }
7897}
7898}
7899
7900namespace RedBeanPHP {
7901
7902use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
7903use RedBeanPHP\QueryWriter as QueryWriter;
7904use RedBeanPHP\BeanHelper as BeanHelper;
7905use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
7906use RedBeanPHP\Repository as Repository;
7907use RedBeanPHP\Repository\Fluid as FluidRepo;
7908use RedBeanPHP\Repository\Frozen as FrozenRepo;
7909
7910/**
7911 * RedBean Object Oriented DataBase.
7912 *
7913 * The RedBean OODB Class is the main class of RedBeanPHP.
7914 * It takes OODBBean objects and stores them to and loads them from the
7915 * database as well as providing other CRUD functions. This class acts as a
7916 * object database.
7917 *
7918 * @file RedBeanPHP/OODB.php
7919 * @author Gabor de Mooij and the RedBeanPHP community
7920 * @license BSD/GPLv2
7921 *
7922 * @copyright
7923 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
7924 * This source file is subject to the BSD/GPLv2 License that is bundled
7925 * with this source code in the file license.txt.
7926 */
7927class OODB extends Observable
7928{
7929 /**
7930 * @var array
7931 */
7932 private static $sqlFilters = array();
7933
7934 /**
7935 * @var array
7936 */
7937 protected $chillList = array();
7938
7939 /**
7940 * @var array
7941 */
7942 protected $stash = NULL;
7943
7944 /*
7945 * @var integer
7946 */
7947 protected $nesting = 0;
7948
7949 /**
7950 * @var DBAdapter
7951 */
7952 protected $writer;
7953
7954 /**
7955 * @var boolean
7956 */
7957 protected $isFrozen = FALSE;
7958
7959 /**
7960 * @var FacadeBeanHelper
7961 */
7962 protected $beanhelper = NULL;
7963
7964 /**
7965 * @var AssociationManager
7966 */
7967 protected $assocManager = NULL;
7968
7969 /**
7970 * @var Repository
7971 */
7972 protected $repository = NULL;
7973
7974 /**
7975 * @var FrozenRepo
7976 */
7977 protected $frozenRepository = NULL;
7978
7979 /**
7980 * @var FluidRepo
7981 */
7982 protected $fluidRepository = NULL;
7983
7984 /**
7985 * @var boolean
7986 */
7987 protected static $autoClearHistoryAfterStore = FALSE;
7988
7989 /**
7990 * If set to TRUE, this method will call clearHistory every time
7991 * the bean gets stored.
7992 *
7993 * @param boolean $autoClear auto clear option
7994 *
7995 * @return void
7996 */
7997 public static function autoClearHistoryAfterStore( $autoClear = TRUE )
7998 {
7999 self::$autoClearHistoryAfterStore = (boolean) $autoClear;
8000 }
8001
8002 /**
8003 * Unboxes a bean from a FUSE model if needed and checks whether the bean is
8004 * an instance of OODBBean.
8005 *
8006 * @param OODBBean $bean bean you wish to unbox
8007 *
8008 * @return OODBBean
8009 */
8010 protected function unboxIfNeeded( $bean )
8011 {
8012 if ( $bean instanceof SimpleModel ) {
8013 $bean = $bean->unbox();
8014 }
8015 if ( !( $bean instanceof OODBBean ) ) {
8016 throw new RedException( 'OODB Store requires a bean, got: ' . gettype( $bean ) );
8017 }
8018
8019 return $bean;
8020 }
8021
8022 /**
8023 * Constructor, requires a query writer.
8024 *
8025 * @param QueryWriter $writer writer
8026 * @param array|boolean $frozen mode of operation: TRUE (frozen), FALSE (default, fluid) or ARRAY (chilled)
8027 */
8028 public function __construct( QueryWriter $writer, $frozen = FALSE )
8029 {
8030 if ( $writer instanceof QueryWriter ) {
8031 $this->writer = $writer;
8032 }
8033
8034 $this->freeze( $frozen );
8035 }
8036
8037 /**
8038 * Toggles fluid or frozen mode. In fluid mode the database
8039 * structure is adjusted to accomodate your objects. In frozen mode
8040 * this is not the case.
8041 *
8042 * You can also pass an array containing a selection of frozen types.
8043 * Let's call this chilly mode, it's just like fluid mode except that
8044 * certain types (i.e. tables) aren't touched.
8045 *
8046 * @param boolean|array $toggle TRUE if you want to use OODB instance in frozen mode
8047 *
8048 * @return void
8049 */
8050 public function freeze( $toggle )
8051 {
8052 if ( is_array( $toggle ) ) {
8053 $this->chillList = $toggle;
8054 $this->isFrozen = FALSE;
8055 } else {
8056 $this->isFrozen = (boolean) $toggle;
8057 }
8058
8059 if ( $this->isFrozen ) {
8060 if ( !$this->frozenRepository ) {
8061 $this->frozenRepository = new FrozenRepo( $this, $this->writer );
8062 }
8063
8064 $this->repository = $this->frozenRepository;
8065
8066 } else {
8067 if ( !$this->fluidRepository ) {
8068 $this->fluidRepository = new FluidRepo( $this, $this->writer );
8069 }
8070
8071 $this->repository = $this->fluidRepository;
8072 }
8073
8074 if ( count( self::$sqlFilters ) ) {
8075 AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
8076 }
8077
8078 }
8079
8080 /**
8081 * Returns the current mode of operation of RedBean.
8082 * In fluid mode the database
8083 * structure is adjusted to accomodate your objects.
8084 * In frozen mode
8085 * this is not the case.
8086 *
8087 * @return boolean
8088 */
8089 public function isFrozen()
8090 {
8091 return (bool) $this->isFrozen;
8092 }
8093
8094 /**
8095 * Determines whether a type is in the chill list.
8096 * If a type is 'chilled' it's frozen, so its schema cannot be
8097 * changed anymore. However other bean types may still be modified.
8098 * This method is a convenience method for other objects to check if
8099 * the schema of a certain type is locked for modification.
8100 *
8101 * @param string $type the type you wish to check
8102 *
8103 * @return boolean
8104 */
8105 public function isChilled( $type )
8106 {
8107 return (boolean) ( in_array( $type, $this->chillList ) );
8108 }
8109
8110 /**
8111 * Dispenses a new bean (a OODBBean Bean Object)
8112 * of the specified type. Always
8113 * use this function to get an empty bean object. Never
8114 * instantiate a OODBBean yourself because it needs
8115 * to be configured before you can use it with RedBean. This
8116 * function applies the appropriate initialization /
8117 * configuration for you.
8118 *
8119 * @param string $type type of bean you want to dispense
8120 * @param string $number number of beans you would like to get
8121 * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
8122 *
8123 * @return OODBBean
8124 */
8125 public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
8126 {
8127 if ( $number < 1 ) {
8128 if ( $alwaysReturnArray ) return array();
8129 return NULL;
8130 }
8131
8132 return $this->repository->dispense( $type, $number, $alwaysReturnArray );
8133 }
8134
8135 /**
8136 * Sets bean helper to be given to beans.
8137 * Bean helpers assist beans in getting a reference to a toolbox.
8138 *
8139 * @param BeanHelper $beanhelper helper
8140 *
8141 * @return void
8142 */
8143 public function setBeanHelper( BeanHelper $beanhelper )
8144 {
8145 $this->beanhelper = $beanhelper;
8146 }
8147
8148 /**
8149 * Returns the current bean helper.
8150 * Bean helpers assist beans in getting a reference to a toolbox.
8151 *
8152 * @return BeanHelper
8153 */
8154 public function getBeanHelper()
8155 {
8156 return $this->beanhelper;
8157 }
8158
8159 /**
8160 * Checks whether a OODBBean bean is valid.
8161 * If the type is not valid or the ID is not valid it will
8162 * throw an exception: Security.
8163 *
8164 * @param OODBBean $bean the bean that needs to be checked
8165 *
8166 * @return void
8167 */
8168 public function check( OODBBean $bean )
8169 {
8170 $this->repository->check( $bean );
8171 }
8172
8173 /**
8174 * Searches the database for a bean that matches conditions $conditions and sql $addSQL
8175 * and returns an array containing all the beans that have been found.
8176 *
8177 * Conditions need to take form:
8178 *
8179 * <code>
8180 * array(
8181 * 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
8182 * 'PROPERTY' => array( POSSIBLE VALUES... )
8183 * );
8184 * </code>
8185 *
8186 * All conditions are glued together using the AND-operator, while all value lists
8187 * are glued using IN-operators thus acting as OR-conditions.
8188 *
8189 * Note that you can use property names; the columns will be extracted using the
8190 * appropriate bean formatter.
8191 *
8192 * @param string $type type of beans you are looking for
8193 * @param array $conditions list of conditions
8194 * @param string $addSQL SQL to be used in query
8195 * @param array $bindings a list of values to bind to query parameters
8196 *
8197 * @return array
8198 */
8199 public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
8200 {
8201 return $this->repository->find( $type, $conditions, $sql, $bindings );
8202 }
8203
8204 /**
8205 * Same as find() but returns a BeanCollection.
8206 *
8207 * @param string $type type of beans you are looking for
8208 * @param string $addSQL SQL to be used in query
8209 * @param array $bindings a list of values to bind to query parameters
8210 *
8211 * @return array
8212 */
8213 public function findCollection( $type, $sql = NULL, $bindings = array() )
8214 {
8215 return $this->repository->findCollection( $type, $sql, $bindings );
8216 }
8217
8218 /**
8219 * Checks whether the specified table already exists in the database.
8220 * Not part of the Object Database interface!
8221 *
8222 * @deprecated Use AQueryWriter::typeExists() instead.
8223 *
8224 * @param string $table table name
8225 *
8226 * @return boolean
8227 */
8228 public function tableExists( $table )
8229 {
8230 return $this->repository->tableExists( $table );
8231 }
8232
8233 /**
8234 * Stores a bean in the database. This method takes a
8235 * OODBBean Bean Object $bean and stores it
8236 * in the database. If the database schema is not compatible
8237 * with this bean and RedBean runs in fluid mode the schema
8238 * will be altered to store the bean correctly.
8239 * If the database schema is not compatible with this bean and
8240 * RedBean runs in frozen mode it will throw an exception.
8241 * This function returns the primary key ID of the inserted
8242 * bean.
8243 *
8244 * The return value is an integer if possible. If it is not possible to
8245 * represent the value as an integer a string will be returned. We use
8246 * explicit casts instead of functions to preserve performance
8247 * (0.13 vs 0.28 for 10000 iterations on Core i3).
8248 *
8249 * @param OODBBean|SimpleModel $bean bean to store
8250 *
8251 * @return integer|string
8252 */
8253 public function store( $bean )
8254 {
8255 $bean = $this->unboxIfNeeded( $bean );
8256 $id = $this->repository->store( $bean );
8257 if ( self::$autoClearHistoryAfterStore ) {
8258 $bean->clearHistory();
8259 }
8260 return $id;
8261 }
8262
8263 /**
8264 * Loads a bean from the object database.
8265 * It searches for a OODBBean Bean Object in the
8266 * database. It does not matter how this bean has been stored.
8267 * RedBean uses the primary key ID $id and the string $type
8268 * to find the bean. The $type specifies what kind of bean you
8269 * are looking for; this is the same type as used with the
8270 * dispense() function. If RedBean finds the bean it will return
8271 * the OODB Bean object; if it cannot find the bean
8272 * RedBean will return a new bean of type $type and with
8273 * primary key ID 0. In the latter case it acts basically the
8274 * same as dispense().
8275 *
8276 * Important note:
8277 * If the bean cannot be found in the database a new bean of
8278 * the specified type will be generated and returned.
8279 *
8280 * @param string $type type of bean you want to load
8281 * @param integer $id ID of the bean you want to load
8282 *
8283 * @return OODBBean
8284 */
8285 public function load( $type, $id )
8286 {
8287 return $this->repository->load( $type, $id );
8288 }
8289
8290 /**
8291 * Removes a bean from the database.
8292 * This function will remove the specified OODBBean
8293 * Bean Object from the database.
8294 *
8295 * @param OODBBean|SimpleModel $bean bean you want to remove from database
8296 *
8297 * @return void
8298 */
8299 public function trash( $bean )
8300 {
8301 $bean = $this->unboxIfNeeded( $bean );
8302 return $this->repository->trash( $bean );
8303 }
8304
8305 /**
8306 * Returns an array of beans. Pass a type and a series of ids and
8307 * this method will bring you the corresponding beans.
8308 *
8309 * important note: Because this method loads beans using the load()
8310 * function (but faster) it will return empty beans with ID 0 for
8311 * every bean that could not be located. The resulting beans will have the
8312 * passed IDs as their keys.
8313 *
8314 * @param string $type type of beans
8315 * @param array $ids ids to load
8316 *
8317 * @return array
8318 */
8319 public function batch( $type, $ids )
8320 {
8321 return $this->repository->batch( $type, $ids );
8322 }
8323
8324 /**
8325 * This is a convenience method; it converts database rows
8326 * (arrays) into beans. Given a type and a set of rows this method
8327 * will return an array of beans of the specified type loaded with
8328 * the data fields provided by the result set from the database.
8329 *
8330 * @param string $type type of beans you would like to have
8331 * @param array $rows rows from the database result
8332 * @param string $mask mask to apply for meta data
8333 *
8334 * @return array
8335 */
8336 public function convertToBeans( $type, $rows, $mask = NULL )
8337 {
8338 return $this->repository->convertToBeans( $type, $rows, $mask );
8339 }
8340
8341 /**
8342 * Counts the number of beans of type $type.
8343 * This method accepts a second argument to modify the count-query.
8344 * A third argument can be used to provide bindings for the SQL snippet.
8345 *
8346 * @param string $type type of bean we are looking for
8347 * @param string $addSQL additional SQL snippet
8348 * @param array $bindings parameters to bind to SQL
8349 *
8350 * @return integer
8351 */
8352 public function count( $type, $addSQL = '', $bindings = array() )
8353 {
8354 return $this->repository->count( $type, $addSQL, $bindings );
8355 }
8356
8357 /**
8358 * Trash all beans of a given type. Wipes an entire type of bean.
8359 *
8360 * @param string $type type of bean you wish to delete all instances of
8361 *
8362 * @return boolean
8363 */
8364 public function wipe( $type )
8365 {
8366 return $this->repository->wipe( $type );
8367 }
8368
8369 /**
8370 * Returns an Association Manager for use with OODB.
8371 * A simple getter function to obtain a reference to the association manager used for
8372 * storage and more.
8373 *
8374 * @return AssociationManager
8375 */
8376 public function getAssociationManager()
8377 {
8378 if ( !isset( $this->assocManager ) ) {
8379 throw new RedException( 'No association manager available.' );
8380 }
8381
8382 return $this->assocManager;
8383 }
8384
8385 /**
8386 * Sets the association manager instance to be used by this OODB.
8387 * A simple setter function to set the association manager to be used for storage and
8388 * more.
8389 *
8390 * @param AssociationManager $assoc sets the association manager to be used
8391 *
8392 * @return void
8393 */
8394 public function setAssociationManager( AssociationManager $assocManager )
8395 {
8396 $this->assocManager = $assocManager;
8397 }
8398
8399 /**
8400 * Returns the currently used repository instance.
8401 * For testing purposes only.
8402 *
8403 * @return Repository
8404 */
8405 public function getCurrentRepository()
8406 {
8407 return $this->repository;
8408 }
8409
8410 /**
8411 * Binds an SQL function to a column.
8412 * This method can be used to setup a decode/encode scheme or
8413 * perform UUID insertion. This method is especially useful for handling
8414 * MySQL spatial columns, because they need to be processed first using
8415 * the asText/GeomFromText functions.
8416 *
8417 * @param string $mode mode to set function for, i.e. read or write
8418 * @param string $field field (table.column) to bind SQL function to
8419 * @param string $function SQL function to bind to field
8420 *
8421 * @return void
8422 */
8423 public function bindFunc( $mode, $field, $function )
8424 {
8425 list( $type, $property ) = explode( '.', $field );
8426 $mode = ($mode === 'write') ? QueryWriter::C_SQLFILTER_WRITE : QueryWriter::C_SQLFILTER_READ;
8427
8428 if ( !isset( self::$sqlFilters[$mode] ) ) self::$sqlFilters[$mode] = array();
8429 if ( !isset( self::$sqlFilters[$mode][$type] ) ) self::$sqlFilters[$mode][$type] = array();
8430
8431 if ( is_null( $function ) ) {
8432 unset( self::$sqlFilters[$mode][$type][$property] );
8433 } else {
8434 if ($mode === QueryWriter::C_SQLFILTER_WRITE) {
8435 self::$sqlFilters[$mode][$type][$property] = $function.'(?)';
8436 } else {
8437 self::$sqlFilters[$mode][$type][$property] = $function."($field)";
8438 }
8439 }
8440
8441 AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
8442 }
8443}
8444}
8445
8446namespace RedBeanPHP {
8447
8448use RedBeanPHP\OODB as OODB;
8449use RedBeanPHP\QueryWriter as QueryWriter;
8450use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
8451use RedBeanPHP\Adapter as Adapter;
8452
8453/**
8454 * ToolBox.
8455 *
8456 * The toolbox is an integral part of RedBeanPHP providing the basic
8457 * architectural building blocks to manager objects, helpers and additional tools
8458 * like plugins. A toolbox contains the three core components of RedBeanPHP:
8459 * the adapter, the query writer and the core functionality of RedBeanPHP in
8460 * OODB.
8461 *
8462 * @file RedBeanPHP/ToolBox.php
8463 * @author Gabor de Mooij and the RedBeanPHP community
8464 * @license BSD/GPLv2
8465 *
8466 * @copyright
8467 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
8468 * This source file is subject to the BSD/GPLv2 License that is bundled
8469 * with this source code in the file license.txt.
8470 */
8471class ToolBox
8472{
8473
8474 /**
8475 * @var OODB
8476 */
8477 protected $oodb;
8478
8479 /**
8480 * @var QueryWriter
8481 */
8482 protected $writer;
8483
8484 /**
8485 * @var DBAdapter
8486 */
8487 protected $adapter;
8488
8489 /**
8490 * Constructor.
8491 * The toolbox is an integral part of RedBeanPHP providing the basic
8492 * architectural building blocks to manager objects, helpers and additional tools
8493 * like plugins. A toolbox contains the three core components of RedBeanPHP:
8494 * the adapter, the query writer and the core functionality of RedBeanPHP in
8495 * OODB.
8496 *
8497 * @param OODB $oodb Object Database, OODB
8498 * @param DBAdapter $adapter Database Adapter
8499 * @param QueryWriter $writer Query Writer
8500 */
8501 public function __construct( OODB $oodb, Adapter $adapter, QueryWriter $writer )
8502 {
8503 $this->oodb = $oodb;
8504 $this->adapter = $adapter;
8505 $this->writer = $writer;
8506 return $this;
8507 }
8508
8509 /**
8510 * Returns the query writer in this toolbox.
8511 * The Query Writer is responsible for building the queries for a
8512 * specific database and executing them through the adapter.
8513 *
8514 * @return QueryWriter
8515 */
8516 public function getWriter()
8517 {
8518 return $this->writer;
8519 }
8520
8521 /**
8522 * Returns the OODB instance in this toolbox.
8523 * OODB is responsible for creating, storing, retrieving and deleting
8524 * single beans. Other components rely
8525 * on OODB for their basic functionality.
8526 *
8527 * @return OODB
8528 */
8529 public function getRedBean()
8530 {
8531 return $this->oodb;
8532 }
8533
8534 /**
8535 * Returns the database adapter in this toolbox.
8536 * The adapter is responsible for executing the query and binding the values.
8537 * The adapter also takes care of transaction handling.
8538 *
8539 * @return DBAdapter
8540 */
8541 public function getDatabaseAdapter()
8542 {
8543 return $this->adapter;
8544 }
8545}
8546}
8547
8548namespace RedBeanPHP {
8549
8550
8551/**
8552 * RedBeanPHP Finder.
8553 * Service class to find beans. For the most part this class
8554 * offers user friendly utility methods for interacting with the
8555 * OODB::find() method, which is rather complex. This class can be
8556 * used to find beans using plain old SQL queries.
8557 *
8558 * @file RedBeanPHP/Finder.php
8559 * @author Gabor de Mooij and the RedBeanPHP Community
8560 * @license BSD/GPLv2
8561 *
8562 * @copyright
8563 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
8564 * This source file is subject to the BSD/GPLv2 License that is bundled
8565 * with this source code in the file license.txt.
8566 */
8567class Finder
8568{
8569 /**
8570 * @var ToolBox
8571 */
8572 protected $toolbox;
8573
8574 /**
8575 * @var OODB
8576 */
8577 protected $redbean;
8578
8579 /**
8580 * Constructor.
8581 * The Finder requires a toolbox.
8582 *
8583 * @param ToolBox $toolbox
8584 */
8585 public function __construct( ToolBox $toolbox )
8586 {
8587 $this->toolbox = $toolbox;
8588 $this->redbean = $toolbox->getRedBean();
8589 }
8590
8591 /**
8592 * Finds a bean using a type and a where clause (SQL).
8593 * As with most Query tools in RedBean you can provide values to
8594 * be inserted in the SQL statement by populating the value
8595 * array parameter; you can either use the question mark notation
8596 * or the slot-notation (:keyname).
8597 *
8598 * @param string $type type the type of bean you are looking for
8599 * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
8600 * @param array $bindings values array of values to be bound to parameters in query
8601 *
8602 * @return array
8603 */
8604 public function find( $type, $sql = NULL, $bindings = array() )
8605 {
8606 if ( !is_array( $bindings ) ) {
8607 throw new RedException(
8608 'Expected array, ' . gettype( $bindings ) . ' given.'
8609 );
8610 }
8611
8612 return $this->redbean->find( $type, array(), $sql, $bindings );
8613 }
8614
8615 /**
8616 * Like find() but also exports the beans as an array.
8617 * This method will perform a find-operation. For every bean
8618 * in the result collection this method will call the export() method.
8619 * This method returns an array containing the array representations
8620 * of every bean in the result set.
8621 *
8622 * @see Finder::find
8623 *
8624 * @param string $type type the type of bean you are looking for
8625 * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
8626 * @param array $bindings values array of values to be bound to parameters in query
8627 *
8628 * @return array
8629 */
8630 public function findAndExport( $type, $sql = NULL, $bindings = array() )
8631 {
8632 $arr = array();
8633 foreach ( $this->find( $type, $sql, $bindings ) as $key => $item ) {
8634 $arr[] = $item->export();
8635 }
8636
8637 return $arr;
8638 }
8639
8640 /**
8641 * Like find() but returns just one bean instead of an array of beans.
8642 * This method will return only the first bean of the array.
8643 * If no beans are found, this method will return NULL.
8644 *
8645 * @see Finder::find
8646 *
8647 * @param string $type type the type of bean you are looking for
8648 * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause
8649 * @param array $bindings values array of values to be bound to parameters in query
8650 *
8651 * @return OODBBean
8652 */
8653 public function findOne( $type, $sql = NULL, $bindings = array() )
8654 {
8655 $sql = $this->toolbox->getWriter()->glueLimitOne( $sql );
8656
8657 $items = $this->find( $type, $sql, $bindings );
8658
8659 if ( empty($items) ) {
8660 return NULL;
8661 }
8662
8663 return reset( $items );
8664 }
8665
8666 /**
8667 * Like find() but returns the last bean of the result array.
8668 * Opposite of Finder::findLast().
8669 * If no beans are found, this method will return NULL.
8670 *
8671 * @see Finder::find
8672 *
8673 * @param string $type the type of bean you are looking for
8674 * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
8675 * @param array $bindings values array of values to be bound to parameters in query
8676 *
8677 * @return OODBBean
8678 */
8679 public function findLast( $type, $sql = NULL, $bindings = array() )
8680 {
8681 $items = $this->find( $type, $sql, $bindings );
8682
8683 if ( empty($items) ) {
8684 return NULL;
8685 }
8686
8687 return end( $items );
8688 }
8689
8690 /**
8691 * Tries to find beans of a certain type,
8692 * if no beans are found, it dispenses a bean of that type.
8693 *
8694 * @see Finder::find
8695 *
8696 * @param string $type the type of bean you are looking for
8697 * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
8698 * @param array $bindings values array of values to be bound to parameters in query
8699 *
8700 * @return array
8701 */
8702 public function findOrDispense( $type, $sql = NULL, $bindings = array() )
8703 {
8704 $foundBeans = $this->find( $type, $sql, $bindings );
8705
8706 if ( empty( $foundBeans ) ) {
8707 return array( $this->redbean->dispense( $type ) );
8708 } else {
8709 return $foundBeans;
8710 }
8711 }
8712
8713 /**
8714 * Finds a BeanCollection using the repository.
8715 * A bean collection can be used to retrieve one bean at a time using
8716 * cursors - this is useful for processing large datasets. A bean collection
8717 * will not load all beans into memory all at once, just one at a time.
8718 *
8719 * @param string $type the type of bean you are looking for
8720 * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
8721 * @param array $bindings values array of values to be bound to parameters in query
8722 *
8723 * @return BeanCollection
8724 */
8725 public function findCollection( $type, $sql, $bindings = array() )
8726 {
8727 return $this->redbean->findCollection( $type, $sql, $bindings );
8728 }
8729
8730 /**
8731 * Finds or creates a bean.
8732 * Tries to find a bean with certain properties specified in the second
8733 * parameter ($like). If the bean is found, it will be returned.
8734 * If multiple beans are found, only the first will be returned.
8735 * If no beans match the criteria, a new bean will be dispensed,
8736 * the criteria will be imported as properties and this new bean
8737 * will be stored and returned.
8738 *
8739 * Format of criteria set: property => value
8740 * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
8741 *
8742 * @param string $type type of bean to search for
8743 * @param array $like criteria set describing bean to search for
8744 *
8745 * @return OODBBean
8746 */
8747 public function findOrCreate( $type, $like = array() )
8748 {
8749 $beans = $this->findLike( $type, $like );
8750 if ( count( $beans ) ) {
8751 $bean = reset( $beans );
8752 return $bean;
8753 }
8754
8755 $bean = $this->redbean->dispense( $type );
8756 $bean->import( $like );
8757 $this->redbean->store( $bean );
8758 return $bean;
8759 }
8760
8761 /**
8762 * Finds beans by its type and a certain criteria set.
8763 *
8764 * Format of criteria set: property => value
8765 * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
8766 *
8767 * If the additional SQL is a condition, this condition will be glued to the rest
8768 * of the query using an AND operator. Note that this is as far as this method
8769 * can go, there is no way to glue additional SQL using an OR-condition.
8770 * This method provides access to an underlying mechanism in the RedBeanPHP architecture
8771 * to find beans using criteria sets. However, please do not use this method
8772 * for complex queries, use plain SQL instead ( the regular find method ) as it is
8773 * more suitable for the job. This method is
8774 * meant for basic search-by-example operations.
8775 *
8776 * @param string $type type of bean to search for
8777 * @param array $conditions criteria set describing the bean to search for
8778 * @param string $sql additional SQL (for sorting)
8779 *
8780 * @return array
8781 */
8782 public function findLike( $type, $conditions = array(), $sql = '' )
8783 {
8784 if ( count( $conditions ) > 0 ) {
8785 foreach( $conditions as $key => $condition ) {
8786 if ( !count( $condition ) ) unset( $conditions[$key] );
8787 }
8788 }
8789
8790 return $this->redbean->find( $type, $conditions, $sql );
8791 }
8792
8793 /**
8794 * Returns a hashmap with bean arrays keyed by type using an SQL
8795 * query as its resource. Given an SQL query like 'SELECT movie.*, review.* FROM movie... JOIN review'
8796 * this method will return movie and review beans.
8797 *
8798 * Example:
8799 *
8800 * <code>
8801 * $stuff = $finder->findMulti('movie,review', '
8802 * SELECT movie.*, review.* FROM movie
8803 * LEFT JOIN review ON review.movie_id = movie.id');
8804 * </code>
8805 *
8806 * After this operation, $stuff will contain an entry 'movie' containing all
8807 * movies and an entry named 'review' containing all reviews (all beans).
8808 * You can also pass bindings.
8809 *
8810 * If you want to re-map your beans, so you can use $movie->ownReviewList without
8811 * having RedBeanPHP executing an SQL query you can use the fourth parameter to
8812 * define a selection of remapping closures.
8813 *
8814 * The remapping argument (optional) should contain an array of arrays.
8815 * Each array in the remapping array should contain the following entries:
8816 *
8817 * <code>
8818 * array(
8819 * 'a' => TYPE A
8820 * 'b' => TYPE B
8821 * 'matcher' => MATCHING FUNCTION ACCEPTING A, B and ALL BEANS
8822 * 'do' => OPERATION FUNCTION ACCEPTING A, B, ALL BEANS, ALL REMAPPINGS
8823 * )
8824 * </code>
8825 *
8826 * Using this mechanism you can build your own 'preloader' with tiny function
8827 * snippets (and those can be re-used and shared online of course).
8828 *
8829 * Example:
8830 *
8831 * <code>
8832 * array(
8833 * 'a' => 'movie' //define A as movie
8834 * 'b' => 'review' //define B as review
8835 * 'matcher' => function( $a, $b ) {
8836 * return ( $b->movie_id == $a->id ); //Perform action if review.movie_id equals movie.id
8837 * }
8838 * 'do' => function( $a, $b ) {
8839 * $a->noLoad()->ownReviewList[] = $b; //Add the review to the movie
8840 * $a->clearHistory(); //optional, act 'as if these beans have been loaded through ownReviewList'.
8841 * }
8842 * )
8843 * </code>
8844 *
8845 * The Query Template parameter is optional as well but can be used to
8846 * set a different SQL template (sprintf-style) for processing the original query.
8847 *
8848 * @note the SQL query provided IS NOT THE ONE used internally by this function,
8849 * this function will pre-process the query to get all the data required to find the beans.
8850 *
8851 * @note if you use the 'book.*' notation make SURE you're
8852 * selector starts with a SPACE. ' book.*' NOT ',book.*'. This is because
8853 * it's actually an SQL-like template SLOT, not real SQL.
8854 *
8855 * @note instead of an SQL query you can pass a result array as well.
8856 *
8857 * @param string|array $types a list of types (either array or comma separated string)
8858 * @param string|array $sqlOrArr an SQL query or an array of prefetched records
8859 * @param array $bindings optional, bindings for SQL query
8860 * @param array $remappings optional, an array of remapping arrays
8861 * @param string $queryTemplate optional, query template
8862 *
8863 * @return array
8864 */
8865 public function findMulti( $types, $sql, $bindings = array(), $remappings = array(), $queryTemplate = ' %s.%s AS %s__%s' )
8866 {
8867 if ( !is_array( $types ) ) $types = explode( ',', $types );
8868 if ( !is_array( $sql ) ) {
8869 $writer = $this->toolbox->getWriter();
8870 $adapter = $this->toolbox->getDatabaseAdapter();
8871
8872 //Repair the query, replace book.* with book.id AS book_id etc..
8873 foreach( $types as $type ) {
8874 $pattern = " {$type}.*";
8875 if ( strpos( $sql, $pattern ) !== FALSE ) {
8876 $newSelectorArray = array();
8877 $columns = $writer->getColumns( $type );
8878 foreach( $columns as $column => $definition ) {
8879 $newSelectorArray[] = sprintf( $queryTemplate, $type, $column, $type, $column );
8880 }
8881 $newSelector = implode( ',', $newSelectorArray );
8882 $sql = str_replace( $pattern, $newSelector, $sql );
8883 }
8884 }
8885
8886 $rows = $adapter->get( $sql, $bindings );
8887 } else {
8888 $rows = $sql;
8889 }
8890
8891 //Gather the bean data from the query results using the prefix
8892 $wannaBeans = array();
8893 foreach( $types as $type ) {
8894 $wannaBeans[$type] = array();
8895 $prefix = "{$type}__";
8896 foreach( $rows as $rowkey=>$row ) {
8897 $wannaBean = array();
8898 foreach( $row as $cell => $value ) {
8899 if ( strpos( $cell, $prefix ) === 0 ) {
8900 $property = substr( $cell, strlen( $prefix ) );
8901 unset( $rows[$rowkey][$cell] );
8902 $wannaBean[$property] = $value;
8903 }
8904 }
8905 if ( !isset( $wannaBean['id'] ) ) continue;
8906 if ( is_null( $wannaBean['id'] ) ) continue;
8907 $wannaBeans[$type][$wannaBean['id']] = $wannaBean;
8908 }
8909 }
8910
8911 //Turn the rows into beans
8912 $beans = array();
8913 foreach( $wannaBeans as $type => $wannabees ) {
8914 $beans[$type] = $this->redbean->convertToBeans( $type, $wannabees );
8915 }
8916
8917 //Apply additional re-mappings
8918 foreach($remappings as $remapping) {
8919 $a = $remapping['a'];
8920 $b = $remapping['b'];
8921 $matcher = $remapping['matcher'];
8922 $do = $remapping['do'];
8923 foreach( $beans[$a] as $bean ) {
8924 foreach( $beans[$b] as $putBean ) {
8925 if ( $matcher( $bean, $putBean, $beans ) ) $do( $bean, $putBean, $beans, $remapping );
8926 }
8927 }
8928 }
8929 return $beans;
8930 }
8931}
8932}
8933
8934namespace RedBeanPHP {
8935
8936use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
8937use RedBeanPHP\QueryWriter as QueryWriter;
8938use RedBeanPHP\RedException as RedException;
8939use RedBeanPHP\RedException\SQL as SQLException;
8940
8941/**
8942 * Association Manager.
8943 * Manages simple bean associations.
8944 *
8945 * @file RedBeanPHP/AssociationManager.php
8946 * @author Gabor de Mooij and the RedBeanPHP Community
8947 * @license BSD/GPLv2
8948 *
8949 * @copyright
8950 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
8951 * This source file is subject to the BSD/GPLv2 License that is bundled
8952 * with this source code in the file license.txt.
8953 */
8954class AssociationManager extends Observable
8955{
8956 /**
8957 * @var OODB
8958 */
8959 protected $oodb;
8960
8961 /**
8962 * @var DBAdapter
8963 */
8964 protected $adapter;
8965
8966 /**
8967 * @var QueryWriter
8968 */
8969 protected $writer;
8970
8971 /**
8972 * Handles exceptions. Suppresses exceptions caused by missing structures.
8973 *
8974 * @param Exception $exception exception to handle
8975 *
8976 * @return void
8977 */
8978 private function handleException( \Exception $exception )
8979 {
8980 if ( $this->oodb->isFrozen() || !$this->writer->sqlStateIn( $exception->getSQLState(),
8981 array(
8982 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
8983 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN )
8984 )
8985 ) {
8986 throw $exception;
8987 }
8988 }
8989
8990 /**
8991 * Internal method.
8992 * Returns the many-to-many related rows of table $type for bean $bean using additional SQL in $sql and
8993 * $bindings bindings. If $getLinks is TRUE, link rows are returned instead.
8994 *
8995 * @param OODBBean $bean reference bean instance
8996 * @param string $type target bean type
8997 * @param string $sql additional SQL snippet
8998 * @param array $bindings bindings for query
8999 *
9000 * @return array
9001 */
9002 private function relatedRows( $bean, $type, $sql = '', $bindings = array() )
9003 {
9004 $ids = array( $bean->id );
9005 $sourceType = $bean->getMeta( 'type' );
9006 try {
9007 return $this->writer->queryRecordRelated( $sourceType, $type, $ids, $sql, $bindings );
9008 } catch ( SQLException $exception ) {
9009 $this->handleException( $exception );
9010 return array();
9011 }
9012 }
9013
9014 /**
9015 * Associates a pair of beans. This method associates two beans, no matter
9016 * what types. Accepts a base bean that contains data for the linking record.
9017 * This method is used by associate. This method also accepts a base bean to be used
9018 * as the template for the link record in the database.
9019 *
9020 * @param OODBBean $bean1 first bean
9021 * @param OODBBean $bean2 second bean
9022 * @param OODBBean $bean base bean (association record)
9023 *
9024 * @return mixed
9025 */
9026 protected function associateBeans( OODBBean $bean1, OODBBean $bean2, OODBBean $bean )
9027 {
9028 $type = $bean->getMeta( 'type' );
9029 $property1 = $bean1->getMeta( 'type' ) . '_id';
9030 $property2 = $bean2->getMeta( 'type' ) . '_id';
9031
9032 if ( $property1 == $property2 ) {
9033 $property2 = $bean2->getMeta( 'type' ) . '2_id';
9034 }
9035
9036 $this->oodb->store( $bean1 );
9037 $this->oodb->store( $bean2 );
9038
9039 $bean->setMeta( "cast.$property1", "id" );
9040 $bean->setMeta( "cast.$property2", "id" );
9041 $bean->setMeta( 'sys.buildcommand.unique', array( $property1, $property2 ) );
9042
9043 $bean->$property1 = $bean1->id;
9044 $bean->$property2 = $bean2->id;
9045
9046 $results = array();
9047
9048 try {
9049 $id = $this->oodb->store( $bean );
9050 $results[] = $id;
9051 } catch ( SQLException $exception ) {
9052 if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
9053 array( QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION ) )
9054 ) {
9055 throw $exception;
9056 }
9057 }
9058
9059 return $results;
9060 }
9061
9062 /**
9063 * Constructor
9064 *
9065 * @param ToolBox $tools toolbox
9066 */
9067 public function __construct( ToolBox $tools )
9068 {
9069 $this->oodb = $tools->getRedBean();
9070 $this->adapter = $tools->getDatabaseAdapter();
9071 $this->writer = $tools->getWriter();
9072 $this->toolbox = $tools;
9073 }
9074
9075 /**
9076 * Creates a table name based on a types array.
9077 * Manages the get the correct name for the linking table for the
9078 * types provided.
9079 *
9080 * @todo find a nice way to decouple this class from QueryWriter?
9081 *
9082 * @param array $types 2 types as strings
9083 *
9084 * @return string
9085 */
9086 public function getTable( $types )
9087 {
9088 return $this->writer->getAssocTable( $types );
9089 }
9090
9091 /**
9092 * Associates two beans in a many-to-many relation.
9093 * This method will associate two beans and store the connection between the
9094 * two in a link table. Instead of two single beans this method also accepts
9095 * two sets of beans. Returns the ID or the IDs of the linking beans.
9096 *
9097 * @param OODBBean|array $beans1 one or more beans to form the association
9098 * @param OODBBean|array $beans2 one or more beans to form the association
9099 *
9100 * @return array
9101 */
9102 public function associate( $beans1, $beans2 )
9103 {
9104 if ( !is_array( $beans1 ) ) {
9105 $beans1 = array( $beans1 );
9106 }
9107
9108 if ( !is_array( $beans2 ) ) {
9109 $beans2 = array( $beans2 );
9110 }
9111
9112 $results = array();
9113 foreach ( $beans1 as $bean1 ) {
9114 foreach ( $beans2 as $bean2 ) {
9115 $table = $this->getTable( array( $bean1->getMeta( 'type' ), $bean2->getMeta( 'type' ) ) );
9116 $bean = $this->oodb->dispense( $table );
9117 $results[] = $this->associateBeans( $bean1, $bean2, $bean );
9118 }
9119 }
9120
9121 return ( count( $results ) > 1 ) ? $results : reset( $results );
9122 }
9123
9124 /**
9125 * Counts the number of related beans in an N-M relation.
9126 * This method returns the number of beans of type $type associated
9127 * with reference bean(s) $bean. The query can be tuned using an
9128 * SQL snippet for additional filtering.
9129 *
9130 * @param OODBBean|array $bean a bean object or an array of beans
9131 * @param string $type type of bean you're interested in
9132 * @param string $sql SQL snippet (optional)
9133 * @param array $bindings bindings for your SQL string
9134 *
9135 * @return integer
9136 */
9137 public function relatedCount( $bean, $type, $sql = NULL, $bindings = array() )
9138 {
9139 if ( !( $bean instanceof OODBBean ) ) {
9140 throw new RedException(
9141 'Expected array or OODBBean but got:' . gettype( $bean )
9142 );
9143 }
9144
9145 if ( !$bean->id ) {
9146 return 0;
9147 }
9148
9149 $beanType = $bean->getMeta( 'type' );
9150
9151 try {
9152 return $this->writer->queryRecordCountRelated( $beanType, $type, $bean->id, $sql, $bindings );
9153 } catch ( SQLException $exception ) {
9154 $this->handleException( $exception );
9155
9156 return 0;
9157 }
9158 }
9159
9160 /**
9161 * Breaks the association between two beans. This method unassociates two beans. If the
9162 * method succeeds the beans will no longer form an association. In the database
9163 * this means that the association record will be removed. This method uses the
9164 * OODB trash() method to remove the association links, thus giving FUSE models the
9165 * opportunity to hook-in additional business logic. If the $fast parameter is
9166 * set to boolean TRUE this method will remove the beans without their consent,
9167 * bypassing FUSE. This can be used to improve performance.
9168 *
9169 * @param OODBBean $bean1 first bean in target association
9170 * @param OODBBean $bean2 second bean in target association
9171 * @param boolean $fast if TRUE, removes the entries by query without FUSE
9172 *
9173 * @return void
9174 */
9175 public function unassociate( $beans1, $beans2, $fast = NULL )
9176 {
9177 $beans1 = ( !is_array( $beans1 ) ) ? array( $beans1 ) : $beans1;
9178 $beans2 = ( !is_array( $beans2 ) ) ? array( $beans2 ) : $beans2;
9179
9180 foreach ( $beans1 as $bean1 ) {
9181 foreach ( $beans2 as $bean2 ) {
9182 try {
9183 $this->oodb->store( $bean1 );
9184 $this->oodb->store( $bean2 );
9185
9186 $type1 = $bean1->getMeta( 'type' );
9187 $type2 = $bean2->getMeta( 'type' );
9188
9189 $row = $this->writer->queryRecordLink( $type1, $type2, $bean1->id, $bean2->id );
9190 $linkType = $this->getTable( array( $type1, $type2 ) );
9191
9192 if ( $fast ) {
9193 $this->writer->deleteRecord( $linkType, array( 'id' => $row['id'] ) );
9194
9195 return;
9196 }
9197
9198 $beans = $this->oodb->convertToBeans( $linkType, array( $row ) );
9199
9200 if ( count( $beans ) > 0 ) {
9201 $bean = reset( $beans );
9202 $this->oodb->trash( $bean );
9203 }
9204 } catch ( SQLException $exception ) {
9205 $this->handleException( $exception );
9206 }
9207 }
9208 }
9209 }
9210
9211 /**
9212 * Removes all relations for a bean. This method breaks every connection between
9213 * a certain bean $bean and every other bean of type $type. Warning: this method
9214 * is really fast because it uses a direct SQL query however it does not inform the
9215 * models about this. If you want to notify FUSE models about deletion use a foreach-loop
9216 * with unassociate() instead. (that might be slower though)
9217 *
9218 * @param OODBBean $bean reference bean
9219 * @param string $type type of beans that need to be unassociated
9220 *
9221 * @return void
9222 */
9223 public function clearRelations( OODBBean $bean, $type )
9224 {
9225 $this->oodb->store( $bean );
9226 try {
9227 $this->writer->deleteRelations( $bean->getMeta( 'type' ), $type, $bean->id );
9228 } catch ( SQLException $exception ) {
9229 $this->handleException( $exception );
9230 }
9231 }
9232
9233 /**
9234 * Returns all the beans associated with $bean.
9235 * This method will return an array containing all the beans that have
9236 * been associated once with the associate() function and are still
9237 * associated with the bean specified. The type parameter indicates the
9238 * type of beans you are looking for. You can also pass some extra SQL and
9239 * values for that SQL to filter your results after fetching the
9240 * related beans.
9241 *
9242 * Don't try to make use of subqueries, a subquery using IN() seems to
9243 * be slower than two queries!
9244 *
9245 * Since 3.2, you can now also pass an array of beans instead just one
9246 * bean as the first parameter.
9247 *
9248 * @param OODBBean|array $bean the bean you have
9249 * @param string $type the type of beans you want
9250 * @param string $sql SQL snippet for extra filtering
9251 * @param array $bindings values to be inserted in SQL slots
9252 *
9253 * @return array
9254 */
9255 public function related( $bean, $type, $sql = '', $bindings = array() )
9256 {
9257 $sql = $this->writer->glueSQLCondition( $sql );
9258 $rows = $this->relatedRows( $bean, $type, $sql, $bindings );
9259 $links = array();
9260
9261 foreach ( $rows as $key => $row ) {
9262 if ( !isset( $links[$row['id']] ) ) $links[$row['id']] = array();
9263 $links[$row['id']][] = $row['linked_by'];
9264 unset( $rows[$key]['linked_by'] );
9265 }
9266
9267 $beans = $this->oodb->convertToBeans( $type, $rows );
9268 foreach ( $beans as $bean ) $bean->setMeta( 'sys.belongs-to', $links[$bean->id] );
9269
9270 return $beans;
9271 }
9272}
9273}
9274
9275namespace RedBeanPHP {
9276
9277use RedBeanPHP\ToolBox as ToolBox;
9278use RedBeanPHP\OODBBean as OODBBean;
9279
9280/**
9281 * Bean Helper Interface.
9282 *
9283 * Interface for Bean Helper.
9284 * A little bolt that glues the whole machinery together.
9285 *
9286 * @file RedBeanPHP/IBeanHelper.php
9287 * @author Gabor de Mooij and the RedBeanPHP Community
9288 * @license BSD/GPLv2
9289 *
9290 * @copyright
9291 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
9292 * This source file is subject to the BSD/GPLv2 License that is bundled
9293 * with this source code in the file license.txt.
9294 */
9295interface BeanHelper
9296{
9297 /**
9298 * Returns a toolbox to empower the bean.
9299 * This allows beans to perform OODB operations by themselves,
9300 * as such the bean is a proxy for OODB. This allows beans to implement
9301 * their magic getters and setters and return lists.
9302 *
9303 * @return ToolBox
9304 */
9305 public function getToolbox();
9306
9307 /**
9308 * Does approximately the same as getToolbox but also extracts the
9309 * toolbox for you.
9310 * This method returns a list with all toolbox items in Toolbox Constructor order:
9311 * OODB, adapter, writer and finally the toolbox itself!.
9312 *
9313 * @return array
9314 */
9315 public function getExtractedToolbox();
9316
9317 /**
9318 * Given a certain bean this method will
9319 * return the corresponding model.
9320 * If no model is returned (NULL), RedBeanPHP might ask again.
9321 *
9322 * @note You can make RedBeanPHP faster by doing the setup wiring yourself.
9323 * The event listeners take time, so to speed-up RedBeanPHP you can
9324 * drop 'FUSE', if you're not interested in the Models.
9325 *
9326 * @note You can do funny stuff with this method but please be careful.
9327 * You *could* create a model depending on properties of the bean, but
9328 * it's a bit well... adventurous, here is an example:
9329 *
9330 * <code>
9331 * class Book extends RedBeanPHP\SimpleModel {};
9332 * class Booklet extends RedBeanPHP\SimpleModel {};
9333 *
9334 * class FlexBeanHelper extends RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper {
9335 * public function getModelForBean( RedBeanPHP\OODBBean $bean ) {
9336 * if (!isset($bean->pages)) return NULL; //will ask again
9337 * if ($bean->pages <= 10) return new Booklet;
9338 * return new Book;
9339 * }
9340 * }
9341 *
9342 * $h = new FlexBeanHelper;
9343 * R::getRedBean()->setBeanHelper($h);
9344 * $book = R::dispense('book');
9345 * var_dump($book->box()); //NULL cant reach model
9346 * $book->pages = 5;
9347 * var_dump($book->box()); //Booklet
9348 * $book->pages = 15;
9349 * var_dump($book->box()); //still.. Booklet, model has been set
9350 * $book2 = R::dispense('book');
9351 * $book2->pages = 15;
9352 * var_dump($book2->box()); //Book, more than 10 pages
9353 * </code>
9354 *
9355 * @param OODBBean $bean bean to obtain the corresponding model of
9356 *
9357 * @return SimpleModel|CustomModel|NULL
9358 */
9359 public function getModelForBean( OODBBean $bean );
9360}
9361}
9362
9363namespace RedBeanPHP\BeanHelper {
9364
9365use RedBeanPHP\BeanHelper as BeanHelper;
9366use RedBeanPHP\Facade as Facade;
9367use RedBeanPHP\OODBBean as OODBBean;
9368use RedBeanPHP\SimpleModelHelper as SimpleModelHelper;
9369
9370/**
9371 * Bean Helper.
9372 *
9373 * The Bean helper helps beans to access access the toolbox and
9374 * FUSE models. This Bean Helper makes use of the facade to obtain a
9375 * reference to the toolbox.
9376 *
9377 * @file RedBeanPHP/BeanHelperFacade.php
9378 * @author Gabor de Mooij and the RedBeanPHP Community
9379 * @license BSD/GPLv2
9380 *
9381 * @copyright
9382 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
9383 * This source file is subject to the BSD/GPLv2 License that is bundled
9384 * with this source code in the file license.txt.
9385 */
9386class SimpleFacadeBeanHelper implements BeanHelper
9387{
9388 /**
9389 * Factory function to create instance of Simple Model, if any.
9390 *
9391 * @var \Closure
9392 */
9393 private static $factory = null;
9394
9395 /**
9396 * Factory method using a customizable factory function to create
9397 * the instance of the Simple Model.
9398 *
9399 * @param string $modelClassName name of the class
9400 *
9401 * @return SimpleModel
9402 */
9403 public static function factory( $modelClassName )
9404 {
9405 $factory = self::$factory;
9406 return ( $factory ) ? $factory( $modelClassName ) : new $modelClassName();
9407 }
9408
9409 /**
9410 * Sets the factory function to create the model when using FUSE
9411 * to connect a bean to a model.
9412 *
9413 * @param \Closure $factory factory function
9414 *
9415 * @return void
9416 */
9417 public static function setFactoryFunction( $factory )
9418 {
9419 self::$factory = $factory;
9420 }
9421
9422 /**
9423 * @see BeanHelper::getToolbox
9424 */
9425 public function getToolbox()
9426 {
9427 return Facade::getToolBox();
9428 }
9429
9430 /**
9431 * @see BeanHelper::getModelForBean
9432 */
9433 public function getModelForBean( OODBBean $bean )
9434 {
9435 $model = $bean->getMeta( 'type' );
9436 $prefix = defined( 'REDBEAN_MODEL_PREFIX' ) ? REDBEAN_MODEL_PREFIX : '\\Model_';
9437
9438 if ( strpos( $model, '_' ) !== FALSE ) {
9439 $modelParts = explode( '_', $model );
9440 $modelName = '';
9441 foreach( $modelParts as $part ) {
9442 $modelName .= ucfirst( $part );
9443 }
9444 $modelName = $prefix . $modelName;
9445
9446 if ( !class_exists( $modelName ) ) {
9447 //second try
9448 $modelName = $prefix . ucfirst( $model );
9449
9450 if ( !class_exists( $modelName ) ) {
9451 return NULL;
9452 }
9453 }
9454
9455 } else {
9456
9457 $modelName = $prefix . ucfirst( $model );
9458 if ( !class_exists( $modelName ) ) {
9459 return NULL;
9460 }
9461 }
9462 $obj = self::factory( $modelName );
9463 $obj->loadBean( $bean );
9464
9465 return $obj;
9466 }
9467
9468 /**
9469 * @see BeanHelper::getExtractedToolbox
9470 */
9471 public function getExtractedToolbox()
9472 {
9473 return Facade::getExtractedToolbox();
9474 }
9475}
9476}
9477
9478namespace RedBeanPHP {
9479
9480use RedBeanPHP\OODBBean as OODBBean;
9481
9482/**
9483 * SimpleModel
9484 * Base Model For All RedBeanPHP Models using FUSE.
9485 *
9486 * RedBeanPHP FUSE is a mechanism to connect beans to posthoc
9487 * models. Models are connected to beans by naming conventions.
9488 * Actions on beans will result in actions on models.
9489 *
9490 * @file RedBeanPHP/SimpleModel.php
9491 * @author Gabor de Mooij and the RedBeanPHP Team
9492 * @license BSD/GPLv2
9493 *
9494 * @copyright
9495 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
9496 * This source file is subject to the BSD/GPLv2 License that is bundled
9497 * with this source code in the file license.txt.
9498 */
9499class SimpleModel
9500{
9501 /**
9502 * @var OODBBean
9503 */
9504 protected $bean;
9505
9506 /**
9507 * Used by FUSE: the ModelHelper class to connect a bean to a model.
9508 * This method loads a bean in the model.
9509 *
9510 * @param OODBBean $bean bean to load
9511 *
9512 * @return void
9513 */
9514 public function loadBean( OODBBean $bean )
9515 {
9516 $this->bean = $bean;
9517 }
9518
9519 /**
9520 * Magic Getter to make the bean properties available from
9521 * the $this-scope.
9522 *
9523 * @note this method returns a value, not a reference!
9524 * To obtain a reference unbox the bean first!
9525 *
9526 * @param string $prop property to get
9527 *
9528 * @return mixed
9529 */
9530 public function __get( $prop )
9531 {
9532 return $this->bean->$prop;
9533 }
9534
9535 /**
9536 * Magic Setter.
9537 * Sets the value directly as a bean property.
9538 *
9539 * @param string $prop property to set value of
9540 * @param mixed $value value to set
9541 *
9542 * @return void
9543 */
9544 public function __set( $prop, $value )
9545 {
9546 $this->bean->$prop = $value;
9547 }
9548
9549 /**
9550 * Isset implementation.
9551 * Implements the isset function for array-like access.
9552 *
9553 * @param string $key key to check
9554 *
9555 * @return boolean
9556 */
9557 public function __isset( $key )
9558 {
9559 return isset( $this->bean->$key );
9560 }
9561
9562 /**
9563 * Box the bean using the current model.
9564 * This method wraps the current bean in this model.
9565 * This method can be reached using FUSE through a simple
9566 * OODBBean. The method returns a RedBeanPHP Simple Model.
9567 * This is useful if you would like to rely on PHP type hinting.
9568 * You can box your beans before passing them to functions or methods
9569 * with typed parameters.
9570 *
9571 * @return SimpleModel
9572 */
9573 public function box()
9574 {
9575 return $this;
9576 }
9577
9578 /**
9579 * Unbox the bean from the model.
9580 * This method returns the bean inside the model.
9581 *
9582 * @return OODBBean
9583 */
9584 public function unbox()
9585 {
9586 return $this->bean;
9587 }
9588}
9589}
9590
9591namespace RedBeanPHP {
9592
9593use RedBeanPHP\Observer as Observer;
9594use RedBeanPHP\OODBBean as OODBBean;
9595use RedBeanPHP\Observable as Observable;
9596
9597/**
9598 * RedBean Model Helper.
9599 *
9600 * Connects beans to models.
9601 * This is the core of so-called FUSE.
9602 *
9603 * @file RedBeanPHP/ModelHelper.php
9604 * @author Gabor de Mooij and the RedBeanPHP Community
9605 * @license BSD/GPLv2
9606 *
9607 * @copyright
9608 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
9609 * This source file is subject to the BSD/GPLv2 License that is bundled
9610 * with this source code in the file license.txt.
9611 */
9612class SimpleModelHelper implements Observer
9613{
9614 /**
9615 * Gets notified by an observable.
9616 * This method decouples the FUSE system from the actual beans.
9617 * If a FUSE event happens 'update', this method will attempt to
9618 * invoke the corresponding method on the bean.
9619 *
9620 * @param string $eventName i.e. 'delete', 'after_delete'
9621 * @param OODBean $bean affected bean
9622 *
9623 * @return void
9624 */
9625 public function onEvent( $eventName, $bean )
9626 {
9627 $bean->$eventName();
9628 }
9629
9630 /**
9631 * Attaches the FUSE event listeners. Now the Model Helper will listen for
9632 * CRUD events. If a CRUD event occurs it will send a signal to the model
9633 * that belongs to the CRUD bean and this model will take over control from
9634 * there. This method will attach the following event listeners to the observable:
9635 *
9636 * - 'update' (gets called by R::store, before the records gets inserted / updated)
9637 * - 'after_update' (gets called by R::store, after the records have been inserted / updated)
9638 * - 'open' (gets called by R::load, after the record has been retrieved)
9639 * - 'delete' (gets called by R::trash, before deletion of record)
9640 * - 'after_delete' (gets called by R::trash, after deletion)
9641 * - 'dispense' (gets called by R::dispense)
9642 *
9643 * For every event type, this method will register this helper as a listener.
9644 * The observable will notify the listener (this object) with the event ID and the
9645 * affected bean. This helper will then process the event (onEvent) by invoking
9646 * the event on the bean. If a bean offers a method with the same name as the
9647 * event ID, this method will be invoked.
9648 *
9649 * @param Observable $observable object to observe
9650 *
9651 * @return void
9652 */
9653 public function attachEventListeners( Observable $observable )
9654 {
9655 foreach ( array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ) as $eventID ) {
9656 $observable->addEventListener( $eventID, $this );
9657 }
9658 }
9659}
9660}
9661
9662namespace RedBeanPHP {
9663
9664use RedBeanPHP\ToolBox as ToolBox;
9665use RedBeanPHP\AssociationManager as AssociationManager;
9666use RedBeanPHP\OODBBean as OODBBean;
9667
9668/**
9669 * RedBeanPHP Tag Manager.
9670 *
9671 * The tag manager offers an easy way to quickly implement basic tagging
9672 * functionality.
9673 *
9674 * Provides methods to tag beans and perform tag-based searches in the
9675 * bean database.
9676 *
9677 * @file RedBeanPHP/TagManager.php
9678 * @author Gabor de Mooij and the RedBeanPHP community
9679 * @license BSD/GPLv2
9680 *
9681 * @copyright
9682 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
9683 * This source file is subject to the BSD/GPLv2 License that is bundled
9684 * with this source code in the file license.txt.
9685 */
9686class TagManager
9687{
9688 /**
9689 * @var ToolBox
9690 */
9691 protected $toolbox;
9692
9693 /**
9694 * @var AssociationManager
9695 */
9696 protected $associationManager;
9697
9698 /**
9699 * @var OODBBean
9700 */
9701 protected $redbean;
9702
9703 /**
9704 * Checks if the argument is a comma separated string, in this case
9705 * it will split the string into words and return an array instead.
9706 * In case of an array the argument will be returned 'as is'.
9707 *
9708 * @param array|string $tagList list of tags
9709 *
9710 * @return array
9711 */
9712 private function extractTagsIfNeeded( $tagList )
9713 {
9714 if ( $tagList !== FALSE && !is_array( $tagList ) ) {
9715 $tags = explode( ',', (string) $tagList );
9716 } else {
9717 $tags = $tagList;
9718 }
9719
9720 return $tags;
9721 }
9722
9723 /**
9724 * Finds a tag bean by it's title.
9725 * Internal method.
9726 *
9727 * @param string $title title to search for
9728 *
9729 * @return OODBBean
9730 */
9731 protected function findTagByTitle( $title )
9732 {
9733 $beans = $this->redbean->find( 'tag', array( 'title' => array( $title ) ) );
9734
9735 if ( $beans ) {
9736 $bean = reset( $beans );
9737
9738 return $bean;
9739 }
9740
9741 return NULL;
9742 }
9743
9744 /**
9745 * Constructor.
9746 * The tag manager offers an easy way to quickly implement basic tagging
9747 * functionality.
9748 *
9749 * @param ToolBox $toolbox toolbox object
9750 */
9751 public function __construct( ToolBox $toolbox )
9752 {
9753 $this->toolbox = $toolbox;
9754 $this->redbean = $toolbox->getRedBean();
9755
9756 $this->associationManager = $this->redbean->getAssociationManager();
9757 }
9758
9759 /**
9760 * Tests whether a bean has been associated with one ore more
9761 * of the listed tags. If the third parameter is TRUE this method
9762 * will return TRUE only if all tags that have been specified are indeed
9763 * associated with the given bean, otherwise FALSE.
9764 * If the third parameter is FALSE this
9765 * method will return TRUE if one of the tags matches, FALSE if none
9766 * match.
9767 *
9768 * Tag list can be either an array with tag names or a comma separated list
9769 * of tag names.
9770 *
9771 * @param OODBBean $bean bean to check for tags
9772 * @param array|string $tags list of tags
9773 * @param boolean $all whether they must all match or just some
9774 *
9775 * @return boolean
9776 */
9777 public function hasTag( $bean, $tags, $all = FALSE )
9778 {
9779 $foundtags = $this->tag( $bean );
9780
9781 $tags = $this->extractTagsIfNeeded( $tags );
9782 $same = array_intersect( $tags, $foundtags );
9783
9784 if ( $all ) {
9785 return ( implode( ',', $same ) === implode( ',', $tags ) );
9786 }
9787
9788 return (bool) ( count( $same ) > 0 );
9789 }
9790
9791 /**
9792 * Removes all sepcified tags from the bean. The tags specified in
9793 * the second parameter will no longer be associated with the bean.
9794 *
9795 * Tag list can be either an array with tag names or a comma separated list
9796 * of tag names.
9797 *
9798 * @param OODBBean $bean tagged bean
9799 * @param array|string $tagList list of tags (names)
9800 *
9801 * @return void
9802 */
9803 public function untag( $bean, $tagList )
9804 {
9805 $tags = $this->extractTagsIfNeeded( $tagList );
9806
9807 foreach ( $tags as $tag ) {
9808 if ( $t = $this->findTagByTitle( $tag ) ) {
9809 $this->associationManager->unassociate( $bean, $t );
9810 }
9811 }
9812 }
9813
9814 /**
9815 * Tags a bean or returns tags associated with a bean.
9816 * If $tagList is NULL or omitted this method will return a
9817 * comma separated list of tags associated with the bean provided.
9818 * If $tagList is a comma separated list (string) of tags all tags will
9819 * be associated with the bean.
9820 * You may also pass an array instead of a string.
9821 *
9822 * Tag list can be either an array with tag names or a comma separated list
9823 * of tag names.
9824 *
9825 * @param OODBBean $bean bean to be tagged
9826 * @param array|string $tagList a list of tags
9827 *
9828 * @return array
9829 */
9830 public function tag( OODBBean $bean, $tagList = NULL )
9831 {
9832 if ( is_null( $tagList ) ) {
9833
9834 $tags = $bean->sharedTag;
9835 $foundTags = array();
9836
9837 foreach ( $tags as $tag ) {
9838 $foundTags[] = $tag->title;
9839 }
9840
9841 return $foundTags;
9842 }
9843
9844 $this->associationManager->clearRelations( $bean, 'tag' );
9845 $this->addTags( $bean, $tagList );
9846
9847 return $tagList;
9848 }
9849
9850 /**
9851 * Adds tags to a bean.
9852 * If $tagList is a comma separated list of tags all tags will
9853 * be associated with the bean.
9854 * You may also pass an array instead of a string.
9855 *
9856 * Tag list can be either an array with tag names or a comma separated list
9857 * of tag names.
9858 *
9859 * @param OODBBean $bean bean to add tags to
9860 * @param array|string $tagList list of tags to add to bean
9861 *
9862 * @return void
9863 */
9864 public function addTags( OODBBean $bean, $tagList )
9865 {
9866 $tags = $this->extractTagsIfNeeded( $tagList );
9867
9868 if ( $tagList === FALSE ) {
9869 return;
9870 }
9871
9872 foreach ( $tags as $tag ) {
9873 if ( !$t = $this->findTagByTitle( $tag ) ) {
9874 $t = $this->redbean->dispense( 'tag' );
9875 $t->title = $tag;
9876
9877 $this->redbean->store( $t );
9878 }
9879
9880 $this->associationManager->associate( $bean, $t );
9881 }
9882 }
9883
9884 /**
9885 * Returns all beans that have been tagged with one or more
9886 * of the specified tags.
9887 *
9888 * Tag list can be either an array with tag names or a comma separated list
9889 * of tag names.
9890 *
9891 * @param string $beanType type of bean you are looking for
9892 * @param array|string $tagList list of tags to match
9893 * @param string $sql additional SQL (use only for pagination)
9894 * @param array $bindings bindings
9895 *
9896 * @return array
9897 */
9898 public function tagged( $beanType, $tagList, $sql = '', $bindings = array() )
9899 {
9900 $tags = $this->extractTagsIfNeeded( $tagList );
9901 $records = $this->toolbox->getWriter()->queryTagged( $beanType, $tags, FALSE, $sql, $bindings );
9902
9903 return $this->redbean->convertToBeans( $beanType, $records );
9904 }
9905
9906 /**
9907 * Returns all beans that have been tagged with ALL of the tags given.
9908 *
9909 * Tag list can be either an array with tag names or a comma separated list
9910 * of tag names.
9911 *
9912 * @param string $beanType type of bean you are looking for
9913 * @param array|string $tagList list of tags to match
9914 *
9915 * @return array
9916 */
9917 public function taggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
9918 {
9919 $tags = $this->extractTagsIfNeeded( $tagList );
9920 $records = $this->toolbox->getWriter()->queryTagged( $beanType, $tags, TRUE, $sql, $bindings );
9921
9922 return $this->redbean->convertToBeans( $beanType, $records );
9923 }
9924}
9925}
9926
9927namespace RedBeanPHP {
9928
9929use RedBeanPHP\ToolBox as ToolBox;
9930use RedBeanPHP\OODBBean as OODBBean;
9931
9932/**
9933 * Label Maker.
9934 * Makes so-called label beans.
9935 * A label is a bean with only an id, type and name property.
9936 * Labels can be used to create simple entities like categories, tags or enums.
9937 * This service class provides convenience methods to deal with this kind of
9938 * beans.
9939 *
9940 * @file RedBeanPHP/LabelMaker.php
9941 * @author Gabor de Mooij and the RedBeanPHP Community
9942 * @license BSD/GPLv2
9943 *
9944 * @copyright
9945 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
9946 * This source file is subject to the BSD/GPLv2 License that is bundled
9947 * with this source code in the file license.txt.
9948 */
9949class LabelMaker
9950{
9951 /**
9952 * @var ToolBox
9953 */
9954 protected $toolbox;
9955
9956 /**
9957 * Constructor.
9958 *
9959 * @param ToolBox $toolbox
9960 */
9961 public function __construct( ToolBox $toolbox )
9962 {
9963 $this->toolbox = $toolbox;
9964 }
9965
9966 /**
9967 * A label is a bean with only an id, type and name property.
9968 * This function will dispense beans for all entries in the array. The
9969 * values of the array will be assigned to the name property of each
9970 * individual bean.
9971 *
9972 * <code>
9973 * $people = R::dispenseLabels( 'person', [ 'Santa', 'Claus' ] );
9974 * </code>
9975 *
9976 * @param string $type type of beans you would like to have
9977 * @param array $labels list of labels, names for each bean
9978 *
9979 * @return array
9980 */
9981 public function dispenseLabels( $type, $labels )
9982 {
9983 $labelBeans = array();
9984 foreach ( $labels as $label ) {
9985 $labelBean = $this->toolbox->getRedBean()->dispense( $type );
9986 $labelBean->name = $label;
9987 $labelBeans[] = $labelBean;
9988 }
9989
9990 return $labelBeans;
9991 }
9992
9993 /**
9994 * Gathers labels from beans. This function loops through the beans,
9995 * collects the value of the name property for each individual bean
9996 * and stores the names in a new array. The array then gets sorted using the
9997 * default sort function of PHP (sort).
9998 *
9999 * Usage:
10000 *
10001 * <code>
10002 * $o1->name = 'hamburger';
10003 * $o2->name = 'pizza';
10004 * implode( ',', R::gatherLabels( [ $o1, $o2 ] ) ); //hamburger,pizza
10005 * </code>
10006 *
10007 * Note that the return value is an array of strings, not beans.
10008 *
10009 * @param array $beans list of beans to loop through
10010 *
10011 * @return array
10012 */
10013 public function gatherLabels( $beans )
10014 {
10015 $labels = array();
10016
10017 foreach ( $beans as $bean ) {
10018 $labels[] = $bean->name;
10019 }
10020
10021 sort( $labels );
10022
10023 return $labels;
10024 }
10025
10026 /**
10027 * Fetches an ENUM from the database and creates it if necessary.
10028 * An ENUM has the following format:
10029 *
10030 * <code>
10031 * ENUM:VALUE
10032 * </code>
10033 *
10034 * If you pass 'ENUM' only, this method will return an array of its
10035 * values:
10036 *
10037 * <code>
10038 * implode( ',', R::gatherLabels( R::enum( 'flavour' ) ) ) //'BANANA,MOCCA'
10039 * </code>
10040 *
10041 * If you pass 'ENUM:VALUE' this method will return the specified enum bean
10042 * and create it in the database if it does not exist yet:
10043 *
10044 * <code>
10045 * $bananaFlavour = R::enum( 'flavour:banana' );
10046 * $bananaFlavour->name;
10047 * </code>
10048 *
10049 * So you can use this method to set an ENUM value in a bean:
10050 *
10051 * <code>
10052 * $shake->flavour = R::enum( 'flavour:banana' );
10053 * </code>
10054 *
10055 * the property flavour now contains the enum bean, a parent bean.
10056 * In the database, flavour_id will point to the flavour record with name 'banana'.
10057 *
10058 * @param string $enum ENUM specification for label
10059 *
10060 * @return array|OODBBean
10061 */
10062 public function enum( $enum )
10063 {
10064 $oodb = $this->toolbox->getRedBean();
10065
10066 if ( strpos( $enum, ':' ) === FALSE ) {
10067 $type = $enum;
10068 $value = FALSE;
10069 } else {
10070 list( $type, $value ) = explode( ':', $enum );
10071 $value = preg_replace( '/\W+/', '_', strtoupper( trim( $value ) ) );
10072 }
10073
10074 /**
10075 * We use simply find here, we could use inspect() in fluid mode etc,
10076 * but this would be useless. At first sight it looks clean, you could even
10077 * bake this into find(), however, find not only has to deal with the primary
10078 * search type, people can also include references in the SQL part, so avoiding
10079 * find failures does not matter, this is still the quickest way making use
10080 * of existing functionality.
10081 *
10082 * @note There seems to be a bug in XDebug v2.3.2 causing suppressed
10083 * exceptions like these to surface anyway, to prevent this use:
10084 *
10085 * "xdebug.default_enable = 0"
10086 *
10087 * Also see Github Issue #464
10088 */
10089 $values = $oodb->find( $type );
10090
10091 if ( $value === FALSE ) {
10092 return $values;
10093 }
10094
10095 foreach( $values as $enumItem ) {
10096 if ( $enumItem->name === $value ) return $enumItem;
10097 }
10098
10099 $newEnumItems = $this->dispenseLabels( $type, array( $value ) );
10100 $newEnumItem = reset( $newEnumItems );
10101
10102 $oodb->store( $newEnumItem );
10103
10104 return $newEnumItem;
10105 }
10106}
10107}
10108
10109namespace RedBeanPHP {
10110
10111use RedBeanPHP\QueryWriter as QueryWriter;
10112use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
10113use RedBeanPHP\RedException\SQL as SQLException;
10114use RedBeanPHP\Logger as Logger;
10115use RedBeanPHP\Logger\RDefault as RDefault;
10116use RedBeanPHP\Logger\RDefault\Debug as Debug;
10117use RedBeanPHP\Adapter as Adapter;
10118use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
10119use RedBeanPHP\RedException as RedException;
10120use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
10121use RedBeanPHP\Driver\RPDO as RPDO;
10122use RedBeanPHP\Util\MultiLoader as MultiLoader;
10123use RedBeanPHP\Util\Transaction as Transaction;
10124use RedBeanPHP\Util\Dump as Dump;
10125use RedBeanPHP\Util\DispenseHelper as DispenseHelper;
10126use RedBeanPHP\Util\ArrayTool as ArrayTool;
10127
10128/**
10129 * RedBean Facade
10130 *
10131 * Version Information
10132 * RedBean Version @version 4.3
10133 *
10134 * This class hides the object landscape of
10135 * RedBeanPHP behind a single letter class providing
10136 * almost all functionality with simple static calls.
10137 *
10138 * @file RedBeanPHP/Facade.php
10139 * @author Gabor de Mooij and the RedBeanPHP Community
10140 * @license BSD/GPLv2
10141 *
10142 * @copyright
10143 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
10144 * This source file is subject to the BSD/GPLv2 License that is bundled
10145 * with this source code in the file license.txt.
10146 */
10147class Facade
10148{
10149 /**
10150 * RedBeanPHP version constant.
10151 */
10152 const C_REDBEANPHP_VERSION = '4.3';
10153
10154 /**
10155 * @var ToolBox
10156 */
10157 public static $toolbox;
10158
10159 /**
10160 * @var OODB
10161 */
10162 private static $redbean;
10163
10164 /**
10165 * @var QueryWriter
10166 */
10167 private static $writer;
10168
10169 /**
10170 * @var DBAdapter
10171 */
10172 private static $adapter;
10173
10174 /**
10175 * @var AssociationManager
10176 */
10177 private static $associationManager;
10178
10179 /**
10180 * @var TagManager
10181 */
10182 private static $tagManager;
10183
10184 /**
10185 * @var DuplicationManager
10186 */
10187 private static $duplicationManager;
10188
10189 /**
10190 * @var LabelMaker
10191 */
10192 private static $labelMaker;
10193
10194 /**
10195 * @var Finder
10196 */
10197 private static $finder;
10198
10199 /**
10200 * @var Logger
10201 */
10202 private static $logger;
10203
10204 /**
10205 * @var array
10206 */
10207 private static $plugins = array();
10208
10209 /**
10210 * @var string
10211 */
10212 private static $exportCaseStyle = 'default';
10213
10214 /**
10215 * Not in use (backward compatibility SQLHelper)
10216 */
10217 public static $f;
10218
10219 /**
10220 * @var string
10221 */
10222 public static $currentDB = '';
10223
10224 /**
10225 * @var array
10226 */
10227 public static $toolboxes = array();
10228
10229 /**
10230 * Internal Query function, executes the desired query. Used by
10231 * all facade query functions. This keeps things DRY.
10232 *
10233 * @param string $method desired query method (i.e. 'cell', 'col', 'exec' etc..)
10234 * @param string $sql the sql you want to execute
10235 * @param array $bindings array of values to be bound to query statement
10236 *
10237 * @return array
10238 */
10239 private static function query( $method, $sql, $bindings )
10240 {
10241 if ( !self::$redbean->isFrozen() ) {
10242 try {
10243 $rs = Facade::$adapter->$method( $sql, $bindings );
10244 } catch ( SQLException $exception ) {
10245 if ( self::$writer->sqlStateIn( $exception->getSQLState(),
10246 array(
10247 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
10248 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
10249 )
10250 ) {
10251 return ( $method === 'getCell' ) ? NULL : array();
10252 } else {
10253 throw $exception;
10254 }
10255 }
10256
10257 return $rs;
10258 } else {
10259 return Facade::$adapter->$method( $sql, $bindings );
10260 }
10261 }
10262
10263 /**
10264 * Returns the RedBeanPHP version string.
10265 * The RedBeanPHP version string always has the same format "X.Y"
10266 * where X is the major version number and Y is the minor version number.
10267 * Point releases are not mentioned in the version string.
10268 *
10269 * @return string
10270 */
10271 public static function getVersion()
10272 {
10273 return self::C_REDBEANPHP_VERSION;
10274 }
10275
10276 /**
10277 * Tests the connection.
10278 * Returns TRUE if connection has been established and
10279 * FALSE otherwise.
10280 *
10281 * @return boolean
10282 */
10283 public static function testConnection()
10284 {
10285 if ( !isset( self::$adapter ) ) return FALSE;
10286
10287 $database = self::$adapter->getDatabase();
10288 try {
10289 @$database->connect();
10290 } catch ( \Exception $e ) {}
10291 return $database->isConnected();
10292 }
10293
10294 /**
10295 * Kickstarts redbean for you. This method should be called before you start using
10296 * RedBean. The Setup() method can be called without any arguments, in this case it will
10297 * try to create a SQLite database in /tmp called red.db (this only works on UNIX-like systems).
10298 *
10299 * @param string $dsn Database connection string
10300 * @param string $username Username for database
10301 * @param string $password Password for database
10302 * @param boolean $frozen TRUE if you want to setup in frozen mode
10303 *
10304 * @return ToolBox
10305 */
10306 public static function setup( $dsn = NULL, $username = NULL, $password = NULL, $frozen = FALSE )
10307 {
10308 if ( is_null( $dsn ) ) {
10309 $dsn = 'sqlite:/' . sys_get_temp_dir() . '/red.db';
10310 }
10311
10312 self::addDatabase( 'default', $dsn, $username, $password, $frozen );
10313 self::selectDatabase( 'default' );
10314
10315 return self::$toolbox;
10316 }
10317
10318 /**
10319 * Toggles Narrow Field Mode.
10320 * See documentation in QueryWriter.
10321 *
10322 * @param boolean $mode TRUE = Narrow Field Mode
10323 *
10324 * @return void
10325 */
10326 public static function setNarrowFieldMode( $mode )
10327 {
10328 AQueryWriter::setNarrowFieldMode( $mode );
10329 }
10330
10331 /**
10332 * Wraps a transaction around a closure or string callback.
10333 * If an Exception is thrown inside, the operation is automatically rolled back.
10334 * If no Exception happens, it commits automatically.
10335 * It also supports (simulated) nested transactions (that is useful when
10336 * you have many methods that needs transactions but are unaware of
10337 * each other).
10338 *
10339 * Example:
10340 *
10341 * <code>
10342 * $from = 1;
10343 * $to = 2;
10344 * $amount = 300;
10345 *
10346 * R::transaction(function() use($from, $to, $amount)
10347 * {
10348 * $accountFrom = R::load('account', $from);
10349 * $accountTo = R::load('account', $to);
10350 * $accountFrom->money -= $amount;
10351 * $accountTo->money += $amount;
10352 * R::store($accountFrom);
10353 * R::store($accountTo);
10354 * });
10355 * </code>
10356 *
10357 * @param callable $callback Closure (or other callable) with the transaction logic
10358 *
10359 * @return mixed
10360 */
10361 public static function transaction( $callback )
10362 {
10363 return Transaction::transaction( self::$adapter, $callback );
10364 }
10365
10366 /**
10367 * Adds a database to the facade, afterwards you can select the database using
10368 * selectDatabase($key), where $key is the name you assigned to this database.
10369 *
10370 * Usage:
10371 *
10372 * <code>
10373 * R::addDatabase( 'database-1', 'sqlite:/tmp/db1.txt' );
10374 * R::selectDatabase( 'database-1' ); //to select database again
10375 * </code>
10376 *
10377 * This method allows you to dynamically add (and select) new databases
10378 * to the facade. Adding a database with the same key will cause an exception.
10379 *
10380 * @param string $key ID for the database
10381 * @param string $dsn DSN for the database
10382 * @param string $user user for connection
10383 * @param NULL|string $pass password for connection
10384 * @param bool $frozen whether this database is frozen or not
10385 *
10386 * @return void
10387 */
10388 public static function addDatabase( $key, $dsn, $user = NULL, $pass = NULL, $frozen = FALSE )
10389 {
10390 if ( isset( self::$toolboxes[$key] ) ) {
10391 throw new RedException( 'A database has already been specified for this key.' );
10392 }
10393
10394 if ( is_object($dsn) ) {
10395 $db = new RPDO( $dsn );
10396 $dbType = $db->getDatabaseType();
10397 } else {
10398 $db = new RPDO( $dsn, $user, $pass, TRUE );
10399 $dbType = substr( $dsn, 0, strpos( $dsn, ':' ) );
10400 }
10401
10402 $adapter = new DBAdapter( $db );
10403
10404 $writers = array(
10405 'pgsql' => 'PostgreSQL',
10406 'sqlite' => 'SQLiteT',
10407 'cubrid' => 'CUBRID',
10408 'mysql' => 'MySQL',
10409 'sqlsrv' => 'SQLServer',
10410 );
10411
10412 $wkey = trim( strtolower( $dbType ) );
10413 if ( !isset( $writers[$wkey] ) ) {
10414 $wkey = preg_replace( '/\W/', '' , $wkey );
10415 throw new RedException( 'Unsupported database ('.$wkey.').' );
10416 }
10417 $writerClass = '\\RedBeanPHP\\QueryWriter\\'.$writers[$wkey];
10418 $writer = new $writerClass( $adapter );
10419 $redbean = new OODB( $writer, $frozen );
10420
10421 self::$toolboxes[$key] = new ToolBox( $redbean, $adapter, $writer );
10422 }
10423
10424 /**
10425 * Determines whether a database identified with the specified key has
10426 * already been added to the facade. This function will return TRUE
10427 * if the database indicated by the key is available and FALSE otherwise.
10428 *
10429 * @param string $key the key/name of the database to check for
10430 *
10431 * @return boolean
10432 */
10433 public static function hasDatabase( $key )
10434 {
10435 return ( isset( self::$toolboxes[$key] ) );
10436 }
10437
10438 /**
10439 * Selects a different database for the Facade to work with.
10440 * If you use the R::setup() you don't need this method. This method is meant
10441 * for multiple database setups. This method selects the database identified by the
10442 * database ID ($key). Use addDatabase() to add a new database, which in turn
10443 * can be selected using selectDatabase(). If you use R::setup(), the resulting
10444 * database will be stored under key 'default', to switch (back) to this database
10445 * use R::selectDatabase( 'default' ). This method returns TRUE if the database has been
10446 * switched and FALSE otherwise (for instance if you already using the specified database).
10447 *
10448 * @param string $key Key of the database to select
10449 *
10450 * @return boolean
10451 */
10452 public static function selectDatabase( $key )
10453 {
10454 if ( self::$currentDB === $key ) {
10455 return FALSE;
10456 }
10457
10458 if ( !isset( self::$toolboxes[$key] ) ) {
10459 throw new RedException( 'Database not found in registry. Add database using R::addDatabase().' );
10460 }
10461
10462 self::configureFacadeWithToolbox( self::$toolboxes[$key] );
10463 self::$currentDB = $key;
10464
10465 return TRUE;
10466 }
10467
10468 /**
10469 * Toggles DEBUG mode.
10470 * In Debug mode all SQL that happens under the hood will
10471 * be printed to the screen and/or logged.
10472 * If no database connection has been configured using R::setup() or
10473 * R::selectDatabase() this method will throw an exception.
10474 *
10475 * There are 2 debug styles:
10476 *
10477 * Classic: separate parameter bindings, explicit and complete but less readable
10478 * Fancy: interpersed bindings, truncates large strings, highlighted schema changes
10479 *
10480 * Fancy style is more readable but sometimes incomplete.
10481 *
10482 * The first parameter turns debugging ON or OFF.
10483 * The second parameter indicates the mode of operation:
10484 *
10485 * 0 Log and write to STDOUT classic style (default)
10486 * 1 Log only, class style
10487 * 2 Log and write to STDOUT fancy style
10488 * 3 Log only, fancy style
10489 *
10490 * This function always returns the logger instance created to generate the
10491 * debug messages.
10492 *
10493 * @param boolean $tf debug mode (TRUE or FALSE)
10494 * @param integer $mode mode of operation
10495 *
10496 * @return RDefault
10497 * @throws RedException
10498 */
10499 public static function debug( $tf = TRUE, $mode = 0 )
10500 {
10501 if ($mode > 1) {
10502 $mode -= 2;
10503 $logger = new Debug;
10504 } else {
10505 $logger = new RDefault;
10506 }
10507
10508 if ( !isset( self::$adapter ) ) {
10509 throw new RedException( 'Use R::setup() first.' );
10510 }
10511 $logger->setMode($mode);
10512 self::$adapter->getDatabase()->setDebugMode( $tf, $logger );
10513
10514 return $logger;
10515 }
10516
10517 /**
10518 * Turns on the fancy debugger.
10519 * In 'fancy' mode the debugger will output queries with bound
10520 * parameters inside the SQL itself. This method has been added to
10521 * offer a convenient way to activate the fancy debugger system
10522 * in one call.
10523 *
10524 * @param boolean $toggle TRUE to activate debugger and select 'fancy' mode
10525 *
10526 * @return void
10527 */
10528 public static function fancyDebug( $toggle = TRUE )
10529 {
10530 self::debug( $toggle, 2 );
10531 }
10532
10533 /**
10534 * Inspects the database schema. If you pass the type of a bean this
10535 * method will return the fields of its table in the database.
10536 * The keys of this array will be the field names and the values will be
10537 * the column types used to store their values.
10538 * If no type is passed, this method returns a list of all tables in the database.
10539 *
10540 * @param string $type Type of bean (i.e. table) you want to inspect
10541 *
10542 * @return array
10543 */
10544 public static function inspect( $type = NULL )
10545 {
10546 return ($type === NULL) ? self::$writer->getTables() : self::$writer->getColumns( $type );
10547 }
10548
10549 /**
10550 * Stores a bean in the database. This method takes a
10551 * OODBBean Bean Object $bean and stores it
10552 * in the database. If the database schema is not compatible
10553 * with this bean and RedBean runs in fluid mode the schema
10554 * will be altered to store the bean correctly.
10555 * If the database schema is not compatible with this bean and
10556 * RedBean runs in frozen mode it will throw an exception.
10557 * This function returns the primary key ID of the inserted
10558 * bean.
10559 *
10560 * The return value is an integer if possible. If it is not possible to
10561 * represent the value as an integer a string will be returned.
10562 *
10563 * @param OODBBean|SimpleModel $bean bean to store
10564 *
10565 * @return integer|string
10566 */
10567 public static function store( $bean )
10568 {
10569 return self::$redbean->store( $bean );
10570 }
10571
10572 /**
10573 * Toggles fluid or frozen mode. In fluid mode the database
10574 * structure is adjusted to accomodate your objects. In frozen mode
10575 * this is not the case.
10576 *
10577 * You can also pass an array containing a selection of frozen types.
10578 * Let's call this chilly mode, it's just like fluid mode except that
10579 * certain types (i.e. tables) aren't touched.
10580 *
10581 * @param boolean|array $trueFalse
10582 */
10583 public static function freeze( $tf = TRUE )
10584 {
10585 self::$redbean->freeze( $tf );
10586 }
10587
10588 /**
10589 * Loads multiple types of beans with the same ID.
10590 * This might look like a strange method, however it can be useful
10591 * for loading a one-to-one relation.
10592 *
10593 * Usage:
10594 * list( $author, $bio ) = R::loadMulti( 'author, bio', $id );
10595 *
10596 * @param string|array $types the set of types to load at once
10597 * @param mixed $id the common ID
10598 *
10599 * @return OODBBean
10600 */
10601 public static function loadMulti( $types, $id )
10602 {
10603 return MultiLoader::load( self::$redbean, $types, $id );
10604 }
10605
10606 /**
10607 * Loads a bean from the object database.
10608 * It searches for a OODBBean Bean Object in the
10609 * database. It does not matter how this bean has been stored.
10610 * RedBean uses the primary key ID $id and the string $type
10611 * to find the bean. The $type specifies what kind of bean you
10612 * are looking for; this is the same type as used with the
10613 * dispense() function. If RedBean finds the bean it will return
10614 * the OODB Bean object; if it cannot find the bean
10615 * RedBean will return a new bean of type $type and with
10616 * primary key ID 0. In the latter case it acts basically the
10617 * same as dispense().
10618 *
10619 * Important note:
10620 * If the bean cannot be found in the database a new bean of
10621 * the specified type will be generated and returned.
10622 *
10623 * @param string $type type of bean you want to load
10624 * @param integer $id ID of the bean you want to load
10625 *
10626 * @return OODBBean
10627 */
10628 public static function load( $type, $id )
10629 {
10630 return self::$redbean->load( $type, $id );
10631 }
10632
10633 /**
10634 * Removes a bean from the database.
10635 * This function will remove the specified OODBBean
10636 * Bean Object from the database.
10637 *
10638 * This facade method also accepts a type-id combination,
10639 * in the latter case this method will attempt to load the specified bean
10640 * and THEN trash it.
10641 *
10642 * @param string|OODBBean|SimpleModel $bean bean you want to remove from database
10643 * @param integer $id ID if the bean to trash (optional, type-id variant only)
10644 *
10645 * @return void
10646 */
10647 public static function trash( $beanOrType, $id = NULL )
10648 {
10649 if ( is_string( $beanOrType ) ) return self::trash( self::load( $beanOrType, $id ) );
10650 return self::$redbean->trash( $beanOrType );
10651 }
10652
10653 /**
10654 * Dispenses a new RedBean OODB Bean for use with
10655 * the rest of the methods.
10656 *
10657 * @param string|array $typeOrBeanArray type or bean array to import
10658 * @param integer $number number of beans to dispense
10659 * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
10660 *
10661 * @return array|OODBBean
10662 */
10663 public static function dispense( $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE )
10664 {
10665 return DispenseHelper::dispense( self::$redbean, $typeOrBeanArray, $num, $alwaysReturnArray );
10666 }
10667
10668 /**
10669 * Takes a comma separated list of bean types
10670 * and dispenses these beans. For each type in the list
10671 * you can specify the number of beans to be dispensed.
10672 *
10673 * Usage:
10674 *
10675 * <code>
10676 * list( $book, $page, $text ) = R::dispenseAll( 'book,page,text' );
10677 * </code>
10678 *
10679 * This will dispense a book, a page and a text. This way you can
10680 * quickly dispense beans of various types in just one line of code.
10681 *
10682 * Usage:
10683 *
10684 * <code>
10685 * list($book, $pages) = R::dispenseAll('book,page*100');
10686 * </code>
10687 *
10688 * This returns an array with a book bean and then another array
10689 * containing 100 page beans.
10690 *
10691 * @param string $order a description of the desired dispense order using the syntax above
10692 * @param boolean $onlyArrays return only arrays even if amount < 2
10693 *
10694 * @return array
10695 */
10696 public static function dispenseAll( $order, $onlyArrays = FALSE )
10697 {
10698 return DispenseHelper::dispenseAll( self::$redbean, $order, $onlyArrays );
10699 }
10700
10701 /**
10702 * Convience method. Tries to find beans of a certain type,
10703 * if no beans are found, it dispenses a bean of that type.
10704 *
10705 * @param string $type type of bean you are looking for
10706 * @param string $sql SQL code for finding the bean
10707 * @param array $bindings parameters to bind to SQL
10708 *
10709 * @return array
10710 */
10711 public static function findOrDispense( $type, $sql = NULL, $bindings = array() )
10712 {
10713 return self::$finder->findOrDispense( $type, $sql, $bindings );
10714 }
10715
10716 /**
10717 * Finds a bean using a type and a where clause (SQL).
10718 * As with most Query tools in RedBean you can provide values to
10719 * be inserted in the SQL statement by populating the value
10720 * array parameter; you can either use the question mark notation
10721 * or the slot-notation (:keyname).
10722 *
10723 * @param string $type the type of bean you are looking for
10724 * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
10725 * @param array $bindings array of values to be bound to parameters in query
10726 *
10727 * @return array
10728 */
10729 public static function find( $type, $sql = NULL, $bindings = array() )
10730 {
10731 return self::$finder->find( $type, $sql, $bindings );
10732 }
10733
10734 /**
10735 * @see Facade::find
10736 * The findAll() method differs from the find() method in that it does
10737 * not assume a WHERE-clause, so this is valid:
10738 *
10739 * R::findAll('person',' ORDER BY name DESC ');
10740 *
10741 * Your SQL does not have to start with a valid WHERE-clause condition.
10742 *
10743 * @param string $type the type of bean you are looking for
10744 * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
10745 * @param array $bindings array of values to be bound to parameters in query
10746 *
10747 * @return array
10748 */
10749 public static function findAll( $type, $sql = NULL, $bindings = array() )
10750 {
10751 return self::$finder->find( $type, $sql, $bindings );
10752 }
10753
10754 /**
10755 * @see Facade::find
10756 * The variation also exports the beans (i.e. it returns arrays).
10757 *
10758 * @param string $type the type of bean you are looking for
10759 * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
10760 * @param array $bindings array of values to be bound to parameters in query
10761 *
10762 * @return array
10763 */
10764 public static function findAndExport( $type, $sql = NULL, $bindings = array() )
10765 {
10766 return self::$finder->findAndExport( $type, $sql, $bindings );
10767 }
10768
10769 /**
10770 * @see Facade::find
10771 * This variation returns the first bean only.
10772 *
10773 * @param string $type the type of bean you are looking for
10774 * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
10775 * @param array $bindings array of values to be bound to parameters in query
10776 *
10777 * @return OODBBean
10778 */
10779 public static function findOne( $type, $sql = NULL, $bindings = array() )
10780 {
10781 return self::$finder->findOne( $type, $sql, $bindings );
10782 }
10783
10784 /**
10785 * @see Facade::find
10786 * This variation returns the last bean only.
10787 *
10788 * @param string $type the type of bean you are looking for
10789 * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
10790 * @param array $bindings array of values to be bound to parameters in query
10791 *
10792 * @return OODBBean
10793 */
10794 public static function findLast( $type, $sql = NULL, $bindings = array() )
10795 {
10796 return self::$finder->findLast( $type, $sql, $bindings );
10797 }
10798
10799 /**
10800 * Finds a bean collection.
10801 * Use this for large datasets.
10802 *
10803 * @param string $type the type of bean you are looking for
10804 * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
10805 * @param array $bindings array of values to be bound to parameters in query
10806 *
10807 * @return BeanCollection
10808 */
10809 public static function findCollection( $type, $sql = NULL, $bindings = array() )
10810 {
10811 return self::$finder->findCollection( $type, $sql, $bindings );
10812 }
10813
10814 /**
10815 * Finds multiple types of beans at once and offers additional
10816 * remapping functionality. This is a very powerful yet complex function.
10817 * For details see Finder::findMulti().
10818 *
10819 * @see Finder::findMulti()
10820 *
10821 * @param array|string $types a list of bean types to find
10822 * @param string|array $sqlOrArr SQL query string or result set array
10823 * @param array $bindings SQL bindings
10824 * @param array $remappings an array of remapping arrays containing closures
10825 *
10826 * @return array
10827 */
10828 public static function findMulti( $types, $sql, $bindings = array(), $remappings = array() )
10829 {
10830 return self::$finder->findMulti( $types, $sql, $bindings, $remappings );
10831 }
10832
10833 /**
10834 * Returns an array of beans. Pass a type and a series of ids and
10835 * this method will bring you the corresponding beans.
10836 *
10837 * important note: Because this method loads beans using the load()
10838 * function (but faster) it will return empty beans with ID 0 for
10839 * every bean that could not be located. The resulting beans will have the
10840 * passed IDs as their keys.
10841 *
10842 * @param string $type type of beans
10843 * @param array $ids ids to load
10844 *
10845 * @return array
10846 */
10847 public static function batch( $type, $ids )
10848 {
10849 return self::$redbean->batch( $type, $ids );
10850 }
10851
10852 /**
10853 * @see Facade::batch
10854 *
10855 * Alias for batch(). Batch method is older but since we added so-called *All
10856 * methods like storeAll, trashAll, dispenseAll and findAll it seemed logical to
10857 * improve the consistency of the Facade API and also add an alias for batch() called
10858 * loadAll.
10859 *
10860 * @param string $type type of beans
10861 * @param array $ids ids to load
10862 *
10863 * @return array
10864 */
10865 public static function loadAll( $type, $ids )
10866 {
10867 return self::$redbean->batch( $type, $ids );
10868 }
10869
10870 /**
10871 * Convenience function to execute Queries directly.
10872 * Executes SQL.
10873 *
10874 * @param string $sql SQL query to execute
10875 * @param array $bindings a list of values to be bound to query parameters
10876 *
10877 * @return integer
10878 */
10879 public static function exec( $sql, $bindings = array() )
10880 {
10881 return self::query( 'exec', $sql, $bindings );
10882 }
10883
10884 /**
10885 * Convenience function to execute Queries directly.
10886 * Executes SQL.
10887 *
10888 * @param string $sql SQL query to execute
10889 * @param array $bindings a list of values to be bound to query parameters
10890 *
10891 * @return array
10892 */
10893 public static function getAll( $sql, $bindings = array() )
10894 {
10895 return self::query( 'get', $sql, $bindings );
10896 }
10897
10898 /**
10899 * Convenience function to execute Queries directly.
10900 * Executes SQL.
10901 *
10902 * @param string $sql SQL query to execute
10903 * @param array $bindings a list of values to be bound to query parameters
10904 *
10905 * @return string
10906 */
10907 public static function getCell( $sql, $bindings = array() )
10908 {
10909 return self::query( 'getCell', $sql, $bindings );
10910 }
10911
10912 /**
10913 * Convenience function to execute Queries directly.
10914 * Executes SQL.
10915 *
10916 * @param string $sql SQL query to execute
10917 * @param array $bindings a list of values to be bound to query parameters
10918 *
10919 * @return array
10920 */
10921 public static function getRow( $sql, $bindings = array() )
10922 {
10923 return self::query( 'getRow', $sql, $bindings );
10924 }
10925
10926 /**
10927 * Convenience function to execute Queries directly.
10928 * Executes SQL.
10929 *
10930 * @param string $sql SQL query to execute
10931 * @param array $bindings a list of values to be bound to query parameters
10932 *
10933 * @return array
10934 */
10935 public static function getCol( $sql, $bindings = array() )
10936 {
10937 return self::query( 'getCol', $sql, $bindings );
10938 }
10939
10940 /**
10941 * Convenience function to execute Queries directly.
10942 * Executes SQL.
10943 * Results will be returned as an associative array. The first
10944 * column in the select clause will be used for the keys in this array and
10945 * the second column will be used for the values. If only one column is
10946 * selected in the query, both key and value of the array will have the
10947 * value of this field for each row.
10948 *
10949 * @param string $sql SQL query to execute
10950 * @param array $bindings a list of values to be bound to query parameters
10951 *
10952 * @return array
10953 */
10954 public static function getAssoc( $sql, $bindings = array() )
10955 {
10956 return self::query( 'getAssoc', $sql, $bindings );
10957 }
10958
10959 /**
10960 * Convenience function to execute Queries directly.
10961 * Executes SQL.
10962 * Results will be returned as an associative array indexed by the first
10963 * column in the select.
10964 *
10965 * @param string $sql SQL query to execute
10966 * @param array $bindings a list of values to be bound to query parameters
10967 *
10968 * @return array
10969 */
10970 public static function getAssocRow( $sql, $bindings = array() )
10971 {
10972 return self::query( 'getAssocRow', $sql, $bindings );
10973 }
10974
10975 /**
10976 * Returns the insert ID for databases that support/require this
10977 * functionality. Alias for R::getAdapter()->getInsertID().
10978 *
10979 * @return mixed
10980 */
10981 public static function getInsertID()
10982 {
10983 return self::$adapter->getInsertID();
10984 }
10985
10986 /**
10987 * Makes a copy of a bean. This method makes a deep copy
10988 * of the bean.The copy will have the following features.
10989 * - All beans in own-lists will be duplicated as well
10990 * - All references to shared beans will be copied but not the shared beans themselves
10991 * - All references to parent objects (_id fields) will be copied but not the parents themselves
10992 * In most cases this is the desired scenario for copying beans.
10993 * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
10994 * (i.e. one that already has been processed) the ID of the bean will be returned.
10995 * This should not happen though.
10996 *
10997 * Note:
10998 * This function does a reflectional database query so it may be slow.
10999 *
11000 * @deprecated
11001 * This function is deprecated in favour of R::duplicate().
11002 * This function has a confusing method signature, the R::duplicate() function
11003 * only accepts two arguments: bean and filters.
11004 *
11005 * @param OODBBean $bean bean to be copied
11006 * @param array $trail for internal usage, pass array()
11007 * @param boolean $pid for internal usage
11008 * @param array $white white list filter with bean types to duplicate
11009 *
11010 * @return array
11011 */
11012 public static function dup( $bean, $trail = array(), $pid = FALSE, $filters = array() )
11013 {
11014 self::$duplicationManager->setFilters( $filters );
11015 return self::$duplicationManager->dup( $bean, $trail, $pid );
11016 }
11017
11018 /**
11019 * Makes a deep copy of a bean. This method makes a deep copy
11020 * of the bean.The copy will have the following:
11021 *
11022 * * All beans in own-lists will be duplicated as well
11023 * * All references to shared beans will be copied but not the shared beans themselves
11024 * * All references to parent objects (_id fields) will be copied but not the parents themselves
11025 *
11026 * In most cases this is the desired scenario for copying beans.
11027 * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
11028 * (i.e. one that already has been processed) the ID of the bean will be returned.
11029 * This should not happen though.
11030 *
11031 * Note:
11032 * This function does a reflectional database query so it may be slow.
11033 *
11034 * Note:
11035 * This is a simplified version of the deprecated R::dup() function.
11036 *
11037 * @param OODBBean $bean bean to be copied
11038 * @param array $white white list filter with bean types to duplicate
11039 *
11040 * @return array
11041 */
11042 public static function duplicate( $bean, $filters = array() )
11043 {
11044 return self::dup( $bean, array(), FALSE, $filters );
11045 }
11046
11047 /**
11048 * Exports a collection of beans. Handy for XML/JSON exports with a
11049 * Javascript framework like Dojo or ExtJS.
11050 * What will be exported:
11051 *
11052 * * contents of the bean
11053 * * all own bean lists (recursively)
11054 * * all shared beans (not THEIR own lists)
11055 *
11056 * @param array|OODBBean $beans beans to be exported
11057 * @param boolean $parents whether you want parent beans to be exported
11058 * @param array $filters whitelist of types
11059 *
11060 * @return array
11061 */
11062 public static function exportAll( $beans, $parents = FALSE, $filters = array())
11063 {
11064 return self::$duplicationManager->exportAll( $beans, $parents, $filters, self::$exportCaseStyle );
11065 }
11066
11067 /**
11068 * Selects case style for export.
11069 * This will determine the case style for the keys of exported beans (see exportAll).
11070 * The following options are accepted:
11071 *
11072 * * 'default' RedBeanPHP by default enforces Snake Case (i.e. book_id is_valid )
11073 * * 'camel' Camel Case (i.e. bookId isValid )
11074 * * 'dolphin' Dolphin Case (i.e. bookID isValid ) Like CamelCase but ID is written all uppercase
11075 *
11076 * @warning RedBeanPHP transforms camelCase to snake_case using a slightly different
11077 * algorithm, it also converts isACL to is_acl (not is_a_c_l) and bookID to book_id.
11078 * Due to information loss this cannot be corrected. However if you might try
11079 * DolphinCase for IDs it takes into account the exception concerning IDs.
11080 *
11081 * @param string $caseStyle case style identifier
11082 *
11083 * @return void
11084 */
11085 public static function useExportCase( $caseStyle = 'default' )
11086 {
11087 if ( !in_array( $caseStyle, array( 'default', 'camel', 'dolphin' ) ) ) throw new RedException( 'Invalid case selected.' );
11088 self::$exportCaseStyle = $caseStyle;
11089 }
11090
11091 /**
11092 * Converts a series of rows to beans.
11093 * This method converts a series of rows to beans.
11094 * The type of the desired output beans can be specified in the
11095 * first parameter. The second parameter is meant for the database
11096 * result rows.
11097 *
11098 * Usage:
11099 *
11100 * <code>
11101 * $rows = R::getAll( 'SELECT * FROM ...' )
11102 * $beans = R::convertToBeans( $rows );
11103 * </code>
11104 *
11105 * As of version 4.3.2 you can specify a meta-mask.
11106 * Data from columns with names starting with the value specified in the mask
11107 * will be transferred to the meta section of a bean (under data.bundle).
11108 *
11109 * <code>
11110 * $rows = R::getAll( 'SELECT FROM... COUNT(*) AS extra_count ...' );
11111 * $beans = R::convertToBeans( $rows );
11112 * $bean = reset( $beans );
11113 * $data = $bean->getMeta( 'data.bundle' );
11114 * $extra_count = $data['extra_count'];
11115 * </code>
11116 *
11117 * @param string $type type of beans to produce
11118 * @param array $rows must contain an array of array
11119 *
11120 * @return array
11121 */
11122 public static function convertToBeans( $type, $rows, $metamask = NULL )
11123 {
11124 return self::$redbean->convertToBeans( $type, $rows, $metamask );
11125 }
11126
11127 /**
11128 * Just like converToBeans, but for one bean.
11129 * @see convertToBeans for more details.
11130 *
11131 * @param string $type type of beans to produce
11132 * @param array $row one row from the database
11133 *
11134 * @return array
11135 */
11136 public static function convertToBean( $type, $row, $metamask = NULL )
11137 {
11138 $beans = self::$redbean->convertToBeans( $type, array( $row ), $metamask );
11139 $bean = reset( $beans );
11140 return $bean;
11141 }
11142
11143 /**
11144 * Part of RedBeanPHP Tagging API.
11145 * Tests whether a bean has been associated with one ore more
11146 * of the listed tags. If the third parameter is TRUE this method
11147 * will return TRUE only if all tags that have been specified are indeed
11148 * associated with the given bean, otherwise FALSE.
11149 * If the third parameter is FALSE this
11150 * method will return TRUE if one of the tags matches, FALSE if none
11151 * match.
11152 *
11153 * @param OODBBean $bean bean to check for tags
11154 * @param array $tags list of tags
11155 * @param boolean $all whether they must all match or just some
11156 *
11157 * @return boolean
11158 */
11159 public static function hasTag( $bean, $tags, $all = FALSE )
11160 {
11161 return self::$tagManager->hasTag( $bean, $tags, $all );
11162 }
11163
11164 /**
11165 * Part of RedBeanPHP Tagging API.
11166 * Removes all specified tags from the bean. The tags specified in
11167 * the second parameter will no longer be associated with the bean.
11168 *
11169 * @param OODBBean $bean tagged bean
11170 * @param array $tagList list of tags (names)
11171 *
11172 * @return void
11173 */
11174 public static function untag( $bean, $tagList )
11175 {
11176 self::$tagManager->untag( $bean, $tagList );
11177 }
11178
11179 /**
11180 * Part of RedBeanPHP Tagging API.
11181 * Tags a bean or returns tags associated with a bean.
11182 * If $tagList is NULL or omitted this method will return a
11183 * comma separated list of tags associated with the bean provided.
11184 * If $tagList is a comma separated list (string) of tags all tags will
11185 * be associated with the bean.
11186 * You may also pass an array instead of a string.
11187 *
11188 * @param OODBBean $bean bean to tag
11189 * @param mixed $tagList tags to attach to the specified bean
11190 *
11191 * @return string
11192 */
11193 public static function tag( OODBBean $bean, $tagList = NULL )
11194 {
11195 return self::$tagManager->tag( $bean, $tagList );
11196 }
11197
11198 /**
11199 * Part of RedBeanPHP Tagging API.
11200 * Adds tags to a bean.
11201 * If $tagList is a comma separated list of tags all tags will
11202 * be associated with the bean.
11203 * You may also pass an array instead of a string.
11204 *
11205 * @param OODBBean $bean bean to tag
11206 * @param array $tagList list of tags to add to bean
11207 *
11208 * @return void
11209 */
11210 public static function addTags( OODBBean $bean, $tagList )
11211 {
11212 self::$tagManager->addTags( $bean, $tagList );
11213 }
11214
11215 /**
11216 * Part of RedBeanPHP Tagging API.
11217 * Returns all beans that have been tagged with one of the tags given.
11218 *
11219 * @param string $beanType type of bean you are looking for
11220 * @param array $tagList list of tags to match
11221 * @param string $sql additional SQL query snippet
11222 * @param array $bindings a list of values to bind to the query parameters
11223 *
11224 * @return array
11225 */
11226 public static function tagged( $beanType, $tagList, $sql = '', $bindings = array() )
11227 {
11228 return self::$tagManager->tagged( $beanType, $tagList, $sql, $bindings );
11229 }
11230
11231 /**
11232 * Part of RedBeanPHP Tagging API.
11233 * Returns all beans that have been tagged with ALL of the tags given.
11234 *
11235 * @param string $beanType type of bean you are looking for
11236 * @param array $tagList list of tags to match
11237 * @param string $sql additional SQL query snippet
11238 * @param array $bindings a list of values to bind to the query parameters
11239 *
11240 * @return array
11241 */
11242 public static function taggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
11243 {
11244 return self::$tagManager->taggedAll( $beanType, $tagList, $sql, $bindings );
11245 }
11246
11247 /**
11248 * Wipes all beans of type $beanType.
11249 *
11250 * @param string $beanType type of bean you want to destroy entirely
11251 *
11252 * @return boolean
11253 */
11254 public static function wipe( $beanType )
11255 {
11256 return Facade::$redbean->wipe( $beanType );
11257 }
11258
11259 /**
11260 * Counts the number of beans of type $type.
11261 * This method accepts a second argument to modify the count-query.
11262 * A third argument can be used to provide bindings for the SQL snippet.
11263 *
11264 * @param string $type type of bean we are looking for
11265 * @param string $addSQL additional SQL snippet
11266 * @param array $bindings parameters to bind to SQL
11267 *
11268 * @return integer
11269 */
11270 public static function count( $type, $addSQL = '', $bindings = array() )
11271 {
11272 return Facade::$redbean->count( $type, $addSQL, $bindings );
11273 }
11274
11275 /**
11276 * Configures the facade, want to have a new Writer? A new Object Database or a new
11277 * Adapter and you want it on-the-fly? Use this method to hot-swap your facade with a new
11278 * toolbox.
11279 *
11280 * @param ToolBox $tb toolbox to configure facade with
11281 *
11282 * @return ToolBox
11283 */
11284 public static function configureFacadeWithToolbox( ToolBox $tb )
11285 {
11286 $oldTools = self::$toolbox;
11287 self::$toolbox = $tb;
11288 self::$writer = self::$toolbox->getWriter();
11289 self::$adapter = self::$toolbox->getDatabaseAdapter();
11290 self::$redbean = self::$toolbox->getRedBean();
11291 self::$finder = new Finder( self::$toolbox );
11292 self::$associationManager = new AssociationManager( self::$toolbox );
11293 self::$redbean->setAssociationManager( self::$associationManager );
11294 self::$labelMaker = new LabelMaker( self::$toolbox );
11295 $helper = new SimpleModelHelper();
11296 $helper->attachEventListeners( self::$redbean );
11297 self::$redbean->setBeanHelper( new SimpleFacadeBeanHelper );
11298 self::$duplicationManager = new DuplicationManager( self::$toolbox );
11299 self::$tagManager = new TagManager( self::$toolbox );
11300 return $oldTools;
11301 }
11302
11303 /**
11304 * Facade Convience method for adapter transaction system.
11305 * Begins a transaction.
11306 *
11307 * @return bool
11308 */
11309 public static function begin()
11310 {
11311 if ( !self::$redbean->isFrozen() ) return FALSE;
11312 self::$adapter->startTransaction();
11313 return TRUE;
11314 }
11315
11316 /**
11317 * Facade Convience method for adapter transaction system.
11318 * Commits a transaction.
11319 *
11320 * @return bool
11321 */
11322 public static function commit()
11323 {
11324 if ( !self::$redbean->isFrozen() ) return FALSE;
11325 self::$adapter->commit();
11326 return TRUE;
11327 }
11328
11329 /**
11330 * Facade Convience method for adapter transaction system.
11331 * Rolls back a transaction.
11332 *
11333 * @return bool
11334 */
11335 public static function rollback()
11336 {
11337 if ( !self::$redbean->isFrozen() ) return FALSE;
11338 self::$adapter->rollback();
11339 return TRUE;
11340 }
11341
11342 /**
11343 * Returns a list of columns. Format of this array:
11344 * array( fieldname => type )
11345 * Note that this method only works in fluid mode because it might be
11346 * quite heavy on production servers!
11347 *
11348 * @param string $table name of the table (not type) you want to get columns of
11349 *
11350 * @return array
11351 */
11352 public static function getColumns( $table )
11353 {
11354 return self::$writer->getColumns( $table );
11355 }
11356
11357 /**
11358 * Generates question mark slots for an array of values.
11359 *
11360 * @param array $array array to generate question mark slots for
11361 *
11362 * @return string
11363 */
11364 public static function genSlots( $array, $template = NULL )
11365 {
11366 return ArrayTool::genSlots( $array, $template );
11367 }
11368
11369 /**
11370 * Flattens a multi dimensional bindings array for use with genSlots().
11371 *
11372 * @param array $array array to flatten
11373 *
11374 * @return array
11375 */
11376 public static function flat( $array, $result = array() )
11377 {
11378 return ArrayTool::flat( $array, $result );
11379 }
11380
11381 /**
11382 * Nukes the entire database.
11383 * This will remove all schema structures from the database.
11384 * Only works in fluid mode. Be careful with this method.
11385 *
11386 * @warning dangerous method, will remove all tables, columns etc.
11387 *
11388 * @return void
11389 */
11390 public static function nuke()
11391 {
11392 if ( !self::$redbean->isFrozen() ) {
11393 self::$writer->wipeAll();
11394 }
11395 }
11396
11397 /**
11398 * Short hand function to store a set of beans at once, IDs will be
11399 * returned as an array. For information please consult the R::store()
11400 * function.
11401 * A loop saver.
11402 *
11403 * @param array $beans list of beans to be stored
11404 *
11405 * @return array
11406 */
11407 public static function storeAll( $beans )
11408 {
11409 $ids = array();
11410 foreach ( $beans as $bean ) {
11411 $ids[] = self::store( $bean );
11412 }
11413 return $ids;
11414 }
11415
11416 /**
11417 * Short hand function to trash a set of beans at once.
11418 * For information please consult the R::trash() function.
11419 * A loop saver.
11420 *
11421 * @param array $beans list of beans to be trashed
11422 *
11423 * @return void
11424 */
11425 public static function trashAll( $beans )
11426 {
11427 foreach ( $beans as $bean ) {
11428 self::trash( $bean );
11429 }
11430 }
11431
11432 /**
11433 * Toggles Writer Cache.
11434 * Turns the Writer Cache on or off. The Writer Cache is a simple
11435 * query based caching system that may improve performance without the need
11436 * for cache management. This caching system will cache non-modifying queries
11437 * that are marked with special SQL comments. As soon as a non-marked query
11438 * gets executed the cache will be flushed. Only non-modifying select queries
11439 * have been marked therefore this mechanism is a rather safe way of caching, requiring
11440 * no explicit flushes or reloads. Of course this does not apply if you intend to test
11441 * or simulate concurrent querying.
11442 *
11443 * @param boolean $yesNo TRUE to enable cache, FALSE to disable cache
11444 *
11445 * @return void
11446 */
11447 public static function useWriterCache( $yesNo )
11448 {
11449 self::getWriter()->setUseCache( $yesNo );
11450 }
11451
11452 /**
11453 * A label is a bean with only an id, type and name property.
11454 * This function will dispense beans for all entries in the array. The
11455 * values of the array will be assigned to the name property of each
11456 * individual bean.
11457 *
11458 * @param string $type type of beans you would like to have
11459 * @param array $labels list of labels, names for each bean
11460 *
11461 * @return array
11462 */
11463 public static function dispenseLabels( $type, $labels )
11464 {
11465 return self::$labelMaker->dispenseLabels( $type, $labels );
11466 }
11467
11468 /**
11469 * Generates and returns an ENUM value. This is how RedBeanPHP handles ENUMs.
11470 * Either returns a (newly created) bean respresenting the desired ENUM
11471 * value or returns a list of all enums for the type.
11472 *
11473 * To obtain (and add if necessary) an ENUM value:
11474 *
11475 * <code>
11476 * $tea->flavour = R::enum( 'flavour:apple' );
11477 * </code>
11478 *
11479 * Returns a bean of type 'flavour' with name = apple.
11480 * This will add a bean with property name (set to APPLE) to the database
11481 * if it does not exist yet.
11482 *
11483 * To obtain all flavours:
11484 *
11485 * <code>
11486 * R::enum('flavour');
11487 * </code>
11488 *
11489 * To get a list of all flavour names:
11490 *
11491 * <code>
11492 * R::gatherLabels( R::enum( 'flavour' ) );
11493 * </code>
11494 *
11495 * @param string $enum either type or type-value
11496 *
11497 * @return array|OODBBean
11498 */
11499 public static function enum( $enum )
11500 {
11501 return self::$labelMaker->enum( $enum );
11502 }
11503
11504 /**
11505 * Gathers labels from beans. This function loops through the beans,
11506 * collects the values of the name properties of each individual bean
11507 * and stores the names in a new array. The array then gets sorted using the
11508 * default sort function of PHP (sort).
11509 *
11510 * @param array $beans list of beans to loop
11511 *
11512 * @return array
11513 */
11514 public static function gatherLabels( $beans )
11515 {
11516 return self::$labelMaker->gatherLabels( $beans );
11517 }
11518
11519 /**
11520 * Closes the database connection.
11521 *
11522 * @return void
11523 */
11524 public static function close()
11525 {
11526 if ( isset( self::$adapter ) ) {
11527 self::$adapter->close();
11528 }
11529 }
11530
11531 /**
11532 * Simple convenience function, returns ISO date formatted representation
11533 * of $time.
11534 *
11535 * @param mixed $time UNIX timestamp
11536 *
11537 * @return string
11538 */
11539 public static function isoDate( $time = NULL )
11540 {
11541 if ( !$time ) {
11542 $time = time();
11543 }
11544
11545 return @date( 'Y-m-d', $time );
11546 }
11547
11548 /**
11549 * Simple convenience function, returns ISO date time
11550 * formatted representation
11551 * of $time.
11552 *
11553 * @param mixed $time UNIX timestamp
11554 *
11555 * @return string
11556 */
11557 public static function isoDateTime( $time = NULL )
11558 {
11559 if ( !$time ) $time = time();
11560 return @date( 'Y-m-d H:i:s', $time );
11561 }
11562
11563 /**
11564 * Optional accessor for neat code.
11565 * Sets the database adapter you want to use.
11566 *
11567 * @param Adapter $adapter Database Adapter for facade to use
11568 *
11569 * @return void
11570 */
11571 public static function setDatabaseAdapter( Adapter $adapter )
11572 {
11573 self::$adapter = $adapter;
11574 }
11575
11576 /**
11577 * Optional accessor for neat code.
11578 * Sets the database adapter you want to use.
11579 *
11580 * @param QueryWriter $writer Query Writer instance for facade to use
11581 *
11582 * @return void
11583 */
11584 public static function setWriter( QueryWriter $writer )
11585 {
11586 self::$writer = $writer;
11587 }
11588
11589 /**
11590 * Optional accessor for neat code.
11591 * Sets the database adapter you want to use.
11592 *
11593 * @param OODB $redbean Object Database for facade to use
11594 */
11595 public static function setRedBean( OODB $redbean )
11596 {
11597 self::$redbean = $redbean;
11598 }
11599
11600 /**
11601 * Optional accessor for neat code.
11602 * Sets the database adapter you want to use.
11603 *
11604 * @return DBAdapter
11605 */
11606 public static function getDatabaseAdapter()
11607 {
11608 return self::$adapter;
11609 }
11610
11611 /**
11612 * In case you use PDO (which is recommended and the default but not mandatory, hence
11613 * the database adapter), you can use this method to obtain the PDO object directly.
11614 * This is a convenience method, it will do the same as:
11615 *
11616 * <code>
11617 * R::getDatabaseAdapter()->getDatabase()->getPDO();
11618 * </code>
11619 *
11620 * If the PDO object could not be found, for whatever reason, this method
11621 * will return NULL instead.
11622 *
11623 * @return NULL|PDO
11624 */
11625 public static function getPDO()
11626 {
11627 $databaseAdapter = self::getDatabaseAdapter();
11628 if ( is_null( $databaseAdapter ) ) return NULL;
11629 $database = $databaseAdapter->getDatabase();
11630 if ( is_null( $database ) ) return NULL;
11631 if ( !method_exists( $database, 'getPDO' ) ) return NULL;
11632 return $database->getPDO();
11633 }
11634
11635 /**
11636 * Returns the current duplication manager instance.
11637 *
11638 * @return DuplicationManager
11639 */
11640 public static function getDuplicationManager()
11641 {
11642 return self::$duplicationManager;
11643 }
11644
11645 /**
11646 * Optional accessor for neat code.
11647 * Sets the database adapter you want to use.
11648 *
11649 * @return QueryWriter
11650 */
11651 public static function getWriter()
11652 {
11653 return self::$writer;
11654 }
11655
11656 /**
11657 * Optional accessor for neat code.
11658 * Sets the database adapter you want to use.
11659 *
11660 * @return OODB
11661 */
11662 public static function getRedBean()
11663 {
11664 return self::$redbean;
11665 }
11666
11667 /**
11668 * Returns the toolbox currently used by the facade.
11669 * To set the toolbox use R::setup() or R::configureFacadeWithToolbox().
11670 * To create a toolbox use Setup::kickstart(). Or create a manual
11671 * toolbox using the ToolBox class.
11672 *
11673 * @return ToolBox
11674 */
11675 public static function getToolBox()
11676 {
11677 return self::$toolbox;
11678 }
11679
11680 /**
11681 * Mostly for internal use, but might be handy
11682 * for some users.
11683 * This returns all the components of the currently
11684 * selected toolbox.
11685 *
11686 * Returns the components in the following order:
11687 *
11688 * # OODB instance (getRedBean())
11689 * # Database Adapter
11690 * # Query Writer
11691 * # Toolbox itself
11692 *
11693 * @return array
11694 */
11695 public static function getExtractedToolbox()
11696 {
11697 return array( self::$redbean, self::$adapter, self::$writer, self::$toolbox );
11698 }
11699
11700 /**
11701 * Facade method for AQueryWriter::renameAssociation()
11702 *
11703 * @param string|array $from
11704 * @param string $to
11705 *
11706 * @return void
11707 */
11708 public static function renameAssociation( $from, $to = NULL )
11709 {
11710 AQueryWriter::renameAssociation( $from, $to );
11711 }
11712
11713 /**
11714 * Little helper method for Resty Bean Can server and others.
11715 * Takes an array of beans and exports each bean.
11716 * Unlike exportAll this method does not recurse into own lists
11717 * and shared lists, the beans are exported as-is, only loaded lists
11718 * are exported.
11719 *
11720 * @param array $beans beans
11721 *
11722 * @return array
11723 */
11724 public static function beansToArray( $beans )
11725 {
11726 $list = array();
11727 foreach( $beans as $bean ) $list[] = $bean->export();
11728 return $list;
11729 }
11730
11731 /**
11732 * Sets the error mode for FUSE.
11733 * What to do if a FUSE model method does not exist?
11734 * You can set the following options:
11735 *
11736 * * OODBBean::C_ERR_IGNORE (default), ignores the call, returns NULL
11737 * * OODBBean::C_ERR_LOG, logs the incident using error_log
11738 * * OODBBean::C_ERR_NOTICE, triggers a E_USER_NOTICE
11739 * * OODBBean::C_ERR_WARN, triggers a E_USER_WARNING
11740 * * OODBBean::C_ERR_EXCEPTION, throws an exception
11741 * * OODBBean::C_ERR_FUNC, allows you to specify a custom handler (function)
11742 * * OODBBean::C_ERR_FATAL, triggers a E_USER_ERROR
11743 *
11744 * <code>
11745 * Custom handler method signature: handler( array (
11746 * 'message' => string
11747 * 'bean' => OODBBean
11748 * 'method' => string
11749 * ) )
11750 * </code>
11751 *
11752 * This method returns the old mode and handler as an array.
11753 *
11754 * @param integer $mode mode, determines how to handle errors
11755 * @param callable|NULL $func custom handler (if applicable)
11756 *
11757 * @return array
11758 */
11759 public static function setErrorHandlingFUSE( $mode, $func = NULL )
11760 {
11761 return OODBBean::setErrorHandlingFUSE( $mode, $func );
11762 }
11763
11764 /**
11765 * Simple but effective debug function.
11766 * Given a one or more beans this method will
11767 * return an array containing first part of the string
11768 * representation of each item in the array.
11769 *
11770 * @param OODBBean|array $data either a bean or an array of beans
11771 *
11772 * @return array
11773 */
11774 public static function dump( $data )
11775 {
11776 return Dump::dump( $data );
11777 }
11778
11779 /**
11780 * Binds an SQL function to a column.
11781 * This method can be used to setup a decode/encode scheme or
11782 * perform UUID insertion. This method is especially useful for handling
11783 * MySQL spatial columns, because they need to be processed first using
11784 * the asText/GeomFromText functions.
11785 *
11786 * Example:
11787 *
11788 * <code>
11789 * R::bindFunc( 'read', 'location.point', 'asText' );
11790 * R::bindFunc( 'write', 'location.point', 'GeomFromText' );
11791 * </code>
11792 *
11793 * Passing NULL as the function will reset (clear) the function
11794 * for this column/mode.
11795 *
11796 * @param string $mode mode for function: i.e. read or write
11797 * @param string $field field (table.column) to bind function to
11798 * @param string $function SQL function to bind to specified column
11799 *
11800 * @return void
11801 */
11802 public static function bindFunc( $mode, $field, $function )
11803 {
11804 self::$redbean->bindFunc( $mode, $field, $function );
11805 }
11806
11807 /**
11808 * Sets global aliases.
11809 * Registers a batch of aliases in one go. This works the same as
11810 * fetchAs and setAutoResolve but explicitly. For instance if you register
11811 * the alias 'cover' for 'page' a property containing a reference to a
11812 * page bean called 'cover' will correctly return the page bean and not
11813 * a (non-existant) cover bean.
11814 *
11815 * <code>
11816 * R::aliases( array( 'cover' => 'page' ) );
11817 * $book = R::dispense( 'book' );
11818 * $page = R::dispense( 'page' );
11819 * $book->cover = $page;
11820 * R::store( $book );
11821 * $book = $book->fresh();
11822 * $cover = $book->cover;
11823 * echo $cover->getMeta( 'type' ); //page
11824 * </code>
11825 *
11826 * The format of the aliases registration array is:
11827 *
11828 * {alias} => {actual type}
11829 *
11830 * In the example above we use:
11831 *
11832 * cover => page
11833 *
11834 * From that point on, every bean reference to a cover
11835 * will return a 'page' bean. Note that with autoResolve this
11836 * feature along with fetchAs() is no longer very important, although
11837 * relying on explicit aliases can be a bit faster.
11838 *
11839 * @param array $list list of global aliases to use
11840 *
11841 * @return void
11842 */
11843 public static function aliases( $list )
11844 {
11845 OODBBean::aliases( $list );
11846 }
11847
11848 /**
11849 * Tries to find a bean matching a certain type and
11850 * criteria set. If no beans are found a new bean
11851 * will be created, the criteria will be imported into this
11852 * bean and the bean will be stored and returned.
11853 * If multiple beans match the criteria only the first one
11854 * will be returned.
11855 *
11856 * @param string $type type of bean to search for
11857 * @param array $like criteria set describing the bean to search for
11858 *
11859 * @return OODBBean
11860 */
11861 public static function findOrCreate( $type, $like = array() )
11862 {
11863 return self::$finder->findOrCreate( $type, $like );
11864 }
11865
11866 /**
11867 * Tries to find beans matching the specified type and
11868 * criteria set.
11869 *
11870 * If the optional additional SQL snippet is a condition, it will
11871 * be glued to the rest of the query using the AND operator.
11872 *
11873 * @param string $type type of bean to search for
11874 * @param array $like optional criteria set describing the bean to search for
11875 * @param string $sql optional additional SQL for sorting
11876 *
11877 * @return array
11878 */
11879 public static function findLike( $type, $like = array(), $sql = '' )
11880 {
11881 return self::$finder->findLike( $type, $like, $sql );
11882 }
11883
11884 /**
11885 * Starts logging queries.
11886 * Use this method to start logging SQL queries being
11887 * executed by the adapter.
11888 *
11889 * @note you cannot use R::debug and R::startLogging
11890 * at the same time because R::debug is essentially a
11891 * special kind of logging.
11892 *
11893 * @return void
11894 */
11895 public static function startLogging()
11896 {
11897 self::debug( TRUE, RDefault::C_LOGGER_ARRAY );
11898 }
11899
11900 /**
11901 * Stops logging, comfortable method to stop logging of queries.
11902 *
11903 * @return void
11904 */
11905 public static function stopLogging()
11906 {
11907 self::debug( FALSE );
11908 }
11909
11910 /**
11911 * Returns the log entries written after the startLogging.
11912 *
11913 * @return array
11914 */
11915 public static function getLogs()
11916 {
11917 return self::getLogger()->getLogs();
11918 }
11919
11920 /**
11921 * Resets the Query counter.
11922 *
11923 * @return integer
11924 */
11925 public static function resetQueryCount()
11926 {
11927 self::$adapter->getDatabase()->resetCounter();
11928 }
11929
11930 /**
11931 * Returns the number of SQL queries processed.
11932 *
11933 * @return integer
11934 */
11935 public static function getQueryCount()
11936 {
11937 return self::$adapter->getDatabase()->getQueryCount();
11938 }
11939
11940 /**
11941 * Returns the current logger instance being used by the
11942 * database object.
11943 *
11944 * @return Logger
11945 */
11946 public static function getLogger()
11947 {
11948 return self::$adapter->getDatabase()->getLogger();
11949 }
11950
11951 /**
11952 * Alias for setAutoResolve() method on OODBBean.
11953 * Enables or disables auto-resolving fetch types.
11954 * Auto-resolving aliased parent beans is convenient but can
11955 * be slower and can create infinite recursion if you
11956 * used aliases to break cyclic relations in your domain.
11957 *
11958 * @param boolean $automatic TRUE to enable automatic resolving aliased parents
11959 *
11960 * @return void
11961 */
11962 public static function setAutoResolve( $automatic = TRUE )
11963 {
11964 OODBBean::setAutoResolve( (boolean) $automatic );
11965 }
11966
11967 /**
11968 * Dynamically extends the facade with a plugin.
11969 * Using this method you can register your plugin with the facade and then
11970 * use the plugin by invoking the name specified plugin name as a method on
11971 * the facade.
11972 *
11973 * Usage:
11974 *
11975 * <code>
11976 * R::ext( 'makeTea', function() { ... } );
11977 * </code>
11978 *
11979 * Now you can use your makeTea plugin like this:
11980 *
11981 * <code>
11982 * R::makeTea();
11983 * </code>
11984 *
11985 * @param string $pluginName name of the method to call the plugin
11986 * @param callable $callable a PHP callable
11987 *
11988 * @return void
11989 */
11990 public static function ext( $pluginName, $callable )
11991 {
11992 if ( !ctype_alnum( $pluginName ) ) {
11993 throw new RedException( 'Plugin name may only contain alphanumeric characters.' );
11994 }
11995 self::$plugins[$pluginName] = $callable;
11996 }
11997
11998 /**
11999 * Call static for use with dynamic plugins. This magic method will
12000 * intercept static calls and route them to the specified plugin.
12001 *
12002 * @param string $pluginName name of the plugin
12003 * @param array $params list of arguments to pass to plugin method
12004 *
12005 * @return mixed
12006 */
12007 public static function __callStatic( $pluginName, $params )
12008 {
12009 if ( !ctype_alnum( $pluginName) ) {
12010 throw new RedException( 'Plugin name may only contain alphanumeric characters.' );
12011 }
12012 if ( !isset( self::$plugins[$pluginName] ) ) {
12013 throw new RedException( 'Plugin \''.$pluginName.'\' does not exist, add this plugin using: R::ext(\''.$pluginName.'\')' );
12014 }
12015 return call_user_func_array( self::$plugins[$pluginName], $params );
12016 }
12017}
12018
12019}
12020
12021namespace RedBeanPHP {
12022
12023use RedBeanPHP\ToolBox as ToolBox;
12024use RedBeanPHP\AssociationManager as AssociationManager;
12025use RedBeanPHP\OODB as OODB;
12026use RedBeanPHP\OODBBean as OODBBean;
12027use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
12028
12029/**
12030 * Duplication Manager.
12031 * Creates deep copies of beans.
12032 *
12033 * @file RedBeanPHP/DuplicationManager.php
12034 * @author Gabor de Mooij and the RedBeanPHP Community
12035 * @license BSD/GPLv2
12036 *
12037 * @copyright
12038 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
12039 * This source file is subject to the BSD/GPLv2 License that is bundled
12040 * with this source code in the file license.txt.
12041 */
12042class DuplicationManager
12043{
12044 /**
12045 * @var ToolBox
12046 */
12047 protected $toolbox;
12048
12049 /**
12050 * @var AssociationManager
12051 */
12052 protected $associationManager;
12053
12054 /**
12055 * @var OODB
12056 */
12057 protected $redbean;
12058
12059 /**
12060 * @var array
12061 */
12062 protected $tables = array();
12063
12064 /**
12065 * @var array
12066 */
12067 protected $columns = array();
12068
12069 /**
12070 * @var array
12071 */
12072 protected $filters = array();
12073
12074 /**
12075 * @var array
12076 */
12077 protected $cacheTables = FALSE;
12078
12079 /**
12080 * Copies the shared beans in a bean, i.e. all the sharedBean-lists.
12081 *
12082 * @param OODBBean $copy target bean to copy lists to
12083 * @param string $shared name of the shared list
12084 * @param array $beans array with shared beans to copy
12085 *
12086 * @return void
12087 */
12088 private function copySharedBeans( OODBBean $copy, $shared, $beans )
12089 {
12090 $copy->$shared = array();
12091
12092 foreach ( $beans as $subBean ) {
12093 array_push( $copy->$shared, $subBean );
12094 }
12095 }
12096
12097 /**
12098 * Copies the own beans in a bean, i.e. all the ownBean-lists.
12099 * Each bean in the own-list belongs exclusively to its owner so
12100 * we need to invoke the duplicate method again to duplicate each bean here.
12101 *
12102 * @param OODBBean $copy target bean to copy lists to
12103 * @param string $owned name of the own list
12104 * @param array $beans array with shared beans to copy
12105 * @param array $trail array with former beans to detect recursion
12106 * @param boolean $preserveIDs TRUE means preserve IDs, for export only
12107 *
12108 * @return void
12109 */
12110 private function copyOwnBeans( OODBBean $copy, $owned, $beans, $trail, $preserveIDs )
12111 {
12112 $copy->$owned = array();
12113 foreach ( $beans as $subBean ) {
12114 array_push( $copy->$owned, $this->duplicate( $subBean, $trail, $preserveIDs ) );
12115 }
12116 }
12117
12118 /**
12119 * Creates a copy of bean $bean and copies all primitive properties (not lists)
12120 * and the parents beans to the newly created bean. Also sets the ID of the bean
12121 * to 0.
12122 *
12123 * @param OODBBean $bean bean to copy
12124 *
12125 * @return OODBBean
12126 */
12127 private function createCopy( OODBBean $bean )
12128 {
12129 $type = $bean->getMeta( 'type' );
12130
12131 $copy = $this->redbean->dispense( $type );
12132 $copy->setMeta( 'sys.dup-from-id', $bean->id );
12133 $copy->setMeta( 'sys.old-id', $bean->id );
12134 $copy->importFrom( $bean );
12135 $copy->id = 0;
12136
12137 return $copy;
12138 }
12139
12140 /**
12141 * Generates a key from the bean type and its ID and determines if the bean
12142 * occurs in the trail, if not the bean will be added to the trail.
12143 * Returns TRUE if the bean occurs in the trail and FALSE otherwise.
12144 *
12145 * @param array $trail list of former beans
12146 * @param OODBBean $bean currently selected bean
12147 *
12148 * @return boolean
12149 */
12150 private function inTrailOrAdd( &$trail, OODBBean $bean )
12151 {
12152 $type = $bean->getMeta( 'type' );
12153 $key = $type . $bean->getID();
12154
12155 if ( isset( $trail[$key] ) ) {
12156 return TRUE;
12157 }
12158
12159 $trail[$key] = $bean;
12160
12161 return FALSE;
12162 }
12163
12164 /**
12165 * Given the type name of a bean this method returns the canonical names
12166 * of the own-list and the shared-list properties respectively.
12167 * Returns a list with two elements: name of the own-list, and name
12168 * of the shared list.
12169 *
12170 * @param string $typeName bean type name
12171 *
12172 * @return array
12173 */
12174 private function getListNames( $typeName )
12175 {
12176 $owned = 'own' . ucfirst( $typeName );
12177 $shared = 'shared' . ucfirst( $typeName );
12178
12179 return array( $owned, $shared );
12180 }
12181
12182 /**
12183 * Determines whether the bean has an own list based on
12184 * schema inspection from realtime schema or cache.
12185 *
12186 * @param string $type bean type to get list for
12187 * @param string $target type of list you want to detect
12188 *
12189 * @return boolean
12190 */
12191 protected function hasOwnList( $type, $target )
12192 {
12193 return isset( $this->columns[$target][$type . '_id'] );
12194 }
12195
12196 /**
12197 * Determines whether the bea has a shared list based on
12198 * schema inspection from realtime schema or cache.
12199 *
12200 * @param string $type bean type to get list for
12201 * @param string $target type of list you are looking for
12202 *
12203 * @return boolean
12204 */
12205 protected function hasSharedList( $type, $target )
12206 {
12207 return in_array( AQueryWriter::getAssocTableFormat( array( $type, $target ) ), $this->tables );
12208 }
12209
12210 /**
12211 * @see DuplicationManager::dup
12212 *
12213 * @param OODBBean $bean bean to be copied
12214 * @param array $trail trail to prevent infinite loops
12215 * @param boolean $preserveIDs preserve IDs
12216 *
12217 * @return OODBBean
12218 */
12219 protected function duplicate( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
12220 {
12221 if ( $this->inTrailOrAdd( $trail, $bean ) ) return $bean;
12222
12223 $type = $bean->getMeta( 'type' );
12224
12225 $copy = $this->createCopy( $bean );
12226 foreach ( $this->tables as $table ) {
12227
12228 if ( !empty( $this->filters ) ) {
12229 if ( !in_array( $table, $this->filters ) ) continue;
12230 }
12231
12232 list( $owned, $shared ) = $this->getListNames( $table );
12233
12234 if ( $this->hasSharedList( $type, $table ) ) {
12235 if ( $beans = $bean->$shared ) {
12236 $this->copySharedBeans( $copy, $shared, $beans );
12237 }
12238 } elseif ( $this->hasOwnList( $type, $table ) ) {
12239 if ( $beans = $bean->$owned ) {
12240 $this->copyOwnBeans( $copy, $owned, $beans, $trail, $preserveIDs );
12241 }
12242
12243 $copy->setMeta( 'sys.shadow.' . $owned, NULL );
12244 }
12245
12246 $copy->setMeta( 'sys.shadow.' . $shared, NULL );
12247 }
12248
12249 $copy->id = ( $preserveIDs ) ? $bean->id : $copy->id;
12250
12251 return $copy;
12252 }
12253
12254 /**
12255 * Constructor,
12256 * creates a new instance of DupManager.
12257 *
12258 * @param ToolBox $toolbox
12259 */
12260 public function __construct( ToolBox $toolbox )
12261 {
12262 $this->toolbox = $toolbox;
12263 $this->redbean = $toolbox->getRedBean();
12264 $this->associationManager = $this->redbean->getAssociationManager();
12265 }
12266
12267 /**
12268 * Recursively turns the keys of an array into
12269 * camelCase.
12270 *
12271 * @param array $array array to camelize
12272 * @param boolean $dolphinMode whether you want the exception for IDs.
12273 *
12274 * @return array
12275 */
12276 public function camelfy( $array, $dolphinMode = false ) {
12277 $newArray = array();
12278 foreach( $array as $key => $element ) {
12279 $newKey = preg_replace_callback( '/_(\w)/', function( &$matches ){
12280 return strtoupper( $matches[1] );
12281 }, $key);
12282
12283 if ( $dolphinMode ) {
12284 $newKey = preg_replace( '/(\w)Id$/', '$1ID', $newKey );
12285 }
12286
12287 $newArray[$newKey] = ( is_array($element) ) ? $this->camelfy( $element, $dolphinMode ) : $element;
12288 }
12289 return $newArray;
12290 }
12291
12292 /**
12293 * For better performance you can pass the tables in an array to this method.
12294 * If the tables are available the duplication manager will not query them so
12295 * this might be beneficial for performance.
12296 *
12297 * This method allows two array formats:
12298 *
12299 * <code>
12300 * array( TABLE1, TABLE2 ... )
12301 * </code>
12302 *
12303 * or
12304 *
12305 * <code>
12306 * array( TABLE1 => array( COLUMN1, COLUMN2 ... ) ... )
12307 * </code>
12308 *
12309 * @param array $tables a table cache array
12310 *
12311 * @return void
12312 */
12313 public function setTables( $tables )
12314 {
12315 foreach ( $tables as $key => $value ) {
12316 if ( is_numeric( $key ) ) {
12317 $this->tables[] = $value;
12318 } else {
12319 $this->tables[] = $key;
12320 $this->columns[$key] = $value;
12321 }
12322 }
12323
12324 $this->cacheTables = TRUE;
12325 }
12326
12327 /**
12328 * Returns a schema array for cache.
12329 * You can use the return value of this method as a cache,
12330 * store it in RAM or on disk and pass it to setTables later.
12331 *
12332 * @return array
12333 */
12334 public function getSchema()
12335 {
12336 return $this->columns;
12337 }
12338
12339 /**
12340 * Indicates whether you want the duplication manager to cache the database schema.
12341 * If this flag is set to TRUE the duplication manager will query the database schema
12342 * only once. Otherwise the duplicationmanager will, by default, query the schema
12343 * every time a duplication action is performed (dup()).
12344 *
12345 * @param boolean $yesNo TRUE to use caching, FALSE otherwise
12346 */
12347 public function setCacheTables( $yesNo )
12348 {
12349 $this->cacheTables = $yesNo;
12350 }
12351
12352 /**
12353 * A filter array is an array with table names.
12354 * By setting a table filter you can make the duplication manager only take into account
12355 * certain bean types. Other bean types will be ignored when exporting or making a
12356 * deep copy. If no filters are set all types will be taking into account, this is
12357 * the default behavior.
12358 *
12359 * @param array $filters list of tables to be filtered
12360 *
12361 * @return void
12362 */
12363 public function setFilters( $filters )
12364 {
12365 if ( !is_array( $filters ) ) {
12366 $filters = array( $filters );
12367 }
12368
12369 $this->filters = $filters;
12370 }
12371
12372 /**
12373 * Makes a copy of a bean. This method makes a deep copy
12374 * of the bean.The copy will have the following features.
12375 * - All beans in own-lists will be duplicated as well
12376 * - All references to shared beans will be copied but not the shared beans themselves
12377 * - All references to parent objects (_id fields) will be copied but not the parents themselves
12378 * In most cases this is the desired scenario for copying beans.
12379 * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
12380 * (i.e. one that already has been processed) the ID of the bean will be returned.
12381 * This should not happen though.
12382 *
12383 * Note:
12384 * This function does a reflectional database query so it may be slow.
12385 *
12386 * Note:
12387 * this function actually passes the arguments to a protected function called
12388 * duplicate() that does all the work. This method takes care of creating a clone
12389 * of the bean to avoid the bean getting tainted (triggering saving when storing it).
12390 *
12391 * @param OODBBean $bean bean to be copied
12392 * @param array $trail for internal usage, pass array()
12393 * @param boolean $preserveIDs for internal usage
12394 *
12395 * @return OODBBean
12396 */
12397 public function dup( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
12398 {
12399 if ( !count( $this->tables ) ) {
12400 $this->tables = $this->toolbox->getWriter()->getTables();
12401 }
12402
12403 if ( !count( $this->columns ) ) {
12404 foreach ( $this->tables as $table ) {
12405 $this->columns[$table] = $this->toolbox->getWriter()->getColumns( $table );
12406 }
12407 }
12408
12409 $rs = $this->duplicate( ( clone $bean ), $trail, $preserveIDs );
12410
12411 if ( !$this->cacheTables ) {
12412 $this->tables = array();
12413 $this->columns = array();
12414 }
12415
12416 return $rs;
12417 }
12418
12419 /**
12420 * Exports a collection of beans recursively.
12421 * This method will export an array of beans in the first argument to a
12422 * set of arrays. This can be used to send JSON or XML representations
12423 * of bean hierarchies to the client.
12424 *
12425 * For every bean in the array this method will export:
12426 *
12427 * - contents of the bean
12428 * - all own bean lists (recursively)
12429 * - all shared beans (but not THEIR own lists)
12430 *
12431 * If the second parameter is set to TRUE the parents of the beans in the
12432 * array will be exported as well (but not THEIR parents).
12433 *
12434 * The third parameter can be used to provide a white-list array
12435 * for filtering. This is an array of strings representing type names,
12436 * only the type names in the filter list will be exported.
12437 *
12438 * The fourth parameter can be used to change the keys of the resulting
12439 * export arrays. The default mode is 'snake case' but this leaves the
12440 * keys as-is, because 'snake' is the default case style used by
12441 * RedBeanPHP in the database. You can set this to 'camel' for
12442 * camel cased keys or 'dolphin' (same as camelcase but id will be
12443 * converted to ID instead of Id).
12444 *
12445 * @param array|OODBBean $beans beans to be exported
12446 * @param boolean $parents also export parents
12447 * @param array $filters only these types (whitelist)
12448 * @param string $caseStyle case style identifier
12449 *
12450 * @return array
12451 */
12452 public function exportAll( $beans, $parents = FALSE, $filters = array(), $caseStyle = 'snake')
12453 {
12454 $array = array();
12455
12456 if ( !is_array( $beans ) ) {
12457 $beans = array( $beans );
12458 }
12459
12460 foreach ( $beans as $bean ) {
12461 $this->setFilters( $filters );
12462
12463 $duplicate = $this->dup( $bean, array(), TRUE );
12464
12465 $array[] = $duplicate->export( FALSE, $parents, FALSE, $filters );
12466 }
12467
12468 if ( $caseStyle === 'camel' ) $array = $this->camelfy( $array );
12469 if ( $caseStyle === 'dolphin' ) $array = $this->camelfy( $array, true );
12470
12471 return $array;
12472 }
12473}
12474}
12475
12476namespace RedBeanPHP\Util {
12477
12478use RedBeanPHP\OODB as OODB;
12479use RedBeanPHP\OODBBean as OODBBean;
12480use RedBeanPHP\RedException as RedException;
12481
12482/**
12483 * Array Tool Helper
12484 *
12485 * This code was originally part of the facade, however it has
12486 * been decided to remove unique features to service classes like
12487 * this to make them available to developers not using the facade class.
12488 *
12489 * This is a helper or service class containing frequently used
12490 * array functions for dealing with SQL queries.
12491 *
12492 * @file RedBeanPHP/Util/ArrayTool.php
12493 * @author Gabor de Mooij and the RedBeanPHP Community
12494 * @license BSD/GPLv2
12495 *
12496 * @copyright
12497 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
12498 * This source file is subject to the BSD/GPLv2 License that is bundled
12499 * with this source code in the file license.txt.
12500 */
12501class ArrayTool
12502{
12503 /**
12504 * Generates question mark slots for an array of values.
12505 *
12506 * @param array $array array to generate question mark slots for
12507 *
12508 * @return string
12509 */
12510 public static function genSlots( $array, $template = NULL )
12511 {
12512 $str = count( $array ) ? implode( ',', array_fill( 0, count( $array ), '?' ) ) : '';
12513 return ( is_null( $template ) || $str === '' ) ? $str : sprintf( $template, $str );
12514 }
12515
12516 /**
12517 * Flattens a multi dimensional bindings array for use with genSlots().
12518 *
12519 * @param array $array array to flatten
12520 * @param array $result result array parameter (for recursion)
12521 *
12522 * @return array
12523 */
12524 public static function flat( $array, $result = array() )
12525 {
12526 foreach( $array as $value ) {
12527 if ( is_array( $value ) ) $result = self::flat( $value, $result );
12528 else $result[] = $value;
12529 }
12530 return $result;
12531 }
12532}
12533}
12534
12535namespace RedBeanPHP\Util {
12536
12537use RedBeanPHP\OODB as OODB;
12538use RedBeanPHP\RedException as RedException;
12539
12540/**
12541 * Dispense Helper
12542 *
12543 * A helper class containing a dispense utility.
12544 *
12545 * @file RedBeanPHP/Util/DispenseHelper.php
12546 * @author Gabor de Mooij and the RedBeanPHP Community
12547 * @license BSD/GPLv2
12548 *
12549 * @copyright
12550 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
12551 * This source file is subject to the BSD/GPLv2 License that is bundled
12552 * with this source code in the file license.txt.
12553 */
12554class DispenseHelper
12555{
12556 /**
12557 * Dispenses a new RedBean OODB Bean for use with
12558 * the rest of the methods.
12559 *
12560 * @param OODB $oodb OODB
12561 * @param string|array $typeOrBeanArray type or bean array to import
12562 * @param integer $number number of beans to dispense
12563 * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
12564 *
12565 * @return array|OODBBean
12566 */
12567 public static function dispense( OODB $oodb, $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE ) {
12568
12569 if ( is_array($typeOrBeanArray) ) {
12570
12571 if ( !isset( $typeOrBeanArray['_type'] ) ) {
12572 $list = array();
12573 foreach( $typeOrBeanArray as $beanArray ) {
12574 if (
12575 !( is_array( $beanArray )
12576 && isset( $beanArray['_type'] ) ) ) {
12577 throw new RedException( 'Invalid Array Bean' );
12578 }
12579 }
12580 foreach( $typeOrBeanArray as $beanArray ) $list[] = self::dispense( $oodb, $beanArray );
12581 return $list;
12582 }
12583
12584 $import = $typeOrBeanArray;
12585 $type = $import['_type'];
12586 unset( $import['_type'] );
12587 } else {
12588 $type = $typeOrBeanArray;
12589 }
12590
12591 if ( !preg_match( '/^[a-z0-9]+$/', $type ) ) {
12592 throw new RedException( 'Invalid type: ' . $type );
12593 }
12594
12595 $beanOrBeans = $oodb->dispense( $type, $num, $alwaysReturnArray );
12596
12597 if ( isset( $import ) ) {
12598 $beanOrBeans->import( $import );
12599 }
12600
12601 return $beanOrBeans;
12602 }
12603
12604
12605 /**
12606 * Takes a comma separated list of bean types
12607 * and dispenses these beans. For each type in the list
12608 * you can specify the number of beans to be dispensed.
12609 *
12610 * Usage:
12611 *
12612 * <code>
12613 * list( $book, $page, $text ) = R::dispenseAll( 'book,page,text' );
12614 * </code>
12615 *
12616 * This will dispense a book, a page and a text. This way you can
12617 * quickly dispense beans of various types in just one line of code.
12618 *
12619 * Usage:
12620 *
12621 * <code>
12622 * list($book, $pages) = R::dispenseAll('book,page*100');
12623 * </code>
12624 *
12625 * This returns an array with a book bean and then another array
12626 * containing 100 page beans.
12627 *
12628 * @param OODB $oodb OODB
12629 * @param string $order a description of the desired dispense order using the syntax above
12630 * @param boolean $onlyArrays return only arrays even if amount < 2
12631 *
12632 * @return array
12633 */
12634 public static function dispenseAll( OODB $oodb, $order, $onlyArrays = FALSE )
12635 {
12636 $list = array();
12637
12638 foreach( explode( ',', $order ) as $order ) {
12639 if ( strpos( $order, '*' ) !== FALSE ) {
12640 list( $type, $amount ) = explode( '*', $order );
12641 } else {
12642 $type = $order;
12643 $amount = 1;
12644 }
12645
12646 $list[] = self::dispense( $oodb, $type, $amount, $onlyArrays );
12647 }
12648
12649 return $list;
12650 }
12651}
12652}
12653
12654namespace RedBeanPHP\Util {
12655
12656use RedBeanPHP\OODB as OODB;
12657use RedBeanPHP\OODBBean as OODBBean;
12658
12659/**
12660 * Dump helper
12661 *
12662 * This code was originally part of the facade, however it has
12663 * been decided to remove unique features to service classes like
12664 * this to make them available to developers not using the facade class.
12665 *
12666 * Dumps the contents of a bean in an array for
12667 * debugging purposes.
12668 *
12669 * @file RedBeanPHP/Util/Dump.php
12670 * @author Gabor de Mooij and the RedBeanPHP Community
12671 * @license BSD/GPLv2
12672 *
12673 * @copyright
12674 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
12675 * This source file is subject to the BSD/GPLv2 License that is bundled
12676 * with this source code in the file license.txt.
12677 */
12678class Dump
12679{
12680 /**
12681 * Simple but effective debug function.
12682 * Given a one or more beans this method will
12683 * return an array containing first part of the string
12684 * representation of each item in the array.
12685 *
12686 * @param OODBBean|array $data either a bean or an array of beans
12687 *
12688 * @return array
12689 */
12690 public static function dump( $data )
12691 {
12692 $array = array();
12693
12694 if ( $data instanceof OODBBean ) {
12695 $str = strval( $data );
12696 if (strlen($str) > 35) {
12697 $beanStr = substr( $str, 0, 35 ).'... ';
12698 } else {
12699 $beanStr = $str;
12700 }
12701 return $beanStr;
12702 }
12703
12704 if ( is_array( $data ) ) {
12705 foreach( $data as $key => $item ) {
12706 $array[$key] = self::dump( $item );
12707 }
12708 }
12709
12710 return $array;
12711 }
12712}
12713}
12714
12715namespace RedBeanPHP\Util {
12716
12717use RedBeanPHP\OODB as OODB;
12718
12719/**
12720 * Multi Bean Loader Helper
12721 *
12722 * This code was originally part of the facade, however it has
12723 * been decided to remove unique features to service classes like
12724 * this to make them available to developers not using the facade class.
12725 *
12726 * This helper class offers limited support for one-to-one
12727 * relations by providing a service to load a set of beans
12728 * with differnt types and a common ID.
12729 *
12730 * @file RedBeanPHP/Util/MultiLoader.php
12731 * @author Gabor de Mooij and the RedBeanPHP Community
12732 * @license BSD/GPLv2
12733 *
12734 * @copyright
12735 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
12736 * This source file is subject to the BSD/GPLv2 License that is bundled
12737 * with this source code in the file license.txt.
12738 */
12739class MultiLoader
12740{
12741 /**
12742 * Loads multiple types of beans with the same ID.
12743 * This might look like a strange method, however it can be useful
12744 * for loading a one-to-one relation.
12745 *
12746 * @param OODB $oodb OODB object
12747 * @param string|array $types the set of types to load at once
12748 * @param mixed $id the common ID
12749 *
12750 * @return OODBBean
12751 */
12752 public static function load( OODB $oodb, $types, $id )
12753 {
12754 if ( is_string( $types ) ) {
12755 $types = explode( ',', $types );
12756 }
12757
12758 if ( !is_array( $types ) ) {
12759 return array();
12760 }
12761
12762 foreach ( $types as $k => $typeItem ) {
12763 $types[$k] = $oodb->load( $typeItem, $id );
12764 }
12765
12766 return $types;
12767 }
12768}
12769}
12770
12771namespace RedBeanPHP\Util {
12772
12773use RedBeanPHP\OODB as OODB;
12774use RedBeanPHP\OODBBean as OODBBean;
12775use RedBeanPHP\RedException as RedException;
12776use RedBeanPHP\Adapter as Adapter;
12777
12778/**
12779 * Transaction Helper
12780 *
12781 * This code was originally part of the facade, however it has
12782 * been decided to remove unique features to service classes like
12783 * this to make them available to developers not using the facade class.
12784 *
12785 * Database transaction helper. This is a convenience class
12786 * to perform a callback in a database transaction. This class
12787 * contains a method to wrap your callback in a transaction.
12788 *
12789 * @file RedBeanPHP/Util/Transaction.php
12790 * @author Gabor de Mooij and the RedBeanPHP Community
12791 * @license BSD/GPLv2
12792 *
12793 * @copyright
12794 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
12795 * This source file is subject to the BSD/GPLv2 License that is bundled
12796 * with this source code in the file license.txt.
12797 */
12798class Transaction
12799{
12800 /**
12801 * Wraps a transaction around a closure or string callback.
12802 * If an Exception is thrown inside, the operation is automatically rolled back.
12803 * If no Exception happens, it commits automatically.
12804 * It also supports (simulated) nested transactions (that is useful when
12805 * you have many methods that needs transactions but are unaware of
12806 * each other).
12807 *
12808 * Example:
12809 *
12810 * <code>
12811 * $from = 1;
12812 * $to = 2;
12813 * $amount = 300;
12814 *
12815 * R::transaction(function() use($from, $to, $amount)
12816 * {
12817 * $accountFrom = R::load('account', $from);
12818 * $accountTo = R::load('account', $to);
12819 * $accountFrom->money -= $amount;
12820 * $accountTo->money += $amount;
12821 * R::store($accountFrom);
12822 * R::store($accountTo);
12823 * });
12824 * </code>
12825 *
12826 * @param Adapter $adapter Database Adapter providing transaction mechanisms.
12827 * @param callable $callback Closure (or other callable) with the transaction logic
12828 *
12829 * @return mixed
12830 */
12831 public static function transaction( Adapter $adapter, $callback )
12832 {
12833 if ( !is_callable( $callback ) ) {
12834 throw new RedException( 'R::transaction needs a valid callback.' );
12835 }
12836
12837 static $depth = 0;
12838 $result = null;
12839 try {
12840 if ( $depth == 0 ) {
12841 $adapter->startTransaction();
12842 }
12843 $depth++;
12844 $result = call_user_func( $callback ); //maintain 5.2 compatibility
12845 $depth--;
12846 if ( $depth == 0 ) {
12847 $adapter->commit();
12848 }
12849 } catch ( \Exception $exception ) {
12850 $depth--;
12851 if ( $depth == 0 ) {
12852 $adapter->rollback();
12853 }
12854 throw $exception;
12855 }
12856 return $result;
12857 }
12858}
12859}
12860
12861namespace RedBeanPHP {
12862
12863/**
12864 * RedBean Plugin.
12865 * Marker interface for plugins.
12866 *
12867 * @file RedBean/Plugin.php
12868 * @author Gabor de Mooij and the RedBeanPHP Community
12869 * @license BSD/GPLv2
12870 *
12871 * @copyright
12872 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
12873 * This source file is subject to the BSD/GPLv2 License that is bundled
12874 * with this source code in the file license.txt.
12875 */
12876interface Plugin
12877{
12878}
12879
12880;
12881}
12882 namespace {
12883
12884 //make some classes available for backward compatibility
12885 class RedBean_SimpleModel extends \RedBeanPHP\SimpleModel {};
12886
12887 if (!class_exists('R')) {
12888 class R extends \RedBeanPHP\Facade{};
12889 }
12890
12891
12892
12893/**
12894 * Support functions for RedBeanPHP.
12895 * Additional convenience shortcut functions for RedBeanPHP.
12896 *
12897 * @file RedBeanPHP/Functions.php
12898 * @author Gabor de Mooij and the RedBeanPHP community
12899 * @license BSD/GPLv2
12900 *
12901 * @copyright
12902 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
12903 * This source file is subject to the BSD/GPLv2 License that is bundled
12904 * with this source code in the file license.txt.
12905 */
12906
12907/**
12908 * Convenience function for ENUM short syntax in queries.
12909 *
12910 * Usage:
12911 *
12912 * <code>
12913 * R::find( 'paint', ' color_id = ? ', [ EID('color:yellow') ] );
12914 * </code>
12915 *
12916 * If a function called EID() already exists you'll have to write this
12917 * wrapper yourself ;)
12918 *
12919 * @param string $enumName enum code as you would pass to R::enum()
12920 *
12921 * @return mixed
12922 */
12923if (!function_exists('EID')) {
12924
12925 function EID($enumName)
12926 {
12927 return \RedBeanPHP\Facade::enum( $enumName )->id;
12928 }
12929
12930}
12931
12932/**
12933 * Prints the result of R::dump() to the screen using
12934 * print_r.
12935 *
12936 * @param mixed $data data to dump
12937 *
12938 * @return void
12939 */
12940if ( !function_exists( 'dump' ) ) {
12941
12942 function dmp( $list )
12943 {
12944 print_r( \RedBeanPHP\Facade::dump( $list ) );
12945 }
12946}
12947
12948/**
12949 * Function alias for R::genSlots().
12950 */
12951if ( !function_exists( 'genslots' ) ) {
12952
12953 function genslots( $slots, $tpl = NULL )
12954 {
12955 return \RedBeanPHP\Facade::genSlots( $slots, $tpl );
12956 }
12957}
12958
12959/**
12960 * Function alias for R::flat().
12961 */
12962if ( !function_exists( 'array_flatten' ) ) {
12963
12964 function array_flatten( $array )
12965 {
12966 return \RedBeanPHP\Facade::flat( $array );
12967 }
12968}
12969
12970
12971 }