Issues (262)

RedBean.php (2 issues)

1
<?php
2
3
namespace 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
 */
19
interface 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
	 *
32
	 * @return void
33
	 */
34
	public function log();
35
}
36
}
37
38
namespace RedBeanPHP\Logger {
39
40
use RedBeanPHP\Logger as Logger;
41
use RedBeanPHP\RedException as RedException;
42
43
/**
44
 * Logger. Provides a basic logging function for RedBeanPHP.
45
 *
46
 * @file    RedBeanPHP/Logger.php
47
 * @author  Gabor de Mooij and the RedBeanPHP Community
48
 * @license BSD/GPLv2
49
 *
50
 * @copyright
51
 * copyright (c) G.J.G.T. (Gabor) de Mooij
52
 * This source file is subject to the BSD/GPLv2 License that is bundled
53
 * with this source code in the file license.txt.
54
 */
55
class RDefault implements Logger
56
{
57
	/**
58
	 * Logger modes
59
	 */
60
	const C_LOGGER_ECHO  = 0;
61
	const C_LOGGER_ARRAY = 1;
62
63
	/**
64
	 * @var integer
65
	 */
66
	protected $mode = 0;
67
68
	/**
69
	 * @var array
70
	 */
71
	protected $logs = array();
72
73
	/**
74
	 * Default logger method logging to STDOUT.
75
	 * This is the default/reference implementation of a logger.
76
	 * This method will write the message value to STDOUT (screen) unless
77
	 * you have changed the mode of operation to C_LOGGER_ARRAY.
78
	 *
79
	 * @param $message (optional) message to log (might also be data or output)
80
	 *
81
	 * @return void
82
	 */
83
	public function log()
84
	{
85
		if ( func_num_args() < 1 ) return;
86
87
		foreach ( func_get_args() as $argument ) {
88
			if ( is_array( $argument ) ) {
89
				$log = var_export( $argument, TRUE );
90
				if ( $this->mode === self::C_LOGGER_ECHO ) {
91
					echo $log;
92
				} else {
93
					$this->logs[] = $log;
94
				}
95
			} else {
96
				if ( $this->mode === self::C_LOGGER_ECHO ) {
97
					echo $argument;
98
				} else {
99
					$this->logs[] = $argument;
100
				}
101
			}
102
103
			if ( $this->mode === self::C_LOGGER_ECHO ) echo "<br>" . PHP_EOL;
104
		}
105
	}
106
107
	/**
108
	 * Returns the internal log array.
109
	 * The internal log array is where all log messages are stored.
110
	 *
111
	 * @return array
112
	 */
113
	public function getLogs()
114
	{
115
		return $this->logs;
116
	}
117
118
	/**
119
	 * Clears the internal log array, removing all
120
	 * previously stored entries.
121
	 *
122
	 * @return self
123
	 */
124
	public function clear()
125
	{
126
		$this->logs = array();
127
		return $this;
128
	}
129
130
	/**
131
	 * Selects a logging mode.
132
	 * There are several options available.
133
	 *
134
	 * * C_LOGGER_ARRAY - log silently, stores entries in internal log array only
135
	 * * C_LOGGER_ECHO  - also forward log messages directly to STDOUT
136
	 *
137
	 * @param integer $mode mode of operation for logging object
138
	 *
139
	 * @return self
140
	 */
141
	public function setMode( $mode )
142
	{
143
		if ($mode !== self::C_LOGGER_ARRAY && $mode !== self::C_LOGGER_ECHO ) {
144
			throw new RedException( 'Invalid mode selected for logger, use C_LOGGER_ARRAY or C_LOGGER_ECHO.' );
145
		}
146
		$this->mode = $mode;
147
		return $this;
148
	}
149
150
	/**
151
	 * Searches for all log entries in internal log array
152
	 * for $needle and returns those entries.
153
	 * This method will return an array containing all matches for your
154
	 * search query.
155
	 *
156
	 * @param string $needle phrase to look for in internal log array
157
	 *
158
	 * @return array
159
	 */
160
	public function grep( $needle )
161
	{
162
		$found = array();
163
		foreach( $this->logs as $logEntry ) {
164
			if ( strpos( $logEntry, $needle ) !== FALSE ) $found[] = $logEntry;
165
		}
166
		return $found;
167
	}
168
}
169
}
170
171
namespace RedBeanPHP\Logger\RDefault {
172
173
use RedBeanPHP\Logger as Logger;
174
use RedBeanPHP\Logger\RDefault as RDefault;
175
use RedBeanPHP\RedException as RedException;
176
177
/**
178
 * Debug logger.
179
 * A special logger for debugging purposes.
180
 * Provides debugging logging functions for RedBeanPHP.
181
 *
182
 * @file    RedBeanPHP/Logger/RDefault/Debug.php
183
 * @author  Gabor de Mooij and the RedBeanPHP Community
184
 * @license BSD/GPLv2
185
 *
186
 * @copyright
187
 * copyright (c) G.J.G.T. (Gabor) de Mooij
188
 * This source file is subject to the BSD/GPLv2 License that is bundled
189
 * with this source code in the file license.txt.
190
 */
191
class Debug extends RDefault implements Logger
192
{
193
	/**
194
	 * @var integer
195
	 */
196
	protected $strLen = 40;
197
198
	/**
199
	 * @var boolean
200
	 */
201
	protected static $noCLI = FALSE;
202
203
	/**
204
	 * Toggles CLI override. By default debugging functions will
205
	 * output differently based on PHP_SAPI values. This function
206
	 * allows you to override the PHP_SAPI setting. If you set
207
	 * this to TRUE, CLI output will be supressed in favour of
208
	 * HTML output. So, to get HTML on the command line use
209
	 * setOverrideCLIOutput( TRUE ).
210
	 *
211
	 * @param boolean $yesNo CLI-override setting flag
212
	 *
213
	 * @return void
214
	 */
215
	public static function setOverrideCLIOutput( $yesNo )
216
	{
217
		self::$noCLI = $yesNo;
218
	}
219
220
	/**
221
	 * Writes a query for logging with all bindings / params filled
222
	 * in.
223
	 *
224
	 * @param string $newSql      the query
225
	 * @param array  $newBindings the bindings to process (key-value pairs)
226
	 *
227
	 * @return string
228
	 */
229
	protected function writeQuery( $newSql, $newBindings )
230
	{
231
		//avoid str_replace collisions: slot1 and slot10 (issue 407).
232
		uksort( $newBindings, function( $a, $b ) {
233
			return ( strlen( $b ) - strlen( $a ) );
234
		} );
235
236
		$newStr = $newSql;
237
		foreach( $newBindings as $slot => $value ) {
238
			if ( strpos( $slot, ':' ) === 0 ) {
239
				$newStr = str_replace( $slot, $this->fillInValue( $value ), $newStr );
240
			}
241
		}
242
		return $newStr;
243
	}
244
245
	/**
246
	 * Fills in a value of a binding and truncates the
247
	 * resulting string if necessary.
248
	 *
249
	 * @param mixed $value bound value
250
	 *
251
	 * @return string
252
	 */
253
	protected function fillInValue( $value )
254
	{
255
		if ( is_null( $value ) ) $value = 'NULL';
256
257
		$value = strval( $value );
258
		if ( strlen( $value ) > ( $this->strLen ) ) {
259
			$value = substr( $value, 0, ( $this->strLen ) ).'... ';
260
		}
261
262
		if ( !\RedBeanPHP\QueryWriter\AQueryWriter::canBeTreatedAsInt( $value ) && $value !== 'NULL') {
263
			$value = '\''.$value.'\'';
264
		}
265
266
		return $value;
267
	}
268
269
	/**
270
	 * Dependending on the current mode of operation,
271
	 * this method will either log and output to STDIN or
272
	 * just log.
273
	 *
274
	 * Depending on the value of constant PHP_SAPI this function
275
	 * will format output for console or HTML.
276
	 *
277
	 * @param string $str string to log or output and log
278
	 *
279
	 * @return void
280
	 */
281
	protected function output( $str )
282
	{
283
		$this->logs[] = $str;
284
		if ( !$this->mode ) {
285
			$highlight = FALSE;
286
			/* just a quick heuritsic to highlight schema changes */
287
			if ( strpos( $str, 'CREATE' ) === 0
288
			|| strpos( $str, 'ALTER' ) === 0
289
			|| strpos( $str, 'DROP' ) === 0) {
290
				$highlight = TRUE;
291
			}
292
			if (PHP_SAPI === 'cli' && !self::$noCLI) {
293
				if ($highlight) echo "\e[91m";
294
				echo $str, PHP_EOL;
295
				echo "\e[39m";
296
			} else {
297
				if ($highlight) {
298
					echo "<b style=\"color:red\">{$str}</b>";
299
				} else {
300
					echo $str;
301
				}
302
				echo '<br />';
303
			}
304
		}
305
	}
306
307
	/**
308
	 * Normalizes the slots in an SQL string.
309
	 * Replaces question mark slots with :slot1 :slot2 etc.
310
	 *
311
	 * @param string $sql sql to normalize
312
	 *
313
	 * @return string
314
	 */
315
	protected function normalizeSlots( $sql )
316
	{
317
		$newSql = $sql;
318
		$i = 0;
319
		while(strpos($newSql, '?') !== FALSE ){
320
			$pos   = strpos( $newSql, '?' );
321
			$slot  = ':slot'.$i;
322
			$begin = substr( $newSql, 0, $pos );
323
			$end   = substr( $newSql, $pos+1 );
324
			if (PHP_SAPI === 'cli' && !self::$noCLI) {
325
				$newSql = "{$begin}\e[32m{$slot}\e[39m{$end}";
326
			} else {
327
				$newSql = "{$begin}<b style=\"color:green\">$slot</b>{$end}";
328
			}
329
			$i ++;
330
		}
331
		return $newSql;
332
	}
333
334
	/**
335
	 * Normalizes the bindings.
336
	 * Replaces numeric binding keys with :slot1 :slot2 etc.
337
	 *
338
	 * @param array $bindings bindings to normalize
339
	 *
340
	 * @return array
341
	 */
342
	protected function normalizeBindings( $bindings )
343
	{
344
		$i = 0;
345
		$newBindings = array();
346
		foreach( $bindings as $key => $value ) {
347
			if ( is_numeric($key) ) {
348
				$newKey = ':slot'.$i;
349
				$newBindings[$newKey] = $value;
350
				$i++;
351
			} else {
352
				$newBindings[$key] = $value;
353
			}
354
		}
355
		return $newBindings;
356
	}
357
358
	/**
359
	 * Logger method.
360
	 *
361
	 * Takes a number of arguments tries to create
362
	 * a proper debug log based on the available data.
363
	 *
364
	 * @return void
365
	 */
366
	public function log()
367
	{
368
		if ( func_num_args() < 1 ) return;
369
370
		$sql = func_get_arg( 0 );
371
372
		if ( func_num_args() < 2) {
373
			$bindings = array();
374
		} else {
375
			$bindings = func_get_arg( 1 );
376
		}
377
378
		if ( !is_array( $bindings ) ) {
379
			return $this->output( $sql );
380
		}
381
382
		$newSql = $this->normalizeSlots( $sql );
383
		$newBindings = $this->normalizeBindings( $bindings );
384
		$newStr = $this->writeQuery( $newSql, $newBindings );
385
		$this->output( $newStr );
386
	}
387
388
	/**
389
	 * Sets the max string length for the parameter output in
390
	 * SQL queries. Set this value to a reasonable number to
391
	 * keep you SQL queries readable.
392
	 *
393
	 * @param integer $len string length
394
	 *
395
	 * @return self
396
	 */
397
	public function setParamStringLength( $len = 20 )
398
	{
399
		$this->strLen = max(0, $len);
400
		return $this;
401
	}
402
}
403
}
404
405
namespace RedBeanPHP {
406
407
/**
408
 * Interface for database drivers.
409
 * The Driver API conforms to the ADODB pseudo standard
410
 * for database drivers.
411
 *
412
 * @file       RedBeanPHP/Driver.php
413
 * @author     Gabor de Mooij and the RedBeanPHP Community
414
 * @license    BSD/GPLv2
415
 *
416
 * @copyright
417
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
418
 * This source file is subject to the BSD/GPLv2 License that is bundled
419
 * with this source code in the file license.txt.
420
 */
421
interface Driver
422
{
423
	/**
424
	 * Runs a query and fetches results as a multi dimensional array.
425
	 *
426
	 * @param string $sql      SQL query to execute
427
	 * @param array  $bindings list of values to bind to SQL snippet
428
	 *
429
	 * @return array
430
	 */
431
	public function GetAll( $sql, $bindings = array() );
432
433
	/**
434
	 * Runs a query and fetches results as a column.
435
	 *
436
	 * @param string $sql      SQL query to execute
437
	 * @param array  $bindings list of values to bind to SQL snippet
438
	 *
439
	 * @return array
440
	 */
441
	public function GetCol( $sql, $bindings = array() );
442
443
	/**
444
	 * Runs a query and returns results as a single cell.
445
	 *
446
	 * @param string $sql      SQL query to execute
447
	 * @param array  $bindings list of values to bind to SQL snippet
448
	 *
449
	 * @return mixed
450
	 */
451
	public function GetOne( $sql, $bindings = array() );
452
453
	/**
454
	 * Runs a query and returns results as an associative array
455
	 * indexed by the first column.
456
	 *
457
	 * @param string $sql      SQL query to execute
458
	 * @param array  $bindings list of values to bind to SQL snippet
459
	 *
460
	 * @return mixed
461
	 */
462
	public function GetAssocRow( $sql, $bindings = array() );
463
464
	/**
465
	 * Runs a query and returns a flat array containing the values of
466
	 * one row.
467
	 *
468
	 * @param string $sql      SQL query to execute
469
	 * @param array  $bindings list of values to bind to SQL snippet
470
	 *
471
	 * @return array
472
	 */
473
	public function GetRow( $sql, $bindings = array() );
474
475
	/**
476
	 * Executes SQL code and allows key-value binding.
477
	 * This function allows you to provide an array with values to bind
478
	 * to query parameters. For instance you can bind values to question
479
	 * marks in the query. Each value in the array corresponds to the
480
	 * question mark in the query that matches the position of the value in the
481
	 * array. You can also bind values using explicit keys, for instance
482
	 * array(":key"=>123) will bind the integer 123 to the key :key in the
483
	 * SQL. This method has no return value.
484
	 *
485
	 * @param string $sql      SQL query to execute
486
	 * @param array  $bindings list of values to bind to SQL snippet
487
	 *
488
	 * @return array Affected Rows
489
	 */
490
	public function Execute( $sql, $bindings = array() );
491
492
	/**
493
	 * Returns the latest insert ID if driver does support this
494
	 * feature.
495
	 *
496
	 * @return integer
497
	 */
498
	public function GetInsertID();
499
500
	/**
501
	 * Returns the number of rows affected by the most recent query
502
	 * if the currently selected driver driver supports this feature.
503
	 *
504
	 * @return integer
505
	 */
506
	public function Affected_Rows();
507
508
	/**
509
	 * Returns a cursor-like object from the database.
510
	 *
511
	 * @param string $sql      SQL query to execute
512
	 * @param array  $bindings list of values to bind to SQL snippet
513
	 *
514
	 * @return mixed
515
	 */
516
	public function GetCursor( $sql, $bindings = array() );
517
518
	/**
519
	 * Toggles debug mode. In debug mode the driver will print all
520
	 * SQL to the screen together with some information about the
521
	 * results.
522
	 *
523
	 * This method is for more fine-grained control. Normally
524
	 * you should use the facade to start the query debugger for
525
	 * you. The facade will manage the object wirings necessary
526
	 * to use the debugging functionality.
527
	 *
528
	 * Usage (through facade):
529
	 *
530
	 * <code>
531
	 * R::debug( TRUE );
532
	 * ...rest of program...
533
	 * R::debug( FALSE );
534
	 * </code>
535
	 *
536
	 * The example above illustrates how to use the RedBeanPHP
537
	 * query debugger through the facade.
538
	 *
539
	 * @param boolean $trueFalse turn on/off
540
	 * @param Logger  $logger    logger instance
541
	 *
542
	 * @return void
543
	 */
544
	public function setDebugMode( $tf, $customLogger );
545
546
	/**
547
	 * Starts a transaction.
548
	 *
549
	 * @return void
550
	 */
551
	public function CommitTrans();
552
553
	/**
554
	 * Commits a transaction.
555
	 *
556
	 * @return void
557
	 */
558
	public function StartTrans();
559
560
	/**
561
	 * Rolls back a transaction.
562
	 *
563
	 * @return void
564
	 */
565
	public function FailTrans();
566
567
	/**
568
	 * Resets the internal Query Counter.
569
	 *
570
	 * @return self
571
	 */
572
	public function resetCounter();
573
574
	/**
575
	 * Returns the number of SQL queries processed.
576
	 *
577
	 * @return integer
578
	 */
579
	public function getQueryCount();
580
}
581
}
582
583
namespace RedBeanPHP\Driver {
584
585
use RedBeanPHP\Driver as Driver;
586
use RedBeanPHP\Logger as Logger;
587
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
588
use RedBeanPHP\RedException as RedException;
589
use RedBeanPHP\RedException\SQL as SQL;
590
use RedBeanPHP\Logger\RDefault as RDefault;
591
use RedBeanPHP\PDOCompatible as PDOCompatible;
592
use RedBeanPHP\Cursor\PDOCursor as PDOCursor;
593
594
/**
595
 * PDO Driver
596
 * This Driver implements the RedBean Driver API.
597
 * for RedBeanPHP. This is the standard / default database driver
598
 * for RedBeanPHP.
599
 *
600
 * @file    RedBeanPHP/PDO.php
601
 * @author  Gabor de Mooij and the RedBeanPHP Community, Desfrenes
602
 * @license BSD/GPLv2
603
 *
604
 * @copyright
605
 * copyright (c) Desfrenes & Gabor de Mooij and the RedBeanPHP community
606
 * This source file is subject to the BSD/GPLv2 License that is bundled
607
 * with this source code in the file license.txt.
608
 */
609
class RPDO implements Driver
610
{
611
	/**
612
	 * @var integer
613
	 */
614
	protected $max;
615
616
	/**
617
	 * @var string
618
	 */
619
	protected $dsn;
620
621
	/**
622
	 * @var boolean
623
	 */
624
	protected $loggingEnabled = FALSE;
625
626
	/**
627
	 * @var Logger
628
	 */
629
	protected $logger = NULL;
630
631
	/**
632
	 * @var PDO
633
	 */
634
	protected $pdo;
635
636
	/**
637
	 * @var integer
638
	 */
639
	protected $affectedRows;
640
641
	/**
642
	 * @var integer
643
	 */
644
	protected $resultArray;
645
646
	/**
647
	 * @var array
648
	 */
649
	protected $connectInfo = array();
650
651
	/**
652
	 * @var boolean
653
	 */
654
	protected $isConnected = FALSE;
655
656
	/**
657
	 * @var bool
658
	 */
659
	protected $flagUseStringOnlyBinding = FALSE;
660
661
	/**
662
	 * @var integer
663
	 */
664
	protected $queryCounter = 0;
665
666
	/**
667
	 * @var string
668
	 */
669
	protected $mysqlCharset = '';
670
	
671
	/**
672
	 * @var string
673
	 */
674
	protected $mysqlCollate = '';
675
676
	/**
677
	 * @var boolean
678
	 */
679
	protected $stringifyFetches = TRUE;
680
681
	/**
682
	 * @var string
683
	 */
684
	protected $initSQL = NULL;
685
686
	/**
687
	 * Binds parameters. This method binds parameters to a PDOStatement for
688
	 * Query Execution. This method binds parameters as NULL, INTEGER or STRING
689
	 * and supports both named keys and question mark keys.
690
	 *
691
	 * @param PDOStatement $statement PDO Statement instance
692
	 * @param array        $bindings  values that need to get bound to the statement
693
	 *
694
	 * @return void
695
	 */
696
	protected function bindParams( $statement, $bindings )
697
	{
698
		foreach ( $bindings as $key => &$value ) {
699
			$k = is_integer( $key ) ? $key + 1 : $key;
700
701
			if ( is_array( $value ) && count( $value ) == 2 ) {
702
				$paramType = end( $value );
703
				$value = reset( $value );
704
			} else {
705
				$paramType = NULL;
706
			}
707
708
			if ( is_null( $value ) ) {
709
				$statement->bindValue( $k, NULL, \PDO::PARAM_NULL );
710
				continue;
711
			}
712
713
			if ( $paramType != \PDO::PARAM_INT && $paramType != \PDO::PARAM_STR ) {
714
				if ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
715
					$paramType = \PDO::PARAM_INT;
716
				} else {
717
					$paramType = \PDO::PARAM_STR;
718
				}
719
			}
720
721
			$statement->bindParam( $k, $value, $paramType );
722
		}
723
	}
724
725
	/**
726
	 * This method runs the actual SQL query and binds a list of parameters to the query.
727
	 * slots. The result of the query will be stored in the protected property
728
	 * $rs (always array). The number of rows affected (result of rowcount, if supported by database)
729
	 * is stored in protected property $affectedRows. If the debug flag is set
730
	 * this function will send debugging output to screen buffer.
731
	 *
732
	 * @param string $sql      the SQL string to be send to database server
733
	 * @param array  $bindings the values that need to get bound to the query slots
734
	 * @param array  $options
735
	 *
736
	 * @return mixed
737
	 * @throws SQL
738
	 */
739
	protected function runQuery( $sql, $bindings, $options = array() )
740
	{
741
		$this->connect();
742
		if ( $this->loggingEnabled && $this->logger ) {
743
			$this->logger->log( $sql, $bindings );
744
		}
745
		try {
746
			if ( strpos( 'pgsql', $this->dsn ) === 0 ) {
747
				//one line because unable to test this otherwise (coverage trick).
748
				if ( defined( '\PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT' ) ) { $statement = $this->pdo->prepare( $sql, array( \PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => TRUE ) ); } else { $statement = $this->pdo->prepare( $sql ); }
749
			} else {
750
				$statement = $this->pdo->prepare( $sql );
751
			}
752
			$this->bindParams( $statement, $bindings );
753
			$statement->execute();
754
			$this->queryCounter ++;
755
			$this->affectedRows = $statement->rowCount();
756
			if ( $statement->columnCount() ) {
757
				$fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL;
758
				if ( isset( $options['noFetch'] ) && $options['noFetch'] ) {
759
					$this->resultArray = array();
760
					return $statement;
761
				}
762
				$this->resultArray = $statement->fetchAll( $fetchStyle );
763
				if ( $this->loggingEnabled && $this->logger ) {
764
					$this->logger->log( 'resultset: ' . count( $this->resultArray ) . ' rows' );
765
				}
766
			} else {
767
				$this->resultArray = array();
768
			}
769
		} catch ( \PDOException $e ) {
770
			//Unfortunately the code field is supposed to be int by default (php)
771
			//So we need a property to convey the SQL State code.
772
			$err = $e->getMessage();
773
			if ( $this->loggingEnabled && $this->logger ) $this->logger->log( 'An error occurred: ' . $err );
774
			$exception = new SQL( $err, 0, $e );
775
			$exception->setSQLState( $e->getCode() );
776
			$exception->setDriverDetails( $e->errorInfo );
777
			throw $exception;
778
		}
779
	}
780
781
	/**
782
	 * Try to fix MySQL character encoding problems.
783
	 * MySQL < 5.5.3 does not support proper 4 byte unicode but they
784
	 * seem to have added it with version 5.5.3 under a different label: utf8mb4.
785
	 * We try to select the best possible charset based on your version data.
786
	 *
787
	 * @return void
788
	 */
789
	protected function setEncoding()
790
	{
791
		$driver = $this->pdo->getAttribute( \PDO::ATTR_DRIVER_NAME );
792
		if ($driver === 'mysql') {
793
			$charset = $this->hasCap( 'utf8mb4' ) ? 'utf8mb4' : 'utf8';
794
			$collate = $this->hasCap( 'utf8mb4_520' ) ? '_unicode_520_ci' : '_unicode_ci';
795
			$this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '. $charset ); //on every re-connect
796
			/* #624 removed space before SET NAMES because it causes trouble with ProxySQL */
797
			$this->pdo->exec('SET NAMES '. $charset); //also for current connection
798
			$this->mysqlCharset = $charset;
799
			$this->mysqlCollate = $charset . $collate;
800
		}
801
	}
802
803
	/**
804
	 * Determine if a database supports a particular feature.
805
	 * Currently this function can be used to detect the following features:
806
	 *
807
	 * - utf8mb4
808
	 * - utf8mb4 520
809
	 *
810
	 * Usage:
811
	 *
812
	 * <code>
813
	 * $this->hasCap( 'utf8mb4_520' );
814
	 * </code>
815
	 *
816
	 * By default, RedBeanPHP uses this method under the hood to make sure
817
	 * you use the latest UTF8 encoding possible for your database.
818
	 *
819
	 * @param $db_cap identifier of database capability
820
	 *
821
	 * @return int|false Whether the database feature is supported, FALSE otherwise.
822
	 **/
823
	protected function hasCap( $db_cap )
824
	{
825
		$compare = FALSE;
826
		$version = $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION );
827
		switch ( strtolower( $db_cap ) ) {
828
			case 'utf8mb4':
829
				//oneliner, to boost code coverage (coverage does not span versions)
830
				if ( version_compare( $version, '5.5.3', '<' ) ) { return FALSE; }
831
				$client_version = $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
832
				/*
833
				 * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
834
				 * mysqlnd has supported utf8mb4 since 5.0.9.
835
				 */
836
				if ( strpos( $client_version, 'mysqlnd' ) !== FALSE ) {
837
					$client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $client_version );
838
					$compare = version_compare( $client_version, '5.0.9', '>=' );
839
				} else {
840
					$compare = version_compare( $client_version, '5.5.3', '>=' );
841
				}
842
			break;
843
			case 'utf8mb4_520':
844
				$compare = version_compare( $version, '5.6', '>=' );
845
			break;
846
		}
847
848
		return $compare;
849
	}
850
851
	/**
852
	 * Constructor. You may either specify dsn, user and password or
853
	 * just give an existing PDO connection.
854
	 *
855
	 * Usage:
856
	 *
857
	 * <code>
858
	 * $driver = new RPDO( $dsn, $user, $password );
859
	 * </code>
860
	 *
861
	 * The example above illustrates how to create a driver
862
	 * instance from a database connection string (dsn), a username
863
	 * and a password. It's also possible to pass a PDO object.
864
	 *
865
	 * Usage:
866
	 *
867
	 * <code>
868
	 * $driver = new RPDO( $existingConnection );
869
	 * </code>
870
	 *
871
	 * The second example shows how to create an RPDO instance
872
	 * from an existing PDO object.
873
	 *
874
	 * @param string|object $dsn  database connection string
875
	 * @param string        $user optional, usename to sign in
876
	 * @param string        $pass optional, password for connection login
877
	 *
878
	 * @return void
879
	 */
880
	public function __construct( $dsn, $user = NULL, $pass = NULL )
881
	{
882
		if ( is_object( $dsn ) ) {
883
			$this->pdo = $dsn;
884
			$this->isConnected = TRUE;
885
			$this->setEncoding();
886
			$this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION );
887
			$this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC );
888
			// make sure that the dsn at least contains the type
889
			$this->dsn = $this->getDatabaseType();
890
		} else {
891
			$this->dsn = $dsn;
892
			$this->connectInfo = array( 'pass' => $pass, 'user' => $user );
893
		}
894
895
		//PHP 5.3 PDO SQLite has a bug with large numbers:
896
		if ( ( strpos( $this->dsn, 'sqlite' ) === 0 && PHP_MAJOR_VERSION === 5 && PHP_MINOR_VERSION === 3 ) ||  defined('HHVM_VERSION') || $this->dsn === 'test-sqlite-53' ) {
897
			$this->max = 2147483647; //otherwise you get -2147483648 ?! demonstrated in build #603 on Travis.
898
		} elseif ( strpos( $this->dsn, 'cubrid' ) === 0 ) {
899
			$this->max = 2147483647; //bindParam in pdo_cubrid also fails...
900
		} else {
901
			$this->max = PHP_INT_MAX; //the normal value of course (makes it possible to use large numbers in LIMIT clause)
902
		}
903
	}
904
905
	/**
906
	 * Sets PDO in stringify fetch mode.
907
	 * If set to TRUE, this method will make sure all data retrieved from
908
	 * the database will be fetched as a string. Default: TRUE.
909
	 *
910
	 * To set it to FALSE...
911
	 *
912
	 * Usage:
913
	 *
914
	 * <code>
915
	 * R::getDatabaseAdapter()->getDatabase()->stringifyFetches( FALSE );
916
	 * </code>
917
	 *
918
	 * Important!
919
	 * Note, this method only works if you set the value BEFORE the connection
920
	 * has been establish. Also, this setting ONLY works with SOME drivers.
921
	 * It's up to the driver to honour this setting.
922
	 *
923
	 * @param boolean $bool
924
	 */
925
	public function stringifyFetches( $bool ) {
926
		$this->stringifyFetches = $bool;
927
	}
928
929
	/**
930
	 * Returns the best possible encoding for MySQL based on version data.
931
	 * This method can be used to obtain the best character set parameters
932
	 * possible for your database when constructing a table creation query
933
	 * containing clauses like:  CHARSET=... COLLATE=...
934
	 * This is a MySQL-specific method and not part of the driver interface.
935
	 *
936
	 * Usage:
937
	 *
938
	 * <code>
939
	 * $charset_collate = $this->adapter->getDatabase()->getMysqlEncoding( TRUE );
940
	 * </code>
941
	 *
942
	 * @param boolean $retCol pass TRUE to return both charset/collate
943
	 *
944
	 * @return string|array
945
	 */
946
	public function getMysqlEncoding( $retCol = FALSE )
947
	{
948
		if( $retCol )
949
			return array( 'charset' => $this->mysqlCharset, 'collate' => $this->mysqlCollate );
950
		return $this->mysqlCharset;
951
	}
952
953
	/**
954
	 * Whether to bind all parameters as strings.
955
	 * If set to TRUE this will cause all integers to be bound as STRINGS.
956
	 * This will NOT affect NULL values.
957
	 *
958
	 * @param boolean $yesNo pass TRUE to bind all parameters as strings.
959
	 *
960
	 * @return void
961
	 */
962
	public function setUseStringOnlyBinding( $yesNo )
963
	{
964
		$this->flagUseStringOnlyBinding = (boolean) $yesNo;
965
	}
966
967
	/**
968
	 * Sets the maximum value to be bound as integer, normally
969
	 * this value equals PHP's MAX INT constant, however sometimes
970
	 * PDO driver bindings cannot bind large integers as integers.
971
	 * This method allows you to manually set the max integer binding
972
	 * value to manage portability/compatibility issues among different
973
	 * PHP builds. This method will return the old value.
974
	 *
975
	 * @param integer $max maximum value for integer bindings
976
	 *
977
	 * @return integer
978
	 */
979
	public function setMaxIntBind( $max )
980
	{
981
		if ( !is_integer( $max ) ) throw new RedException( 'Parameter has to be integer.' );
982
		$oldMax = $this->max;
983
		$this->max = $max;
984
		return $oldMax;
985
	}
986
987
	/**
988
	 * Establishes a connection to the database using PHP\PDO
989
	 * functionality. If a connection has already been established this
990
	 * method will simply return directly. This method also turns on
991
	 * UTF8 for the database and PDO-ERRMODE-EXCEPTION as well as
992
	 * PDO-FETCH-ASSOC.
993
	 *
994
	 * @return void
995
	 */
996
	public function connect()
997
	{
998
		if ( $this->isConnected ) return;
999
		try {
1000
			$user = $this->connectInfo['user'];
1001
			$pass = $this->connectInfo['pass'];
1002
			$this->pdo = new \PDO( $this->dsn, $user, $pass );
1003
			$this->setEncoding();
1004
			$this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, $this->stringifyFetches );
1005
			//cant pass these as argument to constructor, CUBRID driver does not understand...
1006
			$this->pdo->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION );
1007
			$this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC );
1008
			$this->isConnected = TRUE;
1009
			/* run initialisation query if any */
1010
			if ( $this->initSQL !== NULL ) {
1011
				$this->Execute( $this->initSQL );
1012
				$this->initSQL = NULL;
1013
			}
1014
		} catch ( \PDOException $exception ) {
1015
			$matches = array();
1016
			$dbname  = ( preg_match( '/dbname=(\w+)/', $this->dsn, $matches ) ) ? $matches[1] : '?';
1017
			throw new \PDOException( 'Could not connect to database (' . $dbname . ').', $exception->getCode() );
1018
		}
1019
	}
1020
1021
	/**
1022
	 * Directly sets PDO instance into driver.
1023
	 * This method might improve performance, however since the driver does
1024
	 * not configure this instance terrible things may happen... only use
1025
	 * this method if you are an expert on RedBeanPHP, PDO and UTF8 connections and
1026
	 * you know your database server VERY WELL.
1027
	 *
1028
	 * @param PDO $pdo PDO instance
1029
	 *
1030
	 * @return void
1031
	 */
1032
	public function setPDO( \PDO $pdo ) {
1033
		$this->pdo = $pdo;
1034
	}
1035
1036
	/**
1037
	 * @see Driver::GetAll
1038
	 */
1039
	public function GetAll( $sql, $bindings = array() )
1040
	{
1041
		$this->runQuery( $sql, $bindings );
1042
		return $this->resultArray;
1043
	}
1044
1045
	/**
1046
	 * @see Driver::GetAssocRow
1047
	 */
1048
	public function GetAssocRow( $sql, $bindings = array() )
1049
	{
1050
		$this->runQuery( $sql, $bindings, array(
1051
				'fetchStyle' => \PDO::FETCH_ASSOC
1052
			)
1053
		);
1054
		return $this->resultArray;
1055
	}
1056
1057
	/**
1058
	 * @see Driver::GetCol
1059
	 */
1060
	public function GetCol( $sql, $bindings = array() )
1061
	{
1062
		$rows = $this->GetAll( $sql, $bindings );
1063
1064
		if ( empty( $rows ) || !is_array( $rows ) ) {
1065
			return array();
1066
		}
1067
1068
		$cols = array();
1069
		foreach ( $rows as $row ) {
1070
			$cols[] = reset( $row );
1071
		}
1072
1073
		return $cols;
1074
	}
1075
1076
	/**
1077
	 * @see Driver::GetOne
1078
	 */
1079
	public function GetOne( $sql, $bindings = array() )
1080
	{
1081
		$arr = $this->GetAll( $sql, $bindings );
1082
1083
		if ( empty( $arr[0] ) || !is_array( $arr[0] ) ) {
1084
			return NULL;
1085
		}
1086
1087
		return reset( $arr[0] );
1088
	}
1089
1090
	/**
1091
	 * Alias for getOne().
1092
	 * Backward compatibility.
1093
	 *
1094
	 * @param string $sql      SQL
1095
	 * @param array  $bindings bindings
1096
	 *
1097
	 * @return mixed
1098
	 */
1099
	public function GetCell( $sql, $bindings = array() )
1100
	{
1101
		return $this->GetOne( $sql, $bindings );
1102
	}
1103
1104
	/**
1105
	 * @see Driver::GetRow
1106
	 */
1107
	public function GetRow( $sql, $bindings = array() )
1108
	{
1109
		$arr = $this->GetAll( $sql, $bindings );
1110
		return reset( $arr );
1111
	}
1112
1113
	/**
1114
	 * @see Driver::Excecute
1115
	 */
1116
	public function Execute( $sql, $bindings = array() )
1117
	{
1118
		$this->runQuery( $sql, $bindings );
1119
		return $this->affectedRows;
1120
	}
1121
1122
	/**
1123
	 * @see Driver::GetInsertID
1124
	 */
1125
	public function GetInsertID()
1126
	{
1127
		$this->connect();
1128
1129
		return (int) $this->pdo->lastInsertId();
1130
	}
1131
1132
	/**
1133
	 * @see Driver::GetCursor
1134
	 */
1135
	public function GetCursor( $sql, $bindings = array() )
1136
	{
1137
		$statement = $this->runQuery( $sql, $bindings, array( 'noFetch' => TRUE ) );
1138
		$cursor = new PDOCursor( $statement, \PDO::FETCH_ASSOC );
1139
		return $cursor;
1140
	}
1141
1142
	/**
1143
	 * @see Driver::Affected_Rows
1144
	 */
1145
	public function Affected_Rows()
1146
	{
1147
		$this->connect();
1148
		return (int) $this->affectedRows;
1149
	}
1150
1151
	/**
1152
	 * @see Driver::setDebugMode
1153
	 */
1154
	public function setDebugMode( $tf, $logger = NULL )
1155
	{
1156
		$this->connect();
1157
		$this->loggingEnabled = (bool) $tf;
1158
		if ( $this->loggingEnabled and !$logger ) {
1159
			$logger = new RDefault();
1160
		}
1161
		$this->setLogger( $logger );
1162
	}
1163
1164
	/**
1165
	 * Injects Logger object.
1166
	 * Sets the logger instance you wish to use.
1167
	 *
1168
	 * This method is for more fine-grained control. Normally
1169
	 * you should use the facade to start the query debugger for
1170
	 * you. The facade will manage the object wirings necessary
1171
	 * to use the debugging functionality.
1172
	 *
1173
	 * Usage (through facade):
1174
	 *
1175
	 * <code>
1176
	 * R::debug( TRUE );
1177
	 * ...rest of program...
1178
	 * R::debug( FALSE );
1179
	 * </code>
1180
	 *
1181
	 * The example above illustrates how to use the RedBeanPHP
1182
	 * query debugger through the facade.
1183
	 *
1184
	 * @param Logger $logger the logger instance to be used for logging
1185
	 *
1186
	 * @return self
1187
	 */
1188
	public function setLogger( Logger $logger )
1189
	{
1190
		$this->logger = $logger;
1191
		return $this;
1192
	}
1193
1194
	/**
1195
	 * Gets Logger object.
1196
	 * Returns the currently active Logger instance.
1197
	 *
1198
	 * @return Logger
1199
	 */
1200
	public function getLogger()
1201
	{
1202
		return $this->logger;
1203
	}
1204
1205
	/**
1206
	 * @see Driver::StartTrans
1207
	 */
1208
	public function StartTrans()
1209
	{
1210
		$this->connect();
1211
		$this->pdo->beginTransaction();
1212
	}
1213
1214
	/**
1215
	 * @see Driver::CommitTrans
1216
	 */
1217
	public function CommitTrans()
1218
	{
1219
		$this->connect();
1220
		$this->pdo->commit();
1221
	}
1222
1223
	/**
1224
	 * @see Driver::FailTrans
1225
	 */
1226
	public function FailTrans()
1227
	{
1228
		$this->connect();
1229
		$this->pdo->rollback();
1230
	}
1231
1232
	/**
1233
	 * Returns the name of database driver for PDO.
1234
	 * Uses the PDO attribute DRIVER NAME to obtain the name of the
1235
	 * PDO driver. Use this method to identify the current PDO driver
1236
	 * used to provide access to the database. Example of a database
1237
	 * driver string:
1238
	 *
1239
	 * <code>
1240
	 * mysql
1241
	 * </code>
1242
	 *
1243
	 * Usage:
1244
	 *
1245
	 * <code>
1246
	 * echo R::getDatabaseAdapter()->getDatabase()->getDatabaseType();
1247
	 * </code>
1248
	 *
1249
	 * The example above prints the current database driver string to
1250
	 * stdout.
1251
	 *
1252
	 * Note that this is a driver-specific method, not part of the
1253
	 * driver interface. This method might not be available in other
1254
	 * drivers since it relies on PDO.
1255
	 *
1256
	 * @return string
1257
	 */
1258
	public function getDatabaseType()
1259
	{
1260
		$this->connect();
1261
		return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME );
1262
	}
1263
1264
	/**
1265
	 * Returns the version identifier string of the database.
1266
	 * This method can be used to identify the currently installed
1267
	 * database. Note that this method will also establish a connection
1268
	 * (because this is required to obtain the version information).
1269
	 *
1270
	 * Example of a version string:
1271
	 *
1272
	 * <code>
1273
	 * mysqlnd 5.0.12-dev - 20150407 - $Id: b5c5906d452ec590732a93b051f3827e02749b83 $
1274
	 * </code>
1275
	 *
1276
	 * Usage:
1277
	 *
1278
	 * <code>
1279
	 * echo R::getDatabaseAdapter()->getDatabase()->getDatabaseVersion();
1280
	 * </code>
1281
	 *
1282
	 * The example above will print the version string to stdout.
1283
	 *
1284
	 * Note that this is a driver-specific method, not part of the
1285
	 * driver interface. This method might not be available in other
1286
	 * drivers since it relies on PDO.
1287
	 *
1288
	 * @return mixed
1289
	 */
1290
	public function getDatabaseVersion()
1291
	{
1292
		$this->connect();
1293
		return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
1294
	}
1295
1296
	/**
1297
	 * Returns the underlying PHP PDO instance.
1298
	 * For some low-level database operations you'll need access to the PDO
1299
	 * object. Not that this method is only available in RPDO and other
1300
	 * PDO based database drivers for RedBeanPHP. Other drivers may not have
1301
	 * a method like this. The following example demonstrates how to obtain
1302
	 * a reference to the PDO instance from the facade:
1303
	 *
1304
	 * Usage:
1305
	 *
1306
	 * <code>
1307
	 * $pdo = R::getDatabaseAdapter()->getDatabase()->getPDO();
1308
	 * </code>
1309
	 *
1310
	 * @return PDO
1311
	 */
1312
	public function getPDO()
1313
	{
1314
		$this->connect();
1315
		return $this->pdo;
1316
	}
1317
1318
	/**
1319
	 * Closes the database connection.
1320
	 * While database connections are closed automatically at the end of the PHP script,
1321
	 * closing database connections is generally recommended to improve performance.
1322
	 * Closing a database connection will immediately return the resources to PHP.
1323
	 *
1324
	 * Usage:
1325
	 *
1326
	 * <code>
1327
	 * R::setup( ... );
1328
	 * ... do stuff ...
1329
	 * R::close();
1330
	 * </code>
1331
	 *
1332
	 * @return void
1333
	 */
1334
	public function close()
1335
	{
1336
		$this->pdo         = NULL;
1337
		$this->isConnected = FALSE;
1338
	}
1339
1340
	/**
1341
	 * Returns TRUE if the current PDO instance is connected.
1342
	 *
1343
	 * @return boolean
1344
	 */
1345
	public function isConnected()
1346
	{
1347
		return $this->isConnected && $this->pdo;
1348
	}
1349
1350
	/**
1351
	 * Toggles logging, enables or disables logging.
1352
	 *
1353
	 * @param boolean $enable TRUE to enable logging
1354
	 *
1355
	 * @return self
1356
	 */
1357
	public function setEnableLogging( $enable )
1358
	{
1359
		$this->loggingEnabled = (boolean) $enable;
1360
		return $this;
1361
	}
1362
1363
	/**
1364
	 * Resets the query counter.
1365
	 * The query counter can be used to monitor the number
1366
	 * of database queries that have
1367
	 * been processed according to the database driver. You can use this
1368
	 * to monitor the number of queries required to render a page.
1369
	 *
1370
	 * Usage:
1371
	 *
1372
	 * <code>
1373
	 * R::resetQueryCount();
1374
	 * echo R::getQueryCount() . ' queries processed.';
1375
	 * </code>
1376
	 *
1377
	 * @return self
1378
	 */
1379
	public function resetCounter()
1380
	{
1381
		$this->queryCounter = 0;
1382
		return $this;
1383
	}
1384
1385
	/**
1386
	 * Returns the number of SQL queries processed.
1387
	 * This method returns the number of database queries that have
1388
	 * been processed according to the database driver. You can use this
1389
	 * to monitor the number of queries required to render a page.
1390
	 *
1391
	 * Usage:
1392
	 *
1393
	 * <code>
1394
	 * echo R::getQueryCount() . ' queries processed.';
1395
	 * </code>
1396
	 *
1397
	 * @return integer
1398
	 */
1399
	public function getQueryCount()
1400
	{
1401
		return $this->queryCounter;
1402
	}
1403
1404
	/**
1405
	 * Returns the maximum value treated as integer parameter
1406
	 * binding.
1407
	 *
1408
	 * This method is mainly for testing purposes but it can help
1409
	 * you solve some issues relating to integer bindings.
1410
	 *
1411
	 * @return integer
1412
	 */
1413
	public function getIntegerBindingMax()
1414
	{
1415
		return $this->max;
1416
	}
1417
1418
	/**
1419
	 * Sets a query to be executed upon connecting to the database.
1420
	 * This method provides an opportunity to configure the connection
1421
	 * to a database through an SQL-based interface. Objects can provide
1422
	 * an SQL string to be executed upon establishing a connection to
1423
	 * the database. This has been used to solve issues with default
1424
	 * foreign key settings in SQLite3 for instance, see Github issues:
1425
	 * #545 and #548.
1426
	 *
1427
	 * @param string $sql SQL query to run upon connecting to database
1428
	 *
1429
	 * @return self
1430
	 */
1431
	public function setInitQuery( $sql ) {
1432
		$this->initSQL = $sql;
1433
		return $this;
1434
	}
1435
}
1436
}
1437
1438
namespace RedBeanPHP {
1439
1440
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
1441
use RedBeanPHP\BeanHelper as BeanHelper;
1442
use RedBeanPHP\RedException as RedException;
1443
1444
/**
1445
 * PHP 5.3 compatibility
1446
 * We extend JsonSerializable to avoid namespace conflicts,
1447
 * can't define interface with special namespace in PHP
1448
 */
1449
if (interface_exists('\JsonSerializable')) { interface Jsonable extends \JsonSerializable {}; } else { interface Jsonable {}; }
1450
1451
/**
1452
 * OODBBean (Object Oriented DataBase Bean).
1453
 *
1454
 * to exchange information with the database. A bean represents
1455
 * a single table row and offers generic services for interaction
1456
 * with databases systems as well as some meta-data.
1457
 *
1458
 * @file    RedBeanPHP/OODBBean.php
1459
 * @author  Gabor de Mooij and the RedBeanPHP community
1460
 * @license BSD/GPLv2
1461
 * @desc    OODBBean represents a bean. RedBeanPHP uses beans
1462
 *
1463
 * @copyright
1464
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
1465
 * This source file is subject to the BSD/GPLv2 License that is bundled
1466
 * with this source code in the file license.txt.
1467
 */
1468
class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable,Jsonable
1469
{
1470
	/**
1471
	 * FUSE error modes.
1472
	 */
1473
	const C_ERR_IGNORE    = FALSE;
1474
	const C_ERR_LOG       = 1;
1475
	const C_ERR_NOTICE    = 2;
1476
	const C_ERR_WARN      = 3;
1477
	const C_ERR_EXCEPTION = 4;
1478
	const C_ERR_FUNC      = 5;
1479
	const C_ERR_FATAL     = 6;
1480
1481
	/**
1482
	 * @var boolean
1483
	 */
1484
	protected static $convertArraysToJSON = FALSE;
1485
1486
	/**
1487
	 * @var boolean
1488
	 */
1489
	protected static $errorHandlingFUSE = FALSE;
1490
1491
	/**
1492
	 * @var callable|NULL
1493
	 */
1494
	protected static $errorHandler = NULL;
1495
1496
	/**
1497
	 * @var array
1498
	 */
1499
	protected static $aliases = array();
1500
1501
	/**
1502
	 * @var boolean
1503
	 */
1504
	protected static $autoResolve = FALSE;
1505
1506
	/**
1507
	 * If this is set to TRUE, the __toString function will
1508
	 * encode all properties as UTF-8 to repair invalid UTF-8
1509
	 * encodings and prevent exceptions (which are uncatchable from within
1510
	 * a __toString-function).
1511
	 *
1512
	 * @var boolean
1513
	 */
1514
	protected static $enforceUTF8encoding = FALSE;
1515
1516
	/**
1517
	 * This is where the real properties of the bean live. They are stored and retrieved
1518
	 * by the magic getter and setter (__get and __set).
1519
	 *
1520
	 * @var array $properties
1521
	 */
1522
	protected $properties = array();
1523
1524
	/**
1525
	 * Here we keep the meta data of a bean.
1526
	 *
1527
	 * @var array
1528
	 */
1529
	protected $__info = array();
1530
1531
	/**
1532
	 * The BeanHelper allows the bean to access the toolbox objects to implement
1533
	 * rich functionality, otherwise you would have to do everything with R or
1534
	 * external objects.
1535
	 *
1536
	 * @var BeanHelper
1537
	 */
1538
	protected $beanHelper = NULL;
1539
1540
	/**
1541
	 * @var null
1542
	 */
1543
	protected $fetchType = NULL;
1544
1545
	/**
1546
	 * @var string
1547
	 */
1548
	protected $withSql = '';
1549
1550
	/**
1551
	 * @var array
1552
	 */
1553
	protected $withParams = array();
1554
1555
	/**
1556
	 * @var string
1557
	 */
1558
	protected $aliasName = NULL;
1559
1560
	/**
1561
	 * @var string
1562
	 */
1563
	protected $via = NULL;
1564
1565
	/**
1566
	 * @var boolean
1567
	 */
1568
	protected $noLoad = FALSE;
1569
1570
	/**
1571
	 * @var boolean
1572
	 */
1573
	protected $all = FALSE;
1574
1575
	/**
1576
	 * If this is set to TRUE, the __toString function will
1577
	 * encode all properties as UTF-8 to repair invalid UTF-8
1578
	 * encodings and prevent exceptions (which are uncatchable from within
1579
	 * a __toString-function).
1580
	 *
1581
	 * @param boolean $toggle TRUE to enforce UTF-8 encoding (slower)
1582
	 *
1583
	 * @return void
1584
	 */
1585
	 public static function setEnforceUTF8encoding( $toggle )
1586
	 {
1587
		 self::$enforceUTF8encoding = (boolean) $toggle;
1588
	 }
1589
1590
	/**
1591
	 * Sets the error mode for FUSE.
1592
	 * What to do if a FUSE model method does not exist?
1593
	 * You can set the following options:
1594
	 *
1595
	 * * OODBBean::C_ERR_IGNORE (default), ignores the call, returns NULL
1596
	 * * OODBBean::C_ERR_LOG, logs the incident using error_log
1597
	 * * OODBBean::C_ERR_NOTICE, triggers a E_USER_NOTICE
1598
	 * * OODBBean::C_ERR_WARN, triggers a E_USER_WARNING
1599
	 * * OODBBean::C_ERR_EXCEPTION, throws an exception
1600
	 * * OODBBean::C_ERR_FUNC, allows you to specify a custom handler (function)
1601
	 * * OODBBean::C_ERR_FATAL, triggers a E_USER_ERROR
1602
	 *
1603
	 * <code>
1604
	 * Custom handler method signature: handler( array (
1605
	 * 	'message' => string
1606
	 * 	'bean' => OODBBean
1607
	 * 	'method' => string
1608
	 * ) )
1609
	 * </code>
1610
	 *
1611
	 * This method returns the old mode and handler as an array.
1612
	 *
1613
	 * @param integer       $mode error handling mode
1614
	 * @param callable|NULL $func custom handler
1615
	 *
1616
	 * @return array
1617
	 */
1618
	public static function setErrorHandlingFUSE($mode, $func = NULL) {
1619
		if (
1620
			   $mode !== self::C_ERR_IGNORE
1621
			&& $mode !== self::C_ERR_LOG
1622
			&& $mode !== self::C_ERR_NOTICE
1623
			&& $mode !== self::C_ERR_WARN
1624
			&& $mode !== self::C_ERR_EXCEPTION
1625
			&& $mode !== self::C_ERR_FUNC
1626
			&& $mode !== self::C_ERR_FATAL
1627
		) throw new \Exception( 'Invalid error mode selected' );
1628
1629
		if ( $mode === self::C_ERR_FUNC && !is_callable( $func ) ) {
1630
			throw new \Exception( 'Invalid error handler' );
1631
		}
1632
1633
		$old = array( self::$errorHandlingFUSE, self::$errorHandler );
1634
		self::$errorHandlingFUSE = $mode;
1635
		if ( is_callable( $func ) ) {
1636
			self::$errorHandler = $func;
1637
		} else {
1638
			self::$errorHandler = NULL;
1639
		}
1640
		return $old;
1641
	}
1642
1643
	/**
1644
	 * Toggles array to JSON conversion. If set to TRUE any array
1645
	 * set to a bean property that's not a list will be turned into
1646
	 * a JSON string. Used together with AQueryWriter::useJSONColumns this
1647
	 * extends the data type support for JSON columns. Returns the previous
1648
	 * value of the flag.
1649
	 *
1650
	 * @param boolean $flag flag
1651
	 *
1652
	 * @return boolean
1653
	 */
1654
	public static function convertArraysToJSON( $flag )
1655
	{
1656
		$old = self::$convertArraysToJSON;
1657
		self::$convertArraysToJSON = $flag;
1658
		return $old;
1659
	}
1660
1661
	/**
1662
	 * Sets global aliases.
1663
	 * Registers a batch of aliases in one go. This works the same as
1664
	 * fetchAs and setAutoResolve but explicitly. For instance if you register
1665
	 * the alias 'cover' for 'page' a property containing a reference to a
1666
	 * page bean called 'cover' will correctly return the page bean and not
1667
	 * a (non-existant) cover bean.
1668
	 *
1669
	 * <code>
1670
	 * R::aliases( array( 'cover' => 'page' ) );
1671
	 * $book = R::dispense( 'book' );
1672
	 * $page = R::dispense( 'page' );
1673
	 * $book->cover = $page;
1674
	 * R::store( $book );
1675
	 * $book = $book->fresh();
1676
	 * $cover = $book->cover;
1677
	 * echo $cover->getMeta( 'type' ); //page
1678
	 * </code>
1679
	 *
1680
	 * The format of the aliases registration array is:
1681
	 *
1682
	 * {alias} => {actual type}
1683
	 *
1684
	 * In the example above we use:
1685
	 *
1686
	 * cover => page
1687
	 *
1688
	 * From that point on, every bean reference to a cover
1689
	 * will return a 'page' bean. Note that with autoResolve this
1690
	 * feature along with fetchAs() is no longer very important, although
1691
	 * relying on explicit aliases can be a bit faster.
1692
	 *
1693
	 * @param array $list list of global aliases to use
1694
	 *
1695
	 * @return void
1696
	 */
1697
	public static function aliases( $list )
1698
	{
1699
		self::$aliases = $list;
1700
	}
1701
1702
	/**
1703
	 * Enables or disables auto-resolving fetch types.
1704
	 * Auto-resolving aliased parent beans is convenient but can
1705
	 * be slower and can create infinite recursion if you
1706
	 * used aliases to break cyclic relations in your domain.
1707
	 *
1708
	 * @param boolean $automatic TRUE to enable automatic resolving aliased parents
1709
	 *
1710
	 * @return void
1711
	 */
1712
	public static function setAutoResolve( $automatic = TRUE )
1713
	{
1714
		self::$autoResolve = (boolean) $automatic;
1715
	}
1716
1717
	/**
1718
	 * Sets a meta property for all beans. This is a quicker way to set
1719
	 * the meta properties for a collection of beans because this method
1720
	 * can directly access the property arrays of the beans.
1721
	 * This method returns the beans.
1722
	 *
1723
	 * @param array  $beans    beans to set the meta property of
1724
	 * @param string $property property to set
1725
	 * @param mixed  $value    value
1726
	 *
1727
	 * @return array
1728
	 */
1729
	public static function setMetaAll( $beans, $property, $value )
1730
	{
1731
		foreach( $beans as $bean ) {
1732
			if ( $bean instanceof OODBBean ) $bean->__info[ $property ] = $value;
1733
			if ( $property == 'type' && !empty($bean->beanHelper)) {
1734
				$bean->__info['model'] = $bean->beanHelper->getModelForBean( $bean );
1735
			}
1736
		}
1737
		return $beans;
1738
	}
1739
1740
	/**
1741
	 * Parses the join in the with-snippet.
1742
	 * For instance:
1743
	 *
1744
	 * <code>
1745
	 * $author
1746
	 * 	->withCondition(' @joined.detail.title LIKE ? ')
1747
	 *  ->ownBookList;
1748
	 * </code>
1749
	 *
1750
	 * will automatically join 'detail' on book to
1751
	 * access the title field.
1752
	 *
1753
	 * @note this feature requires Narrow Field Mode and Join Feature
1754
	 * to be both activated (default).
1755
	 *
1756
	 * @param string $type the source type for the join
1757
	 *
1758
	 * @return string
1759
	 */
1760
	private function parseJoin( $type )
1761
	{
1762
		if ( strpos($this->withSql, '@joined.' ) === FALSE ) {
1763
			return '';
1764
		}
1765
		
1766
		$joinSql = ' ';
1767
		$joins = array();
1768
		$writer   = $this->beanHelper->getToolBox()->getWriter();
1769
		$oldParts = $parts = explode( '@joined.', $this->withSql );
1770
		array_shift( $parts );
1771
		foreach($parts as $part) {
1772
			$explosion = explode( '.', $part );
1773
			$joinInfo  = reset( $explosion );
1774
			//Dont join more than once..
1775
			if ( !isset( $joins[$joinInfo] ) ) {
1776
				$joins[ $joinInfo ] = TRUE;
1777
				$joinSql  .= $writer->writeJoin( $type, $joinInfo, 'LEFT' );
1778
			}
1779
		}
1780
		$this->withSql = implode( '', $oldParts );
1781
		$joinSql      .= ' WHERE ';
1782
		
1783
		return $joinSql;
1784
	}
1785
1786
	/**
1787
	 * Accesses the shared list of a bean.
1788
	 * To access beans that have been associated with the current bean
1789
	 * using a many-to-many relationship use sharedXList where
1790
	 * X is the type of beans in the list.
1791
	 *
1792
	 * Usage:
1793
	 *
1794
	 * <code>
1795
	 * $person = R::load( 'person', $id );
1796
	 * $friends = $person->sharedFriendList;
1797
	 * </code>
1798
	 *
1799
	 * The code snippet above demonstrates how to obtain all beans of
1800
	 * type 'friend' that have associated using an N-M relation.
1801
	 * This is a private method used by the magic getter / accessor.
1802
	 * The example illustrates usage through these accessors.
1803
	 *
1804
	 * @param string  $type    the name of the list you want to retrieve
1805
	 * @param OODB    $redbean instance of the RedBeanPHP OODB class
1806
	 * @param ToolBox $toolbox instance of ToolBox (to get access to core objects)
1807
	 *
1808
	 * @return array
1809
	 */
1810
	private function getSharedList( $type, $redbean, $toolbox )
1811
	{
1812
		$writer = $toolbox->getWriter();
1813
		if ( $this->via ) {
1814
			$oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
1815
			if ( $oldName !== $this->via ) {
1816
				//set the new renaming rule
1817
				$writer->renameAssocTable( $oldName, $this->via );
1818
			}
1819
			$this->via = NULL;
1820
		}
1821
		$beans = array();
1822
		if ($this->getID()) {
1823
			$type             = $this->beau( $type );
1824
			$assocManager     = $redbean->getAssociationManager();
1825
			$beans            = $assocManager->related( $this, $type, $this->withSql, $this->withParams );
1826
		}
1827
		return $beans;
1828
	}
1829
1830
	/**
1831
	 * Accesses the ownList. The 'own' list contains beans
1832
	 * associated using a one-to-many relation. The own-lists can
1833
	 * be accessed through the magic getter/setter property
1834
	 * ownXList where X is the type of beans in that list.
1835
	 *
1836
	 * Usage:
1837
	 *
1838
	 * <code>
1839
	 * $book = R::load( 'book', $id );
1840
	 * $pages = $book->ownPageList;
1841
	 * </code>
1842
	 *
1843
	 * The example above demonstrates how to access the
1844
	 * pages associated with the book. Since this is a private method
1845
	 * meant to be used by the magic accessors, the example uses the
1846
	 * magic getter instead.
1847
	 *
1848
	 * @param string      $type   name of the list you want to retrieve
1849
	 * @param OODB        $oodb   The RB OODB object database instance
1850
	 *
1851
	 * @return array
1852
	 */
1853
	private function getOwnList( $type, $redbean )
1854
	{
1855
		$type = $this->beau( $type );
1856
		if ( $this->aliasName ) {
1857
			$parentField = $this->aliasName;
1858
			$myFieldLink = $parentField . '_id';
1859
1860
			$this->__info['sys.alias.' . $type] = $this->aliasName;
1861
1862
			$this->aliasName = NULL;
1863
		} else {
1864
			$parentField = $this->__info['type'];
1865
			$myFieldLink = $parentField . '_id';
1866
		}
1867
		$beans = array();
1868
		if ( $this->getID() ) {
1869
			reset( $this->withParams );
1870
			$joinSql = $this->parseJoin( $type );
1871
			$firstKey = count( $this->withParams ) > 0
1872
				? key( $this->withParams )
1873
				: 0;
1874
			if ( is_int( $firstKey ) ) {
1875
				$bindings = array_merge( array( $this->getID() ), $this->withParams );
1876
				$beans = $redbean->find( $type, array(), "{$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
1877
			} else {
1878
				$bindings           = $this->withParams;
1879
				$bindings[':slot0'] = $this->getID();
1880
				$beans = $redbean->find( $type, array(), "{$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
1881
			}
1882
		}
1883
		foreach ( $beans as $beanFromList ) {
1884
			$beanFromList->__info['sys.parentcache.' . $parentField] = $this;
1885
		}
1886
		return $beans;
1887
	}
1888
1889
	/**
1890
	 * Initializes a bean. Used by OODB for dispensing beans.
1891
	 * It is not recommended to use this method to initialize beans. Instead
1892
	 * use the OODB object to dispense new beans. You can use this method
1893
	 * if you build your own bean dispensing mechanism.
1894
	 * This is not recommended.
1895
	 *
1896
	 * Unless you know what you are doing, do NOT use this method.
1897
	 * This is for advanced users only!
1898
	 *
1899
	 * @param string     $type       type of the new bean
1900
	 * @param BeanHelper $beanhelper bean helper to obtain a toolbox and a model
1901
	 *
1902
	 * @return void
1903
	 */
1904
	public function initializeForDispense( $type, $beanhelper = NULL )
1905
	{
1906
		$this->beanHelper         = $beanhelper;
1907
		$this->__info['type']     = $type;
1908
		$this->__info['sys.id']   = 'id';
1909
		$this->__info['sys.orig'] = array( 'id' => 0 );
1910
		$this->__info['tainted']  = TRUE;
1911
		$this->__info['changed']  = TRUE;
1912
		$this->__info['changelist'] = array();
1913
		if ( $beanhelper ) {
1914
			$this->__info['model'] = $this->beanHelper->getModelForBean( $this );
1915
		}
1916
		$this->properties['id']   = 0;
1917
	}
1918
1919
	/**
1920
	 * Sets the Bean Helper. Normally the Bean Helper is set by OODB.
1921
	 * Here you can change the Bean Helper. The Bean Helper is an object
1922
	 * providing access to a toolbox for the bean necessary to retrieve
1923
	 * nested beans (bean lists: ownBean, sharedBean) without the need to
1924
	 * rely on static calls to the facade (or make this class dep. on OODB).
1925
	 *
1926
	 * @param BeanHelper $helper helper to use for this bean
1927
	 *
1928
	 * @return void
1929
	 */
1930
	public function setBeanHelper( BeanHelper $helper )
1931
	{
1932
		$this->beanHelper = $helper;
1933
	}
1934
1935
	/**
1936
	 * Returns an ArrayIterator so you can treat the bean like
1937
	 * an array with the properties container as its contents.
1938
	 * This method is meant for PHP and allows you to access beans as if
1939
	 * they were arrays, i.e. using array notation:
1940
	 *
1941
	 * <code>
1942
	 * $bean[$key] = $value;
1943
	 * </code>
1944
	 *
1945
	 * Note that not all PHP functions work with the array interface.
1946
	 *
1947
	 * @return ArrayIterator
1948
	 */
1949
	public function getIterator()
1950
	{
1951
		return new \ArrayIterator( $this->properties );
1952
	}
1953
1954
	/**
1955
	 * Imports all values from an associative array $array. Chainable.
1956
	 * This method imports the values in the first argument as bean
1957
	 * propery and value pairs. Use the second parameter to provide a
1958
	 * selection. If a selection array is passed, only the entries
1959
	 * having keys mentioned in the selection array will be imported.
1960
	 * Set the third parameter to TRUE to preserve spaces in selection keys.
1961
	 *
1962
	 * @param array        $array     what you want to import
1963
	 * @param string|array $selection selection of values
1964
	 * @param boolean      $notrim    if TRUE selection keys will NOT be trimmed
1965
	 *
1966
	 * @return OODBBean
1967
	 */
1968
	public function import( $array, $selection = FALSE, $notrim = FALSE )
1969
	{
1970
		if ( is_string( $selection ) ) {
1971
			$selection = explode( ',', $selection );
1972
		}
1973
		if ( is_array( $selection ) ) {
1974
			if ( $notrim ) {
1975
				$selected = array_flip($selection);
1976
			} else {
1977
				$selected = array();
1978
				foreach ( $selection as $key => $select ) {
1979
					$selected[trim( $select )] = TRUE;
1980
				}
1981
			}
1982
		} else {
1983
			$selected = FALSE;
1984
		}
1985
		foreach ( $array as $key => $value ) {
1986
			if ( $key != '__info' ) {
1987
				if ( !$selected || isset( $selected[$key] ) ) {
1988
					if ( is_array($value ) ) {
1989
						if ( isset( $value['_type'] ) ) {
1990
							$bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $value['_type'] );
1991
							unset( $value['_type'] );
1992
							$bean->import($value);
1993
							$this->$key = $bean;
1994
						} else {
1995
							$listBeans = array();
1996
							foreach( $value as $listKey => $listItem ) {
1997
								$bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $listItem['_type'] );
1998
								unset( $listItem['_type'] );
1999
								$bean->import($listItem);
2000
								$list = &$this->$key;
2001
								$list[ $listKey ] = $bean;
2002
							}
2003
						}
2004
					} else {
2005
						$this->$key = $value;
2006
					}
2007
				}
2008
			}
2009
		}
2010
		return $this;
2011
	}
2012
2013
	/**
2014
	* Imports an associative array directly into the
2015
	* internal property array of the bean as well as the
2016
	* meta property sys.orig and sets the changed flag to FALSE.
2017
	* This is used by the repository objects to inject database rows
2018
	* into the beans. It is not recommended to use this method outside
2019
	* of a bean repository.
2020
	*
2021
	* @param array $row a database row
2022
	*
2023
	* @return self
2024
	*/
2025
	public function importRow( $row )
2026
	{
2027
		$this->properties = $row;
2028
		$this->__info['sys.orig'] = $row;
2029
		$this->__info['changed'] = FALSE;
2030
		return $this;
2031
	}
2032
2033
	/**
2034
	 * Imports data from another bean. Chainable.
2035
	 * Copies the properties from the source bean to the internal
2036
	 * property list.
2037
	 *
2038
	 * Usage:
2039
	 *
2040
	 * <code>
2041
	 * $copy->importFrom( $bean );
2042
	 * </code>
2043
	 *
2044
	 * The example above demonstrates how to make a shallow copy
2045
	 * of a bean using the importFrom() method.
2046
	 *
2047
	 * @param OODBBean $sourceBean the source bean to take properties from
2048
	 *
2049
	 * @return OODBBean
2050
	 */
2051
	public function importFrom( OODBBean $sourceBean )
2052
	{
2053
		$this->__info['tainted'] = TRUE;
2054
		$this->__info['changed'] = TRUE;
2055
		$this->properties = $sourceBean->properties;
2056
2057
		return $this;
2058
	}
2059
2060
	/**
2061
	 * Injects the properties of another bean but keeps the original ID.
2062
	 * Just like import() but keeps the original ID.
2063
	 * Chainable.
2064
	 *
2065
	 * @param OODBBean $otherBean the bean whose properties you would like to copy
2066
	 *
2067
	 * @return OODBBean
2068
	 */
2069
	public function inject( OODBBean $otherBean )
2070
	{
2071
		$myID = $this->properties['id'];
2072
		$this->import( $otherBean->export( FALSE, FALSE, TRUE ) );
2073
		$this->id = $myID;
2074
2075
		return $this;
2076
	}
2077
2078
	/**
2079
	 * Exports the bean as an array.
2080
	 * This function exports the contents of a bean to an array and returns
2081
	 * the resulting array. Depending on the parameters you can also
2082
	 * export an entire graph of beans, apply filters or exclude meta data.
2083
	 *
2084
	 * Usage:
2085
	 *
2086
	 * <code>
2087
	 * $bookData = $book->export( TRUE, TRUE, FALSE, [ 'author' ] );
2088
	 * </code>
2089
	 *
2090
	 * The example above exports all bean properties to an array
2091
	 * called $bookData including its meta data, parent objects but without
2092
	 * any beans of type 'author'.
2093
	 *
2094
	 * @param boolean $meta    set to TRUE if you want to export meta data as well
2095
	 * @param boolean $parents set to TRUE if you want to export parents as well
2096
	 * @param boolean $onlyMe  set to TRUE if you want to export only this bean
2097
	 * @param array   $filters optional whitelist for export
2098
	 *
2099
	 * @return array
2100
	 */
2101
	public function export( $meta = FALSE, $parents = FALSE, $onlyMe = FALSE, $filters = array() )
2102
	{
2103
		$arr = array();
2104
		if ( $parents ) {
2105
			foreach ( $this as $key => $value ) {
2106
				if ( substr( $key, -3 ) != '_id' ) continue;
2107
2108
				$prop = substr( $key, 0, strlen( $key ) - 3 );
2109
				$this->$prop;
2110
			}
2111
		}
2112
		$hasFilters = is_array( $filters ) && count( $filters );
2113
		foreach ( $this as $key => $value ) {
2114
			if ( !$onlyMe && is_array( $value ) ) {
2115
				$vn = array();
2116
2117
				foreach ( $value as $i => $b ) {
2118
					if ( !( $b instanceof OODBBean ) ) continue;
2119
					$vn[] = $b->export( $meta, FALSE, FALSE, $filters );
2120
					$value = $vn;
2121
				}
2122
			} elseif ( $value instanceof OODBBean ) { if ( $hasFilters ) { //has to be on one line, otherwise code coverage miscounts as miss
2123
					if ( !in_array( strtolower( $value->getMeta( 'type' ) ), $filters ) ) continue;
2124
				}
2125
				$value = $value->export( $meta, $parents, FALSE, $filters );
2126
			}
2127
			$arr[$key] = $value;
2128
		}
2129
		if ( $meta ) {
2130
			$arr['__info'] = $this->__info;
2131
		}
2132
		return $arr;
2133
	}
2134
2135
	/**
2136
	 * Implements isset() function for use as an array.
2137
	 * This allows you to use isset() on bean properties.
2138
	 *
2139
	 * Usage:
2140
	 *
2141
	 * <code>
2142
	 * $book->title = 'my book';
2143
	 * echo isset($book['title']); //TRUE
2144
	 * </code>
2145
	 *
2146
	 * The example illustrates how one can apply the
2147
	 * isset() function to a bean.
2148
	 *
2149
	 * @param string $property name of the property you want to check
2150
	 *
2151
	 * @return boolean
2152
	 */
2153
	public function __isset( $property )
2154
	{
2155
		$property = $this->beau( $property );
2156
		if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
2157
			$property = substr($property, 1);
2158
		}
2159
		return isset( $this->properties[$property] );
2160
	}
2161
2162
	/**
2163
	 * Checks whether a related bean exists.
2164
	 * For instance if a post bean has a related author, this method
2165
	 * can be used to check if the author is set without loading the author.
2166
	 * This method works by checking the related ID-field.
2167
	 *
2168
	 * @param string $property name of the property you wish to check
2169
	 *
2170
	 * @return boolean
2171
	 */
2172
	public function exists( $property )
2173
	{
2174
		$property = $this->beau( $property );
2175
		/* fixes issue #549, see Base/Bean test */
2176
		$hiddenRelationField = "{$property}_id";
2177
		if ( array_key_exists( $hiddenRelationField, $this->properties ) ) {
2178
			if ( !is_null( $this->properties[$hiddenRelationField] ) ) {
2179
				return TRUE;
2180
			}
2181
		}
2182
		return FALSE;
2183
	}
2184
2185
	/**
2186
	 * Returns the ID of the bean.
2187
	 * If for some reason the ID has not been set, this method will
2188
	 * return NULL. This is actually the same as accessing the
2189
	 * id property using $bean->id. The ID of a bean is it's primary
2190
	 * key and should always correspond with a table column named
2191
	 * 'id'.
2192
	 *
2193
	 * @return string|null
2194
	 */
2195
	public function getID()
2196
	{
2197
		return ( isset( $this->properties['id'] ) ) ? (string) $this->properties['id'] : NULL;
2198
	}
2199
2200
	/**
2201
	 * Unsets a property of a bean.
2202
	 * Magic method, gets called implicitly when
2203
	 * performing the unset() operation
2204
	 * on a bean property.
2205
	 *
2206
	 * @param  string $property property to unset
2207
	 *
2208
	 * @return void
2209
	 */
2210
	public function __unset( $property )
2211
	{
2212
		$property = $this->beau( $property );
2213
2214
		if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
2215
			$property = substr($property, 1);
2216
		}
2217
		unset( $this->properties[$property] );
2218
		$shadowKey = 'sys.shadow.'.$property;
2219
		if ( isset( $this->__info[ $shadowKey ] ) ) unset( $this->__info[$shadowKey] );
2220
		//also clear modifiers
2221
		$this->clearModifiers();
2222
		return;
2223
	}
2224
2225
	/**
2226
	 * Adds WHERE clause conditions to ownList retrieval.
2227
	 * For instance to get the pages that belong to a book you would
2228
	 * issue the following command: $book->ownPage
2229
	 * However, to order these pages by number use:
2230
	 *
2231
	 * <code>
2232
	 * $book->with(' ORDER BY `number` ASC ')->ownPage
2233
	 * </code>
2234
	 *
2235
	 * the additional SQL snippet will be merged into the final
2236
	 * query.
2237
	 *
2238
	 * @param string $sql      SQL to be added to retrieval query.
2239
	 * @param array  $bindings array with parameters to bind to SQL snippet
2240
	 *
2241
	 * @return OODBBean
2242
	 */
2243
	public function with( $sql, $bindings = array() )
2244
	{
2245
		$this->withSql    = $sql;
2246
		$this->withParams = $bindings;
2247
		return $this;
2248
	}
2249
2250
	/**
2251
	 * Just like with(). Except that this method prepends the SQL query snippet
2252
	 * with AND which makes it slightly more comfortable to use a conditional
2253
	 * SQL snippet. For instance to filter an own-list with pages (belonging to
2254
	 * a book) on specific chapters you can use:
2255
	 *
2256
	 * $book->withCondition(' chapter = 3 ')->ownPage
2257
	 *
2258
	 * This will return in the own list only the pages having 'chapter == 3'.
2259
	 *
2260
	 * @param string $sql      SQL to be added to retrieval query (prefixed by AND)
2261
	 * @param array  $bindings array with parameters to bind to SQL snippet
2262
	 *
2263
	 * @return OODBBean
2264
	 */
2265
	public function withCondition( $sql, $bindings = array() )
2266
	{
2267
		$this->withSql    = ' AND ' . $sql;
2268
		$this->withParams = $bindings;
2269
		return $this;
2270
	}
2271
2272
	/**
2273
	 * Tells the bean to (re)load the following list without any
2274
	 * conditions. If you have an ownList or sharedList with a
2275
	 * condition you can use this method to reload the entire list.
2276
	 *
2277
	 * Usage:
2278
	 *
2279
	 * <code>
2280
	 * $bean->with( ' LIMIT 3 ' )->ownPage; //Just 3
2281
	 * $bean->all()->ownPage; //Reload all pages
2282
	 * </code>
2283
	 *
2284
	 * @return self
2285
	 */
2286
	public function all()
2287
	{
2288
		$this->all = TRUE;
2289
		return $this;
2290
	}
2291
2292
	/**
2293
	 * Tells the bean to only access the list but not load
2294
	 * its contents. Use this if you only want to add something to a list
2295
	 * and you have no interest in retrieving its contents from the database.
2296
	 *
2297
	 * Usage:
2298
	 *
2299
	 * <code>
2300
	 * $book->noLoad()->ownPage[] = $newPage;
2301
	 * </code>
2302
	 *
2303
	 * In the example above we add the $newPage bean to the
2304
	 * page list of book without loading all the pages first.
2305
	 * If you know in advance that you are not going to use
2306
	 * the contents of the list, you may use the noLoad() modifier
2307
	 * to make sure the queries required to load the list will not
2308
	 * be executed.
2309
	 *
2310
	 * @return self
2311
	 */
2312
	public function noLoad()
2313
	{
2314
		$this->noLoad = TRUE;
2315
		return $this;
2316
	}
2317
2318
	/**
2319
	 * Prepares an own-list to use an alias. This is best explained using
2320
	 * an example. Imagine a project and a person. The project always involves
2321
	 * two persons: a teacher and a student. The person beans have been aliased in this
2322
	 * case, so to the project has a teacher_id pointing to a person, and a student_id
2323
	 * also pointing to a person. Given a project, we obtain the teacher like this:
2324
	 *
2325
	 * <code>
2326
	 * $project->fetchAs('person')->teacher;
2327
	 * </code>
2328
	 *
2329
	 * Now, if we want all projects of a teacher we cant say:
2330
	 *
2331
	 * <code>
2332
	 * $teacher->ownProject
2333
	 * </code>
2334
	 *
2335
	 * because the $teacher is a bean of type 'person' and no project has been
2336
	 * assigned to a person. Instead we use the alias() method like this:
2337
	 *
2338
	 * <code>
2339
	 * $teacher->alias('teacher')->ownProject
2340
	 * </code>
2341
	 *
2342
	 * now we get the projects associated with the person bean aliased as
2343
	 * a teacher.
2344
	 *
2345
	 * @param string $aliasName the alias name to use
2346
	 *
2347
	 * @return OODBBean
2348
	 */
2349
	public function alias( $aliasName )
2350
	{
2351
		$this->aliasName = $this->beau( $aliasName );
2352
		return $this;
2353
	}
2354
2355
	/**
2356
	 * Returns properties of bean as an array.
2357
	 * This method returns the raw internal property list of the
2358
	 * bean. Only use this method for optimization purposes. Otherwise
2359
	 * use the export() method to export bean data to arrays.
2360
	 *
2361
	 * @return array
2362
	 */
2363
	public function getProperties()
2364
	{
2365
		return $this->properties;
2366
	}
2367
2368
	/**
2369
	 * Returns properties of bean as an array.
2370
	 * This method returns the raw internal property list of the
2371
	 * bean. Only use this method for optimization purposes. Otherwise
2372
	 * use the export() method to export bean data to arrays.
2373
	 * This method returns an array with the properties array and
2374
	 * the type (string).
2375
	 *
2376
	 * @return array
2377
	 */
2378
	public function getPropertiesAndType()
2379
	{
2380
		return array( $this->properties, $this->__info['type'] );
2381
	}
2382
2383
	/**
2384
	 * Turns a camelcase property name into an underscored property name.
2385
	 *
2386
	 * Examples:
2387
	 *
2388
	 * - oneACLRoute -> one_acl_route
2389
	 * - camelCase -> camel_case
2390
	 *
2391
	 * Also caches the result to improve performance.
2392
	 *
2393
	 * @param string $property property to un-beautify
2394
	 *
2395
	 * @return string
2396
	 */
2397
	public function beau( $property )
2398
	{
2399
		static $beautifulColumns = array();
2400
2401
		if ( ctype_lower( $property ) ) return $property;
2402
		if (
2403
			( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) )
2404
			|| ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) )
2405
			|| ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) )
2406
		) {
2407
2408
			$property = preg_replace( '/List$/', '', $property );
2409
			return $property;
2410
		}
2411
		if ( !isset( $beautifulColumns[$property] ) ) {
2412
			$beautifulColumns[$property] = AQueryWriter::camelsSnake( $property );
2413
		}
2414
		return $beautifulColumns[$property];
2415
	}
2416
2417
	/**
2418
	 * Modifiers are a powerful concept in RedBeanPHP, they make it possible
2419
	 * to change the way a property has to be loaded.
2420
	 * RedBeanPHP uses property modifiers using a prefix notation like this:
2421
	 *
2422
	 * <code>
2423
	 * $book->fetchAs('page')->cover;
2424
	 * </code>
2425
	 *
2426
	 * Here, we load a bean of type page, identified by the cover property
2427
	 * (or cover_id in the database). Because the modifier is called before
2428
	 * the property is accessed, the modifier must be remembered somehow,
2429
	 * this changes the state of the bean. Accessing a property causes the
2430
	 * bean to clear its modifiers. To clear the modifiers manually you can
2431
	 * use this method.
2432
	 *
2433
	 * Usage:
2434
	 *
2435
	 * <code>
2436
	 * $book->with( 'LIMIT 1' );
2437
	 * $book->clearModifiers()->ownPageList;
2438
	 * </code>
2439
	 *
2440
	 * In the example above, the 'LIMIT 1' clause is
2441
	 * cleared before accessing the pages of the book, causing all pages
2442
	 * to be loaded in the list instead of just one.
2443
	 *
2444
	 * @return self
2445
	 */
2446
	public function clearModifiers()
2447
	{
2448
		$this->withSql    = '';
2449
		$this->withParams = array();
2450
		$this->aliasName  = NULL;
2451
		$this->fetchType  = NULL;
2452
		$this->noLoad     = FALSE;
2453
		$this->all        = FALSE;
2454
		$this->via        = NULL;
2455
		return $this;
2456
	}
2457
2458
	/**
2459
	 * Determines whether a list is opened in exclusive mode or not.
2460
	 * If a list has been opened in exclusive mode this method will return TRUE,
2461
	 * othwerwise it will return FALSE.
2462
	 *
2463
	 * @param string $listName name of the list to check
2464
	 *
2465
	 * @return boolean
2466
	 */
2467
	public function isListInExclusiveMode( $listName )
2468
	{
2469
		$listName = $this->beau( $listName );
2470
2471
		if ( strpos( $listName, 'xown' ) === 0 && ctype_upper( substr( $listName, 4, 1 ) ) ) {
2472
			$listName = substr($listName, 1);
2473
		}
2474
		$listName = lcfirst( substr( $listName, 3 ) );
2475
		return ( isset( $this->__info['sys.exclusive-'.$listName] ) && $this->__info['sys.exclusive-'.$listName] );
2476
	}
2477
2478
	/**
2479
	 * Magic Getter. Gets the value for a specific property in the bean.
2480
	 * If the property does not exist this getter will make sure no error
2481
	 * occurs. This is because RedBean allows you to query (probe) for
2482
	 * properties. If the property can not be found this method will
2483
	 * return NULL instead.
2484
	 *
2485
	 * Usage:
2486
	 *
2487
	 * <code>
2488
	 * $title = $book->title;
2489
	 * $pages = $book->ownPageList;
2490
	 * $tags  = $book->sharedTagList;
2491
	 * </code>
2492
	 *
2493
	 * The example aboves lists several ways to invoke the magic getter.
2494
	 * You can use the magic setter to access properties, own-lists,
2495
	 * exclusive own-lists (xownLists) and shared-lists.
2496
	 *
2497
	 * @param string $property name of the property you wish to obtain the value of
2498
	 *
2499
	 * @return mixed
2500
	 */
2501
	public function &__get( $property )
2502
	{
2503
		$isEx          = FALSE;
2504
		$isOwn         = FALSE;
2505
		$isShared      = FALSE;
2506
		if ( !ctype_lower( $property ) ) {
2507
			$property = $this->beau( $property );
2508
			if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
2509
				$property = substr($property, 1);
2510
				$listName = lcfirst( substr( $property, 3 ) );
2511
				$isEx     = TRUE;
2512
				$isOwn    = TRUE;
2513
				$this->__info['sys.exclusive-'.$listName] = TRUE;
2514
			} elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) )  {
2515
				$isOwn    = TRUE;
2516
				$listName = lcfirst( substr( $property, 3 ) );
2517
			} elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
2518
				$isShared = TRUE;
2519
			}
2520
		}
2521
		$fieldLink      = $property . '_id';
2522
		$exists         = isset( $this->properties[$property] );
2523
2524
		//If not exists and no field link and no list, bail out.
2525
		if ( !$exists && !isset($this->$fieldLink) && (!$isOwn && !$isShared )) {
2526
			$this->clearModifiers();
2527
			/**
2528
			 * Github issue:
2529
			 * Remove $NULL to directly return NULL #625
2530
			 * @@ -1097,8 +1097,7 @@ public function &__get( $property )
2531
			 *		$this->all        = FALSE;
2532
			 *		$this->via        = NULL;
2533
			 *
2534
			 * - $NULL = NULL;
2535
			 * - return $NULL;
2536
			 * + return NULL;
2537
			 *
2538
			 * leads to regression:
2539
			 * PHP Stack trace:
2540
			 * PHP 1. {main}() testje.php:0
2541
			 * PHP 2. RedBeanPHP\OODBBean->__get() testje.php:22
2542
			 * Notice: Only variable references should be returned by reference in rb.php on line 2529
2543
			 */
2544
			$NULL = NULL;
2545
			return $NULL;
2546
		}
2547
2548
		$hasAlias       = (!is_null($this->aliasName));
2549
		$differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
2550
									($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
2551
		$hasSQL         = ($this->withSql !== '' || $this->via !== NULL);
2552
		$hasAll         = (boolean) ($this->all);
2553
2554
		//If exists and no list or exits and list not changed, bail out.
2555
		if ( $exists && ((!$isOwn && !$isShared ) || (!$hasSQL && !$differentAlias && !$hasAll)) ) {
2556
			$this->clearModifiers();
2557
			return $this->properties[$property];
2558
		}
2559
2560
		list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
2561
2562
		//If it's another bean, then we load it and return
2563
		if ( isset( $this->$fieldLink ) ) {
2564
			$this->__info['tainted'] = TRUE;
2565
			if ( isset( $this->__info["sys.parentcache.$property"] ) ) {
2566
				$bean = $this->__info["sys.parentcache.$property"];
2567
			} else {
2568
				if ( isset( self::$aliases[$property] ) ) {
2569
					$type = self::$aliases[$property];
2570
				} elseif ( $this->fetchType ) {
2571
					$type = $this->fetchType;
2572
					$this->fetchType = NULL;
2573
				} else {
2574
					$type = $property;
2575
				}
2576
				$bean = NULL;
2577
				if ( !is_null( $this->properties[$fieldLink] ) ) {
2578
					$bean = $redbean->load( $type, $this->properties[$fieldLink] );
2579
					//If the IDs dont match, we failed to load, so try autoresolv in that case...
2580
					if ( $bean->id !== $this->properties[$fieldLink] && self::$autoResolve ) {
2581
						$type = $this->beanHelper->getToolbox()->getWriter()->inferFetchType( $this->__info['type'], $property );
2582
						if ( !is_null( $type) ) {
2583
							$bean = $redbean->load( $type, $this->properties[$fieldLink] );
2584
							$this->__info["sys.autoresolved.{$property}"] = $type;
2585
						}
2586
					}
2587
				}
2588
			}
2589
			$this->properties[$property] = $bean;
2590
			$this->clearModifiers();
2591
			return $this->properties[$property];
2592
		}
2593
2594
		//Implicit: elseif ( $isOwn || $isShared ) {
2595
		if ( $this->noLoad ) {
2596
			$beans = array();
2597
		} elseif ( $isOwn ) {
2598
			$beans = $this->getOwnList( $listName, $redbean );
2599
		} else {
2600
			$beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
2601
		}
2602
		$this->properties[$property]          = $beans;
2603
		$this->__info["sys.shadow.$property"] = $beans;
2604
		$this->__info['tainted']              = TRUE;
2605
		
2606
		$this->clearModifiers();
2607
		return $this->properties[$property];
2608
2609
	}
2610
2611
	/**
2612
	 * Magic Setter. Sets the value for a specific property.
2613
	 * This setter acts as a hook for OODB to mark beans as tainted.
2614
	 * The tainted meta property can be retrieved using getMeta("tainted").
2615
	 * The tainted meta property indicates whether a bean has been modified and
2616
	 * can be used in various caching mechanisms.
2617
	 *
2618
	 * @param string $property name of the property you wish to assign a value to
2619
	 * @param  mixed $value    the value you want to assign
2620
	 *
2621
	 * @return void
2622
	 */
2623
	public function __set( $property, $value )
2624
	{
2625
		$isEx          = FALSE;
2626
		$isOwn         = FALSE;
2627
		$isShared      = FALSE;
2628
2629
		if ( !ctype_lower( $property ) ) {
2630
			$property = $this->beau( $property );
2631
			if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
2632
				$property = substr($property, 1);
2633
				$listName = lcfirst( substr( $property, 3 ) );
2634
				$isEx     = TRUE;
2635
				$isOwn    = TRUE;
2636
				$this->__info['sys.exclusive-'.$listName] = TRUE;
2637
			} elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) )  {
2638
				$isOwn    = TRUE;
2639
				$listName = lcfirst( substr( $property, 3 ) );
2640
			} elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
2641
				$isShared = TRUE;
2642
			}
2643
		} elseif ( self::$convertArraysToJSON && is_array( $value ) ) {
2644
			$value = json_encode( $value );
2645
		}
2646
2647
		$hasAlias       = (!is_null($this->aliasName));
2648
		$differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
2649
								($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
2650
		$hasSQL         = ($this->withSql !== '' || $this->via !== NULL);
2651
		$exists         = isset( $this->properties[$property] );
2652
		$fieldLink      = $property . '_id';
2653
		$isFieldLink	= (($pos = strrpos($property, '_id')) !== FALSE) && array_key_exists( ($fieldName = substr($property, 0, $pos)), $this->properties );
2654
2655
2656
		if ( ($isOwn || $isShared) &&  (!$exists || $hasSQL || $differentAlias) ) {
2657
2658
			if ( !$this->noLoad ) {
2659
				list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
2660
				if ( $isOwn ) {
2661
					$beans = $this->getOwnList( $listName, $redbean );
2662
				} else {
2663
					$beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
2664
				}
2665
				$this->__info["sys.shadow.$property"] = $beans;
2666
			}
2667
		}
2668
2669
		$this->clearModifiers();
2670
2671
		$this->__info['tainted'] = TRUE;
2672
		$this->__info['changed'] = TRUE;
2673
		array_push( $this->__info['changelist'], $property );
2674
2675
		if ( array_key_exists( $fieldLink, $this->properties ) && !( $value instanceof OODBBean ) ) {
2676
			if ( is_null( $value ) || $value === FALSE ) {
2677
2678
				unset( $this->properties[ $property ]);
2679
				$this->properties[ $fieldLink ] = NULL;
2680
2681
				return;
2682
			} else {
2683
				throw new RedException( 'Cannot cast to bean.' );
2684
			}
2685
		}
2686
		
2687
		if ( $isFieldLink ){
2688
			unset( $this->properties[ $fieldName ]);
2689
			$this->properties[ $property ] = NULL;
2690
		}
2691
2692
2693
		if ( $value === FALSE ) {
2694
			$value = '0';
2695
		} elseif ( $value === TRUE ) {
2696
			$value = '1';
2697
			/* for some reason there is some kind of bug in xdebug so that it doesnt count this line otherwise... */
2698
		} elseif ( $value instanceof \DateTime ) { $value = $value->format( 'Y-m-d H:i:s' ); }
2699
		$this->properties[$property] = $value;
2700
	}
2701
2702
	/**
2703
	 * @deprecated
2704
	 *
2705
	 * Sets a property of the bean allowing you to keep track of
2706
	 * the state yourself. This method sets a property of the bean and
2707
	 * allows you to control how the state of the bean will be affected.
2708
	 *
2709
	 * While there may be some circumstances where this method is needed,
2710
	 * this method is considered to be extremely dangerous.
2711
	 * This method is only for advanced users.
2712
	 *
2713
	 * @param string  $property     property
2714
	 * @param mixed   $value        value
2715
	 * @param boolean $updateShadow whether you want to update the shadow
2716
	 * @param boolean $taint        whether you want to mark the bean as tainted
2717
	 *
2718
	 * @return void
2719
	 */
2720
	public function setProperty( $property, $value, $updateShadow = FALSE, $taint = FALSE )
2721
	{
2722
		$this->properties[$property] = $value;
2723
2724
		if ( $updateShadow ) {
2725
			$this->__info['sys.shadow.' . $property] = $value;
2726
		}
2727
2728
		if ( $taint ) {
2729
			$this->__info['tainted'] = TRUE;
2730
			$this->__info['changed'] = TRUE;
2731
		}
2732
	}
2733
2734
	/**
2735
	 * Returns the value of a meta property. A meta property
2736
	 * contains additional information about the bean object that will not
2737
	 * be stored in the database. Meta information is used to instruct
2738
	 * RedBeanPHP as well as other systems how to deal with the bean.
2739
	 * If the property cannot be found this getter will return NULL instead.
2740
	 *
2741
	 * Example:
2742
	 *
2743
	 * <code>
2744
	 * $bean->setMeta( 'flush-cache', TRUE );
2745
	 * </code>
2746
	 *
2747
	 * RedBeanPHP also stores meta data in beans, this meta data uses
2748
	 * keys prefixed with 'sys.' (system).
2749
	 *
2750
	 * @param string $path    path to property in meta data
2751
	 * @param mixed  $default default value
2752
	 *
2753
	 * @return mixed
2754
	 */
2755
	public function getMeta( $path, $default = NULL )
2756
	{
2757
		return ( isset( $this->__info[$path] ) ) ? $this->__info[$path] : $default;
2758
	}
2759
2760
	/**
2761
	 * Gets and unsets a meta property.
2762
	 * Moves a meta property out of the bean.
2763
	 * This is a short-cut method that can be used instead
2764
	 * of combining a get/unset.
2765
	 *
2766
	 * @param string $path    path to property in meta data
2767
	 * @param mixed  $default default value
2768
	 *
2769
	 * @return mixed
2770
	 */
2771
	public function moveMeta( $path, $value = NULL )
2772
	{
2773
		if ( isset( $this->__info[$path] ) ) {
2774
			$value = $this->__info[ $path ];
2775
			unset( $this->__info[ $path ] );
2776
		}
2777
		return $value;
2778
	}
2779
2780
	/**
2781
	 * Stores a value in the specified Meta information property.
2782
	 * The first argument should be the key to store the value under,
2783
	 * the second argument should be the value. It is common to use
2784
	 * a path-like notation for meta data in RedBeanPHP like:
2785
	 * 'my.meta.data', however the dots are purely for readability, the
2786
	 * meta data methods do not store nested structures or hierarchies.
2787
	 *
2788
	 * @param string $path  path / key to store value under
2789
	 * @param mixed  $value value to store in bean (not in database) as meta data
2790
	 *
2791
	 * @return OODBBean
2792
	 */
2793
	public function setMeta( $path, $value )
2794
	{
2795
		$this->__info[$path] = $value;
2796
		if ( $path == 'type' && !empty($this->beanHelper)) {
2797
			$this->__info['model'] = $this->beanHelper->getModelForBean( $this );
2798
		}
2799
2800
		return $this;
2801
	}
2802
2803
	/**
2804
	 * Copies the meta information of the specified bean
2805
	 * This is a convenience method to enable you to
2806
	 * exchange meta information easily.
2807
	 *
2808
	 * @param OODBBean $bean bean to copy meta data of
2809
	 *
2810
	 * @return OODBBean
2811
	 */
2812
	public function copyMetaFrom( OODBBean $bean )
2813
	{
2814
		$this->__info = $bean->__info;
2815
2816
		return $this;
2817
	}
2818
2819
	/**
2820
	 * Sends the call to the registered model.
2821
	 * This method can also be used to override bean behaviour.
2822
	 * In that case you don't want an error or exception to be triggered
2823
	 * if the method does not exist in the model (because it's optional).
2824
	 * Unfortunately we cannot add an extra argument to __call() for this
2825
	 * because the signature is fixed. Another option would be to set
2826
	 * a special flag ( i.e. $this->isOptionalCall ) but that would
2827
	 * cause additional complexity because we have to deal with extra temporary state.
2828
	 * So, instead I allowed the method name to be prefixed with '@', in practice
2829
	 * nobody creates methods like that - however the '@' symbol in PHP is widely known
2830
	 * to suppress error handling, so we can reuse the semantics of this symbol.
2831
	 * If a method name gets passed starting with '@' the overrideDontFail variable
2832
	 * will be set to TRUE and the '@' will be stripped from the function name before
2833
	 * attempting to invoke the method on the model. This way, we have all the
2834
	 * logic in one place.
2835
	 *
2836
	 * @param string $method name of the method
2837
	 * @param array  $args   argument list
2838
	 *
2839
	 * @return mixed
2840
	 */
2841
	public function __call( $method, $args )
2842
	{
2843
		if ( empty( $this->__info['model'] ) ) {
2844
			return NULL;
2845
		}
2846
		
2847
		$overrideDontFail = FALSE;
2848
		if ( strpos( $method, '@' ) === 0 ) {
2849
			$method = substr( $method, 1 );
2850
			$overrideDontFail = TRUE;
2851
		}
2852
		
2853
		if ( !method_exists( $this->__info['model'], $method ) ) {
2854
2855
			if ( self::$errorHandlingFUSE === FALSE || $overrideDontFail ) {
2856
				return NULL;
2857
			}
2858
2859
			if ( in_array( $method, array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ), TRUE ) ) {
2860
				return NULL;
2861
			}
2862
2863
			$message = "FUSE: method does not exist in model: $method";
2864
			if ( self::$errorHandlingFUSE === self::C_ERR_LOG ) {
2865
				error_log( $message );
2866
				return NULL;
2867
			} elseif ( self::$errorHandlingFUSE === self::C_ERR_NOTICE ) {
2868
				trigger_error( $message, E_USER_NOTICE );
2869
				return NULL;
2870
			} elseif ( self::$errorHandlingFUSE === self::C_ERR_WARN ) {
2871
				trigger_error( $message, E_USER_WARNING );
2872
				return NULL;
2873
			} elseif ( self::$errorHandlingFUSE === self::C_ERR_EXCEPTION ) {
2874
				throw new \Exception( $message );
2875
			} elseif ( self::$errorHandlingFUSE === self::C_ERR_FUNC ) {
2876
				$func = self::$errorHandler;
2877
				return $func(array(
2878
					'message' => $message,
2879
					'method' => $method,
2880
					'args' => $args,
2881
					'bean' => $this
2882
				));
2883
			}
2884
			trigger_error( $message, E_USER_ERROR );
2885
			return NULL;
2886
		}
2887
2888
		return call_user_func_array( array( $this->__info['model'], $method ), $args );
2889
	}
2890
2891
	/**
2892
	 * Implementation of __toString Method
2893
	 * Routes call to Model. If the model implements a __toString() method this
2894
	 * method will be called and the result will be returned. In case of an
2895
	 * echo-statement this result will be printed. If the model does not
2896
	 * implement a __toString method, this method will return a JSON
2897
	 * representation of the current bean.
2898
	 *
2899
	 * @return string
2900
	 */
2901
	public function __toString()
2902
	{
2903
		$string = $this->__call( '@__toString', array() );
2904
2905
		if ( $string === NULL ) {
2906
			$list = array();
2907
			foreach($this->properties as $property => $value) {
2908
				if (is_scalar($value)) {
2909
					if ( self::$enforceUTF8encoding ) {
2910
						$list[$property] = mb_convert_encoding($value, 'UTF-8', 'UTF-8');
2911
					} else {
2912
						$list[$property] = $value;
2913
					}
2914
				}
2915
			}
2916
			$data = json_encode( $list );
2917
			return $data;
2918
		} else {
2919
			return $string;
2920
		}
2921
	}
2922
2923
	/**
2924
	 * Implementation of Array Access Interface, you can access bean objects
2925
	 * like an array.
2926
	 * Call gets routed to __set.
2927
	 *
2928
	 * @param  mixed $offset offset string
2929
	 * @param  mixed $value  value
2930
	 *
2931
	 * @return void
2932
	 */
2933
	public function offsetSet( $offset, $value )
2934
	{
2935
		$this->__set( $offset, $value );
2936
	}
2937
2938
	/**
2939
	 * Implementation of Array Access Interface, you can access bean objects
2940
	 * like an array.
2941
	 *
2942
	 * Array functions do not reveal x-own-lists and list-alias because
2943
	 * you dont want duplicate entries in foreach-loops.
2944
	 * Also offers a slight performance improvement for array access.
2945
	 *
2946
	 * @param  mixed $offset property
2947
	 *
2948
	 * @return boolean
2949
	 */
2950
	public function offsetExists( $offset )
2951
	{
2952
		return $this->__isset( $offset );
2953
	}
2954
2955
	/**
2956
	 * Implementation of Array Access Interface, you can access bean objects
2957
	 * like an array.
2958
	 * Unsets a value from the array/bean.
2959
	 *
2960
	 * Array functions do not reveal x-own-lists and list-alias because
2961
	 * you dont want duplicate entries in foreach-loops.
2962
	 * Also offers a slight performance improvement for array access.
2963
	 *
2964
	 * @param  mixed $offset property
2965
	 *
2966
	 * @return void
2967
	 */
2968
	public function offsetUnset( $offset )
2969
	{
2970
		$this->__unset( $offset );
2971
	}
2972
2973
	/**
2974
	 * Implementation of Array Access Interface, you can access bean objects
2975
	 * like an array.
2976
	 * Returns value of a property.
2977
	 *
2978
	 * Array functions do not reveal x-own-lists and list-alias because
2979
	 * you dont want duplicate entries in foreach-loops.
2980
	 * Also offers a slight performance improvement for array access.
2981
	 *
2982
	 * @param  mixed $offset property
2983
	 *
2984
	 * @return mixed
2985
	 */
2986
	public function &offsetGet( $offset )
2987
	{
2988
		return $this->__get( $offset );
2989
	}
2990
2991
	/**
2992
	 * Chainable method to cast a certain ID to a bean; for instance:
2993
	 * $person = $club->fetchAs('person')->member;
2994
	 * This will load a bean of type person using member_id as ID.
2995
	 *
2996
	 * @param  string $type preferred fetch type
2997
	 *
2998
	 * @return OODBBean
2999
	 */
3000
	public function fetchAs( $type )
3001
	{
3002
		$this->fetchType = $type;
3003
3004
		return $this;
3005
	}
3006
3007
	/**
3008
	 * Prepares to load a bean using the bean type specified by
3009
	 * another property.
3010
	 * Similar to fetchAs but uses a column instead of a direct value.
3011
	 *
3012
	 * Usage:
3013
	 *
3014
	 * <code>
3015
	 * $car = R::load( 'car', $id );
3016
	 * $engine = $car->poly('partType')->part;
3017
	 * </code>
3018
	 *
3019
	 * In the example above, we have a bean of type car that
3020
	 * may consists of several parts (i.e. chassis, wheels).
3021
	 * To obtain the 'engine' we access the property 'part'
3022
	 * using the type (i.e. engine) specified by the property
3023
	 * indicated by the argument of poly().
3024
	 * This essentially is a polymorph relation, hence the name.
3025
	 * In database this relation might look like this:
3026
	 *
3027
	 * partType | part_id
3028
	 * --------------------
3029
	 * engine   | 1020300
3030
	 * wheel    | 4820088
3031
	 * chassis  | 7823122
3032
	 *
3033
	 * @param string $field field name to use for mapping
3034
	 *
3035
	 * @return OODBBean
3036
	 */
3037
	public function poly( $field )
3038
	{
3039
		return $this->fetchAs( $this->$field );
3040
	}
3041
3042
	/**
3043
	 * Traverses a bean property with the specified function.
3044
	 * Recursively iterates through the property invoking the
3045
	 * function for each bean along the way passing the bean to it.
3046
	 *
3047
	 * Can be used together with with, withCondition, alias and fetchAs.
3048
	 *
3049
	 * <code>
3050
	 * $task
3051
	 *    ->withCondition(' priority >= ? ', [ $priority ])
3052
	 *    ->traverse('ownTaskList', function( $t ) use ( &$todo ) {
3053
	 *       $todo[] = $t->descr;
3054
	 *    } );
3055
	 * </code>
3056
	 *
3057
	 * In the example, we create a to-do list by traversing a
3058
	 * hierarchical list of tasks while filtering out all tasks
3059
	 * having a low priority.
3060
	 *
3061
	 * @param string $property property
3062
	 * @param callable $function function
3063
	 * @param integer $maxDepth maximum depth for traversal
3064
	 *
3065
	 * @return OODBBean
3066
	 * @throws RedException
3067
	 */
3068
	public function traverse( $property, $function, $maxDepth = NULL )
3069
	{
3070
		$this->via = NULL;
3071
		if ( strpos( $property, 'shared' ) !== FALSE ) {
3072
			throw new RedException( 'Traverse only works with (x)own-lists.' );
3073
		}
3074
3075
		if ( !is_null( $maxDepth ) ) {
3076
			if ( !$maxDepth-- ) return $this;
3077
		}
3078
3079
		$oldFetchType = $this->fetchType;
3080
		$oldAliasName = $this->aliasName;
3081
		$oldWith      = $this->withSql;
3082
		$oldBindings  = $this->withParams;
3083
3084
		$beans = $this->$property;
3085
3086
		if ( $beans === NULL ) return $this;
3087
3088
		if ( !is_array( $beans ) ) $beans = array( $beans );
3089
3090
		foreach( $beans as $bean ) {
3091
			$function( $bean );
3092
			$bean->fetchType  = $oldFetchType;
3093
			$bean->aliasName  = $oldAliasName;
3094
			$bean->withSql    = $oldWith;
3095
			$bean->withParams = $oldBindings;
3096
3097
			$bean->traverse( $property, $function, $maxDepth );
3098
		}
3099
3100
		return $this;
3101
	}
3102
3103
	/**
3104
	 * Implementation of Countable interface. Makes it possible to use
3105
	 * count() function on a bean. This method gets invoked if you use
3106
	 * the count() function on a bean. The count() method will return
3107
	 * the number of properties of the bean, this includes the id property.
3108
	 *
3109
	 * Usage:
3110
	 *
3111
	 * <code>
3112
	 * $bean = R::dispense('bean');
3113
	 * $bean->property1 = 1;
3114
	 * $bean->property2 = 2;
3115
	 * echo count($bean); //prints 3 (cause id is also a property)
3116
	 * </code>
3117
	 *
3118
	 * The example above will print the number 3 to stdout.
3119
	 * Although we have assigned values to just two properties, the
3120
	 * primary key id is also a property of the bean and together
3121
	 * that makes 3. Besides using the count() function, you can also
3122
	 * call this method using a method notation: $bean->count().
3123
	 *
3124
	 * @return integer
3125
	 */
3126
	public function count()
3127
	{
3128
		return count( $this->properties );
3129
	}
3130
3131
	/**
3132
	 * Checks whether a bean is empty or not.
3133
	 * A bean is empty if it has no other properties than the id field OR
3134
	 * if all the other properties are 'empty()' (this might
3135
	 * include NULL and FALSE values).
3136
	 *
3137
	 * Usage:
3138
	 *
3139
	 * <code>
3140
	 * $newBean = R::dispense( 'bean' );
3141
	 * $newBean->isEmpty(); // TRUE
3142
	 * </code>
3143
	 *
3144
	 * The example above demonstrates that newly dispensed beans are
3145
	 * considered 'empty'.
3146
	 *
3147
	 * @return boolean
3148
	 */
3149
	public function isEmpty()
3150
	{
3151
		$empty = TRUE;
3152
		foreach ( $this->properties as $key => $value ) {
3153
			if ( $key == 'id' ) {
3154
				continue;
3155
			}
3156
			if ( !empty( $value ) ) {
3157
				$empty = FALSE;
3158
			}
3159
		}
3160
3161
		return $empty;
3162
	}
3163
3164
	/**
3165
	 * Chainable setter.
3166
	 * This method is actually the same as just setting a value
3167
	 * using a magic setter (->property = ...). The difference
3168
	 * is that you can chain these setters like this:
3169
	 *
3170
	 * Usage:
3171
	 *
3172
	 * <code>
3173
	 * $book->setAttr('title', 'mybook')->setAttr('author', 'me');
3174
	 * </code>
3175
	 *
3176
	 * This is the same as setting both properties $book->title and
3177
	 * $book->author. Sometimes a chained notation can improve the
3178
	 * readability of the code.
3179
	 *
3180
	 * @param string $property the property of the bean
3181
	 * @param mixed  $value    the value you want to set
3182
	 *
3183
	 * @return OODBBean
3184
	 */
3185
	public function setAttr( $property, $value )
3186
	{
3187
		$this->$property = $value;
3188
3189
		return $this;
3190
	}
3191
3192
	/**
3193
	 * Convience method.
3194
	 * Unsets all properties in the internal properties array.
3195
	 *
3196
	 * Usage:
3197
	 *
3198
	 * <code>
3199
	 * $bean->property = 1;
3200
	 * $bean->unsetAll( array( 'property' ) );
3201
	 * $bean->property; //NULL
3202
	 * </code>
3203
	 *
3204
	 * In the example above the 'property' of the bean will be
3205
	 * unset, resulting in the getter returning NULL instead of 1.
3206
	 *
3207
	 * @param array $properties properties you want to unset.
3208
	 *
3209
	 * @return OODBBean
3210
	 */
3211
	public function unsetAll( $properties )
3212
	{
3213
		foreach ( $properties as $prop ) {
3214
			if ( isset( $this->properties[$prop] ) ) {
3215
				unset( $this->properties[$prop] );
3216
			}
3217
		}
3218
		return $this;
3219
	}
3220
3221
	/**
3222
	 * Returns original (old) value of a property.
3223
	 * You can use this method to see what has changed in a
3224
	 * bean. The original value of a property is the value that
3225
	 * this property has had since the bean has been retrieved
3226
	 * from the databases.
3227
	 *
3228
	 * <code>
3229
	 * $book->title = 'new title';
3230
	 * $oldTitle = $book->old('title');
3231
	 * </code>
3232
	 *
3233
	 * The example shows how to use the old() method.
3234
	 * Here we set the title property of the bean to 'new title', then
3235
	 * we obtain the original value using old('title') and store it in
3236
	 * a variable $oldTitle.
3237
	 *
3238
	 * @param string $property name of the property you want the old value of
3239
	 *
3240
	 * @return mixed
3241
	 */
3242
	public function old( $property )
3243
	{
3244
		$old = $this->getMeta( 'sys.orig', array() );
3245
3246
		if ( array_key_exists( $property, $old ) ) {
3247
			return $old[$property];
3248
		}
3249
3250
		return NULL;
3251
	}
3252
3253
	/**
3254
	 * Convenience method.
3255
	 *
3256
	 * Returns TRUE if the bean has been changed, or FALSE otherwise.
3257
	 * Same as $bean->getMeta('tainted');
3258
	 * Note that a bean becomes tainted as soon as you retrieve a list from
3259
	 * the bean. This is because the bean lists are arrays and the bean cannot
3260
	 * determine whether you have made modifications to a list so RedBeanPHP
3261
	 * will mark the whole bean as tainted.
3262
	 *
3263
	 * @return boolean
3264
	 */
3265
	public function isTainted()
3266
	{
3267
		return $this->getMeta( 'tainted' );
3268
	}
3269
3270
	/**
3271
	 * Returns TRUE if the value of a certain property of the bean has been changed and
3272
	 * FALSE otherwise.
3273
	 *
3274
	 * Note that this method will return TRUE if applied to a loaded list.
3275
	 * Also note that this method keeps track of the bean's history regardless whether
3276
	 * it has been stored or not. Storing a bean does not undo it's history,
3277
	 * to clean the history of a bean use: clearHistory().
3278
	 *
3279
	 * @param string  $property name of the property you want the change-status of
3280
	 *
3281
	 * @return boolean
3282
	 */
3283
	public function hasChanged( $property )
3284
	{
3285
		return ( array_key_exists( $property, $this->properties ) ) ?
3286
			$this->old( $property ) != $this->properties[$property] : FALSE;
3287
	}
3288
3289
	/**
3290
	 * Returns TRUE if the specified list exists, has been loaded
3291
	 * and has been changed:
3292
	 * beans have been added or deleted.
3293
	 * This method will not tell you anything about
3294
	 * the state of the beans in the list.
3295
	 *
3296
	 * Usage:
3297
	 *
3298
	 * <code>
3299
	 * $book->hasListChanged( 'ownPage' ); // FALSE
3300
	 * array_pop( $book->ownPageList );
3301
	 * $book->hasListChanged( 'ownPage' ); // TRUE
3302
	 * </code>
3303
	 *
3304
	 * In the example, the first time we ask whether the
3305
	 * own-page list has been changed we get FALSE. Then we pop
3306
	 * a page from the list and the hasListChanged() method returns TRUE.
3307
	 *
3308
	 * @param string $property name of the list to check
3309
	 *
3310
	 * @return boolean
3311
	 */
3312
	public function hasListChanged( $property )
3313
	{
3314
		if ( !array_key_exists( $property, $this->properties ) ) return FALSE;
3315
		$diffAdded = array_diff_assoc( $this->properties[$property], $this->__info['sys.shadow.'.$property] );
3316
		if ( count( $diffAdded ) ) return TRUE;
3317
		$diffMissing = array_diff_assoc( $this->__info['sys.shadow.'.$property], $this->properties[$property] );
3318
		if ( count( $diffMissing ) ) return TRUE;
3319
		return FALSE;
3320
	}
3321
3322
	/**
3323
	 * Clears (syncs) the history of the bean.
3324
	 * Resets all shadow values of the bean to their current value.
3325
	 *
3326
	 * Usage:
3327
	 *
3328
	 * <code>
3329
	 * $book->title = 'book';
3330
	 * echo $book->hasChanged( 'title' ); //TRUE
3331
	 * R::store( $book );
3332
	 * echo $book->hasChanged( 'title' ); //TRUE
3333
	 * $book->clearHistory();
3334
	 * echo $book->hasChanged( 'title' ); //FALSE
3335
	 * </code>
3336
	 *
3337
	 * Note that even after store(), the history of the bean still
3338
	 * contains the act of changing the title of the book.
3339
	 * Only after invoking clearHistory() will the history of the bean
3340
	 * be cleared and will hasChanged() return FALSE.
3341
	 *
3342
	 * @return self
3343
	 */
3344
	public function clearHistory()
3345
	{
3346
		$this->__info['sys.orig'] = array();
3347
		foreach( $this->properties as $key => $value ) {
3348
			if ( is_scalar($value) ) {
3349
				$this->__info['sys.orig'][$key] = $value;
3350
			} else {
3351
				$this->__info['sys.shadow.'.$key] = $value;
3352
			}
3353
		}
3354
		$this->__info[ 'changelist' ] = array();
3355
		return $this;
3356
	}
3357
3358
	/**
3359
	 * Creates a N-M relation by linking an intermediate bean.
3360
	 * This method can be used to quickly connect beans using indirect
3361
	 * relations. For instance, given an album and a song you can connect the two
3362
	 * using a track with a number like this:
3363
	 *
3364
	 * Usage:
3365
	 *
3366
	 * <code>
3367
	 * $album->link('track', array('number'=>1))->song = $song;
3368
	 * </code>
3369
	 *
3370
	 * or:
3371
	 *
3372
	 * <code>
3373
	 * $album->link($trackBean)->song = $song;
3374
	 * </code>
3375
	 *
3376
	 * What this method does is adding the link bean to the own-list, in this case
3377
	 * ownTrack. If the first argument is a string and the second is an array or
3378
	 * a JSON string then the linking bean gets dispensed on-the-fly as seen in
3379
	 * example #1. After preparing the linking bean, the bean is returned thus
3380
	 * allowing the chained setter: ->song = $song.
3381
	 *
3382
	 * @param string|OODBBean $typeOrBean    type of bean to dispense or the full bean
3383
	 * @param string|array    $qualification JSON string or array (optional)
3384
	 *
3385
	 * @return OODBBean
3386
	 */
3387
	public function link( $typeOrBean, $qualification = array() )
3388
	{
3389
		if ( is_string( $typeOrBean ) ) {
3390
			$typeOrBean = AQueryWriter::camelsSnake( $typeOrBean );
3391
			$bean = $this->beanHelper->getToolBox()->getRedBean()->dispense( $typeOrBean );
3392
			if ( is_string( $qualification ) ) {
3393
				$data = json_decode( $qualification, TRUE );
3394
			} else {
3395
				$data = $qualification;
3396
			}
3397
			foreach ( $data as $key => $value ) {
3398
				$bean->$key = $value;
3399
			}
3400
		} else {
3401
			$bean = $typeOrBean;
3402
		}
3403
		$list = 'own' . ucfirst( $bean->getMeta( 'type' ) );
3404
		array_push( $this->$list, $bean );
3405
		return $bean;
3406
	}
3407
3408
	/**
3409
	 * Returns a bean of the given type with the same ID of as
3410
	 * the current one. This only happens in a one-to-one relation.
3411
	 * This is as far as support for 1-1 goes in RedBeanPHP. This
3412
	 * method will only return a reference to the bean, changing it
3413
	 * and storing the bean will not update the related one-bean.
3414
	 *
3415
	 * Usage:
3416
	 *
3417
	 * <code>
3418
	 * $author = R::load( 'author', $id );
3419
	 * $biography = $author->one( 'bio' );
3420
	 * </code>
3421
	 *
3422
	 * The example loads the biography associated with the author
3423
	 * using a one-to-one relation. These relations are generally not
3424
	 * created (nor supported) by RedBeanPHP.
3425
	 *
3426
	 * @param  $type type of bean to load
3427
	 *
3428
	 * @return OODBBean
3429
	 */
3430
	public function one( $type ) {
3431
		return $this->beanHelper
3432
			->getToolBox()
3433
			->getRedBean()
3434
			->load( $type, $this->id );
3435
	}
3436
3437
	/**
3438
	 * Reloads the bean.
3439
	 * Returns the same bean freshly loaded from the database.
3440
	 * This method is equal to the following code:
3441
	 *
3442
	 * <code>
3443
	 * $id = $bean->id;
3444
	 * $type = $bean->getMeta( 'type' );
3445
	 * $bean = R::load( $type, $id );
3446
	 * </code>
3447
	 *
3448
	 * This is just a convenience method to reload beans
3449
	 * quickly.
3450
	 *
3451
	 * Usage:
3452
	 *
3453
	 * <code>
3454
	 * R::exec( ...update query... );
3455
	 * $book = $book->fresh();
3456
	 * </code>
3457
	 *
3458
	 * The code snippet above illustrates how to obtain changes
3459
	 * caused by an UPDATE query, simply by reloading the bean using
3460
	 * the fresh() method.
3461
	 *
3462
	 * @return OODBBean
3463
	 */
3464
	public function fresh()
3465
	{
3466
		return $this->beanHelper
3467
			->getToolbox()
3468
			->getRedBean()
3469
			->load( $this->getMeta( 'type' ), $this->properties['id'] );
3470
	}
3471
3472
	/**
3473
	 * Registers a association renaming globally.
3474
	 * Use via() and link() to associate shared beans using a
3475
	 * 3rd bean that will act as an intermediate type. For instance
3476
	 * consider an employee and a project. We could associate employees
3477
	 * with projects using a sharedEmployeeList. But, maybe there is more
3478
	 * to the relationship than just the association. Maybe we want
3479
	 * to qualify the relation between a project and an employee with
3480
	 * a role: 'developer', 'designer', 'tester' and so on. In that case,
3481
	 * it might be better to introduce a new concept to reflect this:
3482
	 * the participant. However, we still want the flexibility to
3483
	 * query our employees in one go. This is where link() and via()
3484
	 * can help. You can still introduce the more applicable
3485
	 * concept (participant) and have your easy access to the shared beans.
3486
	 *
3487
	 * <code>
3488
	 * $Anna = R::dispense( 'employee' );
3489
	 * $Anna->badge   = 'Anna';
3490
	 * $project = R::dispense( 'project' );
3491
	 * $project->name = 'x';
3492
	 * $Anna->link( 'participant', array(
3493
	 *	 'arole' => 'developer'
3494
	 *	) )->project = $project;
3495
	 * R::storeAll( array( $project,  $Anna )  );
3496
	 * $employees = $project
3497
	 *	->with(' ORDER BY badge ASC ')
3498
	 *  ->via( 'participant' )
3499
	 *  ->sharedEmployee;
3500
	 * </code>
3501
	 *
3502
	 * This piece of code creates a project and an employee.
3503
	 * It then associates the two using a via-relation called
3504
	 * 'participant' ( employee <-> participant <-> project ).
3505
	 * So, there will be a table named 'participant' instead of
3506
	 * a table named 'employee_project'. Using the via() method, the
3507
	 * employees associated with the project are retrieved 'via'
3508
	 * the participant table (and an SQL snippet to order them by badge).
3509
	 *
3510
	 * @param string $via type you wish to use for shared lists
3511
	 *
3512
	 * @return OODBBean
3513
	 */
3514
	public function via( $via )
3515
	{
3516
		$this->via = AQueryWriter::camelsSnake( $via );
3517
3518
		return $this;
3519
	}
3520
3521
	/**
3522
	 * Counts all own beans of type $type.
3523
	 * Also works with alias(), with() and withCondition().
3524
	 * Own-beans or xOwn-beans (exclusively owned beans) are beans
3525
	 * that have been associated using a one-to-many relation. They can
3526
	 * be accessed through the ownXList where X is the type of the
3527
	 * associated beans.
3528
	 *
3529
	 * Usage:
3530
	 *
3531
	 * <code>
3532
	 * $Bill->alias( 'author' )
3533
	 *      ->countOwn( 'book' );
3534
	 * </code>
3535
	 *
3536
	 * The example above counts all the books associated with 'author'
3537
	 * $Bill.
3538
	 *
3539
	 * @param string $type the type of bean you want to count
3540
	 *
3541
	 * @return integer
3542
	 */
3543
	public function countOwn( $type )
3544
	{
3545
		$type = $this->beau( $type );
3546
		if ( $this->aliasName ) {
3547
			$myFieldLink     = $this->aliasName . '_id';
3548
			$this->aliasName = NULL;
3549
		} else {
3550
			$myFieldLink = $this->__info['type'] . '_id';
3551
		}
3552
		$count = 0;
3553
		if ( $this->getID() ) {
3554
			reset( $this->withParams );
3555
			$joinSql = $this->parseJoin( $type );
3556
			$firstKey = count( $this->withParams ) > 0
3557
				? key( $this->withParams )
3558
				: 0;
3559
			if ( is_int( $firstKey ) ) {
3560
				$bindings = array_merge( array( $this->getID() ), $this->withParams );
3561
				$count    = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), "{$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
3562
			} else {
3563
				$bindings           = $this->withParams;
3564
				$bindings[':slot0'] = $this->getID();
3565
				$count              = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), "{$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
3566
			}
3567
		}
3568
		$this->clearModifiers();
3569
		return (int) $count;
3570
	}
3571
3572
	/**
3573
	 * Counts all shared beans of type $type.
3574
	 * Also works with via(), with() and withCondition().
3575
	 * Shared beans are beans that have an many-to-many relation.
3576
	 * They can be accessed using the sharedXList, where X the
3577
	 * type of the shared bean.
3578
	 *
3579
	 * Usage:
3580
	 *
3581
	 * <code>
3582
	 * $book = R::dispense( 'book' );
3583
	 * $book->sharedPageList = R::dispense( 'page', 5 );
3584
	 * R::store( $book );
3585
	 * echo $book->countShared( 'page' );
3586
	 * </code>
3587
	 *
3588
	 * The code snippet above will output '5', because there
3589
	 * are 5 beans of type 'page' in the shared list.
3590
	 *
3591
	 * @param string $type type of bean you wish to count
3592
	 *
3593
	 * @return integer
3594
	 */
3595
	public function countShared( $type )
3596
	{
3597
		$toolbox = $this->beanHelper->getToolbox();
3598
		$redbean = $toolbox->getRedBean();
3599
		$writer  = $toolbox->getWriter();
3600
		if ( $this->via ) {
3601
			$oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
3602
			if ( $oldName !== $this->via ) {
3603
				//set the new renaming rule
3604
				$writer->renameAssocTable( $oldName, $this->via );
3605
				$this->via = NULL;
3606
			}
3607
		}
3608
		$type  = $this->beau( $type );
3609
		$count = 0;
3610
		if ( $this->getID() ) {
3611
			$count = $redbean->getAssociationManager()->relatedCount( $this, $type, $this->withSql, $this->withParams );
3612
		}
3613
		$this->clearModifiers();
3614
		return (integer) $count;
3615
	}
3616
3617
	/**
3618
	 * Iterates through the specified own-list and
3619
	 * fetches all properties (with their type) and
3620
	 * returns the references.
3621
	 * Use this method to quickly load indirectly related
3622
	 * beans in an own-list. Whenever you cannot use a
3623
	 * shared-list this method offers the same convenience
3624
	 * by aggregating the parent beans of all children in
3625
	 * the specified own-list.
3626
	 *
3627
	 * Example:
3628
	 *
3629
	 * <code>
3630
	 * $quest->aggr( 'xownQuestTarget', 'target', 'quest' );
3631
	 * </code>
3632
	 *
3633
	 * Loads (in batch) and returns references to all
3634
	 * quest beans residing in the $questTarget->target properties
3635
	 * of each element in the xownQuestTargetList.
3636
	 *
3637
	 * @param string $list     the list you wish to process
3638
	 * @param string $property the property to load
3639
	 * @param string $type     the type of bean residing in this property (optional)
3640
	 *
3641
	 * @return array
3642
	 */
3643
	public function &aggr( $list, $property, $type = NULL )
3644
	{
3645
		$this->via = NULL;
3646
		$ids = $beanIndex = $references = array();
3647
3648
		if ( strlen( $list ) < 4 ) throw new RedException('Invalid own-list.');
3649
		if ( strpos( $list, 'own') !== 0 ) throw new RedException('Only own-lists can be aggregated.');
3650
		if ( !ctype_upper( substr( $list, 3, 1 ) ) ) throw new RedException('Invalid own-list.');
3651
3652
		if ( is_null( $type ) ) $type = $property;
3653
3654
		foreach( $this->$list as $bean ) {
3655
			$field = $property . '_id';
3656
			if ( isset( $bean->$field)  ) {
3657
				$ids[] = $bean->$field;
3658
				$beanIndex[$bean->$field] = $bean;
3659
			}
3660
		}
3661
3662
		$beans = $this->beanHelper->getToolBox()->getRedBean()->batch( $type, $ids );
3663
3664
		//now preload the beans as well
3665
		foreach( $beans as $bean ) {
3666
			$beanIndex[$bean->id]->setProperty( $property, $bean );
3667
		}
3668
3669
		foreach( $beanIndex as $indexedBean ) {
3670
			$references[] = $indexedBean->$property;
3671
		}
3672
3673
		return $references;
3674
	}
3675
3676
	/**
3677
	 * Tests whether the database identities of two beans are equal.
3678
	 * Two beans are considered 'equal' if:
3679
	 *
3680
	 * a. the types of the beans match
3681
	 * b. the ids of the beans match
3682
	 *
3683
	 * Returns TRUE if the beans are considered equal according to this
3684
	 * specification and FALSE otherwise.
3685
	 *
3686
	 * Usage:
3687
	 *
3688
	 * <code>
3689
	 * $coffee->fetchAs( 'flavour' )->taste->equals(
3690
	 *    R::enum('flavour:mocca')
3691
	 * );
3692
	 * </code>
3693
	 *
3694
	 * The example above compares the flavour label 'mocca' with
3695
	 * the flavour label attachec to the $coffee bean. This illustrates
3696
	 * how to use equals() with RedBeanPHP-style enums.
3697
	 *
3698
	 * @param OODBBean $bean other bean
3699
	 *
3700
	 * @return boolean
3701
	 */
3702
	public function equals(OODBBean $bean)
3703
	{
3704
		return (bool) (
3705
			   ( (string) $this->properties['id'] === (string) $bean->properties['id'] )
3706
			&& ( (string) $this->__info['type']   === (string) $bean->__info['type']   )
3707
		);
3708
	}
3709
3710
	/**
3711
	 * Magic method jsonSerialize,
3712
	 * implementation for the \JsonSerializable interface,
3713
	 * this method gets called by json_encode and
3714
	 * facilitates a better JSON representation
3715
	 * of the bean. Exports the bean on JSON serialization,
3716
	 * for the JSON fans.
3717
	 *
3718
	 * Models can override jsonSerialize (issue #651) by
3719
	 * implementing a __jsonSerialize method which should return
3720
	 * an array. The __jsonSerialize override gets called with
3721
	 * the @ modifier to prevent errors or warnings.
3722
	 *
3723
	 * @see  http://php.net/manual/en/class.jsonserializable.php
3724
	 *
3725
	 * @return array
3726
	 */
3727
	public function jsonSerialize()
3728
	{
3729
		$json = $this->__call( '@__jsonSerialize', array( ) );
3730
3731
		if ( $json !== NULL ) {
3732
			return $json;
3733
		}
3734
3735
		return $this->export();
3736
	}
3737
}
3738
}
3739
3740
namespace RedBeanPHP {
3741
3742
use RedBeanPHP\Observer as Observer;
3743
3744
/**
3745
 * Observable
3746
 * Base class for Observables
3747
 *
3748
 * @file            RedBeanPHP/Observable.php
3749
 * @author          Gabor de Mooij and the RedBeanPHP community
3750
 * @license         BSD/GPLv2
3751
 *
3752
 * @copyright
3753
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
3754
 * This source file is subject to the BSD/GPLv2 License that is bundled
3755
 * with this source code in the file license.txt.
3756
 */
3757
abstract class Observable { //bracket must be here - otherwise coverage software does not understand.
3758
3759
	/**
3760
	 * @var array
3761
	 */
3762
	private $observers = array();
3763
3764
	/**
3765
	 * Implementation of the Observer Pattern.
3766
	 * Adds an event listener to the observable object.
3767
	 * First argument should be the name of the event you wish to listen for.
3768
	 * Second argument should be the object that wants to be notified in case
3769
	 * the event occurs.
3770
	 *
3771
	 * @param string   $eventname event identifier
3772
	 * @param Observer $observer  observer instance
3773
	 *
3774
	 * @return void
3775
	 */
3776
	public function addEventListener( $eventname, Observer $observer )
3777
	{
3778
		if ( !isset( $this->observers[$eventname] ) ) {
3779
			$this->observers[$eventname] = array();
3780
		}
3781
3782
		if ( in_array( $observer, $this->observers[$eventname] ) ) {
3783
			return;
3784
		}
3785
3786
		$this->observers[$eventname][] = $observer;
3787
	}
3788
3789
	/**
3790
	 * Notifies listeners.
3791
	 * Sends the signal $eventname, the event identifier and a message object
3792
	 * to all observers that have been registered to receive notification for
3793
	 * this event. Part of the observer pattern implementation in RedBeanPHP.
3794
	 *
3795
	 * @param string $eventname event you want signal
3796
	 * @param mixed  $info      message object to send along
3797
	 *
3798
	 * @return void
3799
	 */
3800
	public function signal( $eventname, $info )
3801
	{
3802
		if ( !isset( $this->observers[$eventname] ) ) {
3803
			$this->observers[$eventname] = array();
3804
		}
3805
3806
		foreach ( $this->observers[$eventname] as $observer ) {
3807
			$observer->onEvent( $eventname, $info );
3808
		}
3809
	}
3810
}
3811
}
3812
3813
namespace RedBeanPHP {
3814
3815
/**
3816
 * Observer.
3817
 *
3818
 * Interface for Observer object. Implementation of the
3819
 * observer pattern.
3820
 *
3821
 * @file    RedBeanPHP/Observer.php
3822
 * @author  Gabor de Mooij and the RedBeanPHP community
3823
 * @license BSD/GPLv2
3824
 * @desc    Part of the observer pattern in RedBean
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
 */
3831
interface Observer
3832
{
3833
	/**
3834
	 * An observer object needs to be capable of receiving
3835
	 * notifications. Therefore the observer needs to implement the
3836
	 * onEvent method with two parameters: the event identifier specifying the
3837
	 * current event and a message object (in RedBeanPHP this can also be a bean).
3838
	 *
3839
	 * @param string $eventname event identifier
3840
	 * @param mixed  $bean      a message sent along with the notification
3841
	 *
3842
	 * @return void
3843
	 */
3844
	public function onEvent( $eventname, $bean );
3845
}
3846
}
3847
3848
namespace RedBeanPHP {
3849
3850
/**
3851
 * Adapter Interface.
3852
 * Describes the API for a RedBeanPHP Database Adapter.
3853
 * This interface defines the API contract for
3854
 * a RedBeanPHP Database Adapter.
3855
 *
3856
 * @file    RedBeanPHP/Adapter.php
3857
 * @author  Gabor de Mooij and the RedBeanPHP Community
3858
 * @license BSD/GPLv2
3859
 *
3860
 * @copyright
3861
 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
3862
 * This source file is subject to the BSD/GPLv2 License that is bundled
3863
 * with this source code in the file license.txt.
3864
 */
3865
interface Adapter
3866
{
3867
	/**
3868
	 * Should returns a string containing the most recent SQL query
3869
	 * that has been processed by the adapter.
3870
	 *
3871
	 * @return string
3872
	 */
3873
	public function getSQL();
3874
3875
	/**
3876
	 * Executes an SQL Statement using an array of values to bind
3877
	 * If $noevent is TRUE then this function will not signal its
3878
	 * observers to notify about the SQL execution; this to prevent
3879
	 * infinite recursion when using observers.
3880
	 *
3881
	 * @param string  $sql      string containing SQL code for database
3882
	 * @param array   $bindings array of values to bind to parameters in query string
3883
	 * @param boolean $noevent  no event firing
3884
	 *
3885
	 * @return void
3886
	 */
3887
	public function exec( $sql, $bindings = array(), $noevent = FALSE );
3888
3889
	/**
3890
	 * Executes an SQL Query and returns a resultset.
3891
	 * This method returns a multi dimensional resultset similar to getAll
3892
	 * The values array can be used to bind values to the place holders in the
3893
	 * SQL query.
3894
	 *
3895
	 * @param string $sql      string containing SQL code for database
3896
	 * @param array  $bindings array of values to bind to parameters in query string
3897
	 *
3898
	 * @return array
3899
	 */
3900
	public function get( $sql, $bindings = array() );
3901
3902
	/**
3903
	 * Executes an SQL Query and returns a resultset.
3904
	 * This method returns a single row (one array) resultset.
3905
	 * The values array can be used to bind values to the place holders in the
3906
	 * SQL query.
3907
	 *
3908
	 * @param string $sql      string containing SQL code for database
3909
	 * @param array  $bindings array of values to bind to parameters in query string
3910
	 *
3911
	 * @return array
3912
	 */
3913
	public function getRow( $sql, $bindings = array() );
3914
3915
	/**
3916
	 * Executes an SQL Query and returns a resultset.
3917
	 * This method returns a single column (one array) resultset.
3918
	 * The values array can be used to bind values to the place holders in the
3919
	 * SQL query.
3920
	 *
3921
	 * @param string $sql      string containing SQL code for database
3922
	 * @param array  $bindings array of values to bind to parameters in query string
3923
	 *
3924
	 * @return array
3925
	 */
3926
	public function getCol( $sql, $bindings = array() );
3927
3928
	/**
3929
	 * Executes an SQL Query and returns a resultset.
3930
	 * This method returns a single cell, a scalar value as the resultset.
3931
	 * The values array can be used to bind values to the place holders in the
3932
	 * SQL query.
3933
	 *
3934
	 * @param string $sql      string containing SQL code for database
3935
	 * @param array  $bindings array of values to bind to parameters in query string
3936
	 *
3937
	 * @return string
3938
	 */
3939
	public function getCell( $sql, $bindings = array() );
3940
3941
	/**
3942
	 * Executes the SQL query specified in $sql and takes
3943
	 * the first two columns of the resultset. This function transforms the
3944
	 * resultset into an associative array. Values from the the first column will
3945
	 * serve as keys while the values of the second column will be used as values.
3946
	 * The values array can be used to bind values to the place holders in the
3947
	 * SQL query.
3948
	 *
3949
	 * @param string $sql      string containing SQL code for database
3950
	 * @param array  $bindings array of values to bind to parameters in query string
3951
	 *
3952
	 * @return array
3953
	 */
3954
	public function getAssoc( $sql, $bindings = array() );
3955
3956
	/**
3957
	 * Executes the SQL query specified in $sql and indexes
3958
	 * the row by the first column.
3959
	 *
3960
	 * @param string $sql      Sstring containing SQL code for databaseQL
3961
	 * @param array  $bindings values to bind
3962
	 *
3963
	 * @return array
3964
	 */
3965
	public function getAssocRow( $sql, $bindings = array() );
3966
3967
	/**
3968
	 * Returns the latest insert ID.
3969
	 *
3970
	 * @return integer
3971
	 */
3972
	public function getInsertID();
3973
3974
	/**
3975
	 * Returns the number of rows that have been
3976
	 * affected by the last update statement.
3977
	 *
3978
	 * @return integer
3979
	 */
3980
	public function getAffectedRows();
3981
3982
	/**
3983
	 * Returns a database agnostic Cursor object.
3984
	 *
3985
	 * @param string $sql      string containing SQL code for database
3986
	 * @param array  $bindings array of values to bind to parameters in query string
3987
	 *
3988
	 * @return Cursor
3989
	 */
3990
	public function getCursor( $sql, $bindings = array() );
3991
3992
	/**
3993
	 * Returns the original database resource. This is useful if you want to
3994
	 * perform operations on the driver directly instead of working with the
3995
	 * adapter. RedBean will only access the adapter and never to talk
3996
	 * directly to the driver though.
3997
	 *
3998
	 * @return mixed
3999
	 */
4000
	public function getDatabase();
4001
4002
	/**
4003
	 * This method is part of the RedBean Transaction Management
4004
	 * mechanisms.
4005
	 * Starts a transaction.
4006
	 *
4007
	 * @return void
4008
	 */
4009
	public function startTransaction();
4010
4011
	/**
4012
	 * This method is part of the RedBean Transaction Management
4013
	 * mechanisms.
4014
	 * Commits the transaction.
4015
	 *
4016
	 * @return void
4017
	 */
4018
	public function commit();
4019
4020
	/**
4021
	 * This method is part of the RedBean Transaction Management
4022
	 * mechanisms.
4023
	 * Rolls back the transaction.
4024
	 *
4025
	 * @return void
4026
	 */
4027
	public function rollback();
4028
4029
	/**
4030
	 * Closes database connection.
4031
	 *
4032
	 * @return void
4033
	 */
4034
	public function close();
4035
4036
	/**
4037
	 * Sets a driver specific option.
4038
	 * Using this method you can access driver-specific functions.
4039
	 * If the selected option exists the value will be passed and
4040
	 * this method will return boolean TRUE, otherwise it will return
4041
	 * boolean FALSE.
4042
	 *
4043
	 * @param string $optionKey   option key
4044
	 * @param string $optionValue option value
4045
	 *
4046
	 * @return boolean
4047
	 */
4048
	public function setOption( $optionKey, $optionValue );
4049
}
4050
}
4051
4052
namespace RedBeanPHP\Adapter {
4053
4054
use RedBeanPHP\Observable as Observable;
4055
use RedBeanPHP\Adapter as Adapter;
4056
use RedBeanPHP\Driver as Driver;
4057
4058
/**
4059
 * DBAdapter (Database Adapter)
4060
 *
4061
 * An adapter class to connect various database systems to RedBean
4062
 * Database Adapter Class. The task of the database adapter class is to
4063
 * communicate with the database driver. You can use all sorts of database
4064
 * drivers with RedBeanPHP. The default database drivers that ships with
4065
 * the RedBeanPHP library is the RPDO driver ( which uses the PHP Data Objects
4066
 * Architecture aka PDO ).
4067
 *
4068
 * @file    RedBeanPHP/Adapter/DBAdapter.php
4069
 * @author  Gabor de Mooij and the RedBeanPHP Community.
4070
 * @license BSD/GPLv2
4071
 *
4072
 * @copyright
4073
 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP community.
4074
 * This source file is subject to the BSD/GPLv2 License that is bundled
4075
 * with this source code in the file license.txt.
4076
 */
4077
class DBAdapter extends Observable implements Adapter
4078
{
4079
	/**
4080
	 * @var Driver
4081
	 */
4082
	private $db = NULL;
4083
4084
	/**
4085
	 * @var string
4086
	 */
4087
	private $sql = '';
4088
4089
	/**
4090
	 * Constructor.
4091
	 *
4092
	 * Creates an instance of the RedBean Adapter Class.
4093
	 * This class provides an interface for RedBean to work
4094
	 * with ADO compatible DB instances.
4095
	 *
4096
	 * Usage:
4097
	 *
4098
	 * <code>
4099
	 * $database = new RPDO( $dsn, $user, $pass );
4100
	 * $adapter = new DBAdapter( $database );
4101
	 * $writer = new PostgresWriter( $adapter );
4102
	 * $oodb = new OODB( $writer, FALSE );
4103
	 * $bean = $oodb->dispense( 'bean' );
4104
	 * $bean->name = 'coffeeBean';
4105
	 * $id = $oodb->store( $bean );
4106
	 * $bean = $oodb->load( 'bean', $id );
4107
	 * </code>
4108
	 *
4109
	 * The example above creates the 3 RedBeanPHP core objects:
4110
	 * the Adapter, the Query Writer and the OODB instance and
4111
	 * wires them together. The example also demonstrates some of
4112
	 * the methods that can be used with OODB, as you see, they
4113
	 * closely resemble their facade counterparts.
4114
	 *
4115
	 * The wiring process: create an RPDO instance using your database
4116
	 * connection parameters. Create a database adapter from the RPDO
4117
	 * object and pass that to the constructor of the writer. Next,
4118
	 * create an OODB instance from the writer. Now you have an OODB
4119
	 * object.
4120
	 *
4121
	 * @param Driver $database ADO Compatible DB Instance
4122
	 */
4123
	public function __construct( $database )
4124
	{
4125
		$this->db = $database;
4126
	}
4127
4128
	/**
4129
	 * Returns a string containing the most recent SQL query
4130
	 * processed by the database adapter, thus conforming to the
4131
	 * interface:
4132
	 *
4133
	 * @see Adapter::getSQL
4134
	 *
4135
	 * Methods like get(), getRow() and exec() cause this SQL cache
4136
	 * to get filled. If no SQL query has been processed yet this function
4137
	 * will return an empty string.
4138
	 *
4139
	 * @return string
4140
	 */
4141
	public function getSQL()
4142
	{
4143
		return $this->sql;
4144
	}
4145
4146
	/**
4147
	 * @see Adapter::exec
4148
	 */
4149
	public function exec( $sql, $bindings = array(), $noevent = FALSE )
4150
	{
4151
		if ( !$noevent ) {
4152
			$this->sql = $sql;
4153
			$this->signal( 'sql_exec', $this );
4154
		}
4155
4156
		return $this->db->Execute( $sql, $bindings );
4157
	}
4158
4159
	/**
4160
	 * @see Adapter::get
4161
	 */
4162
	public function get( $sql, $bindings = array() )
4163
	{
4164
		$this->sql = $sql;
4165
		$this->signal( 'sql_exec', $this );
4166
4167
		return $this->db->GetAll( $sql, $bindings );
4168
	}
4169
4170
	/**
4171
	 * @see Adapter::getRow
4172
	 */
4173
	public function getRow( $sql, $bindings = array() )
4174
	{
4175
		$this->sql = $sql;
4176
		$this->signal( 'sql_exec', $this );
4177
4178
		return $this->db->GetRow( $sql, $bindings );
4179
	}
4180
4181
	/**
4182
	 * @see Adapter::getCol
4183
	 */
4184
	public function getCol( $sql, $bindings = array() )
4185
	{
4186
		$this->sql = $sql;
4187
		$this->signal( 'sql_exec', $this );
4188
4189
		return $this->db->GetCol( $sql, $bindings );
4190
	}
4191
4192
	/**
4193
	 * @see Adapter::getAssoc
4194
	 */
4195
	public function getAssoc( $sql, $bindings = array() )
4196
	{
4197
		$this->sql = $sql;
4198
4199
		$this->signal( 'sql_exec', $this );
4200
4201
		$rows  = $this->db->GetAll( $sql, $bindings );
4202
4203
		if ( !$rows ) {
4204
			return array();
4205
		}
4206
4207
		$assoc = array();
4208
		
4209
		foreach ( $rows as $row ) {
4210
			if ( empty( $row ) ) continue;
4211
4212
			$key   = array_shift( $row );
4213
			switch ( count( $row ) ) {
4214
				case 0:
4215
					$value = $key;
4216
					break;
4217
				case 1:
4218
					$value = reset( $row );
4219
					break;
4220
				default:
4221
					$value = $row;
4222
			}
4223
4224
			$assoc[$key] = $value;
4225
		}
4226
4227
		return $assoc;
4228
	}
4229
4230
	/**
4231
	 * @see Adapter::getAssocRow
4232
	 */
4233
	public function getAssocRow($sql, $bindings = array())
4234
	{
4235
		$this->sql = $sql;
4236
		$this->signal( 'sql_exec', $this );
4237
4238
		return $this->db->GetAssocRow( $sql, $bindings );
4239
	}
4240
4241
	/**
4242
	 * @see Adapter::getCell
4243
	 */
4244
	public function getCell( $sql, $bindings = array(), $noSignal = NULL )
4245
	{
4246
		$this->sql = $sql;
4247
4248
		if ( !$noSignal ) $this->signal( 'sql_exec', $this );
4249
4250
		return $this->db->GetOne( $sql, $bindings );
4251
	}
4252
4253
	/**
4254
	 * @see Adapter::getCursor
4255
	 */
4256
	public function getCursor( $sql, $bindings = array() )
4257
	{
4258
		return $this->db->GetCursor( $sql, $bindings );
4259
	}
4260
4261
	/**
4262
	 * @see Adapter::getInsertID
4263
	 */
4264
	public function getInsertID()
4265
	{
4266
		return $this->db->getInsertID();
4267
	}
4268
4269
	/**
4270
	 * @see Adapter::getAffectedRows
4271
	 */
4272
	public function getAffectedRows()
4273
	{
4274
		return $this->db->Affected_Rows();
4275
	}
4276
4277
	/**
4278
	 * @see Adapter::getDatabase
4279
	 */
4280
	public function getDatabase()
4281
	{
4282
		return $this->db;
4283
	}
4284
4285
	/**
4286
	 * @see Adapter::startTransaction
4287
	 */
4288
	public function startTransaction()
4289
	{
4290
		$this->db->StartTrans();
4291
	}
4292
4293
	/**
4294
	 * @see Adapter::commit
4295
	 */
4296
	public function commit()
4297
	{
4298
		$this->db->CommitTrans();
4299
	}
4300
4301
	/**
4302
	 * @see Adapter::rollback
4303
	 */
4304
	public function rollback()
4305
	{
4306
		$this->db->FailTrans();
4307
	}
4308
4309
	/**
4310
	 * @see Adapter::close.
4311
	 */
4312
	public function close()
4313
	{
4314
		$this->db->close();
4315
	}
4316
4317
	/**
4318
	 * @see Adapter::setOption
4319
	 */
4320
	public function setOption( $optionKey, $optionValue ) {
4321
		if ( method_exists( $this->db, $optionKey ) ) {
4322
			call_user_func( array( $this->db, $optionKey ), $optionValue );
4323
			return TRUE;
4324
		}
4325
		return FALSE;
4326
	}
4327
}
4328
}
4329
4330
namespace RedBeanPHP {
4331
4332
/**
4333
 * Database Cursor Interface.
4334
 * A cursor is used by Query Writers to fetch Query Result rows
4335
 * one row at a time. This is useful if you expect the result set to
4336
 * be quite large. This interface dscribes the API of a database
4337
 * cursor. There can be multiple implementations of the Cursor,
4338
 * by default RedBeanPHP offers the PDOCursor for drivers shipping
4339
 * with RedBeanPHP and the NULLCursor.
4340
 *
4341
 * @file    RedBeanPHP/Cursor.php
4342
 * @author  Gabor de Mooij and the RedBeanPHP Community
4343
 * @license BSD/GPLv2
4344
 *
4345
 * @copyright
4346
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
4347
 * This source file is subject to the BSD/GPLv2 License that is bundled
4348
 * with this source code in the file license.txt.
4349
 */
4350
interface Cursor
4351
{
4352
	/**
4353
	 * Should retrieve the next row of the result set.
4354
	 * This method is used to iterate over the result set.
4355
	 *
4356
	 * @return array
4357
	 */
4358
	public function getNextItem();
4359
	
4360
	/**
4361
	 * Resets the cursor by closing it and re-executing the statement.
4362
	 * This reloads fresh data from the database for the whole collection.
4363
	 *
4364
	 * @return void
4365
	 */
4366
	public function reset();
4367
4368
	/**
4369
	 * Closes the database cursor.
4370
	 * Some databases require a cursor to be closed before executing
4371
	 * another statement/opening a new cursor.
4372
	 *
4373
	 * @return void
4374
	 */
4375
	public function close();
4376
}
4377
}
4378
4379
namespace RedBeanPHP\Cursor {
4380
4381
use RedBeanPHP\Cursor as Cursor;
4382
4383
/**
4384
 * PDO Database Cursor
4385
 * Implementation of PDO Database Cursor.
4386
 * Used by the BeanCollection to fetch one bean at a time.
4387
 * The PDO Cursor is used by Query Writers to support retrieval
4388
 * of large bean collections. For instance, this class is used to
4389
 * implement the findCollection()/BeanCollection functionality.
4390
 *
4391
 * @file    RedBeanPHP/Cursor/PDOCursor.php
4392
 * @author  Gabor de Mooij and the RedBeanPHP Community
4393
 * @license BSD/GPLv2
4394
 *
4395
 * @copyright
4396
 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
4397
 * This source file is subject to the BSD/GPLv2 License that is bundled
4398
 * with this source code in the file license.txt.
4399
 */
4400
class PDOCursor implements Cursor
4401
{
4402
	/**
4403
	 * @var PDOStatement
4404
	 */
4405
	protected $res;
4406
4407
	/**
4408
	 * @var string
4409
	 */
4410
	protected $fetchStyle;
4411
4412
	/**
4413
	 * Constructor, creates a new instance of a PDO Database Cursor.
4414
	 *
4415
	 * @param PDOStatement $res        the PDO statement
4416
	 * @param string       $fetchStyle fetch style constant to use
4417
	 *
4418
	 * @return void
4419
	 */
4420
	public function __construct( \PDOStatement $res, $fetchStyle )
4421
	{
4422
		$this->res = $res;
4423
		$this->fetchStyle = $fetchStyle;
4424
	}
4425
4426
	/**
4427
	 * @see Cursor::getNextItem
4428
	 */
4429
	public function getNextItem()
4430
	{
4431
		return $this->res->fetch();
4432
	}
4433
	
4434
	/**
4435
	 * @see Cursor::reset
4436
	 */
4437
	public function reset()
4438
	{
4439
		$this->close();
4440
		$this->res->execute();
4441
	}
4442
4443
	/**
4444
	 * @see Cursor::close
4445
	 */
4446
	public function close()
4447
	{
4448
		$this->res->closeCursor();
4449
	}
4450
}
4451
}
4452
4453
namespace RedBeanPHP\Cursor {
4454
4455
use RedBeanPHP\Cursor as Cursor;
4456
4457
/**
4458
 * NULL Database Cursor
4459
 * Implementation of the NULL Cursor.
4460
 * Used for an empty BeanCollection. This Cursor
4461
 * can be used for instance if a query fails but the interface
4462
 * demands a cursor to be returned.
4463
 *
4464
 * @file    RedBeanPHP/Cursor/NULLCursor.php
4465
 * @author  Gabor de Mooij and the RedBeanPHP Community
4466
 * @license BSD/GPLv2
4467
 *
4468
 * @copyright
4469
 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
4470
 * This source file is subject to the BSD/GPLv2 License that is bundled
4471
 * with this source code in the file license.txt.
4472
 */
4473
class NullCursor implements Cursor
4474
{
4475
	/**
4476
	 * @see Cursor::getNextItem
4477
	 */
4478
	public function getNextItem()
4479
	{
4480
		return NULL;
4481
	}
4482
	
4483
	/**
4484
	 * @see Cursor::reset
4485
	 */
4486
	public function reset()
4487
	{
4488
		return NULL;
4489
	}
4490
4491
	/**
4492
	 * @see Cursor::close
4493
	 */
4494
	public function close()
4495
	{
4496
		return NULL;
4497
	}
4498
}
4499
}
4500
4501
namespace RedBeanPHP {
4502
4503
use RedBeanPHP\Cursor as Cursor;
4504
use RedBeanPHP\Repository as Repository;
4505
4506
/**
4507
 * BeanCollection.
4508
 *
4509
 * The BeanCollection represents a collection of beans and
4510
 * makes it possible to use database cursors. The BeanCollection
4511
 * has a method next() to obtain the first, next and last bean
4512
 * in the collection. The BeanCollection does not implement the array
4513
 * interface nor does it try to act like an array because it cannot go
4514
 * backward or rewind itself.
4515
 *
4516
 * Use the BeanCollection for large datasets where skip/limit is not an
4517
 * option. Keep in mind that ID-marking (querying a start ID) is a decent
4518
 * alternative though.
4519
 *
4520
 * @file    RedBeanPHP/BeanCollection.php
4521
 * @author  Gabor de Mooij and the RedBeanPHP community
4522
 * @license BSD/GPLv2
4523
 *
4524
 * @copyright
4525
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
4526
 * This source file is subject to the BSD/GPLv2 License that is bundled
4527
 * with this source code in the file license.txt.
4528
 */
4529
class BeanCollection
4530
{
4531
	/**
4532
	 * @var Cursor
4533
	 */
4534
	protected $cursor = NULL;
4535
4536
	/**
4537
	 * @var Repository
4538
	 */
4539
	protected $repository = NULL;
4540
4541
	/**
4542
	 * @var string
4543
	 */
4544
	protected $type = NULL;
4545
4546
	/**
4547
	 * Constructor, creates a new instance of the BeanCollection.
4548
	 *
4549
	 * @param string     $type       type of beans in this collection
4550
	 * @param Repository $repository repository to use to generate bean objects
4551
	 * @param Cursor     $cursor     cursor object to use
4552
	 *
4553
	 * @return void
4554
	 */
4555
	public function __construct( $type, Repository $repository, Cursor $cursor )
4556
	{
4557
		$this->type = $type;
4558
		$this->cursor = $cursor;
4559
		$this->repository = $repository;
4560
	}
4561
4562
	/**
4563
	 * Returns the next bean in the collection.
4564
	 * If called the first time, this will return the first bean in the collection.
4565
	 * If there are no more beans left in the collection, this method
4566
	 * will return NULL.
4567
	 *
4568
	 * @return OODBBean|NULL
4569
	 */
4570
	public function next()
4571
	{
4572
		$row = $this->cursor->getNextItem();
4573
		if ( $row ) {
4574
			$beans = $this->repository->convertToBeans( $this->type, array( $row ) );
4575
			return reset( $beans );
4576
		}
4577
		return NULL;
4578
	}
4579
	
4580
	/**
4581
	 * Resets the collection from the start, like a fresh() on a bean.
4582
	 *
4583
	 * @return void
4584
	 */
4585
	public function reset()
4586
	{
4587
		$this->cursor->reset();
4588
	}
4589
4590
	/**
4591
	 * Closes the underlying cursor (needed for some databases).
4592
	 *
4593
	 * @return void
4594
	 */
4595
	public function close()
4596
	{
4597
		$this->cursor->close();
4598
	}
4599
}
4600
}
4601
4602
namespace RedBeanPHP {
4603
4604
/**
4605
 * QueryWriter
4606
 * Interface for QueryWriters.
4607
 * Describes the API for a QueryWriter.
4608
 *
4609
 * Terminology:
4610
 *
4611
 * - beautified property (a camelCased property, has to be converted first)
4612
 * - beautified type (a camelCased type, has to be converted first)
4613
 * - type (a bean type, corresponds directly to a table)
4614
 * - property (a bean property, corresponds directly to a column)
4615
 * - table (a checked and quoted type, ready for use in a query)
4616
 * - column (a checked and quoted property, ready for use in query)
4617
 * - tableNoQ (same as type, but in context of a database operation)
4618
 * - columnNoQ (same as property, but in context of a database operation)
4619
 *
4620
 * @file    RedBeanPHP/QueryWriter.php
4621
 * @author  Gabor de Mooij and the RedBeanPHP community
4622
 * @license BSD/GPLv2
4623
 *
4624
 * @copyright
4625
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
4626
 * This source file is subject to the BSD/GPLv2 License that is bundled
4627
 * with this source code in the file license.txt.
4628
 */
4629
interface QueryWriter
4630
{
4631
	/**
4632
	 * SQL filter constants
4633
	 */
4634
	const C_SQLFILTER_READ  = 'r';
4635
	const C_SQLFILTER_WRITE = 'w';
4636
4637
	/**
4638
	 * Query Writer constants.
4639
	 */
4640
	const C_SQLSTATE_NO_SUCH_TABLE                  = 1;
4641
	const C_SQLSTATE_NO_SUCH_COLUMN                 = 2;
4642
	const C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION = 3;
4643
	const C_SQLSTATE_LOCK_TIMEOUT                   = 4;
4644
4645
	/**
4646
	 * Define data type regions
4647
	 *
4648
	 * 00 - 80: normal data types
4649
	 * 80 - 99: special data types, only scan/code if requested
4650
	 * 99     : specified by user, don't change
4651
	 */
4652
	const C_DATATYPE_RANGE_SPECIAL   = 80;
4653
	const C_DATATYPE_RANGE_SPECIFIED = 99;
4654
4655
	/**
4656
	 * Define GLUE types for use with glueSQLCondition methods.
4657
	 * Determines how to prefix a snippet of SQL before appending it
4658
	 * to other SQL (or integrating it, mixing it otherwise).
4659
	 *
4660
	 * WHERE - glue as WHERE condition
4661
	 * AND   - glue as AND condition
4662
	 */
4663
	const C_GLUE_WHERE = 1;
4664
	const C_GLUE_AND   = 2;
4665
4666
	/**
4667
	 * Writes an SQL Snippet for a JOIN, returns the
4668
	 * SQL snippet string.
4669
	 *
4670
	 * @note A default implementation is available in AQueryWriter
4671
	 * unless a database uses very different SQL this should suffice.
4672
	 *
4673
	 * @param string $type       source type
4674
	 * @param string $targetType target type (type to join)
4675
	 * @param string $joinType   type of join (possible: 'LEFT', 'RIGHT' or 'INNER').
4676
	 *
4677
	 * @return string $joinSQLSnippet
4678
	 */
4679
	public function writeJoin( $type, $targetType, $joinType );
4680
4681
	/**
4682
	 * Glues an SQL snippet to the beginning of a WHERE clause.
4683
	 * This ensures users don't have to add WHERE to their query snippets.
4684
	 *
4685
	 * The snippet gets prefixed with WHERE or AND
4686
	 * if it starts with a condition.
4687
	 *
4688
	 * If the snippet does NOT start with a condition (or this function thinks so)
4689
	 * the snippet is returned as-is.
4690
	 *
4691
	 * The GLUE type determines the prefix:
4692
	 *
4693
	 * * NONE  prefixes with WHERE
4694
	 * * WHERE prefixes with WHERE and replaces AND if snippets starts with AND
4695
	 * * AND   prefixes with AND
4696
	 *
4697
	 * This method will never replace WHERE with AND since a snippet should never
4698
	 * begin with WHERE in the first place. OR is not supported.
4699
	 *
4700
	 * Only a limited set of clauses will be recognized as non-conditions.
4701
	 * For instance beginning a snippet with complex statements like JOIN or UNION
4702
	 * will not work. This is too complex for use in a snippet.
4703
	 *
4704
	 * @note A default implementation is available in AQueryWriter
4705
	 * unless a database uses very different SQL this should suffice.
4706
	 *
4707
	 * @param string  $sql  SQL Snippet
4708
	 * @param integer $glue the GLUE type - how to glue (C_GLUE_WHERE or C_GLUE_AND)
4709
	 *
4710
	 * @return string
4711
	 */
4712
	public function glueSQLCondition( $sql, $glue = NULL );
4713
4714
	/**
4715
	 * Determines if there is a LIMIT 1 clause in the SQL.
4716
	 * If not, it will add a LIMIT 1. (used for findOne).
4717
	 *
4718
	 * @note A default implementation is available in AQueryWriter
4719
	 * unless a database uses very different SQL this should suffice.
4720
	 *
4721
	 * @param string $sql query to scan and adjust
4722
	 *
4723
	 * @return string
4724
	 */
4725
	public function glueLimitOne( $sql );
4726
4727
	/**
4728
	 * Returns the tables that are in the database.
4729
	 *
4730
	 * @return array
4731
	 */
4732
	public function getTables();
4733
4734
	/**
4735
	 * This method will create a table for the bean.
4736
	 * This methods accepts a type and infers the corresponding table name.
4737
	 *
4738
	 * @param string $type type of bean you want to create a table for
4739
	 *
4740
	 * @return void
4741
	 */
4742
	public function createTable( $type );
4743
4744
	/**
4745
	 * Returns an array containing all the columns of the specified type.
4746
	 * The format of the return array looks like this:
4747
	 * $field => $type where $field is the name of the column and $type
4748
	 * is a database specific description of the datatype.
4749
	 *
4750
	 * This methods accepts a type and infers the corresponding table name.
4751
	 *
4752
	 * @param string $type type of bean you want to obtain a column list of
4753
	 *
4754
	 * @return array
4755
	 */
4756
	public function getColumns( $type );
4757
4758
	/**
4759
	 * Returns the Column Type Code (integer) that corresponds
4760
	 * to the given value type. This method is used to determine the minimum
4761
	 * column type required to represent the given value. There are two modes of
4762
	 * operation: with or without special types. Scanning without special types
4763
	 * requires the second parameter to be set to FALSE. This is useful when the
4764
	 * column has already been created and prevents it from being modified to
4765
	 * an incompatible type leading to data loss. Special types will be taken
4766
	 * into account when a column does not exist yet (parameter is then set to TRUE).
4767
	 *
4768
	 * Special column types are determines by the AQueryWriter constant
4769
	 * C_DATA_TYPE_ONLY_IF_NOT_EXISTS (usually 80). Another 'very special' type is type
4770
	 * C_DATA_TYPE_MANUAL (usually 99) which represents a user specified type. Although
4771
	 * no special treatment has been associated with the latter for now.
4772
	 *
4773
	 * @param string  $value                   value
4774
	 * @param boolean $alsoScanSpecialForTypes take special types into account
4775
	 *
4776
	 * @return integer
4777
	 */
4778
	public function scanType( $value, $alsoScanSpecialForTypes = FALSE );
4779
4780
	/**
4781
	 * This method will add a column to a table.
4782
	 * This methods accepts a type and infers the corresponding table name.
4783
	 *
4784
	 * @param string  $type   name of the table
4785
	 * @param string  $column name of the column
4786
	 * @param integer $field  data type for field
4787
	 *
4788
	 * @return void
4789
	 */
4790
	public function addColumn( $type, $column, $field );
4791
4792
	/**
4793
	 * Returns the Type Code for a Column Description.
4794
	 * Given an SQL column description this method will return the corresponding
4795
	 * code for the writer. If the include specials flag is set it will also
4796
	 * return codes for special columns. Otherwise special columns will be identified
4797
	 * as specified columns.
4798
	 *
4799
	 * @param string  $typedescription description
4800
	 * @param boolean $includeSpecials whether you want to get codes for special columns as well
4801
	 *
4802
	 * @return integer
4803
	 */
4804
	public function code( $typedescription, $includeSpecials = FALSE );
4805
4806
	/**
4807
	 * This method will widen the column to the specified data type.
4808
	 * This methods accepts a type and infers the corresponding table name.
4809
	 *
4810
	 * @param string  $type     type / table that needs to be adjusted
4811
	 * @param string  $column   column that needs to be altered
4812
	 * @param integer $datatype target data type
4813
	 *
4814
	 * @return void
4815
	 */
4816
	public function widenColumn( $type, $column, $datatype );
4817
4818
	/**
4819
	 * Selects records from the database.
4820
	 * This methods selects the records from the database that match the specified
4821
	 * type, conditions (optional) and additional SQL snippet (optional).
4822
	 *
4823
	 * @param string $type       name of the table you want to query
4824
	 * @param array  $conditions criteria ( $column => array( $values ) )
4825
	 * @param string $addSql     additional SQL snippet
4826
	 * @param array  $bindings   bindings for SQL snippet
4827
	 *
4828
	 * @return array
4829
	 */
4830
	public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
4831
4832
	/**
4833
	 * Selects records from the database and returns a cursor.
4834
	 * This methods selects the records from the database that match the specified
4835
	 * type, conditions (optional) and additional SQL snippet (optional).
4836
	 *
4837
	 * @param string $type       name of the table you want to query
4838
	 * @param array  $conditions criteria ( $column => array( $values ) )
4839
	 * @param string $addSQL     additional SQL snippet
4840
	 * @param array  $bindings   bindings for SQL snippet
4841
	 *
4842
	 * @return Cursor
4843
	 */
4844
	public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() );
4845
4846
	/**
4847
	 * Returns records through an intermediate type. This method is used to obtain records using a link table and
4848
	 * allows the SQL snippets to reference columns in the link table for additional filtering or ordering.
4849
	 *
4850
	 * @param string $sourceType source type, the reference type you want to use to fetch related items on the other side
4851
	 * @param string $destType   destination type, the target type you want to get beans of
4852
	 * @param mixed  $linkID     ID to use for the link table
4853
	 * @param string $addSql     Additional SQL snippet
4854
	 * @param array  $bindings   Bindings for SQL snippet
4855
	 *
4856
	 * @return array
4857
	 */
4858
	public function queryRecordRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() );
4859
4860
	/**
4861
	 * Returns the row that links $sourceType $sourcID to $destType $destID in an N-M relation.
4862
	 *
4863
	 * @param string $sourceType source type, the first part of the link you're looking for
4864
	 * @param string $destType   destination type, the second part of the link you're looking for
4865
	 * @param string $sourceID   ID for the source
4866
	 * @param string $destID     ID for the destination
4867
	 *
4868
	 * @return array|null
4869
	 */
4870
	public function queryRecordLink( $sourceType, $destType, $sourceID, $destID );
4871
4872
	/**
4873
	 * Counts the number of records in the database that match the
4874
	 * conditions and additional SQL.
4875
	 *
4876
	 * @param string $type       name of the table you want to query
4877
	 * @param array  $conditions criteria ( $column => array( $values ) )
4878
	 * @param string $addSQL     additional SQL snippet
4879
	 * @param array  $bindings   bindings for SQL snippet
4880
	 *
4881
	 * @return integer
4882
	 */
4883
	public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
4884
4885
	/**
4886
	 * Returns the number of records linked through $linkType and satisfying the SQL in $addSQL/$bindings.
4887
	 *
4888
	 * @param string $sourceType source type
4889
	 * @param string $targetType the thing you want to count
4890
	 * @param mixed  $linkID     the of the source type
4891
	 * @param string $addSQL     additional SQL snippet
4892
	 * @param array  $bindings   bindings for SQL snippet
4893
	 *
4894
	 * @return integer
4895
	 */
4896
	public function queryRecordCountRelated( $sourceType, $targetType, $linkID, $addSQL = '', $bindings = array() );
4897
4898
	/**
4899
	 * Returns all rows of specified type that have been tagged with one of the
4900
	 * strings in the specified tag list array.
4901
	 *
4902
	 * Note that the additional SQL snippet can only be used for pagination,
4903
	 * the SQL snippet will be appended to the end of the query.
4904
	 *
4905
	 * @param string  $type     the bean type you want to query
4906
	 * @param array   $tagList  an array of strings, each string containing a tag title
4907
	 * @param boolean $all      if TRUE only return records that have been associated with ALL the tags in the list
4908
	 * @param string  $addSql   addition SQL snippet, for pagination
4909
	 * @param array   $bindings parameter bindings for additional SQL snippet
4910
	 *
4911
	 * @return array
4912
	 */
4913
	public function queryTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() );
4914
4915
	/**
4916
	 * Like queryTagged but only counts.
4917
	 *
4918
	 * @param string  $type     the bean type you want to query
4919
	 * @param array   $tagList  an array of strings, each string containing a tag title
4920
	 * @param boolean $all      if TRUE only return records that have been associated with ALL the tags in the list
4921
	 * @param string  $addSql   addition SQL snippet, for pagination
4922
	 * @param array   $bindings parameter bindings for additional SQL snippet
4923
	 *
4924
	 * @return integer
4925
	 */
4926
	public function queryCountTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() );
4927
4928
	/**
4929
	 * Returns all parent rows or child rows of a specified row.
4930
	 * Given a type specifier and a primary key id, this method returns either all child rows
4931
	 * as defined by having <type>_id = id or all parent rows as defined per id = <type>_id
4932
	 * taking into account an optional SQL snippet along with parameters.
4933
	 *
4934
	 * @param string  $type     the bean type you want to query rows for
4935
	 * @param integer $id       id of the reference row
4936
	 * @param boolean $up       TRUE to query parent rows, FALSE to query child rows
4937
	 * @param string  $addSql   optional SQL snippet to embed in the query
4938
	 * @param array   $bindings parameter bindings for additional SQL snippet
4939
	 *
4940
	 * @return array
4941
	 */
4942
	public function queryRecursiveCommonTableExpression( $type, $id, $up = TRUE, $addSql = NULL, $bindings = array() );
4943
4944
	/**
4945
	 * This method should update (or insert a record), it takes
4946
	 * a table name, a list of update values ( $field => $value ) and an
4947
	 * primary key ID (optional). If no primary key ID is provided, an
4948
	 * INSERT will take place.
4949
	 * Returns the new ID.
4950
	 * This methods accepts a type and infers the corresponding table name.
4951
	 *
4952
	 * @param string  $type         name of the table to update
4953
	 * @param array   $updatevalues list of update values
4954
	 * @param integer $id           optional primary key ID value
4955
	 *
4956
	 * @return integer
4957
	 */
4958
	public function updateRecord( $type, $updatevalues, $id = NULL );
4959
4960
	/**
4961
	 * Deletes records from the database.
4962
	 * @note $addSql is always prefixed with ' WHERE ' or ' AND .'
4963
	 *
4964
	 * @param string $type       name of the table you want to query
4965
	 * @param array  $conditions criteria ( $column => array( $values ) )
4966
	 * @param string $addSql     additional SQL
4967
	 * @param array  $bindings   bindings
4968
	 *
4969
	 * @return void
4970
	 */
4971
	public function deleteRecord( $type, $conditions = array(), $addSql = '', $bindings = array() );
4972
4973
	/**
4974
	 * Deletes all links between $sourceType and $destType in an N-M relation.
4975
	 *
4976
	 * @param string $sourceType source type
4977
	 * @param string $destType   destination type
4978
	 * @param string $sourceID   source ID
4979
	 *
4980
	 * @return void
4981
	 */
4982
	public function deleteRelations( $sourceType, $destType, $sourceID );
4983
4984
	/**
4985
	 * @see QueryWriter::addUniqueConstaint
4986
	 */
4987
	public function addUniqueIndex( $type, $columns );
4988
4989
	/**
4990
	 * This method will add a UNIQUE constraint index to a table on columns $columns.
4991
	 * This methods accepts a type and infers the corresponding table name.
4992
	 *
4993
	 * @param string $type               target bean type
4994
	 * @param array  $columnsPartOfIndex columns to include in index
4995
	 *
4996
	 * @return void
4997
	 */
4998
	public function addUniqueConstraint( $type, $columns );
4999
5000
	/**
5001
	 * This method will check whether the SQL state is in the list of specified states
5002
	 * and returns TRUE if it does appear in this list or FALSE if it
5003
	 * does not. The purpose of this method is to translate the database specific state to
5004
	 * a one of the constants defined in this class and then check whether it is in the list
5005
	 * of standard states provided.
5006
	 *
5007
	 * @param string $state SQL state to consider
5008
	 * @param array  $list  list of standardized SQL state constants to check against
5009
	 * @param array  $extraDriverDetails Some databases communicate state information in a driver-specific format
5010
	 *                                   rather than through the main sqlState code. For those databases, this extra
5011
	 *                                   information can be used to determine the standardized state
5012
	 *
5013
	 * @return boolean
5014
	 */
5015
	public function sqlStateIn( $state, $list, $extraDriverDetails = array() );
5016
5017
	/**
5018
	 * This method will remove all beans of a certain type.
5019
	 * This methods accepts a type and infers the corresponding table name.
5020
	 *
5021
	 * @param  string $type bean type
5022
	 *
5023
	 * @return void
5024
	 */
5025
	public function wipe( $type );
5026
5027
	/**
5028
	 * This method will add a foreign key from type and field to
5029
	 * target type and target field.
5030
	 * The foreign key is created without an action. On delete/update
5031
	 * no action will be triggered. The FK is only used to allow database
5032
	 * tools to generate pretty diagrams and to make it easy to add actions
5033
	 * later on.
5034
	 * This methods accepts a type and infers the corresponding table name.
5035
	 *
5036
	 *
5037
	 * @param  string $type           type that will have a foreign key field
5038
	 * @param  string $targetType     points to this type
5039
	 * @param  string $property       field that contains the foreign key value
5040
	 * @param  string $targetProperty field where the fk points to
5041
	 * @param  string $isDep          whether target is dependent and should cascade on update/delete
5042
	 *
5043
	 * @return void
5044
	 */
5045
	public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE );
5046
5047
	/**
5048
	 * This method will add an index to a type and field with name
5049
	 * $name.
5050
	 * This methods accepts a type and infers the corresponding table name.
5051
	 *
5052
	 * @param string $type     type to add index to
5053
	 * @param string $name     name of the new index
5054
	 * @param string $property field to index
5055
	 *
5056
	 * @return void
5057
	 */
5058
	public function addIndex( $type, $name, $property );
5059
5060
	/**
5061
	 * Checks and filters a database structure element like a table of column
5062
	 * for safe use in a query. A database structure has to conform to the
5063
	 * RedBeanPHP DB security policy which basically means only alphanumeric
5064
	 * symbols are allowed. This security policy is more strict than conventional
5065
	 * SQL policies and does therefore not require database specific escaping rules.
5066
	 *
5067
	 * @param string  $databaseStructure name of the column/table to check
5068
	 * @param boolean $noQuotes          TRUE to NOT put backticks or quotes around the string
5069
	 *
5070
	 * @return string
5071
	 */
5072
	public function esc( $databaseStructure, $dontQuote = FALSE );
5073
5074
	/**
5075
	 * Removes all tables and views from the database.
5076
	 *
5077
	 * @return void
5078
	 */
5079
	public function wipeAll();
5080
5081
	/**
5082
	 * Renames an association. For instance if you would like to refer to
5083
	 * album_song as: track you can specify this by calling this method like:
5084
	 *
5085
	 * <code>
5086
	 * renameAssociation('album_song','track')
5087
	 * </code>
5088
	 *
5089
	 * This allows:
5090
	 *
5091
	 * <code>
5092
	 * $album->sharedSong
5093
	 * </code>
5094
	 *
5095
	 * to add/retrieve beans from track instead of album_song.
5096
	 * Also works for exportAll().
5097
	 *
5098
	 * This method also accepts a single associative array as
5099
	 * its first argument.
5100
	 *
5101
	 * @param string|array $fromType original type name, or array
5102
	 * @param string       $toType   new type name (only if 1st argument is string)
5103
	 *
5104
	 * @return void
5105
	 */
5106
	public function renameAssocTable( $fromType, $toType = NULL );
5107
5108
	/**
5109
	 * Returns the format for link tables.
5110
	 * Given an array containing two type names this method returns the
5111
	 * name of the link table to be used to store and retrieve
5112
	 * association records. For instance, given two types: person and
5113
	 * project, the corresponding link table might be: 'person_project'.
5114
	 *
5115
	 * @param  array $types two types array($type1, $type2)
5116
	 *
5117
	 * @return string
5118
	 */
5119
	public function getAssocTable( $types );
5120
5121
	/**
5122
	 * Given a bean type and a property, this method
5123
	 * tries to infer the fetch type using the foreign key
5124
	 * definitions in the database.
5125
	 * For instance: project, student -> person.
5126
	 * If no fetchType can be inferred, this method will return NULL.
5127
	 *
5128
	 * @note QueryWriters do not have to implement this method,
5129
	 * it's optional. A default version is available in AQueryWriter.
5130
	 *
5131
	 * @param $type     the source type to fetch a target type for
5132
	 * @param $property the property to fetch the type of
5133
	 *
5134
	 * @return string|NULL
5135
	 */
5136
	public function inferFetchType( $type, $property );
5137
}
5138
}
5139
5140
namespace RedBeanPHP\QueryWriter {
5141
5142
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
5143
use RedBeanPHP\RedException as RedException;
5144
use RedBeanPHP\QueryWriter as QueryWriter;
5145
use RedBeanPHP\OODBBean as OODBBean;
5146
use RedBeanPHP\RedException\SQL as SQLException;
5147
5148
/**
5149
 * RedBeanPHP Abstract Query Writer.
5150
 * Represents an abstract Database to RedBean
5151
 * To write a driver for a different database for RedBean
5152
 * Contains a number of functions all implementors can
5153
 * inherit or override.
5154
 *
5155
 * @file    RedBeanPHP/QueryWriter/AQueryWriter.php
5156
 * @author  Gabor de Mooij and the RedBeanPHP Community
5157
 * @license BSD/GPLv2
5158
 *
5159
 * @copyright
5160
 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
5161
 * This source file is subject to the BSD/GPLv2 License that is bundled
5162
 * with this source code in the file license.txt.
5163
 */
5164
abstract class AQueryWriter
5165
{
5166
	/**
5167
	 * Constant: Select Snippet 'FOR UPDATE'
5168
	 */
5169
	const C_SELECT_SNIPPET_FOR_UPDATE = 'FOR UPDATE';
5170
	const C_DATA_TYPE_ONLY_IF_NOT_EXISTS = 80;
5171
	const C_DATA_TYPE_MANUAL = 99;
5172
5173
	/**
5174
	 * @var array
5175
	 */
5176
	private static $sqlFilters = array();
5177
5178
	/**
5179
	 * @var boolean
5180
	 */
5181
	private static $flagSQLFilterSafeMode = FALSE;
5182
5183
	/**
5184
	 * @var boolean
5185
	 */
5186
	private static $flagNarrowFieldMode = TRUE;
5187
5188
	/**
5189
	 * @var boolean
5190
	 */
5191
	protected static $flagUseJSONColumns = FALSE;
5192
5193
	/**
5194
	 * @var array
5195
	 */
5196
	public static $renames = array();
5197
5198
	/**
5199
	 * @var DBAdapter
5200
	 */
5201
	protected $adapter;
5202
5203
	/**
5204
	 * @var string
5205
	 */
5206
	protected $defaultValue = 'NULL';
5207
5208
	/**
5209
	 * @var string
5210
	 */
5211
	protected $quoteCharacter = '';
5212
5213
	/**
5214
	 * @var boolean
5215
	 */
5216
	protected $flagUseCache = TRUE;
5217
5218
	/**
5219
	 * @var array
5220
	 */
5221
	protected $cache = array();
5222
5223
	/**
5224
	 * @var integer
5225
	 */
5226
	protected $maxCacheSizePerType = 20;
5227
5228
	/**
5229
	 * @var string
5230
	 */
5231
	protected $sqlSelectSnippet = '';
5232
5233
	/**
5234
	 * @var array
5235
	 */
5236
	public $typeno_sqltype = array();
5237
5238
	/**
5239
	 * @var bool
5240
	 */
5241
	protected static $noNuke = false;
5242
5243
	/**
5244
	 * Toggles support for automatic generation of JSON columns.
5245
	 * Using JSON columns means that strings containing JSON will
5246
	 * cause the column to be created (not modified) as a JSON column.
5247
	 * However it might also trigger exceptions if this means the DB attempts to
5248
	 * convert a non-json column to a JSON column. Returns the previous
5249
	 * value of the flag.
5250
	 *
5251
	 * @param boolean $flag TRUE or FALSE
5252
	 *
5253
	 * @return boolean
5254
	 */
5255
	public static function useJSONColumns( $flag )
5256
	{
5257
		$old = self::$flagUseJSONColumns;
5258
		self::$flagUseJSONColumns = $flag;
5259
		return $old;
5260
	}
5261
5262
	/**
5263
	 * Toggles support for nuke().
5264
	 * Can be used to turn off the nuke() feature for security reasons.
5265
	 * Returns the old flag value.
5266
	 *
5267
	 * @param boolean $flag TRUE or FALSE
5268
	 *
5269
	 * @return boolean
5270
	 */
5271
	public static function forbidNuke( $flag ) {
5272
		$old = self::$noNuke;
5273
		self::$noNuke = (bool) $flag;
5274
		return $old;
5275
	}
5276
5277
	/**
5278
	 * Checks whether a number can be treated like an int.
5279
	 *
5280
	 * @param  string $value string representation of a certain value
5281
	 *
5282
	 * @return boolean
5283
	 */
5284
	public static function canBeTreatedAsInt( $value )
5285
	{
5286
		return (bool) ( strval( $value ) === strval( intval( $value ) ) );
5287
	}
5288
5289
	/**
5290
	 * @see QueryWriter::getAssocTableFormat
5291
	 */
5292
	public static function getAssocTableFormat( $types )
5293
	{
5294
		sort( $types );
5295
5296
		$assoc = implode( '_', $types );
5297
5298
		return ( isset( self::$renames[$assoc] ) ) ? self::$renames[$assoc] : $assoc;
5299
	}
5300
5301
	/**
5302
	 * @see QueryWriter::renameAssociation
5303
	 */
5304
	public static function renameAssociation( $from, $to = NULL )
5305
	{
5306
		if ( is_array( $from ) ) {
5307
			foreach ( $from as $key => $value ) self::$renames[$key] = $value;
5308
5309
			return;
5310
		}
5311
5312
		self::$renames[$from] = $to;
5313
	}
5314
5315
	/**
5316
	 * Globally available service method for RedBeanPHP.
5317
	 * Converts a camel cased string to a snake cased string.
5318
	 *
5319
	 * @param string $camel camelCased string to converty to snake case
5320
	 *
5321
	 * @return string
5322
	 */
5323
	public static function camelsSnake( $camel )
5324
	{
5325
		return strtolower( preg_replace( '/(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])/', '_$1$2', $camel ) );
5326
	}
5327
5328
	/**
5329
	 * Clears renames.
5330
	 *
5331
	 * @return void
5332
	 */
5333
	public static function clearRenames()
5334
	{
5335
		self::$renames = array();
5336
	}
5337
5338
	/**
5339
	 * Toggles 'Narrow Field Mode'.
5340
	 * In Narrow Field mode the queryRecord method will
5341
	 * narrow its selection field to
5342
	 *
5343
	 * SELECT {table}.*
5344
	 *
5345
	 * instead of
5346
	 *
5347
	 * SELECT *
5348
	 *
5349
	 * This is a better way of querying because it allows
5350
	 * more flexibility (for instance joins). However if you need
5351
	 * the wide selector for backward compatibility; use this method
5352
	 * to turn OFF Narrow Field Mode by passing FALSE.
5353
	 * Default is TRUE.
5354
	 *
5355
	 * @param boolean $narrowField TRUE = Narrow Field FALSE = Wide Field
5356
	 *
5357
	 * @return void
5358
	 */
5359
	public static function setNarrowFieldMode( $narrowField )
5360
	{
5361
		self::$flagNarrowFieldMode = (boolean) $narrowField;
5362
	}
5363
5364
	/**
5365
	 * Sets SQL filters.
5366
	 * This is a lowlevel method to set the SQL filter array.
5367
	 * The format of this array is:
5368
	 *
5369
	 * <code>
5370
	 * array(
5371
	 * 		'<MODE, i.e. 'r' for read, 'w' for write>' => array(
5372
	 * 			'<TABLE NAME>' => array(
5373
	 * 				'<COLUMN NAME>' => '<SQL>'
5374
	 * 			)
5375
	 * 		)
5376
	 * )
5377
	 * </code>
5378
	 *
5379
	 * Example:
5380
	 *
5381
	 * <code>
5382
	 * array(
5383
	 *   QueryWriter::C_SQLFILTER_READ => array(
5384
	 * 	'book' => array(
5385
	 * 		'title' => ' LOWER(book.title) '
5386
	 * 	)
5387
	 * )
5388
	 * </code>
5389
	 *
5390
	 * Note that you can use constants instead of magical chars
5391
	 * as keys for the uppermost array.
5392
	 * This is a lowlevel method. For a more friendly method
5393
	 * please take a look at the facade: R::bindFunc().
5394
	 *
5395
	 * @param array list of filters to set
5396
	 *
5397
	 * @return void
5398
	 */
5399
	public static function setSQLFilters( $sqlFilters, $safeMode = FALSE )
5400
	{
5401
		self::$flagSQLFilterSafeMode = (boolean) $safeMode;
5402
		self::$sqlFilters = $sqlFilters;
5403
	}
5404
5405
	/**
5406
	 * Returns current SQL Filters.
5407
	 * This method returns the raw SQL filter array.
5408
	 * This is a lowlevel method. For a more friendly method
5409
	 * please take a look at the facade: R::bindFunc().
5410
	 *
5411
	 * @return array
5412
	 */
5413
	public static function getSQLFilters()
5414
	{
5415
		return self::$sqlFilters;
5416
	}
5417
5418
	/**
5419
	 * Returns a cache key for the cache values passed.
5420
	 * This method returns a fingerprint string to be used as a key to store
5421
	 * data in the writer cache.
5422
	 *
5423
	 * @param array $keyValues key-value to generate key for
5424
	 *
5425
	 * @return string
5426
	 */
5427
	private function getCacheKey( $keyValues )
5428
	{
5429
		return json_encode( $keyValues );
5430
	}
5431
5432
	/**
5433
	 * Returns the values associated with the provided cache tag and key.
5434
	 *
5435
	 * @param string $cacheTag cache tag to use for lookup
5436
	 * @param string $key      key to use for lookup
5437
	 *
5438
	 * @return mixed
5439
	 */
5440
	private function getCached( $cacheTag, $key )
5441
	{
5442
		$sql = $this->adapter->getSQL();
5443
		if ($this->updateCache()) {
5444
			if ( isset( $this->cache[$cacheTag][$key] ) ) {
5445
				return $this->cache[$cacheTag][$key];
5446
			}
5447
		}
5448
5449
		return NULL;
5450
	}
5451
5452
	/**
5453
	 * Checks if the previous query had a keep-cache tag.
5454
	 * If so, the cache will persist, otherwise the cache will be flushed.
5455
	 *
5456
	 * Returns TRUE if the cache will remain and FALSE if a flush has
5457
	 * been performed.
5458
	 *
5459
	 * @return boolean
5460
	 */
5461
	private function updateCache()
5462
	{
5463
		$sql = $this->adapter->getSQL();
5464
		if ( strpos( $sql, '-- keep-cache' ) !== strlen( $sql ) - 13 ) {
5465
			// If SQL has been taken place outside of this method then something else then
5466
			// a select query might have happened! (or instruct to keep cache)
5467
			$this->cache = array();
5468
			return FALSE;
5469
		}
5470
		return TRUE;
5471
	}
5472
5473
	/**
5474
	 * Stores data from the writer in the cache under a specific key and cache tag.
5475
	 * A cache tag is used to make sure the cache remains consistent. In most cases the cache tag
5476
	 * will be the bean type, this makes sure queries associated with a certain reference type will
5477
	 * never contain conflicting data.
5478
	 * Why not use the cache tag as a key? Well
5479
	 * we need to make sure the cache contents fits the key (and key is based on the cache values).
5480
	 * Otherwise it would be possible to store two different result sets under the same key (the cache tag).
5481
	 *
5482
	 * In previous versions you could only store one key-entry, I have changed this to
5483
	 * improve caching efficiency (issue #400).
5484
	 *
5485
	 * @param string $cacheTag cache tag (secondary key)
5486
	 * @param string $key      key to store values under
5487
	 * @param array  $values   content to be stored
5488
	 *
5489
	 * @return void
5490
	 */
5491
	private function putResultInCache( $cacheTag, $key, $values )
5492
	{
5493
		if ( isset( $this->cache[$cacheTag] ) ) {
5494
			if ( count( $this->cache[$cacheTag] ) > $this->maxCacheSizePerType ) array_shift( $this->cache[$cacheTag] );
5495
		} else {
5496
			$this->cache[$cacheTag] = array();
5497
		}
5498
		$this->cache[$cacheTag][$key] = $values;
5499
	}
5500
5501
	/**
5502
	 * Creates an SQL snippet from a list of conditions of format:
5503
	 *
5504
	 * <code>
5505
	 * array(
5506
	 *    key => array(
5507
	 *           value1, value2, value3 ....
5508
	 *        )
5509
	 * )
5510
	 * </code>
5511
	 *
5512
	 * @param array  $conditions list of conditions
5513
	 * @param array  $bindings   parameter bindings for SQL snippet
5514
	 * @param string $addSql     additional SQL snippet to append to result
5515
	 *
5516
	 * @return string
5517
	 */
5518
	private function makeSQLFromConditions( $conditions, &$bindings, $addSql = '' )
5519
	{
5520
		reset( $bindings );
5521
		$firstKey       = key( $bindings );
5522
		$paramTypeIsNum = ( is_numeric( $firstKey ) );
5523
		$counter        = 0;
5524
5525
		$sqlConditions = array();
5526
		foreach ( $conditions as $column => $values ) {
5527
			if ( $values === NULL ) continue;
5528
5529
			if ( is_array( $values ) ) {
5530
				if ( empty( $values ) ) continue;
5531
			} else {
5532
				$values = array( $values );
5533
			}
5534
5535
			$checkOODB = reset( $values );
5536
			if ( $checkOODB instanceof OODBBean && $checkOODB->getMeta( 'type' ) === $column && substr( $column, -3 ) != '_id' )
5537
				$column = $column . '_id';
5538
5539
5540
			$sql = $this->esc( $column );
5541
			$sql .= ' IN ( ';
5542
5543
			if ( $paramTypeIsNum ) {
5544
				$sql .= implode( ',', array_fill( 0, count( $values ), '?' ) ) . ' ) ';
5545
5546
				array_unshift($sqlConditions, $sql);
5547
5548
				foreach ( $values as $k => $v ) {
5549
					if ( $v instanceof OODBBean ) {
5550
						$v = $v->id;
5551
					}
5552
					$values[$k] = strval( $v );
5553
5554
					array_unshift( $bindings, $v );
5555
				}
5556
			} else {
5557
5558
				$slots = array();
5559
5560
				foreach( $values as $k => $v ) {
5561
					if ( $v instanceof OODBBean ) {
5562
						$v = $v->id;
5563
					}
5564
					$slot            = ':slot'.$counter++;
5565
					$slots[]         = $slot;
5566
					$bindings[$slot] = strval( $v );
5567
				}
5568
5569
				$sql .= implode( ',', $slots ).' ) ';
5570
				$sqlConditions[] = $sql;
5571
			}
5572
		}
5573
5574
		$sql = '';
5575
		if ( !empty( $sqlConditions ) ) {
5576
			$sql .= " WHERE ( " . implode( ' AND ', $sqlConditions ) . ") ";
5577
		}
5578
5579
		$addSql = $this->glueSQLCondition( $addSql, !empty( $sqlConditions ) ? QueryWriter::C_GLUE_AND : NULL );
5580
		if ( $addSql ) $sql .= $addSql;
5581
5582
		return $sql;
5583
	}
5584
5585
	/**
5586
	 * Returns the table names and column names for a relational query.
5587
	 *
5588
	 * @param string  $sourceType type of the source bean
5589
	 * @param string  $destType   type of the bean you want to obtain using the relation
5590
	 * @param boolean $noQuote    TRUE if you want to omit quotes
5591
	 *
5592
	 * @return array
5593
	 */
5594
	private function getRelationalTablesAndColumns( $sourceType, $destType, $noQuote = FALSE )
5595
	{
5596
		$linkTable   = $this->esc( $this->getAssocTable( array( $sourceType, $destType ) ), $noQuote );
5597
		$sourceCol   = $this->esc( $sourceType . '_id', $noQuote );
5598
5599
		if ( $sourceType === $destType ) {
5600
			$destCol = $this->esc( $destType . '2_id', $noQuote );
5601
		} else {
5602
			$destCol = $this->esc( $destType . '_id', $noQuote );
5603
		}
5604
5605
		$sourceTable = $this->esc( $sourceType, $noQuote );
5606
		$destTable   = $this->esc( $destType, $noQuote );
5607
5608
		return array( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol );
5609
	}
5610
5611
	/**
5612
	 * Determines whether a string can be considered JSON or not.
5613
	 * This is used by writers that support JSON columns. However
5614
	 * we dont want that code duplicated over all JSON supporting
5615
	 * Query Writers.
5616
	 *
5617
	 * @param string $value value to determine 'JSONness' of.
5618
	 *
5619
	 * @return boolean
5620
	 */
5621
	protected function isJSON( $value )
5622
	{
5623
		return (
5624
			is_string($value) &&
5625
			is_array(json_decode($value, TRUE)) &&
5626
			(json_last_error() == JSON_ERROR_NONE)
5627
		);
5628
	}
5629
5630
	/**
5631
	 * Given a type and a property name this method
5632
	 * returns the foreign key map section associated with this pair.
5633
	 *
5634
	 * @param string $type     name of the type
5635
	 * @param string $property name of the property
5636
	 *
5637
	 * @return array|NULL
5638
	 */
5639
	protected function getForeignKeyForTypeProperty( $type, $property )
5640
	{
5641
		$property = $this->esc( $property, TRUE );
5642
5643
		try {
5644
			$map = $this->getKeyMapForType( $type );
5645
		} catch ( SQLException $e ) {
5646
			return NULL;
5647
		}
5648
5649
		foreach( $map as $key ) {
5650
			if ( $key['from'] === $property ) return $key;
5651
		}
5652
		return NULL;
5653
	}
5654
5655
	/**
5656
	 * Returns the foreign key map (FKM) for a type.
5657
	 * A foreign key map describes the foreign keys in a table.
5658
	 * A FKM always has the same structure:
5659
	 *
5660
	 * <code>
5661
	 * array(
5662
	 * 	'name'      => <name of the foreign key>
5663
	 *    'from'      => <name of the column on the source table>
5664
	 *    'table'     => <name of the target table>
5665
	 *    'to'        => <name of the target column> (most of the time 'id')
5666
	 *    'on_update' => <update rule: 'SET NULL','CASCADE' or 'RESTRICT'>
5667
	 *    'on_delete' => <delete rule: 'SET NULL','CASCADE' or 'RESTRICT'>
5668
	 * )
5669
	 * </code>
5670
	 *
5671
	 * @note the keys in the result array are FKDLs, i.e. descriptive unique
5672
	 * keys per source table. Also see: AQueryWriter::makeFKLabel for details.
5673
	 *
5674
	 * @param string $type the bean type you wish to obtain a key map of
5675
	 *
5676
	 * @return array
5677
	 */
5678
	protected function getKeyMapForType( $type )
5679
	{
5680
		return array();
5681
	}
5682
5683
	/**
5684
	 * This method makes a key for a foreign key description array.
5685
	 * This key is a readable string unique for every source table.
5686
	 * This uniform key is called the FKDL Foreign Key Description Label.
5687
	 * Note that the source table is not part of the FKDL because
5688
	 * this key is supposed to be 'per source table'. If you wish to
5689
	 * include a source table, prefix the key with 'on_table_<SOURCE>_'.
5690
	 *
5691
	 * @param string $from  the column of the key in the source table
5692
	 * @param string $type  the type (table) where the key points to
5693
	 * @param string $to    the target column of the foreign key (mostly just 'id')
5694
	 *
5695
	 * @return string
5696
	 */
5697
	protected function makeFKLabel($from, $type, $to)
5698
	{
5699
		return "from_{$from}_to_table_{$type}_col_{$to}";
5700
	}
5701
5702
	/**
5703
	 * Returns an SQL Filter snippet for reading.
5704
	 *
5705
	 * @param string $type type of bean
5706
	 *
5707
	 * @return string
5708
	 */
5709
	protected function getSQLFilterSnippet( $type )
5710
	{
5711
		$existingCols = array();
5712
		if (self::$flagSQLFilterSafeMode) {
5713
			$existingCols = $this->getColumns( $type );
5714
		}
5715
5716
		$sqlFilters = array();
5717
		if ( isset( self::$sqlFilters[QueryWriter::C_SQLFILTER_READ][$type] ) ) {
5718
			foreach( self::$sqlFilters[QueryWriter::C_SQLFILTER_READ][$type] as $property => $sqlFilter ) {
5719
				if ( !self::$flagSQLFilterSafeMode || isset( $existingCols[$property] ) ) {
5720
					$sqlFilters[] = $sqlFilter.' AS '.$property.' ';
5721
				}
5722
			}
5723
		}
5724
		$sqlFilterStr = ( count($sqlFilters) ) ? ( ','.implode( ',', $sqlFilters ) ) : '';
5725
		return $sqlFilterStr;
5726
	}
5727
5728
	/**
5729
	 * Generates a list of parameters (slots) for an SQL snippet.
5730
	 * This method calculates the correct number of slots to insert in the
5731
	 * SQL snippet and determines the correct type of slot. If the bindings
5732
	 * array contains named parameters this method will return named ones and
5733
	 * update the keys in the value list accordingly (that's why we use the &).
5734
	 *
5735
	 * If you pass an offset the bindings will be re-added to the value list.
5736
	 * Some databases cant handle duplicate parameter names in queries.
5737
	 *
5738
	 * @param array   &$valueList    list of values to generate slots for (gets modified if needed)
5739
	 * @param array   $otherBindings list of additional bindings
5740
	 * @param integer $offset        start counter at...
5741
	 *
5742
	 * @return string
5743
	 */
5744
	protected function getParametersForInClause( &$valueList, $otherBindings, $offset = 0 )
5745
	{
5746
		if ( is_array( $otherBindings ) && count( $otherBindings ) > 0 ) {
5747
			reset( $otherBindings );
5748
5749
			$key = key( $otherBindings );
5750
5751
			if ( !is_numeric($key) ) {
5752
				$filler  = array();
5753
				$newList = (!$offset) ? array() : $valueList;
5754
				$counter = $offset;
5755
5756
				foreach( $valueList as $value ) {
5757
					$slot           = ':slot' . ( $counter++ );
5758
					$filler[]       = $slot;
5759
					$newList[$slot] = $value;
5760
				}
5761
5762
				// Change the keys!
5763
				$valueList = $newList;
5764
5765
				return implode( ',', $filler );
5766
			}
5767
		}
5768
5769
		return implode( ',', array_fill( 0, count( $valueList ), '?' ) );
5770
	}
5771
5772
	/**
5773
	 * Adds a data type to the list of data types.
5774
	 * Use this method to add a new column type definition to the writer.
5775
	 * Used for UUID support.
5776
	 *
5777
	 * @param integer $dataTypeID    magic number constant assigned to this data type
5778
	 * @param string  $SQLDefinition SQL column definition (i.e. INT(11))
5779
	 *
5780
	 * @return self
5781
	 */
5782
	protected function addDataType( $dataTypeID, $SQLDefinition )
5783
	{
5784
		$this->typeno_sqltype[ $dataTypeID ] = $SQLDefinition;
5785
		$this->sqltype_typeno[ $SQLDefinition ] = $dataTypeID;
5786
		return $this;
5787
	}
5788
5789
	/**
5790
	 * Returns the sql that should follow an insert statement.
5791
	 *
5792
	 * @param string $table name
5793
	 *
5794
	 * @return string
5795
	 */
5796
	protected function getInsertSuffix( $table )
5797
	{
5798
		return '';
5799
	}
5800
5801
	/**
5802
	 * Checks whether a value starts with zeros. In this case
5803
	 * the value should probably be stored using a text datatype instead of a
5804
	 * numerical type in order to preserve the zeros.
5805
	 *
5806
	 * @param string $value value to be checked.
5807
	 *
5808
	 * @return boolean
5809
	 */
5810
	protected function startsWithZeros( $value )
5811
	{
5812
		$value = strval( $value );
5813
5814
		if ( strlen( $value ) > 1 && strpos( $value, '0' ) === 0 && strpos( $value, '0.' ) !== 0 ) {
5815
			return TRUE;
5816
		} else {
5817
			return FALSE;
5818
		}
5819
	}
5820
5821
	/**
5822
	 * Inserts a record into the database using a series of insert columns
5823
	 * and corresponding insertvalues. Returns the insert id.
5824
	 *
5825
	 * @param string $table         table to perform query on
5826
	 * @param array  $insertcolumns columns to be inserted
5827
	 * @param array  $insertvalues  values to be inserted
5828
	 *
5829
	 * @return integer
5830
	 */
5831
	protected function insertRecord( $type, $insertcolumns, $insertvalues )
5832
	{
5833
		$default = $this->defaultValue;
5834
		$suffix  = $this->getInsertSuffix( $type );
5835
		$table   = $this->esc( $type );
5836
5837
		if ( count( $insertvalues ) > 0 && is_array( $insertvalues[0] ) && count( $insertvalues[0] ) > 0 ) {
5838
5839
			$insertSlots = array();
5840
			foreach ( $insertcolumns as $k => $v ) {
5841
				$insertcolumns[$k] = $this->esc( $v );
5842
5843
				if (isset(self::$sqlFilters['w'][$type][$v])) {
5844
					$insertSlots[] = self::$sqlFilters['w'][$type][$v];
5845
				} else {
5846
					$insertSlots[] = '?';
5847
				}
5848
			}
5849
5850
			$insertSQL = "INSERT INTO $table ( id, " . implode( ',', $insertcolumns ) . " ) VALUES
5851
			( $default, " . implode( ',', $insertSlots ) . " ) $suffix";
5852
5853
			$ids = array();
5854
			foreach ( $insertvalues as $i => $insertvalue ) {
5855
				$ids[] = $this->adapter->getCell( $insertSQL, $insertvalue, $i );
5856
			}
5857
5858
			$result = count( $ids ) === 1 ? array_pop( $ids ) : $ids;
5859
		} else {
5860
			$result = $this->adapter->getCell( "INSERT INTO $table (id) VALUES($default) $suffix" );
5861
		}
5862
5863
		if ( $suffix ) return $result;
5864
5865
		$last_id = $this->adapter->getInsertID();
5866
5867
		return $last_id;
5868
	}
5869
5870
	/**
5871
	 * Checks table name or column name.
5872
	 *
5873
	 * @param string $table table string
5874
	 *
5875
	 * @return string
5876
	 */
5877
	protected function check( $struct )
5878
	{
5879
		if ( !is_string( $struct ) || !preg_match( '/^[a-zA-Z0-9_]+$/', $struct ) ) {
5880
			throw new RedException( 'Identifier does not conform to RedBeanPHP security policies.' );
5881
		}
5882
5883
		return $struct;
5884
	}
5885
5886
	/**
5887
	 * Checks whether the specified type (i.e. table) already exists in the database.
5888
	 * Not part of the Object Database interface!
5889
	 *
5890
	 * @param string $table table name
5891
	 *
5892
	 * @return boolean
5893
	 */
5894
	public function tableExists( $table )
5895
	{
5896
		$tables = $this->getTables();
5897
5898
		return in_array( $table, $tables );
5899
	}
5900
5901
	/**
5902
	 * @see QueryWriter::glueSQLCondition
5903
	 */
5904
	public function glueSQLCondition( $sql, $glue = NULL )
5905
	{
5906
		static $snippetCache = array();
5907
5908
		if ( trim( $sql ) === '' ) {
5909
			return $sql;
5910
		}
5911
5912
		$key = $glue . '|' . $sql;
5913
5914
		if ( isset( $snippetCache[$key] ) ) {
5915
			return $snippetCache[$key];
5916
		}
5917
5918
		$lsql = ltrim( $sql );
5919
5920
		if ( preg_match( '/^(INNER|LEFT|RIGHT|JOIN|AND|OR|WHERE|ORDER|GROUP|HAVING|LIMIT|OFFSET)\s+/i', $lsql ) ) {
5921
			if ( $glue === QueryWriter::C_GLUE_WHERE && stripos( $lsql, 'AND' ) === 0 ) {
5922
				$snippetCache[$key] = ' WHERE ' . substr( $lsql, 3 );
5923
			} else {
5924
				$snippetCache[$key] = $sql;
5925
			}
5926
		} else {
5927
			$snippetCache[$key] = ( ( $glue === QueryWriter::C_GLUE_AND ) ? ' AND ' : ' WHERE ') . $sql;
5928
		}
5929
5930
		return $snippetCache[$key];
5931
	}
5932
5933
	/**
5934
	 * @see QueryWriter::glueLimitOne
5935
	 */
5936
	public function glueLimitOne( $sql = '')
5937
	{
5938
		return ( strpos( strtoupper( $sql ), 'LIMIT' ) === FALSE ) ? ( $sql . ' LIMIT 1 ' ) : $sql;
5939
	}
5940
5941
	/**
5942
	 * @see QueryWriter::esc
5943
	 */
5944
	public function esc( $dbStructure, $dontQuote = FALSE )
5945
	{
5946
		$this->check( $dbStructure );
5947
5948
		return ( $dontQuote ) ? $dbStructure : $this->quoteCharacter . $dbStructure . $this->quoteCharacter;
5949
	}
5950
5951
	/**
5952
	 * @see QueryWriter::addColumn
5953
	 */
5954
	public function addColumn( $type, $column, $field )
5955
	{
5956
		$table  = $type;
5957
		$type   = $field;
5958
		$table  = $this->esc( $table );
5959
		$column = $this->esc( $column );
5960
5961
		$type = ( isset( $this->typeno_sqltype[$type] ) ) ? $this->typeno_sqltype[$type] : '';
5962
5963
		$this->adapter->exec( "ALTER TABLE $table ADD $column $type " );
5964
	}
5965
5966
	/**
5967
	 * @see QueryWriter::updateRecord
5968
	 */
5969
	public function updateRecord( $type, $updatevalues, $id = NULL )
5970
	{
5971
		$table = $type;
5972
5973
		if ( !$id ) {
5974
			$insertcolumns = $insertvalues = array();
5975
5976
			foreach ( $updatevalues as $pair ) {
5977
				$insertcolumns[] = $pair['property'];
5978
				$insertvalues[]  = $pair['value'];
5979
			}
5980
5981
			//Otherwise psql returns string while MySQL/SQLite return numeric causing problems with additions (array_diff)
5982
			return (string) $this->insertRecord( $table, $insertcolumns, array( $insertvalues ) );
5983
		}
5984
5985
		if ( $id && !count( $updatevalues ) ) {
5986
			return $id;
5987
		}
5988
5989
		$table = $this->esc( $table );
5990
		$sql   = "UPDATE $table SET ";
5991
5992
		$p = $v = array();
5993
5994
		foreach ( $updatevalues as $uv ) {
5995
5996
			if ( isset( self::$sqlFilters['w'][$type][$uv['property']] ) ) {
5997
				$p[] = " {$this->esc( $uv["property"] )} = ". self::$sqlFilters['w'][$type][$uv['property']];
5998
			} else {
5999
				$p[] = " {$this->esc( $uv["property"] )} = ? ";
6000
			}
6001
6002
			$v[] = $uv['value'];
6003
		}
6004
6005
		$sql .= implode( ',', $p ) . ' WHERE id = ? ';
6006
6007
		$v[] = $id;
6008
6009
		$this->adapter->exec( $sql, $v );
6010
6011
		return $id;
6012
	}
6013
6014
	/**
6015
	 * @see QueryWriter::writeJoin
6016
	 */
6017
	public function writeJoin( $type, $targetType, $leftRight = 'LEFT' )
6018
	{
6019
		if ( $leftRight !== 'LEFT' && $leftRight !== 'RIGHT' && $leftRight !== 'INNER' )
6020
			throw new RedException( 'Invalid JOIN.' );
6021
6022
		$table = $this->esc( $type );
6023
		$targetTable = $this->esc( $targetType );
6024
		$field = $this->esc( $targetType, TRUE );
6025
		return " {$leftRight} JOIN {$targetTable} ON {$targetTable}.id = {$table}.{$field}_id ";
6026
	}
6027
6028
	/**
6029
	 * Sets an SQL snippet to be used for the next queryRecord() operation.
6030
	 * A select snippet will be inserted at the end of the SQL select statement and
6031
	 * can be used to modify SQL-select commands to enable locking, for instance
6032
	 * using the 'FOR UPDATE' snippet (this will generate an SQL query like:
6033
	 * 'SELECT * FROM ... FOR UPDATE'. After the query has been executed the
6034
	 * SQL snippet will be erased. Note that only the first upcoming direct or
6035
	 * indirect invocation of queryRecord() through batch(), find() or load()
6036
	 * will be affected. The SQL snippet will be cached.
6037
	 *
6038
	 * @param string $sql SQL snippet to use in SELECT statement.
6039
	 *
6040
	 * return self
6041
	 */
6042
	public function setSQLSelectSnippet( $sqlSelectSnippet = '' ) {
6043
		$this->sqlSelectSnippet = $sqlSelectSnippet;
6044
		return $this;
6045
	}
6046
6047
	/**
6048
	 * @see QueryWriter::queryRecord
6049
	 */
6050
	public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
6051
	{
6052
		if ( $this->flagUseCache ) {
6053
			$key = $this->getCacheKey( array( $conditions, trim("$addSql {$this->sqlSelectSnippet}"), $bindings, 'select' ) );
6054
			if ( $cached = $this->getCached( $type, $key ) ) {
6055
				return $cached;
6056
			}
6057
		}
6058
6059
		$table = $this->esc( $type );
6060
6061
		$sqlFilterStr = '';
6062
		if ( count( self::$sqlFilters ) ) {
6063
			$sqlFilterStr = $this->getSQLFilterSnippet( $type );
6064
		}
6065
		
6066
		if ( is_array ( $conditions ) && !empty ( $conditions ) ) {
6067
			$sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
6068
		} else {
6069
			$sql = $this->glueSQLCondition( $addSql );
6070
		}
6071
6072
		$fieldSelection = ( self::$flagNarrowFieldMode ) ? "{$table}.*" : '*';
6073
		$sql   = "SELECT {$fieldSelection} {$sqlFilterStr} FROM {$table} {$sql} {$this->sqlSelectSnippet} -- keep-cache";
6074
		$this->sqlSelectSnippet = '';
6075
		$rows  = $this->adapter->get( $sql, $bindings );
6076
6077
		if ( $this->flagUseCache ) {
6078
			$this->putResultInCache( $type, $key, $rows );
6079
		}
6080
6081
		return $rows;
6082
	}
6083
6084
	/**
6085
	 * @see QueryWriter::queryRecordWithCursor
6086
	 */
6087
	public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() )
6088
	{
6089
		$table = $this->esc( $type );
6090
6091
		$sqlFilterStr = '';
6092
		if ( count( self::$sqlFilters ) ) {
6093
			$sqlFilterStr = $this->getSQLFilterSnippet( $type );
6094
		}
6095
6096
		$fieldSelection = ( self::$flagNarrowFieldMode ) ? "{$table}.*" : '*';
6097
6098
		$sql = $this->glueSQLCondition( $addSql, NULL );
6099
		$sql = "SELECT {$fieldSelection} {$sqlFilterStr} FROM {$table} {$sql} -- keep-cache";
6100
6101
		return $this->adapter->getCursor( $sql, $bindings );
6102
	}
6103
6104
	/**
6105
	 * @see QueryWriter::queryRecordRelated
6106
	 */
6107
	public function queryRecordRelated( $sourceType, $destType, $linkIDs, $addSql = '', $bindings = array() )
6108
	{
6109
		list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
6110
6111
		if ( $this->flagUseCache ) {
6112
			$key = $this->getCacheKey( array( $sourceType, implode( ',', $linkIDs ), trim($addSql), $bindings, 'selectrelated' ) );
6113
			if ( $cached = $this->getCached( $destType, $key ) ) {
6114
				return $cached;
6115
			}
6116
		}
6117
6118
		$addSql = $this->glueSQLCondition( $addSql, QueryWriter::C_GLUE_WHERE );
6119
		$inClause = $this->getParametersForInClause( $linkIDs, $bindings );
6120
6121
		$sqlFilterStr = '';
6122
		if ( count( self::$sqlFilters ) ) {
6123
			$sqlFilterStr = $this->getSQLFilterSnippet( $destType );
6124
		}
6125
6126
		if ( $sourceType === $destType ) {
6127
			$inClause2 = $this->getParametersForInClause( $linkIDs, $bindings, count( $bindings ) ); //for some databases
6128
			$sql = "
6129
			SELECT
6130
				{$destTable}.* {$sqlFilterStr} ,
6131
				COALESCE(
6132
				NULLIF({$linkTable}.{$sourceCol}, {$destTable}.id),
6133
				NULLIF({$linkTable}.{$destCol}, {$destTable}.id)) AS linked_by
6134
			FROM {$linkTable}
6135
			INNER JOIN {$destTable} ON
6136
			( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) ) OR
6137
			( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} IN ($inClause2) )
6138
			{$addSql}
6139
			-- keep-cache";
6140
6141
			$linkIDs = array_merge( $linkIDs, $linkIDs );
6142
		} else {
6143
			$sql = "
6144
			SELECT
6145
				{$destTable}.* {$sqlFilterStr},
6146
				{$linkTable}.{$sourceCol} AS linked_by
6147
			FROM {$linkTable}
6148
			INNER JOIN {$destTable} ON
6149
			( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) )
6150
			{$addSql}
6151
			-- keep-cache";
6152
		}
6153
6154
		$bindings = array_merge( $linkIDs, $bindings );
6155
6156
		$rows = $this->adapter->get( $sql, $bindings );
6157
6158
		if ( $this->flagUseCache ) {
6159
			$this->putResultInCache( $destType, $key, $rows );
6160
		}
6161
6162
		return $rows;
6163
	}
6164
6165
	/**
6166
	 * @see QueryWriter::queryRecordLink
6167
	 */
6168
	public function queryRecordLink( $sourceType, $destType, $sourceID, $destID )
6169
	{
6170
		list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
6171
6172
		if ( $this->flagUseCache ) {
6173
			$key = $this->getCacheKey( array( $sourceType, $destType, $sourceID, $destID, 'selectlink' ) );
6174
			if ( $cached = $this->getCached( $linkTable, $key ) ) {
6175
				return $cached;
6176
			}
6177
		}
6178
6179
		$sqlFilterStr = '';
6180
		if ( count( self::$sqlFilters ) ) {
6181
			$sqlFilterStr = $this->getSQLFilterSnippet( $destType );
6182
		}
6183
6184
		if ( $sourceTable === $destTable ) {
6185
			$sql = "SELECT {$linkTable}.* {$sqlFilterStr} FROM {$linkTable}
6186
				WHERE ( {$sourceCol} = ? AND {$destCol} = ? ) OR
6187
				 ( {$destCol} = ? AND {$sourceCol} = ? ) -- keep-cache";
6188
			$row = $this->adapter->getRow( $sql, array( $sourceID, $destID, $sourceID, $destID ) );
6189
		} else {
6190
			$sql = "SELECT {$linkTable}.* {$sqlFilterStr} FROM {$linkTable}
6191
				WHERE {$sourceCol} = ? AND {$destCol} = ? -- keep-cache";
6192
			$row = $this->adapter->getRow( $sql, array( $sourceID, $destID ) );
6193
		}
6194
6195
		if ( $this->flagUseCache ) {
6196
			$this->putResultInCache( $linkTable, $key, $row );
6197
		}
6198
6199
		return $row;
6200
	}
6201
6202
	/**
6203
	 * Returns or counts all rows of specified type that have been tagged with one of the
6204
	 * strings in the specified tag list array.
6205
	 *
6206
	 * Note that the additional SQL snippet can only be used for pagination,
6207
	 * the SQL snippet will be appended to the end of the query.
6208
	 *
6209
	 * @param string  $type     the bean type you want to query
6210
	 * @param array   $tagList  an array of strings, each string containing a tag title
6211
	 * @param boolean $all      if TRUE only return records that have been associated with ALL the tags in the list
6212
	 * @param string  $addSql   addition SQL snippet, for pagination
6213
	 * @param array   $bindings parameter bindings for additional SQL snippet
6214
	 * @param string  $wrap     SQL wrapper string (use %s for subquery)
6215
	 *
6216
	 * @return array
6217
	 */
6218
	private function queryTaggedGeneric( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array(), $wrap = '%s' )
6219
	{
6220
		if ( $this->flagUseCache ) {
6221
			$key = $this->getCacheKey( array( implode( ',', $tagList ), $all, trim($addSql), $bindings, 'selectTagged' ) );
6222
			if ( $cached = $this->getCached( $type, $key ) ) {
6223
				return $cached;
6224
			}
6225
		}
6226
6227
		$assocType = $this->getAssocTable( array( $type, 'tag' ) );
6228
		$assocTable = $this->esc( $assocType );
6229
		$assocField = $type . '_id';
6230
		$table = $this->esc( $type );
6231
		$slots = implode( ',', array_fill( 0, count( $tagList ), '?' ) );
6232
		$score = ( $all ) ? count( $tagList ) : 1;
6233
6234
		$sql = "
6235
			SELECT {$table}.* FROM {$table}
6236
			INNER JOIN {$assocTable} ON {$assocField} = {$table}.id
6237
			INNER JOIN tag ON {$assocTable}.tag_id = tag.id
6238
			WHERE tag.title IN ({$slots})
6239
			GROUP BY {$table}.id
6240
			HAVING count({$table}.id) >= ?
6241
			{$addSql}
6242
			-- keep-cache
6243
		";
6244
		$sql = sprintf($wrap,$sql);
6245
6246
		$bindings = array_merge( $tagList, array( $score ), $bindings );
6247
		$rows = $this->adapter->get( $sql, $bindings );
6248
6249
		if ( $this->flagUseCache ) {
6250
			$this->putResultInCache( $type, $key, $rows );
6251
		}
6252
6253
		return $rows;
6254
	}
6255
6256
	/**
6257
	 * @see QueryWriter::queryTagged
6258
	 */
6259
	public function queryTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() )
6260
	{
6261
		return $this->queryTaggedGeneric( $type, $tagList, $all, $addSql, $bindings );
6262
	}
6263
6264
	/**
6265
	 * @see QueryWriter::queryCountTagged
6266
	 */
6267
	public function queryCountTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() )
6268
	{
6269
		$rows = $this->queryTaggedGeneric( $type, $tagList, $all, $addSql, $bindings, 'SELECT COUNT(*) AS counted FROM (%s) AS counting' );
6270
		return intval($rows[0]['counted']);
6271
	}
6272
6273
	/**
6274
	 * @see QueryWriter::queryRecordCount
6275
	 */
6276
	public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
6277
	{
6278
		if ( $this->flagUseCache ) {
6279
			$key = $this->getCacheKey( array( $conditions, trim($addSql), $bindings, 'count' ) );
6280
			if ( $cached = $this->getCached( $type, $key ) ) {
6281
				return $cached;
6282
			}
6283
		}
6284
6285
		$table  = $this->esc( $type );
6286
6287
		if ( is_array ( $conditions ) && !empty ( $conditions ) ) {
6288
			$sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
6289
		} else {
6290
			$sql = $this->glueSQLCondition( $addSql );
6291
		}
6292
6293
		$sql    = "SELECT COUNT(*) FROM {$table} {$sql} -- keep-cache";
6294
		$count  = (int) $this->adapter->getCell( $sql, $bindings );
6295
6296
		if ( $this->flagUseCache ) {
6297
			$this->putResultInCache( $type, $key, $count );
6298
		}
6299
6300
		return $count;
6301
	}
6302
6303
	/**
6304
	 * @see QueryWriter::queryRecordCountRelated
6305
	 */
6306
	public function queryRecordCountRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() )
6307
	{
6308
		list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
6309
6310
		if ( $this->flagUseCache ) {
6311
			$cacheType = "#{$sourceType}/{$destType}";
6312
			$key = $this->getCacheKey( array( $sourceType, $destType, $linkID, trim($addSql), $bindings, 'countrelated' ) );
6313
			if ( $cached = $this->getCached( $cacheType, $key ) ) {
6314
				return $cached;
6315
			}
6316
		}
6317
6318
		if ( $sourceType === $destType ) {
6319
			$sql = "
6320
			SELECT COUNT(*) FROM {$linkTable}
6321
			INNER JOIN {$destTable} ON
6322
			( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? ) OR
6323
			( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} = ? )
6324
			{$addSql}
6325
			-- keep-cache";
6326
6327
			$bindings = array_merge( array( $linkID, $linkID ), $bindings );
6328
		} else {
6329
			$sql = "
6330
			SELECT COUNT(*) FROM {$linkTable}
6331
			INNER JOIN {$destTable} ON
6332
			( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? )
6333
			{$addSql}
6334
			-- keep-cache";
6335
6336
			$bindings = array_merge( array( $linkID ), $bindings );
6337
		}
6338
6339
		$count = (int) $this->adapter->getCell( $sql, $bindings );
6340
6341
		if ( $this->flagUseCache ) {
6342
			$this->putResultInCache( $cacheType, $key, $count );
6343
		}
6344
6345
		return $count;
6346
	}
6347
6348
	/**
6349
	 * @see QueryWriter::queryRecursiveCommonTableExpression
6350
	 */
6351
	public function queryRecursiveCommonTableExpression( $type, $id, $up = TRUE, $addSql = NULL, $bindings = array() )
6352
	{
6353
		$alias     = $up ? 'parent' : 'child';
6354
		$direction = $up ? " {$alias}.{$type}_id = {$type}.id " : " {$alias}.id = {$type}.{$type}_id ";
6355
6356
		array_unshift( $bindings, $id );
6357
6358
		$sql = $this->glueSQLCondition( $addSql, QueryWriter::C_GLUE_WHERE );
6359
6360
		$rows = $this->adapter->get("
6361
			WITH RECURSIVE tree AS
6362
			(
6363
				SELECT *
6364
				FROM {$type} WHERE {$type}.id = ?
6365
				UNION ALL
6366
				SELECT {$type}.* FROM {$type}
6367
				INNER JOIN tree {$alias} ON {$direction}
6368
			)
6369
			SELECT * FROM tree {$sql};",
6370
			$bindings
6371
		);
6372
6373
		return $rows;
6374
	}
6375
6376
	/**
6377
	 * @see QueryWriter::deleteRecord
6378
	 */
6379
	public function deleteRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
6380
	{
6381
		$table  = $this->esc( $type );
6382
6383
		if ( is_array ( $conditions ) && !empty ( $conditions ) ) {
6384
			$sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
6385
		} else {
6386
			$sql = $this->glueSQLCondition( $addSql );
6387
		}
6388
		
6389
		$sql    = "DELETE FROM {$table} {$sql}";
6390
6391
		$this->adapter->exec( $sql, $bindings );
6392
	}
6393
6394
	/**
6395
	 * @see QueryWriter::deleteRelations
6396
	 */
6397
	public function deleteRelations( $sourceType, $destType, $sourceID )
6398
	{
6399
		list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
6400
6401
		if ( $sourceTable === $destTable ) {
6402
			$sql = "DELETE FROM {$linkTable}
6403
				WHERE ( {$sourceCol} = ? ) OR
6404
				( {$destCol} = ?  )
6405
			";
6406
6407
			$this->adapter->exec( $sql, array( $sourceID, $sourceID ) );
6408
		} else {
6409
			$sql = "DELETE FROM {$linkTable}
6410
				WHERE {$sourceCol} = ? ";
6411
6412
			$this->adapter->exec( $sql, array( $sourceID ) );
6413
		}
6414
	}
6415
6416
	/**
6417
	 * @see QueryWriter::widenColumn
6418
	 */
6419
	public function widenColumn( $type, $property, $dataType )
6420
	{
6421
		if ( !isset($this->typeno_sqltype[$dataType]) ) return FALSE;
6422
6423
		$table   = $this->esc( $type );
6424
		$column  = $this->esc( $property );
6425
6426
		$newType = $this->typeno_sqltype[$dataType];
6427
6428
		$this->adapter->exec( "ALTER TABLE $table CHANGE $column $column $newType " );
6429
6430
		return TRUE;
6431
	}
6432
6433
	/**
6434
	 * @see QueryWriter::wipe
6435
	 */
6436
	public function wipe( $type )
6437
	{
6438
		$table = $this->esc( $type );
6439
6440
		$this->adapter->exec( "TRUNCATE $table " );
6441
	}
6442
6443
	/**
6444
	 * @see QueryWriter::renameAssocTable
6445
	 */
6446
	public function renameAssocTable( $from, $to = NULL )
6447
	{
6448
		self::renameAssociation( $from, $to );
6449
	}
6450
6451
	/**
6452
	 * @see QueryWriter::getAssocTable
6453
	 */
6454
	public function getAssocTable( $types )
6455
	{
6456
		return self::getAssocTableFormat( $types );
6457
	}
6458
6459
	/**
6460
	 * Turns caching on or off. Default: off.
6461
	 * If caching is turned on retrieval queries fired after eachother will
6462
	 * use a result row cache.
6463
	 *
6464
	 * @param boolean
6465
	 *
6466
	 * @return void
6467
	 */
6468
	public function setUseCache( $yesNo )
6469
	{
6470
		$this->flushCache();
6471
6472
		$this->flagUseCache = (bool) $yesNo;
6473
	}
6474
6475
	/**
6476
	 * Flushes the Query Writer Cache.
6477
	 * Clears the internal query cache array and returns its overall
6478
	 * size.
6479
	 *
6480
	 * @return integer
6481
	 */
6482
	public function flushCache( $newMaxCacheSizePerType = NULL )
6483
	{
6484
		if ( !is_null( $newMaxCacheSizePerType ) && $newMaxCacheSizePerType > 0 ) {
6485
			$this->maxCacheSizePerType = $newMaxCacheSizePerType;
6486
		}
6487
		$count = count( $this->cache, COUNT_RECURSIVE );
6488
		$this->cache = array();
6489
		return $count;
6490
	}
6491
6492
	/**
6493
	 * @deprecated Use esc() instead.
6494
	 *
6495
	 * @param string  $column   column to be escaped
6496
	 * @param boolean $noQuotes omit quotes
6497
	 *
6498
	 * @return string
6499
	 */
6500
	public function safeColumn( $column, $noQuotes = FALSE )
6501
	{
6502
		return $this->esc( $column, $noQuotes );
6503
	}
6504
6505
	/**
6506
	 * @deprecated Use esc() instead.
6507
	 *
6508
	 * @param string  $table    table to be escaped
6509
	 * @param boolean $noQuotes omit quotes
6510
	 *
6511
	 * @return string
6512
	 */
6513
	public function safeTable( $table, $noQuotes = FALSE )
6514
	{
6515
		return $this->esc( $table, $noQuotes );
6516
	}
6517
6518
	/**
6519
	 * @see QueryWriter::inferFetchType
6520
	 */
6521
	public function inferFetchType( $type, $property )
6522
	{
6523
		$type = $this->esc( $type, TRUE );
6524
		$field = $this->esc( $property, TRUE ) . '_id';
6525
		$keys = $this->getKeyMapForType( $type );
6526
6527
		foreach( $keys as $key ) {
6528
			if (
6529
				$key['from'] === $field
6530
			) return $key['table'];
6531
		}
6532
		return NULL;
6533
	}
6534
6535
	/**
6536
	 * @see QueryWriter::addUniqueConstraint
6537
	 */
6538
	public function addUniqueIndex( $type, $properties )
6539
	{
6540
		return $this->addUniqueConstraint( $type, $properties );
6541
	}
6542
}
6543
}
6544
6545
namespace RedBeanPHP\QueryWriter {
6546
6547
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
6548
use RedBeanPHP\QueryWriter as QueryWriter;
6549
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
6550
use RedBeanPHP\Adapter as Adapter;
6551
use RedBeanPHP\RedException\SQL as SQLException;
6552
6553
/**
6554
 * RedBeanPHP MySQLWriter.
6555
 * This is a QueryWriter class for RedBeanPHP.
6556
 * This QueryWriter provides support for the MySQL/MariaDB database platform.
6557
 *
6558
 * @file    RedBeanPHP/QueryWriter/MySQL.php
6559
 * @author  Gabor de Mooij and the RedBeanPHP Community
6560
 * @license BSD/GPLv2
6561
 *
6562
 * @copyright
6563
 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
6564
 * This source file is subject to the BSD/GPLv2 License that is bundled
6565
 * with this source code in the file license.txt.
6566
 */
6567
class MySQL extends AQueryWriter implements QueryWriter
6568
{
6569
	/**
6570
	 * Data types
6571
	 */
6572
	const C_DATATYPE_BOOL             = 0;
6573
	const C_DATATYPE_UINT32           = 2;
6574
	const C_DATATYPE_DOUBLE           = 3;
6575
	const C_DATATYPE_TEXT7            = 4; //InnoDB cant index varchar(255) utf8mb4 - so keep 191 as long as possible
6576
	const C_DATATYPE_TEXT8            = 5;
6577
	const C_DATATYPE_TEXT16           = 6;
6578
	const C_DATATYPE_TEXT32           = 7;
6579
	const C_DATATYPE_SPECIAL_DATE     = 80;
6580
	const C_DATATYPE_SPECIAL_DATETIME = 81;
6581
	const C_DATATYPE_SPECIAL_TIME     = 83;  //MySQL time column (only manual)
6582
	const C_DATATYPE_SPECIAL_POINT    = 90;
6583
	const C_DATATYPE_SPECIAL_LINESTRING = 91;
6584
	const C_DATATYPE_SPECIAL_POLYGON    = 92;
6585
	const C_DATATYPE_SPECIAL_MONEY      = 93;
6586
	const C_DATATYPE_SPECIAL_JSON       = 94;  //JSON support (only manual)
6587
6588
	const C_DATATYPE_SPECIFIED        = 99;
6589
6590
	/**
6591
	 * @var DBAdapter
6592
	 */
6593
	protected $adapter;
6594
6595
	/**
6596
	 * @var string
6597
	 */
6598
	protected $quoteCharacter = '`';
6599
6600
	/**
6601
	 * @see AQueryWriter::getKeyMapForType
6602
	 */
6603
	protected function getKeyMapForType( $type )
6604
	{
6605
		$databaseName = $this->adapter->getCell('SELECT DATABASE()');
6606
		$table = $this->esc( $type, TRUE );
6607
		$keys = $this->adapter->get('
6608
			SELECT
6609
				information_schema.key_column_usage.constraint_name AS `name`,
6610
				information_schema.key_column_usage.referenced_table_name AS `table`,
6611
				information_schema.key_column_usage.column_name AS `from`,
6612
				information_schema.key_column_usage.referenced_column_name AS `to`,
6613
				information_schema.referential_constraints.update_rule AS `on_update`,
6614
				information_schema.referential_constraints.delete_rule AS `on_delete`
6615
				FROM information_schema.key_column_usage
6616
				INNER JOIN information_schema.referential_constraints
6617
				ON information_schema.referential_constraints.constraint_name = information_schema.key_column_usage.constraint_name
6618
			WHERE
6619
				information_schema.key_column_usage.table_schema = :database
6620
				AND information_schema.referential_constraints.constraint_schema  = :database
6621
				AND information_schema.key_column_usage.constraint_schema  = :database
6622
				AND information_schema.key_column_usage.table_name = :table
6623
				AND information_schema.key_column_usage.constraint_name != \'PRIMARY\'
6624
				AND information_schema.key_column_usage.referenced_table_name IS NOT NULL
6625
		', array( ':database' => $databaseName, ':table' => $table ) );
6626
		$keyInfoList = array();
6627
		foreach ( $keys as $k ) {
6628
			$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
6629
			$keyInfoList[$label] = array(
6630
				'name'          => $k['name'],
6631
				'from'          => $k['from'],
6632
				'table'         => $k['table'],
6633
				'to'            => $k['to'],
6634
				'on_update'     => $k['on_update'],
6635
				'on_delete'     => $k['on_delete']
6636
			);
6637
		}
6638
		return $keyInfoList;
6639
	}
6640
6641
	/**
6642
	 * Constructor
6643
	 * Most of the time, you do not need to use this constructor,
6644
	 * since the facade takes care of constructing and wiring the
6645
	 * RedBeanPHP core objects. However if you would like to
6646
	 * assemble an OODB instance yourself, this is how it works:
6647
	 *
6648
	 * Usage:
6649
	 *
6650
	 * <code>
6651
	 * $database = new RPDO( $dsn, $user, $pass );
6652
	 * $adapter = new DBAdapter( $database );
6653
	 * $writer = new PostgresWriter( $adapter );
6654
	 * $oodb = new OODB( $writer, FALSE );
6655
	 * $bean = $oodb->dispense( 'bean' );
6656
	 * $bean->name = 'coffeeBean';
6657
	 * $id = $oodb->store( $bean );
6658
	 * $bean = $oodb->load( 'bean', $id );
6659
	 * </code>
6660
	 *
6661
	 * The example above creates the 3 RedBeanPHP core objects:
6662
	 * the Adapter, the Query Writer and the OODB instance and
6663
	 * wires them together. The example also demonstrates some of
6664
	 * the methods that can be used with OODB, as you see, they
6665
	 * closely resemble their facade counterparts.
6666
	 *
6667
	 * The wiring process: create an RPDO instance using your database
6668
	 * connection parameters. Create a database adapter from the RPDO
6669
	 * object and pass that to the constructor of the writer. Next,
6670
	 * create an OODB instance from the writer. Now you have an OODB
6671
	 * object.
6672
	 *
6673
	 * @param Adapter $adapter Database Adapter
6674
	 */
6675
	public function __construct( Adapter $adapter )
6676
	{
6677
		$this->typeno_sqltype = array(
6678
			MySQL::C_DATATYPE_BOOL             => ' TINYINT(1) UNSIGNED ',
6679
			MySQL::C_DATATYPE_UINT32           => ' INT(11) UNSIGNED ',
6680
			MySQL::C_DATATYPE_DOUBLE           => ' DOUBLE ',
6681
			MySQL::C_DATATYPE_TEXT7            => ' VARCHAR(191) ',
6682
			MYSQL::C_DATATYPE_TEXT8	           => ' VARCHAR(255) ',
6683
			MySQL::C_DATATYPE_TEXT16           => ' TEXT ',
6684
			MySQL::C_DATATYPE_TEXT32           => ' LONGTEXT ',
6685
			MySQL::C_DATATYPE_SPECIAL_DATE     => ' DATE ',
6686
			MySQL::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
6687
			MySQL::C_DATATYPE_SPECIAL_TIME     => ' TIME ',
6688
			MySQL::C_DATATYPE_SPECIAL_POINT    => ' POINT ',
6689
			MySQL::C_DATATYPE_SPECIAL_LINESTRING => ' LINESTRING ',
6690
			MySQL::C_DATATYPE_SPECIAL_POLYGON => ' POLYGON ',
6691
			MySQL::C_DATATYPE_SPECIAL_MONEY    => ' DECIMAL(10,2) ',
6692
			MYSQL::C_DATATYPE_SPECIAL_JSON     => ' JSON '
6693
		);
6694
6695
		$this->sqltype_typeno = array();
6696
6697
		foreach ( $this->typeno_sqltype as $k => $v ) {
6698
			$this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
6699
		}
6700
6701
		$this->adapter = $adapter;
6702
6703
		$this->encoding = $this->adapter->getDatabase()->getMysqlEncoding();
6704
	}
6705
6706
	/**
6707
	 * This method returns the datatype to be used for primary key IDS and
6708
	 * foreign keys. Returns one if the data type constants.
6709
	 *
6710
	 * @return integer
6711
	 */
6712
	public function getTypeForID()
6713
	{
6714
		return self::C_DATATYPE_UINT32;
6715
	}
6716
6717
	/**
6718
	 * @see QueryWriter::getTables
6719
	 */
6720
	public function getTables()
6721
	{
6722
		return $this->adapter->getCol( 'show tables' );
6723
	}
6724
6725
	/**
6726
	 * @see QueryWriter::createTable
6727
	 */
6728
	public function createTable( $table )
6729
	{
6730
		$table = $this->esc( $table );
6731
6732
		$charset_collate = $this->adapter->getDatabase()->getMysqlEncoding( TRUE );
6733
		$charset = $charset_collate['charset'];
6734
		$collate = $charset_collate['collate'];
6735
		
6736
		$sql   = "CREATE TABLE $table (id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY ( id )) ENGINE = InnoDB DEFAULT CHARSET={$charset} COLLATE={$collate} ";
6737
6738
		$this->adapter->exec( $sql );
6739
	}
6740
6741
	/**
6742
	 * @see QueryWriter::getColumns
6743
	 */
6744
	public function getColumns( $table )
6745
	{
6746
		$columnsRaw = $this->adapter->get( "DESCRIBE " . $this->esc( $table ) );
6747
6748
		$columns = array();
6749
		foreach ( $columnsRaw as $r ) {
6750
			$columns[$r['Field']] = $r['Type'];
6751
		}
6752
6753
		return $columns;
6754
	}
6755
6756
	/**
6757
	 * @see QueryWriter::scanType
6758
	 */
6759
	public function scanType( $value, $flagSpecial = FALSE )
6760
	{
6761
		$this->svalue = $value;
6762
6763
		if ( is_null( $value ) ) return MySQL::C_DATATYPE_BOOL;
6764
		if ( $value === INF ) return MySQL::C_DATATYPE_TEXT7;
6765
6766
		if ( $flagSpecial ) {
6767
			if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
6768
				return MySQL::C_DATATYPE_SPECIAL_MONEY;
6769
			}
6770
			if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
6771
				return MySQL::C_DATATYPE_SPECIAL_DATE;
6772
			}
6773
			if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
6774
				return MySQL::C_DATATYPE_SPECIAL_DATETIME;
6775
			}
6776
			if ( preg_match( '/^POINT\(/', $value ) ) {
6777
				return MySQL::C_DATATYPE_SPECIAL_POINT;
6778
			}
6779
			if ( preg_match( '/^LINESTRING\(/', $value ) ) {
6780
				return MySQL::C_DATATYPE_SPECIAL_LINESTRING;
6781
			}
6782
			if ( preg_match( '/^POLYGON\(/', $value ) ) {
6783
				return MySQL::C_DATATYPE_SPECIAL_POLYGON;
6784
			}
6785
			if ( self::$flagUseJSONColumns && $this->isJSON( $value ) ) {
6786
				return self::C_DATATYPE_SPECIAL_JSON;
6787
			}
6788
		}
6789
6790
		//setter turns TRUE FALSE into 0 and 1 because database has no real bools (TRUE and FALSE only for test?).
6791
		if ( $value === FALSE || $value === TRUE || $value === '0' || $value === '1' ) {
6792
			return MySQL::C_DATATYPE_BOOL;
6793
		}
6794
6795
		if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
6796
6797
		if ( !$this->startsWithZeros( $value ) ) {
6798
6799
			if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= 0 && $value <= 4294967295 ) {
6800
				return MySQL::C_DATATYPE_UINT32;
6801
			}
6802
6803
			if ( is_numeric( $value ) ) {
6804
				return MySQL::C_DATATYPE_DOUBLE;
6805
			}
6806
		}
6807
6808
		if ( mb_strlen( $value, 'UTF-8' ) <= 191 ) {
6809
			return MySQL::C_DATATYPE_TEXT7;
6810
		}
6811
6812
		if ( mb_strlen( $value, 'UTF-8' ) <= 255 ) {
6813
			return MySQL::C_DATATYPE_TEXT8;
6814
		}
6815
6816
		if ( mb_strlen( $value, 'UTF-8' ) <= 65535 ) {
6817
			return MySQL::C_DATATYPE_TEXT16;
6818
		}
6819
6820
		return MySQL::C_DATATYPE_TEXT32;
6821
	}
6822
6823
	/**
6824
	 * @see QueryWriter::code
6825
	 */
6826
	public function code( $typedescription, $includeSpecials = FALSE )
6827
	{
6828
		if ( isset( $this->sqltype_typeno[$typedescription] ) ) {
6829
			$r = $this->sqltype_typeno[$typedescription];
6830
		} else {
6831
			$r = self::C_DATATYPE_SPECIFIED;
6832
		}
6833
6834
		if ( $includeSpecials ) {
6835
			return $r;
6836
		}
6837
6838
		if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
6839
			return self::C_DATATYPE_SPECIFIED;
6840
		}
6841
6842
		return $r;
6843
	}
6844
6845
	/**
6846
	 * @see QueryWriter::addUniqueIndex
6847
	 */
6848
	public function addUniqueConstraint( $type, $properties )
6849
	{
6850
		$tableNoQ = $this->esc( $type, TRUE );
6851
		$columns = array();
6852
		foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
6853
		$table = $this->esc( $type );
6854
		sort( $columns ); // Else we get multiple indexes due to order-effects
6855
		$name = 'UQ_' . sha1( implode( ',', $columns ) );
6856
		try {
6857
			$sql = "ALTER TABLE $table
6858
						 ADD UNIQUE INDEX $name (" . implode( ',', $columns ) . ")";
6859
			$this->adapter->exec( $sql );
6860
		} catch ( SQLException $e ) {
6861
			//do nothing, dont use alter table ignore, this will delete duplicate records in 3-ways!
6862
			return FALSE;
6863
		}
6864
		return TRUE;
6865
	}
6866
6867
	/**
6868
	 * @see QueryWriter::addIndex
6869
	 */
6870
	public function addIndex( $type, $name, $property )
6871
	{
6872
		try {
6873
			$table  = $this->esc( $type );
6874
			$name   = preg_replace( '/\W/', '', $name );
6875
			$column = $this->esc( $property );
6876
			$this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
6877
			return TRUE;
6878
		} catch ( SQLException $e ) {
6879
			return FALSE;
6880
		}
6881
	}
6882
6883
	/**
6884
	 * @see QueryWriter::addFK
6885
	 * @return bool
6886
	 */
6887
	public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
6888
	{
6889
		$table = $this->esc( $type );
6890
		$targetTable = $this->esc( $targetType );
6891
		$targetTableNoQ = $this->esc( $targetType, TRUE );
6892
		$field = $this->esc( $property );
6893
		$fieldNoQ = $this->esc( $property, TRUE );
6894
		$targetField = $this->esc( $targetProperty );
6895
		$targetFieldNoQ = $this->esc( $targetProperty, TRUE );
6896
		$tableNoQ = $this->esc( $type, TRUE );
6897
		$fieldNoQ = $this->esc( $property, TRUE );
6898
		if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
6899
6900
		//Widen the column if it's incapable of representing a foreign key (at least INT).
6901
		$columns = $this->getColumns( $tableNoQ );
6902
		$idType = $this->getTypeForID();
6903
		if ( $this->code( $columns[$fieldNoQ] ) !==  $idType ) {
6904
			$this->widenColumn( $type, $property, $idType );
6905
		}
6906
6907
		$fkName = 'fk_'.($tableNoQ.'_'.$fieldNoQ);
6908
		$cName = 'c_'.$fkName;
6909
		try {
6910
			$this->adapter->exec( "
6911
				ALTER TABLE {$table}
6912
				ADD CONSTRAINT $cName
6913
				FOREIGN KEY $fkName ( `{$fieldNoQ}` ) REFERENCES `{$targetTableNoQ}`
6914
				(`{$targetFieldNoQ}`) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';');
6915
		} catch ( SQLException $e ) {
6916
			// Failure of fk-constraints is not a problem
6917
		}
6918
		return TRUE;
6919
	}
6920
6921
	/**
6922
	 * @see QueryWriter::sqlStateIn
6923
	 */
6924
	public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
6925
	{
6926
		$stateMap = array(
6927
			'42S02' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
6928
			'42S22' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
6929
			'23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
6930
		);
6931
6932
		if ( $state == 'HY000' && !empty( $extraDriverDetails[1] ) ) {
6933
			$driverCode = $extraDriverDetails[1];
6934
6935
			if ( $driverCode == '1205' && in_array( QueryWriter::C_SQLSTATE_LOCK_TIMEOUT, $list ) ) {
6936
				return TRUE;
6937
			}
6938
		}
6939
6940
		return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
6941
	}
6942
6943
	/**
6944
	 * @see QueryWriter::wipeAll
6945
	 */
6946
	public function wipeAll()
6947
	{
6948
		if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
6949
		$this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 0;' );
6950
6951
		foreach ( $this->getTables() as $t ) {
6952
			try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
6953
			try { $this->adapter->exec( "DROP VIEW IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
6954
		}
6955
6956
		$this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 1;' );
6957
	}
6958
}
6959
}
6960
6961
namespace RedBeanPHP\QueryWriter {
6962
6963
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
6964
use RedBeanPHP\QueryWriter as QueryWriter;
6965
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
6966
use RedBeanPHP\Adapter as Adapter;
6967
use RedBeanPHP\RedException\SQL as SQLException;
6968
6969
/**
6970
 * RedBeanPHP SQLiteWriter with support for SQLite types
6971
 * This is a QueryWriter class for RedBeanPHP.
6972
 * This QueryWriter provides support for the SQLite database platform.
6973
 *
6974
 * @file    RedBeanPHP/QueryWriter/SQLiteT.php
6975
 * @author  Gabor de Mooij and the RedBeanPHP Community
6976
 * @license BSD/GPLv2
6977
 *
6978
 * @copyright
6979
 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
6980
 * This source file is subject to the BSD/GPLv2 License that is bundled
6981
 * with this source code in the file license.txt.
6982
 */
6983
class SQLiteT extends AQueryWriter implements QueryWriter
6984
{
6985
	/**
6986
	 * Data types
6987
	 */
6988
	const C_DATATYPE_INTEGER   = 0;
6989
	const C_DATATYPE_NUMERIC   = 1;
6990
	const C_DATATYPE_TEXT      = 2;
6991
	const C_DATATYPE_SPECIFIED = 99;
6992
6993
	/**
6994
	 * @var DBAdapter
6995
	 */
6996
	protected $adapter;
6997
6998
	/**
6999
	 * @var string
7000
	 */
7001
	protected $quoteCharacter = '`';
7002
7003
	/**
7004
	 * Gets all information about a table (from a type).
7005
	 *
7006
	 * Format:
7007
	 * array(
7008
	 *    name => name of the table
7009
	 *    columns => array( name => datatype )
7010
	 *    indexes => array() raw index information rows from PRAGMA query
7011
	 *    keys => array() raw key information rows from PRAGMA query
7012
	 * )
7013
	 *
7014
	 * @param string $type type you want to get info of
7015
	 *
7016
	 * @return array
7017
	 */
7018
	protected function getTable( $type )
7019
	{
7020
		$tableName = $this->esc( $type, TRUE );
7021
		$columns   = $this->getColumns( $type );
7022
		$indexes   = $this->getIndexes( $type );
7023
		$keys      = $this->getKeyMapForType( $type );
7024
7025
		$table = array(
7026
			'columns' => $columns,
7027
			'indexes' => $indexes,
7028
			'keys' => $keys,
7029
			'name' => $tableName
7030
		);
7031
7032
		$this->tableArchive[$tableName] = $table;
7033
7034
		return $table;
7035
	}
7036
7037
	/**
7038
	 * Puts a table. Updates the table structure.
7039
	 * In SQLite we can't change columns, drop columns, change or add foreign keys so we
7040
	 * have a table-rebuild function. You simply load your table with getTable(), modify it and
7041
	 * then store it with putTable()...
7042
	 *
7043
	 * @param array $tableMap information array
7044
	 *
7045
	 * @return void
7046
	 */
7047
	protected function putTable( $tableMap )
7048
	{
7049
		$table = $tableMap['name'];
7050
		$q     = array();
7051
		$q[]   = "DROP TABLE IF EXISTS tmp_backup;";
7052
7053
		$oldColumnNames = array_keys( $this->getColumns( $table ) );
7054
7055
		foreach ( $oldColumnNames as $k => $v ) $oldColumnNames[$k] = "`$v`";
7056
7057
		$q[] = "CREATE TEMPORARY TABLE tmp_backup(" . implode( ",", $oldColumnNames ) . ");";
7058
		$q[] = "INSERT INTO tmp_backup SELECT * FROM `$table`;";
7059
		$q[] = "PRAGMA foreign_keys = 0 ";
7060
		$q[] = "DROP TABLE `$table`;";
7061
7062
		$newTableDefStr = '';
7063
		foreach ( $tableMap['columns'] as $column => $type ) {
7064
			if ( $column != 'id' ) {
7065
				$newTableDefStr .= ",`$column` $type";
7066
			}
7067
		}
7068
7069
		$fkDef = '';
7070
		foreach ( $tableMap['keys'] as $key ) {
7071
			$fkDef .= ", FOREIGN KEY(`{$key['from']}`)
7072
						 REFERENCES `{$key['table']}`(`{$key['to']}`)
7073
						 ON DELETE {$key['on_delete']} ON UPDATE {$key['on_update']}";
7074
		}
7075
7076
		$q[] = "CREATE TABLE `$table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT  $newTableDefStr  $fkDef );";
7077
7078
		foreach ( $tableMap['indexes'] as $name => $index ) {
7079
			if ( strpos( $name, 'UQ_' ) === 0 ) {
7080
				$cols = explode( '__', substr( $name, strlen( 'UQ_' . $table ) ) );
7081
				foreach ( $cols as $k => $v ) $cols[$k] = "`$v`";
7082
				$q[] = "CREATE UNIQUE INDEX $name ON `$table` (" . implode( ',', $cols ) . ")";
7083
			} else $q[] = "CREATE INDEX $name ON `$table` ({$index['name']}) ";
7084
		}
7085
7086
		$q[] = "INSERT INTO `$table` SELECT * FROM tmp_backup;";
7087
		$q[] = "DROP TABLE tmp_backup;";
7088
		$q[] = "PRAGMA foreign_keys = 1 ";
7089
7090
		foreach ( $q as $sq ) $this->adapter->exec( $sq );
7091
	}
7092
7093
	/**
7094
	 * Returns the an array describing the indexes for type $type.
7095
	 *
7096
	 * @param string $type type to describe indexes of
7097
	 *
7098
	 * @return array
7099
	 */
7100
	protected function getIndexes( $type )
7101
	{
7102
		$table   = $this->esc( $type, TRUE );
7103
		$indexes = $this->adapter->get( "PRAGMA index_list('$table')" );
7104
7105
		$indexInfoList = array();
7106
		foreach ( $indexes as $i ) {
7107
			$indexInfoList[$i['name']] = $this->adapter->getRow( "PRAGMA index_info('{$i['name']}') " );
7108
7109
			$indexInfoList[$i['name']]['unique'] = $i['unique'];
7110
		}
7111
7112
		return $indexInfoList;
7113
	}
7114
7115
	/**
7116
	 * Adds a foreign key to a type.
7117
	 * Note: cant put this in try-catch because that can hide the fact
7118
	 * that database has been damaged.
7119
	 *
7120
	 * @param  string  $type        type you want to modify table of
7121
	 * @param  string  $targetType  target type
7122
	 * @param  string  $field       field of the type that needs to get the fk
7123
	 * @param  string  $targetField field where the fk needs to point to
7124
	 * @param  integer $buildopt    0 = NO ACTION, 1 = ON DELETE CASCADE
7125
	 *
7126
	 * @return boolean
7127
	 */
7128
	protected function buildFK( $type, $targetType, $property, $targetProperty, $constraint = FALSE )
7129
	{
7130
		$table           = $this->esc( $type, TRUE );
7131
		$targetTable     = $this->esc( $targetType, TRUE );
7132
		$column          = $this->esc( $property, TRUE );
7133
		$targetColumn    = $this->esc( $targetProperty, TRUE );
7134
7135
		$tables = $this->getTables();
7136
		if ( !in_array( $targetTable, $tables ) ) return FALSE;
7137
7138
		if ( !is_null( $this->getForeignKeyForTypeProperty( $table, $column ) ) ) return FALSE;
7139
		$t = $this->getTable( $table );
7140
		$consSQL = ( $constraint ? 'CASCADE' : 'SET NULL' );
7141
		$label   = 'from_' . $column . '_to_table_' . $targetTable . '_col_' . $targetColumn;
7142
		$t['keys'][$label] = array(
7143
			'table'     => $targetTable,
7144
			'from'      => $column,
7145
			'to'        => $targetColumn,
7146
			'on_update' => $consSQL,
7147
			'on_delete' => $consSQL
7148
		);
7149
		$this->putTable( $t );
7150
		return TRUE;
7151
	}
7152
7153
	/**
7154
	 * @see AQueryWriter::getKeyMapForType
7155
	 */
7156
	protected function getKeyMapForType( $type )
7157
	{
7158
		$table = $this->esc( $type, TRUE );
7159
		$keys  = $this->adapter->get( "PRAGMA foreign_key_list('$table')" );
7160
		$keyInfoList = array();
7161
		foreach ( $keys as $k ) {
7162
			$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
7163
			$keyInfoList[$label] = array(
7164
				'name'          => $label,
7165
				'from'          => $k['from'],
7166
				'table'         => $k['table'],
7167
				'to'            => $k['to'],
7168
				'on_update'     => $k['on_update'],
7169
				'on_delete'     => $k['on_delete']
7170
			);
7171
		}
7172
		return $keyInfoList;
7173
	}
7174
7175
	/**
7176
	 * Constructor
7177
	 * Most of the time, you do not need to use this constructor,
7178
	 * since the facade takes care of constructing and wiring the
7179
	 * RedBeanPHP core objects. However if you would like to
7180
	 * assemble an OODB instance yourself, this is how it works:
7181
	 *
7182
	 * Usage:
7183
	 *
7184
	 * <code>
7185
	 * $database = new RPDO( $dsn, $user, $pass );
7186
	 * $adapter = new DBAdapter( $database );
7187
	 * $writer = new PostgresWriter( $adapter );
7188
	 * $oodb = new OODB( $writer, FALSE );
7189
	 * $bean = $oodb->dispense( 'bean' );
7190
	 * $bean->name = 'coffeeBean';
7191
	 * $id = $oodb->store( $bean );
7192
	 * $bean = $oodb->load( 'bean', $id );
7193
	 * </code>
7194
	 *
7195
	 * The example above creates the 3 RedBeanPHP core objects:
7196
	 * the Adapter, the Query Writer and the OODB instance and
7197
	 * wires them together. The example also demonstrates some of
7198
	 * the methods that can be used with OODB, as you see, they
7199
	 * closely resemble their facade counterparts.
7200
	 *
7201
	 * The wiring process: create an RPDO instance using your database
7202
	 * connection parameters. Create a database adapter from the RPDO
7203
	 * object and pass that to the constructor of the writer. Next,
7204
	 * create an OODB instance from the writer. Now you have an OODB
7205
	 * object.
7206
	 *
7207
	 * @param Adapter $adapter Database Adapter
7208
	 */
7209
	public function __construct( Adapter $adapter )
7210
	{
7211
		$this->typeno_sqltype = array(
7212
			SQLiteT::C_DATATYPE_INTEGER => 'INTEGER',
7213
			SQLiteT::C_DATATYPE_NUMERIC => 'NUMERIC',
7214
			SQLiteT::C_DATATYPE_TEXT    => 'TEXT',
7215
		);
7216
7217
		$this->sqltype_typeno = array();
7218
7219
		foreach ( $this->typeno_sqltype as $k => $v ) {
7220
			$this->sqltype_typeno[$v] = $k;
7221
		}
7222
7223
		$this->adapter = $adapter;
7224
		$this->adapter->setOption( 'setInitQuery', ' PRAGMA foreign_keys = 1 ' );
7225
	}
7226
7227
	/**
7228
	 * This method returns the datatype to be used for primary key IDS and
7229
	 * foreign keys. Returns one if the data type constants.
7230
	 *
7231
	 * @return integer $const data type to be used for IDS.
7232
	 */
7233
	public function getTypeForID()
7234
	{
7235
		return self::C_DATATYPE_INTEGER;
7236
	}
7237
7238
	/**
7239
	 * @see QueryWriter::scanType
7240
	 */
7241
	public function scanType( $value, $flagSpecial = FALSE )
7242
	{
7243
		$this->svalue = $value;
7244
7245
		if ( $value === NULL ) return self::C_DATATYPE_INTEGER;
7246
		if ( $value === INF ) return self::C_DATATYPE_TEXT;
7247
7248
		if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
7249
7250
		if ( $value === TRUE || $value === FALSE )  return self::C_DATATYPE_INTEGER;
7251
7252
		if ( is_numeric( $value ) && ( intval( $value ) == $value ) && $value < 2147483648 && $value > -2147483648 ) return self::C_DATATYPE_INTEGER;
7253
7254
		if ( ( is_numeric( $value ) && $value < 2147483648 && $value > -2147483648)
7255
			|| preg_match( '/\d{4}\-\d\d\-\d\d/', $value )
7256
			|| preg_match( '/\d{4}\-\d\d\-\d\d\s\d\d:\d\d:\d\d/', $value )
7257
		) {
7258
			return self::C_DATATYPE_NUMERIC;
7259
		}
7260
7261
		return self::C_DATATYPE_TEXT;
7262
	}
7263
7264
	/**
7265
	 * @see QueryWriter::addColumn
7266
	 */
7267
	public function addColumn( $table, $column, $type )
7268
	{
7269
		$column = $this->check( $column );
7270
		$table  = $this->check( $table );
7271
		$type   = $this->typeno_sqltype[$type];
7272
7273
		$this->adapter->exec( "ALTER TABLE `$table` ADD `$column` $type " );
7274
	}
7275
7276
	/**
7277
	 * @see QueryWriter::code
7278
	 */
7279
	public function code( $typedescription, $includeSpecials = FALSE )
7280
	{
7281
		$r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99 );
7282
7283
		return $r;
7284
	}
7285
7286
	/**
7287
	 * @see QueryWriter::widenColumn
7288
	 */
7289
	public function widenColumn( $type, $column, $datatype )
7290
	{
7291
		$t = $this->getTable( $type );
7292
7293
		$t['columns'][$column] = $this->typeno_sqltype[$datatype];
7294
7295
		$this->putTable( $t );
7296
	}
7297
7298
	/**
7299
	 * @see QueryWriter::getTables();
7300
	 */
7301
	public function getTables()
7302
	{
7303
		return $this->adapter->getCol( "SELECT name FROM sqlite_master
7304
			WHERE type='table' AND name!='sqlite_sequence';" );
7305
	}
7306
7307
	/**
7308
	 * @see QueryWriter::createTable
7309
	 */
7310
	public function createTable( $table )
7311
	{
7312
		$table = $this->esc( $table );
7313
7314
		$sql   = "CREATE TABLE $table ( id INTEGER PRIMARY KEY AUTOINCREMENT ) ";
7315
7316
		$this->adapter->exec( $sql );
7317
	}
7318
7319
	/**
7320
	 * @see QueryWriter::getColumns
7321
	 */
7322
	public function getColumns( $table )
7323
	{
7324
		$table      = $this->esc( $table, TRUE );
7325
7326
		$columnsRaw = $this->adapter->get( "PRAGMA table_info('$table')" );
7327
7328
		$columns    = array();
7329
		foreach ( $columnsRaw as $r ) $columns[$r['name']] = $r['type'];
7330
7331
		return $columns;
7332
	}
7333
7334
	/**
7335
	 * @see QueryWriter::addUniqueIndex
7336
	 */
7337
	public function addUniqueConstraint( $type, $properties )
7338
	{
7339
		$tableNoQ = $this->esc( $type, TRUE );
7340
		$name  = 'UQ_' . $this->esc( $type, TRUE ) . implode( '__', $properties );
7341
		$t     = $this->getTable( $type );
7342
		$t['indexes'][$name] = array( 'name' => $name );
7343
		try {
7344
			$this->putTable( $t );
7345
		} catch( SQLException $e ) {
7346
			return FALSE;
7347
		}
7348
		return TRUE;
7349
	}
7350
7351
	/**
7352
	 * @see QueryWriter::sqlStateIn
7353
	 */
7354
	public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
7355
	{
7356
		$stateMap = array(
7357
			'23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
7358
		);
7359
		if ( $state == 'HY000'
7360
		&& isset($extraDriverDetails[1])
7361
		&& $extraDriverDetails[1] == 1
7362
		&& ( in_array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, $list )
7363
			|| in_array( QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, $list )
7364
		)) {
7365
			return TRUE;
7366
		}
7367
		return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
7368
	}
7369
7370
	/**
7371
	 * @see QueryWriter::addIndex
7372
	 */
7373
	public function addIndex( $type, $name, $column )
7374
	{
7375
		$columns = $this->getColumns( $type );
7376
		if ( !isset( $columns[$column] ) ) return FALSE;
7377
7378
		$table  = $this->esc( $type );
7379
		$name   = preg_replace( '/\W/', '', $name );
7380
		$column = $this->esc( $column, TRUE );
7381
7382
		try {
7383
			$t = $this->getTable( $type );
7384
			$t['indexes'][$name] = array( 'name' => $column );
7385
			$this->putTable( $t );
7386
			return TRUE;
7387
		} catch( SQLException $exception ) {
7388
			return FALSE;
7389
		}
7390
	}
7391
7392
	/**
7393
	 * @see QueryWriter::wipe
7394
	 */
7395
	public function wipe( $type )
7396
	{
7397
		$table = $this->esc( $type );
7398
7399
		$this->adapter->exec( "DELETE FROM $table " );
7400
	}
7401
7402
	/**
7403
	 * @see QueryWriter::addFK
7404
	 */
7405
	public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
7406
	{
7407
		return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDep );
7408
	}
7409
7410
	/**
7411
	 * @see QueryWriter::wipeAll
7412
	 */
7413
	public function wipeAll()
7414
	{
7415
		if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
7416
		$this->adapter->exec( 'PRAGMA foreign_keys = 0 ' );
7417
7418
		foreach ( $this->getTables() as $t ) {
7419
			try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
7420
			try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
7421
		}
7422
7423
		$this->adapter->exec( 'PRAGMA foreign_keys = 1 ' );
7424
	}
7425
}
7426
}
7427
7428
namespace RedBeanPHP\QueryWriter {
7429
7430
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
7431
use RedBeanPHP\QueryWriter as QueryWriter;
7432
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
7433
use RedBeanPHP\Adapter as Adapter;
7434
use RedBeanPHP\RedException\SQL as SQLException;
7435
7436
/**
7437
 * RedBeanPHP PostgreSQL Query Writer.
7438
 * This is a QueryWriter class for RedBeanPHP.
7439
 * This QueryWriter provides support for the PostgreSQL database platform.
7440
 *
7441
 * @file    RedBeanPHP/QueryWriter/PostgreSQL.php
7442
 * @author  Gabor de Mooij and the RedBeanPHP Community
7443
 * @license BSD/GPLv2
7444
 *
7445
 * @copyright
7446
 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
7447
 * This source file is subject to the BSD/GPLv2 License that is bundled
7448
 * with this source code in the file license.txt.
7449
 */
7450
class PostgreSQL extends AQueryWriter implements QueryWriter
7451
{
7452
	/**
7453
	 * Data types
7454
	 */
7455
	const C_DATATYPE_INTEGER          = 0;
7456
	const C_DATATYPE_DOUBLE           = 1;
7457
	const C_DATATYPE_TEXT             = 3;
7458
	const C_DATATYPE_SPECIAL_DATE     = 80;
7459
	const C_DATATYPE_SPECIAL_DATETIME = 81;
7460
	const C_DATATYPE_SPECIAL_TIME     = 82; //TIME (no zone) only manual
7461
	const C_DATATYPE_SPECIAL_TIMEZ    = 83; //TIME (plus zone) only manual
7462
	const C_DATATYPE_SPECIAL_POINT    = 90;
7463
	const C_DATATYPE_SPECIAL_LSEG     = 91;
7464
	const C_DATATYPE_SPECIAL_CIRCLE   = 92;
7465
	const C_DATATYPE_SPECIAL_MONEY    = 93;
7466
	const C_DATATYPE_SPECIAL_POLYGON  = 94;
7467
	const C_DATATYPE_SPECIAL_MONEY2   = 95; //Numbers only money, i.e. fixed point numeric
7468
	const C_DATATYPE_SPECIAL_JSON     = 96; //JSON support (only manual)
7469
	const C_DATATYPE_SPECIFIED        = 99;
7470
7471
	/**
7472
	 * @var DBAdapter
7473
	 */
7474
	protected $adapter;
7475
7476
	/**
7477
	 * @var string
7478
	 */
7479
	protected $quoteCharacter = '"';
7480
7481
	/**
7482
	 * @var string
7483
	 */
7484
	protected $defaultValue = 'DEFAULT';
7485
7486
	/**
7487
	 * Returns the insert suffix SQL Snippet
7488
	 *
7489
	 * @param string $table table
7490
	 *
7491
	 * @return  string $sql SQL Snippet
7492
	 */
7493
	protected function getInsertSuffix( $table )
7494
	{
7495
		return 'RETURNING id ';
7496
	}
7497
7498
	/**
7499
	 * @see AQueryWriter::getKeyMapForType
7500
	 */
7501
	protected function getKeyMapForType( $type )
7502
	{
7503
		$table = $this->esc( $type, TRUE );
7504
		$keys = $this->adapter->get( '
7505
			SELECT
7506
			information_schema.key_column_usage.constraint_name AS "name",
7507
			information_schema.key_column_usage.column_name AS "from",
7508
			information_schema.constraint_table_usage.table_name AS "table",
7509
			information_schema.constraint_column_usage.column_name AS "to",
7510
			information_schema.referential_constraints.update_rule AS "on_update",
7511
			information_schema.referential_constraints.delete_rule AS "on_delete"
7512
				FROM information_schema.key_column_usage
7513
			INNER JOIN information_schema.constraint_table_usage
7514
				ON (
7515
					information_schema.key_column_usage.constraint_name = information_schema.constraint_table_usage.constraint_name
7516
					AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_table_usage.constraint_schema
7517
					AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_table_usage.constraint_catalog
7518
				)
7519
			INNER JOIN information_schema.constraint_column_usage
7520
				ON (
7521
					information_schema.key_column_usage.constraint_name = information_schema.constraint_column_usage.constraint_name
7522
					AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_column_usage.constraint_schema
7523
					AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_column_usage.constraint_catalog
7524
				)
7525
			INNER JOIN information_schema.referential_constraints
7526
				ON (
7527
					information_schema.key_column_usage.constraint_name = information_schema.referential_constraints.constraint_name
7528
					AND information_schema.key_column_usage.constraint_schema = information_schema.referential_constraints.constraint_schema
7529
					AND information_schema.key_column_usage.constraint_catalog = information_schema.referential_constraints.constraint_catalog
7530
				)
7531
			WHERE
7532
				information_schema.key_column_usage.table_catalog = current_database()
7533
				AND information_schema.key_column_usage.table_schema = ANY( current_schemas( FALSE ) )
7534
				AND information_schema.key_column_usage.table_name = ?
7535
		', array( $type ) );
7536
		$keyInfoList = array();
7537
		foreach ( $keys as $k ) {
7538
			$label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
7539
			$keyInfoList[$label] = array(
7540
				'name'          => $k['name'],
7541
				'from'          => $k['from'],
7542
				'table'         => $k['table'],
7543
				'to'            => $k['to'],
7544
				'on_update'     => $k['on_update'],
7545
				'on_delete'     => $k['on_delete']
7546
			);
7547
		}
7548
		return $keyInfoList;
7549
	}
7550
7551
	/**
7552
	 * Constructor
7553
	 * Most of the time, you do not need to use this constructor,
7554
	 * since the facade takes care of constructing and wiring the
7555
	 * RedBeanPHP core objects. However if you would like to
7556
	 * assemble an OODB instance yourself, this is how it works:
7557
	 *
7558
	 * Usage:
7559
	 *
7560
	 * <code>
7561
	 * $database = new RPDO( $dsn, $user, $pass );
7562
	 * $adapter = new DBAdapter( $database );
7563
	 * $writer = new PostgresWriter( $adapter );
7564
	 * $oodb = new OODB( $writer, FALSE );
7565
	 * $bean = $oodb->dispense( 'bean' );
7566
	 * $bean->name = 'coffeeBean';
7567
	 * $id = $oodb->store( $bean );
7568
	 * $bean = $oodb->load( 'bean', $id );
7569
	 * </code>
7570
	 *
7571
	 * The example above creates the 3 RedBeanPHP core objects:
7572
	 * the Adapter, the Query Writer and the OODB instance and
7573
	 * wires them together. The example also demonstrates some of
7574
	 * the methods that can be used with OODB, as you see, they
7575
	 * closely resemble their facade counterparts.
7576
	 *
7577
	 * The wiring process: create an RPDO instance using your database
7578
	 * connection parameters. Create a database adapter from the RPDO
7579
	 * object and pass that to the constructor of the writer. Next,
7580
	 * create an OODB instance from the writer. Now you have an OODB
7581
	 * object.
7582
	 *
7583
	 * @param Adapter $adapter Database Adapter
7584
	 */
7585
	public function __construct( Adapter $adapter )
7586
	{
7587
		$this->typeno_sqltype = array(
7588
			self::C_DATATYPE_INTEGER          => ' integer ',
7589
			self::C_DATATYPE_DOUBLE           => ' double precision ',
7590
			self::C_DATATYPE_TEXT             => ' text ',
7591
			self::C_DATATYPE_SPECIAL_DATE     => ' date ',
7592
			self::C_DATATYPE_SPECIAL_TIME     => ' time ',
7593
			self::C_DATATYPE_SPECIAL_TIMEZ    => ' time with time zone ',
7594
			self::C_DATATYPE_SPECIAL_DATETIME => ' timestamp without time zone ',
7595
			self::C_DATATYPE_SPECIAL_POINT    => ' point ',
7596
			self::C_DATATYPE_SPECIAL_LSEG     => ' lseg ',
7597
			self::C_DATATYPE_SPECIAL_CIRCLE   => ' circle ',
7598
			self::C_DATATYPE_SPECIAL_MONEY    => ' money ',
7599
			self::C_DATATYPE_SPECIAL_MONEY2   => ' numeric(10,2) ',
7600
			self::C_DATATYPE_SPECIAL_POLYGON  => ' polygon ',
7601
			self::C_DATATYPE_SPECIAL_JSON     => ' json ',
7602
		);
7603
7604
		$this->sqltype_typeno = array();
7605
7606
		foreach ( $this->typeno_sqltype as $k => $v ) {
7607
			$this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
7608
		}
7609
7610
		$this->adapter = $adapter;
7611
	}
7612
7613
	/**
7614
	 * This method returns the datatype to be used for primary key IDS and
7615
	 * foreign keys. Returns one if the data type constants.
7616
	 *
7617
	 * @return integer
7618
	 */
7619
	public function getTypeForID()
7620
	{
7621
		return self::C_DATATYPE_INTEGER;
7622
	}
7623
7624
	/**
7625
	 * @see QueryWriter::getTables
7626
	 */
7627
	public function getTables()
7628
	{
7629
		return $this->adapter->getCol( 'SELECT table_name FROM information_schema.tables WHERE table_schema = ANY( current_schemas( FALSE ) )' );
7630
	}
7631
7632
	/**
7633
	 * @see QueryWriter::createTable
7634
	 */
7635
	public function createTable( $table )
7636
	{
7637
		$table = $this->esc( $table );
7638
7639
		$this->adapter->exec( " CREATE TABLE $table (id SERIAL PRIMARY KEY); " );
7640
	}
7641
7642
	/**
7643
	 * @see QueryWriter::getColumns
7644
	 */
7645
	public function getColumns( $table )
7646
	{
7647
		$table      = $this->esc( $table, TRUE );
7648
7649
		$columnsRaw = $this->adapter->get( "SELECT column_name, data_type FROM information_schema.columns WHERE table_name='$table' AND table_schema = ANY( current_schemas( FALSE ) )" );
7650
7651
		$columns = array();
7652
		foreach ( $columnsRaw as $r ) {
7653
			$columns[$r['column_name']] = $r['data_type'];
7654
		}
7655
7656
		return $columns;
7657
	}
7658
7659
	/**
7660
	 * @see QueryWriter::scanType
7661
	 */
7662
	public function scanType( $value, $flagSpecial = FALSE )
7663
	{
7664
		$this->svalue = $value;
7665
7666
		if ( $value === INF ) return self::C_DATATYPE_TEXT;
7667
7668
		if ( $flagSpecial && $value ) {
7669
			if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
7670
				return PostgreSQL::C_DATATYPE_SPECIAL_DATE;
7671
			}
7672
7673
			if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d(\.\d{1,6})?$/', $value ) ) {
7674
				return PostgreSQL::C_DATATYPE_SPECIAL_DATETIME;
7675
			}
7676
7677
			if ( preg_match( '/^\([\d\.]+,[\d\.]+\)$/', $value ) ) {
7678
				return PostgreSQL::C_DATATYPE_SPECIAL_POINT;
7679
			}
7680
7681
			if ( preg_match( '/^\[\([\d\.]+,[\d\.]+\),\([\d\.]+,[\d\.]+\)\]$/', $value ) ) {
7682
				return PostgreSQL::C_DATATYPE_SPECIAL_LSEG;
7683
			}
7684
7685
			if ( preg_match( '/^\<\([\d\.]+,[\d\.]+\),[\d\.]+\>$/', $value ) ) {
7686
				return PostgreSQL::C_DATATYPE_SPECIAL_CIRCLE;
7687
			}
7688
7689
			if ( preg_match( '/^\((\([\d\.]+,[\d\.]+\),?)+\)$/', $value ) ) {
7690
				return PostgreSQL::C_DATATYPE_SPECIAL_POLYGON;
7691
			}
7692
7693
			if ( preg_match( '/^\-?(\$|€|¥|£)[\d,\.]+$/', $value ) ) {
7694
				return PostgreSQL::C_DATATYPE_SPECIAL_MONEY;
7695
			}
7696
7697
			if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
7698
				return PostgreSQL::C_DATATYPE_SPECIAL_MONEY2;
7699
			}
7700
			if ( self::$flagUseJSONColumns && $this->isJSON( $value ) ) {
7701
				return self::C_DATATYPE_SPECIAL_JSON;
7702
			}
7703
		}
7704
7705
		if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
7706
7707
		if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
7708
7709
		if ( $value === FALSE || $value === TRUE || $value === NULL || ( is_numeric( $value )
7710
				&& AQueryWriter::canBeTreatedAsInt( $value )
7711
				&& $value < 2147483648
7712
				&& $value > -2147483648 )
7713
		) {
7714
			return self::C_DATATYPE_INTEGER;
7715
		} elseif ( is_numeric( $value ) ) {
7716
			return self::C_DATATYPE_DOUBLE;
7717
		} else {
7718
			return self::C_DATATYPE_TEXT;
7719
		}
7720
	}
7721
7722
	/**
7723
	 * @see QueryWriter::code
7724
	 */
7725
	public function code( $typedescription, $includeSpecials = FALSE )
7726
	{
7727
		$r = ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99;
7728
7729
		if ( $includeSpecials ) return $r;
7730
7731
		if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
7732
			return self::C_DATATYPE_SPECIFIED;
7733
		}
7734
7735
		return $r;
7736
	}
7737
7738
	/**
7739
	 * @see QueryWriter::widenColumn
7740
	 */
7741
	public function widenColumn( $type, $column, $datatype )
7742
	{
7743
		$table   = $type;
7744
		$type    = $datatype;
7745
7746
		$table   = $this->esc( $table );
7747
		$column  = $this->esc( $column );
7748
7749
		$newtype = $this->typeno_sqltype[$type];
7750
7751
		$this->adapter->exec( "ALTER TABLE $table \n\t ALTER COLUMN $column TYPE $newtype " );
7752
	}
7753
7754
	/**
7755
	 * @see QueryWriter::addUniqueIndex
7756
	 */
7757
	public function addUniqueConstraint( $type, $properties )
7758
	{
7759
		$tableNoQ = $this->esc( $type, TRUE );
7760
		$columns = array();
7761
		foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
7762
		$table = $this->esc( $type );
7763
		sort( $columns ); //else we get multiple indexes due to order-effects
7764
		$name = "UQ_" . sha1( $table . implode( ',', $columns ) );
7765
		$sql = "ALTER TABLE {$table}
7766
                ADD CONSTRAINT $name UNIQUE (" . implode( ',', $columns ) . ")";
7767
		try {
7768
			$this->adapter->exec( $sql );
7769
		} catch( SQLException $e ) {
7770
			return FALSE;
7771
		}
7772
		return TRUE;
7773
	}
7774
7775
	/**
7776
	 * @see QueryWriter::sqlStateIn
7777
	 */
7778
	public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
7779
	{
7780
		$stateMap = array(
7781
			'42P01' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
7782
			'42703' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
7783
			'23505' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
7784
			'55P03' => QueryWriter::C_SQLSTATE_LOCK_TIMEOUT
7785
		);
7786
		return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
7787
	}
7788
7789
	/**
7790
	 * @see QueryWriter::addIndex
7791
	 */
7792
	public function addIndex( $type, $name, $property )
7793
	{
7794
		$table  = $this->esc( $type );
7795
		$name   = preg_replace( '/\W/', '', $name );
7796
		$column = $this->esc( $property );
7797
7798
		try {
7799
			$this->adapter->exec( "CREATE INDEX {$name} ON $table ({$column}) " );
7800
			return TRUE;
7801
		} catch ( SQLException $e ) {
7802
			return FALSE;
7803
		}
7804
	}
7805
7806
	/**
7807
	 * @see QueryWriter::addFK
7808
	 */
7809
	public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
7810
	{
7811
		$table = $this->esc( $type );
7812
		$targetTable = $this->esc( $targetType );
7813
		$field = $this->esc( $property );
7814
		$targetField = $this->esc( $targetProperty );
7815
		$tableNoQ = $this->esc( $type, TRUE );
7816
		$fieldNoQ = $this->esc( $property, TRUE );
7817
		if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
7818
		try{
7819
			$delRule = ( $isDep ? 'CASCADE' : 'SET NULL' );
7820
			$this->adapter->exec( "ALTER TABLE {$table}
7821
				ADD FOREIGN KEY ( {$field} ) REFERENCES  {$targetTable}
7822
				({$targetField}) ON DELETE {$delRule} ON UPDATE {$delRule} DEFERRABLE ;" );
7823
			return TRUE;
7824
		} catch ( SQLException $e ) {
7825
			return FALSE;
7826
		}
7827
	}
7828
7829
	/**
7830
	 * @see QueryWriter::wipeAll
7831
	 */
7832
	public function wipeAll()
7833
	{
7834
		if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
7835
		$this->adapter->exec( 'SET CONSTRAINTS ALL DEFERRED' );
7836
7837
		foreach ( $this->getTables() as $t ) {
7838
			$t = $this->esc( $t );
7839
			//Some plugins (PostGIS have unremovable tables/views), avoid exceptions.
7840
			try { $this->adapter->exec( "DROP TABLE IF EXISTS $t CASCADE " ); }catch( \Exception $e ) {}
7841
		}
7842
7843
		$this->adapter->exec( 'SET CONSTRAINTS ALL IMMEDIATE' );
7844
	}
7845
}
7846
}
7847
7848
namespace RedBeanPHP\QueryWriter {
7849
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
7850
use RedBeanPHP\QueryWriter as QueryWriter;
7851
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
7852
use RedBeanPHP\Adapter as Adapter;
7853
use RedBeanPHP\RedException\SQL as SQLException;
7854
7855
/**
7856
 * RedBeanPHP CUBRID Writer.
7857
 * This is a QueryWriter class for RedBeanPHP.
7858
 * This QueryWriter provides support for the CUBRID database platform.
7859
 *
7860
 * @file    RedBeanPHP/QueryWriter/CUBRID.php
7861
 * @author  Gabor de Mooij and the RedBeanPHP Community
7862
 * @license BSD/GPLv2
7863
 *
7864
 * @copyright
7865
 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
7866
 * This source file is subject to the BSD/GPLv2 License that is bundled
7867
 * with this source code in the file license.txt.
7868
 */
7869
class CUBRID extends AQueryWriter implements QueryWriter
7870
{
7871
	/**
7872
	 * Data types
7873
	 */
7874
	const C_DATATYPE_INTEGER          = 0;
7875
	const C_DATATYPE_DOUBLE           = 1;
7876
	const C_DATATYPE_STRING           = 2;
7877
	const C_DATATYPE_SPECIAL_DATE     = 80;
7878
	const C_DATATYPE_SPECIAL_DATETIME = 81;
7879
	const C_DATATYPE_SPECIFIED        = 99;
7880
7881
	/**
7882
	 * @var DBAdapter
7883
	 */
7884
	protected $adapter;
7885
7886
	/**
7887
	 * @var string
7888
	 */
7889
	protected $quoteCharacter = '`';
7890
7891
	/**
7892
	 * This method adds a foreign key from type and field to
7893
	 * target type and target field.
7894
	 * The foreign key is created without an action. On delete/update
7895
	 * no action will be triggered. The FK is only used to allow database
7896
	 * tools to generate pretty diagrams and to make it easy to add actions
7897
	 * later on.
7898
	 * This methods accepts a type and infers the corresponding table name.
7899
	 *
7900
	 * @param  string  $type           type that will have a foreign key field
7901
	 * @param  string  $targetType     points to this type
7902
	 * @param  string  $property       field that contains the foreign key value
7903
	 * @param  string  $targetProperty field where the fk points to
7904
	 * @param  boolean $isDep          is dependent
7905
	 *
7906
	 * @return bool
7907
	 */
7908
	protected function buildFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
7909
	{
7910
		$table           = $this->esc( $type );
7911
		$tableNoQ        = $this->esc( $type, TRUE );
7912
		$targetTable     = $this->esc( $targetType );
7913
		$targetTableNoQ  = $this->esc( $targetType, TRUE );
7914
		$column          = $this->esc( $property );
7915
		$columnNoQ       = $this->esc( $property, TRUE );
7916
		$targetColumn    = $this->esc( $targetProperty );
7917
		if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $columnNoQ ) ) ) return FALSE;
7918
		$needsToDropFK   = FALSE;
7919
		$casc = ( $isDep ? 'CASCADE' : 'SET NULL' );
7920
		$sql  = "ALTER TABLE $table ADD CONSTRAINT FOREIGN KEY($column) REFERENCES $targetTable($targetColumn) ON DELETE $casc ";
7921
		try {
7922
			$this->adapter->exec( $sql );
7923
		} catch( SQLException $e ) {
7924
			return FALSE;
7925
		}
7926
		return TRUE;
7927
	}
7928
7929
	/**
7930
	 * @see AQueryWriter::getKeyMapForType
7931
	 */
7932
	protected function getKeyMapForType( $type  )
7933
	{
7934
		$sqlCode = $this->adapter->get("SHOW CREATE TABLE `{$type}`");
7935
		if (!isset($sqlCode[0])) return array();
7936
		$matches = array();
7937
		preg_match_all( '/CONSTRAINT\s+\[([\w_]+)\]\s+FOREIGN\s+KEY\s+\(\[([\w_]+)\]\)\s+REFERENCES\s+\[([\w_]+)\](\s+ON\s+DELETE\s+(CASCADE|SET\sNULL|RESTRICT|NO\sACTION)\s+ON\s+UPDATE\s+(SET\sNULL|RESTRICT|NO\sACTION))?/', $sqlCode[0]['CREATE TABLE'], $matches );
7938
		$list = array();
7939
		if (!isset($matches[0])) return $list;
7940
		$max = count($matches[0]);
7941
		for($i = 0; $i < $max; $i++) {
7942
			$label = $this->makeFKLabel( $matches[2][$i], $matches[3][$i], 'id' );
7943
			$list[ $label ] = array(
7944
				'name' => $matches[1][$i],
7945
				'from' => $matches[2][$i],
7946
				'table' => $matches[3][$i],
7947
				'to' => 'id',
7948
				'on_update' => $matches[6][$i],
7949
				'on_delete' => $matches[5][$i]
7950
			);
7951
		}
7952
		return $list;
7953
	}
7954
7955
	/**
7956
	 * Constructor
7957
	 * Most of the time, you do not need to use this constructor,
7958
	 * since the facade takes care of constructing and wiring the
7959
	 * RedBeanPHP core objects. However if you would like to
7960
	 * assemble an OODB instance yourself, this is how it works:
7961
	 *
7962
	 * Usage:
7963
	 *
7964
	 * <code>
7965
	 * $database = new RPDO( $dsn, $user, $pass );
7966
	 * $adapter = new DBAdapter( $database );
7967
	 * $writer = new PostgresWriter( $adapter );
7968
	 * $oodb = new OODB( $writer, FALSE );
7969
	 * $bean = $oodb->dispense( 'bean' );
7970
	 * $bean->name = 'coffeeBean';
7971
	 * $id = $oodb->store( $bean );
7972
	 * $bean = $oodb->load( 'bean', $id );
7973
	 * </code>
7974
	 *
7975
	 * The example above creates the 3 RedBeanPHP core objects:
7976
	 * the Adapter, the Query Writer and the OODB instance and
7977
	 * wires them together. The example also demonstrates some of
7978
	 * the methods that can be used with OODB, as you see, they
7979
	 * closely resemble their facade counterparts.
7980
	 *
7981
	 * The wiring process: create an RPDO instance using your database
7982
	 * connection parameters. Create a database adapter from the RPDO
7983
	 * object and pass that to the constructor of the writer. Next,
7984
	 * create an OODB instance from the writer. Now you have an OODB
7985
	 * object.
7986
	 *
7987
	 * @param Adapter $adapter Database Adapter
7988
	 */
7989
	public function __construct( Adapter $adapter )
7990
	{
7991
		$this->typeno_sqltype = array(
7992
			CUBRID::C_DATATYPE_INTEGER          => ' INTEGER ',
7993
			CUBRID::C_DATATYPE_DOUBLE           => ' DOUBLE ',
7994
			CUBRID::C_DATATYPE_STRING           => ' STRING ',
7995
			CUBRID::C_DATATYPE_SPECIAL_DATE     => ' DATE ',
7996
			CUBRID::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
7997
		);
7998
7999
		$this->sqltype_typeno = array();
8000
8001
		foreach ( $this->typeno_sqltype as $k => $v ) {
8002
			$this->sqltype_typeno[trim( ( $v ) )] = $k;
8003
		}
8004
8005
		$this->sqltype_typeno['STRING(1073741823)'] = self::C_DATATYPE_STRING;
8006
8007
		$this->adapter = $adapter;
8008
	}
8009
8010
	/**
8011
	 * This method returns the datatype to be used for primary key IDS and
8012
	 * foreign keys. Returns one if the data type constants.
8013
	 *
8014
	 * @return integer
8015
	 */
8016
	public function getTypeForID()
8017
	{
8018
		return self::C_DATATYPE_INTEGER;
8019
	}
8020
8021
	/**
8022
	 * @see QueryWriter::getTables
8023
	 */
8024
	public function getTables()
8025
	{
8026
		$rows = $this->adapter->getCol( "SELECT class_name FROM db_class WHERE is_system_class = 'NO';" );
8027
8028
		return $rows;
8029
	}
8030
8031
	/**
8032
	 * @see QueryWriter::createTable
8033
	 */
8034
	public function createTable( $table )
8035
	{
8036
		$sql  = 'CREATE TABLE '
8037
			. $this->esc( $table )
8038
			. ' ("id" integer AUTO_INCREMENT, CONSTRAINT "pk_'
8039
			. $this->esc( $table, TRUE )
8040
			. '_id" PRIMARY KEY("id"))';
8041
8042
		$this->adapter->exec( $sql );
8043
	}
8044
8045
	/**
8046
	 * @see QueryWriter::getColumns
8047
	 */
8048
	public function getColumns( $table )
8049
	{
8050
		$table = $this->esc( $table );
8051
8052
		$columnsRaw = $this->adapter->get( "SHOW COLUMNS FROM $table" );
8053
8054
		$columns = array();
8055
		foreach ( $columnsRaw as $r ) {
8056
			$columns[$r['Field']] = $r['Type'];
8057
		}
8058
8059
		return $columns;
8060
	}
8061
8062
	/**
8063
	 * @see QueryWriter::scanType
8064
	 */
8065
	public function scanType( $value, $flagSpecial = FALSE )
8066
	{
8067
		$this->svalue = $value;
8068
8069
		if ( is_null( $value ) ) {
8070
			return self::C_DATATYPE_INTEGER;
8071
		}
8072
8073
		if ( $flagSpecial ) {
8074
			if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
8075
				return self::C_DATATYPE_SPECIAL_DATE;
8076
			}
8077
			if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
8078
				return self::C_DATATYPE_SPECIAL_DATETIME;
8079
			}
8080
		}
8081
8082
		$value = strval( $value );
8083
8084
		if ( !$this->startsWithZeros( $value ) ) {
8085
			if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= -2147483647 && $value <= 2147483647 ) {
8086
				return self::C_DATATYPE_INTEGER;
8087
			}
8088
			if ( is_numeric( $value ) ) {
8089
				return self::C_DATATYPE_DOUBLE;
8090
			}
8091
		}
8092
8093
		return self::C_DATATYPE_STRING;
8094
	}
8095
8096
	/**
8097
	 * @see QueryWriter::code
8098
	 */
8099
	public function code( $typedescription, $includeSpecials = FALSE )
8100
	{
8101
		$r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : self::C_DATATYPE_SPECIFIED );
8102
8103
		if ( $includeSpecials ) {
8104
			return $r;
8105
		}
8106
8107
		if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
8108
			return self::C_DATATYPE_SPECIFIED;
8109
		}
8110
8111
		return $r;
8112
	}
8113
8114
	/**
8115
	 * @see QueryWriter::addColumn
8116
	 */
8117
	public function addColumn( $type, $column, $field )
8118
	{
8119
		$table  = $type;
8120
		$type   = $field;
8121
8122
		$table  = $this->esc( $table );
8123
		$column = $this->esc( $column );
8124
8125
		$type   = array_key_exists( $type, $this->typeno_sqltype ) ? $this->typeno_sqltype[$type] : '';
8126
8127
		$this->adapter->exec( "ALTER TABLE $table ADD COLUMN $column $type " );
8128
	}
8129
8130
	/**
8131
	 * @see QueryWriter::addUniqueIndex
8132
	 */
8133
	public function addUniqueConstraint( $type, $properties )
8134
	{
8135
		$tableNoQ = $this->esc( $type, TRUE );
8136
		$columns = array();
8137
		foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
8138
		$table = $this->esc( $type );
8139
		sort( $columns ); // else we get multiple indexes due to order-effects
8140
		$name = 'UQ_' . sha1( implode( ',', $columns ) );
8141
		$sql = "ALTER TABLE $table ADD CONSTRAINT UNIQUE $name (" . implode( ',', $columns ) . ")";
8142
		try {
8143
			$this->adapter->exec( $sql );
8144
		} catch( SQLException $e ) {
8145
			return FALSE;
8146
		}
8147
		return TRUE;
8148
	}
8149
8150
	/**
8151
	 * @see QueryWriter::sqlStateIn
8152
	 */
8153
	public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
8154
	{
8155
		return ( $state == 'HY000' ) ? ( count( array_diff( array(
8156
				QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
8157
				QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
8158
				QueryWriter::C_SQLSTATE_NO_SUCH_TABLE
8159
			), $list ) ) !== 3 ) : FALSE;
8160
	}
8161
8162
	/**
8163
	 * @see QueryWriter::addIndex
8164
	 */
8165
	public function addIndex( $type, $name, $column )
8166
	{
8167
		try {
8168
			$table  = $this->esc( $type );
8169
			$name   = preg_replace( '/\W/', '', $name );
8170
			$column = $this->esc( $column );
8171
			$this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
8172
			return TRUE;
8173
		} catch ( SQLException $e ) {
8174
			return FALSE;
8175
		}
8176
	}
8177
8178
	/**
8179
	 * @see QueryWriter::addFK
8180
	 */
8181
	public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
8182
	{
8183
		return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDependent );
8184
	}
8185
8186
	/**
8187
	 * @see QueryWriter::wipeAll
8188
	 */
8189
	public function wipeAll()
8190
	{
8191
		if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
8192
		foreach ( $this->getTables() as $t ) {
8193
			foreach ( $this->getKeyMapForType( $t ) as $k ) {
8194
				$this->adapter->exec( "ALTER TABLE \"$t\" DROP FOREIGN KEY \"{$k['name']}\"" );
8195
			}
8196
		}
8197
		foreach ( $this->getTables() as $t ) {
8198
			$this->adapter->exec( "DROP TABLE \"$t\"" );
8199
		}
8200
	}
8201
8202
	/**
8203
	 * @see QueryWriter::esc
8204
	 */
8205
	public function esc( $dbStructure, $noQuotes = FALSE )
8206
	{
8207
		return parent::esc( strtolower( $dbStructure ), $noQuotes );
8208
	}
8209
8210
	/**
8211
	 * @see QueryWriter::inferFetchType
8212
	 */
8213
	public function inferFetchType( $type, $property )
8214
	{
8215
		$table = $this->esc( $type, TRUE );
8216
		$field = $this->esc( $property, TRUE ) . '_id';
8217
		$keys = $this->getKeyMapForType( $table );
8218
8219
		foreach( $keys as $key ) {
8220
			if (
8221
				$key['from'] === $field
8222
			) return $key['table'];
8223
		}
8224
		return NULL;
8225
	}
8226
}
8227
}
8228
8229
namespace RedBeanPHP {
8230
8231
/**
8232
 * RedBean\Exception Base.
8233
 * Represents the base class for RedBeanPHP\Exceptions.
8234
 *
8235
 * @file    RedBeanPHP/Exception.php
8236
 * @author  Gabor de Mooij and the RedBeanPHP Community
8237
 * @license BSD/GPLv2
8238
 *
8239
 * @copyright
8240
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
8241
 * This source file is subject to the BSD/GPLv2 License that is bundled
8242
 * with this source code in the file license.txt.
8243
 */
8244
class RedException extends \Exception
8245
{
8246
}
8247
}
8248
8249
namespace RedBeanPHP\RedException {
8250
8251
use RedBeanPHP\RedException as RedException;
8252
8253
/**
8254
 * SQL Exception.
8255
 * Represents a generic database exception independent of the underlying driver.
8256
 *
8257
 * @file       RedBeanPHP/RedException/SQL.php
8258
 * @author     Gabor de Mooij and the RedBeanPHP Community
8259
 * @license    BSD/GPLv2
8260
 *
8261
 * @copyright
8262
 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
8263
 * This source file is subject to the BSD/GPLv2 License that is bundled
8264
 * with this source code in the file license.txt.
8265
 */
8266
class SQL extends RedException
8267
{
8268
	/**
8269
	 * @var string
8270
	 */
8271
	private $sqlState;
8272
8273
	/**
8274
	 * @var array
8275
	 */
8276
	private $driverDetails = array();
8277
8278
	/**
8279
	 * @return array
8280
	 */
8281
	public function getDriverDetails()
8282
	{
8283
		return $this->driverDetails;
8284
	}
8285
8286
	/**
8287
	 * @param array $driverDetails
8288
	 */
8289
	public function setDriverDetails($driverDetails)
8290
	{
8291
		$this->driverDetails = $driverDetails;
8292
	}
8293
8294
	/**
8295
	 * Returns an ANSI-92 compliant SQL state.
8296
	 *
8297
	 * @return string
8298
	 */
8299
	public function getSQLState()
8300
	{
8301
		return $this->sqlState;
8302
	}
8303
8304
	/**
8305
	 * Returns the raw SQL STATE, possibly compliant with
8306
	 * ANSI SQL error codes - but this depends on database driver.
8307
	 *
8308
	 * @param string $sqlState SQL state error code
8309
	 *
8310
	 * @return void
8311
	 */
8312
	public function setSQLState( $sqlState )
8313
	{
8314
		$this->sqlState = $sqlState;
8315
	}
8316
8317
	/**
8318
	 * To String prints both code and SQL state.
8319
	 *
8320
	 * @return string
8321
	 */
8322
	public function __toString()
8323
	{
8324
		return '[' . $this->getSQLState() . '] - ' . $this->getMessage()."\n".
8325
				'trace: ' . $this->getTraceAsString();
8326
	}
8327
}
8328
}
8329
8330
namespace RedBeanPHP {
8331
8332
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
8333
use RedBeanPHP\QueryWriter as QueryWriter;
8334
use RedBeanPHP\BeanHelper as BeanHelper;
8335
use RedBeanPHP\RedException\SQL as SQLException;
8336
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
8337
use RedBeanPHP\Cursor as Cursor;
8338
use RedBeanPHP\Cursor\NullCursor as NullCursor;
8339
8340
/**
8341
 * Abstract Repository.
8342
 *
8343
 * OODB manages two repositories, a fluid one that
8344
 * adjust the database schema on-the-fly to accomodate for
8345
 * new bean types (tables) and new properties (columns) and
8346
 * a frozen one for use in a production environment. OODB
8347
 * allows you to swap the repository instances using the freeze()
8348
 * method.
8349
 *
8350
 * @file    RedBeanPHP/Repository.php
8351
 * @author  Gabor de Mooij and the RedBeanPHP community
8352
 * @license BSD/GPLv2
8353
 *
8354
 * @copyright
8355
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
8356
 * This source file is subject to the BSD/GPLv2 License that is bundled
8357
 * with this source code in the file license.txt.
8358
 */
8359
abstract class Repository
8360
{
8361
	/**
8362
	 * @var array
8363
	 */
8364
	protected $stash = NULL;
8365
8366
	/*
8367
	 * @var integer
8368
	 */
8369
	protected $nesting = 0;
8370
8371
	/**
8372
	 * @var DBAdapter
8373
	 */
8374
	protected $writer;
8375
8376
	/**
8377
	 * @var boolean
8378
	 */
8379
	protected $partialBeans = FALSE;
8380
8381
	/**
8382
	 * Toggles 'partial bean mode'. If this mode has been
8383
	 * selected the repository will only update the fields of a bean that
8384
	 * have been changed rather than the entire bean.
8385
	 * Pass the value TRUE to select 'partial mode' for all beans.
8386
	 * Pass the value FALSE to disable 'partial mode'.
8387
	 * Pass an array of bean types if you wish to use partial mode only
8388
	 * for some types.
8389
	 * This method will return the previous value.
8390
	 *
8391
	 * @param boolean|array $yesNoBeans List of type names or 'all'
8392
	 *
8393
	 * @return mixed
8394
	 */
8395
	public function usePartialBeans( $yesNoBeans )
8396
	{
8397
		$oldValue = $this->partialBeans;
8398
		$this->partialBeans = $yesNoBeans;
8399
		return $oldValue;
8400
	}
8401
8402
	/**
8403
	 * Fully processes a bean and updates the associated records in the database.
8404
	 * First the bean properties will be grouped as 'embedded' bean,
8405
	 * addition, deleted 'trash can' or residue. Next, the different groups
8406
	 * of beans will be processed accordingly and the reference bean (i.e.
8407
	 * the one that was passed to the method as an argument) will be stored.
8408
	 * Each type of list (own/shared) has 3 bean processors: 
8409
	 *
8410
	 * - trashCanProcessor : removes the bean or breaks its association with the current bean
8411
	 * - additionProcessor : associates the bean with the current one
8412
	 * - residueProcessor  : manages beans in lists that 'remain' but may need to be updated
8413
	 * 
8414
	 * This method first groups the beans and then calls the
8415
	 * internal processing methods.
8416
	 *
8417
	 * @param OODBBean $bean bean to process
8418
	 *
8419
	 * @return void
8420
	 */
8421
	protected function storeBeanWithLists( OODBBean $bean )
8422
	{
8423
		$sharedAdditions = $sharedTrashcan = $sharedresidue = $sharedItems = $ownAdditions = $ownTrashcan = $ownresidue = $embeddedBeans = array(); //Define groups
8424
		foreach ( $bean as $property => $value ) {
8425
			$value = ( $value instanceof SimpleModel ) ? $value->unbox() : $value;
8426
			if ( $value instanceof OODBBean ) {
8427
				$this->processEmbeddedBean( $embeddedBeans, $bean, $property, $value );
8428
				$bean->setMeta("sys.typeof.{$property}", $value->getMeta('type'));
8429
			} elseif ( is_array( $value ) ) {
8430
				foreach($value as &$item) {
8431
					$item = ( $item instanceof SimpleModel ) ? $item->unbox() : $item;
8432
				}
8433
				$originals = $bean->moveMeta( 'sys.shadow.' . $property, array() );
8434
				if ( strpos( $property, 'own' ) === 0 ) {
8435
					list( $ownAdditions, $ownTrashcan, $ownresidue ) = $this->processGroups( $originals, $value, $ownAdditions, $ownTrashcan, $ownresidue );
8436
					$listName = lcfirst( substr( $property, 3 ) );
8437
					if ($bean->moveMeta( 'sys.exclusive-'.  $listName ) ) {
8438
						OODBBean::setMetaAll( $ownTrashcan, 'sys.garbage', TRUE );
8439
						OODBBean::setMetaAll( $ownAdditions, 'sys.buildcommand.fkdependson', $bean->getMeta( 'type' ) );
8440
					}
8441
					unset( $bean->$property );
8442
				} elseif ( strpos( $property, 'shared' ) === 0 ) {
8443
					list( $sharedAdditions, $sharedTrashcan, $sharedresidue ) = $this->processGroups( $originals, $value, $sharedAdditions, $sharedTrashcan, $sharedresidue );
8444
					unset( $bean->$property );
8445
				}
8446
			}
8447
		}
8448
		$this->storeBean( $bean );
8449
		$this->processTrashcan( $bean, $ownTrashcan );
8450
		$this->processAdditions( $bean, $ownAdditions );
8451
		$this->processResidue( $ownresidue );
8452
		$this->processSharedTrashcan( $bean, $sharedTrashcan );
8453
		$this->processSharedAdditions( $bean, $sharedAdditions );
8454
		$this->processSharedResidue( $bean, $sharedresidue );
8455
	}
8456
8457
	/**
8458
	 * Process groups. Internal function. Processes different kind of groups for
8459
	 * storage function. Given a list of original beans and a list of current beans,
8460
	 * this function calculates which beans remain in the list (residue), which
8461
	 * have been deleted (are in the trashcan) and which beans have been added
8462
	 * (additions).
8463
	 *
8464
	 * @param  array $originals originals
8465
	 * @param  array $current   the current beans
8466
	 * @param  array $additions beans that have been added
8467
	 * @param  array $trashcan  beans that have been deleted
8468
	 * @param  array $residue   beans that have been left untouched
8469
	 *
8470
	 * @return array
8471
	 */
8472
	protected function processGroups( $originals, $current, $additions, $trashcan, $residue )
8473
	{
8474
		return array(
8475
			array_merge( $additions, array_diff( $current, $originals ) ),
8476
			array_merge( $trashcan, array_diff( $originals, $current ) ),
8477
			array_merge( $residue, array_intersect( $current, $originals ) )
8478
		);
8479
	}
8480
8481
	/**
8482
	 * Processes a list of beans from a bean.
8483
	 * A bean may contain lists. This
8484
	 * method handles shared addition lists; i.e.
8485
	 * the $bean->sharedObject properties.
8486
	 * Shared beans will be associated with eachother using the
8487
	 * Association Manager.
8488
	 *
8489
	 * @param OODBBean $bean            the bean
8490
	 * @param array    $sharedAdditions list with shared additions
8491
	 *
8492
	 * @return void
8493
	 */
8494
	protected function processSharedAdditions( $bean, $sharedAdditions )
8495
	{
8496
		foreach ( $sharedAdditions as $addition ) {
8497
			if ( $addition instanceof OODBBean ) {
8498
				$this->oodb->getAssociationManager()->associate( $addition, $bean );
8499
			} else {
8500
				throw new RedException( 'Array may only contain OODBBeans' );
8501
			}
8502
		}
8503
	}
8504
8505
	/**
8506
	 * Processes a list of beans from a bean.
8507
	 * A bean may contain lists. This
8508
	 * method handles own lists; i.e.
8509
	 * the $bean->ownObject properties.
8510
	 * A residue is a bean in an own-list that stays
8511
	 * where it is. This method checks if there have been any
8512
	 * modification to this bean, in that case
8513
	 * the bean is stored once again, otherwise the bean will be left untouched.
8514
	 *
8515
	 * @param array    $ownresidue list to process
8516
	 *
8517
	 * @return void
8518
	 */
8519
	protected function processResidue( $ownresidue )
8520
	{
8521
		foreach ( $ownresidue as $residue ) {
8522
			if ( $residue->getMeta( 'tainted' ) ) {
8523
				$this->store( $residue );
8524
			}
8525
		}
8526
	}
8527
8528
	/**
8529
	 * Processes a list of beans from a bean. A bean may contain lists. This
8530
	 * method handles own lists; i.e. the $bean->ownObject properties.
8531
	 * A trash can bean is a bean in an own-list that has been removed
8532
	 * (when checked with the shadow). This method
8533
	 * checks if the bean is also in the dependency list. If it is the bean will be removed.
8534
	 * If not, the connection between the bean and the owner bean will be broken by
8535
	 * setting the ID to NULL.
8536
	 *
8537
	 * @param OODBBean $bean bean   to process
8538
	 * @param array    $ownTrashcan list to process
8539
	 *
8540
	 * @return void
8541
	 */
8542
	protected function processTrashcan( $bean, $ownTrashcan )
8543
	{
8544
		foreach ( $ownTrashcan as $trash ) {
8545
8546
			$myFieldLink = $bean->getMeta( 'type' ) . '_id';
8547
			$alias = $bean->getMeta( 'sys.alias.' . $trash->getMeta( 'type' ) );
8548
			if ( $alias ) $myFieldLink = $alias . '_id';
8549
8550
			if ( $trash->getMeta( 'sys.garbage' ) === TRUE ) {
8551
				$this->trash( $trash );
8552
			} else {
8553
				$trash->$myFieldLink = NULL;
8554
				$this->store( $trash );
8555
			}
8556
		}
8557
	}
8558
8559
	/**
8560
	 * Unassociates the list items in the trashcan.
8561
	 * This bean processor processes the beans in the shared trash can.
8562
	 * This group of beans has been deleted from a shared list.
8563
	 * The affected beans will no longer be associated with the bean
8564
	 * that contains the shared list.
8565
	 *
8566
	 * @param OODBBean $bean           bean to process
8567
	 * @param array    $sharedTrashcan list to process
8568
	 *
8569
	 * @return void
8570
	 */
8571
	protected function processSharedTrashcan( $bean, $sharedTrashcan )
8572
	{
8573
		foreach ( $sharedTrashcan as $trash ) {
8574
			$this->oodb->getAssociationManager()->unassociate( $trash, $bean );
8575
		}
8576
	}
8577
8578
	/**
8579
	 * Stores all the beans in the residue group.
8580
	 * This bean processor processes the beans in the shared residue
8581
	 * group. This group of beans 'remains' in the list but might need
8582
	 * to be updated or synced. The affected beans will be stored
8583
	 * to perform the required database queries.
8584
	 *
8585
	 * @param OODBBean $bean          bean to process
8586
	 * @param array    $sharedresidue list to process
8587
	 *
8588
	 * @return void
8589
	 */
8590
	protected function processSharedResidue( $bean, $sharedresidue )
8591
	{
8592
		foreach ( $sharedresidue as $residue ) {
8593
			$this->store( $residue );
8594
		}
8595
	}
8596
8597
	/**
8598
	 * Determines whether the bean has 'loaded lists' or
8599
	 * 'loaded embedded beans' that need to be processed
8600
	 * by the store() method.
8601
	 *
8602
	 * @param OODBBean $bean bean to be examined
8603
	 *
8604
	 * @return boolean
8605
	 */
8606
	protected function hasListsOrObjects( OODBBean $bean )
8607
	{
8608
		$processLists = FALSE;
8609
		foreach ( $bean as $value ) {
8610
			if ( is_array( $value ) || is_object( $value ) ) {
8611
				$processLists = TRUE;
8612
				break;
8613
			}
8614
		}
8615
8616
		return $processLists;
8617
	}
8618
8619
	/**
8620
	 * Converts an embedded bean to an ID, removes the bean property and
8621
	 * stores the bean in the embedded beans array. The id will be
8622
	 * assigned to the link field property, i.e. 'bean_id'.
8623
	 *
8624
	 * @param array    $embeddedBeans destination array for embedded bean
8625
	 * @param OODBBean $bean          target bean to process
8626
	 * @param string   $property      property that contains the embedded bean
8627
	 * @param OODBBean $value         embedded bean itself
8628
	 *
8629
	 * @return void
8630
	 */
8631
	protected function processEmbeddedBean( &$embeddedBeans, $bean, $property, OODBBean $value )
8632
	{
8633
		$linkField = $property . '_id';
8634
		if ( !$value->id || $value->getMeta( 'tainted' ) ) {
8635
			$this->store( $value );
8636
		}
8637
		$id = $value->id;
8638
		if ($bean->$linkField != $id) $bean->$linkField = $id;
8639
		$bean->setMeta( 'cast.' . $linkField, 'id' );
8640
		$embeddedBeans[$linkField] = $value;
8641
		unset( $bean->$property );
8642
	}
8643
8644
	/**
8645
	 * Constructor, requires a query writer and OODB.
8646
	 * Creates a new instance of the bean respository class.
8647
	 *
8648
	 * @param OODB        $oodb   instance of object database
8649
	 * @param QueryWriter $writer the Query Writer to use for this repository
8650
	 *
8651
	 * @return void
8652
	 */
8653
	public function __construct( OODB $oodb, QueryWriter $writer )
8654
	{
8655
		$this->writer = $writer;
8656
		$this->oodb = $oodb;
8657
	}
8658
8659
	/**
8660
	 * Checks whether a OODBBean bean is valid.
8661
	 * If the type is not valid or the ID is not valid it will
8662
	 * throw an exception: Security. To be valid a bean
8663
	 * must abide to the following rules:
8664
	 *
8665
	 * - It must have an primary key id property named: id
8666
	 * - It must have a type
8667
	 * - The type must conform to the RedBeanPHP naming policy
8668
	 * - All properties must be valid
8669
	 * - All values must be valid
8670
	 *
8671
	 * @param OODBBean $bean the bean that needs to be checked
8672
	 *
8673
	 * @return void
8674
	 */
8675
	public function check( OODBBean $bean )
8676
	{
8677
		//Is all meta information present?
8678
		if ( !isset( $bean->id ) ) {
8679
			throw new RedException( 'Bean has incomplete Meta Information id ' );
8680
		}
8681
		if ( !( $bean->getMeta( 'type' ) ) ) {
8682
			throw new RedException( 'Bean has incomplete Meta Information II' );
8683
		}
8684
		//Pattern of allowed characters
8685
		$pattern = '/[^a-z0-9_]/i';
8686
		//Does the type contain invalid characters?
8687
		if ( preg_match( $pattern, $bean->getMeta( 'type' ) ) ) {
8688
			throw new RedException( 'Bean Type is invalid' );
8689
		}
8690
		//Are the properties and values valid?
8691
		foreach ( $bean as $prop => $value ) {
8692
			if (
8693
				is_array( $value )
8694
				|| ( is_object( $value ) )
8695
			) {
8696
				throw new RedException( "Invalid Bean value: property $prop" );
8697
			} else if (
8698
				strlen( $prop ) < 1
8699
				|| preg_match( $pattern, $prop )
8700
			) {
8701
				throw new RedException( "Invalid Bean property: property $prop" );
8702
			}
8703
		}
8704
	}
8705
8706
	/**
8707
	 * Searches the database for a bean that matches conditions $conditions and sql $addSQL
8708
	 * and returns an array containing all the beans that have been found.
8709
	 *
8710
	 * Conditions need to take form:
8711
	 *
8712
	 * <code>
8713
	 * array(
8714
	 *    'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
8715
	 *    'PROPERTY' => array( POSSIBLE VALUES... )
8716
	 * );
8717
	 * </code>
8718
	 *
8719
	 * All conditions are glued together using the AND-operator, while all value lists
8720
	 * are glued using IN-operators thus acting as OR-conditions.
8721
	 *
8722
	 * Note that you can use property names; the columns will be extracted using the
8723
	 * appropriate bean formatter.
8724
	 *
8725
	 * @param string $type       type of beans you are looking for
8726
	 * @param array  $conditions list of conditions
8727
	 * @param string $sql        SQL to be used in query
8728
	 * @param array  $bindings   whether you prefer to use a WHERE clause or not (TRUE = not)
8729
	 *
8730
	 * @return array
8731
	 */
8732
	public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
8733
	{
8734
		//for backward compatibility, allow mismatch arguments:
8735
		if ( is_array( $sql ) ) {
8736
			if ( isset( $sql[1] ) ) {
8737
				$bindings = $sql[1];
8738
			}
8739
			$sql = $sql[0];
8740
		}
8741
		try {
8742
			$beans = $this->convertToBeans( $type, $this->writer->queryRecord( $type, $conditions, $sql, $bindings ) );
8743
8744
			return $beans;
8745
		} catch ( SQLException $exception ) {
8746
			$this->handleException( $exception );
8747
		}
8748
8749
		return array();
8750
	}
8751
8752
	/**
8753
	 * Finds a BeanCollection.
8754
	 * Given a type, an SQL snippet and optionally some parameter bindings
8755
	 * this methods returns a BeanCollection for your query.
8756
	 *
8757
	 * The BeanCollection represents a collection of beans and
8758
	 * makes it possible to use database cursors. The BeanCollection
8759
	 * has a method next() to obtain the first, next and last bean
8760
	 * in the collection. The BeanCollection does not implement the array
8761
	 * interface nor does it try to act like an array because it cannot go
8762
	 * backward or rewind itself.
8763
	 *
8764
	 * @param string $type     type of beans you are looking for
8765
	 * @param string $sql      SQL to be used in query
8766
	 * @param array  $bindings whether you prefer to use a WHERE clause or not (TRUE = not)
8767
	 *
8768
	 * @return BeanCollection
8769
	 */
8770
	public function findCollection( $type, $sql, $bindings = array() )
8771
	{
8772
		try {
8773
			$cursor = $this->writer->queryRecordWithCursor( $type, $sql, $bindings );
8774
			return new BeanCollection( $type, $this, $cursor );
8775
		} catch ( SQLException $exception ) {
8776
			$this->handleException( $exception );
8777
		}
8778
		return new BeanCollection( $type, $this, new NullCursor );
8779
	}
8780
8781
	/**
8782
	 * Stores a bean in the database. This method takes a
8783
	 * OODBBean Bean Object $bean and stores it
8784
	 * in the database. If the database schema is not compatible
8785
	 * with this bean and RedBean runs in fluid mode the schema
8786
	 * will be altered to store the bean correctly.
8787
	 * If the database schema is not compatible with this bean and
8788
	 * RedBean runs in frozen mode it will throw an exception.
8789
	 * This function returns the primary key ID of the inserted
8790
	 * bean.
8791
	 *
8792
	 * The return value is an integer if possible. If it is not possible to
8793
	 * represent the value as an integer a string will be returned. We use
8794
	 * explicit casts instead of functions to preserve performance
8795
	 * (0.13 vs 0.28 for 10000 iterations on Core i3).
8796
	 *
8797
	 * @param OODBBean|SimpleModel $bean bean to store
8798
	 *
8799
	 * @return integer|string
8800
	 */
8801
	public function store( $bean )
8802
	{
8803
		$processLists = $this->hasListsOrObjects( $bean );
8804
		if ( !$processLists && !$bean->getMeta( 'tainted' ) ) {
8805
			return $bean->getID(); //bail out!
8806
		}
8807
		$this->oodb->signal( 'update', $bean );
8808
		$processLists = $this->hasListsOrObjects( $bean ); //check again, might have changed by model!
8809
		if ( $processLists ) {
8810
			$this->storeBeanWithLists( $bean );
8811
		} else {
8812
			$this->storeBean( $bean );
8813
		}
8814
		$this->oodb->signal( 'after_update', $bean );
8815
8816
		return ( (string) $bean->id === (string) (int) $bean->id ) ? (int) $bean->id : (string) $bean->id;
8817
	}
8818
8819
	/**
8820
	 * Returns an array of beans. Pass a type and a series of ids and
8821
	 * this method will bring you the corresponding beans.
8822
	 *
8823
	 * important note: Because this method loads beans using the load()
8824
	 * function (but faster) it will return empty beans with ID 0 for
8825
	 * every bean that could not be located. The resulting beans will have the
8826
	 * passed IDs as their keys.
8827
	 *
8828
	 * @param string $type type of beans
8829
	 * @param array  $ids  ids to load
8830
	 *
8831
	 * @return array
8832
	 */
8833
	public function batch( $type, $ids )
8834
	{
8835
		if ( !$ids ) {
8836
			return array();
8837
		}
8838
		$collection = array();
8839
		try {
8840
			$rows = $this->writer->queryRecord( $type, array( 'id' => $ids ) );
8841
		} catch ( SQLException $e ) {
8842
			$this->handleException( $e );
8843
			$rows = FALSE;
8844
		}
8845
		$this->stash[$this->nesting] = array();
8846
		if ( !$rows ) {
8847
			return array();
8848
		}
8849
		foreach ( $rows as $row ) {
8850
			$this->stash[$this->nesting][$row['id']] = $row;
8851
		}
8852
		foreach ( $ids as $id ) {
8853
			$collection[$id] = $this->load( $type, $id );
8854
		}
8855
		$this->stash[$this->nesting] = NULL;
8856
8857
		return $collection;
8858
	}
8859
8860
	/**
8861
	 * This is a convenience method; it converts database rows
8862
	 * (arrays) into beans. Given a type and a set of rows this method
8863
	 * will return an array of beans of the specified type loaded with
8864
	 * the data fields provided by the result set from the database.
8865
	 *
8866
	 * New in 4.3.2: meta mask. The meta mask is a special mask to send
8867
	 * data from raw result rows to the meta store of the bean. This is
8868
	 * useful for bundling additional information with custom queries.
8869
	 * Values of every column whos name starts with $mask will be
8870
	 * transferred to the meta section of the bean under key 'data.bundle'.
8871
	 *
8872
	 * @param string $type type of beans you would like to have
8873
	 * @param array  $rows rows from the database result
8874
	 * @param string $mask meta mask to apply (optional)
8875
	 *
8876
	 * @return array
8877
	 */
8878
	public function convertToBeans( $type, $rows, $mask = NULL )
8879
	{
8880
		$masklen = 0;
8881
		if ( $mask !== NULL ) $masklen = mb_strlen( $mask );
8882
8883
		$collection                  = array();
8884
		$this->stash[$this->nesting] = array();
8885
		foreach ( $rows as $row ) {
8886
			$meta = array();
8887
			if ( !is_null( $mask ) ) {
8888
				foreach( $row as $key => $value ) {
8889
					if ( strpos( $key, $mask ) === 0 ) {
8890
						unset( $row[$key] );
8891
						$meta[$key] = $value;
8892
					}
8893
				}
8894
			}
8895
8896
			$id                               = $row['id'];
8897
			$this->stash[$this->nesting][$id] = $row;
8898
			$collection[$id]                  = $this->load( $type, $id );
8899
8900
			if ( $mask !== NULL ) {
8901
				$collection[$id]->setMeta( 'data.bundle', $meta );
8902
			}
8903
		}
8904
		$this->stash[$this->nesting] = NULL;
8905
8906
		return $collection;
8907
	}
8908
8909
	/**
8910
	 * Counts the number of beans of type $type.
8911
	 * This method accepts a second argument to modify the count-query.
8912
	 * A third argument can be used to provide bindings for the SQL snippet.
8913
	 *
8914
	 * @param string $type     type of bean we are looking for
8915
	 * @param string $addSQL   additional SQL snippet
8916
	 * @param array  $bindings parameters to bind to SQL
8917
	 *
8918
	 * @return integer
8919
	 */
8920
	public function count( $type, $addSQL = '', $bindings = array() )
8921
	{
8922
		$type = AQueryWriter::camelsSnake( $type );
8923
		if ( count( explode( '_', $type ) ) > 2 ) {
8924
			throw new RedException( 'Invalid type for count.' );
8925
		}
8926
8927
		try {
8928
			return (int) $this->writer->queryRecordCount( $type, array(), $addSQL, $bindings );
8929
		} catch ( SQLException $exception ) {
8930
			if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array(
8931
				 QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
8932
				 QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ),
8933
				 $exception->getDriverDetails() ) ) {
8934
				throw $exception;
8935
			}
8936
		}
8937
8938
		return 0;
8939
	}
8940
8941
	/**
8942
	 * Removes a bean from the database.
8943
	 * This function will remove the specified OODBBean
8944
	 * Bean Object from the database.
8945
	 *
8946
	 * @param OODBBean|SimpleModel $bean bean you want to remove from database
8947
	 *
8948
	 * @return void
8949
	 */
8950
	public function trash( $bean )
8951
	{
8952
		$this->oodb->signal( 'delete', $bean );
8953
		foreach ( $bean as $property => $value ) {
8954
			if ( $value instanceof OODBBean ) {
8955
				unset( $bean->$property );
8956
			}
8957
			if ( is_array( $value ) ) {
8958
				if ( strpos( $property, 'own' ) === 0 ) {
8959
					unset( $bean->$property );
8960
				} elseif ( strpos( $property, 'shared' ) === 0 ) {
8961
					unset( $bean->$property );
8962
				}
8963
			}
8964
		}
8965
		try {
8966
			$this->writer->deleteRecord( $bean->getMeta( 'type' ), array( 'id' => array( $bean->id ) ), NULL );
8967
		} catch ( SQLException $exception ) {
8968
			$this->handleException( $exception );
8969
		}
8970
		$bean->id = 0;
8971
		$this->oodb->signal( 'after_delete', $bean );
8972
	}
8973
8974
	/**
8975
	 * Checks whether the specified table already exists in the database.
8976
	 * Not part of the Object Database interface!
8977
	 *
8978
	 * @deprecated Use AQueryWriter::typeExists() instead.
8979
	 *
8980
	 * @param string $table table name
8981
	 *
8982
	 * @return boolean
8983
	 */
8984
	public function tableExists( $table )
8985
	{
8986
		return $this->writer->tableExists( $table );
8987
	}
8988
8989
	/**
8990
	 * Trash all beans of a given type.
8991
	 * Wipes an entire type of bean. After this operation there
8992
	 * will be no beans left of the specified type.
8993
	 * This method will ignore exceptions caused by database
8994
	 * tables that do not exist.
8995
	 *
8996
	 * @param string $type type of bean you wish to delete all instances of
8997
	 *
8998
	 * @return boolean
8999
	 */
9000
	public function wipe( $type )
9001
	{
9002
		try {
9003
			$this->writer->wipe( $type );
9004
9005
			return TRUE;
9006
		} catch ( SQLException $exception ) {
9007
			if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE ), $exception->getDriverDetails() ) ) {
9008
				throw $exception;
9009
			}
9010
9011
			return FALSE;
9012
		}
9013
	}
9014
}
9015
}
9016
9017
namespace RedBeanPHP\Repository {
9018
9019
use RedBeanPHP\OODBBean as OODBBean;
9020
use RedBeanPHP\QueryWriter as QueryWriter;
9021
use RedBeanPHP\RedException as RedException;
9022
use RedBeanPHP\BeanHelper as BeanHelper;
9023
use RedBeanPHP\RedException\SQL as SQLException;
9024
use RedBeanPHP\Repository as Repository;
9025
9026
/**
9027
 * Fluid Repository.
9028
 * OODB manages two repositories, a fluid one that
9029
 * adjust the database schema on-the-fly to accomodate for
9030
 * new bean types (tables) and new properties (columns) and
9031
 * a frozen one for use in a production environment. OODB
9032
 * allows you to swap the repository instances using the freeze()
9033
 * method.
9034
 *
9035
 * @file    RedBeanPHP/Repository/Fluid.php
9036
 * @author  Gabor de Mooij and the RedBeanPHP community
9037
 * @license BSD/GPLv2
9038
 *
9039
 * @copyright
9040
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
9041
 * This source file is subject to the BSD/GPLv2 License that is bundled
9042
 * with this source code in the file license.txt.
9043
 */
9044
class Fluid extends Repository
9045
{
9046
	/**
9047
	 * Figures out the desired type given the cast string ID.
9048
	 * Given a cast ID, this method will return the associated
9049
	 * type (INT(10) or VARCHAR for instance). The returned type
9050
	 * can be processed by the Query Writer to build the specified
9051
	 * column for you in the database. The Cast ID is actually just
9052
	 * a superset of the QueryWriter types. In addition to default
9053
	 * Query Writer column types you can pass the following 'cast types':
9054
	 * 'id' and 'string'. These will map to Query Writer specific
9055
	 * column types (probably INT and VARCHAR).
9056
	 *
9057
	 * @param string $cast cast identifier
9058
	 *
9059
	 * @return integer
9060
	 */
9061
	private function getTypeFromCast( $cast )
9062
	{
9063
		if ( $cast == 'string' ) {
9064
			$typeno = $this->writer->scanType( 'STRING' );
9065
		} elseif ( $cast == 'id' ) {
9066
			$typeno = $this->writer->getTypeForID();
9067
		} elseif ( isset( $this->writer->sqltype_typeno[$cast] ) ) {
9068
			$typeno = $this->writer->sqltype_typeno[$cast];
9069
		} else {
9070
			throw new RedException( 'Invalid Cast' );
9071
		}
9072
9073
		return $typeno;
9074
	}
9075
9076
	/**
9077
	 * Orders the Query Writer to create a table if it does not exist already and
9078
	 * adds a note in the build report about the creation.
9079
	 *
9080
	 * @param OODBBean $bean bean to update report of
9081
	 * @param string         $table table to check and create if not exists
9082
	 *
9083
	 * @return void
9084
	 */
9085
	private function createTableIfNotExists( OODBBean $bean, $table )
9086
	{
9087
		//Does table exist? If not, create
9088
		if ( !$this->tableExists( $this->writer->esc( $table, TRUE ) ) ) {
9089
			$this->writer->createTable( $table );
9090
			$bean->setMeta( 'buildreport.flags.created', TRUE );
9091
		}
9092
	}
9093
9094
	/**
9095
	 * Modifies the table to fit the bean data.
9096
	 * Given a property and a value and the bean, this method will
9097
	 * adjust the table structure to fit the requirements of the property and value.
9098
	 * This may include adding a new column or widening an existing column to hold a larger
9099
	 * or different kind of value. This method employs the writer to adjust the table
9100
	 * structure in the database. Schema updates are recorded in meta properties of the bean.
9101
	 *
9102
	 * This method will also apply indexes, unique constraints and foreign keys.
9103
	 *
9104
	 * @param OODBBean $bean     bean to get cast data from and store meta in
9105
	 * @param string   $property property to store
9106
	 * @param mixed    $value    value to store
9107
	 *
9108
	 * @return void
9109
	 */
9110
	private function modifySchema( OODBBean $bean, $property, $value, &$columns = NULL )
9111
	{
9112
		$doFKStuff = FALSE;
9113
		$table   = $bean->getMeta( 'type' );
9114
		if ($columns === NULL) {
9115
			$columns = $this->writer->getColumns( $table );
9116
		}
9117
		$columnNoQ = $this->writer->esc( $property, TRUE );
9118
		if ( !$this->oodb->isChilled( $bean->getMeta( 'type' ) ) ) {
9119
			if ( $bean->getMeta( "cast.$property", -1 ) !== -1 ) { //check for explicitly specified types
9120
				$cast   = $bean->getMeta( "cast.$property" );
9121
				$typeno = $this->getTypeFromCast( $cast );
9122
			} else {
9123
				$cast   = FALSE;
9124
				$typeno = $this->writer->scanType( $value, TRUE );
9125
			}
9126
			if ( isset( $columns[$this->writer->esc( $property, TRUE )] ) ) { //Is this property represented in the table ?
9127
				if ( !$cast ) { //rescan without taking into account special types >80
9128
					$typeno = $this->writer->scanType( $value, FALSE );
9129
				}
9130
				$sqlt = $this->writer->code( $columns[$this->writer->esc( $property, TRUE )] );
9131
				if ( $typeno > $sqlt ) { //no, we have to widen the database column type
9132
					$this->writer->widenColumn( $table, $property, $typeno );
9133
					$bean->setMeta( 'buildreport.flags.widen', TRUE );
9134
					$doFKStuff = TRUE;
9135
				}
9136
			} else {
9137
				$this->writer->addColumn( $table, $property, $typeno );
9138
				$bean->setMeta( 'buildreport.flags.addcolumn', TRUE );
9139
				$doFKStuff = TRUE;
9140
			}
9141
			if ($doFKStuff) {
9142
				if (strrpos($columnNoQ, '_id')===(strlen($columnNoQ)-3)) {
9143
					$destinationColumnNoQ = substr($columnNoQ, 0, strlen($columnNoQ)-3);
9144
					$indexName = "index_foreignkey_{$table}_{$destinationColumnNoQ}";
9145
					$this->writer->addIndex($table, $indexName, $columnNoQ);
9146
					$typeof = $bean->getMeta("sys.typeof.{$destinationColumnNoQ}", $destinationColumnNoQ);
9147
					$isLink = $bean->getMeta( 'sys.buildcommand.unique', FALSE );
9148
					//Make FK CASCADING if part of exclusive list (dependson=typeof) or if link bean
9149
					$isDep = ( $bean->moveMeta( 'sys.buildcommand.fkdependson' ) === $typeof || is_array( $isLink ) );
9150
					$result = $this->writer->addFK( $table, $typeof, $columnNoQ, 'id', $isDep );
9151
					//If this is a link bean and all unique columns have been added already, then apply unique constraint
9152
					if ( is_array( $isLink ) && !count( array_diff( $isLink, array_keys( $this->writer->getColumns( $table ) ) ) ) ) {
9153
						$this->writer->addUniqueConstraint( $table, $bean->moveMeta('sys.buildcommand.unique') );
9154
						$bean->setMeta("sys.typeof.{$destinationColumnNoQ}", NULL);
9155
					}
9156
				}
9157
			}
9158
		}
9159
	}
9160
9161
	/**
9162
	 * Part of the store() functionality.
9163
	 * Handles all new additions after the bean has been saved.
9164
	 * Stores addition bean in own-list, extracts the id and
9165
	 * adds a foreign key. Also adds a constraint in case the type is
9166
	 * in the dependent list.
9167
	 *
9168
	 * Note that this method raises a custom exception if the bean
9169
	 * is not an instance of OODBBean. Therefore it does not use
9170
	 * a type hint. This allows the user to take action in case
9171
	 * invalid objects are passed in the list.
9172
	 *
9173
	 * @param OODBBean $bean         bean to process
9174
	 * @param array    $ownAdditions list of addition beans in own-list
9175
	 *
9176
	 * @return void
9177
	 */
9178
	protected function processAdditions( $bean, $ownAdditions )
9179
	{
9180
		$beanType = $bean->getMeta( 'type' );
9181
9182
		foreach ( $ownAdditions as $addition ) {
9183
			if ( $addition instanceof OODBBean ) {
9184
9185
				$myFieldLink = $beanType . '_id';
9186
				$alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
9187
				if ( $alias ) $myFieldLink = $alias . '_id';
9188
9189
				$addition->$myFieldLink = $bean->id;
9190
				$addition->setMeta( 'cast.' . $myFieldLink, 'id' );
9191
9192
				if ($alias) {
9193
					$addition->setMeta( "sys.typeof.{$alias}", $beanType );
9194
				} else {
9195
					$addition->setMeta( "sys.typeof.{$beanType}", $beanType );
9196
				}
9197
9198
				$this->store( $addition );
9199
			} else {
9200
				throw new RedException( 'Array may only contain OODBBeans' );
9201
			}
9202
		}
9203
	}
9204
9205
	/**
9206
	 * Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
9207
	 * method. When all lists and embedded beans (parent objects) have been processed and
9208
	 * removed from the original bean the bean is passed to this method to be stored
9209
	 * in the database.
9210
	 *
9211
	 * @param OODBBean $bean the clean bean
9212
	 *
9213
	 * @return void
9214
	 */
9215
	protected function storeBean( OODBBean $bean )
9216
	{
9217
		if ( $bean->getMeta( 'changed' ) ) {
9218
			$this->check( $bean );
9219
			$table = $bean->getMeta( 'type' );
9220
			$this->createTableIfNotExists( $bean, $table );
9221
9222
			$updateValues = array();
9223
9224
			$partial = ( $this->partialBeans === TRUE || ( is_array( $this->partialBeans ) && in_array( $table, $this->partialBeans ) ) );
9225
			if ( $partial ) {
9226
				$mask = $bean->getMeta( 'changelist' );
9227
				$bean->setMeta( 'changelist', array() );
9228
			}
9229
9230
			$columnCache = NULL;
9231
			foreach ( $bean as $property => $value ) {
9232
				if ( $partial && !in_array( $property, $mask ) ) continue;
9233
				if ( $property !== 'id' ) {
9234
					$this->modifySchema( $bean, $property, $value, $columnCache );
9235
				}
9236
				if ( $property !== 'id' ) {
9237
					$updateValues[] = array( 'property' => $property, 'value' => $value );
9238
				}
9239
			}
9240
9241
			$bean->id = $this->writer->updateRecord( $table, $updateValues, $bean->id );
9242
			$bean->setMeta( 'changed', FALSE );
9243
		}
9244
		$bean->setMeta( 'tainted', FALSE );
9245
	}
9246
9247
	/**
9248
	 * Exception handler.
9249
	 * Fluid and Frozen mode have different ways of handling
9250
	 * exceptions. Fluid mode (using the fluid repository) ignores
9251
	 * exceptions caused by the following:
9252
	 *
9253
	 * - missing tables
9254
	 * - missing column
9255
	 *
9256
	 * In these situations, the repository will behave as if
9257
	 * no beans could be found. This is because in fluid mode
9258
	 * it might happen to query a table or column that has not been
9259
	 * created yet. In frozen mode, this is not supposed to happen
9260
	 * and the corresponding exceptions will be thrown.
9261
	 *
9262
	 * @param \Exception $exception exception
9263
	 *
9264
	 * @return void
9265
	 */
9266
	protected function handleException( \Exception $exception )
9267
	{
9268
		if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
9269
			array(
9270
				QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
9271
				QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ),
9272
				$exception->getDriverDetails() )
9273
		) {
9274
			throw $exception;
9275
		}
9276
	}
9277
9278
	/**
9279
	 * Dispenses a new bean (a OODBBean Bean Object)
9280
	 * of the specified type. Always
9281
	 * use this function to get an empty bean object. Never
9282
	 * instantiate a OODBBean yourself because it needs
9283
	 * to be configured before you can use it with RedBean. This
9284
	 * function applies the appropriate initialization /
9285
	 * configuration for you.
9286
	 *
9287
	 * @param string  $type              type of bean you want to dispense
9288
	 * @param string  $number            number of beans you would like to get
9289
	 * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
9290
	 *
9291
	 * @return OODBBean
9292
	 */
9293
	public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
9294
	{
9295
		$OODBBEAN = defined( 'REDBEAN_OODBBEAN_CLASS' ) ? REDBEAN_OODBBEAN_CLASS : '\RedBeanPHP\OODBBean';
9296
		$beans = array();
9297
		for ( $i = 0; $i < $number; $i++ ) {
9298
			$bean = new $OODBBEAN;
9299
			$bean->initializeForDispense( $type, $this->oodb->getBeanHelper() );
9300
			$this->check( $bean );
9301
			$this->oodb->signal( 'dispense', $bean );
9302
			$beans[] = $bean;
9303
		}
9304
9305
		return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans;
9306
	}
9307
9308
	/**
9309
	 * Loads a bean from the object database.
9310
	 * It searches for a OODBBean Bean Object in the
9311
	 * database. It does not matter how this bean has been stored.
9312
	 * RedBean uses the primary key ID $id and the string $type
9313
	 * to find the bean. The $type specifies what kind of bean you
9314
	 * are looking for; this is the same type as used with the
9315
	 * dispense() function. If RedBean finds the bean it will return
9316
	 * the OODB Bean object; if it cannot find the bean
9317
	 * RedBean will return a new bean of type $type and with
9318
	 * primary key ID 0. In the latter case it acts basically the
9319
	 * same as dispense().
9320
	 *
9321
	 * Important note:
9322
	 * If the bean cannot be found in the database a new bean of
9323
	 * the specified type will be generated and returned.
9324
	 *
9325
	 * @param string  $type type of bean you want to load
9326
	 * @param integer $id   ID of the bean you want to load
9327
	 *
9328
	 * @return OODBBean
9329
	 */
9330
	public function load( $type, $id )
9331
	{
9332
		$rows = array();
9333
		$bean = $this->dispense( $type );
9334
		if ( isset( $this->stash[$this->nesting][$id] ) ) {
9335
			$row = $this->stash[$this->nesting][$id];
9336
		} else {
9337
			try {
9338
				$rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
9339
			} catch ( SQLException $exception ) {
9340
				if (
9341
					$this->writer->sqlStateIn(
9342
						$exception->getSQLState(),
9343
						array(
9344
							QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
9345
							QueryWriter::C_SQLSTATE_NO_SUCH_TABLE
9346
						),
9347
						$exception->getDriverDetails()
9348
					)
9349
				) {
9350
					$rows = array();
9351
				} else {
9352
					throw $exception;
9353
				}
9354
			}
9355
			if ( !count( $rows ) ) {
9356
				return $bean;
9357
			}
9358
			$row = array_pop( $rows );
9359
		}
9360
		$bean->importRow( $row );
9361
		$this->nesting++;
9362
		$this->oodb->signal( 'open', $bean );
9363
		$this->nesting--;
9364
9365
		return $bean->setMeta( 'tainted', FALSE );
9366
	}
9367
}
9368
}
9369
9370
namespace RedBeanPHP\Repository {
9371
9372
use RedBeanPHP\OODBBean as OODBBean;
9373
use RedBeanPHP\QueryWriter as QueryWriter;
9374
use RedBeanPHP\RedException as RedException;
9375
use RedBeanPHP\BeanHelper as BeanHelper;
9376
use RedBeanPHP\RedException\SQL as SQLException;
9377
use RedBeanPHP\Repository as Repository;
9378
9379
/**
9380
 * Frozen Repository.
9381
 * OODB manages two repositories, a fluid one that
9382
 * adjust the database schema on-the-fly to accomodate for
9383
 * new bean types (tables) and new properties (columns) and
9384
 * a frozen one for use in a production environment. OODB
9385
 * allows you to swap the repository instances using the freeze()
9386
 * method.
9387
 *
9388
 * @file    RedBeanPHP/Repository/Frozen.php
9389
 * @author  Gabor de Mooij and the RedBeanPHP community
9390
 * @license BSD/GPLv2
9391
 *
9392
 * @copyright
9393
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
9394
 * This source file is subject to the BSD/GPLv2 License that is bundled
9395
 * with this source code in the file license.txt.
9396
 */
9397
class Frozen extends Repository
9398
{
9399
	/**
9400
	 * Exception handler.
9401
	 * Fluid and Frozen mode have different ways of handling
9402
	 * exceptions. Fluid mode (using the fluid repository) ignores
9403
	 * exceptions caused by the following:
9404
	 *
9405
	 * - missing tables
9406
	 * - missing column
9407
	 *
9408
	 * In these situations, the repository will behave as if
9409
	 * no beans could be found. This is because in fluid mode
9410
	 * it might happen to query a table or column that has not been
9411
	 * created yet. In frozen mode, this is not supposed to happen
9412
	 * and the corresponding exceptions will be thrown.
9413
	 *
9414
	 * @param \Exception $exception exception
9415
	 *
9416
	 * @return void
9417
	 */
9418
	protected function handleException( \Exception $exception )
9419
	{
9420
		throw $exception;
9421
	}
9422
9423
	/**
9424
	 * Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
9425
	 * method. When all lists and embedded beans (parent objects) have been processed and
9426
	 * removed from the original bean the bean is passed to this method to be stored
9427
	 * in the database.
9428
	 *
9429
	 * @param OODBBean $bean the clean bean
9430
	 *
9431
	 * @return void
9432
	 */
9433
	protected function storeBean( OODBBean $bean )
9434
	{
9435
		if ( $bean->getMeta( 'changed' ) ) {
9436
9437
			list( $properties, $table ) = $bean->getPropertiesAndType();
9438
			$id = $properties['id'];
9439
			unset($properties['id']);
9440
			$updateValues = array();
9441
			$k1 = 'property';
9442
			$k2 = 'value';
9443
9444
			$partial = ( $this->partialBeans === TRUE || ( is_array( $this->partialBeans ) && in_array( $table, $this->partialBeans ) ) );
9445
			if ( $partial ) {
9446
				$mask = $bean->getMeta( 'changelist' );
9447
				$bean->setMeta( 'changelist', array() );
9448
			}
9449
9450
			foreach( $properties as $key => $value ) {
9451
				if ( $partial && !in_array( $key, $mask ) ) continue;
9452
				$updateValues[] = array( $k1 => $key, $k2 => $value );
9453
			}
9454
			$bean->id = $this->writer->updateRecord( $table, $updateValues, $id );
9455
			$bean->setMeta( 'changed', FALSE );
9456
		}
9457
		$bean->setMeta( 'tainted', FALSE );
9458
	}
9459
9460
	/**
9461
	 * Part of the store() functionality.
9462
	 * Handles all new additions after the bean has been saved.
9463
	 * Stores addition bean in own-list, extracts the id and
9464
	 * adds a foreign key. Also adds a constraint in case the type is
9465
	 * in the dependent list.
9466
	 *
9467
	 * Note that this method raises a custom exception if the bean
9468
	 * is not an instance of OODBBean. Therefore it does not use
9469
	 * a type hint. This allows the user to take action in case
9470
	 * invalid objects are passed in the list.
9471
	 *
9472
	 * @param OODBBean $bean         bean to process
9473
	 * @param array    $ownAdditions list of addition beans in own-list
9474
	 *
9475
	 * @return void
9476
	 * @throws RedException
9477
	 */
9478
	protected function processAdditions( $bean, $ownAdditions )
9479
	{
9480
		$beanType = $bean->getMeta( 'type' );
9481
9482
		$cachedIndex = array();
9483
		foreach ( $ownAdditions as $addition ) {
9484
			if ( $addition instanceof OODBBean ) {
9485
9486
				$myFieldLink = $beanType . '_id';
9487
				$alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
9488
				if ( $alias ) $myFieldLink = $alias . '_id';
9489
9490
				$addition->$myFieldLink = $bean->id;
9491
				$addition->setMeta( 'cast.' . $myFieldLink, 'id' );
9492
				$this->store( $addition );
9493
9494
			} else {
9495
				throw new RedException( 'Array may only contain OODBBeans' );
9496
			}
9497
		}
9498
	}
9499
9500
	/**
9501
	 * Dispenses a new bean (a OODBBean Bean Object)
9502
	 * of the specified type. Always
9503
	 * use this function to get an empty bean object. Never
9504
	 * instantiate a OODBBean yourself because it needs
9505
	 * to be configured before you can use it with RedBean. This
9506
	 * function applies the appropriate initialization /
9507
	 * configuration for you.
9508
	 *
9509
	 * @param string  $type              type of bean you want to dispense
9510
	 * @param int  $number            number of beans you would like to get
9511
	 * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
9512
	 *
9513
	 * @return OODBBean
9514
	 */
9515
	public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
9516
	{
9517
		$OODBBEAN = defined( 'REDBEAN_OODBBEAN_CLASS' ) ? REDBEAN_OODBBEAN_CLASS : '\RedBeanPHP\OODBBean';
9518
		$beans = array();
9519
		for ( $i = 0; $i < $number; $i++ ) {
9520
			/** @var \RedBeanPHP\OODBBean $bean */
9521
			$bean = new $OODBBEAN;
9522
			$bean->initializeForDispense( $type, $this->oodb->getBeanHelper() );
9523
			$this->oodb->signal( 'dispense', $bean );
9524
			$beans[] = $bean;
9525
		}
9526
9527
		return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans;
9528
	}
9529
9530
	/**
9531
	 * Loads a bean from the object database.
9532
	 * It searches for a OODBBean Bean Object in the
9533
	 * database. It does not matter how this bean has been stored.
9534
	 * RedBean uses the primary key ID $id and the string $type
9535
	 * to find the bean. The $type specifies what kind of bean you
9536
	 * are looking for; this is the same type as used with the
9537
	 * dispense() function. If RedBean finds the bean it will return
9538
	 * the OODB Bean object; if it cannot find the bean
9539
	 * RedBean will return a new bean of type $type and with
9540
	 * primary key ID 0. In the latter case it acts basically the
9541
	 * same as dispense().
9542
	 *
9543
	 * Important note:
9544
	 * If the bean cannot be found in the database a new bean of
9545
	 * the specified type will be generated and returned.
9546
	 *
9547
	 * @param string  $type type of bean you want to load
9548
	 * @param integer $id   ID of the bean you want to load
9549
	 *
9550
	 * @return OODBBean
9551
	 * @throws SQLException
9552
	 */
9553
	public function load( $type, $id )
9554
	{
9555
		$rows = array();
9556
		$bean = $this->dispense( $type );
9557
		if ( isset( $this->stash[$this->nesting][$id] ) ) {
9558
			$row = $this->stash[$this->nesting][$id];
9559
		} else {
9560
			$rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
9561
			if ( !count( $rows ) ) {
9562
				return $bean;
9563
			}
9564
			$row = array_pop( $rows );
9565
		}
9566
		$bean->importRow( $row );
9567
		$this->nesting++;
9568
		$this->oodb->signal( 'open', $bean );
9569
		$this->nesting--;
9570
9571
		return $bean->setMeta( 'tainted', FALSE );
9572
	}
9573
}
9574
}
9575
9576
namespace RedBeanPHP {
9577
9578
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
9579
use RedBeanPHP\QueryWriter as QueryWriter;
9580
use RedBeanPHP\BeanHelper as BeanHelper;
9581
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
9582
use RedBeanPHP\Repository as Repository;
9583
use RedBeanPHP\Repository\Fluid as FluidRepo;
9584
use RedBeanPHP\Repository\Frozen as FrozenRepo;
9585
9586
/**
9587
 * RedBean Object Oriented DataBase.
9588
 *
9589
 * The RedBean OODB Class is the main class of RedBeanPHP.
9590
 * It takes OODBBean objects and stores them to and loads them from the
9591
 * database as well as providing other CRUD functions. This class acts as a
9592
 * object database.
9593
 *
9594
 * @file    RedBeanPHP/OODB.php
9595
 * @author  Gabor de Mooij and the RedBeanPHP community
9596
 * @license BSD/GPLv2
9597
 *
9598
 * @copyright
9599
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
9600
 * This source file is subject to the BSD/GPLv2 License that is bundled
9601
 * with this source code in the file license.txt.
9602
 */
9603
class OODB extends Observable
9604
{
9605
	/**
9606
	 * @var array
9607
	 */
9608
	private static $sqlFilters = array();
9609
9610
	/**
9611
	 * @var array
9612
	 */
9613
	protected $chillList = array();
9614
9615
	/**
9616
	 * @var array
9617
	 */
9618
	protected $stash = NULL;
9619
9620
	/*
9621
	 * @var integer
9622
	 */
9623
	protected $nesting = 0;
9624
9625
	/**
9626
	 * @var DBAdapter
9627
	 */
9628
	protected $writer;
9629
9630
	/**
9631
	 * @var boolean
9632
	 */
9633
	protected $isFrozen = FALSE;
9634
9635
	/**
9636
	 * @var FacadeBeanHelper
9637
	 */
9638
	protected $beanhelper = NULL;
9639
9640
	/**
9641
	 * @var AssociationManager
9642
	 */
9643
	protected $assocManager = NULL;
9644
9645
	/**
9646
	 * @var Repository
9647
	 */
9648
	protected $repository = NULL;
9649
9650
	/**
9651
	 * @var FrozenRepo
9652
	 */
9653
	protected $frozenRepository = NULL;
9654
9655
	/**
9656
	 * @var FluidRepo
9657
	 */
9658
	protected $fluidRepository = NULL;
9659
9660
	/**
9661
	 * @var boolean
9662
	 */
9663
	protected static $autoClearHistoryAfterStore = FALSE;
9664
9665
	/**
9666
	 * If set to TRUE, this method will call clearHistory every time
9667
	 * the bean gets stored.
9668
	 *
9669
	 * @param boolean $autoClear auto clear option
9670
	 *
9671
	 * @return void
9672
	 */
9673
	public static function autoClearHistoryAfterStore( $autoClear = TRUE )
9674
	{
9675
		self::$autoClearHistoryAfterStore = (boolean) $autoClear;
9676
	}
9677
9678
	/**
9679
	 * Unboxes a bean from a FUSE model if needed and checks whether the bean is
9680
	 * an instance of OODBBean.
9681
	 *
9682
	 * @param OODBBean $bean bean you wish to unbox
9683
	 *
9684
	 * @return OODBBean
9685
	 */
9686
	protected function unboxIfNeeded( $bean )
9687
	{
9688
		if ( $bean instanceof SimpleModel ) {
9689
			$bean = $bean->unbox();
9690
		}
9691
		if ( !( $bean instanceof OODBBean ) ) {
9692
			throw new RedException( 'OODB Store requires a bean, got: ' . gettype( $bean ) );
9693
		}
9694
9695
		return $bean;
9696
	}
9697
9698
	/**
9699
	 * Constructor, requires a query writer.
9700
	 * Most of the time, you do not need to use this constructor,
9701
	 * since the facade takes care of constructing and wiring the
9702
	 * RedBeanPHP core objects. However if you would like to
9703
	 * assemble an OODB instance yourself, this is how it works:
9704
	 *
9705
	 * Usage:
9706
	 *
9707
	 * <code>
9708
	 * $database = new RPDO( $dsn, $user, $pass );
9709
	 * $adapter = new DBAdapter( $database );
9710
	 * $writer = new PostgresWriter( $adapter );
9711
	 * $oodb = new OODB( $writer, FALSE );
9712
	 * $bean = $oodb->dispense( 'bean' );
9713
	 * $bean->name = 'coffeeBean';
9714
	 * $id = $oodb->store( $bean );
9715
	 * $bean = $oodb->load( 'bean', $id );
9716
	 * </code>
9717
	 *
9718
	 * The example above creates the 3 RedBeanPHP core objects:
9719
	 * the Adapter, the Query Writer and the OODB instance and
9720
	 * wires them together. The example also demonstrates some of
9721
	 * the methods that can be used with OODB, as you see, they
9722
	 * closely resemble their facade counterparts.
9723
	 *
9724
	 * The wiring process: create an RPDO instance using your database
9725
	 * connection parameters. Create a database adapter from the RPDO
9726
	 * object and pass that to the constructor of the writer. Next,
9727
	 * create an OODB instance from the writer. Now you have an OODB
9728
	 * object.
9729
	 *
9730
	 * @param QueryWriter   $writer writer
9731
	 * @param array|boolean $frozen mode of operation: TRUE (frozen), FALSE (default, fluid) or ARRAY (chilled)
9732
	 */
9733
	public function __construct( QueryWriter $writer, $frozen = FALSE )
9734
	{
9735
		if ( $writer instanceof QueryWriter ) {
9736
			$this->writer = $writer;
9737
		}
9738
9739
		$this->freeze( $frozen );
9740
	}
9741
9742
	/**
9743
	 * Toggles fluid or frozen mode. In fluid mode the database
9744
	 * structure is adjusted to accomodate your objects. In frozen mode
9745
	 * this is not the case.
9746
	 *
9747
	 * You can also pass an array containing a selection of frozen types.
9748
	 * Let's call this chilly mode, it's just like fluid mode except that
9749
	 * certain types (i.e. tables) aren't touched.
9750
	 *
9751
	 * @param boolean|array $toggle TRUE if you want to use OODB instance in frozen mode
9752
	 *
9753
	 * @return void
9754
	 */
9755
	public function freeze( $toggle )
9756
	{
9757
		if ( is_array( $toggle ) ) {
9758
			$this->chillList = $toggle;
9759
			$this->isFrozen  = FALSE;
9760
		} else {
9761
			$this->isFrozen = (boolean) $toggle;
9762
		}
9763
9764
		if ( $this->isFrozen ) {
9765
			if ( !$this->frozenRepository ) {
9766
				$this->frozenRepository = new FrozenRepo( $this, $this->writer );
9767
			}
9768
9769
			$this->repository = $this->frozenRepository;
9770
9771
		} else {
9772
			if ( !$this->fluidRepository ) {
9773
				$this->fluidRepository = new FluidRepo( $this, $this->writer );
9774
			}
9775
9776
			$this->repository = $this->fluidRepository;
9777
		}
9778
9779
		if ( count( self::$sqlFilters ) ) {
9780
			AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
9781
		}
9782
9783
	}
9784
9785
	/**
9786
	 * Returns the current mode of operation of RedBean.
9787
	 * In fluid mode the database
9788
	 * structure is adjusted to accomodate your objects.
9789
	 * In frozen mode
9790
	 * this is not the case.
9791
	 *
9792
	 * @return boolean
9793
	 */
9794
	public function isFrozen()
9795
	{
9796
		return (bool) $this->isFrozen;
9797
	}
9798
9799
	/**
9800
	 * Determines whether a type is in the chill list.
9801
	 * If a type is 'chilled' it's frozen, so its schema cannot be
9802
	 * changed anymore. However other bean types may still be modified.
9803
	 * This method is a convenience method for other objects to check if
9804
	 * the schema of a certain type is locked for modification.
9805
	 *
9806
	 * @param string $type the type you wish to check
9807
	 *
9808
	 * @return boolean
9809
	 */
9810
	public function isChilled( $type )
9811
	{
9812
		return (boolean) ( in_array( $type, $this->chillList ) );
9813
	}
9814
9815
	/**
9816
	 * Dispenses a new bean (a OODBBean Bean Object)
9817
	 * of the specified type. Always
9818
	 * use this function to get an empty bean object. Never
9819
	 * instantiate a OODBBean yourself because it needs
9820
	 * to be configured before you can use it with RedBean. This
9821
	 * function applies the appropriate initialization /
9822
	 * configuration for you.
9823
	 *
9824
	 * @param string  $type              type of bean you want to dispense
9825
	 * @param string  $number            number of beans you would like to get
9826
	 * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
9827
	 *
9828
	 * @return OODBBean
9829
	 */
9830
	public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
9831
	{
9832
		if ( $number < 1 ) {
9833
			if ( $alwaysReturnArray ) return array();
9834
			return NULL;
9835
		}
9836
9837
		return $this->repository->dispense( $type, $number, $alwaysReturnArray );
9838
	}
9839
9840
	/**
9841
	 * Sets bean helper to be given to beans.
9842
	 * Bean helpers assist beans in getting a reference to a toolbox.
9843
	 *
9844
	 * @param BeanHelper $beanhelper helper
9845
	 *
9846
	 * @return void
9847
	 */
9848
	public function setBeanHelper( BeanHelper $beanhelper )
9849
	{
9850
		$this->beanhelper = $beanhelper;
9851
	}
9852
9853
	/**
9854
	 * Returns the current bean helper.
9855
	 * Bean helpers assist beans in getting a reference to a toolbox.
9856
	 *
9857
	 * @return BeanHelper
9858
	 */
9859
	public function getBeanHelper()
9860
	{
9861
		return $this->beanhelper;
9862
	}
9863
9864
	/**
9865
	 * Checks whether a OODBBean bean is valid.
9866
	 * If the type is not valid or the ID is not valid it will
9867
	 * throw an exception: Security.
9868
	 *
9869
	 * @param OODBBean $bean the bean that needs to be checked
9870
	 *
9871
	 * @return void
9872
	 */
9873
	public function check( OODBBean $bean )
9874
	{
9875
		$this->repository->check( $bean );
9876
	}
9877
9878
	/**
9879
	 * Searches the database for a bean that matches conditions $conditions and sql $addSQL
9880
	 * and returns an array containing all the beans that have been found.
9881
	 *
9882
	 * Conditions need to take form:
9883
	 *
9884
	 * <code>
9885
	 * array(
9886
	 *    'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
9887
	 *    'PROPERTY' => array( POSSIBLE VALUES... )
9888
	 * );
9889
	 * </code>
9890
	 *
9891
	 * All conditions are glued together using the AND-operator, while all value lists
9892
	 * are glued using IN-operators thus acting as OR-conditions.
9893
	 *
9894
	 * Note that you can use property names; the columns will be extracted using the
9895
	 * appropriate bean formatter.
9896
	 *
9897
	 * @param string $type       type of beans you are looking for
9898
	 * @param array  $conditions list of conditions
9899
	 * @param string $sql        SQL to be used in query
9900
	 * @param array  $bindings   a list of values to bind to query parameters
9901
	 *
9902
	 * @return array
9903
	 */
9904
	public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
9905
	{
9906
		return $this->repository->find( $type, $conditions, $sql, $bindings );
9907
	}
9908
9909
	/**
9910
	 * Same as find() but returns a BeanCollection.
9911
	 *
9912
	 * @param string $type     type of beans you are looking for
9913
	 * @param string $sql      SQL to be used in query
9914
	 * @param array  $bindings a list of values to bind to query parameters
9915
	 *
9916
	 * @return BeanCollection
9917
	 */
9918
	public function findCollection(  $type, $sql = NULL, $bindings = array() )
9919
	{
9920
		return $this->repository->findCollection( $type, $sql, $bindings );
9921
	}
9922
9923
	/**
9924
	 * Checks whether the specified table already exists in the database.
9925
	 * Not part of the Object Database interface!
9926
	 *
9927
	 * @deprecated Use AQueryWriter::typeExists() instead.
9928
	 *
9929
	 * @param string $table table name
9930
	 *
9931
	 * @return boolean
9932
	 */
9933
	public function tableExists( $table )
9934
	{
9935
		return $this->repository->tableExists( $table );
9936
	}
9937
9938
	/**
9939
	 * Stores a bean in the database. This method takes a
9940
	 * OODBBean Bean Object $bean and stores it
9941
	 * in the database. If the database schema is not compatible
9942
	 * with this bean and RedBean runs in fluid mode the schema
9943
	 * will be altered to store the bean correctly.
9944
	 * If the database schema is not compatible with this bean and
9945
	 * RedBean runs in frozen mode it will throw an exception.
9946
	 * This function returns the primary key ID of the inserted
9947
	 * bean.
9948
	 *
9949
	 * The return value is an integer if possible. If it is not possible to
9950
	 * represent the value as an integer a string will be returned. We use
9951
	 * explicit casts instead of functions to preserve performance
9952
	 * (0.13 vs 0.28 for 10000 iterations on Core i3).
9953
	 *
9954
	 * @param OODBBean|SimpleModel $bean bean to store
9955
	 *
9956
	 * @return integer|string
9957
	 */
9958
	public function store( $bean )
9959
	{
9960
		$bean = $this->unboxIfNeeded( $bean );
9961
		$id = $this->repository->store( $bean );
9962
		if ( self::$autoClearHistoryAfterStore ) {
9963
				$bean->clearHistory();
9964
		}
9965
		return $id;
9966
	}
9967
9968
	/**
9969
	 * Loads a bean from the object database.
9970
	 * It searches for a OODBBean Bean Object in the
9971
	 * database. It does not matter how this bean has been stored.
9972
	 * RedBean uses the primary key ID $id and the string $type
9973
	 * to find the bean. The $type specifies what kind of bean you
9974
	 * are looking for; this is the same type as used with the
9975
	 * dispense() function. If RedBean finds the bean it will return
9976
	 * the OODB Bean object; if it cannot find the bean
9977
	 * RedBean will return a new bean of type $type and with
9978
	 * primary key ID 0. In the latter case it acts basically the
9979
	 * same as dispense().
9980
	 *
9981
	 * Important note:
9982
	 * If the bean cannot be found in the database a new bean of
9983
	 * the specified type will be generated and returned.
9984
	 *
9985
	 * @param string  $type type of bean you want to load
9986
	 * @param integer $id   ID of the bean you want to load
9987
	 *
9988
	 * @return OODBBean
9989
	 */
9990
	public function load( $type, $id )
9991
	{
9992
		return $this->repository->load( $type, $id );
9993
	}
9994
9995
	/**
9996
	 * Removes a bean from the database.
9997
	 * This function will remove the specified OODBBean
9998
	 * Bean Object from the database.
9999
	 *
10000
	 * @param OODBBean|SimpleModel $bean bean you want to remove from database
10001
	 *
10002
	 * @return void
10003
	 */
10004
	public function trash( $bean )
10005
	{
10006
		$bean = $this->unboxIfNeeded( $bean );
10007
		return $this->repository->trash( $bean );
10008
	}
10009
10010
	/**
10011
	 * Returns an array of beans. Pass a type and a series of ids and
10012
	 * this method will bring you the corresponding beans.
10013
	 *
10014
	 * important note: Because this method loads beans using the load()
10015
	 * function (but faster) it will return empty beans with ID 0 for
10016
	 * every bean that could not be located. The resulting beans will have the
10017
	 * passed IDs as their keys.
10018
	 *
10019
	 * @param string $type type of beans
10020
	 * @param array  $ids  ids to load
10021
	 *
10022
	 * @return array
10023
	 */
10024
	public function batch( $type, $ids )
10025
	{
10026
		return $this->repository->batch( $type, $ids );
10027
	}
10028
10029
	/**
10030
	 * This is a convenience method; it converts database rows
10031
	 * (arrays) into beans. Given a type and a set of rows this method
10032
	 * will return an array of beans of the specified type loaded with
10033
	 * the data fields provided by the result set from the database.
10034
	 *
10035
	 * @param string $type type of beans you would like to have
10036
	 * @param array  $rows rows from the database result
10037
	 * @param string $mask mask to apply for meta data
10038
	 *
10039
	 * @return array
10040
	 */
10041
	public function convertToBeans( $type, $rows, $mask = NULL )
10042
	{
10043
		return $this->repository->convertToBeans( $type, $rows, $mask );
10044
	}
10045
10046
	/**
10047
	 * Counts the number of beans of type $type.
10048
	 * This method accepts a second argument to modify the count-query.
10049
	 * A third argument can be used to provide bindings for the SQL snippet.
10050
	 *
10051
	 * @param string $type     type of bean we are looking for
10052
	 * @param string $addSQL   additional SQL snippet
10053
	 * @param array  $bindings parameters to bind to SQL
10054
	 *
10055
	 * @return integer
10056
	 */
10057
	public function count( $type, $addSQL = '', $bindings = array() )
10058
	{
10059
		return $this->repository->count( $type, $addSQL, $bindings );
10060
	}
10061
10062
	/**
10063
	 * Trash all beans of a given type. Wipes an entire type of bean.
10064
	 *
10065
	 * @param string $type type of bean you wish to delete all instances of
10066
	 *
10067
	 * @return boolean
10068
	 */
10069
	public function wipe( $type )
10070
	{
10071
		return $this->repository->wipe( $type );
10072
	}
10073
10074
	/**
10075
	 * Returns an Association Manager for use with OODB.
10076
	 * A simple getter function to obtain a reference to the association manager used for
10077
	 * storage and more.
10078
	 *
10079
	 * @return AssociationManager
10080
	 */
10081
	public function getAssociationManager()
10082
	{
10083
		if ( !isset( $this->assocManager ) ) {
10084
			throw new RedException( 'No association manager available.' );
10085
		}
10086
10087
		return $this->assocManager;
10088
	}
10089
10090
	/**
10091
	 * Sets the association manager instance to be used by this OODB.
10092
	 * A simple setter function to set the association manager to be used for storage and
10093
	 * more.
10094
	 *
10095
	 * @param AssociationManager $assocManager sets the association manager to be used
10096
	 *
10097
	 * @return void
10098
	 */
10099
	public function setAssociationManager( AssociationManager $assocManager )
10100
	{
10101
		$this->assocManager = $assocManager;
10102
	}
10103
10104
	/**
10105
	 * Returns the currently used repository instance.
10106
	 * For testing purposes only.
10107
	 *
10108
	 * @return Repository
10109
	 */
10110
	public function getCurrentRepository()
10111
	{
10112
		return $this->repository;
10113
	}
10114
10115
	/**
10116
	 * Binds an SQL function to a column.
10117
	 * This method can be used to setup a decode/encode scheme or
10118
	 * perform UUID insertion. This method is especially useful for handling
10119
	 * MySQL spatial columns, because they need to be processed first using
10120
	 * the asText/GeomFromText functions.
10121
	 *
10122
	 * @param string $mode     mode to set function for, i.e. read or write
10123
	 * @param string $field    field (table.column) to bind SQL function to
10124
	 * @param string $function SQL function to bind to field
10125
	 *
10126
	 * @return void
10127
	 */
10128
	public function bindFunc( $mode, $field, $function )
10129
	{
10130
		list( $type, $property ) = explode( '.', $field );
10131
		$mode = ($mode === 'write') ? QueryWriter::C_SQLFILTER_WRITE : QueryWriter::C_SQLFILTER_READ;
10132
10133
		if ( !isset( self::$sqlFilters[$mode] ) ) self::$sqlFilters[$mode] = array();
10134
		if ( !isset( self::$sqlFilters[$mode][$type] ) ) self::$sqlFilters[$mode][$type] = array();
10135
10136
		if ( is_null( $function ) ) {
10137
			unset( self::$sqlFilters[$mode][$type][$property] );
10138
		} else {
10139
			if ($mode === QueryWriter::C_SQLFILTER_WRITE) {
10140
				self::$sqlFilters[$mode][$type][$property] = $function.'(?)';
10141
			} else {
10142
				self::$sqlFilters[$mode][$type][$property] = $function."($field)";
10143
			}
10144
		}
10145
10146
		AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
10147
	}
10148
}
10149
}
10150
10151
namespace RedBeanPHP {
10152
10153
use RedBeanPHP\OODB as OODB;
10154
use RedBeanPHP\QueryWriter as QueryWriter;
10155
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
10156
use RedBeanPHP\Adapter as Adapter;
10157
10158
/**
10159
 * ToolBox.
10160
 *
10161
 * The toolbox is an integral part of RedBeanPHP providing the basic
10162
 * architectural building blocks to manager objects, helpers and additional tools
10163
 * like plugins. A toolbox contains the three core components of RedBeanPHP:
10164
 * the adapter, the query writer and the core functionality of RedBeanPHP in
10165
 * OODB.
10166
 *
10167
 * @file      RedBeanPHP/ToolBox.php
10168
 * @author    Gabor de Mooij and the RedBeanPHP community
10169
 * @license   BSD/GPLv2
10170
 *
10171
 * @copyright
10172
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
10173
 * This source file is subject to the BSD/GPLv2 License that is bundled
10174
 * with this source code in the file license.txt.
10175
 */
10176
class ToolBox
10177
{
10178
	/**
10179
	 * @var OODB
10180
	 */
10181
	protected $oodb;
10182
10183
	/**
10184
	 * @var QueryWriter
10185
	 */
10186
	protected $writer;
10187
10188
	/**
10189
	 * @var DBAdapter
10190
	 */
10191
	protected $adapter;
10192
10193
	/**
10194
	 * Constructor.
10195
	 * The toolbox is an integral part of RedBeanPHP providing the basic
10196
	 * architectural building blocks to manager objects, helpers and additional tools
10197
	 * like plugins. A toolbox contains the three core components of RedBeanPHP:
10198
	 * the adapter, the query writer and the core functionality of RedBeanPHP in
10199
	 * OODB.
10200
	 *
10201
	 * Usage:
10202
	 *
10203
	 * <code>
10204
	 * $toolbox = new ToolBox( $oodb, $adapter, $writer );
10205
	 * $plugin  = new MyPlugin( $toolbox );
10206
	 * </code>
10207
	 *
10208
	 * The example above illustrates how the toolbox is used.
10209
	 * The core objects are passed to the ToolBox constructor to
10210
	 * assemble a toolbox instance. The toolbox is then passed to
10211
	 * the plugin, helper or manager object. Instances of
10212
	 * TagManager, AssociationManager and so on are examples of
10213
	 * this, they all require a toolbox. The toolbox can also
10214
	 * be obtained from the facade using: R::getToolBox();
10215
	 *
10216
	 * @param OODB        $oodb    Object Database, OODB
10217
	 * @param DBAdapter   $adapter Database Adapter
10218
	 * @param QueryWriter $writer  Query Writer
10219
	 */
10220
	public function __construct( OODB $oodb, Adapter $adapter, QueryWriter $writer )
10221
	{
10222
		$this->oodb    = $oodb;
10223
		$this->adapter = $adapter;
10224
		$this->writer  = $writer;
10225
		return $this;
10226
	}
10227
10228
	/**
10229
	 * Returns the query writer in this toolbox.
10230
	 * The Query Writer is responsible for building the queries for a
10231
	 * specific database and executing them through the adapter.
10232
	 *
10233
	 * Usage:
10234
	 *
10235
	 * <code>
10236
	 * $toolbox = R::getToolBox();
10237
	 * $redbean = $toolbox->getRedBean();
10238
	 * $adapter = $toolbox->getDatabaseAdapter();
10239
	 * $writer  = $toolbox->getWriter();
10240
	 * </code>
10241
	 *
10242
	 * The example above illustrates how to obtain the core objects
10243
	 * from a toolbox instance. If you are working with the R-object
10244
	 * only, the following shortcuts exist as well:
10245
	 *
10246
	 * - R::getRedBean()
10247
	 * - R::getDatabaseAdapter()
10248
	 * - R::getWriter()
10249
	 *
10250
	 * @return QueryWriter
10251
	 */
10252
	public function getWriter()
10253
	{
10254
		return $this->writer;
10255
	}
10256
10257
	/**
10258
	 * Returns the OODB instance in this toolbox.
10259
	 * OODB is responsible for creating, storing, retrieving and deleting
10260
	 * single beans. Other components rely
10261
	 * on OODB for their basic functionality.
10262
	 *
10263
	 * Usage:
10264
	 *
10265
	 * <code>
10266
	 * $toolbox = R::getToolBox();
10267
	 * $redbean = $toolbox->getRedBean();
10268
	 * $adapter = $toolbox->getDatabaseAdapter();
10269
	 * $writer  = $toolbox->getWriter();
10270
	 * </code>
10271
	 *
10272
	 * The example above illustrates how to obtain the core objects
10273
	 * from a toolbox instance. If you are working with the R-object
10274
	 * only, the following shortcuts exist as well:
10275
	 *
10276
	 * - R::getRedBean()
10277
	 * - R::getDatabaseAdapter()
10278
	 * - R::getWriter()
10279
	 *
10280
	 * @return OODB
10281
	 */
10282
	public function getRedBean()
10283
	{
10284
		return $this->oodb;
10285
	}
10286
10287
	/**
10288
	 * Returns the database adapter in this toolbox.
10289
	 * The adapter is responsible for executing the query and binding the values.
10290
	 * The adapter also takes care of transaction handling.
10291
	 *
10292
	 * Usage:
10293
	 *
10294
	 * <code>
10295
	 * $toolbox = R::getToolBox();
10296
	 * $redbean = $toolbox->getRedBean();
10297
	 * $adapter = $toolbox->getDatabaseAdapter();
10298
	 * $writer  = $toolbox->getWriter();
10299
	 * </code>
10300
	 *
10301
	 * The example above illustrates how to obtain the core objects
10302
	 * from a toolbox instance. If you are working with the R-object
10303
	 * only, the following shortcuts exist as well:
10304
	 *
10305
	 * - R::getRedBean()
10306
	 * - R::getDatabaseAdapter()
10307
	 * - R::getWriter()
10308
	 *
10309
	 * @return DBAdapter
10310
	 */
10311
	public function getDatabaseAdapter()
10312
	{
10313
		return $this->adapter;
10314
	}
10315
}
10316
}
10317
10318
namespace RedBeanPHP {
10319
10320
10321
/**
10322
 * RedBeanPHP Finder.
10323
 * Service class to find beans. For the most part this class
10324
 * offers user friendly utility methods for interacting with the
10325
 * OODB::find() method, which is rather complex. This class can be
10326
 * used to find beans using plain old SQL queries.
10327
 *
10328
 * @file    RedBeanPHP/Finder.php
10329
 * @author  Gabor de Mooij and the RedBeanPHP Community
10330
 * @license BSD/GPLv2
10331
 *
10332
 * @copyright
10333
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
10334
 * This source file is subject to the BSD/GPLv2 License that is bundled
10335
 * with this source code in the file license.txt.
10336
 */
10337
class Finder
10338
{
10339
	/**
10340
	 * @var ToolBox
10341
	 */
10342
	protected $toolbox;
10343
10344
	/**
10345
	 * @var OODB
10346
	 */
10347
	protected $redbean;
10348
10349
	/**
10350
	 * Constructor.
10351
	 * The Finder requires a toolbox.
10352
	 *
10353
	 * @param ToolBox $toolbox
10354
	 */
10355
	public function __construct( ToolBox $toolbox )
10356
	{
10357
		$this->toolbox = $toolbox;
10358
		$this->redbean = $toolbox->getRedBean();
10359
	}
10360
10361
	/**
10362
	 * A custom record-to-bean mapping function for findMulti.
10363
	 *
10364
	 * Usage:
10365
	 *
10366
	 * <code>
10367
	 * $collection = R::findMulti( 'shop,product,price',
10368
	 * 'SELECT shop.*, product.*, price.* FROM shop
10369
	 *	LEFT JOIN product ON product.shop_id = shop.id
10370
	 *	LEFT JOIN price ON price.product_id = product.id', [], [
10371
	 *		Finder::map( 'shop', 'product' ),
10372
	 *		Finder::map( 'product', 'price' ),
10373
	 *	]);
10374
	 * </code>
10375
	 *
10376
	 * @param string $parentName name of the parent bean
10377
	 * @param string $childName  name of the child bean
10378
	 *
10379
	 * @return array
10380
	 */
10381
	public static function map($parentName,$childName) {
10382
		return array(
10383
			'a' => $parentName,
10384
			'b' => $childName,
10385
			'matcher' => function( $parent, $child ) use ( $parentName, $childName ) {
10386
				$propertyName = 'own' . ucfirst( $childName );
10387
				if (!isset($parent[$propertyName])) {
10388
					$parent->noLoad()->{$propertyName} = array();
10389
				}
10390
				$property = "{$parentName}ID";
10391
				return ( $child->$property == $parent->id );
10392
			},
10393
			'do' => function( $parent, $child ) use ( $childName ) {
10394
				$list = 'own'.ucfirst( $childName ).'List';
10395
				$parent->noLoad()->{$list}[$child->id] = $child;
10396
			}
10397
		);
10398
	}
10399
10400
	/**
10401
	* A custom record-to-bean mapping function for findMulti.
10402
	*
10403
	* Usage:
10404
	*
10405
	* <code>
10406
	* $collection = R::findMulti( 'book,book_tag,tag',
10407
	* 'SELECT book.*, book_tag.*, tag.* FROM book
10408
	*      LEFT JOIN book_tag ON book_tag.book_id = book.id
10409
	*      LEFT JOIN tag ON book_tag.tag_id = tag.id', [], [
10410
	*              Finder::nmMap( 'book', 'tag' ),
10411
	*      ]);
10412
	* </code>
10413
	*
10414
	* @param string $parentName name of the parent bean
10415
	* @param string $childName  name of the child bean
10416
	*
10417
	* @return array
10418
	*/
10419
	public static function nmMap( $parentName, $childName )
10420
	{
10421
		$types = array($parentName, $childName);
10422
		sort( $types );
10423
		$link = implode( '_', $types );
10424
		return array(
10425
			'a' => $parentName,
10426
			'b' => $childName,
10427
			'matcher' => function( $parent, $child, $beans ) use ( $parentName, $childName, $link ) {
10428
				$propertyName = 'shared' . ucfirst( $childName );
10429
				if (!isset($parent[$propertyName])) {
10430
					$parent->noLoad()->{$propertyName} = array();
10431
				}
10432
				foreach( $beans[$link] as $linkBean ) {
10433
					if ( $linkBean["{$parentName}ID"] == $parent->id && $linkBean["{$childName}ID"] == $child->id ) {
10434
						return true;
10435
					}
10436
				}
10437
			},
10438
			'do' => function( $parent, $child ) use ( $childName ) {
10439
				$list = 'shared'.ucfirst( $childName ).'List';
10440
				$parent->noLoad()->{$list}[$child->id] = $child;
10441
			}
10442
		);
10443
	}
10444
10445
	/**
10446
	 * Finds a bean using a type and a where clause (SQL).
10447
	 * As with most Query tools in RedBean you can provide values to
10448
	 * be inserted in the SQL statement by populating the value
10449
	 * array parameter; you can either use the question mark notation
10450
	 * or the slot-notation (:keyname).
10451
	 *
10452
	 * @param string $type     type   the type of bean you are looking for
10453
	 * @param string $sql      sql    SQL query to find the desired bean, starting right after WHERE clause
10454
	 * @param array  $bindings values array of values to be bound to parameters in query
10455
	 *
10456
	 * @return array
10457
	 */
10458
	public function find( $type, $sql = NULL, $bindings = array() )
10459
	{
10460
		if ( !is_array( $bindings ) ) {
10461
			throw new RedException(
10462
				'Expected array, ' . gettype( $bindings ) . ' given.'
10463
			);
10464
		}
10465
10466
		return $this->redbean->find( $type, array(), $sql, $bindings );
10467
	}
10468
10469
	/**
10470
	 * Like find() but also exports the beans as an array.
10471
	 * This method will perform a find-operation. For every bean
10472
	 * in the result collection this method will call the export() method.
10473
	 * This method returns an array containing the array representations
10474
	 * of every bean in the result set.
10475
	 *
10476
	 * @see Finder::find
10477
	 *
10478
	 * @param string $type     type   the type of bean you are looking for
10479
	 * @param string $sql      sql    SQL query to find the desired bean, starting right after WHERE clause
10480
	 * @param array  $bindings values array of values to be bound to parameters in query
10481
	 *
10482
	 * @return array
10483
	 */
10484
	public function findAndExport( $type, $sql = NULL, $bindings = array() )
10485
	{
10486
		$arr = array();
10487
		foreach ( $this->find( $type, $sql, $bindings ) as $key => $item ) {
10488
			$arr[] = $item->export();
10489
		}
10490
10491
		return $arr;
10492
	}
10493
10494
	/**
10495
	 * Like find() but returns just one bean instead of an array of beans.
10496
	 * This method will return only the first bean of the array.
10497
	 * If no beans are found, this method will return NULL.
10498
	 *
10499
	 * @see Finder::find
10500
	 *
10501
	 * @param string $type     type   the type of bean you are looking for
10502
	 * @param string $sql      sql    SQL query to find the desired bean, starting right after WHERE clause
10503
	 * @param array  $bindings values array of values to be bound to parameters in query
10504
	 *
10505
	 * @return OODBBean|NULL
10506
	 */
10507
	public function findOne( $type, $sql = NULL, $bindings = array() )
10508
	{
10509
		$sql = $this->toolbox->getWriter()->glueLimitOne( $sql );
10510
10511
		$items = $this->find( $type, $sql, $bindings );
10512
10513
		if ( empty($items) ) {
10514
			return NULL;
10515
		}
10516
10517
		return reset( $items );
10518
	}
10519
10520
	/**
10521
	 * Like find() but returns the last bean of the result array.
10522
	 * Opposite of Finder::findLast().
10523
	 * If no beans are found, this method will return NULL.
10524
	 *
10525
	 * @see Finder::find
10526
	 *
10527
	 * @param string $type     the type of bean you are looking for
10528
	 * @param string $sql      SQL query to find the desired bean, starting right after WHERE clause
10529
	 * @param array  $bindings values array of values to be bound to parameters in query
10530
	 *
10531
	 * @return OODBBean|NULL
10532
	 */
10533
	public function findLast( $type, $sql = NULL, $bindings = array() )
10534
	{
10535
		$items = $this->find( $type, $sql, $bindings );
10536
10537
		if ( empty($items) ) {
10538
			return NULL;
10539
		}
10540
10541
		return end( $items );
10542
	}
10543
10544
	/**
10545
	 * Tries to find beans of a certain type,
10546
	 * if no beans are found, it dispenses a bean of that type.
10547
	 * Note that this function always returns an array.
10548
	 *
10549
	 * @see Finder::find
10550
	 *
10551
	 * @param  string $type     the type of bean you are looking for
10552
	 * @param  string $sql      SQL query to find the desired bean, starting right after WHERE clause
10553
	 * @param  array  $bindings values array of values to be bound to parameters in query
10554
	 *
10555
	 * @return array
10556
	 */
10557
	public function findOrDispense( $type, $sql = NULL, $bindings = array() )
10558
	{
10559
		$foundBeans = $this->find( $type, $sql, $bindings );
10560
10561
		if ( empty( $foundBeans ) ) {
10562
			return array( $this->redbean->dispense( $type ) );
10563
		} else {
10564
			return $foundBeans;
10565
		}
10566
	}
10567
10568
	/**
10569
	 * Finds a BeanCollection using the repository.
10570
	 * A bean collection can be used to retrieve one bean at a time using
10571
	 * cursors - this is useful for processing large datasets. A bean collection
10572
	 * will not load all beans into memory all at once, just one at a time.
10573
	 *
10574
	 * @param  string $type     the type of bean you are looking for
10575
	 * @param  string $sql      SQL query to find the desired bean, starting right after WHERE clause
10576
	 * @param  array  $bindings values array of values to be bound to parameters in query
10577
	 *
10578
	 * @return BeanCollection
10579
	 */
10580
	public function findCollection( $type, $sql, $bindings = array() )
10581
	{
10582
		return $this->redbean->findCollection( $type, $sql, $bindings );
10583
	}
10584
10585
	/**
10586
	 * Finds or creates a bean.
10587
	 * Tries to find a bean with certain properties specified in the second
10588
	 * parameter ($like). If the bean is found, it will be returned.
10589
	 * If multiple beans are found, only the first will be returned.
10590
	 * If no beans match the criteria, a new bean will be dispensed,
10591
	 * the criteria will be imported as properties and this new bean
10592
	 * will be stored and returned.
10593
	 *
10594
	 * Format of criteria set: property => value
10595
	 * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
10596
	 *
10597
	 * @param string $type type of bean to search for
10598
	 * @param array  $like criteria set describing bean to search for
10599
	 *
10600
	 * @return OODBBean
10601
	 */
10602
	public function findOrCreate( $type, $like = array(), $sql = '' )
10603
	{
10604
			$sql = $this->toolbox->getWriter()->glueLimitOne( $sql );
10605
			$beans = $this->findLike( $type, $like, $sql );
10606
			if ( count( $beans ) ) {
10607
				$bean = reset( $beans );
10608
				return $bean;
10609
			}
10610
10611
			$bean = $this->redbean->dispense( $type );
10612
			$bean->import( $like );
10613
			$this->redbean->store( $bean );
10614
			return $bean;
10615
	}
10616
10617
	/**
10618
	 * Finds beans by its type and a certain criteria set.
10619
	 *
10620
	 * Format of criteria set: property => value
10621
	 * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
10622
	 *
10623
	 * If the additional SQL is a condition, this condition will be glued to the rest
10624
	 * of the query using an AND operator. Note that this is as far as this method
10625
	 * can go, there is no way to glue additional SQL using an OR-condition.
10626
	 * This method provides access to an underlying mechanism in the RedBeanPHP architecture
10627
	 * to find beans using criteria sets. However, please do not use this method
10628
	 * for complex queries, use plain SQL instead ( the regular find method ) as it is
10629
	 * more suitable for the job. This method is
10630
	 * meant for basic search-by-example operations.
10631
	 *
10632
	 * @param string $type       type of bean to search for
10633
	 * @param array  $conditions criteria set describing the bean to search for
10634
	 * @param string $sql        additional SQL (for sorting)
10635
	 * @param array  $bindings   bindings
10636
	 *
10637
	 * @return array
10638
	 */
10639
	public function findLike( $type, $conditions = array(), $sql = '', $bindings = array() )
10640
	{
10641
		return $this->redbean->find( $type, $conditions, $sql, $bindings );
10642
	}
10643
10644
	/**
10645
	 * Returns a hashmap with bean arrays keyed by type using an SQL
10646
	 * query as its resource. Given an SQL query like 'SELECT movie.*, review.* FROM movie... JOIN review'
10647
	 * this method will return movie and review beans.
10648
	 *
10649
	 * Example:
10650
	 *
10651
	 * <code>
10652
	 * $stuff = $finder->findMulti('movie,review', '
10653
	 *          SELECT movie.*, review.* FROM movie
10654
	 *          LEFT JOIN review ON review.movie_id = movie.id');
10655
	 * </code>
10656
	 *
10657
	 * After this operation, $stuff will contain an entry 'movie' containing all
10658
	 * movies and an entry named 'review' containing all reviews (all beans).
10659
	 * You can also pass bindings.
10660
	 *
10661
	 * If you want to re-map your beans, so you can use $movie->ownReviewList without
10662
	 * having RedBeanPHP executing an SQL query you can use the fourth parameter to
10663
	 * define a selection of remapping closures.
10664
	 *
10665
	 * The remapping argument (optional) should contain an array of arrays.
10666
	 * Each array in the remapping array should contain the following entries:
10667
	 *
10668
	 * <code>
10669
	 * array(
10670
	 * 	'a'       => TYPE A
10671
	 *    'b'       => TYPE B
10672
	 *    'matcher' => MATCHING FUNCTION ACCEPTING A, B and ALL BEANS
10673
	 *    'do'      => OPERATION FUNCTION ACCEPTING A, B, ALL BEANS, ALL REMAPPINGS
10674
	 * )
10675
	 * </code>
10676
	 *
10677
	 * Using this mechanism you can build your own 'preloader' with tiny function
10678
	 * snippets (and those can be re-used and shared online of course).
10679
	 *
10680
	 * Example:
10681
	 *
10682
	 * <code>
10683
	 * array(
10684
	 * 	'a'       => 'movie'     //define A as movie
10685
	 *    'b'       => 'review'    //define B as review
10686
	 *    'matcher' => function( $a, $b ) {
10687
	 *       return ( $b->movie_id == $a->id );  //Perform action if review.movie_id equals movie.id
10688
	 *    }
10689
	 *    'do'      => function( $a, $b ) {
10690
	 *       $a->noLoad()->ownReviewList[] = $b; //Add the review to the movie
10691
	 *       $a->clearHistory();                 //optional, act 'as if these beans have been loaded through ownReviewList'.
10692
	 *    }
10693
	 * )
10694
	 * </code>
10695
	 *
10696
	 * The Query Template parameter is optional as well but can be used to
10697
	 * set a different SQL template (sprintf-style) for processing the original query.
10698
	 *
10699
	 * @note the SQL query provided IS NOT THE ONE used internally by this function,
10700
	 * this function will pre-process the query to get all the data required to find the beans.
10701
	 *
10702
	 * @note if you use the 'book.*' notation make SURE you're
10703
	 * selector starts with a SPACE. ' book.*' NOT ',book.*'. This is because
10704
	 * it's actually an SQL-like template SLOT, not real SQL.
10705
	 *
10706
	 * @note instead of an SQL query you can pass a result array as well.
10707
	 *
10708
	 * @param string|array $types         a list of types (either array or comma separated string)
10709
	 * @param string|array $sql           optional, an SQL query or an array of prefetched records
10710
	 * @param array        $bindings      optional, bindings for SQL query
10711
	 * @param array        $remappings    optional, an array of remapping arrays
10712
	 * @param string       $queryTemplate optional, query template
10713
	 *
10714
	 * @return array
10715
	 */
10716
	public function findMulti( $types, $sql = NULL, $bindings = array(), $remappings = array(), $queryTemplate = ' %s.%s AS %s__%s' )
10717
	{
10718
		if ( !is_array( $types ) ) $types = array_map( 'trim', explode( ',', $types ) );
10719
		if ( is_null( $sql ) ) {
10720
			$beans = array();
10721
			foreach( $types as $type ) $beans[$type] = $this->redbean->find( $type );
10722
		} else {
10723
			if ( !is_array( $sql ) ) {
10724
				$writer = $this->toolbox->getWriter();
10725
				$adapter = $this->toolbox->getDatabaseAdapter();
10726
10727
				//Repair the query, replace book.* with book.id AS book_id etc..
10728
				foreach( $types as $type ) {
10729
					$regex = "#( (`?{$type}`?)\.\*)#";
10730
					if ( preg_match( $regex, $sql, $matches ) ) {
10731
						$pattern = $matches[1];
10732
						$table = $matches[2];
10733
						$newSelectorArray = array();
10734
						$columns = $writer->getColumns( $type );
10735
						foreach( $columns as $column => $definition ) {
10736
							$newSelectorArray[] = sprintf( $queryTemplate, $table, $column, $type, $column );
10737
						}
10738
						$newSelector = implode( ',', $newSelectorArray );
10739
						$sql = str_replace( $pattern, $newSelector, $sql );
10740
					}
10741
				}
10742
10743
				$rows = $adapter->get( $sql, $bindings );
10744
			} else {
10745
				$rows = $sql;
10746
			}
10747
10748
			//Gather the bean data from the query results using the prefix
10749
			$wannaBeans = array();
10750
			foreach( $types as $type ) {
10751
				$wannaBeans[$type] = array();
10752
				$prefix            = "{$type}__";
10753
				foreach( $rows as $rowkey=>$row ) {
10754
					$wannaBean = array();
10755
					foreach( $row as $cell => $value ) {
10756
						if ( strpos( $cell, $prefix ) === 0 ) {
10757
							$property = substr( $cell, strlen( $prefix ) );
10758
							unset( $rows[$rowkey][$cell] );
10759
							$wannaBean[$property] = $value;
10760
						}
10761
					}
10762
					if ( !isset( $wannaBean['id'] ) ) continue;
10763
					if ( is_null( $wannaBean['id'] ) ) continue;
10764
					$wannaBeans[$type][$wannaBean['id']] = $wannaBean;
10765
				}
10766
			}
10767
10768
			//Turn the rows into beans
10769
			$beans = array();
10770
			foreach( $wannaBeans as $type => $wannabees ) {
10771
				$beans[$type] = $this->redbean->convertToBeans( $type, $wannabees );
10772
			}
10773
		}
10774
10775
		//Apply additional re-mappings
10776
		foreach($remappings as $remapping) {
10777
			$a       = $remapping['a'];
10778
			$b       = $remapping['b'];
10779
			$matcher = $remapping['matcher'];
10780
			$do      = $remapping['do'];
10781
			foreach( $beans[$a] as $bean ) {
10782
				foreach( $beans[$b] as $putBean ) {
10783
					if ( $matcher( $bean, $putBean, $beans ) ) $do( $bean, $putBean, $beans, $remapping );
10784
				}
10785
			}
10786
		}
10787
		return $beans;
10788
	}
10789
}
10790
}
10791
10792
namespace RedBeanPHP {
10793
10794
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
10795
use RedBeanPHP\QueryWriter as QueryWriter;
10796
use RedBeanPHP\RedException as RedException;
10797
use RedBeanPHP\RedException\SQL as SQLException;
10798
10799
/**
10800
 * Association Manager.
10801
 * The association manager can be used to create and manage
10802
 * many-to-many relations (for example sharedLists). In a many-to-many relation,
10803
 * one bean can be associated with many other beans, while each of those beans
10804
 * can also be related to multiple beans.
10805
 *
10806
 * @file    RedBeanPHP/AssociationManager.php
10807
 * @author  Gabor de Mooij and the RedBeanPHP Community
10808
 * @license BSD/GPLv2
10809
 *
10810
 * @copyright
10811
 * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
10812
 * This source file is subject to the BSD/GPLv2 License that is bundled
10813
 * with this source code in the file license.txt.
10814
 */
10815
class AssociationManager extends Observable
10816
{
10817
	/**
10818
	 * @var OODB
10819
	 */
10820
	protected $oodb;
10821
10822
	/**
10823
	 * @var DBAdapter
10824
	 */
10825
	protected $adapter;
10826
10827
	/**
10828
	 * @var QueryWriter
10829
	 */
10830
	protected $writer;
10831
10832
	/**
10833
	 * Exception handler.
10834
	 * Fluid and Frozen mode have different ways of handling
10835
	 * exceptions. Fluid mode (using the fluid repository) ignores
10836
	 * exceptions caused by the following:
10837
	 *
10838
	 * - missing tables
10839
	 * - missing column
10840
	 *
10841
	 * In these situations, the repository will behave as if
10842
	 * no beans could be found. This is because in fluid mode
10843
	 * it might happen to query a table or column that has not been
10844
	 * created yet. In frozen mode, this is not supposed to happen
10845
	 * and the corresponding exceptions will be thrown.
10846
	 *
10847
	 * @param \Exception $exception exception
10848
	 *
10849
	 * @return void
10850
	 */
10851
	private function handleException( \Exception $exception )
10852
	{
10853
		if ( $this->oodb->isFrozen() || !$this->writer->sqlStateIn( $exception->getSQLState(),
10854
			array(
10855
				QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
10856
				QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ),
10857
				$exception->getDriverDetails()
10858
			)
10859
		) {
10860
			throw $exception;
10861
		}
10862
	}
10863
10864
	/**
10865
	 * Internal method.
10866
	 * Returns the many-to-many related rows of table $type for bean $bean using additional SQL in $sql and
10867
	 * $bindings bindings. If $getLinks is TRUE, link rows are returned instead.
10868
	 *
10869
	 * @param OODBBean $bean     reference bean instance
10870
	 * @param string   $type     target bean type
10871
	 * @param string   $sql      additional SQL snippet
10872
	 * @param array    $bindings bindings for query
10873
	 *
10874
	 * @return array
10875
	 */
10876
	private function relatedRows( $bean, $type, $sql = '', $bindings = array() )
10877
	{
10878
		$ids = array( $bean->id );
10879
		$sourceType = $bean->getMeta( 'type' );
10880
		try {
10881
			return $this->writer->queryRecordRelated( $sourceType, $type, $ids, $sql, $bindings );
10882
		} catch ( SQLException $exception ) {
10883
			$this->handleException( $exception );
10884
			return array();
10885
		}
10886
	}
10887
10888
	/**
10889
	 * Associates a pair of beans. This method associates two beans, no matter
10890
	 * what types. Accepts a base bean that contains data for the linking record.
10891
	 * This method is used by associate. This method also accepts a base bean to be used
10892
	 * as the template for the link record in the database.
10893
	 *
10894
	 * @param OODBBean $bean1 first bean
10895
	 * @param OODBBean $bean2 second bean
10896
	 * @param OODBBean $bean  base bean (association record)
10897
	 *
10898
	 * @return mixed
10899
	 */
10900
	protected function associateBeans( OODBBean $bean1, OODBBean $bean2, OODBBean $bean )
10901
	{
10902
		$type      = $bean->getMeta( 'type' );
10903
		$property1 = $bean1->getMeta( 'type' ) . '_id';
10904
		$property2 = $bean2->getMeta( 'type' ) . '_id';
10905
10906
		if ( $property1 == $property2 ) {
10907
			$property2 = $bean2->getMeta( 'type' ) . '2_id';
10908
		}
10909
10910
		$this->oodb->store( $bean1 );
10911
		$this->oodb->store( $bean2 );
10912
10913
		$bean->setMeta( "cast.$property1", "id" );
10914
		$bean->setMeta( "cast.$property2", "id" );
10915
		$bean->setMeta( 'sys.buildcommand.unique', array( $property1, $property2 ) );
10916
10917
		$bean->$property1 = $bean1->id;
10918
		$bean->$property2 = $bean2->id;
10919
10920
		$results   = array();
10921
10922
		try {
10923
			$id = $this->oodb->store( $bean );
10924
			$results[] = $id;
10925
		} catch ( SQLException $exception ) {
10926
			if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
10927
				array( QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION ),
10928
				$exception->getDriverDetails() )
10929
			) {
10930
				throw $exception;
10931
			}
10932
		}
10933
10934
		return $results;
10935
	}
10936
10937
	/**
10938
	 * Constructor, creates a new instance of the Association Manager.
10939
	 * The association manager can be used to create and manage
10940
	 * many-to-many relations (for example sharedLists). In a many-to-many relation,
10941
	 * one bean can be associated with many other beans, while each of those beans
10942
	 * can also be related to multiple beans. To create an Association Manager
10943
	 * instance you'll need to pass a ToolBox object.
10944
	 *
10945
	 * @param ToolBox $tools toolbox supplying core RedBeanPHP objects
10946
	 */
10947
	public function __construct( ToolBox $tools )
10948
	{
10949
		$this->oodb    = $tools->getRedBean();
10950
		$this->adapter = $tools->getDatabaseAdapter();
10951
		$this->writer  = $tools->getWriter();
10952
		$this->toolbox = $tools;
10953
	}
10954
10955
	/**
10956
	 * Creates a table name based on a types array.
10957
	 * Manages the get the correct name for the linking table for the
10958
	 * types provided.
10959
	 *
10960
	 * @param array $types 2 types as strings
10961
	 *
10962
	 * @return string
10963
	 */
10964
	public function getTable( $types )
10965
	{
10966
		return $this->writer->getAssocTable( $types );
10967
	}
10968
10969
	/**
10970
	 * Associates two beans in a many-to-many relation.
10971
	 * This method will associate two beans and store the connection between the
10972
	 * two in a link table. Instead of two single beans this method also accepts
10973
	 * two sets of beans. Returns the ID or the IDs of the linking beans.
10974
	 *
10975
	 * @param OODBBean|array $beans1 one or more beans to form the association
10976
	 * @param OODBBean|array $beans2 one or more beans to form the association
10977
	 *
10978
	 * @return array
10979
	 */
10980
	public function associate( $beans1, $beans2 )
10981
	{
10982
		if ( !is_array( $beans1 ) ) {
10983
			$beans1 = array( $beans1 );
10984
		}
10985
10986
		if ( !is_array( $beans2 ) ) {
10987
			$beans2 = array( $beans2 );
10988
		}
10989
10990
		$results = array();
10991
		foreach ( $beans1 as $bean1 ) {
10992
			foreach ( $beans2 as $bean2 ) {
10993
				$table     = $this->getTable( array( $bean1->getMeta( 'type' ), $bean2->getMeta( 'type' ) ) );
10994
				$bean      = $this->oodb->dispense( $table );
10995
				$results[] = $this->associateBeans( $bean1, $bean2, $bean );
10996
			}
10997
		}
10998
10999
		return ( count( $results ) > 1 ) ? $results : reset( $results );
11000
	}
11001
11002
	/**
11003
	 * Counts the number of related beans in an N-M relation.
11004
	 * This method returns the number of beans of type $type associated
11005
	 * with reference bean(s) $bean. The query can be tuned using an
11006
	 * SQL snippet for additional filtering.
11007
	 *
11008
	 * @param OODBBean|array $bean     a bean object or an array of beans
11009
	 * @param string         $type     type of bean you're interested in
11010
	 * @param string         $sql      SQL snippet (optional)
11011
	 * @param array          $bindings bindings for your SQL string
11012
	 *
11013
	 * @return integer
11014
	 */
11015
	public function relatedCount( $bean, $type, $sql = NULL, $bindings = array() )
11016
	{
11017
		if ( !( $bean instanceof OODBBean ) ) {
11018
			throw new RedException(
11019
				'Expected array or OODBBean but got:' . gettype( $bean )
11020
			);
11021
		}
11022
11023
		if ( !$bean->id ) {
11024
			return 0;
11025
		}
11026
11027
		$beanType = $bean->getMeta( 'type' );
11028
11029
		try {
11030
			return $this->writer->queryRecordCountRelated( $beanType, $type, $bean->id, $sql, $bindings );
11031
		} catch ( SQLException $exception ) {
11032
			$this->handleException( $exception );
11033
11034
			return 0;
11035
		}
11036
	}
11037
11038
	/**
11039
	 * Breaks the association between two beans. This method unassociates two beans. If the
11040
	 * method succeeds the beans will no longer form an association. In the database
11041
	 * this means that the association record will be removed. This method uses the
11042
	 * OODB trash() method to remove the association links, thus giving FUSE models the
11043
	 * opportunity to hook-in additional business logic. If the $fast parameter is
11044
	 * set to boolean TRUE this method will remove the beans without their consent,
11045
	 * bypassing FUSE. This can be used to improve performance.
11046
	 *
11047
	 * @param OODBBean $beans1 first bean in target association
11048
	 * @param OODBBean $beans2 second bean in target association
11049
	 * @param boolean  $fast  if TRUE, removes the entries by query without FUSE
11050
	 *
11051
	 * @return void
11052
	 */
11053
	public function unassociate( $beans1, $beans2, $fast = NULL )
11054
	{
11055
		$beans1 = ( !is_array( $beans1 ) ) ? array( $beans1 ) : $beans1;
11056
		$beans2 = ( !is_array( $beans2 ) ) ? array( $beans2 ) : $beans2;
11057
11058
		foreach ( $beans1 as $bean1 ) {
11059
			foreach ( $beans2 as $bean2 ) {
11060
				try {
11061
					$this->oodb->store( $bean1 );
11062
					$this->oodb->store( $bean2 );
11063
11064
					$type1 = $bean1->getMeta( 'type' );
11065
					$type2 = $bean2->getMeta( 'type' );
11066
11067
					$row      = $this->writer->queryRecordLink( $type1, $type2, $bean1->id, $bean2->id );
11068
					$linkType = $this->getTable( array( $type1, $type2 ) );
11069
11070
					if ( $fast ) {
11071
						$this->writer->deleteRecord( $linkType, array( 'id' => $row['id'] ) );
11072
11073
						return;
11074
					}
11075
11076
					$beans = $this->oodb->convertToBeans( $linkType, array( $row ) );
11077
11078
					if ( count( $beans ) > 0 ) {
11079
						$bean = reset( $beans );
11080
						$this->oodb->trash( $bean );
11081
					}
11082
				} catch ( SQLException $exception ) {
11083
					$this->handleException( $exception );
11084
				}
11085
			}
11086
		}
11087
	}
11088
11089
	/**
11090
	 * Removes all relations for a bean. This method breaks every connection between
11091
	 * a certain bean $bean and every other bean of type $type. Warning: this method
11092
	 * is really fast because it uses a direct SQL query however it does not inform the
11093
	 * models about this. If you want to notify FUSE models about deletion use a foreach-loop
11094
	 * with unassociate() instead. (that might be slower though)
11095
	 *
11096
	 * @param OODBBean $bean reference bean
11097
	 * @param string   $type type of beans that need to be unassociated
11098
	 *
11099
	 * @return void
11100
	 */
11101
	public function clearRelations( OODBBean $bean, $type )
11102
	{
11103
		$this->oodb->store( $bean );
11104
		try {
11105
			$this->writer->deleteRelations( $bean->getMeta( 'type' ), $type, $bean->id );
11106
		} catch ( SQLException $exception ) {
11107
			$this->handleException( $exception );
11108
		}
11109
	}
11110
11111
	/**
11112
	 * Returns all the beans associated with $bean.
11113
	 * This method will return an array containing all the beans that have
11114
	 * been associated once with the associate() function and are still
11115
	 * associated with the bean specified. The type parameter indicates the
11116
	 * type of beans you are looking for. You can also pass some extra SQL and
11117
	 * values for that SQL to filter your results after fetching the
11118
	 * related beans.
11119
	 *
11120
	 * Don't try to make use of subqueries, a subquery using IN() seems to
11121
	 * be slower than two queries!
11122
	 *
11123
	 * Since 3.2, you can now also pass an array of beans instead just one
11124
	 * bean as the first parameter.
11125
	 *
11126
	 * @param OODBBean|array $bean the bean you have
11127
	 * @param string         $type      the type of beans you want
11128
	 * @param string         $sql       SQL snippet for extra filtering
11129
	 * @param array          $bindings  values to be inserted in SQL slots
11130
	 *
11131
	 * @return array
11132
	 */
11133
	public function related( $bean, $type, $sql = '', $bindings = array() )
11134
	{
11135
		$sql   = $this->writer->glueSQLCondition( $sql );
11136
		$rows  = $this->relatedRows( $bean, $type, $sql, $bindings );
11137
		$links = array();
11138
11139
		foreach ( $rows as $key => $row ) {
11140
			if ( !isset( $links[$row['id']] ) ) $links[$row['id']] = array();
11141
			$links[$row['id']][] = $row['linked_by'];
11142
			unset( $rows[$key]['linked_by'] );
11143
		}
11144
11145
		$beans = $this->oodb->convertToBeans( $type, $rows );
11146
		foreach ( $beans as $bean ) $bean->setMeta( 'sys.belongs-to', $links[$bean->id] );
11147
11148
		return $beans;
11149
	}
11150
}
11151
}
11152
11153
namespace RedBeanPHP {
11154
11155
use RedBeanPHP\ToolBox as ToolBox;
11156
use RedBeanPHP\OODBBean as OODBBean;
11157
11158
/**
11159
 * Bean Helper Interface.
11160
 *
11161
 * Interface for Bean Helper.
11162
 * A little bolt that glues the whole machinery together.
11163
 * The Bean Helper is passed to the OODB RedBeanPHP Object to
11164
 * faciliatte the creation of beans and providing them with
11165
 * a toolbox. The Helper also facilitates the FUSE feature,
11166
 * determining how beans relate to their models. By overriding
11167
 * the getModelForBean method you can tune the FUSEing to
11168
 * fit your business application needs.
11169
 *
11170
 * @file    RedBeanPHP/IBeanHelper.php
11171
 * @author  Gabor de Mooij and the RedBeanPHP Community
11172
 * @license BSD/GPLv2
11173
 *
11174
 * @copyright
11175
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
11176
 * This source file is subject to the BSD/GPLv2 License that is bundled
11177
 * with this source code in the file license.txt.
11178
 */
11179
interface BeanHelper
11180
{
11181
	/**
11182
	 * Returns a toolbox to empower the bean.
11183
	 * This allows beans to perform OODB operations by themselves,
11184
	 * as such the bean is a proxy for OODB. This allows beans to implement
11185
	 * their magic getters and setters and return lists.
11186
	 *
11187
	 * @return ToolBox
11188
	 */
11189
	public function getToolbox();
11190
11191
	/**
11192
	 * Does approximately the same as getToolbox but also extracts the
11193
	 * toolbox for you.
11194
	 * This method returns a list with all toolbox items in Toolbox Constructor order:
11195
	 * OODB, adapter, writer and finally the toolbox itself!.
11196
	 *
11197
	 * @return array
11198
	 */
11199
	public function getExtractedToolbox();
11200
11201
	/**
11202
	 * Given a certain bean this method will
11203
	 * return the corresponding model.
11204
	 *
11205
	 * @param OODBBean $bean bean to obtain the corresponding model of
11206
	 *
11207
	 * @return SimpleModel|CustomModel|NULL
11208
	 */
11209
	public function getModelForBean( OODBBean $bean );
11210
}
11211
}
11212
11213
namespace RedBeanPHP\BeanHelper {
11214
11215
use RedBeanPHP\BeanHelper as BeanHelper;
11216
use RedBeanPHP\Facade as Facade;
11217
use RedBeanPHP\OODBBean as OODBBean;
11218
use RedBeanPHP\SimpleModelHelper as SimpleModelHelper;
11219
11220
/**
11221
 * Bean Helper.
11222
 *
11223
 * The Bean helper helps beans to access access the toolbox and
11224
 * FUSE models. This Bean Helper makes use of the facade to obtain a
11225
 * reference to the toolbox.
11226
 *
11227
 * @file    RedBeanPHP/BeanHelperFacade.php
11228
 * @author  Gabor de Mooij and the RedBeanPHP Community
11229
 * @license BSD/GPLv2
11230
 *
11231
 * @copyright
11232
 * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
11233
 * This source file is subject to the BSD/GPLv2 License that is bundled
11234
 * with this source code in the file license.txt.
11235
 */
11236
class SimpleFacadeBeanHelper implements BeanHelper
11237
{
11238
	/**
11239
	 * Factory function to create instance of Simple Model, if any.
11240
	 *
11241
	 * @var \Closure
11242
	 */
11243
	private static $factory = null;
11244
11245
	/**
11246
	 * Factory method using a customizable factory function to create
11247
	 * the instance of the Simple Model.
11248
	 *
11249
	 * @param string $modelClassName name of the class
11250
	 *
11251
	 * @return SimpleModel
11252
	 */
11253
	public static function factory( $modelClassName )
11254
	{
11255
		$factory = self::$factory;
11256
		return ( $factory ) ? $factory( $modelClassName ) : new $modelClassName();
11257
	}
11258
11259
	/**
11260
	 * Sets the factory function to create the model when using FUSE
11261
	 * to connect a bean to a model.
11262
	 *
11263
	 * @param \Closure $factory factory function
11264
	 *
11265
	 * @return void
11266
	 */
11267
	public static function setFactoryFunction( $factory )
11268
	{
11269
		self::$factory = $factory;
11270
	}
11271
11272
	/**
11273
	 * @see BeanHelper::getToolbox
11274
	 */
11275
	public function getToolbox()
11276
	{
11277
		return Facade::getToolBox();
11278
	}
11279
11280
	/**
11281
	 * @see BeanHelper::getModelForBean
11282
	 */
11283
	public function getModelForBean( OODBBean $bean )
11284
	{
11285
		$model     = $bean->getMeta( 'type' );
11286
		$prefix    = defined( 'REDBEAN_MODEL_PREFIX' ) ? REDBEAN_MODEL_PREFIX : '\\Model_';
11287
11288
		if ( strpos( $model, '_' ) !== FALSE ) {
11289
			$modelParts = explode( '_', $model );
11290
			$modelName = '';
11291
			foreach( $modelParts as $part ) {
11292
				$modelName .= ucfirst( $part );
11293
			}
11294
			$modelName = $prefix . $modelName;
11295
			if ( !class_exists( $modelName ) ) {
11296
				$modelName = $prefix . ucfirst( $model );
11297
				if ( !class_exists( $modelName ) ) {
11298
					return NULL;
11299
				}
11300
			}
11301
		} else {
11302
			$modelName = $prefix . ucfirst( $model );
11303
			if ( !class_exists( $modelName ) ) {
11304
				return NULL;
11305
			}
11306
		}
11307
		$obj = self::factory( $modelName );
11308
		$obj->loadBean( $bean );
11309
		return $obj;
11310
	}
11311
11312
	/**
11313
	 * @see BeanHelper::getExtractedToolbox
11314
	 */
11315
	public function getExtractedToolbox()
11316
	{
11317
		return Facade::getExtractedToolbox();
11318
	}
11319
}
11320
}
11321
11322
namespace RedBeanPHP {
11323
11324
use RedBeanPHP\OODBBean as OODBBean;
11325
11326
/**
11327
 * SimpleModel
11328
 * Base Model For All RedBeanPHP Models using FUSE.
11329
 *
11330
 * RedBeanPHP FUSE is a mechanism to connect beans to posthoc
11331
 * models. Models are connected to beans by naming conventions.
11332
 * Actions on beans will result in actions on models.
11333
 *
11334
 * @file       RedBeanPHP/SimpleModel.php
11335
 * @author     Gabor de Mooij and the RedBeanPHP Team
11336
 * @license    BSD/GPLv2
11337
 *
11338
 * @copyright
11339
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
11340
 * This source file is subject to the BSD/GPLv2 License that is bundled
11341
 * with this source code in the file license.txt.
11342
 */
11343
class SimpleModel
11344
{
11345
	/**
11346
	 * @var OODBBean
11347
	 */
11348
	protected $bean;
11349
11350
	/**
11351
	 * Used by FUSE: the ModelHelper class to connect a bean to a model.
11352
	 * This method loads a bean in the model.
11353
	 *
11354
	 * @param OODBBean $bean bean to load
11355
	 *
11356
	 * @return void
11357
	 */
11358
	public function loadBean( OODBBean $bean )
11359
	{
11360
		$this->bean = $bean;
11361
	}
11362
11363
	/**
11364
	 * Magic Getter to make the bean properties available from
11365
	 * the $this-scope.
11366
	 *
11367
	 * @note this method returns a value, not a reference!
11368
	 *       To obtain a reference unbox the bean first!
11369
	 *
11370
	 * @param string $prop property to get
11371
	 *
11372
	 * @return mixed
11373
	 */
11374
	public function __get( $prop )
11375
	{
11376
		return $this->bean->$prop;
11377
	}
11378
11379
	/**
11380
	 * Magic Setter.
11381
	 * Sets the value directly as a bean property.
11382
	 *
11383
	 * @param string $prop  property to set value of
11384
	 * @param mixed  $value value to set
11385
	 *
11386
	 * @return void
11387
	 */
11388
	public function __set( $prop, $value )
11389
	{
11390
		$this->bean->$prop = $value;
11391
	}
11392
11393
	/**
11394
	 * Isset implementation.
11395
	 * Implements the isset function for array-like access.
11396
	 *
11397
	 * @param  string $key key to check
11398
	 *
11399
	 * @return boolean
11400
	 */
11401
	public function __isset( $key )
11402
	{
11403
		return isset( $this->bean->$key );
11404
	}
11405
11406
	/**
11407
	 * Box the bean using the current model.
11408
	 * This method wraps the current bean in this model.
11409
	 * This method can be reached using FUSE through a simple
11410
	 * OODBBean. The method returns a RedBeanPHP Simple Model.
11411
	 * This is useful if you would like to rely on PHP type hinting.
11412
	 * You can box your beans before passing them to functions or methods
11413
	 * with typed parameters.
11414
	 *
11415
	 * Note about beans vs models:
11416
	 * Use unbox to obtain the bean powering the model. If you want to use bean functionality,
11417
	 * you should -always- unbox first. While some functionality (like magic get/set) is
11418
	 * available in the model, this is just read-only. To use a model as a typical RedBean
11419
	 * OODBBean you should always unbox the model to a bean. Models are meant to
11420
	 * expose only domain logic added by the developer (business logic, no ORM logic).
11421
	 *
11422
	 * @return SimpleModel
11423
	 */
11424
	public function box()
11425
	{
11426
		return $this;
11427
	}
11428
11429
	/**
11430
	 * Unbox the bean from the model.
11431
	 * This method returns the bean inside the model.
11432
	 *
11433
	 * Note about beans vs models:
11434
	 * Use unbox to obtain the bean powering the model. If you want to use bean functionality,
11435
	 * you should -always- unbox first. While some functionality (like magic get/set) is
11436
	 * available in the model, this is just read-only. To use a model as a typical RedBean
11437
	 * OODBBean you should always unbox the model to a bean. Models are meant to
11438
	 * expose only domain logic added by the developer (business logic, no ORM logic).
11439
	 *
11440
	 * @return OODBBean
11441
	 */
11442
	public function unbox()
11443
	{
11444
		return $this->bean;
11445
	}
11446
}
11447
}
11448
11449
namespace RedBeanPHP {
11450
11451
use RedBeanPHP\Observer as Observer;
11452
use RedBeanPHP\OODBBean as OODBBean;
11453
use RedBeanPHP\Observable as Observable;
11454
11455
/**
11456
 * RedBean Model Helper.
11457
 *
11458
 * Connects beans to models.
11459
 * This is the core of so-called FUSE.
11460
 *
11461
 * @file    RedBeanPHP/ModelHelper.php
11462
 * @author  Gabor de Mooij and the RedBeanPHP Community
11463
 * @license BSD/GPLv2
11464
 *
11465
 * @copyright
11466
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
11467
 * This source file is subject to the BSD/GPLv2 License that is bundled
11468
 * with this source code in the file license.txt.
11469
 */
11470
class SimpleModelHelper implements Observer
11471
{
11472
	/**
11473
	 * Gets notified by an observable.
11474
	 * This method decouples the FUSE system from the actual beans.
11475
	 * If a FUSE event happens 'update', this method will attempt to
11476
	 * invoke the corresponding method on the bean.
11477
	 *
11478
	 * @param string  $eventName i.e. 'delete', 'after_delete'
11479
	 * @param OODBean $bean      affected bean
11480
	 *
11481
	 * @return void
11482
	 */
11483
	public function onEvent( $eventName, $bean )
11484
	{
11485
		$bean->$eventName();
11486
	}
11487
11488
	/**
11489
	 * Attaches the FUSE event listeners. Now the Model Helper will listen for
11490
	 * CRUD events. If a CRUD event occurs it will send a signal to the model
11491
	 * that belongs to the CRUD bean and this model will take over control from
11492
	 * there. This method will attach the following event listeners to the observable:
11493
	 *
11494
	 * - 'update'       (gets called by R::store, before the records gets inserted / updated)
11495
	 * - 'after_update' (gets called by R::store, after the records have been inserted / updated)
11496
	 * - 'open'         (gets called by R::load, after the record has been retrieved)
11497
	 * - 'delete'       (gets called by R::trash, before deletion of record)
11498
	 * - 'after_delete' (gets called by R::trash, after deletion)
11499
	 * - 'dispense'     (gets called by R::dispense)
11500
	 *
11501
	 * For every event type, this method will register this helper as a listener.
11502
	 * The observable will notify the listener (this object) with the event ID and the
11503
	 * affected bean. This helper will then process the event (onEvent) by invoking
11504
	 * the event on the bean. If a bean offers a method with the same name as the
11505
	 * event ID, this method will be invoked.
11506
	 *
11507
	 * @param Observable $observable object to observe
11508
	 *
11509
	 * @return void
11510
	 */
11511
	public function attachEventListeners( Observable $observable )
11512
	{
11513
		foreach ( array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ) as $eventID ) {
11514
			$observable->addEventListener( $eventID, $this );
11515
		}
11516
	}
11517
}
11518
}
11519
11520
namespace RedBeanPHP {
11521
11522
use RedBeanPHP\ToolBox as ToolBox;
11523
use RedBeanPHP\AssociationManager as AssociationManager;
11524
use RedBeanPHP\OODBBean as OODBBean;
11525
11526
/**
11527
 * RedBeanPHP Tag Manager.
11528
 *
11529
 * The tag manager offers an easy way to quickly implement basic tagging
11530
 * functionality.
11531
 *
11532
 * Provides methods to tag beans and perform tag-based searches in the
11533
 * bean database.
11534
 *
11535
 * @file       RedBeanPHP/TagManager.php
11536
 * @author     Gabor de Mooij and the RedBeanPHP community
11537
 * @license    BSD/GPLv2
11538
 *
11539
 * @copyright
11540
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
11541
 * This source file is subject to the BSD/GPLv2 License that is bundled
11542
 * with this source code in the file license.txt.
11543
 */
11544
class TagManager
11545
{
11546
	/**
11547
	 * @var ToolBox
11548
	 */
11549
	protected $toolbox;
11550
11551
	/**
11552
	 * @var AssociationManager
11553
	 */
11554
	protected $associationManager;
11555
11556
	/**
11557
	 * @var OODBBean
11558
	 */
11559
	protected $redbean;
11560
11561
	/**
11562
	 * Checks if the argument is a comma separated string, in this case
11563
	 * it will split the string into words and return an array instead.
11564
	 * In case of an array the argument will be returned 'as is'.
11565
	 *
11566
	 * @param array|string $tagList list of tags
11567
	 *
11568
	 * @return array
11569
	 */
11570
	private function extractTagsIfNeeded( $tagList )
11571
	{
11572
		if ( $tagList !== FALSE && !is_array( $tagList ) ) {
11573
			$tags = explode( ',', (string) $tagList );
11574
		} else {
11575
			$tags = $tagList;
11576
		}
11577
11578
		return $tags;
11579
	}
11580
11581
	/**
11582
	 * Finds a tag bean by it's title.
11583
	 * Internal method.
11584
	 *
11585
	 * @param string $title title to search for
11586
	 *
11587
	 * @return OODBBean
11588
	 */
11589
	protected function findTagByTitle( $title )
11590
	{
11591
		$beans = $this->redbean->find( 'tag', array( 'title' => array( $title ) ) );
11592
11593
		if ( $beans ) {
11594
			$bean = reset( $beans );
11595
11596
			return $bean;
11597
		}
11598
11599
		return NULL;
11600
	}
11601
11602
	/**
11603
	 * Constructor.
11604
	 * The tag manager offers an easy way to quickly implement basic tagging
11605
	 * functionality.
11606
	 *
11607
	 * @param ToolBox $toolbox toolbox object
11608
	 */
11609
	public function __construct( ToolBox $toolbox )
11610
	{
11611
		$this->toolbox = $toolbox;
11612
		$this->redbean = $toolbox->getRedBean();
11613
11614
		$this->associationManager = $this->redbean->getAssociationManager();
11615
	}
11616
11617
	/**
11618
	 * Tests whether a bean has been associated with one ore more
11619
	 * of the listed tags. If the third parameter is TRUE this method
11620
	 * will return TRUE only if all tags that have been specified are indeed
11621
	 * associated with the given bean, otherwise FALSE.
11622
	 * If the third parameter is FALSE this
11623
	 * method will return TRUE if one of the tags matches, FALSE if none
11624
	 * match.
11625
	 *
11626
	 * Tag list can be either an array with tag names or a comma separated list
11627
	 * of tag names.
11628
	 *
11629
	 * Usage:
11630
	 *
11631
	 * <code>
11632
	 * R::hasTag( $blog, 'horror,movie', TRUE );
11633
	 * </code>
11634
	 *
11635
	 * The example above returns TRUE if the $blog bean has been tagged
11636
	 * as BOTH horror and movie. If the post has only been tagged as 'movie'
11637
	 * or 'horror' this operation will return FALSE because the third parameter
11638
	 * has been set to TRUE.
11639
	 *
11640
	 * @param  OODBBean     $bean bean to check for tags
11641
	 * @param  array|string $tags list of tags
11642
	 * @param  boolean      $all  whether they must all match or just some
11643
	 *
11644
	 * @return boolean
11645
	 */
11646
	public function hasTag( $bean, $tags, $all = FALSE )
11647
	{
11648
		$foundtags = $this->tag( $bean );
11649
11650
		$tags = $this->extractTagsIfNeeded( $tags );
11651
		$same = array_intersect( $tags, $foundtags );
11652
11653
		if ( $all ) {
11654
			return ( implode( ',', $same ) === implode( ',', $tags ) );
11655
		}
11656
11657
		return (bool) ( count( $same ) > 0 );
11658
	}
11659
11660
	/**
11661
	 * Removes all specified tags from the bean. The tags specified in
11662
	 * the second parameter will no longer be associated with the bean.
11663
	 *
11664
	 * Tag list can be either an array with tag names or a comma separated list
11665
	 * of tag names.
11666
	 *
11667
	 * Usage:
11668
	 *
11669
	 * <code>
11670
	 * R::untag( $blog, 'smart,interesting' );
11671
	 * </code>
11672
	 *
11673
	 * In the example above, the $blog bean will no longer
11674
	 * be associated with the tags 'smart' and 'interesting'.
11675
	 *
11676
	 * @param  OODBBean $bean    tagged bean
11677
	 * @param  array    $tagList list of tags (names)
11678
	 *
11679
	 * @return void
11680
	 */
11681
	public function untag( $bean, $tagList )
11682
	{
11683
		$tags = $this->extractTagsIfNeeded( $tagList );
11684
11685
		foreach ( $tags as $tag ) {
11686
			if ( $t = $this->findTagByTitle( $tag ) ) {
11687
				$this->associationManager->unassociate( $bean, $t );
11688
			}
11689
		}
11690
	}
11691
11692
	/**
11693
	 * Part of RedBeanPHP Tagging API.
11694
	 * Tags a bean or returns tags associated with a bean.
11695
	 * If $tagList is NULL or omitted this method will return a
11696
	 * comma separated list of tags associated with the bean provided.
11697
	 * If $tagList is a comma separated list (string) of tags all tags will
11698
	 * be associated with the bean.
11699
	 * You may also pass an array instead of a string.
11700
	 *
11701
	 * Usage:
11702
	 *
11703
	 * <code>
11704
	 * R::tag( $meal, "TexMex,Mexican" );
11705
	 * $tags = R::tag( $meal );
11706
	 * </code>
11707
	 *
11708
	 * The first line in the example above will tag the $meal
11709
	 * as 'TexMex' and 'Mexican Cuisine'. The second line will
11710
	 * retrieve all tags attached to the meal object.
11711
	 *
11712
	 * @param OODBBean $bean    bean to tag
11713
	 * @param mixed    $tagList tags to attach to the specified bean
11714
	 *
11715
	 * @return string
11716
	 */
11717
	public function tag( OODBBean $bean, $tagList = NULL )
11718
	{
11719
		if ( is_null( $tagList ) ) {
11720
11721
			$tags = $bean->sharedTag;
11722
			$foundTags = array();
11723
11724
			foreach ( $tags as $tag ) {
11725
				$foundTags[] = $tag->title;
11726
			}
11727
11728
			return $foundTags;
11729
		}
11730
11731
		$this->associationManager->clearRelations( $bean, 'tag' );
11732
		$this->addTags( $bean, $tagList );
11733
11734
		return $tagList;
11735
	}
11736
11737
	/**
11738
	 * Part of RedBeanPHP Tagging API.
11739
	 * Adds tags to a bean.
11740
	 * If $tagList is a comma separated list of tags all tags will
11741
	 * be associated with the bean.
11742
	 * You may also pass an array instead of a string.
11743
	 *
11744
	 * Usage:
11745
	 *
11746
	 * <code>
11747
	 * R::addTags( $blog, ["halloween"] );
11748
	 * </code>
11749
	 *
11750
	 * The example adds the tag 'halloween' to the $blog
11751
	 * bean.
11752
	 *
11753
	 * @param OODBBean $bean    bean to tag
11754
	 * @param array    $tagList list of tags to add to bean
11755
	 *
11756
	 * @return void
11757
	 */
11758
	public function addTags( OODBBean $bean, $tagList )
11759
	{
11760
		$tags = $this->extractTagsIfNeeded( $tagList );
11761
11762
		if ( $tagList === FALSE ) {
11763
			return;
11764
		}
11765
11766
		foreach ( $tags as $tag ) {
11767
			if ( !$t = $this->findTagByTitle( $tag ) ) {
11768
				$t        = $this->redbean->dispense( 'tag' );
11769
				$t->title = $tag;
11770
11771
				$this->redbean->store( $t );
11772
			}
11773
11774
			$this->associationManager->associate( $bean, $t );
11775
		}
11776
	}
11777
11778
	/**
11779
	 * Returns all beans that have been tagged with one or more
11780
	 * of the specified tags.
11781
	 *
11782
	 * Tag list can be either an array with tag names or a comma separated list
11783
	 * of tag names.
11784
	 *
11785
	 * Usage:
11786
	 *
11787
	 * <code>
11788
	 * $watchList = R::tagged(
11789
	 *   'movie',
11790
	 *   'horror,gothic',
11791
	 *   ' ORDER BY movie.title DESC LIMIT ?',
11792
	 *   [ 10 ]
11793
	 * );
11794
	 * </code>
11795
	 *
11796
	 * The example uses R::tagged() to find all movies that have been
11797
	 * tagged as 'horror' or 'gothic', order them by title and limit
11798
	 * the number of movies to be returned to 10.
11799
	 *
11800
	 * @param string       $beanType type of bean you are looking for
11801
	 * @param array|string $tagList  list of tags to match
11802
	 * @param string       $sql      additional SQL (use only for pagination)
11803
	 * @param array        $bindings bindings
11804
	 *
11805
	 * @return array
11806
	 */
11807
	public function tagged( $beanType, $tagList, $sql = '', $bindings = array() )
11808
	{
11809
		$tags       = $this->extractTagsIfNeeded( $tagList );
11810
		$records    = $this->toolbox->getWriter()->queryTagged( $beanType, $tags, FALSE, $sql, $bindings );
11811
11812
		return $this->redbean->convertToBeans( $beanType, $records );
11813
	}
11814
11815
	/**
11816
	 * Returns all beans that have been tagged with ALL of the tags given.
11817
	 * This method works the same as R::tagged() except that this method only returns
11818
	 * beans that have been tagged with all the specified labels.
11819
	 *
11820
	 * Tag list can be either an array with tag names or a comma separated list
11821
	 * of tag names.
11822
	 *
11823
	 * Usage:
11824
	 *
11825
	 * <code>
11826
	 * $watchList = R::taggedAll(
11827
	 *    'movie',
11828
	 *    [ 'gothic', 'short' ],
11829
	 *    ' ORDER BY movie.id DESC LIMIT ? ',
11830
	 *    [ 4 ]
11831
	 * );
11832
	 * </code>
11833
	 *
11834
	 * The example above returns at most 4 movies (due to the LIMIT clause in the SQL
11835
	 * Query Snippet) that have been tagged as BOTH 'short' AND 'gothic'.
11836
	 *
11837
	 * @param string       $beanType type of bean you are looking for
11838
	 * @param array|string $tagList  list of tags to match
11839
	 * @param string       $sql      additional sql snippet
11840
	 * @param array        $bindings bindings
11841
	 *
11842
	 * @return array
11843
	 */
11844
	public function taggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
11845
	{
11846
		$tags  = $this->extractTagsIfNeeded( $tagList );
11847
		$records    = $this->toolbox->getWriter()->queryTagged( $beanType, $tags, TRUE, $sql, $bindings );
11848
11849
		return $this->redbean->convertToBeans( $beanType, $records );
11850
	}
11851
11852
	/**
11853
	 * Like taggedAll() but only counts.
11854
	 *
11855
	 * @see taggedAll
11856
	 *
11857
	 * @param string       $beanType type of bean you are looking for
11858
	 * @param array|string $tagList  list of tags to match
11859
	 * @param string       $sql      additional sql snippet
11860
	 * @param array        $bindings bindings
11861
	 *
11862
	 * @return integer
11863
	 */
11864
	public function countTaggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
11865
	{
11866
		$tags  = $this->extractTagsIfNeeded( $tagList );
11867
		return $this->toolbox->getWriter()->queryCountTagged( $beanType, $tags, TRUE, $sql, $bindings );
11868
	}
11869
11870
	/**
11871
	 * Like tagged() but only counts.
11872
	 *
11873
	 * @see tagged
11874
	 *
11875
	 * @param string       $beanType type of bean you are looking for
11876
	 * @param array|string $tagList  list of tags to match
11877
	 * @param string       $sql      additional sql snippet
11878
	 * @param array        $bindings bindings
11879
	 *
11880
	 * @return integer
11881
	 */
11882
	public function countTagged( $beanType, $tagList, $sql = '', $bindings = array() )
11883
	{
11884
		$tags  = $this->extractTagsIfNeeded( $tagList );
11885
		return $this->toolbox->getWriter()->queryCountTagged( $beanType, $tags, FALSE, $sql, $bindings );
11886
	}
11887
}
11888
}
11889
11890
namespace RedBeanPHP {
11891
11892
use RedBeanPHP\ToolBox as ToolBox;
11893
use RedBeanPHP\OODBBean as OODBBean;
11894
11895
/**
11896
 * Label Maker.
11897
 * Makes so-called label beans.
11898
 * A label is a bean with only an id, type and name property.
11899
 * Labels can be used to create simple entities like categories, tags or enums.
11900
 * This service class provides convenience methods to deal with this kind of
11901
 * beans.
11902
 *
11903
 * @file       RedBeanPHP/LabelMaker.php
11904
 * @author     Gabor de Mooij and the RedBeanPHP Community
11905
 * @license    BSD/GPLv2
11906
 *
11907
 * @copyright
11908
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
11909
 * This source file is subject to the BSD/GPLv2 License that is bundled
11910
 * with this source code in the file license.txt.
11911
 */
11912
class LabelMaker
11913
{
11914
	/**
11915
	 * @var ToolBox
11916
	 */
11917
	protected $toolbox;
11918
11919
	/**
11920
	 * Constructor.
11921
	 *
11922
	 * @param ToolBox $toolbox
11923
	 */
11924
	public function __construct( ToolBox $toolbox )
11925
	{
11926
		$this->toolbox = $toolbox;
11927
	}
11928
11929
	/**
11930
	 * A label is a bean with only an id, type and name property.
11931
	 * This function will dispense beans for all entries in the array. The
11932
	 * values of the array will be assigned to the name property of each
11933
	 * individual bean.
11934
	 *
11935
	 * <code>
11936
	 * $people = R::dispenseLabels( 'person', [ 'Santa', 'Claus' ] );
11937
	 * </code>
11938
	 *
11939
	 * @param string $type   type of beans you would like to have
11940
	 * @param array  $labels list of labels, names for each bean
11941
	 *
11942
	 * @return array
11943
	 */
11944
	public function dispenseLabels( $type, $labels )
11945
	{
11946
		$labelBeans = array();
11947
		foreach ( $labels as $label ) {
11948
			$labelBean       = $this->toolbox->getRedBean()->dispense( $type );
11949
			$labelBean->name = $label;
11950
			$labelBeans[]    = $labelBean;
11951
		}
11952
11953
		return $labelBeans;
11954
	}
11955
11956
	/**
11957
	 * Gathers labels from beans. This function loops through the beans,
11958
	 * collects the value of the name property for each individual bean
11959
	 * and stores the names in a new array. The array then gets sorted using the
11960
	 * default sort function of PHP (sort).
11961
	 *
11962
	 * Usage:
11963
	 *
11964
	 * <code>
11965
	 * $o1->name = 'hamburger';
11966
	 * $o2->name = 'pizza';
11967
	 * implode( ',', R::gatherLabels( [ $o1, $o2 ] ) ); //hamburger,pizza
11968
	 * </code>
11969
	 *
11970
	 * Note that the return value is an array of strings, not beans.
11971
	 *
11972
	 * @param array $beans list of beans to loop through
11973
	 *
11974
	 * @return array
11975
	 */
11976
	public function gatherLabels( $beans )
11977
	{
11978
		$labels = array();
11979
11980
		foreach ( $beans as $bean ) {
11981
			$labels[] = $bean->name;
11982
		}
11983
11984
		sort( $labels );
11985
11986
		return $labels;
11987
	}
11988
11989
	/**
11990
	 * Fetches an ENUM from the database and creates it if necessary.
11991
	 * An ENUM has the following format:
11992
	 *
11993
	 * <code>
11994
	 * ENUM:VALUE
11995
	 * </code>
11996
	 *
11997
	 * If you pass 'ENUM' only, this method will return an array of its
11998
	 * values:
11999
	 *
12000
	 * <code>
12001
	 * implode( ',', R::gatherLabels( R::enum( 'flavour' ) ) ) //'BANANA,MOCCA'
12002
	 * </code>
12003
	 *
12004
	 * If you pass 'ENUM:VALUE' this method will return the specified enum bean
12005
	 * and create it in the database if it does not exist yet:
12006
	 *
12007
	 * <code>
12008
	 * $bananaFlavour = R::enum( 'flavour:banana' );
12009
	 * $bananaFlavour->name;
12010
	 * </code>
12011
	 *
12012
	 * So you can use this method to set an ENUM value in a bean:
12013
	 *
12014
	 * <code>
12015
	 * $shake->flavour = R::enum( 'flavour:banana' );
12016
	 * </code>
12017
	 *
12018
	 * the property flavour now contains the enum bean, a parent bean.
12019
	 * In the database, flavour_id will point to the flavour record with name 'banana'.
12020
	 *
12021
	 * @param string $enum ENUM specification for label
12022
	 *
12023
	 * @return array|OODBBean
12024
	 */
12025
	public function enum( $enum )
12026
	{
12027
		$oodb = $this->toolbox->getRedBean();
12028
12029
		if ( strpos( $enum, ':' ) === FALSE ) {
12030
			$type  = $enum;
12031
			$value = FALSE;
12032
		} else {
12033
			list( $type, $value ) = explode( ':', $enum );
12034
			$value                = preg_replace( '/\W+/', '_', strtoupper( trim( $value ) ) );
12035
		}
12036
12037
		/**
12038
		 * We use simply find here, we could use inspect() in fluid mode etc,
12039
		 * but this would be useless. At first sight it looks clean, you could even
12040
		 * bake this into find(), however, find not only has to deal with the primary
12041
		 * search type, people can also include references in the SQL part, so avoiding
12042
		 * find failures does not matter, this is still the quickest way making use
12043
		 * of existing functionality.
12044
		 *
12045
		 * @note There seems to be a bug in XDebug v2.3.2 causing suppressed
12046
		 * exceptions like these to surface anyway, to prevent this use:
12047
		 *
12048
		 * "xdebug.default_enable = 0"
12049
		 *
12050
		 *  Also see Github Issue #464
12051
		 */
12052
		$values = $oodb->find( $type );
12053
12054
		if ( $value === FALSE ) {
12055
			return $values;
12056
		}
12057
12058
		foreach( $values as $enumItem ) {
12059
				if ( $enumItem->name === $value ) return $enumItem;
12060
		}
12061
12062
		$newEnumItems = $this->dispenseLabels( $type, array( $value ) );
12063
		$newEnumItem  = reset( $newEnumItems );
12064
12065
		$oodb->store( $newEnumItem );
12066
12067
		return $newEnumItem;
12068
	}
12069
}
12070
}
12071
12072
namespace RedBeanPHP {
12073
12074
use RedBeanPHP\QueryWriter as QueryWriter;
12075
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
12076
use RedBeanPHP\RedException\SQL as SQLException;
12077
use RedBeanPHP\Logger as Logger;
12078
use RedBeanPHP\Logger\RDefault as RDefault;
12079
use RedBeanPHP\Logger\RDefault\Debug as Debug;
12080
use RedBeanPHP\Adapter as Adapter;
12081
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
12082
use RedBeanPHP\RedException as RedException;
12083
use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
12084
use RedBeanPHP\Driver\RPDO as RPDO;
12085
use RedBeanPHP\Util\MultiLoader as MultiLoader;
12086
use RedBeanPHP\Util\Transaction as Transaction;
12087
use RedBeanPHP\Util\Dump as Dump;
12088
use RedBeanPHP\Util\DispenseHelper as DispenseHelper;
12089
use RedBeanPHP\Util\ArrayTool as ArrayTool;
12090
use RedBeanPHP\Util\QuickExport as QuickExport;
12091
use RedBeanPHP\Util\MatchUp as MatchUp;
12092
use RedBeanPHP\Util\Look as Look;
12093
use RedBeanPHP\Util\Diff as Diff;
12094
use RedBeanPHP\Util\Tree as Tree;
12095
use RedBeanPHP\Util\Feature;
12096
12097
/**
12098
 * RedBean Facade
12099
 *
12100
 * Version Information
12101
 * RedBean Version @version 5.3
12102
 *
12103
 * This class hides the object landscape of
12104
 * RedBeanPHP behind a single letter class providing
12105
 * almost all functionality with simple static calls.
12106
 *
12107
 * @file    RedBeanPHP/Facade.php
12108
 * @author  Gabor de Mooij and the RedBeanPHP Community
12109
 * @license BSD/GPLv2
12110
 *
12111
 * @copyright
12112
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
12113
 * This source file is subject to the BSD/GPLv2 License that is bundled
12114
 * with this source code in the file license.txt.
12115
 */
12116
class Facade
12117
{
12118
	/**
12119
	 * RedBeanPHP version constant.
12120
	 */
12121
	const C_REDBEANPHP_VERSION = '5.3';
12122
12123
	/**
12124
	 * @var ToolBox
12125
	 */
12126
	public static $toolbox;
12127
12128
	/**
12129
	 * @var OODB
12130
	 */
12131
	private static $redbean;
12132
12133
	/**
12134
	 * @var QueryWriter
12135
	 */
12136
	private static $writer;
12137
12138
	/**
12139
	 * @var DBAdapter
12140
	 */
12141
	private static $adapter;
12142
12143
	/**
12144
	 * @var AssociationManager
12145
	 */
12146
	private static $associationManager;
12147
12148
	/**
12149
	 * @var TagManager
12150
	 */
12151
	private static $tagManager;
12152
12153
	/**
12154
	 * @var DuplicationManager
12155
	 */
12156
	private static $duplicationManager;
12157
12158
	/**
12159
	 * @var LabelMaker
12160
	 */
12161
	private static $labelMaker;
12162
12163
	/**
12164
	 * @var Finder
12165
	 */
12166
	private static $finder;
12167
12168
	/**
12169
	 * @var Tree
12170
	 */
12171
	private static $tree;
12172
12173
	/**
12174
	 * @var Logger
12175
	 */
12176
	private static $logger;
12177
12178
	/**
12179
	 * @var array
12180
	 */
12181
	private static $plugins = array();
12182
12183
	/**
12184
	 * @var string
12185
	 */
12186
	private static $exportCaseStyle = 'default';
12187
12188
	/**
12189
	 * @var flag allows transactions through facade in fluid mode
12190
	 */
12191
	private static $allowFluidTransactions = FALSE;
12192
12193
	/**
12194
	 * Not in use (backward compatibility SQLHelper)
12195
	 */
12196
	public static $f;
12197
12198
	/**
12199
	 * @var string
12200
	 */
12201
	public static $currentDB = '';
12202
12203
	/**
12204
	 * @var array
12205
	 */
12206
	public static $toolboxes = array();
12207
12208
	/**
12209
	 * Internal Query function, executes the desired query. Used by
12210
	 * all facade query functions. This keeps things DRY.
12211
	 *
12212
	 * @param string $method   desired query method (i.e. 'cell', 'col', 'exec' etc..)
12213
	 * @param string $sql      the sql you want to execute
12214
	 * @param array  $bindings array of values to be bound to query statement
12215
	 *
12216
	 * @return array
12217
	 */
12218
	private static function query( $method, $sql, $bindings )
12219
	{
12220
		if ( !self::$redbean->isFrozen() ) {
12221
			try {
12222
				$rs = Facade::$adapter->$method( $sql, $bindings );
12223
			} catch ( SQLException $exception ) {
12224
				if ( self::$writer->sqlStateIn( $exception->getSQLState(),
12225
					array(
12226
						QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
12227
						QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
12228
					,$exception->getDriverDetails()
12229
					)
12230
				) {
12231
					return ( $method === 'getCell' ) ? NULL : array();
12232
				} else {
12233
					throw $exception;
12234
				}
12235
			}
12236
12237
			return $rs;
12238
		} else {
12239
			return Facade::$adapter->$method( $sql, $bindings );
12240
		}
12241
	}
12242
12243
	/**
12244
	 * Returns the RedBeanPHP version string.
12245
	 * The RedBeanPHP version string always has the same format "X.Y"
12246
	 * where X is the major version number and Y is the minor version number.
12247
	 * Point releases are not mentioned in the version string.
12248
	 *
12249
	 * @return string
12250
	 */
12251
	public static function getVersion()
12252
	{
12253
		return self::C_REDBEANPHP_VERSION;
12254
	}
12255
12256
	/**
12257
	 * Tests the database connection.
12258
	 * Returns TRUE if connection has been established and
12259
	 * FALSE otherwise. Suppresses any warnings that may
12260
	 * occur during the testing process and catches all
12261
	 * exceptions that might be thrown during the test.
12262
	 *
12263
	 * @return boolean
12264
	 */
12265
	public static function testConnection()
12266
	{
12267
		if ( !isset( self::$adapter ) ) return FALSE;
12268
12269
		$database = self::$adapter->getDatabase();
12270
		try {
12271
			@$database->connect();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for connect(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

12271
			/** @scrutinizer ignore-unhandled */ @$database->connect();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
12272
		} catch ( \Exception $e ) {}
12273
		return $database->isConnected();
12274
	}
12275
12276
	/**
12277
	 * Kickstarts redbean for you. This method should be called before you start using
12278
	 * RedBeanPHP. The Setup() method can be called without any arguments, in this case it will
12279
	 * try to create a SQLite database in /tmp called red.db (this only works on UNIX-like systems).
12280
	 *
12281
	 * Usage:
12282
	 *
12283
	 * <code>
12284
	 * R::setup( 'mysql:host=localhost;dbname=mydatabase', 'dba', 'dbapassword' );
12285
	 * </code>
12286
	 *
12287
	 * You can replace 'mysql:' with the name of the database you want to use.
12288
	 * Possible values are:
12289
	 *
12290
	 * - pgsql  (PostgreSQL database)
12291
	 * - sqlite (SQLite database)
12292
	 * - mysql  (MySQL database)
12293
	 * - mysql  (also for Maria database)
12294
	 * - sqlsrv (MS SQL Server - community supported experimental driver)
12295
	 * - CUBRID (CUBRID driver - basic support provided by Plugin)
12296
	 *
12297
	 * Note that setup() will not immediately establish a connection to the database.
12298
	 * Instead, it will prepare the connection and connect 'lazily', i.e. the moment
12299
	 * a connection is really required, for instance when attempting to load
12300
	 * a bean.
12301
	 *
12302
	 * @param string  $dsn      Database connection string
12303
	 * @param string  $username Username for database
12304
	 * @param string  $password Password for database
12305
	 * @param boolean $frozen   TRUE if you want to setup in frozen mode
12306
	 *
12307
	 * @return ToolBox
12308
	 */
12309
	public static function setup( $dsn = NULL, $username = NULL, $password = NULL, $frozen = FALSE, $partialBeans = FALSE )
12310
	{
12311
		if ( is_null( $dsn ) ) {
12312
			$dsn = 'sqlite:/' . sys_get_temp_dir() . '/red.db';
12313
		}
12314
12315
		self::addDatabase( 'default', $dsn, $username, $password, $frozen, $partialBeans );
12316
		self::selectDatabase( 'default' );
12317
12318
		return self::$toolbox;
12319
	}
12320
12321
	/**
12322
	 * Toggles 'Narrow Field Mode'.
12323
	 * In Narrow Field mode the queryRecord method will
12324
	 * narrow its selection field to
12325
	 *
12326
	 * <code>
12327
	 * SELECT {table}.*
12328
	 * </code>
12329
	 *
12330
	 * instead of
12331
	 *
12332
	 * <code>
12333
	 * SELECT *
12334
	 * </code>
12335
	 *
12336
	 * This is a better way of querying because it allows
12337
	 * more flexibility (for instance joins). However if you need
12338
	 * the wide selector for backward compatibility; use this method
12339
	 * to turn OFF Narrow Field Mode by passing FALSE.
12340
	 * Default is TRUE.
12341
	 *
12342
	 * @param boolean $narrowField TRUE = Narrow Field FALSE = Wide Field
12343
	 *
12344
	 * @return void
12345
	 */
12346
	public static function setNarrowFieldMode( $mode )
12347
	{
12348
		AQueryWriter::setNarrowFieldMode( $mode );
12349
	}
12350
12351
	/**
12352
	 * Toggles fluid transactions. By default fluid transactions
12353
	 * are not active. Starting, committing or rolling back a transaction
12354
	 * through the facade in fluid mode will have no effect. If you wish
12355
	 * to replace this standard portable behavor with behavior depending
12356
	 * on how the used database platform handles fluid (DDL) transactions
12357
	 * set this flag to TRUE.
12358
	 *
12359
	 * @param boolean $mode allow fluid transaction mode
12360
	 *
12361
	 * @return void
12362
	 */
12363
	public static function setAllowFluidTransactions( $mode )
12364
	{
12365
		self::$allowFluidTransactions = $mode;
12366
	}
12367
12368
	/**
12369
	 * Wraps a transaction around a closure or string callback.
12370
	 * If an Exception is thrown inside, the operation is automatically rolled back.
12371
	 * If no Exception happens, it commits automatically.
12372
	 * It also supports (simulated) nested transactions (that is useful when
12373
	 * you have many methods that needs transactions but are unaware of
12374
	 * each other).
12375
	 *
12376
	 * Example:
12377
	 *
12378
	 * <code>
12379
	 * $from = 1;
12380
	 * $to = 2;
12381
	 * $amount = 300;
12382
	 *
12383
	 * R::transaction(function() use($from, $to, $amount)
12384
	 * {
12385
	 *   $accountFrom = R::load('account', $from);
12386
	 *   $accountTo = R::load('account', $to);
12387
	 *   $accountFrom->money -= $amount;
12388
	 *   $accountTo->money += $amount;
12389
	 *   R::store($accountFrom);
12390
	 *   R::store($accountTo);
12391
	 * });
12392
	 * </code>
12393
	 *
12394
	 * @param callable $callback Closure (or other callable) with the transaction logic
12395
	 *
12396
	 * @return mixed
12397
	 */
12398
	public static function transaction( $callback )
12399
	{
12400
		return Transaction::transaction( self::$adapter, $callback );
12401
	}
12402
12403
	/**
12404
	 * Adds a database to the facade, afterwards you can select the database using
12405
	 * selectDatabase($key), where $key is the name you assigned to this database.
12406
	 *
12407
	 * Usage:
12408
	 *
12409
	 * <code>
12410
	 * R::addDatabase( 'database-1', 'sqlite:/tmp/db1.txt' );
12411
	 * R::selectDatabase( 'database-1' ); //to select database again
12412
	 * </code>
12413
	 *
12414
	 * This method allows you to dynamically add (and select) new databases
12415
	 * to the facade. Adding a database with the same key will cause an exception.
12416
	 *
12417
	 * @param string      $key    ID for the database
12418
	 * @param string      $dsn    DSN for the database
12419
	 * @param string      $user   user for connection
12420
	 * @param NULL|string $pass   password for connection
12421
	 * @param bool        $frozen whether this database is frozen or not
12422
	 *
12423
	 * @return void
12424
	 */
12425
	public static function addDatabase( $key, $dsn, $user = NULL, $pass = NULL, $frozen = FALSE, $partialBeans = FALSE )
12426
	{
12427
		if ( isset( self::$toolboxes[$key] ) ) {
12428
			throw new RedException( 'A database has already been specified for this key.' );
12429
		}
12430
12431
		if ( is_object($dsn) ) {
12432
			$db  = new RPDO( $dsn );
12433
			$dbType = $db->getDatabaseType();
12434
		} else {
12435
			$db = new RPDO( $dsn, $user, $pass, TRUE );
12436
			$dbType = substr( $dsn, 0, strpos( $dsn, ':' ) );
12437
		}
12438
12439
		$adapter = new DBAdapter( $db );
12440
12441
		$writers = array(
12442
			'pgsql'  => 'PostgreSQL',
12443
			'sqlite' => 'SQLiteT',
12444
			'cubrid' => 'CUBRID',
12445
			'mysql'  => 'MySQL',
12446
			'sqlsrv' => 'SQLServer',
12447
		);
12448
12449
		$wkey = trim( strtolower( $dbType ) );
12450
		if ( !isset( $writers[$wkey] ) ) {
12451
			$wkey = preg_replace( '/\W/', '' , $wkey );
12452
			throw new RedException( 'Unsupported database ('.$wkey.').' );
12453
		}
12454
		$writerClass = '\\RedBeanPHP\\QueryWriter\\'.$writers[$wkey];
12455
		$writer      = new $writerClass( $adapter );
12456
		$redbean     = new OODB( $writer, $frozen );
12457
		
12458
		if ( $partialBeans ) {
12459
			$redbean->getCurrentRepository()->usePartialBeans( $partialBeans );
12460
		}
12461
12462
		self::$toolboxes[$key] = new ToolBox( $redbean, $adapter, $writer );
12463
	}
12464
12465
	/**
12466
	 * Sets PDO attributes for MySQL SSL connection.
12467
	 *
12468
	 * @param string $key  path client key i.e. '/etc/mysql/ssl/client-key.pem'
12469
	 * @param string $cert path client cert i.e. '/etc/mysql/ssl/client-cert.pem'
12470
	 * @param string $ca   path certifying agent certificate '/etc/mysql/ssl/ca-cert.pem'
12471
	 * @param string $id   apply to toolbox (default = 'default')
12472
	 */
12473
	public static function useMysqlSSL( $key, $cert, $ca, $id = 'default' ) {
12474
		$pdo = self::$toolboxes[$id]->getDatabaseAdapter()->getDatabase()->getPDO();
12475
		$pdo->setAttribute( \PDO::MYSQL_ATTR_SSL_KEY,  $key);
12476
		$pdo->setAttribute( \PDO::MYSQL_ATTR_SSL_CERT,  $cert);
12477
		$pdo->setAttribute( \PDO::MYSQL_ATTR_SSL_CA,  $ca);
12478
	}
12479
12480
	/**
12481
	 * Determines whether a database identified with the specified key has
12482
	 * already been added to the facade. This function will return TRUE
12483
	 * if the database indicated by the key is available and FALSE otherwise.
12484
	 *
12485
	 * @param string $key the key/name of the database to check for
12486
	 *
12487
	 * @return boolean
12488
	 */
12489
	public static function hasDatabase( $key )
12490
	{
12491
		return ( isset( self::$toolboxes[$key] ) );
12492
	}
12493
12494
	/**
12495
	 * Selects a different database for the Facade to work with.
12496
	 * If you use the R::setup() you don't need this method. This method is meant
12497
	 * for multiple database setups. This method selects the database identified by the
12498
	 * database ID ($key). Use addDatabase() to add a new database, which in turn
12499
	 * can be selected using selectDatabase(). If you use R::setup(), the resulting
12500
	 * database will be stored under key 'default', to switch (back) to this database
12501
	 * use R::selectDatabase( 'default' ). This method returns TRUE if the database has been
12502
	 * switched and FALSE otherwise (for instance if you already using the specified database).
12503
	 *
12504
	 * @param  string $key Key of the database to select
12505
	 *
12506
	 * @return boolean
12507
	 */
12508
	public static function selectDatabase( $key, $force = FALSE )
12509
	{
12510
		if ( self::$currentDB === $key && !$force ) {
12511
			return FALSE;
12512
		}
12513
12514
		if ( !isset( self::$toolboxes[$key] ) ) {
12515
			throw new RedException( 'Database not found in registry. Add database using R::addDatabase().' );
12516
		}
12517
12518
		self::configureFacadeWithToolbox( self::$toolboxes[$key] );
12519
		self::$currentDB = $key;
12520
12521
		return TRUE;
12522
	}
12523
12524
	/**
12525
	 * Toggles DEBUG mode.
12526
	 * In Debug mode all SQL that happens under the hood will
12527
	 * be printed to the screen and/or logged.
12528
	 * If no database connection has been configured using R::setup() or
12529
	 * R::selectDatabase() this method will throw an exception.
12530
	 *
12531
	 * There are 2 debug styles:
12532
	 *
12533
	 * Classic: separate parameter bindings, explicit and complete but less readable
12534
	 * Fancy:   interpersed bindings, truncates large strings, highlighted schema changes
12535
	 *
12536
	 * Fancy style is more readable but sometimes incomplete.
12537
	 *
12538
	 * The first parameter turns debugging ON or OFF.
12539
	 * The second parameter indicates the mode of operation:
12540
	 *
12541
	 * 0 Log and write to STDOUT classic style (default)
12542
	 * 1 Log only, class style
12543
	 * 2 Log and write to STDOUT fancy style
12544
	 * 3 Log only, fancy style
12545
	 *
12546
	 * This function always returns the logger instance created to generate the
12547
	 * debug messages.
12548
	 *
12549
	 * @param boolean $tf   debug mode (TRUE or FALSE)
12550
	 * @param integer $mode mode of operation
12551
	 *
12552
	 * @return RDefault
12553
	 * @throws RedException
12554
	 */
12555
	public static function debug( $tf = TRUE, $mode = 0 )
12556
	{
12557
		if ($mode > 1) {
12558
			$mode -= 2;
12559
			$logger = new Debug;
12560
		} else {
12561
			$logger = new RDefault;
12562
		}
12563
12564
		if ( !isset( self::$adapter ) ) {
12565
			throw new RedException( 'Use R::setup() first.' );
12566
		}
12567
		$logger->setMode($mode);
12568
		self::$adapter->getDatabase()->setDebugMode( $tf, $logger );
12569
12570
		return $logger;
12571
	}
12572
12573
	/**
12574
	 * Turns on the fancy debugger.
12575
	 * In 'fancy' mode the debugger will output queries with bound
12576
	 * parameters inside the SQL itself. This method has been added to
12577
	 * offer a convenient way to activate the fancy debugger system
12578
	 * in one call.
12579
	 *
12580
	 * @param boolean $toggle TRUE to activate debugger and select 'fancy' mode
12581
	 *
12582
	 * @return void
12583
	 */
12584
	public static function fancyDebug( $toggle = TRUE )
12585
	{
12586
		self::debug( $toggle, 2 );
12587
	}
12588
12589
	/**
12590
	* Inspects the database schema. If you pass the type of a bean this
12591
	* method will return the fields of its table in the database.
12592
	* The keys of this array will be the field names and the values will be
12593
	* the column types used to store their values.
12594
	* If no type is passed, this method returns a list of all tables in the database.
12595
	*
12596
	* @param string $type Type of bean (i.e. table) you want to inspect
12597
	*
12598
	* @return array
12599
	*/
12600
	public static function inspect( $type = NULL )
12601
	{
12602
		return ($type === NULL) ? self::$writer->getTables() : self::$writer->getColumns( $type );
12603
	}
12604
12605
	/**
12606
	 * Stores a bean in the database. This method takes a
12607
	 * OODBBean Bean Object $bean and stores it
12608
	 * in the database. If the database schema is not compatible
12609
	 * with this bean and RedBean runs in fluid mode the schema
12610
	 * will be altered to store the bean correctly.
12611
	 * If the database schema is not compatible with this bean and
12612
	 * RedBean runs in frozen mode it will throw an exception.
12613
	 * This function returns the primary key ID of the inserted
12614
	 * bean.
12615
	 *
12616
	 * The return value is an integer if possible. If it is not possible to
12617
	 * represent the value as an integer a string will be returned.
12618
	 *
12619
	 * Usage:
12620
	 *
12621
	 * <code>
12622
	 * $post = R::dispense('post');
12623
	 * $post->title = 'my post';
12624
	 * $id = R::store( $post );
12625
	 * $post = R::load( 'post', $id );
12626
	 * R::trash( $post );
12627
	 * </code>
12628
	 *
12629
	 * In the example above, we create a new bean of type 'post'.
12630
	 * We then set the title of the bean to 'my post' and we
12631
	 * store the bean. The store() method will return the primary
12632
	 * key ID $id assigned by the database. We can now use this
12633
	 * ID to load the bean from the database again and delete it.
12634
	 *
12635
	 * @param OODBBean|SimpleModel $bean bean to store
12636
	 *
12637
	 * @return integer|string
12638
	 */
12639
	public static function store( $bean )
12640
	{
12641
		return self::$redbean->store( $bean );
12642
	}
12643
12644
	/**
12645
	 * Toggles fluid or frozen mode. In fluid mode the database
12646
	 * structure is adjusted to accomodate your objects. In frozen mode
12647
	 * this is not the case.
12648
	 *
12649
	 * You can also pass an array containing a selection of frozen types.
12650
	 * Let's call this chilly mode, it's just like fluid mode except that
12651
	 * certain types (i.e. tables) aren't touched.
12652
	 *
12653
	 * @param boolean|array $tf mode of operation (TRUE means frozen)
12654
	 */
12655
	public static function freeze( $tf = TRUE )
12656
	{
12657
		self::$redbean->freeze( $tf );
12658
	}
12659
12660
	/**
12661
	 * Loads multiple types of beans with the same ID.
12662
	 * This might look like a strange method, however it can be useful
12663
	 * for loading a one-to-one relation. In a typical 1-1 relation,
12664
	 * you have two records sharing the same primary key.
12665
	 * RedBeanPHP has only limited support for 1-1 relations.
12666
	 * In general it is recommended to use 1-N for this.
12667
	 *
12668
	 * Usage:
12669
	 *
12670
	 * <code>
12671
	 * list( $author, $bio ) = R::loadMulti( 'author, bio', $id );
12672
	 * </code>
12673
	 *
12674
	 * @param string|array $types the set of types to load at once
12675
	 * @param mixed        $id    the common ID
12676
	 *
12677
	 * @return OODBBean
12678
	 */
12679
	public static function loadMulti( $types, $id )
12680
	{
12681
		return MultiLoader::load( self::$redbean, $types, $id );
12682
	}
12683
12684
	/**
12685
	 * Loads a bean from the object database.
12686
	 * It searches for a OODBBean Bean Object in the
12687
	 * database. It does not matter how this bean has been stored.
12688
	 * RedBean uses the primary key ID $id and the string $type
12689
	 * to find the bean. The $type specifies what kind of bean you
12690
	 * are looking for; this is the same type as used with the
12691
	 * dispense() function. If RedBean finds the bean it will return
12692
	 * the OODB Bean object; if it cannot find the bean
12693
	 * RedBean will return a new bean of type $type and with
12694
	 * primary key ID 0. In the latter case it acts basically the
12695
	 * same as dispense().
12696
	 *
12697
	 * Important note:
12698
	 * If the bean cannot be found in the database a new bean of
12699
	 * the specified type will be generated and returned.
12700
	 *
12701
	 * Usage:
12702
	 *
12703
	 * <code>
12704
	 * $post = R::dispense('post');
12705
	 * $post->title = 'my post';
12706
	 * $id = R::store( $post );
12707
	 * $post = R::load( 'post', $id );
12708
	 * R::trash( $post );
12709
	 * </code>
12710
	 *
12711
	 * In the example above, we create a new bean of type 'post'.
12712
	 * We then set the title of the bean to 'my post' and we
12713
	 * store the bean. The store() method will return the primary
12714
	 * key ID $id assigned by the database. We can now use this
12715
	 * ID to load the bean from the database again and delete it.
12716
	 *
12717
	 * @param string  $type    type of bean you want to load
12718
	 * @param integer $id      ID of the bean you want to load
12719
	 * @param string  $snippet string to use after select  (optional)
12720
	 *
12721
	 * @return OODBBean
12722
	 */
12723
	public static function load( $type, $id, $snippet = NULL )
12724
	{
12725
		if ( $snippet !== NULL ) self::$writer->setSQLSelectSnippet( $snippet );
12726
		$bean = self::$redbean->load( $type, $id );
12727
		return $bean;
12728
	}
12729
12730
	/**
12731
	 * Same as load, but selects the bean for update, thus locking the bean.
12732
	 * This equals an SQL query like 'SELECT ... FROM ... FOR UPDATE'.
12733
	 * Use this method if you want to load a bean you intend to UPDATE.
12734
	 * This method should be used to 'LOCK a bean'.
12735
	 *
12736
	 * Usage:
12737
	 *
12738
	 * <code>
12739
	 * $bean = R::loadForUpdate( 'bean', $id );
12740
	 * ...update...
12741
	 * R::store( $bean );
12742
	 * </code>
12743
	 *
12744
	 * @param string  $type    type of bean you want to load
12745
	 * @param integer $id      ID of the bean you want to load
12746
	 *
12747
	 * @return OODBBean
12748
	 */
12749
	public static function loadForUpdate( $type, $id )
12750
	{
12751
		return self::load( $type, $id, AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
12752
	}
12753
12754
	/**
12755
	 * Removes a bean from the database.
12756
	 * This function will remove the specified OODBBean
12757
	 * Bean Object from the database.
12758
	 *
12759
	 * This facade method also accepts a type-id combination,
12760
	 * in the latter case this method will attempt to load the specified bean
12761
	 * and THEN trash it.
12762
	 *
12763
	 * Usage:
12764
	 *
12765
	 * <code>
12766
	 * $post = R::dispense('post');
12767
	 * $post->title = 'my post';
12768
	 * $id = R::store( $post );
12769
	 * $post = R::load( 'post', $id );
12770
	 * R::trash( $post );
12771
	 * </code>
12772
	 *
12773
	 * In the example above, we create a new bean of type 'post'.
12774
	 * We then set the title of the bean to 'my post' and we
12775
	 * store the bean. The store() method will return the primary
12776
	 * key ID $id assigned by the database. We can now use this
12777
	 * ID to load the bean from the database again and delete it.
12778
	 *
12779
	 * @param string|OODBBean|SimpleModel $beanOrType bean you want to remove from database
12780
	 * @param integer                     $id         ID if the bean to trash (optional, type-id variant only)
12781
	 *
12782
	 * @return void
12783
	 */
12784
	public static function trash( $beanOrType, $id = NULL )
12785
	{
12786
		if ( is_string( $beanOrType ) ) return self::trash( self::load( $beanOrType, $id ) );
12787
		return self::$redbean->trash( $beanOrType );
12788
	}
12789
12790
	/**
12791
	 * Dispenses a new RedBean OODB Bean for use with
12792
	 * the rest of the methods. RedBeanPHP thinks in beans, the bean is the
12793
	 * primary way to interact with RedBeanPHP and the database managed by
12794
	 * RedBeanPHP. To load, store and delete data from the database using RedBeanPHP
12795
	 * you exchange these RedBeanPHP OODB Beans. The only exception to this rule
12796
	 * are the raw query methods like R::getCell() or R::exec() and so on.
12797
	 * The dispense method is the 'preferred way' to create a new bean.
12798
	 *
12799
	 * Usage:
12800
	 *
12801
	 * <code>
12802
	 * $book = R::dispense( 'book' );
12803
	 * $book->title = 'My Book';
12804
	 * R::store( $book );
12805
	 * </code>
12806
	 *
12807
	 * This method can also be used to create an entire bean graph at once.
12808
	 * Given an array with keys specifying the property names of the beans
12809
	 * and a special _type key to indicate the type of bean, one can
12810
	 * make the Dispense Helper generate an entire hierarchy of beans, including
12811
	 * lists. To make dispense() generate a list, simply add a key like:
12812
	 * ownXList or sharedXList where X is the type of beans it contains and
12813
	 * a set its value to an array filled with arrays representing the beans.
12814
	 * Note that, although the type may have been hinted at in the list name,
12815
	 * you still have to specify a _type key for every bean array in the list.
12816
	 * Note that, if you specify an array to generate a bean graph, the number
12817
	 * parameter will be ignored.
12818
	 *
12819
	 * Usage:
12820
	 *
12821
	 * <code>
12822
	 *  $book = R::dispense( [
12823
     *   '_type' => 'book',
12824
     *   'title'  => 'Gifted Programmers',
12825
     *   'author' => [ '_type' => 'author', 'name' => 'Xavier' ],
12826
     *   'ownPageList' => [ ['_type'=>'page', 'text' => '...'] ]
12827
     * ] );
12828
	 * </code>
12829
	 *
12830
	 * @param string|array $typeOrBeanArray   type or bean array to import
12831
	 * @param integer      $num               number of beans to dispense
12832
	 * @param boolean      $alwaysReturnArray if TRUE always returns the result as an array
12833
	 *
12834
	 * @return array|OODBBean
12835
	 */
12836
	public static function dispense( $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE )
12837
	{
12838
		return DispenseHelper::dispense( self::$redbean, $typeOrBeanArray, $num, $alwaysReturnArray );
12839
	}
12840
12841
	/**
12842
	 * Takes a comma separated list of bean types
12843
	 * and dispenses these beans. For each type in the list
12844
	 * you can specify the number of beans to be dispensed.
12845
	 *
12846
	 * Usage:
12847
	 *
12848
	 * <code>
12849
	 * list( $book, $page, $text ) = R::dispenseAll( 'book,page,text' );
12850
	 * </code>
12851
	 *
12852
	 * This will dispense a book, a page and a text. This way you can
12853
	 * quickly dispense beans of various types in just one line of code.
12854
	 *
12855
	 * Usage:
12856
	 *
12857
	 * <code>
12858
	 * list($book, $pages) = R::dispenseAll('book,page*100');
12859
	 * </code>
12860
	 *
12861
	 * This returns an array with a book bean and then another array
12862
	 * containing 100 page beans.
12863
	 *
12864
	 * @param string  $order      a description of the desired dispense order using the syntax above
12865
	 * @param boolean $onlyArrays return only arrays even if amount < 2
12866
	 *
12867
	 * @return array
12868
	 */
12869
	public static function dispenseAll( $order, $onlyArrays = FALSE )
12870
	{
12871
		return DispenseHelper::dispenseAll( self::$redbean, $order, $onlyArrays );
12872
	}
12873
12874
	/**
12875
	 * Convience method. Tries to find beans of a certain type,
12876
	 * if no beans are found, it dispenses a bean of that type.
12877
	 * Note that this function always returns an array.
12878
	 *
12879
	 * @param  string $type     type of bean you are looking for
12880
	 * @param  string $sql      SQL code for finding the bean
12881
	 * @param  array  $bindings parameters to bind to SQL
12882
	 *
12883
	 * @return array
12884
	 */
12885
	public static function findOrDispense( $type, $sql = NULL, $bindings = array() )
12886
	{
12887
		DispenseHelper::checkType( $type );
12888
		return self::$finder->findOrDispense( $type, $sql, $bindings );
12889
	}
12890
12891
	/**
12892
	 * Same as findOrDispense but returns just one element.
12893
	 *
12894
	 * @param  string $type     type of bean you are looking for
12895
	 * @param  string $sql      SQL code for finding the bean
12896
	 * @param  array  $bindings parameters to bind to SQL
12897
	 *
12898
	 * @return OODBBean
12899
	 */
12900
	public static function findOneOrDispense( $type, $sql = NULL, $bindings = array() )
12901
	{
12902
		DispenseHelper::checkType( $type );
12903
		$arrayOfBeans = self::findOrDispense( $type, $sql, $bindings );
12904
		return reset($arrayOfBeans);
12905
	}
12906
12907
	/**
12908
	 * Finds beans using a type and optional SQL statement.
12909
	 * As with most Query tools in RedBean you can provide values to
12910
	 * be inserted in the SQL statement by populating the value
12911
	 * array parameter; you can either use the question mark notation
12912
	 * or the slot-notation (:keyname).
12913
	 *
12914
	 * Your SQL does not have to start with a WHERE-clause condition.
12915
	 *
12916
	 * @param string $type     the type of bean you are looking for
12917
	 * @param string $sql      SQL query to find the desired bean, starting right after WHERE clause
12918
	 * @param array  $bindings array of values to be bound to parameters in query
12919
	 *
12920
	 * @return array
12921
	 */
12922
	public static function find( $type, $sql = NULL, $bindings = array() )
12923
	{
12924
		return self::$finder->find( $type, $sql, $bindings );
12925
	}
12926
12927
	/**
12928
	 * Alias for find().
12929
	 *
12930
	 * @param string $type     the type of bean you are looking for
12931
	 * @param string $sql      SQL query to find the desired bean, starting right after WHERE clause
12932
	 * @param array  $bindings array of values to be bound to parameters in query
12933
	 *
12934
	 * @return array
12935
	 */
12936
	public static function findAll( $type, $sql = NULL, $bindings = array() )
12937
	{
12938
		return self::$finder->find( $type, $sql, $bindings );
12939
	}
12940
12941
	/**
12942
	 * Like find() but also exports the beans as an array.
12943
	 * This method will perform a find-operation. For every bean
12944
	 * in the result collection this method will call the export() method.
12945
	 * This method returns an array containing the array representations
12946
	 * of every bean in the result set.
12947
	 *
12948
	 * @see Finder::find
12949
	 *
12950
	 * @param string $type     type   the type of bean you are looking for
12951
	 * @param string $sql      sql    SQL query to find the desired bean, starting right after WHERE clause
12952
	 * @param array  $bindings values array of values to be bound to parameters in query
12953
	 *
12954
	 * @return array
12955
	 */
12956
	public static function findAndExport( $type, $sql = NULL, $bindings = array() )
12957
	{
12958
		return self::$finder->findAndExport( $type, $sql, $bindings );
12959
	}
12960
12961
	/**
12962
	 * Like R::find() but returns the first bean only.
12963
	 *
12964
	 * @param string $type     the type of bean you are looking for
12965
	 * @param string $sql      SQL query to find the desired bean, starting right after WHERE clause
12966
	 * @param array  $bindings array of values to be bound to parameters in query
12967
	 *
12968
	 * @return OODBBean|NULL
12969
	 */
12970
	public static function findOne( $type, $sql = NULL, $bindings = array() )
12971
	{
12972
		return self::$finder->findOne( $type, $sql, $bindings );
12973
	}
12974
12975
	/**
12976
	 * @deprecated
12977
	 *
12978
	 * Like find() but returns the last bean of the result array.
12979
	 * Opposite of Finder::findLast().
12980
	 * If no beans are found, this method will return NULL.
12981
	 *
12982
	 * Please do not use this function, it is horribly ineffective.
12983
	 * Instead use a reversed ORDER BY clause and a LIMIT 1 with R::findOne().
12984
	 * This function should never be used and only remains for
12985
	 * the sake of backward compatibility.
12986
	 *
12987
	 * @see Finder::find
12988
	 *
12989
	 * @param string $type     the type of bean you are looking for
12990
	 * @param string $sql      SQL query to find the desired bean, starting right after WHERE clause
12991
	 * @param array  $bindings values array of values to be bound to parameters in query
12992
	 *
12993
	 * @return OODBBean|NULL
12994
	 */
12995
	public static function findLast( $type, $sql = NULL, $bindings = array() )
12996
	{
12997
		return self::$finder->findLast( $type, $sql, $bindings );
12998
	}
12999
13000
	/**
13001
	 * Finds a BeanCollection using the repository.
13002
	 * A bean collection can be used to retrieve one bean at a time using
13003
	 * cursors - this is useful for processing large datasets. A bean collection
13004
	 * will not load all beans into memory all at once, just one at a time.
13005
	 *
13006
	 * @param  string $type     the type of bean you are looking for
13007
	 * @param  string $sql      SQL query to find the desired bean, starting right after WHERE clause
13008
	 * @param  array  $bindings values array of values to be bound to parameters in query
13009
	 *
13010
	 * @return BeanCollection
13011
	 */
13012
	public static function findCollection( $type, $sql = NULL, $bindings = array() )
13013
	{
13014
		return self::$finder->findCollection( $type, $sql, $bindings );
13015
	}
13016
13017
	/**
13018
	 * Returns a hashmap with bean arrays keyed by type using an SQL
13019
	 * query as its resource. Given an SQL query like 'SELECT movie.*, review.* FROM movie... JOIN review'
13020
	 * this method will return movie and review beans.
13021
	 *
13022
	 * Example:
13023
	 *
13024
	 * <code>
13025
	 * $stuff = $finder->findMulti('movie,review', '
13026
	 *          SELECT movie.*, review.* FROM movie
13027
	 *          LEFT JOIN review ON review.movie_id = movie.id');
13028
	 * </code>
13029
	 *
13030
	 * After this operation, $stuff will contain an entry 'movie' containing all
13031
	 * movies and an entry named 'review' containing all reviews (all beans).
13032
	 * You can also pass bindings.
13033
	 *
13034
	 * If you want to re-map your beans, so you can use $movie->ownReviewList without
13035
	 * having RedBeanPHP executing an SQL query you can use the fourth parameter to
13036
	 * define a selection of remapping closures.
13037
	 *
13038
	 * The remapping argument (optional) should contain an array of arrays.
13039
	 * Each array in the remapping array should contain the following entries:
13040
	 *
13041
	 * <code>
13042
	 * array(
13043
	 * 	'a'       => TYPE A
13044
	 *    'b'       => TYPE B
13045
	 *    'matcher' => MATCHING FUNCTION ACCEPTING A, B and ALL BEANS
13046
	 *    'do'      => OPERATION FUNCTION ACCEPTING A, B, ALL BEANS, ALL REMAPPINGS
13047
	 * )
13048
	 * </code>
13049
	 *
13050
	 * Using this mechanism you can build your own 'preloader' with tiny function
13051
	 * snippets (and those can be re-used and shared online of course).
13052
	 *
13053
	 * Example:
13054
	 *
13055
	 * <code>
13056
	 * array(
13057
	 * 	'a'       => 'movie'     //define A as movie
13058
	 *    'b'       => 'review'    //define B as review
13059
	 *    'matcher' => function( $a, $b ) {
13060
	 *       return ( $b->movie_id == $a->id );  //Perform action if review.movie_id equals movie.id
13061
	 *    }
13062
	 *    'do'      => function( $a, $b ) {
13063
	 *       $a->noLoad()->ownReviewList[] = $b; //Add the review to the movie
13064
	 *       $a->clearHistory();                 //optional, act 'as if these beans have been loaded through ownReviewList'.
13065
	 *    }
13066
	 * )
13067
	 * </code>
13068
	 *
13069
	 * @note the SQL query provided IS NOT THE ONE used internally by this function,
13070
	 * this function will pre-process the query to get all the data required to find the beans.
13071
	 *
13072
	 * @note if you use the 'book.*' notation make SURE you're
13073
	 * selector starts with a SPACE. ' book.*' NOT ',book.*'. This is because
13074
	 * it's actually an SQL-like template SLOT, not real SQL.
13075
	 *
13076
	 * @note instead of an SQL query you can pass a result array as well.
13077
	 *
13078
	 * @param string|array $types         a list of types (either array or comma separated string)
13079
	 * @param string|array $sql           an SQL query or an array of prefetched records
13080
	 * @param array        $bindings      optional, bindings for SQL query
13081
	 * @param array        $remappings    optional, an array of remapping arrays
13082
	 *
13083
	 * @return array
13084
	 */
13085
	public static function findMulti( $types, $sql, $bindings = array(), $remappings = array() )
13086
	{
13087
		return self::$finder->findMulti( $types, $sql, $bindings, $remappings );
13088
	}
13089
13090
	/**
13091
	 * Returns an array of beans. Pass a type and a series of ids and
13092
	 * this method will bring you the corresponding beans.
13093
	 *
13094
	 * important note: Because this method loads beans using the load()
13095
	 * function (but faster) it will return empty beans with ID 0 for
13096
	 * every bean that could not be located. The resulting beans will have the
13097
	 * passed IDs as their keys.
13098
	 *
13099
	 * @param string $type type of beans
13100
	 * @param array  $ids  ids to load
13101
	 *
13102
	 * @return array
13103
	 */
13104
	public static function batch( $type, $ids )
13105
	{
13106
		return self::$redbean->batch( $type, $ids );
13107
	}
13108
13109
	/**
13110
	 * Alias for batch(). Batch method is older but since we added so-called *All
13111
	 * methods like storeAll, trashAll, dispenseAll and findAll it seemed logical to
13112
	 * improve the consistency of the Facade API and also add an alias for batch() called
13113
	 * loadAll.
13114
	 *
13115
	 * @param string $type type of beans
13116
	 * @param array  $ids  ids to load
13117
	 *
13118
	 * @return array
13119
	 */
13120
	public static function loadAll( $type, $ids )
13121
	{
13122
		return self::$redbean->batch( $type, $ids );
13123
	}
13124
13125
	/**
13126
	 * Convenience function to execute Queries directly.
13127
	 * Executes SQL.
13128
	 *
13129
	 * @param string $sql       SQL query to execute
13130
	 * @param array  $bindings  a list of values to be bound to query parameters
13131
	 *
13132
	 * @return integer
13133
	 */
13134
	public static function exec( $sql, $bindings = array() )
13135
	{
13136
		return self::query( 'exec', $sql, $bindings );
13137
	}
13138
13139
	/**
13140
	 * Convenience function to fire an SQL query using the RedBeanPHP
13141
	 * database adapter. This method allows you to directly query the
13142
	 * database without having to obtain an database adapter instance first.
13143
	 * Executes the specified SQL query together with the specified
13144
	 * parameter bindings and returns all rows
13145
	 * and all columns.
13146
	 *
13147
	 * @param string $sql      SQL query to execute
13148
	 * @param array  $bindings a list of values to be bound to query parameters
13149
	 *
13150
	 * @return array
13151
	 */
13152
	public static function getAll( $sql, $bindings = array() )
13153
	{
13154
		return self::query( 'get', $sql, $bindings );
13155
	}
13156
13157
	/**
13158
	 * Convenience function to fire an SQL query using the RedBeanPHP
13159
	 * database adapter. This method allows you to directly query the
13160
	 * database without having to obtain an database adapter instance first.
13161
	 * Executes the specified SQL query together with the specified
13162
	 * parameter bindings and returns a single cell.
13163
	 *
13164
	 * @param string $sql      SQL query to execute
13165
	 * @param array  $bindings a list of values to be bound to query parameters
13166
	 *
13167
	 * @return string
13168
	 */
13169
	public static function getCell( $sql, $bindings = array() )
13170
	{
13171
		return self::query( 'getCell', $sql, $bindings );
13172
	}
13173
13174
	/**
13175
	 * Convenience function to fire an SQL query using the RedBeanPHP
13176
	 * database adapter. This method allows you to directly query the
13177
	 * database without having to obtain an database adapter instance first.
13178
	 * Executes the specified SQL query together with the specified
13179
	 * parameter bindings and returns a PDOCursor instance.
13180
	 *
13181
	 * @param string $sql      SQL query to execute
13182
	 * @param array  $bindings a list of values to be bound to query parameters
13183
	 *
13184
	 * @return RedBeanPHP\Cursor\PDOCursor
13185
	 */
13186
	public static function getCursor( $sql, $bindings = array() )
13187
	{
13188
		return self::query( 'getCursor', $sql, $bindings );
13189
	}
13190
13191
	/**
13192
	 * Convenience function to fire an SQL query using the RedBeanPHP
13193
	 * database adapter. This method allows you to directly query the
13194
	 * database without having to obtain an database adapter instance first.
13195
	 * Executes the specified SQL query together with the specified
13196
	 * parameter bindings and returns a single row.
13197
	 *
13198
	 * @param string $sql      SQL query to execute
13199
	 * @param array  $bindings a list of values to be bound to query parameters
13200
	 *
13201
	 * @return array
13202
	 */
13203
	public static function getRow( $sql, $bindings = array() )
13204
	{
13205
		return self::query( 'getRow', $sql, $bindings );
13206
	}
13207
13208
	/**
13209
	 * Convenience function to fire an SQL query using the RedBeanPHP
13210
	 * database adapter. This method allows you to directly query the
13211
	 * database without having to obtain an database adapter instance first.
13212
	 * Executes the specified SQL query together with the specified
13213
	 * parameter bindings and returns a single column.
13214
	 *
13215
	 * @param string $sql      SQL query to execute
13216
	 * @param array  $bindings a list of values to be bound to query parameters
13217
	 *
13218
	 * @return array
13219
	 */
13220
	public static function getCol( $sql, $bindings = array() )
13221
	{
13222
		return self::query( 'getCol', $sql, $bindings );
13223
	}
13224
13225
	/**
13226
	 * Convenience function to execute Queries directly.
13227
	 * Executes SQL.
13228
	 * Results will be returned as an associative array. The first
13229
	 * column in the select clause will be used for the keys in this array and
13230
	 * the second column will be used for the values. If only one column is
13231
	 * selected in the query, both key and value of the array will have the
13232
	 * value of this field for each row.
13233
	 *
13234
	 * @param string $sql      SQL query to execute
13235
	 * @param array  $bindings a list of values to be bound to query parameters
13236
	 *
13237
	 * @return array
13238
	 */
13239
	public static function getAssoc( $sql, $bindings = array() )
13240
	{
13241
		return self::query( 'getAssoc', $sql, $bindings );
13242
	}
13243
13244
	/**
13245
	 *Convenience function to fire an SQL query using the RedBeanPHP
13246
	 * database adapter. This method allows you to directly query the
13247
	 * database without having to obtain an database adapter instance first.
13248
	 * Executes the specified SQL query together with the specified
13249
	 * parameter bindings and returns an associative array.
13250
	 * Results will be returned as an associative array indexed by the first
13251
	 * column in the select.
13252
	 *
13253
	 * @param string $sql      SQL query to execute
13254
	 * @param array  $bindings a list of values to be bound to query parameters
13255
	 *
13256
	 * @return array
13257
	 */
13258
	public static function getAssocRow( $sql, $bindings = array() )
13259
	{
13260
		return self::query( 'getAssocRow', $sql, $bindings );
13261
	}
13262
13263
	/**
13264
	 * Returns the insert ID for databases that support/require this
13265
	 * functionality. Alias for R::getAdapter()->getInsertID().
13266
	 *
13267
	 * @return mixed
13268
	 */
13269
	public static function getInsertID()
13270
	{
13271
		return self::$adapter->getInsertID();
13272
	}
13273
13274
	/**
13275
	 * Makes a copy of a bean. This method makes a deep copy
13276
	 * of the bean.The copy will have the following features.
13277
	 * - All beans in own-lists will be duplicated as well
13278
	 * - All references to shared beans will be copied but not the shared beans themselves
13279
	 * - All references to parent objects (_id fields) will be copied but not the parents themselves
13280
	 * In most cases this is the desired scenario for copying beans.
13281
	 * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
13282
	 * (i.e. one that already has been processed) the ID of the bean will be returned.
13283
	 * This should not happen though.
13284
	 *
13285
	 * Note:
13286
	 * This function does a reflectional database query so it may be slow.
13287
	 *
13288
	 * @deprecated
13289
	 * This function is deprecated in favour of R::duplicate().
13290
	 * This function has a confusing method signature, the R::duplicate() function
13291
	 * only accepts two arguments: bean and filters.
13292
	 *
13293
	 * @param OODBBean $bean    bean to be copied
13294
	 * @param array    $trail   for internal usage, pass array()
13295
	 * @param boolean  $pid     for internal usage
13296
	 * @param array    $filters white list filter with bean types to duplicate
13297
	 *
13298
	 * @return array
13299
	 */
13300
	public static function dup( $bean, $trail = array(), $pid = FALSE, $filters = array() )
13301
	{
13302
		self::$duplicationManager->setFilters( $filters );
13303
		return self::$duplicationManager->dup( $bean, $trail, $pid );
13304
	}
13305
13306
	/**
13307
	 * Makes a deep copy of a bean. This method makes a deep copy
13308
	 * of the bean.The copy will have the following:
13309
	 *
13310
	 * * All beans in own-lists will be duplicated as well
13311
	 * * All references to shared beans will be copied but not the shared beans themselves
13312
	 * * All references to parent objects (_id fields) will be copied but not the parents themselves
13313
	 *
13314
	 * In most cases this is the desired scenario for copying beans.
13315
	 * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
13316
	 * (i.e. one that already has been processed) the ID of the bean will be returned.
13317
	 * This should not happen though.
13318
	 *
13319
	 * Note:
13320
	 * This function does a reflectional database query so it may be slow.
13321
	 *
13322
	 * Note:
13323
	 * This is a simplified version of the deprecated R::dup() function.
13324
	 *
13325
	 * @param OODBBean $bean  bean to be copied
13326
	 * @param array    $white white list filter with bean types to duplicate
13327
	 *
13328
	 * @return array
13329
	 */
13330
	public static function duplicate( $bean, $filters = array() )
13331
	{
13332
		return self::dup( $bean, array(), FALSE, $filters );
13333
	}
13334
13335
	/**
13336
	 * Exports a collection of beans. Handy for XML/JSON exports with a
13337
	 * Javascript framework like Dojo or ExtJS.
13338
	 * What will be exported:
13339
	 *
13340
	 * * contents of the bean
13341
	 * * all own bean lists (recursively)
13342
	 * * all shared beans (not THEIR own lists)
13343
	 *
13344
	 * @param    array|OODBBean $beans   beans to be exported
13345
	 * @param    boolean        $parents whether you want parent beans to be exported
13346
	 * @param    array          $filters whitelist of types
13347
	 *
13348
	 * @return array
13349
	 */
13350
	public static function exportAll( $beans, $parents = FALSE, $filters = array())
13351
	{
13352
		return self::$duplicationManager->exportAll( $beans, $parents, $filters, self::$exportCaseStyle );
13353
	}
13354
13355
	/**
13356
	 * Selects case style for export.
13357
	 * This will determine the case style for the keys of exported beans (see exportAll).
13358
	 * The following options are accepted:
13359
	 *
13360
	 * * 'default' RedBeanPHP by default enforces Snake Case (i.e. book_id is_valid )
13361
	 * * 'camel'   Camel Case   (i.e. bookId isValid   )
13362
	 * * 'dolphin' Dolphin Case (i.e. bookID isValid   ) Like CamelCase but ID is written all uppercase
13363
	 *
13364
	 * @warning RedBeanPHP transforms camelCase to snake_case using a slightly different
13365
	 * algorithm, it also converts isACL to is_acl (not is_a_c_l) and bookID to book_id.
13366
	 * Due to information loss this cannot be corrected. However if you might try
13367
	 * DolphinCase for IDs it takes into account the exception concerning IDs.
13368
	 *
13369
	 * @param string $caseStyle case style identifier
13370
	 *
13371
	 * @return void
13372
	 */
13373
	public static function useExportCase( $caseStyle = 'default' )
13374
	{
13375
		if ( !in_array( $caseStyle, array( 'default', 'camel', 'dolphin' ) ) ) throw new RedException( 'Invalid case selected.' );
13376
		self::$exportCaseStyle = $caseStyle;
13377
	}
13378
13379
	/**
13380
	 * Converts a series of rows to beans.
13381
	 * This method converts a series of rows to beans.
13382
	 * The type of the desired output beans can be specified in the
13383
	 * first parameter. The second parameter is meant for the database
13384
	 * result rows.
13385
	 *
13386
	 * Usage:
13387
	 *
13388
	 * <code>
13389
	 * $rows = R::getAll( 'SELECT * FROM ...' )
13390
	 * $beans = R::convertToBeans( $rows );
13391
	 * </code>
13392
	 *
13393
	 * As of version 4.3.2 you can specify a meta-mask.
13394
	 * Data from columns with names starting with the value specified in the mask
13395
	 * will be transferred to the meta section of a bean (under data.bundle).
13396
	 *
13397
	 * <code>
13398
	 * $rows = R::getAll( 'SELECT FROM... COUNT(*) AS extra_count ...' );
13399
	 * $beans = R::convertToBeans( $rows );
13400
	 * $bean = reset( $beans );
13401
	 * $data = $bean->getMeta( 'data.bundle' );
13402
	 * $extra_count = $data['extra_count'];
13403
	 * </code>
13404
	 *
13405
	 * New in 4.3.2: meta mask. The meta mask is a special mask to send
13406
	 * data from raw result rows to the meta store of the bean. This is
13407
	 * useful for bundling additional information with custom queries.
13408
	 * Values of every column whos name starts with $mask will be
13409
	 * transferred to the meta section of the bean under key 'data.bundle'.
13410
	 *
13411
	 * @param string $type     type of beans to produce
13412
	 * @param array  $rows     must contain an array of array
13413
	 * @param string $metamask meta mask to apply (optional)
13414
	 *
13415
	 * @return array
13416
	 */
13417
	public static function convertToBeans( $type, $rows, $metamask = NULL )
13418
	{
13419
		return self::$redbean->convertToBeans( $type, $rows, $metamask );
13420
	}
13421
13422
	/**
13423
	 * Just like converToBeans, but for one bean.
13424
	 *
13425
	 * @param string $type      type of bean to produce
13426
	 * @param array  $row       one row from the database
13427
	 * @param string $metamask  metamask (see convertToBeans)
13428
	 *
13429
	 * @return OODBBean
13430
	 */
13431
	public static function convertToBean( $type, $row, $metamask = NULL )
13432
	{
13433
		$beans = self::$redbean->convertToBeans( $type, array( $row ), $metamask );
13434
		$bean  = reset( $beans );
13435
		return $bean;
13436
	}
13437
13438
	/**
13439
	 * Tests whether a bean has been associated with one ore more
13440
	 * of the listed tags. If the third parameter is TRUE this method
13441
	 * will return TRUE only if all tags that have been specified are indeed
13442
	 * associated with the given bean, otherwise FALSE.
13443
	 * If the third parameter is FALSE this
13444
	 * method will return TRUE if one of the tags matches, FALSE if none
13445
	 * match.
13446
	 *
13447
	 * Tag list can be either an array with tag names or a comma separated list
13448
	 * of tag names.
13449
	 *
13450
	 * Usage:
13451
	 *
13452
	 * <code>
13453
	 * R::hasTag( $blog, 'horror,movie', TRUE );
13454
	 * </code>
13455
	 *
13456
	 * The example above returns TRUE if the $blog bean has been tagged
13457
	 * as BOTH horror and movie. If the post has only been tagged as 'movie'
13458
	 * or 'horror' this operation will return FALSE because the third parameter
13459
	 * has been set to TRUE.
13460
	 *
13461
	 * @param  OODBBean     $bean bean to check for tags
13462
	 * @param  array|string $tags list of tags
13463
	 * @param  boolean      $all  whether they must all match or just some
13464
	 *
13465
	 * @return boolean
13466
	 */
13467
	public static function hasTag( $bean, $tags, $all = FALSE )
13468
	{
13469
		return self::$tagManager->hasTag( $bean, $tags, $all );
13470
	}
13471
13472
	/**
13473
	 * Removes all specified tags from the bean. The tags specified in
13474
	 * the second parameter will no longer be associated with the bean.
13475
	 *
13476
	 * Tag list can be either an array with tag names or a comma separated list
13477
	 * of tag names.
13478
	 *
13479
	 * Usage:
13480
	 *
13481
	 * <code>
13482
	 * R::untag( $blog, 'smart,interesting' );
13483
	 * </code>
13484
	 *
13485
	 * In the example above, the $blog bean will no longer
13486
	 * be associated with the tags 'smart' and 'interesting'.
13487
	 *
13488
	 * @param  OODBBean $bean    tagged bean
13489
	 * @param  array    $tagList list of tags (names)
13490
	 *
13491
	 * @return void
13492
	 */
13493
	public static function untag( $bean, $tagList )
13494
	{
13495
		self::$tagManager->untag( $bean, $tagList );
13496
	}
13497
13498
	/**
13499
	 * Tags a bean or returns tags associated with a bean.
13500
	 * If $tagList is NULL or omitted this method will return a
13501
	 * comma separated list of tags associated with the bean provided.
13502
	 * If $tagList is a comma separated list (string) of tags all tags will
13503
	 * be associated with the bean.
13504
	 * You may also pass an array instead of a string.
13505
	 *
13506
	 * Usage:
13507
	 *
13508
	 * <code>
13509
	 * R::tag( $meal, "TexMex,Mexican" );
13510
	 * $tags = R::tag( $meal );
13511
	 * </code>
13512
	 *
13513
	 * The first line in the example above will tag the $meal
13514
	 * as 'TexMex' and 'Mexican Cuisine'. The second line will
13515
	 * retrieve all tags attached to the meal object.
13516
	 *
13517
	 * @param OODBBean $bean    bean to tag
13518
	 * @param mixed    $tagList tags to attach to the specified bean
13519
	 *
13520
	 * @return string
13521
	 */
13522
	public static function tag( OODBBean $bean, $tagList = NULL )
13523
	{
13524
		return self::$tagManager->tag( $bean, $tagList );
13525
	}
13526
13527
	/**
13528
	 * Adds tags to a bean.
13529
	 * If $tagList is a comma separated list of tags all tags will
13530
	 * be associated with the bean.
13531
	 * You may also pass an array instead of a string.
13532
	 *
13533
	 * Usage:
13534
	 *
13535
	 * <code>
13536
	 * R::addTags( $blog, ["halloween"] );
13537
	 * </code>
13538
	 *
13539
	 * The example adds the tag 'halloween' to the $blog
13540
	 * bean.
13541
	 *
13542
	 * @param OODBBean $bean    bean to tag
13543
	 * @param array    $tagList list of tags to add to bean
13544
	 *
13545
	 * @return void
13546
	 */
13547
	public static function addTags( OODBBean $bean, $tagList )
13548
	{
13549
		self::$tagManager->addTags( $bean, $tagList );
13550
	}
13551
13552
	/**
13553
	 * Returns all beans that have been tagged with one or more
13554
	 * of the specified tags.
13555
	 *
13556
	 * Tag list can be either an array with tag names or a comma separated list
13557
	 * of tag names.
13558
	 *
13559
	 * Usage:
13560
	 *
13561
	 * <code>
13562
	 * $watchList = R::tagged(
13563
	 *   'movie',
13564
	 *   'horror,gothic',
13565
	 *   ' ORDER BY movie.title DESC LIMIT ?',
13566
	 *   [ 10 ]
13567
	 * );
13568
	 * </code>
13569
	 *
13570
	 * The example uses R::tagged() to find all movies that have been
13571
	 * tagged as 'horror' or 'gothic', order them by title and limit
13572
	 * the number of movies to be returned to 10.
13573
	 *
13574
	 * @param string       $beanType type of bean you are looking for
13575
	 * @param array|string $tagList  list of tags to match
13576
	 * @param string       $sql      additional SQL (use only for pagination)
13577
	 * @param array        $bindings bindings
13578
	 *
13579
	 * @return array
13580
	 */
13581
	public static function tagged( $beanType, $tagList, $sql = '', $bindings = array() )
13582
	{
13583
		return self::$tagManager->tagged( $beanType, $tagList, $sql, $bindings );
13584
	}
13585
13586
	/**
13587
	 * Returns all beans that have been tagged with ALL of the tags given.
13588
	 * This method works the same as R::tagged() except that this method only returns
13589
	 * beans that have been tagged with all the specified labels.
13590
	 *
13591
	 * Tag list can be either an array with tag names or a comma separated list
13592
	 * of tag names.
13593
	 *
13594
	 * Usage:
13595
	 *
13596
	 * <code>
13597
	 * $watchList = R::taggedAll(
13598
	 *    'movie',
13599
	 *    [ 'gothic', 'short' ],
13600
	 *    ' ORDER BY movie.id DESC LIMIT ? ',
13601
	 *    [ 4 ]
13602
	 * );
13603
	 * </code>
13604
	 *
13605
	 * The example above returns at most 4 movies (due to the LIMIT clause in the SQL
13606
	 * Query Snippet) that have been tagged as BOTH 'short' AND 'gothic'.
13607
	 *
13608
	 * @param string       $beanType type of bean you are looking for
13609
	 * @param array|string $tagList  list of tags to match
13610
	 * @param string       $sql      additional sql snippet
13611
	 * @param array        $bindings bindings
13612
	 *
13613
	 * @return array
13614
	 */
13615
	public static function taggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
13616
	{
13617
		return self::$tagManager->taggedAll( $beanType, $tagList, $sql, $bindings );
13618
	}
13619
13620
	/**
13621
	 * Same as taggedAll() but counts beans only (does not return beans).
13622
	 *
13623
	 * @see R::taggedAll
13624
	 *
13625
	 * @param string       $beanType type of bean you are looking for
13626
	 * @param array|string $tagList  list of tags to match
13627
	 * @param string       $sql      additional sql snippet
13628
	 * @param array        $bindings bindings
13629
	 *
13630
	 * @return integer
13631
	 */
13632
	public static function countTaggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
13633
	{
13634
		return self::$tagManager->countTaggedAll( $beanType, $tagList, $sql, $bindings );
13635
	}
13636
13637
	/**
13638
	 * Same as tagged() but counts beans only (does not return beans).
13639
	 *
13640
	 * @see R::tagged
13641
	 *
13642
	 * @param string       $beanType type of bean you are looking for
13643
	 * @param array|string $tagList  list of tags to match
13644
	 * @param string       $sql      additional sql snippet
13645
	 * @param array        $bindings bindings
13646
	 *
13647
	 * @return integer
13648
	 */
13649
	public static function countTagged( $beanType, $tagList, $sql = '', $bindings = array() )
13650
	{
13651
		return self::$tagManager->countTagged( $beanType, $tagList, $sql, $bindings );
13652
	}
13653
13654
	/**
13655
	 * Wipes all beans of type $beanType.
13656
	 *
13657
	 * @param string $beanType type of bean you want to destroy entirely
13658
	 *
13659
	 * @return boolean
13660
	 */
13661
	public static function wipe( $beanType )
13662
	{
13663
		return Facade::$redbean->wipe( $beanType );
13664
	}
13665
13666
	/**
13667
	 * Counts the number of beans of type $type.
13668
	 * This method accepts a second argument to modify the count-query.
13669
	 * A third argument can be used to provide bindings for the SQL snippet.
13670
	 *
13671
	 * @param string $type     type of bean we are looking for
13672
	 * @param string $addSQL   additional SQL snippet
13673
	 * @param array  $bindings parameters to bind to SQL
13674
	 *
13675
	 * @return integer
13676
	 */
13677
	public static function count( $type, $addSQL = '', $bindings = array() )
13678
	{
13679
		return Facade::$redbean->count( $type, $addSQL, $bindings );
13680
	}
13681
13682
	/**
13683
	 * Configures the facade, want to have a new Writer? A new Object Database or a new
13684
	 * Adapter and you want it on-the-fly? Use this method to hot-swap your facade with a new
13685
	 * toolbox.
13686
	 *
13687
	 * @param ToolBox $tb toolbox to configure facade with
13688
	 *
13689
	 * @return ToolBox
13690
	 */
13691
	public static function configureFacadeWithToolbox( ToolBox $tb )
13692
	{
13693
		$oldTools                 = self::$toolbox;
13694
		self::$toolbox            = $tb;
13695
		self::$writer             = self::$toolbox->getWriter();
13696
		self::$adapter            = self::$toolbox->getDatabaseAdapter();
13697
		self::$redbean            = self::$toolbox->getRedBean();
13698
		self::$finder             = new Finder( self::$toolbox );
13699
		self::$associationManager = new AssociationManager( self::$toolbox );
13700
		self::$tree               = new Tree( self::$toolbox );
13701
		self::$redbean->setAssociationManager( self::$associationManager );
13702
		self::$labelMaker         = new LabelMaker( self::$toolbox );
13703
		$helper                   = new SimpleModelHelper();
13704
		$helper->attachEventListeners( self::$redbean );
13705
		if (self::$redbean->getBeanHelper() == NULL) {
13706
			self::$redbean->setBeanHelper( new SimpleFacadeBeanHelper );
13707
		}
13708
		self::$duplicationManager = new DuplicationManager( self::$toolbox );
13709
		self::$tagManager         = new TagManager( self::$toolbox );
13710
		return $oldTools;
13711
	}
13712
13713
	/**
13714
	 * Facade Convience method for adapter transaction system.
13715
	 * Begins a transaction.
13716
	 *
13717
	 * Usage:
13718
	 *
13719
	 * <code>
13720
	 * R::begin();
13721
	 * try {
13722
	 *  $bean1 = R::dispense( 'bean' );
13723
	 *  R::store( $bean1 );
13724
	 *  $bean2 = R::dispense( 'bean' );
13725
	 *  R::store( $bean2 );
13726
	 *  R::commit();
13727
	 * } catch( \Exception $e ) {
13728
	 *  R::rollback();
13729
	 * }
13730
	 * </code>
13731
	 *
13732
	 * The example above illustrates how transactions in RedBeanPHP are used.
13733
	 * In this example 2 beans are stored or nothing is stored at all.
13734
	 * It's not possible for this piece of code to store only half of the beans.
13735
	 * If an exception occurs, the transaction gets rolled back and the database
13736
	 * will be left 'untouched'.
13737
	 *
13738
	 * In fluid mode transactions will be ignored and all queries will
13739
	 * be executed as-is because database schema changes will automatically
13740
	 * trigger the transaction system to commit everything in some database
13741
	 * systems. If you use a database that can handle DDL changes you might wish
13742
	 * to use setAllowFluidTransactions(TRUE). If you do this, the behavior of
13743
	 * this function in fluid mode will depend on the database platform used.
13744
	 *
13745
	 * @return bool
13746
	 */
13747
	public static function begin()
13748
	{
13749
		if ( !self::$allowFluidTransactions && !self::$redbean->isFrozen() ) return FALSE;
13750
		self::$adapter->startTransaction();
13751
		return TRUE;
13752
	}
13753
13754
	/**
13755
	 * Facade Convience method for adapter transaction system.
13756
	 * Commits a transaction.
13757
	 *
13758
	 * Usage:
13759
	 *
13760
	 * <code>
13761
	 * R::begin();
13762
	 * try {
13763
	 *  $bean1 = R::dispense( 'bean' );
13764
	 *  R::store( $bean1 );
13765
	 *  $bean2 = R::dispense( 'bean' );
13766
	 *  R::store( $bean2 );
13767
	 *  R::commit();
13768
	 * } catch( \Exception $e ) {
13769
	 *  R::rollback();
13770
	 * }
13771
	 * </code>
13772
	 *
13773
	 * The example above illustrates how transactions in RedBeanPHP are used.
13774
	 * In this example 2 beans are stored or nothing is stored at all.
13775
	 * It's not possible for this piece of code to store only half of the beans.
13776
	 * If an exception occurs, the transaction gets rolled back and the database
13777
	 * will be left 'untouched'.
13778
	 *
13779
	 * In fluid mode transactions will be ignored and all queries will
13780
	 * be executed as-is because database schema changes will automatically
13781
	 * trigger the transaction system to commit everything in some database
13782
	 * systems. If you use a database that can handle DDL changes you might wish
13783
	 * to use setAllowFluidTransactions(TRUE). If you do this, the behavior of
13784
	 * this function in fluid mode will depend on the database platform used.
13785
	 *
13786
	 * @return bool
13787
	 */
13788
	public static function commit()
13789
	{
13790
		if ( !self::$allowFluidTransactions && !self::$redbean->isFrozen() ) return FALSE;
13791
		self::$adapter->commit();
13792
		return TRUE;
13793
	}
13794
13795
	/**
13796
	 * Facade Convience method for adapter transaction system.
13797
	 * Rolls back a transaction.
13798
	 *
13799
	 * Usage:
13800
	 *
13801
	 * <code>
13802
	 * R::begin();
13803
	 * try {
13804
	 *  $bean1 = R::dispense( 'bean' );
13805
	 *  R::store( $bean1 );
13806
	 *  $bean2 = R::dispense( 'bean' );
13807
	 *  R::store( $bean2 );
13808
	 *  R::commit();
13809
	 * } catch( \Exception $e ) {
13810
	 *  R::rollback();
13811
	 * }
13812
	 * </code>
13813
	 *
13814
	 * The example above illustrates how transactions in RedBeanPHP are used.
13815
	 * In this example 2 beans are stored or nothing is stored at all.
13816
	 * It's not possible for this piece of code to store only half of the beans.
13817
	 * If an exception occurs, the transaction gets rolled back and the database
13818
	 * will be left 'untouched'.
13819
	 *
13820
	 * In fluid mode transactions will be ignored and all queries will
13821
	 * be executed as-is because database schema changes will automatically
13822
	 * trigger the transaction system to commit everything in some database
13823
	 * systems. If you use a database that can handle DDL changes you might wish
13824
	 * to use setAllowFluidTransactions(TRUE). If you do this, the behavior of
13825
	 * this function in fluid mode will depend on the database platform used.
13826
	 *
13827
	 * @return bool
13828
	 */
13829
	public static function rollback()
13830
	{
13831
		if ( !self::$allowFluidTransactions && !self::$redbean->isFrozen() ) return FALSE;
13832
		self::$adapter->rollback();
13833
		return TRUE;
13834
	}
13835
13836
	/**
13837
	 * Returns a list of columns. Format of this array:
13838
	 * array( fieldname => type )
13839
	 * Note that this method only works in fluid mode because it might be
13840
	 * quite heavy on production servers!
13841
	 *
13842
	 * @param  string $table name of the table (not type) you want to get columns of
13843
	 *
13844
	 * @return array
13845
	 */
13846
	public static function getColumns( $table )
13847
	{
13848
		return self::$writer->getColumns( $table );
13849
	}
13850
13851
	/**
13852
	 * Generates question mark slots for an array of values.
13853
	 * Given an array and an optional template string this method
13854
	 * will produce string containing parameter slots for use in
13855
	 * an SQL query string.
13856
	 *
13857
	 * Usage:
13858
	 *
13859
	 * <code>
13860
	 * R::genSlots( array( 'a', 'b' ) );
13861
	 * </code>
13862
	 *
13863
	 * The statement in the example will produce the string:
13864
	 * '?,?'.
13865
	 *
13866
	 * Another example, using a template string:
13867
	 *
13868
	 * <code>
13869
	 * R::genSlots( array('a', 'b'), ' IN( %s ) ' );
13870
	 * </code>
13871
	 *
13872
	 * The statement in the example will produce the string:
13873
	 * ' IN( ?,? ) '.
13874
	 *
13875
	 * @param array  $array    array to generate question mark slots for
13876
	 * @param string $template template to use
13877
	 *
13878
	 * @return string
13879
	 */
13880
	public static function genSlots( $array, $template = NULL )
13881
	{
13882
		return ArrayTool::genSlots( $array, $template );
13883
	}
13884
13885
	/**
13886
	 * Flattens a multi dimensional bindings array for use with genSlots().
13887
	 *
13888
	 * Usage:
13889
	 *
13890
	 * <code>
13891
	 * R::flat( array( 'a', array( 'b' ), 'c' ) );
13892
	 * </code>
13893
	 *
13894
	 * produces an array like: [ 'a', 'b', 'c' ]
13895
	 *
13896
	 * @param array $array  array to flatten
13897
	 * @param array $result result array parameter (for recursion)
13898
	 *
13899
	 * @return array
13900
	 */
13901
	public static function flat( $array, $result = array() )
13902
	{
13903
		return ArrayTool::flat( $array, $result );
13904
	}
13905
13906
	/**
13907
	 * Nukes the entire database.
13908
	 * This will remove all schema structures from the database.
13909
	 * Only works in fluid mode. Be careful with this method.
13910
	 *
13911
	 * @warning dangerous method, will remove all tables, columns etc.
13912
	 *
13913
	 * @return void
13914
	 */
13915
	public static function nuke()
13916
	{
13917
		if ( !self::$redbean->isFrozen() ) {
13918
			self::$writer->wipeAll();
13919
		}
13920
	}
13921
13922
	/**
13923
	 * Short hand function to store a set of beans at once, IDs will be
13924
	 * returned as an array. For information please consult the R::store()
13925
	 * function.
13926
	 * A loop saver.
13927
	 *
13928
	 * @param array $beans list of beans to be stored
13929
	 *
13930
	 * @return array
13931
	 */
13932
	public static function storeAll( $beans )
13933
	{
13934
		$ids = array();
13935
		foreach ( $beans as $bean ) {
13936
			$ids[] = self::store( $bean );
13937
		}
13938
		return $ids;
13939
	}
13940
13941
	/**
13942
	 * Short hand function to trash a set of beans at once.
13943
	 * For information please consult the R::trash() function.
13944
	 * A loop saver.
13945
	 *
13946
	 * @param array $beans list of beans to be trashed
13947
	 *
13948
	 * @return void
13949
	 */
13950
	public static function trashAll( $beans )
13951
	{
13952
		foreach ( $beans as $bean ) {
13953
			self::trash( $bean );
13954
		}
13955
	}
13956
13957
	/**
13958
	 * Short hand function to trash a series of beans using
13959
	 * only IDs. This function combines trashAll and batch loading
13960
	 * in one call. Note that while this function accepts just
13961
	 * bean IDs, the beans will still be loaded first. This is because
13962
	 * the function still respects all the FUSE hooks that may have beeb
13963
	 * associated with the domain logic associated with these beans.
13964
	 * If you really want to delete just records from the database use
13965
	 * a simple DELETE-FROM SQL query instead.
13966
	 *
13967
	 * @param string type  $type the bean type you wish to trash
13968
	 * @param string array $ids  list of bean IDs
13969
	 *
13970
	 * @return void
13971
	 */
13972
	public static function trashBatch( $type, $ids )
13973
	{
13974
		self::trashAll( self::batch( $type, $ids ) );
13975
	}
13976
13977
	/**
13978
	 * Short hand function to find and trash beans.
13979
	 * This function combines trashAll and find.
13980
	 * Given a bean type, a query snippet and optionally some parameter
13981
	 * bindings, this function will search for the beans described in the
13982
	 * query and its parameters and then feed them to the trashAll function
13983
	 * to be trashed.
13984
	 *
13985
	 * Note that while this function accepts just
13986
	 * a bean type and query snippet, the beans will still be loaded first. This is because
13987
	 * the function still respects all the FUSE hooks that may have been
13988
	 * associated with the domain logic associated with these beans.
13989
	 * If you really want to delete just records from the database use
13990
	 * a simple DELETE-FROM SQL query instead.
13991
	 *
13992
	 * Returns the number of beans deleted.
13993
	 *
13994
	 * @param string $type       bean type to look for in database
13995
	 * @param string $sqlSnippet an SQL query snippet
13996
	 * @param array  $bindings   SQL parameter bindings
13997
	 *
13998
	 * @return int
13999
	 */
14000
	public static function hunt( $type, $sqlSnippet = NULL, $bindings = array() )
14001
	{
14002
		$numberOfTrashedBeans = 0;
14003
		$beans = self::findCollection( $type, $sqlSnippet, $bindings );
14004
		while( $bean = $beans->next() ) {
14005
			self::trash( $bean );
14006
			$numberOfTrashedBeans++;
14007
		}
14008
		return $numberOfTrashedBeans;
14009
	}
14010
14011
	/**
14012
	 * Toggles Writer Cache.
14013
	 * Turns the Writer Cache on or off. The Writer Cache is a simple
14014
	 * query based caching system that may improve performance without the need
14015
	 * for cache management. This caching system will cache non-modifying queries
14016
	 * that are marked with special SQL comments. As soon as a non-marked query
14017
	 * gets executed the cache will be flushed. Only non-modifying select queries
14018
	 * have been marked therefore this mechanism is a rather safe way of caching, requiring
14019
	 * no explicit flushes or reloads. Of course this does not apply if you intend to test
14020
	 * or simulate concurrent querying.
14021
	 *
14022
	 * @param boolean $yesNo TRUE to enable cache, FALSE to disable cache
14023
	 *
14024
	 * @return void
14025
	 */
14026
	public static function useWriterCache( $yesNo )
14027
	{
14028
		self::getWriter()->setUseCache( $yesNo );
14029
	}
14030
14031
	/**
14032
	 * A label is a bean with only an id, type and name property.
14033
	 * This function will dispense beans for all entries in the array. The
14034
	 * values of the array will be assigned to the name property of each
14035
	 * individual bean.
14036
	 *
14037
	 * @param string $type   type of beans you would like to have
14038
	 * @param array  $labels list of labels, names for each bean
14039
	 *
14040
	 * @return array
14041
	 */
14042
	public static function dispenseLabels( $type, $labels )
14043
	{
14044
		return self::$labelMaker->dispenseLabels( $type, $labels );
14045
	}
14046
14047
	/**
14048
	 * Generates and returns an ENUM value. This is how RedBeanPHP handles ENUMs.
14049
	 * Either returns a (newly created) bean respresenting the desired ENUM
14050
	 * value or returns a list of all enums for the type.
14051
	 *
14052
	 * To obtain (and add if necessary) an ENUM value:
14053
	 *
14054
	 * <code>
14055
	 * $tea->flavour = R::enum( 'flavour:apple' );
14056
	 * </code>
14057
	 *
14058
	 * Returns a bean of type 'flavour' with  name = apple.
14059
	 * This will add a bean with property name (set to APPLE) to the database
14060
	 * if it does not exist yet.
14061
	 *
14062
	 * To obtain all flavours:
14063
	 *
14064
	 * <code>
14065
	 * R::enum('flavour');
14066
	 * </code>
14067
	 *
14068
	 * To get a list of all flavour names:
14069
	 *
14070
	 * <code>
14071
	 * R::gatherLabels( R::enum( 'flavour' ) );
14072
	 * </code>
14073
	 *
14074
	 * @param string $enum either type or type-value
14075
	 *
14076
	 * @return array|OODBBean
14077
	 */
14078
	public static function enum( $enum )
14079
	{
14080
		return self::$labelMaker->enum( $enum );
14081
	}
14082
14083
	/**
14084
	 * Gathers labels from beans. This function loops through the beans,
14085
	 * collects the values of the name properties of each individual bean
14086
	 * and stores the names in a new array. The array then gets sorted using the
14087
	 * default sort function of PHP (sort).
14088
	 *
14089
	 * @param array $beans list of beans to loop
14090
	 *
14091
	 * @return array
14092
	 */
14093
	public static function gatherLabels( $beans )
14094
	{
14095
		return self::$labelMaker->gatherLabels( $beans );
14096
	}
14097
14098
	/**
14099
	 * Closes the database connection.
14100
	 * While database connections are closed automatically at the end of the PHP script,
14101
	 * closing database connections is generally recommended to improve performance.
14102
	 * Closing a database connection will immediately return the resources to PHP.
14103
	 *
14104
	 * Usage:
14105
	 *
14106
	 * <code>
14107
	 * R::setup( ... );
14108
	 * ... do stuff ...
14109
	 * R::close();
14110
	 * </code>
14111
	 *
14112
	 * @return void
14113
	 */
14114
	public static function close()
14115
	{
14116
		if ( isset( self::$adapter ) ) {
14117
			self::$adapter->close();
14118
		}
14119
	}
14120
14121
	/**
14122
	 * Simple convenience function, returns ISO date formatted representation
14123
	 * of $time.
14124
	 *
14125
	 * @param mixed $time UNIX timestamp
14126
	 *
14127
	 * @return string
14128
	 */
14129
	public static function isoDate( $time = NULL )
14130
	{
14131
		if ( !$time ) {
14132
			$time = time();
14133
		}
14134
14135
		return @date( 'Y-m-d', $time );
14136
	}
14137
14138
	/**
14139
	 * Simple convenience function, returns ISO date time
14140
	 * formatted representation
14141
	 * of $time.
14142
	 *
14143
	 * @param mixed $time UNIX timestamp
14144
	 *
14145
	 * @return string
14146
	 */
14147
	public static function isoDateTime( $time = NULL )
14148
	{
14149
		if ( !$time ) $time = time();
14150
		return @date( 'Y-m-d H:i:s', $time );
14151
	}
14152
14153
	/**
14154
	 * Sets the database adapter you want to use.
14155
	 * The database adapter manages the connection to the database
14156
	 * and abstracts away database driver specific interfaces.
14157
	 *
14158
	 * @param Adapter $adapter Database Adapter for facade to use
14159
	 *
14160
	 * @return void
14161
	 */
14162
	public static function setDatabaseAdapter( Adapter $adapter )
14163
	{
14164
		self::$adapter = $adapter;
14165
	}
14166
14167
	/**
14168
	 * Sets the Query Writer you want to use.
14169
	 * The Query Writer writes and executes database queries using
14170
	 * the database adapter. It turns RedBeanPHP 'commands' into
14171
	 * database 'statements'.
14172
	 *
14173
	 * @param QueryWriter $writer Query Writer instance for facade to use
14174
	 *
14175
	 * @return void
14176
	 */
14177
	public static function setWriter( QueryWriter $writer )
14178
	{
14179
		self::$writer = $writer;
14180
	}
14181
14182
	/**
14183
	 * Sets the OODB you want to use.
14184
	 * The RedBeanPHP Object oriented database is the main RedBeanPHP
14185
	 * interface that allows you to store and retrieve RedBeanPHP
14186
	 * objects (i.e. beans).
14187
	 *
14188
	 * @param OODB $redbean Object Database for facade to use
14189
	 */
14190
	public static function setRedBean( OODB $redbean )
14191
	{
14192
		self::$redbean = $redbean;
14193
	}
14194
14195
	/**
14196
	 * Optional accessor for neat code.
14197
	 * Sets the database adapter you want to use.
14198
	 *
14199
	 * @return DBAdapter
14200
	 */
14201
	public static function getDatabaseAdapter()
14202
	{
14203
		return self::$adapter;
14204
	}
14205
14206
	/**
14207
	 * In case you use PDO (which is recommended and the default but not mandatory, hence
14208
	 * the database adapter), you can use this method to obtain the PDO object directly.
14209
	 * This is a convenience method, it will do the same as:
14210
	 *
14211
	 * <code>
14212
	 * R::getDatabaseAdapter()->getDatabase()->getPDO();
14213
	 * </code>
14214
	 *
14215
	 * If the PDO object could not be found, for whatever reason, this method
14216
	 * will return NULL instead.
14217
	 *
14218
	 * @return NULL|PDO
14219
	 */
14220
	public static function getPDO()
14221
	{
14222
		$databaseAdapter = self::getDatabaseAdapter();
14223
		if ( is_null( $databaseAdapter ) ) return NULL;
14224
		$database = $databaseAdapter->getDatabase();
14225
		if ( is_null( $database ) ) return NULL;
14226
		if ( !method_exists( $database, 'getPDO' ) ) return NULL;
14227
		return $database->getPDO();
14228
	}
14229
14230
	/**
14231
	 * Returns the current duplication manager instance.
14232
	 *
14233
	 * @return DuplicationManager
14234
	 */
14235
	public static function getDuplicationManager()
14236
	{
14237
		return self::$duplicationManager;
14238
	}
14239
14240
	/**
14241
	 * Optional accessor for neat code.
14242
	 * Sets the database adapter you want to use.
14243
	 *
14244
	 * @return QueryWriter
14245
	 */
14246
	public static function getWriter()
14247
	{
14248
		return self::$writer;
14249
	}
14250
14251
	/**
14252
	 * Optional accessor for neat code.
14253
	 * Sets the database adapter you want to use.
14254
	 *
14255
	 * @return OODB
14256
	 */
14257
	public static function getRedBean()
14258
	{
14259
		return self::$redbean;
14260
	}
14261
14262
	/**
14263
	 * Returns the toolbox currently used by the facade.
14264
	 * To set the toolbox use R::setup() or R::configureFacadeWithToolbox().
14265
	 * To create a toolbox use Setup::kickstart(). Or create a manual
14266
	 * toolbox using the ToolBox class.
14267
	 *
14268
	 * @return ToolBox
14269
	 */
14270
	public static function getToolBox()
14271
	{
14272
		return self::$toolbox;
14273
	}
14274
14275
	/**
14276
	 * Mostly for internal use, but might be handy
14277
	 * for some users.
14278
	 * This returns all the components of the currently
14279
	 * selected toolbox.
14280
	 *
14281
	 * Returns the components in the following order:
14282
	 *
14283
	 * # OODB instance (getRedBean())
14284
	 * # Database Adapter
14285
	 * # Query Writer
14286
	 * # Toolbox itself
14287
	 *
14288
	 * @return array
14289
	 */
14290
	public static function getExtractedToolbox()
14291
	{
14292
		return array( self::$redbean, self::$adapter, self::$writer, self::$toolbox );
14293
	}
14294
14295
	/**
14296
	 * Facade method for AQueryWriter::renameAssociation()
14297
	 *
14298
	 * @param string|array $from
14299
	 * @param string       $to
14300
	 *
14301
	 * @return void
14302
	 */
14303
	public static function renameAssociation( $from, $to = NULL )
14304
	{
14305
		AQueryWriter::renameAssociation( $from, $to );
14306
	}
14307
14308
	/**
14309
	 * Little helper method for Resty Bean Can server and others.
14310
	 * Takes an array of beans and exports each bean.
14311
	 * Unlike exportAll this method does not recurse into own lists
14312
	 * and shared lists, the beans are exported as-is, only loaded lists
14313
	 * are exported.
14314
	 *
14315
	 * @param array $beans beans
14316
	 *
14317
	 * @return array
14318
	 */
14319
	public static function beansToArray( $beans )
14320
	{
14321
		$list = array();
14322
		foreach( $beans as $bean ) $list[] = $bean->export();
14323
		return $list;
14324
	}
14325
14326
	/**
14327
	 * Sets the error mode for FUSE.
14328
	 * What to do if a FUSE model method does not exist?
14329
	 * You can set the following options:
14330
	 *
14331
	 * * OODBBean::C_ERR_IGNORE (default), ignores the call, returns NULL
14332
	 * * OODBBean::C_ERR_LOG, logs the incident using error_log
14333
	 * * OODBBean::C_ERR_NOTICE, triggers a E_USER_NOTICE
14334
	 * * OODBBean::C_ERR_WARN, triggers a E_USER_WARNING
14335
	 * * OODBBean::C_ERR_EXCEPTION, throws an exception
14336
	 * * OODBBean::C_ERR_FUNC, allows you to specify a custom handler (function)
14337
	 * * OODBBean::C_ERR_FATAL, triggers a E_USER_ERROR
14338
	 *
14339
	 * <code>
14340
	 * Custom handler method signature: handler( array (
14341
	 * 	'message' => string
14342
	 * 	'bean' => OODBBean
14343
	 * 	'method' => string
14344
	 * ) )
14345
	 * </code>
14346
	 *
14347
	 * This method returns the old mode and handler as an array.
14348
	 *
14349
	 * @param integer       $mode mode, determines how to handle errors
14350
	 * @param callable|NULL $func custom handler (if applicable)
14351
	 *
14352
	 * @return array
14353
	 */
14354
	public static function setErrorHandlingFUSE( $mode, $func = NULL )
14355
	{
14356
		return OODBBean::setErrorHandlingFUSE( $mode, $func );
14357
	}
14358
14359
	/**
14360
	 * Dumps bean data to array.
14361
	 * Given a one or more beans this method will
14362
	 * return an array containing first part of the string
14363
	 * representation of each item in the array.
14364
	 *
14365
	 * Usage:
14366
	 *
14367
	 * <code>
14368
	 * echo R::dump( $bean );
14369
	 * </code>
14370
	 *
14371
	 * The example shows how to echo the result of a simple
14372
	 * dump. This will print the string representation of the
14373
	 * specified bean to the screen, limiting the output per bean
14374
	 * to 35 characters to improve readability. Nested beans will
14375
	 * also be dumped.
14376
	 *
14377
	 * @param OODBBean|array $data either a bean or an array of beans
14378
	 *
14379
	 * @return array
14380
	 */
14381
	public static function dump( $data )
14382
	{
14383
		return Dump::dump( $data );
14384
	}
14385
14386
	/**
14387
	 * Binds an SQL function to a column.
14388
	 * This method can be used to setup a decode/encode scheme or
14389
	 * perform UUID insertion. This method is especially useful for handling
14390
	 * MySQL spatial columns, because they need to be processed first using
14391
	 * the asText/GeomFromText functions.
14392
	 *
14393
	 * Example:
14394
	 *
14395
	 * <code>
14396
	 * R::bindFunc( 'read', 'location.point', 'asText' );
14397
	 * R::bindFunc( 'write', 'location.point', 'GeomFromText' );
14398
	 * </code>
14399
	 *
14400
	 * Passing NULL as the function will reset (clear) the function
14401
	 * for this column/mode.
14402
	 *
14403
	 * @param string $mode     mode for function: i.e. read or write
14404
	 * @param string $field    field (table.column) to bind function to
14405
	 * @param string $function SQL function to bind to specified column
14406
	 *
14407
	 * @return void
14408
	 */
14409
	public static function bindFunc( $mode, $field, $function )
14410
	{
14411
		self::$redbean->bindFunc( $mode, $field, $function );
14412
	}
14413
14414
	/**
14415
	 * Sets global aliases.
14416
	 * Registers a batch of aliases in one go. This works the same as
14417
	 * fetchAs and setAutoResolve but explicitly. For instance if you register
14418
	 * the alias 'cover' for 'page' a property containing a reference to a
14419
	 * page bean called 'cover' will correctly return the page bean and not
14420
	 * a (non-existant) cover bean.
14421
	 *
14422
	 * <code>
14423
	 * R::aliases( array( 'cover' => 'page' ) );
14424
	 * $book = R::dispense( 'book' );
14425
	 * $page = R::dispense( 'page' );
14426
	 * $book->cover = $page;
14427
	 * R::store( $book );
14428
	 * $book = $book->fresh();
14429
	 * $cover = $book->cover;
14430
	 * echo $cover->getMeta( 'type' ); //page
14431
	 * </code>
14432
	 *
14433
	 * The format of the aliases registration array is:
14434
	 *
14435
	 * {alias} => {actual type}
14436
	 *
14437
	 * In the example above we use:
14438
	 *
14439
	 * cover => page
14440
	 *
14441
	 * From that point on, every bean reference to a cover
14442
	 * will return a 'page' bean. Note that with autoResolve this
14443
	 * feature along with fetchAs() is no longer very important, although
14444
	 * relying on explicit aliases can be a bit faster.
14445
	 *
14446
	 * @param array $list list of global aliases to use
14447
	 *
14448
	 * @return void
14449
	 */
14450
	public static function aliases( $list )
14451
	{
14452
		OODBBean::aliases( $list );
14453
	}
14454
14455
	/**
14456
	 * Tries to find a bean matching a certain type and
14457
	 * criteria set. If no beans are found a new bean
14458
	 * will be created, the criteria will be imported into this
14459
	 * bean and the bean will be stored and returned.
14460
	 * If multiple beans match the criteria only the first one
14461
	 * will be returned.
14462
	 *
14463
	 * @param string $type type of bean to search for
14464
	 * @param array  $like criteria set describing the bean to search for
14465
	 *
14466
	 * @return OODBBean
14467
	 */
14468
	public static function findOrCreate( $type, $like = array(), $sql = '' )
14469
	{
14470
		return self::$finder->findOrCreate( $type, $like, $sql = '' );
14471
	}
14472
14473
	/**
14474
	 * Tries to find beans matching the specified type and
14475
	 * criteria set.
14476
	 *
14477
	 * If the optional additional SQL snippet is a condition, it will
14478
	 * be glued to the rest of the query using the AND operator.
14479
	 *
14480
	 * @param string $type type of bean to search for
14481
	 * @param array  $like optional criteria set describing the bean to search for
14482
	 * @param string $sql  optional additional SQL for sorting
14483
	 * @param array  $bindings bindings
14484
	 *
14485
	 * @return array
14486
	 */
14487
	public static function findLike( $type, $like = array(), $sql = '', $bindings = array() )
14488
	{
14489
		return self::$finder->findLike( $type, $like, $sql, $bindings );
14490
	}
14491
14492
	/**
14493
	 * Starts logging queries.
14494
	 * Use this method to start logging SQL queries being
14495
	 * executed by the adapter. Logging queries will not
14496
	 * print them on the screen. Use R::getLogs() to
14497
	 * retrieve the logs.
14498
	 *
14499
	 * Usage:
14500
	 *
14501
	 * <code>
14502
	 * R::startLogging();
14503
	 * R::store( R::dispense( 'book' ) );
14504
	 * R::find('book', 'id > ?',[0]);
14505
	 * $logs = R::getLogs();
14506
	 * $count = count( $logs );
14507
	 * print_r( $logs );
14508
	 * R::stopLogging();
14509
	 * </code>
14510
	 *
14511
	 * In the example above we start a logging session during
14512
	 * which we store an empty bean of type book. To inspect the
14513
	 * logs we invoke R::getLogs() after stopping the logging.
14514
	 *
14515
	 * @note you cannot use R::debug and R::startLogging
14516
	 * at the same time because R::debug is essentially a
14517
	 * special kind of logging.
14518
	 *
14519
	 * @return void
14520
	 */
14521
	public static function startLogging()
14522
	{
14523
		self::debug( TRUE, RDefault::C_LOGGER_ARRAY );
14524
	}
14525
14526
	/**
14527
	 * Stops logging and flushes the logs,
14528
	 * convient method to stop logging of queries.
14529
	 * Use this method to stop logging SQL queries being
14530
	 * executed by the adapter. Logging queries will not
14531
	 * print them on the screen. Use R::getLogs() to
14532
	 * retrieve the logs.
14533
	 *
14534
	 * <code>
14535
	 * R::startLogging();
14536
	 * R::store( R::dispense( 'book' ) );
14537
	 * R::find('book', 'id > ?',[0]);
14538
	 * $logs = R::getLogs();
14539
	 * $count = count( $logs );
14540
	 * print_r( $logs );
14541
	 * R::stopLogging();
14542
	 * </code>
14543
	 *
14544
	 * In the example above we start a logging session during
14545
	 * which we store an empty bean of type book. To inspect the
14546
	 * logs we invoke R::getLogs() after stopping the logging.
14547
	 *
14548
	 * @note you cannot use R::debug and R::startLogging
14549
	 * at the same time because R::debug is essentially a
14550
	 * special kind of logging.
14551
	 *
14552
	 * @note by stopping the logging you also flush the logs.
14553
	 * Therefore, only stop logging AFTER you have obtained the
14554
	 * query logs using R::getLogs()
14555
	 *
14556
	 * @return void
14557
	 */
14558
	public static function stopLogging()
14559
	{
14560
		self::debug( FALSE );
14561
	}
14562
14563
	/**
14564
	 * Returns the log entries written after the startLogging.
14565
	 *
14566
	 * Use this method to obtain the query logs gathered
14567
	 * by the logging mechanisms.
14568
	 * Logging queries will not
14569
	 * print them on the screen. Use R::getLogs() to
14570
	 * retrieve the logs.
14571
	 *
14572
	 * <code>
14573
	 * R::startLogging();
14574
	 * R::store( R::dispense( 'book' ) );
14575
	 * R::find('book', 'id > ?',[0]);
14576
	 * $logs = R::getLogs();
14577
	 * $count = count( $logs );
14578
	 * print_r( $logs );
14579
	 * R::stopLogging();
14580
	 * </code>
14581
	 *
14582
	 * In the example above we start a logging session during
14583
	 * which we store an empty bean of type book. To inspect the
14584
	 * logs we invoke R::getLogs() after stopping the logging.
14585
	 *
14586
	 * The logs may look like:
14587
	 *
14588
	 * [1] => SELECT `book`.*  FROM `book`  WHERE id > ?  -- keep-cache
14589
	 * [2] => array ( 0 => 0, )
14590
	 * [3] => resultset: 1 rows
14591
	 *
14592
	 * Basically, element in the array is a log entry.
14593
	 * Parameter bindings are  represented as nested arrays (see 2).
14594
	 *
14595
	 * @note you cannot use R::debug and R::startLogging
14596
	 * at the same time because R::debug is essentially a
14597
	 * special kind of logging.
14598
	 *
14599
	 * @note by stopping the logging you also flush the logs.
14600
	 * Therefore, only stop logging AFTER you have obtained the
14601
	 * query logs using R::getLogs()
14602
	 *
14603
	 * @return array
14604
	 */
14605
	public static function getLogs()
14606
	{
14607
		return self::getLogger()->getLogs();
14608
	}
14609
14610
	/**
14611
	 * Resets the query counter.
14612
	 * The query counter can be used to monitor the number
14613
	 * of database queries that have
14614
	 * been processed according to the database driver. You can use this
14615
	 * to monitor the number of queries required to render a page.
14616
	 *
14617
	 * Usage:
14618
	 *
14619
	 * <code>
14620
	 * R::resetQueryCount();
14621
	 * echo R::getQueryCount() . ' queries processed.';
14622
	 * </code>
14623
	 *
14624
	 * @return void
14625
	 */
14626
	public static function resetQueryCount()
14627
	{
14628
		self::$adapter->getDatabase()->resetCounter();
14629
	}
14630
14631
	/**
14632
	 * Returns the number of SQL queries processed.
14633
	 * This method returns the number of database queries that have
14634
	 * been processed according to the database driver. You can use this
14635
	 * to monitor the number of queries required to render a page.
14636
	 *
14637
	 * Usage:
14638
	 *
14639
	 * <code>
14640
	 * echo R::getQueryCount() . ' queries processed.';
14641
	 * </code>
14642
	 *
14643
	 * @return integer
14644
	 */
14645
	public static function getQueryCount()
14646
	{
14647
		return self::$adapter->getDatabase()->getQueryCount();
14648
	}
14649
14650
	/**
14651
	 * Returns the current logger instance being used by the
14652
	 * database object.
14653
	 *
14654
	 * @return Logger
14655
	 */
14656
	public static function getLogger()
14657
	{
14658
		return self::$adapter->getDatabase()->getLogger();
14659
	}
14660
14661
	/**
14662
	 * Alias for setAutoResolve() method on OODBBean.
14663
	 * Enables or disables auto-resolving fetch types.
14664
	 * Auto-resolving aliased parent beans is convenient but can
14665
	 * be slower and can create infinite recursion if you
14666
	 * used aliases to break cyclic relations in your domain.
14667
	 *
14668
	 * @param boolean $automatic TRUE to enable automatic resolving aliased parents
14669
	 *
14670
	 * @return void
14671
	 */
14672
	public static function setAutoResolve( $automatic = TRUE )
14673
	{
14674
		OODBBean::setAutoResolve( (boolean) $automatic );
14675
	}
14676
14677
	/**
14678
	 * Toggles 'partial bean mode'. If this mode has been
14679
	 * selected the repository will only update the fields of a bean that
14680
	 * have been changed rather than the entire bean.
14681
	 * Pass the value TRUE to select 'partial mode' for all beans.
14682
	 * Pass the value FALSE to disable 'partial mode'.
14683
	 * Pass an array of bean types if you wish to use partial mode only
14684
	 * for some types.
14685
	 * This method will return the previous value.
14686
	 *
14687
	 * @param boolean|array $yesNoBeans List of type names or 'all'
14688
	 *
14689
	 * @return mixed
14690
	 */
14691
	public static function usePartialBeans( $yesNoBeans )
14692
	{
14693
		return self::$redbean->getCurrentRepository()->usePartialBeans( $yesNoBeans );
14694
	}
14695
14696
	/**
14697
	 * Exposes the result of the specified SQL query as a CSV file.
14698
	 * Usage:
14699
	 *
14700
	 * R::csv( 'SELECT
14701
	 *                 `name`,
14702
	 *                  population
14703
	 *          FROM city
14704
	 *          WHERE region = :region ',
14705
	 *          array( ':region' => 'Denmark' ),
14706
	 *          array( 'city', 'population' ),
14707
	 *          '/tmp/cities.csv'
14708
	 * );
14709
	 *
14710
	 * The command above will select all cities in Denmark
14711
	 * and create a CSV with columns 'city' and 'population' and
14712
	 * populate the cells under these column headers with the
14713
	 * names of the cities and the population numbers respectively.
14714
	 *
14715
	 * @param string  $sql      SQL query to expose result of
14716
	 * @param array   $bindings parameter bindings
14717
	 * @param array   $columns  column headers for CSV file
14718
	 * @param string  $path     path to save CSV file to
14719
	 * @param boolean $output   TRUE to output CSV directly using readfile
14720
	 * @param array   $options  delimiter, quote and escape character respectively
14721
	 *
14722
	 * @return void
14723
	 */
14724
	public static function csv( $sql = '', $bindings = array(), $columns = NULL, $path = '/tmp/redexport_%s.csv', $output = TRUE )
14725
	{
14726
		$quickExport = new QuickExport( self::$toolbox );
14727
		$quickExport->csv( $sql, $bindings, $columns, $path, $output );
14728
	}
14729
14730
	/**
14731
	 * MatchUp is a powerful productivity boosting method that can replace simple control
14732
	 * scripts with a single RedBeanPHP command. Typically, matchUp() is used to
14733
	 * replace login scripts, token generation scripts and password reset scripts.
14734
	 * The MatchUp method takes a bean type, an SQL query snippet (starting at the WHERE clause),
14735
	 * SQL bindings, a pair of task arrays and a bean reference.
14736
	 *
14737
	 * If the first 3 parameters match a bean, the first task list will be considered,
14738
	 * otherwise the second one will be considered. On consideration, each task list,
14739
	 * an array of keys and values will be executed. Every key in the task list should
14740
	 * correspond to a bean property while every value can either be an expression to
14741
	 * be evaluated or a closure (PHP 5.3+). After applying the task list to the bean
14742
	 * it will be stored. If no bean has been found, a new bean will be dispensed.
14743
	 *
14744
	 * This method will return TRUE if the bean was found and FALSE if not AND
14745
	 * there was a NOT-FOUND task list. If no bean was found AND there was also
14746
	 * no second task list, NULL will be returned.
14747
	 *
14748
	 * To obtain the bean, pass a variable as the sixth parameter.
14749
	 * The function will put the matching bean in the specified variable.
14750
	 *
14751
	 * @param string   $type         type of bean you're looking for
14752
	 * @param string   $sql          SQL snippet (starting at the WHERE clause, omit WHERE-keyword)
14753
	 * @param array    $bindings     array of parameter bindings for SQL snippet
14754
	 * @param array    $onFoundDo    task list to be considered on finding the bean
14755
	 * @param array    $onNotFoundDo task list to be considered on NOT finding the bean
14756
	 * @param OODBBean &$bean        reference to obtain the found bean
14757
	 *
14758
	 * @return mixed
14759
	 */
14760
	public static function matchUp( $type, $sql, $bindings = array(), $onFoundDo = NULL, $onNotFoundDo = NULL, &$bean = NULL 	) {
14761
		$matchUp = new MatchUp( self::$toolbox );
14762
		return $matchUp->matchUp( $type, $sql, $bindings, $onFoundDo, $onNotFoundDo, $bean );
14763
	}
14764
14765
	/**
14766
	 * @deprecated
14767
	 *
14768
	 * Returns an instance of the Look Helper class.
14769
	 * The instance will be configured with the current toolbox.
14770
	 *
14771
	 * In previous versions of RedBeanPHP you had to use:
14772
	 * R::getLook()->look() instead of R::look(). However to improve useability of the
14773
	 * library the look() function can now directly be invoked from the facade.
14774
	 *
14775
	 * For more details regarding the Look functionality, please consult R::look().
14776
	 * @see Facade::look
14777
	 * @see Look::look
14778
	 *
14779
	 * @return Look
14780
	 */
14781
	public static function getLook()
14782
	{
14783
		return new Look( self::$toolbox );
14784
	}
14785
14786
	/**
14787
	 * Takes an full SQL query with optional bindings, a series of keys, a template
14788
	 * and optionally a filter function and glue and assembles a view from all this.
14789
	 * This is the fastest way from SQL to view. Typically this function is used to
14790
	 * generate pulldown (select tag) menus with options queried from the database.
14791
	 *
14792
	 * Usage:
14793
	 *
14794
	 * <code>
14795
	 * $htmlPulldown = R::look(
14796
	 *   'SELECT * FROM color WHERE value != ? ORDER BY value ASC',
14797
	 *   [ 'g' ],
14798
	 *   [ 'value', 'name' ],
14799
	 *   '<option value="%s">%s</option>',
14800
	 *   'strtoupper',
14801
	 *   "\n"
14802
	 * );
14803
	 *</code>
14804
	 *
14805
	 * The example above creates an HTML fragment like this:
14806
	 *
14807
	 * <option value="B">BLUE</option>
14808
	 * <option value="R">RED</option>
14809
	 *
14810
	 * to pick a color from a palette. The HTML fragment gets constructed by
14811
	 * an SQL query that selects all colors that do not have value 'g' - this
14812
	 * excludes green. Next, the bean properties 'value' and 'name' are mapped to the
14813
	 * HTML template string, note that the order here is important. The mapping and
14814
	 * the HTML template string follow vsprintf-rules. All property values are then
14815
	 * passed through the specified filter function 'strtoupper' which in this case
14816
	 * is a native PHP function to convert strings to uppercase characters only.
14817
	 * Finally the resulting HTML fragment strings are glued together using a
14818
	 * newline character specified in the last parameter for readability.
14819
	 *
14820
	 * In previous versions of RedBeanPHP you had to use:
14821
	 * R::getLook()->look() instead of R::look(). However to improve useability of the
14822
	 * library the look() function can now directly be invoked from the facade.
14823
	 *
14824
	 * @param string   $sql      query to execute
14825
	 * @param array    $bindings parameters to bind to slots mentioned in query or an empty array
14826
	 * @param array    $keys     names in result collection to map to template
14827
	 * @param string   $template HTML template to fill with values associated with keys, use printf notation (i.e. %s)
14828
	 * @param callable $filter   function to pass values through (for translation for instance)
14829
	 * @param string   $glue     optional glue to use when joining resulting strings
14830
	 *
14831
	 * @return string
14832
	 */
14833
	public static function look( $sql, $bindings = array(), $keys = array( 'selected', 'id', 'name' ), $template = '<option %s value="%s">%s</option>', $filter = 'trim', $glue = '' )
14834
	{
14835
		return self::getLook()->look( $sql, $bindings, $keys, $template, $filter, $glue );
14836
	}
14837
14838
	/**
14839
	 * Calculates a diff between two beans (or arrays of beans).
14840
	 * The result of this method is an array describing the differences of the second bean compared to
14841
	 * the first, where the first bean is taken as reference. The array is keyed by type/property, id and property name, where
14842
	 * type/property is either the type (in case of the root bean) or the property of the parent bean where the type resides.
14843
	 * The diffs are mainly intended for logging, you cannot apply these diffs as patches to other beans.
14844
	 * However this functionality might be added in the future.
14845
	 *
14846
	 * The keys of the array can be formatted using the $format parameter.
14847
	 * A key will be composed of a path (1st), id (2nd) and property (3rd).
14848
	 * Using printf-style notation you can determine the exact format of the key.
14849
	 * The default format will look like:
14850
	 *
14851
	 * 'book.1.title' => array( <OLDVALUE>, <NEWVALUE> )
14852
	 *
14853
	 * If you only want a simple diff of one bean and you don't care about ids,
14854
	 * you might pass a format like: '%1$s.%3$s' which gives:
14855
	 *
14856
	 * 'book.1.title' => array( <OLDVALUE>, <NEWVALUE> )
14857
	 *
14858
	 * The filter parameter can be used to set filters, it should be an array
14859
	 * of property names that have to be skipped. By default this array is filled with
14860
	 * two strings: 'created' and 'modified'.
14861
	 *
14862
	 * @param OODBBean|array $bean    reference beans
14863
	 * @param OODBBean|array $other   beans to compare
14864
	 * @param array          $filters names of properties of all beans to skip
14865
	 * @param string         $format  the format of the key, defaults to '%s.%s.%s'
14866
	 * @param string         $type    type/property of bean to use for key generation
14867
	 *
14868
	 * @return array
14869
	 */
14870
	public static function diff( $bean, $other, $filters = array( 'created', 'modified' ), $pattern = '%s.%s.%s' )
14871
	{
14872
		$diff = new Diff( self::$toolbox );
14873
		return $diff->diff( $bean, $other, $filters, $pattern );
14874
	}
14875
14876
	/**
14877
	 * The gentleman's way to register a RedBeanPHP ToolBox instance
14878
	 * with the facade. Stores the toolbox in the static toolbox
14879
	 * registry of the facade class. This allows for a neat and
14880
	 * explicit way to register a toolbox.
14881
	 *
14882
	 * @param string  $key     key to store toolbox instance under
14883
	 * @param ToolBox $toolbox toolbox to register
14884
	 *
14885
	 * @return void
14886
	 */
14887
	public static function addToolBoxWithKey( $key, ToolBox $toolbox )
14888
	{
14889
		self::$toolboxes[$key] = $toolbox;
14890
	}
14891
14892
	/**
14893
	 * The gentleman's way to remove a RedBeanPHP ToolBox instance
14894
	 * from the facade. Removes the toolbox identified by
14895
	 * the specified key in the static toolbox
14896
	 * registry of the facade class. This allows for a neat and
14897
	 * explicit way to remove a toolbox.
14898
	 * Returns TRUE if the specified toolbox was found and removed.
14899
	 * Returns FALSE otherwise.
14900
	 *
14901
	 * @param string  $key     identifier of the toolbox to remove
14902
	 *
14903
	 * @return boolean
14904
	 */
14905
	public static function removeToolBoxByKey( $key )
14906
	{
14907
		if ( !array_key_exists( $key, self::$toolboxes ) ) {
14908
			return FALSE;
14909
		}
14910
		return TRUE;
14911
	}
14912
14913
	/**
14914
	 * Returns the toolbox associated with the specified key.
14915
	 *
14916
	 * @param string  $key     key to store toolbox instance under
14917
	 * @param ToolBox $toolbox toolbox to register
14918
	 *
14919
	 * @return ToolBox|NULL
14920
	 */
14921
	public static function getToolBoxByKey( $key )
14922
	{
14923
		if ( !array_key_exists( $key, self::$toolboxes ) ) {
14924
			return NULL;
14925
		}
14926
		return self::$toolboxes[$key];
14927
	}
14928
14929
	/**
14930
	 * Toggles JSON column features.
14931
	 * Invoking this method with boolean TRUE causes 2 JSON features to be enabled.
14932
	 * Beans will automatically JSONify any array that's not in a list property and
14933
	 * the Query Writer (if capable) will attempt to create a JSON column for strings that
14934
	 * appear to contain JSON.
14935
	 *
14936
	 * Feature #1:
14937
	 * AQueryWriter::useJSONColumns
14938
	 *
14939
	 * Toggles support for automatic generation of JSON columns.
14940
	 * Using JSON columns means that strings containing JSON will
14941
	 * cause the column to be created (not modified) as a JSON column.
14942
	 * However it might also trigger exceptions if this means the DB attempts to
14943
	 * convert a non-json column to a JSON column.
14944
	 *
14945
	 * Feature #2:
14946
	 * OODBBean::convertArraysToJSON
14947
	 *
14948
	 * Toggles array to JSON conversion. If set to TRUE any array
14949
	 * set to a bean property that's not a list will be turned into
14950
	 * a JSON string. Used together with AQueryWriter::useJSONColumns this
14951
	 * extends the data type support for JSON columns.
14952
	 *
14953
	 * So invoking this method is the same as:
14954
	 *
14955
	 * AQueryWriter::useJSONColumns( $flag );
14956
	 * OODBBean::convertArraysToJSON( $flag );
14957
	 *
14958
	 * Unlike the methods above, that return the previous state, this
14959
	 * method does not return anything (void).
14960
	 *
14961
	 * @param boolean $flag feature flag (either TRUE or FALSE)
14962
	 *
14963
	 * @return void
14964
	 */
14965
	public static function useJSONFeatures( $flag )
14966
	{
14967
		AQueryWriter::useJSONColumns( $flag );
14968
		OODBBean::convertArraysToJSON( $flag );
14969
	}
14970
14971
	/**
14972
	 * @experimental
14973
	 *
14974
	 * Given a bean and an optional SQL snippet,
14975
	 * this method will return all child beans in a hierarchically structured
14976
	 * bean table.
14977
	 *
14978
	 * @note that not all database support this functionality. You'll need
14979
	 * at least MariaDB 10.2.2 or Postgres. This method does not include
14980
	 * a warning mechanism in case your database does not support this
14981
	 * functionality.
14982
	 *
14983
	 * @note that this functionality is considered 'experimental'.
14984
	 * It may still contain bugs.
14985
	 *
14986
	 * @param OODBBean $bean     bean to find children of
14987
	 * @param string   $sql      optional SQL snippet
14988
	 * @param array    $bindings SQL snippet parameter bindings
14989
	 */
14990
	public static function children( OODBBean $bean, $sql = NULL, $bindings = array() )
14991
	{
14992
		return self::$tree->children( $bean, $sql, $bindings );
14993
	}
14994
14995
	/**
14996
	 * @experimental
14997
	 *
14998
	 * Given a bean and an optional SQL snippet,
14999
	 * this method will return all parent beans in a hierarchically structured
15000
	 * bean table.
15001
	 *
15002
	 * @note that not all database support this functionality. You'll need
15003
	 * at least MariaDB 10.2.2 or Postgres. This method does not include
15004
	 * a warning mechanism in case your database does not support this
15005
	 * functionality.
15006
	 *
15007
	 * @note that this functionality is considered 'experimental'.
15008
	 * It may still contain bugs.
15009
	 *
15010
	 * @param OODBBean $bean     bean to find parents of
15011
	 * @param string   $sql      optional SQL snippet
15012
	 * @param array    $bindings SQL snippet parameter bindings
15013
	 */
15014
	public static function parents( OODBBean $bean, $sql = NULL, $bindings = array() )
15015
	{
15016
		return self::$tree->parents( $bean, $sql, $bindings );
15017
	}
15018
15019
	/**
15020
	 * Toggles support for nuke().
15021
	 * Can be used to turn off the nuke() feature for security reasons.
15022
	 * Returns the old flag value.
15023
	 *
15024
	 * @param boolean $flag TRUE or FALSE
15025
	 *
15026
	 * @return boolean
15027
	 */
15028
	public static function noNuke( $yesNo ) {
15029
		return AQueryWriter::forbidNuke( $yesNo );
15030
	}
15031
15032
	/**
15033
	 * Selects the feature set you want as specified by
15034
	 * the label.
15035
	 *
15036
	 * Usage:
15037
	 * R::useFeatureSet( 'novice/latest' );
15038
	 *
15039
	 * @param string $label label
15040
	 *
15041
	 * @return void
15042
	 */
15043
	public static function useFeatureSet( $label ) {
15044
		return Feature::feature($label);
15045
	}
15046
15047
	/**
15048
	 * Dynamically extends the facade with a plugin.
15049
	 * Using this method you can register your plugin with the facade and then
15050
	 * use the plugin by invoking the name specified plugin name as a method on
15051
	 * the facade.
15052
	 *
15053
	 * Usage:
15054
	 *
15055
	 * <code>
15056
	 * R::ext( 'makeTea', function() { ... }  );
15057
	 * </code>
15058
	 *
15059
	 * Now you can use your makeTea plugin like this:
15060
	 *
15061
	 * <code>
15062
	 * R::makeTea();
15063
	 * </code>
15064
	 *
15065
	 * @param string   $pluginName name of the method to call the plugin
15066
	 * @param callable $callable   a PHP callable
15067
	 *
15068
	 * @return void
15069
	 */
15070
	public static function ext( $pluginName, $callable )
15071
	{
15072
		if ( !ctype_alnum( $pluginName ) ) {
15073
			throw new RedException( 'Plugin name may only contain alphanumeric characters.' );
15074
		}
15075
		self::$plugins[$pluginName] = $callable;
15076
	}
15077
15078
	/**
15079
	 * Call static for use with dynamic plugins. This magic method will
15080
	 * intercept static calls and route them to the specified plugin.
15081
	 *
15082
	 * @param string $pluginName name of the plugin
15083
	 * @param array  $params     list of arguments to pass to plugin method
15084
	 *
15085
	 * @return mixed
15086
	 */
15087
	public static function __callStatic( $pluginName, $params )
15088
	{
15089
		if ( !ctype_alnum( $pluginName) ) {
15090
			throw new RedException( 'Plugin name may only contain alphanumeric characters.' );
15091
		}
15092
		if ( !isset( self::$plugins[$pluginName] ) ) {
15093
			throw new RedException( 'Plugin \''.$pluginName.'\' does not exist, add this plugin using: R::ext(\''.$pluginName.'\')' );
15094
		}
15095
		return call_user_func_array( self::$plugins[$pluginName], $params );
15096
	}
15097
}
15098
15099
}
15100
15101
namespace RedBeanPHP {
15102
15103
use RedBeanPHP\ToolBox as ToolBox;
15104
use RedBeanPHP\AssociationManager as AssociationManager;
15105
use RedBeanPHP\OODB as OODB;
15106
use RedBeanPHP\OODBBean as OODBBean;
15107
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
15108
15109
/**
15110
 * Duplication Manager
15111
 * The Duplication Manager creates deep copies from beans, this means
15112
 * it can duplicate an entire bean hierarchy. You can use this feature to
15113
 * implement versioning for instance. Because duplication and exporting are
15114
 * closely related this class is also used to export beans recursively
15115
 * (i.e. we make a duplicate and then convert to array). This class allows
15116
 * you to tune the duplication process by specifying filters determining
15117
 * which relations to take into account and by specifying tables
15118
 * (in which case no reflective queries have to be issued thus improving
15119
 * performance). This class also hosts the Camelfy function used to
15120
 * reformat the keys of an array, this method is publicly available and
15121
 * used internally by exportAll().
15122
 *
15123
 * @file    RedBeanPHP/DuplicationManager.php
15124
 * @author  Gabor de Mooij and the RedBeanPHP Community
15125
 * @license BSD/GPLv2
15126
 *
15127
 * @copyright
15128
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
15129
 * This source file is subject to the BSD/GPLv2 License that is bundled
15130
 * with this source code in the file license.txt.
15131
 */
15132
class DuplicationManager
15133
{
15134
	/**
15135
	 * @var ToolBox
15136
	 */
15137
	protected $toolbox;
15138
15139
	/**
15140
	 * @var AssociationManager
15141
	 */
15142
	protected $associationManager;
15143
15144
	/**
15145
	 * @var OODB
15146
	 */
15147
	protected $redbean;
15148
15149
	/**
15150
	 * @var array
15151
	 */
15152
	protected $tables = array();
15153
15154
	/**
15155
	 * @var array
15156
	 */
15157
	protected $columns = array();
15158
15159
	/**
15160
	 * @var array
15161
	 */
15162
	protected $filters = array();
15163
15164
	/**
15165
	 * @var array
15166
	 */
15167
	protected $cacheTables = FALSE;
15168
15169
	/**
15170
	 * Copies the shared beans in a bean, i.e. all the sharedBean-lists.
15171
	 *
15172
	 * @param OODBBean $copy   target bean to copy lists to
15173
	 * @param string   $shared name of the shared list
15174
	 * @param array    $beans  array with shared beans to copy
15175
	 *
15176
	 * @return void
15177
	 */
15178
	private function copySharedBeans( OODBBean $copy, $shared, $beans )
15179
	{
15180
		$copy->$shared = array();
15181
15182
		foreach ( $beans as $subBean ) {
15183
			array_push( $copy->$shared, $subBean );
15184
		}
15185
	}
15186
15187
	/**
15188
	 * Copies the own beans in a bean, i.e. all the ownBean-lists.
15189
	 * Each bean in the own-list belongs exclusively to its owner so
15190
	 * we need to invoke the duplicate method again to duplicate each bean here.
15191
	 *
15192
	 * @param OODBBean $copy        target bean to copy lists to
15193
	 * @param string   $owned       name of the own list
15194
	 * @param array    $beans       array with shared beans to copy
15195
	 * @param array    $trail       array with former beans to detect recursion
15196
	 * @param boolean  $preserveIDs TRUE means preserve IDs, for export only
15197
	 *
15198
	 * @return void
15199
	 */
15200
	private function copyOwnBeans( OODBBean $copy, $owned, $beans, $trail, $preserveIDs )
15201
	{
15202
		$copy->$owned = array();
15203
		foreach ( $beans as $subBean ) {
15204
			array_push( $copy->$owned, $this->duplicate( $subBean, $trail, $preserveIDs ) );
15205
		}
15206
	}
15207
15208
	/**
15209
	 * Creates a copy of bean $bean and copies all primitive properties (not lists)
15210
	 * and the parents beans to the newly created bean. Also sets the ID of the bean
15211
	 * to 0.
15212
	 *
15213
	 * @param OODBBean $bean bean to copy
15214
	 *
15215
	 * @return OODBBean
15216
	 */
15217
	private function createCopy( OODBBean $bean )
15218
	{
15219
		$type = $bean->getMeta( 'type' );
15220
15221
		$copy = $this->redbean->dispense( $type );
15222
		$copy->setMeta( 'sys.dup-from-id', $bean->id );
15223
		$copy->setMeta( 'sys.old-id', $bean->id );
15224
		$copy->importFrom( $bean );
15225
		$copy->id = 0;
15226
15227
		return $copy;
15228
	}
15229
15230
	/**
15231
	 * Generates a key from the bean type and its ID and determines if the bean
15232
	 * occurs in the trail, if not the bean will be added to the trail.
15233
	 * Returns TRUE if the bean occurs in the trail and FALSE otherwise.
15234
	 *
15235
	 * @param array    $trail list of former beans
15236
	 * @param OODBBean $bean  currently selected bean
15237
	 *
15238
	 * @return boolean
15239
	 */
15240
	private function inTrailOrAdd( &$trail, OODBBean $bean )
15241
	{
15242
		$type = $bean->getMeta( 'type' );
15243
		$key  = $type . $bean->getID();
15244
15245
		if ( isset( $trail[$key] ) ) {
15246
			return TRUE;
15247
		}
15248
15249
		$trail[$key] = $bean;
15250
15251
		return FALSE;
15252
	}
15253
15254
	/**
15255
	 * Given the type name of a bean this method returns the canonical names
15256
	 * of the own-list and the shared-list properties respectively.
15257
	 * Returns a list with two elements: name of the own-list, and name
15258
	 * of the shared list.
15259
	 *
15260
	 * @param string $typeName bean type name
15261
	 *
15262
	 * @return array
15263
	 */
15264
	private function getListNames( $typeName )
15265
	{
15266
		$owned  = 'own' . ucfirst( $typeName );
15267
		$shared = 'shared' . ucfirst( $typeName );
15268
15269
		return array( $owned, $shared );
15270
	}
15271
15272
	/**
15273
	 * Determines whether the bean has an own list based on
15274
	 * schema inspection from realtime schema or cache.
15275
	 *
15276
	 * @param string $type   bean type to get list for
15277
	 * @param string $target type of list you want to detect
15278
	 *
15279
	 * @return boolean
15280
	 */
15281
	protected function hasOwnList( $type, $target )
15282
	{
15283
		return isset( $this->columns[$target][$type . '_id'] );
15284
	}
15285
15286
	/**
15287
	 * Determines whether the bea has a shared list based on
15288
	 * schema inspection from realtime schema or cache.
15289
	 *
15290
	 * @param string $type   bean type to get list for
15291
	 * @param string $target type of list you are looking for
15292
	 *
15293
	 * @return boolean
15294
	 */
15295
	protected function hasSharedList( $type, $target )
15296
	{
15297
		return in_array( AQueryWriter::getAssocTableFormat( array( $type, $target ) ), $this->tables );
15298
	}
15299
15300
	/**
15301
	 * @see DuplicationManager::dup
15302
	 *
15303
	 * @param OODBBean $bean        bean to be copied
15304
	 * @param array    $trail       trail to prevent infinite loops
15305
	 * @param boolean  $preserveIDs preserve IDs
15306
	 *
15307
	 * @return OODBBean
15308
	 */
15309
	protected function duplicate( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
15310
	{
15311
		if ( $this->inTrailOrAdd( $trail, $bean ) ) return $bean;
15312
15313
		$type = $bean->getMeta( 'type' );
15314
15315
		$copy = $this->createCopy( $bean );
15316
		foreach ( $this->tables as $table ) {
15317
15318
			if ( !empty( $this->filters ) ) {
15319
				if ( !in_array( $table, $this->filters ) ) continue;
15320
			}
15321
15322
			list( $owned, $shared ) = $this->getListNames( $table );
15323
15324
			if ( $this->hasSharedList( $type, $table ) ) {
15325
				if ( $beans = $bean->$shared ) {
15326
					$this->copySharedBeans( $copy, $shared, $beans );
15327
				}
15328
			} elseif ( $this->hasOwnList( $type, $table ) ) {
15329
				if ( $beans = $bean->$owned ) {
15330
					$this->copyOwnBeans( $copy, $owned, $beans, $trail, $preserveIDs );
15331
				}
15332
15333
				$copy->setMeta( 'sys.shadow.' . $owned, NULL );
15334
			}
15335
15336
			$copy->setMeta( 'sys.shadow.' . $shared, NULL );
15337
		}
15338
15339
		$copy->id = ( $preserveIDs ) ? $bean->id : $copy->id;
15340
15341
		return $copy;
15342
	}
15343
15344
	/**
15345
	 * Constructor,
15346
	 * creates a new instance of DupManager.
15347
	 *
15348
	 * @param ToolBox $toolbox
15349
	 */
15350
	public function __construct( ToolBox $toolbox )
15351
	{
15352
		$this->toolbox            = $toolbox;
15353
		$this->redbean            = $toolbox->getRedBean();
15354
		$this->associationManager = $this->redbean->getAssociationManager();
15355
	}
15356
15357
	/**
15358
	 * Recursively turns the keys of an array into
15359
	 * camelCase.
15360
	 *
15361
	 * @param array   $array       array to camelize
15362
	 * @param boolean $dolphinMode whether you want the exception for IDs.
15363
	 *
15364
	 * @return array
15365
	 */
15366
	public function camelfy( $array, $dolphinMode = FALSE ) {
15367
		$newArray = array();
15368
		foreach( $array as $key => $element ) {
15369
			$newKey = preg_replace_callback( '/_(\w)/', function( &$matches ){
15370
				return strtoupper( $matches[1] );
15371
			}, $key);
15372
15373
			if ( $dolphinMode ) {
15374
				$newKey = preg_replace( '/(\w)Id$/', '$1ID', $newKey );
15375
			}
15376
15377
			$newArray[$newKey] = ( is_array($element) ) ? $this->camelfy( $element, $dolphinMode ) : $element;
15378
		}
15379
		return $newArray;
15380
	}
15381
15382
	/**
15383
	 * For better performance you can pass the tables in an array to this method.
15384
	 * If the tables are available the duplication manager will not query them so
15385
	 * this might be beneficial for performance.
15386
	 *
15387
	 * This method allows two array formats:
15388
	 *
15389
	 * <code>
15390
	 * array( TABLE1, TABLE2 ... )
15391
	 * </code>
15392
	 *
15393
	 * or
15394
	 *
15395
	 * <code>
15396
	 * array( TABLE1 => array( COLUMN1, COLUMN2 ... ) ... )
15397
	 * </code>
15398
	 *
15399
	 * @param array $tables a table cache array
15400
	 *
15401
	 * @return void
15402
	 */
15403
	public function setTables( $tables )
15404
	{
15405
		foreach ( $tables as $key => $value ) {
15406
			if ( is_numeric( $key ) ) {
15407
				$this->tables[] = $value;
15408
			} else {
15409
				$this->tables[]      = $key;
15410
				$this->columns[$key] = $value;
15411
			}
15412
		}
15413
15414
		$this->cacheTables = TRUE;
15415
	}
15416
15417
	/**
15418
	 * Returns a schema array for cache.
15419
	 * You can use the return value of this method as a cache,
15420
	 * store it in RAM or on disk and pass it to setTables later.
15421
	 *
15422
	 * @return array
15423
	 */
15424
	public function getSchema()
15425
	{
15426
		return $this->columns;
15427
	}
15428
15429
	/**
15430
	 * Indicates whether you want the duplication manager to cache the database schema.
15431
	 * If this flag is set to TRUE the duplication manager will query the database schema
15432
	 * only once. Otherwise the duplicationmanager will, by default, query the schema
15433
	 * every time a duplication action is performed (dup()).
15434
	 *
15435
	 * @param boolean $yesNo TRUE to use caching, FALSE otherwise
15436
	 */
15437
	public function setCacheTables( $yesNo )
15438
	{
15439
		$this->cacheTables = $yesNo;
15440
	}
15441
15442
	/**
15443
	 * A filter array is an array with table names.
15444
	 * By setting a table filter you can make the duplication manager only take into account
15445
	 * certain bean types. Other bean types will be ignored when exporting or making a
15446
	 * deep copy. If no filters are set all types will be taking into account, this is
15447
	 * the default behavior.
15448
	 *
15449
	 * @param array $filters list of tables to be filtered
15450
	 *
15451
	 * @return void
15452
	 */
15453
	public function setFilters( $filters )
15454
	{
15455
		if ( !is_array( $filters ) ) {
15456
			$filters = array( $filters );
15457
		}
15458
15459
		$this->filters = $filters;
15460
	}
15461
15462
	/**
15463
	 * Makes a copy of a bean. This method makes a deep copy
15464
	 * of the bean.The copy will have the following features.
15465
	 * - All beans in own-lists will be duplicated as well
15466
	 * - All references to shared beans will be copied but not the shared beans themselves
15467
	 * - All references to parent objects (_id fields) will be copied but not the parents themselves
15468
	 * In most cases this is the desired scenario for copying beans.
15469
	 * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
15470
	 * (i.e. one that already has been processed) the ID of the bean will be returned.
15471
	 * This should not happen though.
15472
	 *
15473
	 * Note:
15474
	 * This function does a reflectional database query so it may be slow.
15475
	 *
15476
	 * Note:
15477
	 * this function actually passes the arguments to a protected function called
15478
	 * duplicate() that does all the work. This method takes care of creating a clone
15479
	 * of the bean to avoid the bean getting tainted (triggering saving when storing it).
15480
	 *
15481
	 * @param OODBBean $bean        bean to be copied
15482
	 * @param array    $trail       for internal usage, pass array()
15483
	 * @param boolean  $preserveIDs for internal usage
15484
	 *
15485
	 * @return OODBBean
15486
	 */
15487
	public function dup( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
15488
	{
15489
		if ( !count( $this->tables ) ) {
15490
			$this->tables = $this->toolbox->getWriter()->getTables();
15491
		}
15492
15493
		if ( !count( $this->columns ) ) {
15494
			foreach ( $this->tables as $table ) {
15495
				$this->columns[$table] = $this->toolbox->getWriter()->getColumns( $table );
15496
			}
15497
		}
15498
15499
		$rs = $this->duplicate( ( clone $bean ), $trail, $preserveIDs );
15500
15501
		if ( !$this->cacheTables ) {
15502
			$this->tables  = array();
15503
			$this->columns = array();
15504
		}
15505
15506
		return $rs;
15507
	}
15508
15509
	/**
15510
	 * Exports a collection of beans recursively.
15511
	 * This method will export an array of beans in the first argument to a
15512
	 * set of arrays. This can be used to send JSON or XML representations
15513
	 * of bean hierarchies to the client.
15514
	 *
15515
	 * For every bean in the array this method will export:
15516
	 *
15517
	 * - contents of the bean
15518
	 * - all own bean lists (recursively)
15519
	 * - all shared beans (but not THEIR own lists)
15520
	 *
15521
	 * If the second parameter is set to TRUE the parents of the beans in the
15522
	 * array will be exported as well (but not THEIR parents).
15523
	 *
15524
	 * The third parameter can be used to provide a white-list array
15525
	 * for filtering. This is an array of strings representing type names,
15526
	 * only the type names in the filter list will be exported.
15527
	 *
15528
	 * The fourth parameter can be used to change the keys of the resulting
15529
	 * export arrays. The default mode is 'snake case' but this leaves the
15530
	 * keys as-is, because 'snake' is the default case style used by
15531
	 * RedBeanPHP in the database. You can set this to 'camel' for
15532
	 * camel cased keys or 'dolphin' (same as camelcase but id will be
15533
	 * converted to ID instead of Id).
15534
	 *
15535
	 * @param array|OODBBean $beans     beans to be exported
15536
	 * @param boolean        $parents   also export parents
15537
	 * @param array          $filters   only these types (whitelist)
15538
	 * @param string         $caseStyle case style identifier
15539
	 *
15540
	 * @return array
15541
	 */
15542
	public function exportAll( $beans, $parents = FALSE, $filters = array(), $caseStyle = 'snake')
15543
	{
15544
		$array = array();
15545
		if ( !is_array( $beans ) ) {
15546
			$beans = array( $beans );
15547
		}
15548
		foreach ( $beans as $bean ) {
15549
			$this->setFilters( $filters );
15550
			$duplicate = $this->dup( $bean, array(), TRUE );
15551
			$array[]   = $duplicate->export( FALSE, $parents, FALSE, $filters );
15552
		}
15553
		if ( $caseStyle === 'camel' ) $array = $this->camelfy( $array );
15554
		if ( $caseStyle === 'dolphin' ) $array = $this->camelfy( $array, TRUE );
15555
		return $array;
15556
	}
15557
}
15558
}
15559
15560
namespace RedBeanPHP\Util {
15561
15562
use RedBeanPHP\OODB as OODB;
15563
use RedBeanPHP\OODBBean as OODBBean;
15564
use RedBeanPHP\RedException as RedException;
15565
15566
/**
15567
 * Array Tool Helper
15568
 *
15569
 * This code was originally part of the facade, however it has
15570
 * been decided to remove unique features to service classes like
15571
 * this to make them available to developers not using the facade class.
15572
 *
15573
 * This is a helper or service class containing frequently used
15574
 * array functions for dealing with SQL queries.
15575
 * 
15576
 * @file    RedBeanPHP/Util/ArrayTool.php
15577
 * @author  Gabor de Mooij and the RedBeanPHP Community
15578
 * @license BSD/GPLv2
15579
 *
15580
 * @copyright
15581
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
15582
 * This source file is subject to the BSD/GPLv2 License that is bundled
15583
 * with this source code in the file license.txt.
15584
 */
15585
class ArrayTool
15586
{
15587
	/**
15588
	 * Generates question mark slots for an array of values.
15589
	 * Given an array and an optional template string this method
15590
	 * will produce string containing parameter slots for use in
15591
	 * an SQL query string.
15592
	 *
15593
	 * Usage:
15594
	 *
15595
	 * <code>
15596
	 * R::genSlots( array( 'a', 'b' ) );
15597
	 * </code>
15598
	 *
15599
	 * The statement in the example will produce the string:
15600
	 * '?,?'.
15601
	 *
15602
	 * Another example, using a template string:
15603
	 *
15604
	 * <code>
15605
	 * R::genSlots( array('a', 'b'), ' IN( %s ) ' );
15606
	 * </code>
15607
	 *
15608
	 * The statement in the example will produce the string:
15609
	 * ' IN( ?,? ) '.
15610
	 *
15611
	 * @param array  $array    array to generate question mark slots for
15612
	 * @param string $template template to use
15613
	 *
15614
	 * @return string
15615
	 */
15616
	public static function genSlots( $array, $template = NULL )
15617
	{
15618
		$str = count( $array ) ? implode( ',', array_fill( 0, count( $array ), '?' ) ) : '';
15619
		return ( is_null( $template ) ||  $str === '' ) ? $str : sprintf( $template, $str );
15620
	}
15621
15622
	/**
15623
	 * Flattens a multi dimensional bindings array for use with genSlots().
15624
	 *
15625
	 * Usage:
15626
	 *
15627
	 * <code>
15628
	 * R::flat( array( 'a', array( 'b' ), 'c' ) );
15629
	 * </code>
15630
	 *
15631
	 * produces an array like: [ 'a', 'b', 'c' ]
15632
	 *
15633
	 * @param array $array  array to flatten
15634
	 * @param array $result result array parameter (for recursion)
15635
	 *
15636
	 * @return array
15637
	 */
15638
	public static function flat( $array, $result = array() )
15639
	{		
15640
		foreach( $array as $value ) {
15641
			if ( is_array( $value ) ) $result = self::flat( $value, $result );
15642
			else $result[] = $value;
15643
		}
15644
		return $result;
15645
	}
15646
}
15647
}
15648
15649
namespace RedBeanPHP\Util {
15650
15651
use RedBeanPHP\OODB as OODB;
15652
use RedBeanPHP\RedException as RedException;
15653
15654
/**
15655
 * Dispense Helper
15656
 *
15657
 * A helper class containing a dispense utility.
15658
 * 
15659
 * @file    RedBeanPHP/Util/DispenseHelper.php
15660
 * @author  Gabor de Mooij and the RedBeanPHP Community
15661
 * @license BSD/GPLv2
15662
 *
15663
 * @copyright
15664
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
15665
 * This source file is subject to the BSD/GPLv2 License that is bundled
15666
 * with this source code in the file license.txt.
15667
 */
15668
class DispenseHelper
15669
{
15670
	/**
15671
	 * @var boolean
15672
	 */
15673
	private static $enforceNamingPolicy = TRUE;
15674
15675
	/**
15676
	 * Sets the enforce naming policy flag. If set to
15677
	 * TRUE the RedBeanPHP naming policy will be enforced.
15678
	 * Otherwise it will not. Use at your own risk.
15679
	 * Setting this to FALSE is not recommended.
15680
	 *
15681
	 * @param boolean $yesNo whether to enforce RB name policy
15682
	 *
15683
	 * @return void
15684
	 */
15685
	public static function setEnforceNamingPolicy( $yesNo )
15686
	{
15687
		self::$enforceNamingPolicy = (boolean) $yesNo;
15688
	}
15689
15690
	/**
15691
	 * Checks whether the bean type conforms to the RedbeanPHP
15692
	 * naming policy. This method will throw an exception if the
15693
	 * type does not conform to the RedBeanPHP database column naming
15694
	 * policy.
15695
	 *
15696
	 * The RedBeanPHP naming policy for beans states that valid
15697
	 * bean type names contain only:
15698
	 *
15699
	 * - lowercase alphanumeric characters a-z
15700
	 * - numbers 0-9
15701
	 * - at least one character
15702
	 *
15703
	 * Although there are no restrictions on length, database
15704
	 * specific implementations may apply further restrictions
15705
	 * regarding the length of a table which means these restrictions
15706
	 * also apply to bean types.
15707
	 *
15708
	 * The RedBeanPHP naming policy ensures that, without any
15709
	 * configuration, the core functionalities work across many
15710
	 * databases and operating systems, including those that are
15711
	 * case insensitive or restricted to the ASCII character set.
15712
	 *
15713
	 * Although these restrictions can be bypassed, this is not
15714
	 * recommended.
15715
	 *
15716
	 * @param string $type type of bean
15717
	 *
15718
	 * @return void
15719
	 */
15720
	public static function checkType( $type )
15721
	{
15722
		if ( !preg_match( '/^[a-z0-9]+$/', $type ) ) {
15723
			throw new RedException( 'Invalid type: ' . $type );
15724
		}
15725
	}
15726
15727
	/**
15728
	 * Dispenses a new RedBean OODB Bean for use with
15729
	 * the rest of the methods. RedBeanPHP thinks in beans, the bean is the
15730
	 * primary way to interact with RedBeanPHP and the database managed by
15731
	 * RedBeanPHP. To load, store and delete data from the database using RedBeanPHP
15732
	 * you exchange these RedBeanPHP OODB Beans. The only exception to this rule
15733
	 * are the raw query methods like R::getCell() or R::exec() and so on.
15734
	 * The dispense method is the 'preferred way' to create a new bean.
15735
	 *
15736
	 * Usage:
15737
	 *
15738
	 * <code>
15739
	 * $book = R::dispense( 'book' );
15740
	 * $book->title = 'My Book';
15741
	 * R::store( $book );
15742
	 * </code>
15743
	 *
15744
	 * This method can also be used to create an entire bean graph at once.
15745
	 * Given an array with keys specifying the property names of the beans
15746
	 * and a special _type key to indicate the type of bean, one can
15747
	 * make the Dispense Helper generate an entire hierarchy of beans, including
15748
	 * lists. To make dispense() generate a list, simply add a key like:
15749
	 * ownXList or sharedXList where X is the type of beans it contains and
15750
	 * a set its value to an array filled with arrays representing the beans.
15751
	 * Note that, although the type may have been hinted at in the list name,
15752
	 * you still have to specify a _type key for every bean array in the list.
15753
	 * Note that, if you specify an array to generate a bean graph, the number
15754
	 * parameter will be ignored.
15755
	 *
15756
	 * Usage:
15757
	 *
15758
	 * <code>
15759
	 *  $book = R::dispense( [
15760
     *   '_type' => 'book',
15761
     *   'title'  => 'Gifted Programmers',
15762
     *   'author' => [ '_type' => 'author', 'name' => 'Xavier' ],
15763
     *   'ownPageList' => [ ['_type'=>'page', 'text' => '...'] ]
15764
     * ] );
15765
	 * </code>
15766
	 *
15767
	 * @param string|array $typeOrBeanArray   type or bean array to import
15768
	 * @param integer      $num               number of beans to dispense
15769
	 * @param boolean      $alwaysReturnArray if TRUE always returns the result as an array
15770
	 *
15771
	 * @return array|OODBBean
15772
	 */
15773
	public static function dispense( OODB $oodb, $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE ) {
15774
15775
		if ( is_array($typeOrBeanArray) ) {
15776
15777
			if ( !isset( $typeOrBeanArray['_type'] ) ) {
15778
				$list = array();
15779
				foreach( $typeOrBeanArray as $beanArray ) {
15780
					if ( 
15781
						!( is_array( $beanArray ) 
15782
						&& isset( $beanArray['_type'] ) ) ) {
15783
						throw new RedException( 'Invalid Array Bean' );
15784
					}
15785
				}
15786
				foreach( $typeOrBeanArray as $beanArray ) $list[] = self::dispense( $oodb, $beanArray );
15787
				return $list;
15788
			}
15789
15790
			$import = $typeOrBeanArray;
15791
			$type = $import['_type'];
15792
			unset( $import['_type'] );
15793
		} else {
15794
			$type = $typeOrBeanArray;
15795
		}
15796
15797
		if (self::$enforceNamingPolicy) self::checkType( $type );
15798
15799
		$beanOrBeans = $oodb->dispense( $type, $num, $alwaysReturnArray );
15800
15801
		if ( isset( $import ) ) {
15802
			$beanOrBeans->import( $import );
15803
		}
15804
15805
		return $beanOrBeans;
15806
	}
15807
	
15808
	
15809
	/**
15810
	 * Takes a comma separated list of bean types
15811
	 * and dispenses these beans. For each type in the list
15812
	 * you can specify the number of beans to be dispensed.
15813
	 *
15814
	 * Usage:
15815
	 *
15816
	 * <code>
15817
	 * list( $book, $page, $text ) = R::dispenseAll( 'book,page,text' );
15818
	 * </code>
15819
	 *
15820
	 * This will dispense a book, a page and a text. This way you can
15821
	 * quickly dispense beans of various types in just one line of code.
15822
	 *
15823
	 * Usage:
15824
	 *
15825
	 * <code>
15826
	 * list($book, $pages) = R::dispenseAll('book,page*100');
15827
	 * </code>
15828
	 *
15829
	 * This returns an array with a book bean and then another array
15830
	 * containing 100 page beans.
15831
	 *
15832
	 * @param OODB    $oodb       OODB
15833
	 * @param string  $order      a description of the desired dispense order using the syntax above
15834
	 * @param boolean $onlyArrays return only arrays even if amount < 2
15835
	 *
15836
	 * @return array
15837
	 */
15838
	public static function dispenseAll( OODB $oodb, $order, $onlyArrays = FALSE )
15839
	{
15840
		$list = array();
15841
15842
		foreach( explode( ',', $order ) as $order ) {
15843
			if ( strpos( $order, '*' ) !== FALSE ) {
15844
				list( $type, $amount ) = explode( '*', $order );
15845
			} else {
15846
				$type   = $order;
15847
				$amount = 1;
15848
			}
15849
15850
			$list[] = self::dispense( $oodb, $type, $amount, $onlyArrays );
15851
		}
15852
15853
		return $list;
15854
	}
15855
}
15856
}
15857
15858
namespace RedBeanPHP\Util {
15859
15860
use RedBeanPHP\OODB as OODB;
15861
use RedBeanPHP\OODBBean as OODBBean;
15862
15863
/**
15864
 * Dump helper
15865
 *
15866
 * This code was originally part of the facade, however it has
15867
 * been decided to remove unique features to service classes like
15868
 * this to make them available to developers not using the facade class.
15869
 *
15870
 * Dumps the contents of a bean in an array for
15871
 * debugging purposes.
15872
 * 
15873
 * @file    RedBeanPHP/Util/Dump.php
15874
 * @author  Gabor de Mooij and the RedBeanPHP Community
15875
 * @license BSD/GPLv2
15876
 *
15877
 * @copyright
15878
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
15879
 * This source file is subject to the BSD/GPLv2 License that is bundled
15880
 * with this source code in the file license.txt.
15881
 */
15882
class Dump
15883
{
15884
	/**
15885
	 * Dumps bean data to array.
15886
	 * Given a one or more beans this method will
15887
	 * return an array containing first part of the string
15888
	 * representation of each item in the array.
15889
	 *
15890
	 * Usage:
15891
	 *
15892
	 * <code>
15893
	 * echo R::dump( $bean );
15894
	 * </code>
15895
	 *
15896
	 * The example shows how to echo the result of a simple
15897
	 * dump. This will print the string representation of the
15898
	 * specified bean to the screen, limiting the output per bean
15899
	 * to 35 characters to improve readability. Nested beans will
15900
	 * also be dumped.
15901
	 *
15902
	 * @param OODBBean|array $data either a bean or an array of beans
15903
	 *
15904
	 * @return array
15905
	 */
15906
	public static function dump( $data )
15907
	{
15908
		$array = array();
15909
		if ( $data instanceof OODBBean ) {
15910
			$str = strval( $data );
15911
			if (strlen($str) > 35) {
15912
				$beanStr = substr( $str, 0, 35 ).'... ';
15913
			} else {
15914
				$beanStr = $str;
15915
			}
15916
			return $beanStr;
15917
		}
15918
		if ( is_array( $data ) ) {
15919
			foreach( $data as $key => $item ) {
15920
				$array[$key] = self::dump( $item );
15921
			}
15922
		}
15923
		return $array;
15924
	}
15925
}
15926
}
15927
15928
namespace RedBeanPHP\Util {
15929
15930
use RedBeanPHP\OODB as OODB;
15931
15932
/**
15933
 * Multi Bean Loader Helper
15934
 *
15935
 * This code was originally part of the facade, however it has
15936
 * been decided to remove unique features to service classes like
15937
 * this to make them available to developers not using the facade class.
15938
 *
15939
 * This helper class offers limited support for one-to-one
15940
 * relations by providing a service to load a set of beans
15941
 * with differnt types and a common ID.
15942
 *
15943
 * @file    RedBeanPHP/Util/MultiLoader.php
15944
 * @author  Gabor de Mooij and the RedBeanPHP Community
15945
 * @license BSD/GPLv2
15946
 *
15947
 * @copyright
15948
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
15949
 * This source file is subject to the BSD/GPLv2 License that is bundled
15950
 * with this source code in the file license.txt.
15951
 */
15952
class MultiLoader
15953
{
15954
	/**
15955
	 * Loads multiple types of beans with the same ID.
15956
	 * This might look like a strange method, however it can be useful
15957
	 * for loading a one-to-one relation. In a typical 1-1 relation,
15958
	 * you have two records sharing the same primary key.
15959
	 * RedBeanPHP has only limited support for 1-1 relations.
15960
	 * In general it is recommended to use 1-N for this.
15961
	 *
15962
	 * Usage:
15963
	 *
15964
	 * <code>
15965
	 * list( $author, $bio ) = R::loadMulti( 'author, bio', $id );
15966
	 * </code>
15967
	 *
15968
	 * @param OODB         $oodb  OODB object
15969
	 * @param string|array $types the set of types to load at once
15970
	 * @param mixed        $id    the common ID
15971
	 *
15972
	 * @return OODBBean
15973
	 */
15974
	public static function load( OODB $oodb, $types, $id )
15975
	{
15976
		if ( is_string( $types ) ) {
15977
			$types = explode( ',', $types );
15978
		}
15979
15980
		if ( !is_array( $types ) ) {
15981
			return array();
15982
		}
15983
15984
		foreach ( $types as $k => $typeItem ) {
15985
			$types[$k] = $oodb->load( $typeItem, $id );
15986
		}
15987
15988
		return $types;
15989
	}
15990
}
15991
}
15992
15993
namespace RedBeanPHP\Util {
15994
15995
use RedBeanPHP\OODB as OODB;
15996
use RedBeanPHP\OODBBean as OODBBean;
15997
use RedBeanPHP\RedException as RedException;
15998
use RedBeanPHP\Adapter as Adapter;
15999
16000
/**
16001
 * Transaction Helper
16002
 *
16003
 * This code was originally part of the facade, however it has
16004
 * been decided to remove unique features to service classes like
16005
 * this to make them available to developers not using the facade class.
16006
 *
16007
 * Database transaction helper. This is a convenience class
16008
 * to perform a callback in a database transaction. This class
16009
 * contains a method to wrap your callback in a transaction.
16010
 *
16011
 * @file    RedBeanPHP/Util/Transaction.php
16012
 * @author  Gabor de Mooij and the RedBeanPHP Community
16013
 * @license BSD/GPLv2
16014
 *
16015
 * @copyright
16016
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
16017
 * This source file is subject to the BSD/GPLv2 License that is bundled
16018
 * with this source code in the file license.txt.
16019
 */
16020
class Transaction
16021
{
16022
	/**
16023
	 * Wraps a transaction around a closure or string callback.
16024
	 * If an Exception is thrown inside, the operation is automatically rolled back.
16025
	 * If no Exception happens, it commits automatically.
16026
	 * It also supports (simulated) nested transactions (that is useful when
16027
	 * you have many methods that needs transactions but are unaware of
16028
	 * each other).
16029
	 *
16030
	 * Example:
16031
	 *
16032
	 * <code>
16033
	 * $from = 1;
16034
	 * $to = 2;
16035
	 * $amount = 300;
16036
	 *
16037
	 * R::transaction(function() use($from, $to, $amount)
16038
	 * {
16039
	 *   $accountFrom = R::load('account', $from);
16040
	 *   $accountTo = R::load('account', $to);
16041
	 *   $accountFrom->money -= $amount;
16042
	 *   $accountTo->money += $amount;
16043
	 *   R::store($accountFrom);
16044
	 *   R::store($accountTo);
16045
	 * });
16046
	 * </code>
16047
	 *
16048
	 * @param Adapter  $adapter  Database Adapter providing transaction mechanisms.
16049
	 * @param callable $callback Closure (or other callable) with the transaction logic
16050
	 *
16051
	 * @return mixed
16052
	 */
16053
	public static function transaction( Adapter $adapter, $callback )
16054
	{
16055
		if ( !is_callable( $callback ) ) {
16056
			throw new RedException( 'R::transaction needs a valid callback.' );
16057
		}
16058
16059
		static $depth = 0;
16060
		$result = null;
16061
		try {
16062
			if ( $depth == 0 ) {
16063
				$adapter->startTransaction();
16064
			}
16065
			$depth++;
16066
			$result = call_user_func( $callback ); //maintain 5.2 compatibility
16067
			$depth--;
16068
			if ( $depth == 0 ) {
16069
				$adapter->commit();
16070
			}
16071
		} catch ( \Exception $exception ) {
16072
			$depth--;
16073
			if ( $depth == 0 ) {
16074
				$adapter->rollback();
16075
			}
16076
			throw $exception;
16077
		}
16078
		return $result;
16079
	}
16080
}
16081
}
16082
16083
namespace RedBeanPHP\Util {
16084
16085
use RedBeanPHP\OODB as OODB;
16086
use RedBeanPHP\OODBBean as OODBBean;
16087
use RedBeanPHP\ToolBox as ToolBox;
16088
16089
/**
16090
 * Quick Export Utility
16091
 *
16092
 * The Quick Export Utility Class provides functionality to easily
16093
 * expose the result of SQL queries as well-known formats like CSV.
16094
 *
16095
 * @file    RedBeanPHP/Util/QuickExporft.php
16096
 * @author  Gabor de Mooij and the RedBeanPHP Community
16097
 * @license BSD/GPLv2
16098
 *
16099
 * @copyright
16100
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
16101
 * This source file is subject to the BSD/GPLv2 License that is bundled
16102
 * with this source code in the file license.txt.
16103
 */
16104
class QuickExport
16105
{
16106
	/**
16107
	 * @var Finder
16108
	 */
16109
	protected $toolbox;
16110
16111
	/**
16112
	 * @boolean
16113
	 */
16114
	private static $test = FALSE;
16115
16116
	/**
16117
	 * Constructor.
16118
	 * The Quick Export requires a toolbox.
16119
	 *
16120
	 * @param ToolBox $toolbox
16121
	 */
16122
	public function __construct( ToolBox $toolbox )
16123
	{
16124
		$this->toolbox = $toolbox;
16125
	}
16126
16127
	/**
16128
	 * Makes csv() testable.
16129
	 */
16130
	public static function operation( $name, $arg1, $arg2 = TRUE ) {
16131
		$out = '';
16132
		switch( $name ) {
16133
			case 'test':
16134
				self::$test = (boolean) $arg1;
16135
				break;
16136
			case 'header':
16137
				$out = ( self::$test ) ? $arg1 : header( $arg1, $arg2 );
16138
				break;
16139
			case 'readfile':
16140
				$out = ( self::$test ) ? file_get_contents( $arg1 ) : readfile( $arg1 );
16141
				break;
16142
			case 'exit':
16143
				$out = ( self::$test ) ? 'exit' : exit();
16144
				break;
16145
		}
16146
		return $out;
16147
	}
16148
16149
	/**
16150
	 * Exposes the result of the specified SQL query as a CSV file.
16151
	 * Usage:
16152
	 *
16153
	 * R::csv( 'SELECT
16154
	 *                 `name`,
16155
	 *                  population
16156
	 *          FROM city
16157
	 *          WHERE region = :region ',
16158
	 *          array( ':region' => 'Denmark' ),
16159
	 *          array( 'city', 'population' ),
16160
	 *          '/tmp/cities.csv'
16161
	 * );
16162
	 *
16163
	 * The command above will select all cities in Denmark
16164
	 * and create a CSV with columns 'city' and 'population' and
16165
	 * populate the cells under these column headers with the
16166
	 * names of the cities and the population numbers respectively.
16167
	 *
16168
	 * @param string  $sql      SQL query to expose result of
16169
	 * @param array   $bindings parameter bindings
16170
	 * @param array   $columns  column headers for CSV file
16171
	 * @param string  $path     path to save CSV file to
16172
	 * @param boolean $output   TRUE to output CSV directly using readfile
16173
	 * @param array   $options  delimiter, quote and escape character respectively
16174
	 *
16175
	 * @return void
16176
	 */
16177
	public function csv( $sql = '', $bindings = array(), $columns = NULL, $path = '/tmp/redexport_%s.csv', $output = TRUE, $options = array(',','"','\\') )
16178
	{
16179
		list( $delimiter, $enclosure, $escapeChar ) = $options;
16180
		$path = sprintf( $path, date('Ymd_his') );
16181
		$handle = fopen( $path, 'w' );
16182
		if ($columns) if (PHP_VERSION_ID>=505040) fputcsv($handle, $columns, $delimiter, $enclosure, $escapeChar ); else fputcsv($handle, $columns, $delimiter, $enclosure );
16183
		$cursor = $this->toolbox->getDatabaseAdapter()->getCursor( $sql, $bindings );
16184
		while( $row = $cursor->getNextItem() ) {
16185
			if (PHP_VERSION_ID>=505040) fputcsv($handle, $row, $delimiter, $enclosure, $escapeChar ); else fputcsv($handle, $row, $delimiter, $enclosure );
16186
		}
16187
		fclose($handle);
16188
		if ( $output ) {
16189
			$file = basename($path);
16190
			$out = self::operation('header',"Pragma: public");
16191
			$out .= self::operation('header',"Expires: 0");
16192
			$out .= self::operation('header',"Cache-Control: must-revalidate, post-check=0, pre-check=0");
16193
			$out .= self::operation('header',"Cache-Control: private", FALSE );
16194
			$out .= self::operation('header',"Content-Type: text/csv");
16195
			$out .= self::operation('header',"Content-Disposition: attachment; filename={$file}" );
16196
			$out .= self::operation('header',"Content-Transfer-Encoding: binary");
16197
			$out .= self::operation('readfile',$path );
16198
			@unlink( $path );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

16198
			/** @scrutinizer ignore-unhandled */ @unlink( $path );

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
16199
			self::operation('exit', FALSE);
16200
			return $out;
16201
		}
16202
	}
16203
}
16204
}
16205
16206
namespace RedBeanPHP\Util {
16207
16208
use RedBeanPHP\OODB as OODB;
16209
use RedBeanPHP\OODBBean as OODBBean;
16210
use RedBeanPHP\ToolBox as ToolBox;
16211
use RedBeanPHP\Finder;
16212
16213
/**
16214
 * MatchUp Utility
16215
 *
16216
 * Tired of creating login systems and password-forget systems?
16217
 * MatchUp is an ORM-translation of these kind of problems.
16218
 * A matchUp is a match-and-update combination in terms of beans.
16219
 * Typically login related problems are all about a match and
16220
 * a conditional update.
16221
 * 
16222
 * @file    RedBeanPHP/Util/MatchUp.php
16223
 * @author  Gabor de Mooij and the RedBeanPHP Community
16224
 * @license BSD/GPLv2
16225
 *
16226
 * @copyright
16227
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
16228
 * This source file is subject to the BSD/GPLv2 License that is bundled
16229
 * with this source code in the file license.txt.
16230
 */
16231
class MatchUp
16232
{
16233
	/**
16234
	 * @var Toolbox
16235
	 */
16236
	protected $toolbox;
16237
16238
	/**
16239
	 * Constructor.
16240
	 * The MatchUp class requires a toolbox
16241
	 *
16242
	 * @param ToolBox $toolbox
16243
	 */
16244
	public function __construct( ToolBox $toolbox )
16245
	{
16246
		$this->toolbox = $toolbox;
16247
	}
16248
16249
	/**
16250
	 * MatchUp is a powerful productivity boosting method that can replace simple control
16251
	 * scripts with a single RedBeanPHP command. Typically, matchUp() is used to
16252
	 * replace login scripts, token generation scripts and password reset scripts.
16253
	 * The MatchUp method takes a bean type, an SQL query snippet (starting at the WHERE clause),
16254
	 * SQL bindings, a pair of task arrays and a bean reference.
16255
	 *
16256
	 * If the first 3 parameters match a bean, the first task list will be considered,
16257
	 * otherwise the second one will be considered. On consideration, each task list,
16258
	 * an array of keys and values will be executed. Every key in the task list should
16259
	 * correspond to a bean property while every value can either be an expression to
16260
	 * be evaluated or a closure (PHP 5.3+). After applying the task list to the bean
16261
	 * it will be stored. If no bean has been found, a new bean will be dispensed.
16262
	 *
16263
	 * This method will return TRUE if the bean was found and FALSE if not AND
16264
	 * there was a NOT-FOUND task list. If no bean was found AND there was also
16265
	 * no second task list, NULL will be returned.
16266
	 *
16267
	 * To obtain the bean, pass a variable as the sixth parameter.
16268
	 * The function will put the matching bean in the specified variable.
16269
	 *
16270
	 * @param string   $type         type of bean you're looking for
16271
	 * @param string   $sql          SQL snippet (starting at the WHERE clause, omit WHERE-keyword)
16272
	 * @param array    $bindings     array of parameter bindings for SQL snippet
16273
	 * @param array    $onFoundDo    task list to be considered on finding the bean
16274
	 * @param array    $onNotFoundDo task list to be considered on NOT finding the bean
16275
	 * @param OODBBean &$bean        reference to obtain the found bean
16276
	 *
16277
	 * @return mixed
16278
	 */
16279
	public function matchUp( $type, $sql, $bindings = array(), $onFoundDo = NULL, $onNotFoundDo = NULL, &$bean = NULL )
16280
	{
16281
		$finder = new Finder( $this->toolbox );
16282
		$oodb   = $this->toolbox->getRedBean();
16283
		$bean = $finder->findOne( $type, $sql, $bindings );
16284
		if ( $bean && $onFoundDo ) {
16285
			foreach( $onFoundDo as $property => $value ) {
16286
				if ( function_exists('is_callable') && is_callable( $value ) ) {
16287
					$bean[$property] = call_user_func_array( $value, array( $bean ) );
16288
				} else {
16289
					$bean[$property] = $value;
16290
				}
16291
			}
16292
			$oodb->store( $bean );
16293
			return TRUE;
16294
		}
16295
		if ( $onNotFoundDo ) {
16296
			$bean = $oodb->dispense( $type );
16297
			foreach( $onNotFoundDo as $property => $value ) {
16298
				if ( function_exists('is_callable') && is_callable( $value ) ) {
16299
					$bean[$property] = call_user_func_array( $value, array( $bean ) );
16300
				} else {
16301
					$bean[$property] = $value;
16302
				}
16303
			}
16304
			$oodb->store( $bean );
16305
			return FALSE;
16306
		}
16307
		return NULL;
16308
	}
16309
}
16310
}
16311
16312
namespace RedBeanPHP\Util {
16313
16314
use RedBeanPHP\OODB as OODB;
16315
use RedBeanPHP\OODBBean as OODBBean;
16316
use RedBeanPHP\ToolBox as ToolBox;
16317
use RedBeanPHP\Finder;
16318
16319
/**
16320
 * Look Utility
16321
 *
16322
 * The Look Utility class provides an easy way to generate
16323
 * tables and selects (pulldowns) from the database.
16324
 * 
16325
 * @file    RedBeanPHP/Util/Look.php
16326
 * @author  Gabor de Mooij and the RedBeanPHP Community
16327
 * @license BSD/GPLv2
16328
 *
16329
 * @copyright
16330
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
16331
 * This source file is subject to the BSD/GPLv2 License that is bundled
16332
 * with this source code in the file license.txt.
16333
 */
16334
class Look
16335
{
16336
	/**
16337
	 * @var Toolbox
16338
	 */
16339
	protected $toolbox;
16340
16341
	/**
16342
	 * Constructor.
16343
	 * The MatchUp class requires a toolbox
16344
	 *
16345
	 * @param ToolBox $toolbox
16346
	 */
16347
	public function __construct( ToolBox $toolbox )
16348
	{
16349
		$this->toolbox = $toolbox;
16350
	}
16351
16352
	/**
16353
	 * Takes an full SQL query with optional bindings, a series of keys, a template
16354
	 * and optionally a filter function and glue and assembles a view from all this.
16355
	 * This is the fastest way from SQL to view. Typically this function is used to
16356
	 * generate pulldown (select tag) menus with options queried from the database.
16357
	 *
16358
	 * Usage:
16359
	 *
16360
	 * <code>
16361
	 * $htmlPulldown = R::look(
16362
	 *   'SELECT * FROM color WHERE value != ? ORDER BY value ASC',
16363
	 *   [ 'g' ],
16364
	 *   [ 'value', 'name' ],
16365
	 *   '<option value="%s">%s</option>',
16366
	 *   'strtoupper',
16367
	 *   "\n"
16368
	 * );
16369
	 *</code>
16370
	 *
16371
	 * The example above creates an HTML fragment like this:
16372
	 *
16373
	 * <option value="B">BLUE</option>
16374
	 * <option value="R">RED</option>
16375
	 *
16376
	 * to pick a color from a palette. The HTML fragment gets constructed by
16377
	 * an SQL query that selects all colors that do not have value 'g' - this
16378
	 * excludes green. Next, the bean properties 'value' and 'name' are mapped to the
16379
	 * HTML template string, note that the order here is important. The mapping and
16380
	 * the HTML template string follow vsprintf-rules. All property values are then
16381
	 * passed through the specified filter function 'strtoupper' which in this case
16382
	 * is a native PHP function to convert strings to uppercase characters only.
16383
	 * Finally the resulting HTML fragment strings are glued together using a
16384
	 * newline character specified in the last parameter for readability.
16385
	 *
16386
	 * In previous versions of RedBeanPHP you had to use:
16387
	 * R::getLook()->look() instead of R::look(). However to improve useability of the
16388
	 * library the look() function can now directly be invoked from the facade.
16389
	 *
16390
	 * @param string   $sql      query to execute
16391
	 * @param array    $bindings parameters to bind to slots mentioned in query or an empty array
16392
	 * @param array    $keys     names in result collection to map to template
16393
	 * @param string   $template HTML template to fill with values associated with keys, use printf notation (i.e. %s)
16394
	 * @param callable $filter   function to pass values through (for translation for instance)
16395
	 * @param string   $glue     optional glue to use when joining resulting strings
16396
	 *
16397
	 * @return string
16398
	 */
16399
	public function look( $sql, $bindings = array(), $keys = array( 'selected', 'id', 'name' ), $template = '<option %s value="%s">%s</option>', $filter = 'trim', $glue = '' )
16400
	{
16401
		$adapter = $this->toolbox->getDatabaseAdapter();
16402
		$lines = array();
16403
		$rows = $adapter->get( $sql, $bindings );
16404
		foreach( $rows as $row ) {
16405
			$values = array();
16406
			foreach( $keys as $key ) {
16407
				if (!empty($filter)) {
16408
					$values[] = call_user_func_array( $filter, array( $row[$key] ) );
16409
				} else {
16410
					$values[] = $row[$key];
16411
				}
16412
			}
16413
			$lines[] = vsprintf( $template, $values );
16414
		}
16415
		$string = implode( $glue, $lines );
16416
		return $string;
16417
	}
16418
}
16419
}
16420
16421
namespace RedBeanPHP\Util {
16422
16423
use RedBeanPHP\OODB as OODB;
16424
use RedBeanPHP\OODBBean as OODBBean;
16425
use RedBeanPHP\ToolBox as ToolBox;
16426
use RedBeanPHP\Finder;
16427
16428
/**
16429
 * Diff Utility
16430
 *
16431
 * The Look Utility class provides an easy way to generate
16432
 * tables and selects (pulldowns) from the database.
16433
 * 
16434
 * @file    RedBeanPHP/Util/Diff.php
16435
 * @author  Gabor de Mooij and the RedBeanPHP Community
16436
 * @license BSD/GPLv2
16437
 *
16438
 * @copyright
16439
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
16440
 * This source file is subject to the BSD/GPLv2 License that is bundled
16441
 * with this source code in the file license.txt.
16442
 */
16443
class Diff
16444
{
16445
	/**
16446
	 * @var Toolbox
16447
	 */
16448
	protected $toolbox;
16449
16450
	/**
16451
	 * Constructor.
16452
	 * The MatchUp class requires a toolbox
16453
	 *
16454
	 * @param ToolBox $toolbox
16455
	 */
16456
	public function __construct( ToolBox $toolbox )
16457
	{
16458
		$this->toolbox = $toolbox;
16459
	}
16460
16461
	/**
16462
	 * Calculates a diff between two beans (or arrays of beans).
16463
	 * The result of this method is an array describing the differences of the second bean compared to
16464
	 * the first, where the first bean is taken as reference. The array is keyed by type/property, id and property name, where
16465
	 * type/property is either the type (in case of the root bean) or the property of the parent bean where the type resides.
16466
	 * The diffs are mainly intended for logging, you cannot apply these diffs as patches to other beans.
16467
	 * However this functionality might be added in the future.
16468
	 *
16469
	 * The keys of the array can be formatted using the $format parameter.
16470
	 * A key will be composed of a path (1st), id (2nd) and property (3rd).
16471
	 * Using printf-style notation you can determine the exact format of the key.
16472
	 * The default format will look like:
16473
	 *
16474
	 * 'book.1.title' => array( <OLDVALUE>, <NEWVALUE> )
16475
	 *
16476
	 * If you only want a simple diff of one bean and you don't care about ids,
16477
	 * you might pass a format like: '%1$s.%3$s' which gives:
16478
	 *
16479
	 * 'book.1.title' => array( <OLDVALUE>, <NEWVALUE> )
16480
	 *
16481
	 * The filter parameter can be used to set filters, it should be an array
16482
	 * of property names that have to be skipped. By default this array is filled with
16483
	 * two strings: 'created' and 'modified'.
16484
	 *
16485
	 * @param OODBBean|array $beans   reference beans
16486
	 * @param OODBBean|array $others  beans to compare
16487
	 * @param array          $filters names of properties of all beans to skip
16488
	 * @param string         $format  the format of the key, defaults to '%s.%s.%s'
16489
	 * @param string         $type    type/property of bean to use for key generation
16490
	 *
16491
	 * @return array
16492
	 */
16493
	public function diff( $beans, $others, $filters = array( 'created', 'modified' ), $format = '%s.%s.%s', $type = NULL )
16494
	{
16495
		$diff = array();
16496
		if ( !is_array( $beans ) ) $beans = array( $beans );
16497
		if ( !is_array( $others ) ) $others = array( $others );
16498
		foreach( $beans as $bean ) {
16499
			if ( !is_object( $bean ) ) continue;
16500
			if ( !( $bean instanceof OODBBean ) ) continue;
16501
			if ( $type == NULL ) $type = $bean->getMeta( 'type' );
16502
			foreach( $others as $other ) {
16503
				if ( !is_object( $other ) ) continue;
16504
				if ( !( $other instanceof OODBBean ) ) continue;
16505
				if ( $other->id == $bean->id ) {
16506
					foreach( $bean as $property => $value ) {
16507
						if ( in_array( $property, $filters ) ) continue;
16508
						$key = vsprintf( $format, array( $type, $bean->id, $property ) );
16509
						$compare = $other->{$property};
16510
						if ( !is_object( $value ) && !is_array( $value ) ) {
16511
							if ( $value != $compare ) {
16512
								$diff[$key] = array( $value, $compare );
16513
							}
16514
							continue;
16515
						} else {
16516
							$diff = array_merge( $diff, $this->diff( $value, $compare, $filters, $format, $key ) );
16517
							continue;
16518
						}
16519
					}
16520
				}
16521
			}
16522
		}
16523
		return $diff;
16524
	}
16525
}
16526
}
16527
16528
namespace RedBeanPHP\Util {
16529
16530
use RedBeanPHP\ToolBox;
16531
use RedBeanPHP\OODBBean;
16532
16533
/**
16534
 * Tree
16535
 *
16536
 * Given a bean, finds it children or parents
16537
 * in a hierchical structure.
16538
 *
16539
 * @experimental feature
16540
 *
16541
 * @file    RedBeanPHP/Util/Tree.php
16542
 * @author  Gabor de Mooij and the RedBeanPHP Community
16543
 * @license BSD/GPLv2
16544
 *
16545
 * @copyright
16546
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
16547
 * This source file is subject to the BSD/GPLv2 License that is bundled
16548
 * with this source code in the file license.txt.
16549
 */
16550
class Tree {
16551
16552
	/**
16553
	 * @var ToolBox
16554
	 */
16555
	protected $toolbox;
16556
16557
	/**
16558
	 * @var QueryWriter
16559
	 */
16560
	protected $writer;
16561
16562
	/**
16563
	 * @var OODB
16564
	 */
16565
	protected $oodb;
16566
16567
	/**
16568
	 * Constructor, creates a new instance of
16569
	 * the Tree.
16570
	 *
16571
	 * @param ToolBox $toolbox toolbox
16572
	 */
16573
	public function __construct( ToolBox $toolbox )
16574
	{
16575
		$this->toolbox = $toolbox;
16576
		$this->writer  = $toolbox->getWriter();
16577
		$this->oodb    = $toolbox->getRedBean();
16578
	}
16579
16580
	/**
16581
	 * Returns all child beans associates with the specified
16582
	 * bean in a tree structure.
16583
	 *
16584
	 * @note this only works for databases that support
16585
	 * recusrive common table expressions.
16586
	 *
16587
	 * @param OODBBean $bean     reference bean to find children of
16588
	 * @param string   $sql      optional SQL snippet
16589
	 * @param array    $bindings optional parameter bindings for SQL snippet
16590
	 *
16591
	 * @return array
16592
	 */
16593
	public function children( OODBBean $bean, $sql = NULL, $bindings = array() )
16594
	{
16595
		$type = $bean->getMeta('type');
16596
		$id   = $bean->id;
16597
16598
		$rows = $this->writer->queryRecursiveCommonTableExpression( $type, $id, FALSE, $sql, $bindings );
16599
16600
		return $this->oodb->convertToBeans( $type, $rows );
16601
	}
16602
	
16603
	/**
16604
	 * Returns all parent beans associates with the specified
16605
	 * bean in a tree structure.
16606
	 *
16607
	 * @note this only works for databases that support
16608
	 * recusrive common table expressions.
16609
	 *
16610
	 * @param OODBBean $bean     reference bean to find parents of
16611
	 * @param string   $sql      optional SQL snippet
16612
	 * @param array    $bindings optional parameter bindings for SQL snippet
16613
	 *
16614
	 * @return array
16615
	 */
16616
	public function parents( OODBBean $bean, $sql = NULL, $bindings = array() )
16617
	{
16618
		$type = $bean->getMeta('type');
16619
		$id   = $bean->id;
16620
16621
		$rows = $this->writer->queryRecursiveCommonTableExpression( $type, $id, TRUE, $sql, $bindings );
16622
16623
		return $this->oodb->convertToBeans( $type, $rows );		
16624
	}	
16625
}
16626
}
16627
16628
namespace RedBeanPHP\Util {
16629
use RedBeanPHP\Facade as R;
16630
16631
/**
16632
 * Feature Utility
16633
 *
16634
 * The Feature Utility class provides an easy way to turn
16635
 * on or off features. This allows us to introduce new features
16636
 * without accidentally breaking backward compatibility.
16637
 * 
16638
 * @file    RedBeanPHP/Util/Feature.php
16639
 * @author  Gabor de Mooij and the RedBeanPHP Community
16640
 * @license BSD/GPLv2
16641
 *
16642
 * @copyright
16643
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
16644
 * This source file is subject to the BSD/GPLv2 License that is bundled
16645
 * with this source code in the file license.txt.
16646
 */
16647
class Feature
16648
{
16649
	/**
16650
	 * Selects the feature set you want as specified by
16651
	 * the label.
16652
	 *
16653
	 * Usage:
16654
	 * R::useFeatureSet( 'novice/latest' );
16655
	 *
16656
	 * @param string $label label
16657
	 *
16658
	 * @return void
16659
	 */
16660
	public static function feature( $label ) {
16661
		switch( $label ) {
16662
			case "novice/latest":
16663
				R::noNuke( TRUE );
16664
				R::setAutoResolve( TRUE );
16665
				break;
16666
			case "latest":
16667
				R::noNuke( FALSE );
16668
				R::setAutoResolve( TRUE );
16669
				break;
16670
			case "novice/5.3":
16671
				R::noNuke( TRUE );
16672
				R::setAutoResolve( TRUE );
16673
				break;
16674
			case "5.3":
16675
				R::noNuke( FALSE );
16676
				R::setAutoResolve( TRUE );
16677
				break;
16678
			default:
16679
				throw new \Exception("Unknown feature set label.");
16680
				break;
16681
		}
16682
	}
16683
}
16684
}
16685
16686
namespace RedBeanPHP {
16687
16688
/**
16689
 * RedBean Plugin.
16690
 * Marker interface for plugins.
16691
 * Use this interface when defining new plugins, it's an
16692
 * easy way for the rest of the application to recognize your
16693
 * plugin. This plugin interface does not require you to
16694
 * implement a specific API.
16695
 *
16696
 * @file    RedBean/Plugin.php
16697
 * @author  Gabor de Mooij and the RedBeanPHP Community
16698
 * @license BSD/GPLv2
16699
 *
16700
 * @copyright
16701
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
16702
 * This source file is subject to the BSD/GPLv2 License that is bundled
16703
 * with this source code in the file license.txt.
16704
 */
16705
interface Plugin
16706
{
16707
}
16708
16709
;
16710
}
16711
namespace {
16712
16713
//make some classes available for backward compatibility
16714
class RedBean_SimpleModel extends \RedBeanPHP\SimpleModel {};
16715
16716
if (!class_exists('R')) {
16717
	class R extends \RedBeanPHP\Facade{};
16718
}
16719
16720
16721
16722
/**
16723
 * Support functions for RedBeanPHP.
16724
 * Additional convenience shortcut functions for RedBeanPHP.
16725
 *
16726
 * @file    RedBeanPHP/Functions.php
16727
 * @author  Gabor de Mooij and the RedBeanPHP community
16728
 * @license BSD/GPLv2
16729
 *
16730
 * @copyright
16731
 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
16732
 * This source file is subject to the BSD/GPLv2 License that is bundled
16733
 * with this source code in the file license.txt.
16734
 */
16735
16736
/**
16737
 * Convenience function for ENUM short syntax in queries.
16738
 *
16739
 * Usage:
16740
 *
16741
 * <code>
16742
 * R::find( 'paint', ' color_id = ? ', [ EID('color:yellow') ] );
16743
 * </code>
16744
 *
16745
 * If a function called EID() already exists you'll have to write this
16746
 * wrapper yourself ;)
16747
 *
16748
 * @param string $enumName enum code as you would pass to R::enum()
16749
 *
16750
 * @return mixed
16751
 */
16752
if (!function_exists('EID')) {
16753
16754
	function EID($enumName)
16755
	{
16756
		return \RedBeanPHP\Facade::enum( $enumName )->id;
16757
	}
16758
16759
}
16760
16761
/**
16762
 * Prints the result of R::dump() to the screen using
16763
 * print_r.
16764
 *
16765
 * @param mixed $data data to dump
16766
 *
16767
 * @return void
16768
 */
16769
if ( !function_exists( 'dmp' ) ) {
16770
16771
	function dmp( $list )
16772
	{
16773
		print_r( \RedBeanPHP\Facade::dump( $list ) );
16774
	}
16775
}
16776
16777
/**
16778
 * Function alias for R::genSlots().
16779
 */
16780
if ( !function_exists( 'genslots' ) ) {
16781
16782
	function genslots( $slots, $tpl = NULL )
16783
	{
16784
		return \RedBeanPHP\Facade::genSlots( $slots, $tpl );
16785
	}
16786
}
16787
16788
/**
16789
 * Function alias for R::flat().
16790
 */
16791
if ( !function_exists( 'array_flatten' ) ) {
16792
16793
	function array_flatten( $array )
16794
	{
16795
		return \RedBeanPHP\Facade::flat( $array );
16796
	}
16797
}
16798
16799
16800
}
16801