Issues (262)

RedBean.php (4 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 ) ) ) {
0 ignored issues
show
Deprecated Code introduced by
The function RedBeanPHP\Repository::tableExists() has been deprecated: Use AQueryWriter::typeExists() instead. ( Ignorable by Annotation )

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

9088
		if ( !/** @scrutinizer ignore-deprecated */ $this->tableExists( $this->writer->esc( $table, TRUE ) ) ) {

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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 );
0 ignored issues
show
Deprecated Code introduced by
The function RedBeanPHP\Repository::tableExists() has been deprecated: Use AQueryWriter::typeExists() instead. ( Ignorable by Annotation )

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

9935
		return /** @scrutinizer ignore-deprecated */ $this->repository->tableExists( $table );

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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();
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 );
0 ignored issues
show
Deprecated Code introduced by
The function RedBeanPHP\Facade::dup() has been deprecated. ( Ignorable by Annotation )

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

13332
		return /** @scrutinizer ignore-deprecated */ self::dup( $bean, array(), FALSE, $filters );
Loading history...
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 );
0 ignored issues
show
Deprecated Code introduced by
The function RedBeanPHP\Facade::getLook() has been deprecated. ( Ignorable by Annotation )

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

14835
		return /** @scrutinizer ignore-deprecated */ self::getLook()->look( $sql, $bindings, $keys, $template, $filter, $glue );
Loading history...
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 );
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