diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..3b51926
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,12 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(head:*)",
+ "Bash(tail:*)",
+ "mcp__serena__list_dir",
+ "Bash(curl:*)",
+ "WebSearch",
+ "Bash(php:*)"
+ ]
+ }
+}
diff --git a/autoload/class.DbModel.php b/autoload/class.DbModel.php
index 8ecc2e0..051aec5 100644
--- a/autoload/class.DbModel.php
+++ b/autoload/class.DbModel.php
@@ -1,7 +1,7 @@
', '@', '#', '$', '%', '^', '&', '*' . '(', ')' . '-', '=', '\\', '|', '[', ']', ':', '(', ')' );
$array_rep2 = array( '-', '-', '-', '-', '', '', '', '-', '-', '', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '-', '', '-', '-', '-', '-', '-', '-', '-', '-' );
@@ -47,6 +48,7 @@ class S
static public function no_pl_excel( $val )
{
+ $val = (string) $val;
$table = Array(
"\xc4\x85" => "a", "\xc4\x84" => "A", "\xc4\x87" => "c", "\xc4\x86" => "C",
"\xc4\x99" => "e", "\xc4\x98" => "E", "\xc5\x82" => "l", "\xc5\x81" => "L",
@@ -62,6 +64,7 @@ class S
public static function noPL( $val )
{
+ $val = (string) $val;
$table = array(
"А" => "a", "Б" => "b", "В" => "v", "Г" => "g", "Д" => "d",
"Е" => "e", "Ё" => "yo", "Ж" => "zh", "З" => "z", "И" => "i",
@@ -129,6 +132,7 @@ class S
public static function json_to_array( $json )
{
+ $values = [];
$values_tmp = json_decode( $json, true );
if ( is_array( $values_tmp ) ) foreach ( $values_tmp as $val )
@@ -148,7 +152,7 @@ class S
public static function get_session( $var )
{
- return $_SESSION[ $var ];
+ return $_SESSION[ $var ] ?? null;
}
public static function del_session( $var ) {
@@ -164,21 +168,13 @@ class S
{
if ( isset( $_POST[ $var ] ) )
{
- if ( is_string( $_POST[ $var ] ) )
- return $_POST[ $var ];
- else
- return $_POST[ $var ];
+ return $_POST[ $var ];
}
- else
+ if ( isset( $_GET[ $var ] ) )
{
- if ( isset( $_GET[ $var ] ) )
- {
- if ( is_string( $_GET[ $var ] ) )
- return $_GET[ $var ];
- else
- return $_GET[ $var ];
- }
+ return $_GET[ $var ];
}
+ return null;
}
public static function pre( $data , $type = '' )
@@ -235,13 +231,13 @@ class S
{
foreach ( $file as $file_tmp )
{
- if ( file_exists( $file_tmp ) )
+ if ( !empty( $file_tmp ) && file_exists( $file_tmp ) )
$mail -> AddAttachment( $file_tmp );
}
}
else
{
- if ( file_exists( $file ) )
+ if ( !empty( $file ) && file_exists( $file ) )
$mail -> AddAttachment( $file );
}
diff --git a/autoload/class.Tpl.php b/autoload/class.Tpl.php
index 72960f5..a9ed809 100644
--- a/autoload/class.Tpl.php
+++ b/autoload/class.Tpl.php
@@ -20,7 +20,7 @@ class Tpl
public function secureHTML( $val )
{
- $out = stripslashes( $val );
+ $out = stripslashes( (string) $val );
$out = str_replace( "'", "'", $out );
$out = str_replace( '"', """, $out );
$out = str_replace( "<", "<", $out );
diff --git a/autoload/controls/class.Apanel.php b/autoload/controls/class.Apanel.php
index b54b15a..ade063a 100644
--- a/autoload/controls/class.Apanel.php
+++ b/autoload/controls/class.Apanel.php
@@ -1,4 +1,4 @@
-
+ 0)
+ if (isset($basket[$ticket_id][$diffdays]) && $basket[$ticket_id][$diffdays]['quantity'] > 0)
{
$basket[$ticket_id][$diffdays]['quantity']--;
@@ -235,23 +235,23 @@ class Tickets
}
}
- $hash = md5(trim($_POST['email']) . trim($_POST['city']) . trim(date("Y-m-d H:i:s")));
+ $hash = md5(trim((string)($_POST['email'] ?? '')) . trim((string)($_POST['city'] ?? '')) . trim(date("Y-m-d H:i:s")));
//* Zapisywanie do DB klienta
$mdb->insert('orders', [
- 'name' => trim($_POST['name']),
- 'surname' => trim($_POST['surname']),
- 'email' => trim($_POST['email']),
- 'zip_code' => trim($_POST['zip_code']),
- 'city' => trim($_POST['city']),
- 'street' => trim($_POST['street']),
- 'order_price' => trim($finalPrice),
+ 'name' => trim((string)($_POST['name'] ?? '')),
+ 'surname' => trim((string)($_POST['surname'] ?? '')),
+ 'email' => trim((string)($_POST['email'] ?? '')),
+ 'zip_code' => trim((string)($_POST['zip_code'] ?? '')),
+ 'city' => trim((string)($_POST['city'] ?? '')),
+ 'street' => trim((string)($_POST['street'] ?? '')),
+ 'order_price' => $finalPrice,
'date_added' => $date,
'hash' => $hash,
- 'vat' => trim($_POST['vat'] == 'on' ? '1' : '0'),
- 'company_name' => trim($_POST['company_name']),
- 'nip' => trim($_POST['nip']),
- 'gift_address' => trim($_POST['gift_address'])
+ 'vat' => (($_POST['vat'] ?? '') == 'on' ? '1' : '0'),
+ 'company_name' => trim((string)($_POST['company_name'] ?? '')),
+ 'nip' => trim((string)($_POST['nip'] ?? '')),
+ 'gift_address' => trim((string)($_POST['gift_address'] ?? ''))
] );
//* Id klienta
@@ -273,7 +273,7 @@ class Tickets
{
foreach ( $value as $key => $val )
{
- if ( $val['date'] ) {
+ if ( !empty($val['date']) ) {
$dateFormatted = new \DateTime( $val['date'] );
$dateFormatted = $dateFormatted->format('Y-m-d');
} else
@@ -596,8 +596,10 @@ class Tickets
{
global $mdb;
$order = \factory\Tickets::get_order_details_by_hash(\S::get('order'));
+ $order_successful = false;
+ $order_fail = false;
- if ( $order['payment_status'] and !$order['informed_user'] )
+ if ( $order && !empty($order['payment_status']) && empty($order['informed_user']) )
{
if ($order['payment_status'])
{
diff --git a/autoload/factory/class.Apanel.php b/autoload/factory/class.Apanel.php
index 756643e..fff5fd9 100644
--- a/autoload/factory/class.Apanel.php
+++ b/autoload/factory/class.Apanel.php
@@ -1,4 +1,4 @@
-
+ get( 'users', '*', [ 'email' => strtolower( $email ) ] );
+ $result = $mdb -> get( 'users', '*', [ 'email' => strtolower( (string) $email ) ] );
if ( $result === false )
return false;
else
diff --git a/autoload/view/class.Site.php b/autoload/view/class.Site.php
index 5450ac5..8116c8d 100644
--- a/autoload/view/class.Site.php
+++ b/autoload/view/class.Site.php
@@ -8,11 +8,12 @@ class Site
$class = '\controls\\';
- $results = explode( '_', \S::get( 'module' ) );
+ $module = \S::get( 'module' ) ?? '';
+ $results = explode( '_', $module );
if ( is_array( $results ) ) foreach ( $results as $row )
$class .= ucfirst( $row );
- $action = \S::get( 'action' );
+ $action = \S::get( 'action' ) ?? '';
$tpl = new \Tpl;
$tpl -> content = \controls\Site::route();
diff --git a/config.php b/config.php
index fef2680..3bed4c9 100644
--- a/config.php
+++ b/config.php
@@ -15,7 +15,7 @@ $settings['tickets']['enable_sell'] = true;
// $settings['tickets']['price_0day'][rodzinn1] = 5;
// $settings['tickets']['price_0day'][rodzinny2] = 5;
//TODO: Bilet ulgowy - do 140 cm wzrostu
-if ( $_SESSION['lower_price_ticket'] )
+if ( !empty($_SESSION['lower_price_ticket']) )
$settings['tickets']['plac-zabaw-ulgowy']['price'] = 1;
else
$settings['tickets']['plac-zabaw-ulgowy']['price'] = 45;
diff --git a/dev/team/2026-02-22_22-51_php81-kompatybilnosc.md b/dev/team/2026-02-22_22-51_php81-kompatybilnosc.md
new file mode 100644
index 0000000..939f086
--- /dev/null
+++ b/dev/team/2026-02-22_22-51_php81-kompatybilnosc.md
@@ -0,0 +1,108 @@
+# Migracja PHP 7.4 → 8.1 — poprawki kompatybilności
+
+**Data:** 2026-02-22 22:51
+**Orkiestrator:** Claude Code (Team)
+**Zapytanie:** Otwarty projekt był napisany pod PHP 7.4, obecnie na serwerze ktoś zmienił wersję na 8.1 przez co strona wywala błąd 500. Przeanalizuj projekt i popraw metody które mogą być problematyczne.
+
+---
+
+## Analiza
+- **Typ:** debug + refactor
+- **Złożoność:** 4/5
+- **Pliki kontekstowe:** 20 plików PHP przeanalizowanych
+
+## Routing
+- **Agent główny:** Claude Code — debug wymaga bezpośredniego dostępu do plików i edycji
+- **Wsparcie:** Gemini CLI (niedostępny — 429 rate limit) → fallback do Claude Code
+
+## Wynik — lista poprawek
+
+### KRYTYCZNE (powodują Fatal Error / 500)
+
+#### 1. Krótkie tagi `` zamiast `mode === self::C_LOGGER_ECHO ) {
+ echo $log;
+ } else {
+ $this->logs[] = $log;
+ }
+ } else {
+ if ( $this->mode === self::C_LOGGER_ECHO ) {
+ echo $argument;
+ } else {
+ $this->logs[] = $argument;
+ }
+ }
+
+ if ( $this->mode === self::C_LOGGER_ECHO ) echo " " . PHP_EOL;
+ }
+ }
+
+ /**
+ * Returns the internal log array.
+ * The internal log array is where all log messages are stored.
+ *
+ * @return array
+ */
+ public function getLogs()
+ {
+ return $this->logs;
+ }
+
+ /**
+ * Clears the internal log array, removing all
+ * previously stored entries.
+ *
+ * @return self
+ */
+ public function clear()
+ {
+ $this->logs = array();
+ return $this;
+ }
+
+ /**
+ * Selects a logging mode.
+ * There are several options available.
+ *
+ * * C_LOGGER_ARRAY - log silently, stores entries in internal log array only
+ * * C_LOGGER_ECHO - also forward log messages directly to STDOUT
+ *
+ * @param integer $mode mode of operation for logging object
+ *
+ * @return self
+ */
+ public function setMode( $mode )
+ {
+ if ($mode !== self::C_LOGGER_ARRAY && $mode !== self::C_LOGGER_ECHO ) {
+ throw new RedException( 'Invalid mode selected for logger, use C_LOGGER_ARRAY or C_LOGGER_ECHO.' );
+ }
+ $this->mode = $mode;
+ return $this;
+ }
+
+ /**
+ * Searches for all log entries in internal log array
+ * for $needle and returns those entries.
+ * This method will return an array containing all matches for your
+ * search query.
+ *
+ * @param string $needle phrase to look for in internal log array
+ *
+ * @return array
+ */
+ public function grep( $needle )
+ {
+ $found = array();
+ foreach( $this->logs as $logEntry ) {
+ if ( strpos( $logEntry, $needle ) !== FALSE ) $found[] = $logEntry;
+ }
+ return $found;
+ }
+}
+}
+
+namespace RedBeanPHP\Logger\RDefault {
+
+use RedBeanPHP\Logger as Logger;
+use RedBeanPHP\Logger\RDefault as RDefault;
+use RedBeanPHP\RedException as RedException;
+
+/**
+ * Debug logger.
+ * A special logger for debugging purposes.
+ * Provides debugging logging functions for RedBeanPHP.
+ *
+ * @file RedBeanPHP/Logger/RDefault/Debug.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Debug extends RDefault implements Logger
+{
+ /**
+ * @var integer
+ */
+ protected $strLen = 40;
+
+ /**
+ * @var boolean
+ */
+ protected static $noCLI = FALSE;
+
+ /**
+ * @var boolean
+ */
+ protected $flagUseStringOnlyBinding = FALSE;
+
+ /**
+ * Toggles CLI override. By default debugging functions will
+ * output differently based on PHP_SAPI values. This function
+ * allows you to override the PHP_SAPI setting. If you set
+ * this to TRUE, CLI output will be suppressed in favour of
+ * HTML output. So, to get HTML on the command line use
+ * setOverrideCLIOutput( TRUE ).
+ *
+ * @param boolean $yesNo CLI-override setting flag
+ *
+ * @return void
+ */
+ public static function setOverrideCLIOutput( $yesNo )
+ {
+ self::$noCLI = $yesNo;
+ }
+
+ /**
+ * Writes a query for logging with all bindings / params filled
+ * in.
+ *
+ * @param string $newSql the query
+ * @param array $newBindings the bindings to process (key-value pairs)
+ *
+ * @return string
+ */
+ protected function writeQuery( $newSql, $newBindings )
+ {
+ //avoid str_replace collisions: slot1 and slot10 (issue 407).
+ uksort( $newBindings, function( $a, $b ) {
+ return ( strlen( $b ) - strlen( $a ) );
+ } );
+
+ $newStr = $newSql;
+ foreach( $newBindings as $slot => $value ) {
+ if ( strpos( $slot, ':' ) === 0 ) {
+ $newStr = str_replace( $slot, $this->fillInValue( $value ), $newStr );
+ }
+ }
+ return $newStr;
+ }
+
+ /**
+ * Fills in a value of a binding and truncates the
+ * resulting string if necessary.
+ *
+ * @param mixed $value bound value
+ *
+ * @return string
+ */
+ protected function fillInValue( $value )
+ {
+ if ( is_array( $value ) && count( $value ) == 2 ) {
+ $paramType = end( $value );
+ $value = reset( $value );
+ } else {
+ $paramType = NULL;
+ }
+
+ if ( is_null( $value ) ) $value = 'NULL';
+
+ if ( $this->flagUseStringOnlyBinding ) $paramType = \PDO::PARAM_STR;
+
+ if ( $paramType != \PDO::PARAM_INT && $paramType != \PDO::PARAM_STR ) {
+ if ( \RedBeanPHP\QueryWriter\AQueryWriter::canBeTreatedAsInt( $value ) || $value === 'NULL') {
+ $paramType = \PDO::PARAM_INT;
+ } else {
+ $paramType = \PDO::PARAM_STR;
+ }
+ }
+
+ if ( strlen( $value ) > ( $this->strLen ) ) {
+ $value = substr( $value, 0, ( $this->strLen ) ).'... ';
+ }
+
+ if ($paramType === \PDO::PARAM_STR) {
+ $value = '\''.$value.'\'';
+ }
+
+ return $value;
+ }
+
+ /**
+ * Depending on the current mode of operation,
+ * this method will either log and output to STDIN or
+ * just log.
+ *
+ * Depending on the value of constant PHP_SAPI this function
+ * will format output for console or HTML.
+ *
+ * @param string $str string to log or output and log
+ *
+ * @return void
+ */
+ protected function output( $str )
+ {
+ $this->logs[] = $str;
+ if ( !$this->mode ) {
+ $highlight = FALSE;
+ /* just a quick heuristic to highlight schema changes */
+ if ( strpos( $str, 'CREATE' ) === 0
+ || strpos( $str, 'ALTER' ) === 0
+ || strpos( $str, 'DROP' ) === 0) {
+ $highlight = TRUE;
+ }
+ if (PHP_SAPI === 'cli' && !self::$noCLI) {
+ if ($highlight) echo "\e[91m";
+ echo $str, PHP_EOL;
+ echo "\e[39m";
+ } else {
+ if ($highlight) {
+ echo "{$str}";
+ } else {
+ echo $str;
+ }
+ echo ' ';
+ }
+ }
+ }
+
+ /**
+ * Normalizes the slots in an SQL string.
+ * Replaces question mark slots with :slot1 :slot2 etc.
+ *
+ * @param string $sql sql to normalize
+ *
+ * @return string
+ */
+ protected function normalizeSlots( $sql )
+ {
+ $newSql = $sql;
+ $i = 0;
+ while(strpos($newSql, '?') !== FALSE ){
+ $pos = strpos( $newSql, '?' );
+ $slot = ':slot'.$i;
+ $begin = substr( $newSql, 0, $pos );
+ $end = substr( $newSql, $pos+1 );
+ if (PHP_SAPI === 'cli' && !self::$noCLI) {
+ $newSql = "{$begin}\e[32m{$slot}\e[39m{$end}";
+ } else {
+ $newSql = "{$begin}$slot{$end}";
+ }
+ $i ++;
+ }
+ return $newSql;
+ }
+
+ /**
+ * Normalizes the bindings.
+ * Replaces numeric binding keys with :slot1 :slot2 etc.
+ *
+ * @param array $bindings bindings to normalize
+ *
+ * @return array
+ */
+ protected function normalizeBindings( $bindings )
+ {
+ $i = 0;
+ $newBindings = array();
+ foreach( $bindings as $key => $value ) {
+ if ( is_numeric($key) ) {
+ $newKey = ':slot'.$i;
+ $newBindings[$newKey] = $value;
+ $i++;
+ } else {
+ $newBindings[$key] = $value;
+ }
+ }
+ return $newBindings;
+ }
+
+ /**
+ * Logger method.
+ *
+ * Takes a number of arguments tries to create
+ * a proper debug log based on the available data.
+ *
+ * @return void
+ */
+ public function log()
+ {
+ if ( func_num_args() < 1 ) return;
+
+ $sql = func_get_arg( 0 );
+
+ if ( func_num_args() < 2) {
+ $bindings = array();
+ } else {
+ $bindings = func_get_arg( 1 );
+ }
+
+ if ( !is_array( $bindings ) ) {
+ return $this->output( $sql );
+ }
+
+ $newSql = $this->normalizeSlots( $sql );
+ $newBindings = $this->normalizeBindings( $bindings );
+ $newStr = $this->writeQuery( $newSql, $newBindings );
+ $this->output( $newStr );
+ }
+
+ /**
+ * Sets the max string length for the parameter output in
+ * SQL queries. Set this value to a reasonable number to
+ * keep you SQL queries readable.
+ *
+ * @param integer $len string length
+ *
+ * @return self
+ */
+ public function setParamStringLength( $len = 20 )
+ {
+ $this->strLen = max(0, $len);
+ return $this;
+ }
+
+ /**
+ * Whether to bind all parameters as strings.
+ * If set to TRUE this will cause all integers to be bound as STRINGS.
+ * This will NOT affect NULL values.
+ *
+ * @param boolean $yesNo pass TRUE to bind all parameters as strings.
+ *
+ * @return self
+ */
+ public function setUseStringOnlyBinding( $yesNo = false )
+ {
+ $this->flagUseStringOnlyBinding = (boolean) $yesNo;
+ return $this;
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+/**
+ * Interface for database drivers.
+ * The Driver API conforms to the ADODB pseudo standard
+ * for database drivers.
+ *
+ * @file RedBeanPHP/Driver.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+interface Driver
+{
+ /**
+ * Runs a query and fetches results as a multi dimensional array.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings list of values to bind to SQL snippet
+ *
+ * @return array
+ */
+ public function GetAll( $sql, $bindings = array() );
+
+ /**
+ * Runs a query and fetches results as a column.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings list of values to bind to SQL snippet
+ *
+ * @return array
+ */
+ public function GetCol( $sql, $bindings = array() );
+
+ /**
+ * Runs a query and returns results as a single cell.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings list of values to bind to SQL snippet
+ *
+ * @return mixed
+ */
+ public function GetOne( $sql, $bindings = array() );
+
+ /**
+ * Runs a query and returns results as an associative array
+ * indexed by the first column.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings list of values to bind to SQL snippet
+ *
+ * @return array
+ */
+ public function GetAssocRow( $sql, $bindings = array() );
+
+ /**
+ * Runs a query and returns a flat array containing the values of
+ * one row.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings list of values to bind to SQL snippet
+ *
+ * @return array
+ */
+ public function GetRow( $sql, $bindings = array() );
+
+ /**
+ * Executes SQL code and allows key-value binding.
+ * This function allows you to provide an array with values to bind
+ * to query parameters. For instance you can bind values to question
+ * marks in the query. Each value in the array corresponds to the
+ * question mark in the query that matches the position of the value in the
+ * array. You can also bind values using explicit keys, for instance
+ * array(":key"=>123) will bind the integer 123 to the key :key in the
+ * SQL. This method has no return value.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings list of values to bind to SQL snippet
+ *
+ * @return int Affected Rows
+ */
+ public function Execute( $sql, $bindings = array() );
+
+ /**
+ * Returns the latest insert ID if driver does support this
+ * feature.
+ *
+ * @return integer
+ */
+ public function GetInsertID();
+
+ /**
+ * Returns the number of rows affected by the most recent query
+ * if the currently selected driver driver supports this feature.
+ *
+ * @return integer
+ */
+ public function Affected_Rows();
+
+ /**
+ * Returns a cursor-like object from the database.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings list of values to bind to SQL snippet
+ *
+ * @return Cursor
+ */
+ public function GetCursor( $sql, $bindings = array() );
+
+ /**
+ * Toggles debug mode. In debug mode the driver will print all
+ * SQL to the screen together with some information about the
+ * results.
+ *
+ * This method is for more fine-grained control. Normally
+ * you should use the facade to start the query debugger for
+ * you. The facade will manage the object wirings necessary
+ * to use the debugging functionality.
+ *
+ * Usage (through facade):
+ *
+ *
+ * R::debug( TRUE );
+ * ...rest of program...
+ * R::debug( FALSE );
+ *
+ *
+ * The example above illustrates how to use the RedBeanPHP
+ * query debugger through the facade.
+ *
+ * @param boolean $trueFalse turn on/off
+ * @param Logger $logger logger instance
+ *
+ * @return void
+ */
+ public function setDebugMode( $tf, $customLogger );
+
+ /**
+ * Commits a transaction.
+ *
+ * @return void
+ */
+ public function CommitTrans();
+
+ /**
+ * Starts a transaction.
+ *
+ * @return void
+ */
+ public function StartTrans();
+
+ /**
+ * Rolls back a transaction.
+ *
+ * @return void
+ */
+ public function FailTrans();
+
+ /**
+ * Resets the internal Query Counter.
+ *
+ * @return self
+ */
+ public function resetCounter();
+
+ /**
+ * Returns the number of SQL queries processed.
+ *
+ * @return integer
+ */
+ public function getQueryCount();
+
+ /**
+ * Sets initialization code for connection.
+ *
+ * @param callable|NULL $code code
+ *
+ * @return void
+ */
+ public function setInitCode( $code );
+
+ /**
+ * Returns the version string from the database server.
+ *
+ * @return string
+ */
+ public function DatabaseServerVersion();
+}
+}
+
+namespace RedBeanPHP\Driver {
+
+use RedBeanPHP\Driver as Driver;
+use RedBeanPHP\Logger as Logger;
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+use RedBeanPHP\RedException as RedException;
+use RedBeanPHP\RedException\SQL as SQL;
+use RedBeanPHP\Logger\RDefault as RDefault;
+use RedBeanPHP\PDOCompatible as PDOCompatible;
+use RedBeanPHP\Cursor\PDOCursor as PDOCursor;
+
+/**
+ * PDO Driver
+ * This Driver implements the RedBean Driver API.
+ * for RedBeanPHP. This is the standard / default database driver
+ * for RedBeanPHP.
+ *
+ * @file RedBeanPHP/PDO.php
+ * @author Gabor de Mooij and the RedBeanPHP Community, Desfrenes
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) Desfrenes & Gabor de Mooij and the RedBeanPHP community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class RPDO implements Driver
+{
+ /**
+ * @var integer
+ */
+ protected $max;
+
+ /**
+ * @var string
+ */
+ protected $dsn;
+
+ /**
+ * @var boolean
+ */
+ protected $loggingEnabled = FALSE;
+
+ /**
+ * @var Logger|NULL
+ */
+ protected $logger = NULL;
+
+ /**
+ * @var \PDO|NULL
+ */
+ protected $pdo;
+
+ /**
+ * @var integer
+ */
+ protected $affectedRows;
+
+ /**
+ * @var array
+ */
+ protected $resultArray;
+
+ /**
+ * @var array
+ */
+ protected $connectInfo = array();
+
+ /**
+ * @var boolean
+ */
+ protected $isConnected = FALSE;
+
+ /**
+ * @var bool
+ */
+ protected $flagUseStringOnlyBinding = FALSE;
+
+ /**
+ * @var integer
+ */
+ protected $queryCounter = 0;
+
+ /**
+ * @var string
+ */
+ protected $mysqlCharset = '';
+
+ /**
+ * @var string
+ */
+ protected $mysqlCollate = '';
+
+ /**
+ * @var boolean
+ */
+ protected $stringifyFetches = TRUE;
+
+ /**
+ * @var string|NULL
+ */
+ protected $initSQL = NULL;
+
+ /**
+ * @var callable|NULL
+ */
+ protected $initCode = NULL;
+
+ /**
+ * Binds parameters. This method binds parameters to a PDOStatement for
+ * Query Execution. This method binds parameters as NULL, INTEGER or STRING
+ * and supports both named keys and question mark keys.
+ *
+ * @param \PDOStatement $statement PDO Statement instance
+ * @param array $bindings values that need to get bound to the statement
+ *
+ * @return void
+ */
+ protected function bindParams( $statement, $bindings )
+ {
+ foreach ( $bindings as $key => &$value ) {
+ $k = is_integer( $key ) ? $key + 1 : $key;
+
+ if ( is_array( $value ) && count( $value ) == 2 ) {
+ $paramType = end( $value );
+ $value = reset( $value );
+ } else {
+ $paramType = NULL;
+ }
+
+ if ( is_null( $value ) ) {
+ $statement->bindValue( $k, NULL, \PDO::PARAM_NULL );
+ continue;
+ }
+
+ if ( $paramType != \PDO::PARAM_INT && $paramType != \PDO::PARAM_STR ) {
+ if ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) {
+ $paramType = \PDO::PARAM_INT;
+ } else {
+ $paramType = \PDO::PARAM_STR;
+ }
+ }
+
+ $statement->bindParam( $k, $value, $paramType );
+ }
+ }
+
+ /**
+ * This method runs the actual SQL query and binds a list of parameters to the query.
+ * slots. The result of the query will be stored in the protected property
+ * $rs (always array). The number of rows affected (result of rowcount, if supported by database)
+ * is stored in protected property $affectedRows. If the debug flag is set
+ * this function will send debugging output to screen buffer.
+ *
+ * @param string $sql the SQL string to be send to database server
+ * @param array $bindings the values that need to get bound to the query slots
+ * @param array $options
+ *
+ * @return mixed
+ * @throws SQL
+ */
+ public function runQuery( $sql, $bindings, $options = array() )
+ {
+ $this->connect();
+ if ( $this->loggingEnabled && $this->logger ) {
+ $this->logger->log( $sql, $bindings );
+ }
+ try {
+ if ( strpos( 'pgsql', $this->dsn ) === 0 ) {
+ 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);
+ }
+ } else {
+ $statement = $this->pdo->prepare( $sql );
+ }
+ $this->bindParams( $statement, $bindings );
+ $statement->execute();
+ $this->queryCounter ++;
+ $this->affectedRows = $statement->rowCount();
+ if ( isset( $options['noFetch'] ) && $options['noFetch'] ) {
+ $this->resultArray = array();
+ return $statement;
+ }
+ if ( $statement->columnCount() ) {
+ $fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL;
+ if ( is_null( $fetchStyle) ) {
+ $this->resultArray = $statement->fetchAll();
+ } else {
+ $this->resultArray = $statement->fetchAll( $fetchStyle );
+ }
+ if ( $this->loggingEnabled && $this->logger ) {
+ $this->logger->log( 'resultset: ' . count( $this->resultArray ) . ' rows' );
+ }
+ } else {
+ $this->resultArray = array();
+ }
+ } catch ( \PDOException $e ) {
+ //Unfortunately the code field is supposed to be int by default (php)
+ //So we need a property to convey the SQL State code.
+ $err = $e->getMessage();
+ if ( $this->loggingEnabled && $this->logger ) $this->logger->log( 'An error occurred: ' . $err );
+ $exception = new SQL( $err, 0, $e );
+ $exception->setSQLState( $e->getCode() );
+ $exception->setDriverDetails( $e->errorInfo );
+ throw $exception;
+ }
+ }
+
+ /**
+ * Try to fix MySQL character encoding problems.
+ * MySQL < 5.5.3 does not support proper 4 byte unicode but they
+ * seem to have added it with version 5.5.3 under a different label: utf8mb4.
+ * We try to select the best possible charset based on your version data.
+ *
+ * @return void
+ */
+ protected function setEncoding()
+ {
+ $driver = $this->pdo->getAttribute( \PDO::ATTR_DRIVER_NAME );
+ if ($driver === 'mysql') {
+ $charset = $this->hasCap( 'utf8mb4' ) ? 'utf8mb4' : 'utf8';
+ $collate = $this->hasCap( 'utf8mb4_520' ) ? '_unicode_520_ci' : '_unicode_ci';
+ $this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '. $charset ); //on every re-connect
+ /* #624 removed space before SET NAMES because it causes trouble with ProxySQL */
+ $this->pdo->exec('SET NAMES '. $charset); //also for current connection
+ $this->mysqlCharset = $charset;
+ $this->mysqlCollate = $charset . $collate;
+ }
+ }
+
+ /**
+ * Determine if a database supports a particular feature.
+ * Currently this function can be used to detect the following features:
+ *
+ * - utf8mb4
+ * - utf8mb4 520
+ *
+ * Usage:
+ *
+ *
+ * $this->hasCap( 'utf8mb4_520' );
+ *
+ *
+ * By default, RedBeanPHP uses this method under the hood to make sure
+ * you use the latest UTF8 encoding possible for your database.
+ *
+ * @param string $db_cap identifier of database capability
+ *
+ * @return int|false Whether the database feature is supported, FALSE otherwise.
+ **/
+ protected function hasCap( $db_cap )
+ {
+ $compare = FALSE;
+ $version = $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION );
+ switch ( strtolower( $db_cap ) ) {
+ case 'utf8mb4':
+ //oneliner, to boost code coverage (coverage does not span versions)
+ if ( version_compare( $version, '5.5.3', '<' ) ) { return FALSE; }
+ $client_version = $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
+ /*
+ * libmysql has supported utf8mb4 since 5.5.3, same as the MySQL server.
+ * mysqlnd has supported utf8mb4 since 5.0.9.
+ */
+ if ( strpos( $client_version, 'mysqlnd' ) !== FALSE ) {
+ $client_version = preg_replace( '/^\D+([\d.]+).*/', '$1', $client_version );
+ $compare = version_compare( $client_version, '5.0.9', '>=' );
+ } else {
+ $compare = version_compare( $client_version, '5.5.3', '>=' );
+ }
+ break;
+ case 'utf8mb4_520':
+ $compare = version_compare( $version, '5.6', '>=' );
+ break;
+ }
+
+ return $compare;
+ }
+
+ /**
+ * Constructor. You may either specify dsn, user and password or
+ * just give an existing PDO connection.
+ *
+ * Usage:
+ *
+ *
+ * $driver = new RPDO( $dsn, $user, $password );
+ *
+ *
+ * The example above illustrates how to create a driver
+ * instance from a database connection string (dsn), a username
+ * and a password. It's also possible to pass a PDO object.
+ *
+ * Usage:
+ *
+ *
+ * $driver = new RPDO( $existingConnection );
+ *
+ *
+ * The second example shows how to create an RPDO instance
+ * from an existing PDO object.
+ *
+ * @param string|\PDO $dsn database connection string
+ * @param string $user optional, username to sign in
+ * @param string $pass optional, password for connection login
+ *
+ * @return void
+ */
+ public function __construct( $dsn, $user = NULL, $pass = NULL, $options = array() )
+ {
+ if ( is_object( $dsn ) ) {
+ $this->pdo = $dsn;
+ $this->isConnected = TRUE;
+ $this->setEncoding();
+ $this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION );
+ $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC );
+ // make sure that the dsn at least contains the type
+ $this->dsn = $this->getDatabaseType();
+ } else {
+ $this->dsn = $dsn;
+ $this->connectInfo = array( 'pass' => $pass, 'user' => $user );
+ if (is_array($options)) $this->connectInfo['options'] = $options;
+ }
+
+ //PHP 5.3 PDO SQLite has a bug with large numbers:
+ if ( ( strpos( $this->dsn, 'sqlite' ) === 0 && PHP_MAJOR_VERSION === 5 && PHP_MINOR_VERSION === 3 ) || defined('HHVM_VERSION') || $this->dsn === 'test-sqlite-53' ) {
+ $this->max = 2147483647; //otherwise you get -2147483648 ?! demonstrated in build #603 on Travis.
+ } elseif ( strpos( $this->dsn, 'cubrid' ) === 0 ) {
+ $this->max = 2147483647; //bindParam in pdo_cubrid also fails...
+ } else {
+ $this->max = PHP_INT_MAX; //the normal value of course (makes it possible to use large numbers in LIMIT clause)
+ }
+ }
+
+ /**
+ * Sets PDO in stringify fetch mode.
+ * If set to TRUE, this method will make sure all data retrieved from
+ * the database will be fetched as a string. Default: TRUE.
+ *
+ * To set it to FALSE...
+ *
+ * Usage:
+ *
+ *
+ * R::getDatabaseAdapter()->getDatabase()->stringifyFetches( FALSE );
+ *
+ *
+ * Important!
+ * Note, this method only works if you set the value BEFORE the connection
+ * has been establish. Also, this setting ONLY works with SOME drivers.
+ * It's up to the driver to honour this setting.
+ *
+ * @param boolean $bool
+ */
+ public function stringifyFetches( $bool ) {
+ $this->stringifyFetches = $bool;
+ }
+
+ /**
+ * Returns the best possible encoding for MySQL based on version data.
+ * This method can be used to obtain the best character set parameters
+ * possible for your database when constructing a table creation query
+ * containing clauses like: CHARSET=... COLLATE=...
+ * This is a MySQL-specific method and not part of the driver interface.
+ *
+ * Usage:
+ *
+ *
+ * $charset_collate = $this->adapter->getDatabase()->getMysqlEncoding( TRUE );
+ *
+ *
+ * @param boolean $retCol pass TRUE to return both charset/collate
+ *
+ * @return string|array
+ */
+ public function getMysqlEncoding( $retCol = FALSE )
+ {
+ if( $retCol )
+ return array( 'charset' => $this->mysqlCharset, 'collate' => $this->mysqlCollate );
+ return $this->mysqlCharset;
+ }
+
+ /**
+ * Whether to bind all parameters as strings.
+ * If set to TRUE this will cause all integers to be bound as STRINGS.
+ * This will NOT affect NULL values.
+ *
+ * @param boolean $yesNo pass TRUE to bind all parameters as strings.
+ *
+ * @return void
+ */
+ public function setUseStringOnlyBinding( $yesNo )
+ {
+ $this->flagUseStringOnlyBinding = (boolean) $yesNo;
+ if ( $this->loggingEnabled && $this->logger && method_exists($this->logger,'setUseStringOnlyBinding')) {
+ $this->logger->setUseStringOnlyBinding( $this->flagUseStringOnlyBinding );
+ }
+ }
+
+ /**
+ * Sets the maximum value to be bound as integer, normally
+ * this value equals PHP's MAX INT constant, however sometimes
+ * PDO driver bindings cannot bind large integers as integers.
+ * This method allows you to manually set the max integer binding
+ * value to manage portability/compatibility issues among different
+ * PHP builds. This method will return the old value.
+ *
+ * @param integer $max maximum value for integer bindings
+ *
+ * @return integer
+ */
+ public function setMaxIntBind( $max )
+ {
+ if ( !is_integer( $max ) ) throw new RedException( 'Parameter has to be integer.' );
+ $oldMax = $this->max;
+ $this->max = $max;
+ return $oldMax;
+ }
+
+ /**
+ * Sets initialization code to execute upon connecting.
+ *
+ * @param callable|NULL $code
+ *
+ * @return void
+ */
+ public function setInitCode($code)
+ {
+ $this->initCode= $code;
+ }
+
+ /**
+ * Establishes a connection to the database using PHP\PDO
+ * functionality. If a connection has already been established this
+ * method will simply return directly. This method also turns on
+ * UTF8 for the database and PDO-ERRMODE-EXCEPTION as well as
+ * PDO-FETCH-ASSOC.
+ *
+ * @return void
+ */
+ public function connect()
+ {
+ if ( $this->isConnected ) return;
+ try {
+ $user = $this->connectInfo['user'];
+ $pass = $this->connectInfo['pass'];
+ $options = array();
+ if (isset($this->connectInfo['options']) && is_array($this->connectInfo['options'])) {
+ $options = $this->connectInfo['options'];
+ }
+ $this->pdo = new \PDO( $this->dsn, $user, $pass, $options );
+ $this->setEncoding();
+ $this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, $this->stringifyFetches );
+ //cant pass these as argument to constructor, CUBRID driver does not understand...
+ $this->pdo->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION );
+ $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC );
+ $this->isConnected = TRUE;
+ /* run initialisation query if any */
+ if ( $this->initSQL !== NULL ) {
+ $this->Execute( $this->initSQL );
+ $this->initSQL = NULL;
+ }
+ if ( $this->initCode !== NULL ) {
+ $code = $this->initCode;
+ $code( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) );
+ }
+ } catch ( \PDOException $exception ) {
+ $matches = array();
+ $dbname = ( preg_match( '/dbname=(\w+)/', $this->dsn, $matches ) ) ? $matches[1] : '?';
+ throw new \PDOException( 'Could not connect to database (' . $dbname . ').', $exception->getCode() );
+ }
+ }
+
+ /**
+ * Directly sets PDO instance into driver.
+ * This method might improve performance, however since the driver does
+ * not configure this instance terrible things may happen... only use
+ * this method if you are an expert on RedBeanPHP, PDO and UTF8 connections and
+ * you know your database server VERY WELL.
+ *
+ * - connected TRUE|FALSE (treat this instance as connected, default: TRUE)
+ * - setEncoding TRUE|FALSE (let RedBeanPHP set encoding for you, default: TRUE)
+ * - setAttributes TRUE|FALSE (let RedBeanPHP set attributes for you, default: TRUE)*
+ * - setDSNString TRUE|FALSE (extract DSN string from PDO instance, default: TRUE)
+ * - stringFetch TRUE|FALSE (whether you want to stringify fetches or not, default: TRUE)
+ * - runInitCode TRUE|FALSE (run init code if any, default: TRUE)
+ *
+ * *attributes:
+ * - RedBeanPHP will ask database driver to throw Exceptions on errors (recommended for compatibility)
+ * - RedBeanPHP will ask database driver to use associative arrays when fetching (recommended for compatibility)
+ *
+ * @param \PDO $pdo PDO instance
+ * @param array $options Options to apply
+ *
+ * @return void
+ */
+ public function setPDO( \PDO $pdo, $options = array() ) {
+ $this->pdo = $pdo;
+
+ $connected = TRUE;
+ $setEncoding = TRUE;
+ $setAttributes = TRUE;
+ $setDSNString = TRUE;
+ $runInitCode = TRUE;
+ $stringFetch = TRUE;
+
+ if ( isset($options['connected']) ) $connected = $options['connected'];
+ if ( isset($options['setEncoding']) ) $setEncoding = $options['setEncoding'];
+ if ( isset($options['setAttributes']) ) $setAttributes = $options['setAttributes'];
+ if ( isset($options['setDSNString']) ) $setDSNString = $options['setDSNString'];
+ if ( isset($options['runInitCode']) ) $runInitCode = $options['runInitCode'];
+ if ( isset($options['stringFetch']) ) $stringFetch = $options['stringFetch'];
+
+ if ($connected) $this->isConnected = $connected;
+ if ($setEncoding) $this->setEncoding();
+ if ($setAttributes) {
+ $this->pdo->setAttribute( \PDO::ATTR_ERRMODE,\PDO::ERRMODE_EXCEPTION );
+ $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE,\PDO::FETCH_ASSOC );
+ $this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, $stringFetch );
+ }
+ if ($runInitCode) {
+ /* run initialisation query if any */
+ if ( $this->initSQL !== NULL ) {
+ $this->Execute( $this->initSQL );
+ $this->initSQL = NULL;
+ }
+ if ( $this->initCode !== NULL ) {
+ $code = $this->initCode;
+ $code( $this->pdo->getAttribute( \PDO::ATTR_SERVER_VERSION ) );
+ }
+ }
+ if ($setDSNString) $this->dsn = $this->getDatabaseType();
+ }
+
+ /**
+ * @see Driver::GetAll
+ */
+ public function GetAll( $sql, $bindings = array() )
+ {
+ $this->runQuery( $sql, $bindings );
+
+ $result_array = $this->resultArray;
+ $this->resultArray = null;
+ return $result_array;
+ }
+
+ /**
+ * @see Driver::GetAssocRow
+ */
+ public function GetAssocRow( $sql, $bindings = array() )
+ {
+ $this->runQuery( $sql, $bindings, array(
+ 'fetchStyle' => \PDO::FETCH_ASSOC
+ )
+ );
+ $result_array = $this->resultArray;
+ $this->resultArray = null;
+ return $result_array;
+ }
+
+ /**
+ * @see Driver::GetCol
+ */
+ public function GetCol( $sql, $bindings = array() )
+ {
+ if (!defined('PDO::FETCH_COLUMN')) { //PHP 5.3
+ $rows = $this->GetAll( $sql, $bindings );
+ if ( empty( $rows ) || !is_array( $rows ) ) {
+ return array();
+ }
+ $cols = array();
+ foreach ( $rows as $row ) {
+ $cols[] = reset( $row );
+ }
+
+ $result_array = $cols;
+ $this->resultArray = null;
+ } else {
+ $this->runQuery( $sql, $bindings, array(
+ 'fetchStyle' => \PDO::FETCH_COLUMN
+ )
+ );
+ $result_array = $this->resultArray;
+ $this->resultArray = null;
+
+ if ( empty( $result_array ) || !is_array( $result_array ) ) {
+ return array();
+ }
+ }
+
+ return $result_array;
+ }
+
+ /**
+ * @see Driver::GetOne
+ */
+ public function GetOne( $sql, $bindings = array() )
+ {
+ $arr = $this->GetAll( $sql, $bindings );
+
+ if ( empty( $arr[0] ) || !is_array( $arr[0] ) ) {
+ return NULL;
+ }
+
+ return reset( $arr[0] );
+ }
+
+ /**
+ * Alias for getOne().
+ * Backward compatibility.
+ *
+ * @param string $sql SQL
+ * @param array $bindings bindings
+ *
+ * @return string|NULL
+ */
+ public function GetCell( $sql, $bindings = array() )
+ {
+ return $this->GetOne( $sql, $bindings );
+ }
+
+ /**
+ * @see Driver::GetRow
+ */
+ public function GetRow( $sql, $bindings = array() )
+ {
+ $arr = $this->GetAll( $sql, $bindings );
+
+ if ( is_array( $arr ) && count( $arr ) ) {
+ return reset( $arr );
+ }
+
+ return array();
+ }
+
+ /**
+ * @see Driver::Execute
+ */
+ public function Execute( $sql, $bindings = array() )
+ {
+ $this->runQuery( $sql, $bindings );
+ return $this->affectedRows;
+ }
+
+ /**
+ * @see Driver::GetInsertID
+ */
+ public function GetInsertID()
+ {
+ $this->connect();
+
+ return (int) $this->pdo->lastInsertId();
+ }
+
+ /**
+ * @see Driver::GetCursor
+ */
+ public function GetCursor( $sql, $bindings = array() )
+ {
+ $statement = $this->runQuery( $sql, $bindings, array( 'noFetch' => TRUE ) );
+ $cursor = new PDOCursor( $statement, \PDO::FETCH_ASSOC );
+ return $cursor;
+ }
+
+ /**
+ * @see Driver::Affected_Rows
+ */
+ public function Affected_Rows()
+ {
+ $this->connect();
+ return (int) $this->affectedRows;
+ }
+
+ /**
+ * @see Driver::setDebugMode
+ */
+ public function setDebugMode( $tf, $logger = NULL )
+ {
+ $this->connect();
+ $this->loggingEnabled = (bool) $tf;
+ if ( $this->loggingEnabled and !$logger ) {
+ $logger = new RDefault();
+ }
+ $this->setLogger( $logger );
+ }
+
+ /**
+ * Injects Logger object.
+ * Sets the logger instance you wish to use.
+ *
+ * This method is for more fine-grained control. Normally
+ * you should use the facade to start the query debugger for
+ * you. The facade will manage the object wirings necessary
+ * to use the debugging functionality.
+ *
+ * Usage (through facade):
+ *
+ *
+ * R::debug( TRUE );
+ * ...rest of program...
+ * R::debug( FALSE );
+ *
+ *
+ * The example above illustrates how to use the RedBeanPHP
+ * query debugger through the facade.
+ *
+ * @param Logger $logger the logger instance to be used for logging
+ *
+ * @return self
+ */
+ public function setLogger( Logger $logger )
+ {
+ $this->logger = $logger;
+ return $this;
+ }
+
+ /**
+ * Gets Logger object.
+ * Returns the currently active Logger instance.
+ *
+ * @return Logger
+ */
+ public function getLogger()
+ {
+ return $this->logger;
+ }
+
+ /**
+ * @see Driver::StartTrans
+ */
+ public function StartTrans()
+ {
+ $this->connect();
+ $this->pdo->beginTransaction();
+ }
+
+ /**
+ * @see Driver::CommitTrans
+ */
+ public function CommitTrans()
+ {
+ $this->connect();
+ $this->pdo->commit();
+ }
+
+ /**
+ * @see Driver::FailTrans
+ */
+ public function FailTrans()
+ {
+ $this->connect();
+ $this->pdo->rollback();
+ }
+
+ /**
+ * Returns the name of database driver for PDO.
+ * Uses the PDO attribute DRIVER NAME to obtain the name of the
+ * PDO driver. Use this method to identify the current PDO driver
+ * used to provide access to the database. Example of a database
+ * driver string:
+ *
+ *
+ * mysql
+ *
+ *
+ * Usage:
+ *
+ *
+ * echo R::getDatabaseAdapter()->getDatabase()->getDatabaseType();
+ *
+ *
+ * The example above prints the current database driver string to
+ * stdout.
+ *
+ * Note that this is a driver-specific method, not part of the
+ * driver interface. This method might not be available in other
+ * drivers since it relies on PDO.
+ *
+ * @return string
+ */
+ public function getDatabaseType()
+ {
+ $this->connect();
+ return $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME );
+ }
+
+ /**
+ * Returns the version identifier string of the database client.
+ * This method can be used to identify the currently installed
+ * database client. Note that this method will also establish a connection
+ * (because this is required to obtain the version information).
+ *
+ * Example of a version string:
+ *
+ *
+ * mysqlnd 5.0.12-dev - 20150407 - $Id: b5c5906d452ec590732a93b051f3827e02749b83 $
+ *
+ *
+ * Usage:
+ *
+ *
+ * echo R::getDatabaseAdapter()->getDatabase()->getDatabaseVersion();
+ *
+ *
+ * The example above will print the version string to stdout.
+ *
+ * Note that this is a driver-specific method, not part of the
+ * driver interface. This method might not be available in other
+ * drivers since it relies on PDO.
+ *
+ * To obtain the database server version, use getDatabaseServerVersion()
+ * instead.
+ *
+ * @return mixed
+ */
+ public function getDatabaseVersion()
+ {
+ $this->connect();
+ return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION );
+ }
+
+ /**
+ * Returns the underlying PHP PDO instance.
+ * For some low-level database operations you'll need access to the PDO
+ * object. Not that this method is only available in RPDO and other
+ * PDO based database drivers for RedBeanPHP. Other drivers may not have
+ * a method like this. The following example demonstrates how to obtain
+ * a reference to the PDO instance from the facade:
+ *
+ * Usage:
+ *
+ *
+ * $pdo = R::getDatabaseAdapter()->getDatabase()->getPDO();
+ *
+ *
+ * @return \PDO
+ */
+ public function getPDO()
+ {
+ $this->connect();
+ return $this->pdo;
+ }
+
+ /**
+ * Closes the database connection.
+ * While database connections are closed automatically at the end of the PHP script,
+ * closing database connections is generally recommended to improve performance.
+ * Closing a database connection will immediately return the resources to PHP.
+ *
+ * Usage:
+ *
+ *
+ * R::setup( ... );
+ * ... do stuff ...
+ * R::close();
+ *
+ *
+ * @return void
+ */
+ public function close()
+ {
+ $this->pdo = NULL;
+ $this->isConnected = FALSE;
+ }
+
+ /**
+ * Returns TRUE if the current PDO instance is connected.
+ *
+ * @return boolean
+ */
+ public function isConnected()
+ {
+ return $this->isConnected && $this->pdo;
+ }
+
+ /**
+ * Toggles logging, enables or disables logging.
+ *
+ * @param boolean $enable TRUE to enable logging
+ *
+ * @return self
+ */
+ public function setEnableLogging( $enable )
+ {
+ $this->loggingEnabled = (boolean) $enable;
+ return $this;
+ }
+
+ /**
+ * Resets the query counter.
+ * The query counter can be used to monitor the number
+ * of database queries that have
+ * been processed according to the database driver. You can use this
+ * to monitor the number of queries required to render a page.
+ *
+ * Usage:
+ *
+ *
+ * R::resetQueryCount();
+ * echo R::getQueryCount() . ' queries processed.';
+ *
+ *
+ * @return self
+ */
+ public function resetCounter()
+ {
+ $this->queryCounter = 0;
+ return $this;
+ }
+
+ /**
+ * Returns the number of SQL queries processed.
+ * This method returns the number of database queries that have
+ * been processed according to the database driver. You can use this
+ * to monitor the number of queries required to render a page.
+ *
+ * Usage:
+ *
+ *
+ * echo R::getQueryCount() . ' queries processed.';
+ *
+ *
+ * @return integer
+ */
+ public function getQueryCount()
+ {
+ return $this->queryCounter;
+ }
+
+ /**
+ * Returns the maximum value treated as integer parameter
+ * binding.
+ *
+ * This method is mainly for testing purposes but it can help
+ * you solve some issues relating to integer bindings.
+ *
+ * @return integer
+ */
+ public function getIntegerBindingMax()
+ {
+ return $this->max;
+ }
+
+ /**
+ * Sets a query to be executed upon connecting to the database.
+ * This method provides an opportunity to configure the connection
+ * to a database through an SQL-based interface. Objects can provide
+ * an SQL string to be executed upon establishing a connection to
+ * the database. This has been used to solve issues with default
+ * foreign key settings in SQLite3 for instance, see Github issues:
+ * #545 and #548.
+ *
+ * @param string $sql SQL query to run upon connecting to database
+ *
+ * @return self
+ */
+ public function setInitQuery( $sql ) {
+ $this->initSQL = $sql;
+ return $this;
+ }
+
+ /**
+ * Returns the version string from the database server.
+ *
+ * @return string
+ */
+ public function DatabaseServerVersion() {
+ return trim( strval( $this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION) ) );
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+use RedBeanPHP\BeanHelper as BeanHelper;
+use RedBeanPHP\RedException as RedException;
+use RedBeanPHP\Util\Either as Either;
+
+/**
+ * PHP 5.3 compatibility
+ * We extend JsonSerializable to avoid namespace conflicts,
+ * can't define interface with special namespace in PHP
+ */
+if (interface_exists('\JsonSerializable')) { interface Jsonable extends \JsonSerializable {}; } else { interface Jsonable {}; }
+
+/**
+ * OODBBean (Object Oriented DataBase Bean).
+ *
+ * to exchange information with the database. A bean represents
+ * a single table row and offers generic services for interaction
+ * with databases systems as well as some meta-data.
+ *
+ * @file RedBeanPHP/OODBBean.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ * @desc OODBBean represents a bean. RedBeanPHP uses beans
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class OODBBean implements \IteratorAggregate,\ArrayAccess,\Countable,Jsonable
+{
+ /**
+ * FUSE error modes.
+ */
+ const C_ERR_IGNORE = FALSE;
+ const C_ERR_LOG = 1;
+ const C_ERR_NOTICE = 2;
+ const C_ERR_WARN = 3;
+ const C_ERR_EXCEPTION = 4;
+ const C_ERR_FUNC = 5;
+ const C_ERR_FATAL = 6;
+
+ /**
+ * @var boolean
+ */
+ protected static $useFluidCount = FALSE;
+
+ /**
+ * @var boolean
+ */
+ protected static $convertArraysToJSON = FALSE;
+
+ /**
+ * @var boolean
+ */
+ protected static $errorHandlingFUSE = FALSE;
+
+ /**
+ * @var callable|NULL
+ */
+ protected static $errorHandler = NULL;
+
+ /**
+ * @var array
+ */
+ protected static $aliases = array();
+
+ /**
+ * If this is set to TRUE, the __toString function will
+ * encode all properties as UTF-8 to repair invalid UTF-8
+ * encodings and prevent exceptions (which are uncatchable from within
+ * a __toString-function).
+ *
+ * @var boolean
+ */
+ protected static $enforceUTF8encoding = FALSE;
+
+ /**
+ * This is where the real properties of the bean live. They are stored and retrieved
+ * by the magic getter and setter (__get and __set).
+ *
+ * @var array $properties
+ */
+ protected $properties = array();
+
+ /**
+ * Here we keep the meta data of a bean.
+ *
+ * @var array
+ */
+ protected $__info = array();
+
+ /**
+ * The BeanHelper allows the bean to access the toolbox objects to implement
+ * rich functionality, otherwise you would have to do everything with R or
+ * external objects.
+ *
+ * @var BeanHelper|NULL
+ */
+ protected $beanHelper = NULL;
+
+ /**
+ * @var string|NULL
+ */
+ protected $fetchType = NULL;
+
+ /**
+ * @var string
+ */
+ protected $withSql = '';
+
+ /**
+ * @var array
+ */
+ protected $withParams = array();
+
+ /**
+ * @var string|NULL
+ */
+ protected $aliasName = NULL;
+
+ /**
+ * @var string|NULL
+ */
+ protected $via = NULL;
+
+ /**
+ * @var boolean
+ */
+ protected $noLoad = FALSE;
+
+ /**
+ * @var boolean
+ */
+ protected $all = FALSE;
+
+ /**
+ * @var string|NULL
+ */
+ protected $castProperty = NULL;
+
+ /**
+ * If fluid count is set to TRUE then $bean->ownCount() will
+ * return 0 if the table does not exists.
+ * Only for backward compatibility.
+ * Returns previous value.
+ *
+ * @param boolean $toggle toggle
+ *
+ * @return boolean
+ */
+ public static function useFluidCount( $toggle )
+ {
+ $old = self::$useFluidCount;
+ self::$useFluidCount = $toggle;
+ return $old;
+ }
+
+ /**
+ * If this is set to TRUE, the __toString function will
+ * encode all properties as UTF-8 to repair invalid UTF-8
+ * encodings and prevent exceptions (which are uncatchable from within
+ * a __toString-function).
+ *
+ * @param boolean $toggle TRUE to enforce UTF-8 encoding (slower)
+ *
+ * @return void
+ */
+ public static function setEnforceUTF8encoding( $toggle )
+ {
+ self::$enforceUTF8encoding = (boolean) $toggle;
+ }
+
+ /**
+ * Sets the error mode for FUSE.
+ * What to do if a FUSE model method does not exist?
+ * You can set the following options:
+ *
+ * * OODBBean::C_ERR_IGNORE (default), ignores the call, returns NULL
+ * * OODBBean::C_ERR_LOG, logs the incident using error_log
+ * * OODBBean::C_ERR_NOTICE, triggers a E_USER_NOTICE
+ * * OODBBean::C_ERR_WARN, triggers a E_USER_WARNING
+ * * OODBBean::C_ERR_EXCEPTION, throws an exception
+ * * OODBBean::C_ERR_FUNC, allows you to specify a custom handler (function)
+ * * OODBBean::C_ERR_FATAL, triggers a E_USER_ERROR
+ *
+ *
+ * Custom handler method signature: handler( array (
+ * 'message' => string
+ * 'bean' => OODBBean
+ * 'method' => string
+ * ) )
+ *
+ *
+ * This method returns the old mode and handler as an array.
+ *
+ * @param integer $mode error handling mode
+ * @param callable|NULL $func custom handler
+ *
+ * @return array
+ */
+ public static function setErrorHandlingFUSE($mode, $func = NULL) {
+ if (
+ $mode !== self::C_ERR_IGNORE
+ && $mode !== self::C_ERR_LOG
+ && $mode !== self::C_ERR_NOTICE
+ && $mode !== self::C_ERR_WARN
+ && $mode !== self::C_ERR_EXCEPTION
+ && $mode !== self::C_ERR_FUNC
+ && $mode !== self::C_ERR_FATAL
+ ) throw new \Exception( 'Invalid error mode selected' );
+
+ if ( $mode === self::C_ERR_FUNC && !is_callable( $func ) ) {
+ throw new \Exception( 'Invalid error handler' );
+ }
+
+ $old = array( self::$errorHandlingFUSE, self::$errorHandler );
+ self::$errorHandlingFUSE = $mode;
+ if ( is_callable( $func ) ) {
+ self::$errorHandler = $func;
+ } else {
+ self::$errorHandler = NULL;
+ }
+ return $old;
+ }
+
+ /**
+ * Toggles array to JSON conversion. If set to TRUE any array
+ * set to a bean property that's not a list will be turned into
+ * a JSON string. Used together with AQueryWriter::useJSONColumns this
+ * extends the data type support for JSON columns. Returns the previous
+ * value of the flag.
+ *
+ * @param boolean $flag flag
+ *
+ * @return boolean
+ */
+ public static function convertArraysToJSON( $flag )
+ {
+ $old = self::$convertArraysToJSON;
+ self::$convertArraysToJSON = $flag;
+ return $old;
+ }
+
+ /**
+ * Sets global aliases.
+ * Registers a batch of aliases in one go. This works the same as
+ * fetchAs and setAutoResolve but explicitly. For instance if you register
+ * the alias 'cover' for 'page' a property containing a reference to a
+ * page bean called 'cover' will correctly return the page bean and not
+ * a (non-existent) cover bean.
+ *
+ *
+ * R::aliases( array( 'cover' => 'page' ) );
+ * $book = R::dispense( 'book' );
+ * $page = R::dispense( 'page' );
+ * $book->cover = $page;
+ * R::store( $book );
+ * $book = $book->fresh();
+ * $cover = $book->cover;
+ * echo $cover->getMeta( 'type' ); //page
+ *
+ *
+ * The format of the aliases registration array is:
+ *
+ * {alias} => {actual type}
+ *
+ * In the example above we use:
+ *
+ * cover => page
+ *
+ * From that point on, every bean reference to a cover
+ * will return a 'page' bean. Note that with autoResolve this
+ * feature along with fetchAs() is no longer very important, although
+ * relying on explicit aliases can be a bit faster.
+ *
+ * @param array $list list of global aliases to use
+ *
+ * @return void
+ */
+ public static function aliases( $list )
+ {
+ self::$aliases = $list;
+ }
+
+ /**
+ * Return list of global aliases
+ *
+ * @return array
+ */
+ public static function getAliases()
+ {
+ return self::$aliases;
+ }
+
+ /**
+ * Sets a meta property for all beans. This is a quicker way to set
+ * the meta properties for a collection of beans because this method
+ * can directly access the property arrays of the beans.
+ * This method returns the beans.
+ *
+ * @param array $beans beans to set the meta property of
+ * @param string $property property to set
+ * @param mixed $value value
+ *
+ * @return array
+ */
+ public static function setMetaAll( $beans, $property, $value )
+ {
+ foreach( $beans as $bean ) {
+ if ( $bean instanceof OODBBean ) $bean->__info[ $property ] = $value;
+ if ( $property == 'type' && !empty($bean->beanHelper)) {
+ $bean->__info['model'] = $bean->beanHelper->getModelForBean( $bean );
+ }
+ }
+ return $beans;
+ }
+
+ /**
+ * Accesses the shared list of a bean.
+ * To access beans that have been associated with the current bean
+ * using a many-to-many relationship use sharedXList where
+ * X is the type of beans in the list.
+ *
+ * Usage:
+ *
+ *
+ * $person = R::load( 'person', $id );
+ * $friends = $person->sharedFriendList;
+ *
+ *
+ * The code snippet above demonstrates how to obtain all beans of
+ * type 'friend' that have associated using an N-M relation.
+ * This is a private method used by the magic getter / accessor.
+ * The example illustrates usage through these accessors.
+ *
+ * @param string $type the name of the list you want to retrieve
+ * @param OODB $redbean instance of the RedBeanPHP OODB class
+ * @param ToolBox $toolbox instance of ToolBox (to get access to core objects)
+ *
+ * @return array
+ */
+ private function getSharedList( $type, $redbean, $toolbox )
+ {
+ $writer = $toolbox->getWriter();
+ if ( $this->via ) {
+ $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
+ if ( $oldName !== $this->via ) {
+ //set the new renaming rule
+ $writer->renameAssocTable( $oldName, $this->via );
+ }
+ $this->via = NULL;
+ }
+ $beans = array();
+ if ($this->getID()) {
+ $type = $this->beau( $type );
+ $assocManager = $redbean->getAssociationManager();
+ $beans = $assocManager->related( $this, $type, $this->withSql, $this->withParams );
+ }
+ return $beans;
+ }
+
+ /**
+ * Accesses the ownList. The 'own' list contains beans
+ * associated using a one-to-many relation. The own-lists can
+ * be accessed through the magic getter/setter property
+ * ownXList where X is the type of beans in that list.
+ *
+ * Usage:
+ *
+ *
+ * $book = R::load( 'book', $id );
+ * $pages = $book->ownPageList;
+ *
+ *
+ * The example above demonstrates how to access the
+ * pages associated with the book. Since this is a private method
+ * meant to be used by the magic accessors, the example uses the
+ * magic getter instead.
+ *
+ * @param string $type name of the list you want to retrieve
+ * @param OODB $oodb The RB OODB object database instance
+ *
+ * @return array
+ */
+ private function getOwnList( $type, $redbean )
+ {
+ $type = $this->beau( $type );
+ if ( $this->aliasName ) {
+ $parentField = $this->aliasName;
+ $myFieldLink = $parentField . '_id';
+
+ $this->__info['sys.alias.' . $type] = $this->aliasName;
+
+ $this->aliasName = NULL;
+ } else {
+ $parentField = $this->__info['type'];
+ $myFieldLink = $parentField . '_id';
+ }
+ $beans = array();
+ if ( $this->getID() ) {
+ reset( $this->withParams );
+ $firstKey = count( $this->withParams ) > 0
+ ? key( $this->withParams )
+ : 0;
+ if ( is_int( $firstKey ) ) {
+ $sql = "{$myFieldLink} = ? {$this->withSql}";
+ $bindings = array_merge( array( $this->getID() ), $this->withParams );
+ } else {
+ $sql = "{$myFieldLink} = :slot0 {$this->withSql}";
+ $bindings = $this->withParams;
+ $bindings[':slot0'] = $this->getID();
+ }
+ $beans = $redbean->find( $type, array(), $sql, $bindings );
+ }
+ foreach ( $beans as $beanFromList ) {
+ $beanFromList->__info['sys.parentcache.' . $parentField] = $this;
+ }
+ return $beans;
+ }
+
+ /**
+ * Initializes a bean. Used by OODB for dispensing beans.
+ * It is not recommended to use this method to initialize beans. Instead
+ * use the OODB object to dispense new beans. You can use this method
+ * if you build your own bean dispensing mechanism.
+ * This is not recommended.
+ *
+ * Unless you know what you are doing, do NOT use this method.
+ * This is for advanced users only!
+ *
+ * @param string $type type of the new bean
+ * @param BeanHelper|NULL $beanhelper bean helper to obtain a toolbox and a model
+ *
+ * @return void
+ */
+ public function initializeForDispense( $type, $beanhelper = NULL )
+ {
+ $this->beanHelper = $beanhelper;
+ $this->__info['type'] = $type;
+ $this->__info['sys.id'] = 'id';
+ $this->__info['sys.orig'] = array( 'id' => 0 );
+ $this->__info['tainted'] = TRUE;
+ $this->__info['changed'] = TRUE;
+ $this->__info['changelist'] = array();
+ if ( $beanhelper ) {
+ $this->__info['model'] = $this->beanHelper->getModelForBean( $this );
+ }
+ $this->properties['id'] = 0;
+ }
+
+ /**
+ * Sets the Bean Helper. Normally the Bean Helper is set by OODB.
+ * Here you can change the Bean Helper. The Bean Helper is an object
+ * providing access to a toolbox for the bean necessary to retrieve
+ * nested beans (bean lists: ownBean, sharedBean) without the need to
+ * rely on static calls to the facade (or make this class dep. on OODB).
+ *
+ * @param BeanHelper $helper helper to use for this bean
+ *
+ * @return void
+ */
+ public function setBeanHelper( BeanHelper $helper )
+ {
+ $this->beanHelper = $helper;
+ }
+
+ /**
+ * Returns an ArrayIterator so you can treat the bean like
+ * an array with the properties container as its contents.
+ * This method is meant for PHP and allows you to access beans as if
+ * they were arrays, i.e. using array notation:
+ *
+ *
+ * $bean[$key] = $value;
+ *
+ *
+ * Note that not all PHP functions work with the array interface.
+ *
+ * @return \ArrayIterator
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ return new \ArrayIterator( $this->properties );
+ }
+
+ /**
+ * Imports all values from an associative array $array. Chainable.
+ * This method imports the values in the first argument as bean
+ * property and value pairs. Use the second parameter to provide a
+ * selection. If a selection array is passed, only the entries
+ * having keys mentioned in the selection array will be imported.
+ * Set the third parameter to TRUE to preserve spaces in selection keys.
+ *
+ * @param array $array what you want to import
+ * @param string|array $selection selection of values
+ * @param boolean $notrim if TRUE selection keys will NOT be trimmed
+ *
+ * @return OODBBean
+ */
+ public function import( $array, $selection = FALSE, $notrim = FALSE )
+ {
+ if ( is_string( $selection ) ) {
+ $selection = explode( ',', $selection );
+ }
+ if ( is_array( $selection ) ) {
+ if ( $notrim ) {
+ $selected = array_flip($selection);
+ } else {
+ $selected = array();
+ foreach ( $selection as $key => $select ) {
+ $selected[trim( $select )] = TRUE;
+ }
+ }
+ } else {
+ $selected = FALSE;
+ }
+ foreach ( $array as $key => $value ) {
+ if ( $key != '__info' ) {
+ if ( !$selected || isset( $selected[$key] ) ) {
+ if ( is_array($value ) ) {
+ if ( isset( $value['_type'] ) ) {
+ $bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $value['_type'] );
+ unset( $value['_type'] );
+ $bean->import($value);
+ $this->$key = $bean;
+ } else {
+ $listBeans = array();
+ foreach( $value as $listKey => $listItem ) {
+ $bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $listItem['_type'] );
+ unset( $listItem['_type'] );
+ $bean->import($listItem);
+ $list = &$this->$key;
+ $list[ $listKey ] = $bean;
+ }
+ }
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Same as import() but trims all values by default.
+ * Set the second parameter to apply a different function.
+ *
+ * @param array $array what you want to import
+ * @param callable $function function to apply (default is trim)
+ * @param string|array $selection selection of values
+ * @param boolean $notrim if TRUE selection keys will NOT be trimmed
+ *
+ * @return OODBBean
+ */
+ public function trimport( $array, $function='trim', $selection = FALSE, $notrim = FALSE ) {
+ return $this->import( array_map( $function, $array ), $selection, $notrim );
+ }
+
+ /**
+ * Imports an associative array directly into the
+ * internal property array of the bean as well as the
+ * meta property sys.orig and sets the changed flag to FALSE.
+ * This is used by the repository objects to inject database rows
+ * into the beans. It is not recommended to use this method outside
+ * of a bean repository.
+ *
+ * @param array $row a database row
+ *
+ * @return self
+ */
+ public function importRow( $row )
+ {
+ $this->properties = $row;
+ $this->__info['sys.orig'] = $row;
+ $this->__info['changed'] = FALSE;
+ $this->__info['changelist'] = array();
+ return $this;
+ }
+
+ /**
+ * Imports data from another bean. Chainable.
+ * Copies the properties from the source bean to the internal
+ * property list.
+ *
+ * Usage:
+ *
+ *
+ * $copy->importFrom( $bean );
+ *
+ *
+ * The example above demonstrates how to make a shallow copy
+ * of a bean using the importFrom() method.
+ *
+ * @param OODBBean $sourceBean the source bean to take properties from
+ *
+ * @return OODBBean
+ */
+ public function importFrom( OODBBean $sourceBean )
+ {
+ $this->__info['tainted'] = TRUE;
+ $this->__info['changed'] = TRUE;
+ $this->properties = $sourceBean->properties;
+
+ return $this;
+ }
+
+ /**
+ * Injects the properties of another bean but keeps the original ID.
+ * Just like import() but keeps the original ID.
+ * Chainable.
+ *
+ * @param OODBBean $otherBean the bean whose properties you would like to copy
+ *
+ * @return OODBBean
+ */
+ public function inject( OODBBean $otherBean )
+ {
+ $myID = $this->properties['id'];
+ $this->import( $otherBean->export( FALSE, FALSE, TRUE ) );
+ $this->id = $myID;
+
+ return $this;
+ }
+
+ /**
+ * Exports the bean as an array.
+ * This function exports the contents of a bean to an array and returns
+ * the resulting array. Depending on the parameters you can also
+ * export an entire graph of beans, apply filters or exclude meta data.
+ *
+ * Usage:
+ *
+ *
+ * $bookData = $book->export( TRUE, TRUE, FALSE, [ 'author' ] );
+ *
+ *
+ * The example above exports all bean properties to an array
+ * called $bookData including its meta data, parent objects and includes only
+ * beans of type 'author'.
+ *
+ * @param boolean $meta set to TRUE if you want to export meta data as well
+ * @param boolean $parents set to TRUE if you want to export parents as well
+ * @param boolean $onlyMe set to TRUE if you want to export only this bean
+ * @param array $filters optional whitelist for export
+ *
+ * @return array
+ */
+ public function export( $meta = FALSE, $parents = FALSE, $onlyMe = FALSE, $filters = array() )
+ {
+ $arr = array();
+ if ( $parents ) {
+ foreach ( $this as $key => $value ) {
+ if ( substr( $key, -3 ) != '_id' ) continue;
+
+ $prop = substr( $key, 0, strlen( $key ) - 3 );
+ $this->$prop;
+ }
+ }
+ $hasFilters = is_array( $filters ) && count( $filters );
+ foreach ( $this as $key => $value ) {
+ if ( !$onlyMe && is_array( $value ) ) {
+ $vn = array();
+
+ foreach ( $value as $i => $b ) {
+ if ( !( $b instanceof OODBBean ) ) continue;
+ $vn[] = $b->export( $meta, FALSE, FALSE, $filters );
+ $value = $vn;
+ }
+ } elseif ( $value instanceof OODBBean ) { if ( $hasFilters ) { //has to be on one line, otherwise code coverage miscounts as miss
+ if ( !in_array( strtolower( $value->getMeta( 'type' ) ), $filters ) ) continue;
+ }
+ $value = $value->export( $meta, $parents, FALSE, $filters );
+ }
+ $arr[$key] = $value;
+ }
+ if ( $meta ) {
+ $arr['__info'] = $this->__info;
+ }
+ return $arr;
+ }
+
+ /**
+ * Implements isset() function for use as an array.
+ * This allows you to use isset() on bean properties.
+ *
+ * Usage:
+ *
+ *
+ * $book->title = 'my book';
+ * echo isset($book['title']); //TRUE
+ *
+ *
+ * The example illustrates how one can apply the
+ * isset() function to a bean.
+ *
+ * @param string $property name of the property you want to check
+ *
+ * @return boolean
+ */
+ public function __isset( $property )
+ {
+ $property = $this->beau( $property );
+ if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
+ $property = substr($property, 1);
+ }
+ return isset( $this->properties[$property] );
+ }
+
+ /**
+ * Checks whether a related bean exists.
+ * For instance if a post bean has a related author, this method
+ * can be used to check if the author is set without loading the author.
+ * This method works by checking the related ID-field.
+ *
+ * @param string $property name of the property you wish to check
+ *
+ * @return boolean
+ */
+ public function exists( $property )
+ {
+ $property = $this->beau( $property );
+ /* fixes issue #549, see Base/Bean test */
+ $hiddenRelationField = "{$property}_id";
+ if ( array_key_exists( $hiddenRelationField, $this->properties ) ) {
+ if ( !is_null( $this->properties[$hiddenRelationField] ) ) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+
+ /**
+ * Returns the ID of the bean.
+ * If for some reason the ID has not been set, this method will
+ * return NULL. This is actually the same as accessing the
+ * id property using $bean->id. The ID of a bean is its primary
+ * key and should always correspond with a table column named
+ * 'id'.
+ *
+ * @return string|NULL
+ */
+ public function getID()
+ {
+ return ( isset( $this->properties['id'] ) ) ? (string) $this->properties['id'] : NULL;
+ }
+
+ /**
+ * Unsets a property of a bean.
+ * Magic method, gets called implicitly when
+ * performing the unset() operation
+ * on a bean property.
+ *
+ * @param string $property property to unset
+ *
+ * @return void
+ */
+ public function __unset( $property )
+ {
+ $property = $this->beau( $property );
+
+ if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
+ $property = substr($property, 1);
+ }
+ unset( $this->properties[$property] );
+ $shadowKey = 'sys.shadow.'.$property;
+ if ( isset( $this->__info[ $shadowKey ] ) ) unset( $this->__info[$shadowKey] );
+ //also clear modifiers
+ $this->clearModifiers();
+ return;
+ }
+
+ /**
+ * Returns the bean wrapped in an Either-instance.
+ * This allows the user to extract data from the bean using a chain
+ * of methods without any NULL checks, similar to the ?? operator but also
+ * in a way that is compatible with older versions of PHP.
+ * For more details consult the documentation of the Either class.
+ *
+ * @return Either
+ */
+ public function either() {
+ return new Either( $this );
+ }
+
+ /**
+ * Adds WHERE clause conditions to ownList retrieval.
+ * For instance to get the pages that belong to a book you would
+ * issue the following command: $book->ownPage
+ * However, to order these pages by number use:
+ *
+ *
+ * $book->with(' ORDER BY `number` ASC ')->ownPage
+ *
+ *
+ * the additional SQL snippet will be merged into the final
+ * query.
+ *
+ * @param string $sql SQL to be added to retrieval query.
+ * @param array $bindings array with parameters to bind to SQL snippet
+ *
+ * @return OODBBean
+ */
+ public function with( $sql, $bindings = array() )
+ {
+ $this->withSql = $sql;
+ $this->withParams = $bindings;
+ return $this;
+ }
+
+ /**
+ * Just like with(). Except that this method prepends the SQL query snippet
+ * with AND which makes it slightly more comfortable to use a conditional
+ * SQL snippet. For instance to filter an own-list with pages (belonging to
+ * a book) on specific chapters you can use:
+ *
+ * $book->withCondition(' chapter = 3 ')->ownPage
+ *
+ * This will return in the own list only the pages having 'chapter == 3'.
+ *
+ * @param string $sql SQL to be added to retrieval query (prefixed by AND)
+ * @param array $bindings array with parameters to bind to SQL snippet
+ *
+ * @return OODBBean
+ */
+ public function withCondition( $sql, $bindings = array() )
+ {
+ $this->withSql = ' AND ' . $sql;
+ $this->withParams = $bindings;
+ return $this;
+ }
+
+ /**
+ * Tells the bean to (re)load the following list without any
+ * conditions. If you have an ownList or sharedList with a
+ * condition you can use this method to reload the entire list.
+ *
+ * Usage:
+ *
+ *
+ * $bean->with( ' LIMIT 3 ' )->ownPage; //Just 3
+ * $bean->all()->ownPage; //Reload all pages
+ *
+ *
+ * @return self
+ */
+ public function all()
+ {
+ $this->all = TRUE;
+ return $this;
+ }
+
+ /**
+ * Tells the bean to only access the list but not load
+ * its contents. Use this if you only want to add something to a list
+ * and you have no interest in retrieving its contents from the database.
+ *
+ * Usage:
+ *
+ *
+ * $book->noLoad()->ownPage[] = $newPage;
+ *
+ *
+ * In the example above we add the $newPage bean to the
+ * page list of book without loading all the pages first.
+ * If you know in advance that you are not going to use
+ * the contents of the list, you may use the noLoad() modifier
+ * to make sure the queries required to load the list will not
+ * be executed.
+ *
+ * @return self
+ */
+ public function noLoad()
+ {
+ $this->noLoad = TRUE;
+ return $this;
+ }
+
+ /**
+ * Prepares an own-list to use an alias. This is best explained using
+ * an example. Imagine a project and a person. The project always involves
+ * two persons: a teacher and a student. The person beans have been aliased in this
+ * case, so to the project has a teacher_id pointing to a person, and a student_id
+ * also pointing to a person. Given a project, we obtain the teacher like this:
+ *
+ *
+ * $project->fetchAs('person')->teacher;
+ *
+ *
+ * Now, if we want all projects of a teacher we cant say:
+ *
+ *
+ * $teacher->ownProject
+ *
+ *
+ * because the $teacher is a bean of type 'person' and no project has been
+ * assigned to a person. Instead we use the alias() method like this:
+ *
+ *
+ * $teacher->alias('teacher')->ownProject
+ *
+ *
+ * now we get the projects associated with the person bean aliased as
+ * a teacher.
+ *
+ * @param string $aliasName the alias name to use
+ *
+ * @return OODBBean
+ */
+ public function alias( $aliasName )
+ {
+ $this->aliasName = $this->beau( $aliasName );
+ return $this;
+ }
+
+ /**
+ * Returns properties of bean as an array.
+ * This method returns the raw internal property list of the
+ * bean. Only use this method for optimization purposes. Otherwise
+ * use the export() method to export bean data to arrays.
+ *
+ * @return array
+ */
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * Returns properties of bean as an array.
+ * This method returns the raw internal property list of the
+ * bean. Only use this method for optimization purposes. Otherwise
+ * use the export() method to export bean data to arrays.
+ * This method returns an array with the properties array and
+ * the type (string).
+ *
+ * @return array
+ */
+ public function getPropertiesAndType()
+ {
+ return array( $this->properties, $this->__info['type'] );
+ }
+
+ /**
+ * Turns a camelcase property name into an underscored property name.
+ *
+ * Examples:
+ *
+ * - oneACLRoute -> one_acl_route
+ * - camelCase -> camel_case
+ *
+ * Also caches the result to improve performance.
+ *
+ * @param string $property property to un-beautify
+ *
+ * @return string
+ */
+ public function beau( $property )
+ {
+ static $beautifulColumns = array();
+
+ if ( ctype_lower( $property ) ) return $property;
+ if (
+ ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) )
+ || ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) )
+ || ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) )
+ ) {
+
+ $property = preg_replace( '/List$/', '', $property );
+ return $property;
+ }
+ if ( !isset( $beautifulColumns[$property] ) ) {
+ $beautifulColumns[$property] = AQueryWriter::camelsSnake( $property );
+ }
+ return $beautifulColumns[$property];
+ }
+
+ /**
+ * Modifiers are a powerful concept in RedBeanPHP, they make it possible
+ * to change the way a property has to be loaded.
+ * RedBeanPHP uses property modifiers using a prefix notation like this:
+ *
+ *
+ * $book->fetchAs('page')->cover;
+ *
+ *
+ * Here, we load a bean of type page, identified by the cover property
+ * (or cover_id in the database). Because the modifier is called before
+ * the property is accessed, the modifier must be remembered somehow,
+ * this changes the state of the bean. Accessing a property causes the
+ * bean to clear its modifiers. To clear the modifiers manually you can
+ * use this method.
+ *
+ * Usage:
+ *
+ *
+ * $book->with( 'LIMIT 1' );
+ * $book->clearModifiers()->ownPageList;
+ *
+ *
+ * In the example above, the 'LIMIT 1' clause is
+ * cleared before accessing the pages of the book, causing all pages
+ * to be loaded in the list instead of just one.
+ *
+ * @return self
+ */
+ public function clearModifiers()
+ {
+ $this->withSql = '';
+ $this->withParams = array();
+ $this->aliasName = NULL;
+ $this->fetchType = NULL;
+ $this->noLoad = FALSE;
+ $this->all = FALSE;
+ $this->via = NULL;
+ $this->castProperty = NULL;
+ return $this;
+ }
+
+ /**
+ * Determines whether a list is opened in exclusive mode or not.
+ * If a list has been opened in exclusive mode this method will return TRUE,
+ * otherwise it will return FALSE.
+ *
+ * @param string $listName name of the list to check
+ *
+ * @return boolean
+ */
+ public function isListInExclusiveMode( $listName )
+ {
+ $listName = $this->beau( $listName );
+
+ if ( strpos( $listName, 'xown' ) === 0 && ctype_upper( substr( $listName, 4, 1 ) ) ) {
+ $listName = substr($listName, 1);
+ }
+ $listName = lcfirst( substr( $listName, 3 ) );
+ return ( isset( $this->__info['sys.exclusive-'.$listName] ) && $this->__info['sys.exclusive-'.$listName] );
+ }
+
+ /**
+ * Magic Getter. Gets the value for a specific property in the bean.
+ * If the property does not exist this getter will make sure no error
+ * occurs. This is because RedBean allows you to query (probe) for
+ * properties. If the property can not be found this method will
+ * return NULL instead.
+ *
+ * Usage:
+ *
+ *
+ * $title = $book->title;
+ * $pages = $book->ownPageList;
+ * $tags = $book->sharedTagList;
+ *
+ *
+ * The example aboves lists several ways to invoke the magic getter.
+ * You can use the magic setter to access properties, own-lists,
+ * exclusive own-lists (xownLists) and shared-lists.
+ *
+ * @param string $property name of the property you wish to obtain the value of
+ *
+ * @return mixed
+ */
+ public function &__get( $property )
+ {
+ $isEx = FALSE;
+ $isOwn = FALSE;
+ $isShared = FALSE;
+ if ( !ctype_lower( $property ) ) {
+ $property = $this->beau( $property );
+ if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
+ $property = substr($property, 1);
+ $listName = lcfirst( substr( $property, 3 ) );
+ $isEx = TRUE;
+ $isOwn = TRUE;
+ $this->__info['sys.exclusive-'.$listName] = TRUE;
+ } elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) ) {
+ $isOwn = TRUE;
+ $listName = lcfirst( substr( $property, 3 ) );
+ } elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
+ $isShared = TRUE;
+ }
+ }
+ $fieldLink = $property . '_id';
+ $exists = isset( $this->properties[$property] );
+
+ //If not exists and no field link and no list, bail out.
+ if ( !$exists && !isset($this->$fieldLink) && (!$isOwn && !$isShared )) {
+ $this->clearModifiers();
+ /**
+ * Github issue:
+ * Remove $NULL to directly return NULL #625
+ * @@ -1097,8 +1097,7 @@ public function &__get( $property )
+ * $this->all = FALSE;
+ * $this->via = NULL;
+ *
+ * - $NULL = NULL;
+ * - return $NULL;
+ * + return NULL;
+ *
+ * leads to regression:
+ * PHP Stack trace:
+ * PHP 1. {main}() testje.php:0
+ * PHP 2. RedBeanPHP\OODBBean->__get() testje.php:22
+ * Notice: Only variable references should be returned by reference in rb.php on line 2529
+ */
+ $NULL = NULL;
+ return $NULL;
+ }
+
+ $hasAlias = (!is_null($this->aliasName));
+ $differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
+ ($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
+ $hasSQL = ($this->withSql !== '' || $this->via !== NULL);
+ $hasAll = (boolean) ($this->all);
+
+ //If exists and no list or exits and list not changed, bail out.
+ if ( $exists && ((!$isOwn && !$isShared ) || (!$hasSQL && !$differentAlias && !$hasAll)) ) {
+ $castProperty = $this->castProperty;
+ $this->clearModifiers();
+ if (!is_null($castProperty)) {
+ $object = new $castProperty( $this->properties[$property] );
+ return $object;
+ }
+ return $this->properties[$property];
+ }
+
+ list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
+
+ //If it's another bean, then we load it and return
+ if ( isset( $this->$fieldLink ) ) {
+ $this->__info['tainted'] = TRUE;
+ if ( isset( $this->__info["sys.parentcache.$property"] ) ) {
+ $bean = $this->__info["sys.parentcache.$property"];
+ } else {
+ if ( isset( self::$aliases[$property] ) ) {
+ $type = self::$aliases[$property];
+ } elseif ( $this->fetchType ) {
+ $type = $this->fetchType;
+ $this->fetchType = NULL;
+ } else {
+ $type = $property;
+ }
+ $bean = NULL;
+ if ( !is_null( $this->properties[$fieldLink] ) ) {
+ $bean = $redbean->load( $type, $this->properties[$fieldLink] );
+ }
+ }
+ $this->properties[$property] = $bean;
+ $this->clearModifiers();
+ return $this->properties[$property];
+ }
+
+ /* Implicit: elseif ( $isOwn || $isShared ) */
+ if ( $this->noLoad ) {
+ $beans = array();
+ } elseif ( $isOwn ) {
+ $beans = $this->getOwnList( $listName, $redbean );
+ } else {
+ $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
+ }
+ $this->properties[$property] = $beans;
+ $this->__info["sys.shadow.$property"] = $beans;
+ $this->__info['tainted'] = TRUE;
+ $this->clearModifiers();
+ return $this->properties[$property];
+
+ }
+
+ /**
+ * Magic Setter. Sets the value for a specific property.
+ * This setter acts as a hook for OODB to mark beans as tainted.
+ * The tainted meta property can be retrieved using getMeta("tainted").
+ * The tainted meta property indicates whether a bean has been modified and
+ * can be used in various caching mechanisms.
+ *
+ * @param string $property name of the property you wish to assign a value to
+ * @param mixed $value the value you want to assign
+ *
+ * @return void
+ */
+ public function __set( $property, $value )
+ {
+ $isEx = FALSE;
+ $isOwn = FALSE;
+ $isShared = FALSE;
+
+ if ( !ctype_lower( $property ) ) {
+ $property = $this->beau( $property );
+ if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
+ $property = substr($property, 1);
+ $listName = lcfirst( substr( $property, 3 ) );
+ $isEx = TRUE;
+ $isOwn = TRUE;
+ $this->__info['sys.exclusive-'.$listName] = TRUE;
+ } elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) ) {
+ $isOwn = TRUE;
+ $listName = lcfirst( substr( $property, 3 ) );
+ } elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
+ $isShared = TRUE;
+ }
+ } elseif ( self::$convertArraysToJSON && is_array( $value ) ) {
+ $value = json_encode( $value );
+ }
+
+ $hasAlias = (!is_null($this->aliasName));
+ $differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
+ ($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
+ $hasSQL = ($this->withSql !== '' || $this->via !== NULL);
+ $exists = isset( $this->properties[$property] );
+ $fieldLink = $property . '_id';
+ $isFieldLink = (($pos = strrpos($property, '_id')) !== FALSE) && array_key_exists( ($fieldName = substr($property, 0, $pos)), $this->properties );
+
+
+ if ( ($isOwn || $isShared) && (!$exists || $hasSQL || $differentAlias) ) {
+
+ if ( !$this->noLoad ) {
+ list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
+ if ( $isOwn ) {
+ $beans = $this->getOwnList( $listName, $redbean );
+ } else {
+ $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
+ }
+ $this->__info["sys.shadow.$property"] = $beans;
+ }
+ }
+
+ $this->clearModifiers();
+
+ $this->__info['tainted'] = TRUE;
+ $this->__info['changed'] = TRUE;
+ array_push( $this->__info['changelist'], $property );
+
+ if ( array_key_exists( $fieldLink, $this->properties ) && !( $value instanceof OODBBean ) ) {
+ if ( is_null( $value ) || $value === FALSE ) {
+
+ unset( $this->properties[ $property ]);
+ $this->properties[ $fieldLink ] = NULL;
+
+ return;
+ } else {
+ throw new RedException( 'Cannot cast to bean.' );
+ }
+ }
+
+ if ( $isFieldLink ){
+ unset( $this->properties[ $fieldName ]);
+ $this->properties[ $property ] = NULL;
+ }
+
+
+ if ( $value === FALSE ) {
+ $value = '0';
+ } elseif ( $value === TRUE ) {
+ $value = '1';
+ /* for some reason there is some kind of bug in xdebug so that it doesn't count this line otherwise... */
+ } elseif ( ( ( $value instanceof \DateTime ) or ( $value instanceof \DateTimeInterface ) ) ) { $value = $value->format( 'Y-m-d H:i:s' ); }
+ $this->properties[$property] = $value;
+ }
+
+ /**
+ * @deprecated
+ *
+ * Sets a property of the bean allowing you to keep track of
+ * the state yourself. This method sets a property of the bean and
+ * allows you to control how the state of the bean will be affected.
+ *
+ * While there may be some circumstances where this method is needed,
+ * this method is considered to be extremely dangerous.
+ * This method is only for advanced users.
+ *
+ * @param string $property property
+ * @param mixed $value value
+ * @param boolean $updateShadow whether you want to update the shadow
+ * @param boolean $taint whether you want to mark the bean as tainted
+ *
+ * @return void
+ */
+ public function setProperty( $property, $value, $updateShadow = FALSE, $taint = FALSE )
+ {
+ $this->properties[$property] = $value;
+
+ if ( $updateShadow ) {
+ $this->__info['sys.shadow.' . $property] = $value;
+ }
+
+ if ( $taint ) {
+ $this->__info['tainted'] = TRUE;
+ $this->__info['changed'] = TRUE;
+ }
+ }
+
+ /**
+ * Returns the value of a meta property. A meta property
+ * contains additional information about the bean object that will not
+ * be stored in the database. Meta information is used to instruct
+ * RedBeanPHP as well as other systems how to deal with the bean.
+ * If the property cannot be found this getter will return NULL instead.
+ *
+ * Example:
+ *
+ *
+ * $bean->setMeta( 'flush-cache', TRUE );
+ *
+ *
+ * RedBeanPHP also stores meta data in beans, this meta data uses
+ * keys prefixed with 'sys.' (system).
+ *
+ * @param string $path path to property in meta data
+ * @param mixed $default default value
+ *
+ * @return mixed
+ */
+ public function getMeta( $path, $default = NULL )
+ {
+ return ( isset( $this->__info[$path] ) ) ? $this->__info[$path] : $default;
+ }
+
+ /**
+ * Returns a value from the data bundle.
+ * The data bundle might contain additional data send from an SQL query,
+ * for instance, the total number of rows. If the property cannot be
+ * found, the default value will be returned. If no default has
+ * been specified, this method returns NULL.
+ *
+ * @param string $key key
+ * @param mixed $default default (defaults to NULL)
+ *
+ * @return mixed;
+ */
+ public function info( $key, $default = NULL ) {
+ return ( isset( $this->__info['data.bundle'][$key] ) ) ? $this->__info['data.bundle'][$key] : $default;
+ }
+
+ /**
+ * Gets and unsets a meta property.
+ * Moves a meta property out of the bean.
+ * This is a short-cut method that can be used instead
+ * of combining a get/unset.
+ *
+ * @param string $path path to property in meta data
+ * @param mixed $default default value
+ *
+ * @return mixed
+ */
+ public function moveMeta( $path, $value = NULL )
+ {
+ if ( isset( $this->__info[$path] ) ) {
+ $value = $this->__info[ $path ];
+ unset( $this->__info[ $path ] );
+ }
+ return $value;
+ }
+
+ /**
+ * Stores a value in the specified Meta information property.
+ * The first argument should be the key to store the value under,
+ * the second argument should be the value. It is common to use
+ * a path-like notation for meta data in RedBeanPHP like:
+ * 'my.meta.data', however the dots are purely for readability, the
+ * meta data methods do not store nested structures or hierarchies.
+ *
+ * @param string $path path / key to store value under
+ * @param mixed $value value to store in bean (not in database) as meta data
+ *
+ * @return OODBBean
+ */
+ public function setMeta( $path, $value )
+ {
+ $this->__info[$path] = $value;
+ if ( $path == 'type' && !empty($this->beanHelper)) {
+ $this->__info['model'] = $this->beanHelper->getModelForBean( $this );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Copies the meta information of the specified bean
+ * This is a convenience method to enable you to
+ * exchange meta information easily.
+ *
+ * @param OODBBean $bean bean to copy meta data of
+ *
+ * @return OODBBean
+ */
+ public function copyMetaFrom( OODBBean $bean )
+ {
+ $this->__info = $bean->__info;
+
+ return $this;
+ }
+
+ /**
+ * Captures a dynamic casting.
+ * Enables you to obtain a bean value as an object by type-hinting
+ * the desired return object using asX where X is the class you wish
+ * to use as a wrapper for the property.
+ *
+ * Usage:
+ *
+ * $dateTime = $bean->asDateTime()->date;
+ *
+ * @param string $method method (asXXX)...
+ *
+ * @return self|NULL
+ */
+ public function captureDynamicCasting( $method )
+ {
+ if ( strpos( $method, 'as' ) === 0 && ctype_upper( substr( $method, 2, 1) ) === TRUE ) {
+ $this->castProperty = substr( $method, 2 );
+ return $this;
+ }
+ return NULL;
+ }
+
+ /**
+ * Sends the call to the registered model.
+ * This method can also be used to override bean behaviour.
+ * In that case you don't want an error or exception to be triggered
+ * if the method does not exist in the model (because it's optional).
+ * Unfortunately we cannot add an extra argument to __call() for this
+ * because the signature is fixed. Another option would be to set
+ * a special flag ( i.e. $this->isOptionalCall ) but that would
+ * cause additional complexity because we have to deal with extra temporary state.
+ * So, instead I allowed the method name to be prefixed with '@', in practice
+ * nobody creates methods like that - however the '@' symbol in PHP is widely known
+ * to suppress error handling, so we can reuse the semantics of this symbol.
+ * If a method name gets passed starting with '@' the overrideDontFail variable
+ * will be set to TRUE and the '@' will be stripped from the function name before
+ * attempting to invoke the method on the model. This way, we have all the
+ * logic in one place.
+ *
+ * @param string $method name of the method
+ * @param array $args argument list
+ *
+ * @return mixed
+ */
+ public function __call( $method, $args )
+ {
+ if ( empty( $this->__info['model'] ) ) {
+ return $this->captureDynamicCasting($method);
+ }
+
+ $overrideDontFail = FALSE;
+ if ( strpos( $method, '@' ) === 0 ) {
+ $method = substr( $method, 1 );
+ $overrideDontFail = TRUE;
+ }
+
+ if ( !is_callable( array( $this->__info['model'], $method ) ) ) {
+
+ $self = $this->captureDynamicCasting($method);
+ if ($self) return $self;
+
+ if ( self::$errorHandlingFUSE === FALSE || $overrideDontFail ) {
+ return NULL;
+ }
+
+ if ( in_array( $method, array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ), TRUE ) ) {
+ return NULL;
+ }
+
+ $message = "FUSE: method does not exist in model: $method";
+ if ( self::$errorHandlingFUSE === self::C_ERR_LOG ) {
+ error_log( $message );
+ return NULL;
+ } elseif ( self::$errorHandlingFUSE === self::C_ERR_NOTICE ) {
+ trigger_error( $message, E_USER_NOTICE );
+ return NULL;
+ } elseif ( self::$errorHandlingFUSE === self::C_ERR_WARN ) {
+ trigger_error( $message, E_USER_WARNING );
+ return NULL;
+ } elseif ( self::$errorHandlingFUSE === self::C_ERR_EXCEPTION ) {
+ throw new \Exception( $message );
+ } elseif ( self::$errorHandlingFUSE === self::C_ERR_FUNC ) {
+ $func = self::$errorHandler;
+ return $func(array(
+ 'message' => $message,
+ 'method' => $method,
+ 'args' => $args,
+ 'bean' => $this
+ ));
+ }
+ trigger_error( $message, E_USER_ERROR );
+ return NULL;
+ }
+
+ return call_user_func_array( array( $this->__info['model'], $method ), $args );
+ }
+
+ /**
+ * Implementation of __toString Method
+ * Routes call to Model. If the model implements a __toString() method this
+ * method will be called and the result will be returned. In case of an
+ * echo-statement this result will be printed. If the model does not
+ * implement a __toString method, this method will return a JSON
+ * representation of the current bean.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ $string = $this->__call( '@__toString', array() );
+
+ if ( $string === NULL ) {
+ $list = array();
+ foreach($this->properties as $property => $value) {
+ if (is_scalar($value)) {
+ if ( self::$enforceUTF8encoding ) {
+ $list[$property] = mb_convert_encoding($value, 'UTF-8', 'UTF-8');
+ } else {
+ $list[$property] = $value;
+ }
+ }
+ }
+ $data = json_encode( $list );
+ return $data;
+ } else {
+ return $string;
+ }
+ }
+
+ /**
+ * Implementation of Array Access Interface, you can access bean objects
+ * like an array.
+ * Call gets routed to __set.
+ *
+ * @param mixed $offset offset string
+ * @param mixed $value value
+ *
+ * @return void
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetSet( $offset, $value )
+ {
+ $this->__set( $offset, $value );
+ }
+
+ /**
+ * Implementation of Array Access Interface, you can access bean objects
+ * like an array.
+ *
+ * Array functions do not reveal x-own-lists and list-alias because
+ * you dont want duplicate entries in foreach-loops.
+ * Also offers a slight performance improvement for array access.
+ *
+ * @param mixed $offset property
+ *
+ * @return boolean
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetExists( $offset )
+ {
+ return $this->__isset( $offset );
+ }
+
+ /**
+ * Implementation of Array Access Interface, you can access bean objects
+ * like an array.
+ * Unsets a value from the array/bean.
+ *
+ * Array functions do not reveal x-own-lists and list-alias because
+ * you don't want duplicate entries in foreach-loops.
+ * Also offers a slight performance improvement for array access.
+ *
+ * @param mixed $offset property
+ *
+ * @return void
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetUnset( $offset )
+ {
+ $this->__unset( $offset );
+ }
+
+ /**
+ * Implementation of Array Access Interface, you can access bean objects
+ * like an array.
+ * Returns value of a property.
+ *
+ * Array functions do not reveal x-own-lists and list-alias because
+ * you don't want duplicate entries in foreach-loops.
+ * Also offers a slight performance improvement for array access.
+ *
+ * @param mixed $offset property
+ *
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function &offsetGet( $offset )
+ {
+ return $this->__get( $offset );
+ }
+
+ /**
+ * Chainable method to cast a certain ID to a bean; for instance:
+ * $person = $club->fetchAs('person')->member;
+ * This will load a bean of type person using member_id as ID.
+ *
+ * @param string $type preferred fetch type
+ *
+ * @return OODBBean
+ */
+ public function fetchAs( $type )
+ {
+ $this->fetchType = $type;
+
+ return $this;
+ }
+
+ /**
+ * Prepares to load a bean using the bean type specified by
+ * another property.
+ * Similar to fetchAs but uses a column instead of a direct value.
+ *
+ * Usage:
+ *
+ *
+ * $car = R::load( 'car', $id );
+ * $engine = $car->poly('partType')->part;
+ *
+ *
+ * In the example above, we have a bean of type car that
+ * may consists of several parts (i.e. chassis, wheels).
+ * To obtain the 'engine' we access the property 'part'
+ * using the type (i.e. engine) specified by the property
+ * indicated by the argument of poly().
+ * This essentially is a polymorph relation, hence the name.
+ * In database this relation might look like this:
+ *
+ * partType | part_id
+ * --------------------
+ * engine | 1020300
+ * wheel | 4820088
+ * chassis | 7823122
+ *
+ * @param string $field field name to use for mapping
+ *
+ * @return OODBBean
+ */
+ public function poly( $field )
+ {
+ return $this->fetchAs( $this->$field );
+ }
+
+ /**
+ * Traverses a bean property with the specified function.
+ * Recursively iterates through the property invoking the
+ * function for each bean along the way passing the bean to it.
+ *
+ * Can be used together with with, withCondition, alias and fetchAs.
+ *
+ *
+ * $task
+ * ->withCondition(' priority >= ? ', [ $priority ])
+ * ->traverse('ownTaskList', function( $t ) use ( &$todo ) {
+ * $todo[] = $t->descr;
+ * } );
+ *
+ *
+ * In the example, we create a to-do list by traversing a
+ * hierarchical list of tasks while filtering out all tasks
+ * having a low priority.
+ *
+ * @param string $property property
+ * @param callable $function function
+ * @param integer|NULL $maxDepth maximum depth for traversal
+ *
+ * @return OODBBean
+ * @throws RedException
+ */
+ public function traverse( $property, $function, $maxDepth = NULL, $depth = 1 )
+ {
+ $this->via = NULL;
+ if ( strpos( $property, 'shared' ) !== FALSE ) {
+ throw new RedException( 'Traverse only works with (x)own-lists.' );
+ }
+
+ if ( !is_null( $maxDepth ) ) {
+ if ( !$maxDepth-- ) return $this;
+ }
+
+ $oldFetchType = $this->fetchType;
+ $oldAliasName = $this->aliasName;
+ $oldWith = $this->withSql;
+ $oldBindings = $this->withParams;
+
+ $beans = $this->$property;
+
+ if ( $beans === NULL ) return $this;
+
+ if ( !is_array( $beans ) ) $beans = array( $beans );
+
+ foreach( $beans as $bean ) {
+ $function( $bean, $depth );
+ $bean->fetchType = $oldFetchType;
+ $bean->aliasName = $oldAliasName;
+ $bean->withSql = $oldWith;
+ $bean->withParams = $oldBindings;
+
+ $bean->traverse( $property, $function, $maxDepth, $depth + 1 );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Implementation of Countable interface. Makes it possible to use
+ * count() function on a bean. This method gets invoked if you use
+ * the count() function on a bean. The count() method will return
+ * the number of properties of the bean, this includes the id property.
+ *
+ * Usage:
+ *
+ *
+ * $bean = R::dispense('bean');
+ * $bean->property1 = 1;
+ * $bean->property2 = 2;
+ * echo count($bean); //prints 3 (cause id is also a property)
+ *
+ *
+ * The example above will print the number 3 to stdout.
+ * Although we have assigned values to just two properties, the
+ * primary key id is also a property of the bean and together
+ * that makes 3. Besides using the count() function, you can also
+ * call this method using a method notation: $bean->count().
+ *
+ * @return integer
+ */
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ return count( $this->properties );
+ }
+
+ /**
+ * Checks whether a bean is empty or not.
+ * A bean is empty if it has no other properties than the id field OR
+ * if all the other properties are 'empty()' (this might
+ * include NULL and FALSE values).
+ *
+ * Usage:
+ *
+ *
+ * $newBean = R::dispense( 'bean' );
+ * $newBean->isEmpty(); // TRUE
+ *
+ *
+ * The example above demonstrates that newly dispensed beans are
+ * considered 'empty'.
+ *
+ * @return boolean
+ */
+ public function isEmpty()
+ {
+ $empty = TRUE;
+ foreach ( $this->properties as $key => $value ) {
+ if ( $key == 'id' ) {
+ continue;
+ }
+ if ( !empty( $value ) ) {
+ $empty = FALSE;
+ }
+ }
+
+ return $empty;
+ }
+
+ /**
+ * Chainable setter.
+ * This method is actually the same as just setting a value
+ * using a magic setter (->property = ...). The difference
+ * is that you can chain these setters like this:
+ *
+ * Usage:
+ *
+ *
+ * $book->setAttr('title', 'mybook')->setAttr('author', 'me');
+ *
+ *
+ * This is the same as setting both properties $book->title and
+ * $book->author. Sometimes a chained notation can improve the
+ * readability of the code.
+ *
+ * @param string $property the property of the bean
+ * @param mixed $value the value you want to set
+ *
+ * @return OODBBean
+ */
+ public function setAttr( $property, $value )
+ {
+ $this->$property = $value;
+
+ return $this;
+ }
+
+ /**
+ * Convenience method.
+ * Unsets all properties in the internal properties array.
+ *
+ * Usage:
+ *
+ *
+ * $bean->property = 1;
+ * $bean->unsetAll( array( 'property' ) );
+ * $bean->property; //NULL
+ *
+ *
+ * In the example above the 'property' of the bean will be
+ * unset, resulting in the getter returning NULL instead of 1.
+ *
+ * @param array $properties properties you want to unset.
+ *
+ * @return OODBBean
+ */
+ public function unsetAll( $properties )
+ {
+ foreach ( $properties as $prop ) {
+ if ( isset( $this->properties[$prop] ) ) {
+ unset( $this->properties[$prop] );
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Returns original (old) value of a property.
+ * You can use this method to see what has changed in a
+ * bean. The original value of a property is the value that
+ * this property has had since the bean has been retrieved
+ * from the databases.
+ *
+ *
+ * $book->title = 'new title';
+ * $oldTitle = $book->old('title');
+ *
+ *
+ * The example shows how to use the old() method.
+ * Here we set the title property of the bean to 'new title', then
+ * we obtain the original value using old('title') and store it in
+ * a variable $oldTitle.
+ *
+ * @param string $property name of the property you want the old value of
+ *
+ * @return mixed
+ */
+ public function old( $property )
+ {
+ $old = $this->getMeta( 'sys.orig', array() );
+
+ if ( array_key_exists( $property, $old ) ) {
+ return $old[$property];
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Convenience method.
+ *
+ * Returns TRUE if the bean has been changed, or FALSE otherwise.
+ * Same as $bean->getMeta('tainted');
+ * Note that a bean becomes tainted as soon as you retrieve a list from
+ * the bean. This is because the bean lists are arrays and the bean cannot
+ * determine whether you have made modifications to a list so RedBeanPHP
+ * will mark the whole bean as tainted.
+ *
+ * @return boolean
+ */
+ public function isTainted()
+ {
+ return $this->getMeta( 'tainted' );
+ }
+
+ /**
+ * Returns TRUE if the value of a certain property of the bean has been changed and
+ * FALSE otherwise.
+ *
+ * Note that this method will return TRUE if applied to a loaded list.
+ * Also note that this method keeps track of the bean's history regardless whether
+ * it has been stored or not. Storing a bean does not undo its history,
+ * to clean the history of a bean use: clearHistory().
+ *
+ * @param string $property name of the property you want the change-status of
+ *
+ * @return boolean
+ */
+ public function hasChanged( $property )
+ {
+ return ( array_key_exists( $property, $this->properties ) ) ?
+ $this->old( $property ) != $this->properties[$property] : FALSE;
+ }
+
+ /**
+ * Returns TRUE if the specified list exists, has been loaded
+ * and has been changed:
+ * beans have been added or deleted.
+ * This method will not tell you anything about
+ * the state of the beans in the list.
+ *
+ * Usage:
+ *
+ *
+ * $book->hasListChanged( 'ownPage' ); // FALSE
+ * array_pop( $book->ownPageList );
+ * $book->hasListChanged( 'ownPage' ); // TRUE
+ *
+ *
+ * In the example, the first time we ask whether the
+ * own-page list has been changed we get FALSE. Then we pop
+ * a page from the list and the hasListChanged() method returns TRUE.
+ *
+ * @param string $property name of the list to check
+ *
+ * @return boolean
+ */
+ public function hasListChanged( $property )
+ {
+ if ( !array_key_exists( $property, $this->properties ) ) return FALSE;
+ $diffAdded = array_diff_assoc( $this->properties[$property], $this->__info['sys.shadow.'.$property] );
+ if ( count( $diffAdded ) ) return TRUE;
+ $diffMissing = array_diff_assoc( $this->__info['sys.shadow.'.$property], $this->properties[$property] );
+ if ( count( $diffMissing ) ) return TRUE;
+ return FALSE;
+ }
+
+ /**
+ * Clears (syncs) the history of the bean.
+ * Resets all shadow values of the bean to their current value.
+ *
+ * Usage:
+ *
+ *
+ * $book->title = 'book';
+ * echo $book->hasChanged( 'title' ); //TRUE
+ * R::store( $book );
+ * echo $book->hasChanged( 'title' ); //TRUE
+ * $book->clearHistory();
+ * echo $book->hasChanged( 'title' ); //FALSE
+ *
+ *
+ * Note that even after store(), the history of the bean still
+ * contains the act of changing the title of the book.
+ * Only after invoking clearHistory() will the history of the bean
+ * be cleared and will hasChanged() return FALSE.
+ *
+ * @return self
+ */
+ public function clearHistory()
+ {
+ $this->__info['sys.orig'] = array();
+ foreach( $this->properties as $key => $value ) {
+ if ( is_scalar($value) ) {
+ $this->__info['sys.orig'][$key] = $value;
+ } else {
+ $this->__info['sys.shadow.'.$key] = $value;
+ }
+ }
+ $this->__info[ 'changelist' ] = array();
+ return $this;
+ }
+
+ /**
+ * Creates a N-M relation by linking an intermediate bean.
+ * This method can be used to quickly connect beans using indirect
+ * relations. For instance, given an album and a song you can connect the two
+ * using a track with a number like this:
+ *
+ * Usage:
+ *
+ *
+ * $album->link('track', array('number'=>1))->song = $song;
+ *
+ *
+ * or:
+ *
+ *
+ * $album->link($trackBean)->song = $song;
+ *
+ *
+ * What this method does is adding the link bean to the own-list, in this case
+ * ownTrack. If the first argument is a string and the second is an array or
+ * a JSON string then the linking bean gets dispensed on-the-fly as seen in
+ * example #1. After preparing the linking bean, the bean is returned thus
+ * allowing the chained setter: ->song = $song.
+ *
+ * @param string|OODBBean $typeOrBean type of bean to dispense or the full bean
+ * @param string|array $qualification JSON string or array (optional)
+ *
+ * @return OODBBean
+ */
+ public function link( $typeOrBean, $qualification = array() )
+ {
+ if ( is_string( $typeOrBean ) ) {
+ $typeOrBean = AQueryWriter::camelsSnake( $typeOrBean );
+ $bean = $this->beanHelper->getToolBox()->getRedBean()->dispense( $typeOrBean );
+ if ( is_string( $qualification ) ) {
+ $data = json_decode( $qualification, TRUE );
+ } else {
+ $data = $qualification;
+ }
+ foreach ( $data as $key => $value ) {
+ $bean->$key = $value;
+ }
+ } else {
+ $bean = $typeOrBean;
+ }
+ $list = 'own' . ucfirst( $bean->getMeta( 'type' ) );
+ array_push( $this->$list, $bean );
+ return $bean;
+ }
+
+ /**
+ * Returns a bean of the given type with the same ID of as
+ * the current one. This only happens in a one-to-one relation.
+ * This is as far as support for 1-1 goes in RedBeanPHP. This
+ * method will only return a reference to the bean, changing it
+ * and storing the bean will not update the related one-bean.
+ *
+ * Usage:
+ *
+ *
+ * $author = R::load( 'author', $id );
+ * $biography = $author->one( 'bio' );
+ *
+ *
+ * The example loads the biography associated with the author
+ * using a one-to-one relation. These relations are generally not
+ * created (nor supported) by RedBeanPHP.
+ *
+ * @param $type type of bean to load
+ *
+ * @return OODBBean
+ */
+ public function one( $type ) {
+ return $this->beanHelper
+ ->getToolBox()
+ ->getRedBean()
+ ->load( $type, $this->id );
+ }
+
+ /**
+ * Reloads the bean.
+ * Returns the same bean freshly loaded from the database.
+ * This method is equal to the following code:
+ *
+ *
+ * $id = $bean->id;
+ * $type = $bean->getMeta( 'type' );
+ * $bean = R::load( $type, $id );
+ *
+ *
+ * This is just a convenience method to reload beans
+ * quickly.
+ *
+ * Usage:
+ *
+ *
+ * R::exec( ...update query... );
+ * $book = $book->fresh();
+ *
+ *
+ * The code snippet above illustrates how to obtain changes
+ * caused by an UPDATE query, simply by reloading the bean using
+ * the fresh() method.
+ *
+ * @return OODBBean
+ */
+ public function fresh()
+ {
+ return $this->beanHelper
+ ->getToolbox()
+ ->getRedBean()
+ ->load( $this->getMeta( 'type' ), $this->properties['id'] );
+ }
+
+ /**
+ * Registers a association renaming globally.
+ * Use via() and link() to associate shared beans using a
+ * 3rd bean that will act as an intermediate type. For instance
+ * consider an employee and a project. We could associate employees
+ * with projects using a sharedEmployeeList. But, maybe there is more
+ * to the relationship than just the association. Maybe we want
+ * to qualify the relation between a project and an employee with
+ * a role: 'developer', 'designer', 'tester' and so on. In that case,
+ * it might be better to introduce a new concept to reflect this:
+ * the participant. However, we still want the flexibility to
+ * query our employees in one go. This is where link() and via()
+ * can help. You can still introduce the more applicable
+ * concept (participant) and have your easy access to the shared beans.
+ *
+ *
+ * $Anna = R::dispense( 'employee' );
+ * $Anna->badge = 'Anna';
+ * $project = R::dispense( 'project' );
+ * $project->name = 'x';
+ * $Anna->link( 'participant', array(
+ * 'arole' => 'developer'
+ * ) )->project = $project;
+ * R::storeAll( array( $project, $Anna ) );
+ * $employees = $project
+ * ->with(' ORDER BY badge ASC ')
+ * ->via( 'participant' )
+ * ->sharedEmployee;
+ *
+ *
+ * This piece of code creates a project and an employee.
+ * It then associates the two using a via-relation called
+ * 'participant' ( employee <-> participant <-> project ).
+ * So, there will be a table named 'participant' instead of
+ * a table named 'employee_project'. Using the via() method, the
+ * employees associated with the project are retrieved 'via'
+ * the participant table (and an SQL snippet to order them by badge).
+ *
+ * @param string $via type you wish to use for shared lists
+ *
+ * @return OODBBean
+ */
+ public function via( $via )
+ {
+ $this->via = AQueryWriter::camelsSnake( $via );
+
+ return $this;
+ }
+
+ /**
+ * Counts all own beans of type $type.
+ * Also works with alias(), with() and withCondition().
+ * Own-beans or xOwn-beans (exclusively owned beans) are beans
+ * that have been associated using a one-to-many relation. They can
+ * be accessed through the ownXList where X is the type of the
+ * associated beans.
+ *
+ * Usage:
+ *
+ *
+ * $Bill->alias( 'author' )
+ * ->countOwn( 'book' );
+ *
+ *
+ * The example above counts all the books associated with 'author'
+ * $Bill.
+ *
+ * @param string $type the type of bean you want to count
+ *
+ * @return integer
+ */
+ public function countOwn( $type )
+ {
+ $type = $this->beau( $type );
+ if ( $this->aliasName ) {
+ $myFieldLink = $this->aliasName . '_id';
+ $this->aliasName = NULL;
+ } else {
+ $myFieldLink = $this->__info['type'] . '_id';
+ }
+ $count = 0;
+ if ( $this->getID() ) {
+ reset( $this->withParams );
+ $firstKey = count( $this->withParams ) > 0
+ ? key( $this->withParams )
+ : 0;
+ if ( is_int( $firstKey ) ) {
+ $sql = "{$myFieldLink} = ? {$this->withSql}";
+ $bindings = array_merge( array( $this->getID() ), $this->withParams );
+ } else {
+ $sql = "{$myFieldLink} = :slot0 {$this->withSql}";
+ $bindings = $this->withParams;
+ $bindings[':slot0'] = $this->getID();
+ }
+ if ( !self::$useFluidCount ) {
+ $count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), $sql, $bindings );
+ } else {
+ $count = $this->beanHelper->getToolbox()->getRedBean()->count( $type, $sql, $bindings );
+ }
+ }
+ $this->clearModifiers();
+ return (int) $count;
+ }
+
+ /**
+ * Counts all shared beans of type $type.
+ * Also works with via(), with() and withCondition().
+ * Shared beans are beans that have an many-to-many relation.
+ * They can be accessed using the sharedXList, where X the
+ * type of the shared bean.
+ *
+ * Usage:
+ *
+ *
+ * $book = R::dispense( 'book' );
+ * $book->sharedPageList = R::dispense( 'page', 5 );
+ * R::store( $book );
+ * echo $book->countShared( 'page' );
+ *
+ *
+ * The code snippet above will output '5', because there
+ * are 5 beans of type 'page' in the shared list.
+ *
+ * @param string $type type of bean you wish to count
+ *
+ * @return integer
+ */
+ public function countShared( $type )
+ {
+ $toolbox = $this->beanHelper->getToolbox();
+ $redbean = $toolbox->getRedBean();
+ $writer = $toolbox->getWriter();
+ if ( $this->via ) {
+ $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
+ if ( $oldName !== $this->via ) {
+ //set the new renaming rule
+ $writer->renameAssocTable( $oldName, $this->via );
+ $this->via = NULL;
+ }
+ }
+ $type = $this->beau( $type );
+ $count = 0;
+ if ( $this->getID() ) {
+ $count = $redbean->getAssociationManager()->relatedCount( $this, $type, $this->withSql, $this->withParams );
+ }
+ $this->clearModifiers();
+ return (integer) $count;
+ }
+
+ /**
+ * Iterates through the specified own-list and
+ * fetches all properties (with their type) and
+ * returns the references.
+ * Use this method to quickly load indirectly related
+ * beans in an own-list. Whenever you cannot use a
+ * shared-list this method offers the same convenience
+ * by aggregating the parent beans of all children in
+ * the specified own-list.
+ *
+ * Example:
+ *
+ *
+ * $quest->aggr( 'xownQuestTarget', 'target', 'quest' );
+ *
+ *
+ * Loads (in batch) and returns references to all
+ * quest beans residing in the $questTarget->target properties
+ * of each element in the xownQuestTargetList.
+ *
+ * @param string $list the list you wish to process
+ * @param string $property the property to load
+ * @param string|NULL $type the type of bean residing in this property (optional)
+ *
+ * @return array
+ */
+ public function &aggr( $list, $property, $type = NULL )
+ {
+ $this->via = NULL;
+ $ids = $beanIndex = $references = array();
+
+ if ( strlen( $list ) < 4 ) throw new RedException('Invalid own-list.');
+ if ( strpos( $list, 'own') !== 0 ) throw new RedException('Only own-lists can be aggregated.');
+ if ( !ctype_upper( substr( $list, 3, 1 ) ) ) throw new RedException('Invalid own-list.');
+
+ if ( is_null( $type ) ) $type = $property;
+
+ foreach( $this->$list as $bean ) {
+ $field = $property . '_id';
+ if ( isset( $bean->$field) ) {
+ $ids[] = $bean->$field;
+ $beanIndex[$bean->$field] = $bean;
+ }
+ }
+
+ $beans = $this->beanHelper->getToolBox()->getRedBean()->batch( $type, $ids );
+
+ //now preload the beans as well
+ foreach( $beans as $bean ) {
+ $beanIndex[$bean->id]->setProperty( $property, $bean );
+ }
+
+ foreach( $beanIndex as $indexedBean ) {
+ $references[] = $indexedBean->$property;
+ }
+
+ return $references;
+ }
+
+ /**
+ * Tests whether the database identities of two beans are equal.
+ * Two beans are considered 'equal' if:
+ *
+ * a. the types of the beans match
+ * b. the ids of the beans match
+ *
+ * Returns TRUE if the beans are considered equal according to this
+ * specification and FALSE otherwise.
+ *
+ * Usage:
+ *
+ *
+ * $coffee->fetchAs( 'flavour' )->taste->equals(
+ * R::enum('flavour:mocca')
+ * );
+ *
+ *
+ * The example above compares the flavour label 'mocca' with
+ * the flavour label attached to the $coffee bean. This illustrates
+ * how to use equals() with RedBeanPHP-style enums.
+ *
+ * @param OODBBean|null $bean other bean
+ *
+ * @return boolean
+ */
+ public function equals(OODBBean $bean)
+ {
+ if ( is_null($bean) ) return false;
+
+ return (bool) (
+ ( (string) $this->properties['id'] === (string) $bean->properties['id'] )
+ && ( (string) $this->__info['type'] === (string) $bean->__info['type'] )
+ );
+ }
+
+ /**
+ * Magic method jsonSerialize,
+ * implementation for the \JsonSerializable interface,
+ * this method gets called by json_encode and
+ * facilitates a better JSON representation
+ * of the bean. Exports the bean on JSON serialization,
+ * for the JSON fans.
+ *
+ * Models can override jsonSerialize (issue #651) by
+ * implementing a __jsonSerialize method which should return
+ * an array. The __jsonSerialize override gets called with
+ * the @ modifier to prevent errors or warnings.
+ *
+ * @see http://php.net/manual/en/class.jsonserializable.php
+ *
+ * @return array
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ $json = $this->__call( '@__jsonSerialize', array( ) );
+
+ if ( $json !== NULL ) {
+ return $json;
+ }
+
+ return $this->export();
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\Observer as Observer;
+
+/**
+ * Observable
+ * Base class for Observables
+ *
+ * @file RedBeanPHP/Observable.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+abstract class Observable { //bracket must be here - otherwise coverage software does not understand.
+
+ /**
+ * @var array
+ */
+ private $observers = array();
+
+ /**
+ * Implementation of the Observer Pattern.
+ * Adds an event listener to the observable object.
+ * First argument should be the name of the event you wish to listen for.
+ * Second argument should be the object that wants to be notified in case
+ * the event occurs.
+ *
+ * @param string $eventname event identifier
+ * @param Observer $observer observer instance
+ *
+ * @return void
+ */
+ public function addEventListener( $eventname, Observer $observer )
+ {
+ if ( !isset( $this->observers[$eventname] ) ) {
+ $this->observers[$eventname] = array();
+ }
+
+ if ( in_array( $observer, $this->observers[$eventname] ) ) {
+ return;
+ }
+
+ $this->observers[$eventname][] = $observer;
+ }
+
+ /**
+ * Notifies listeners.
+ * Sends the signal $eventname, the event identifier and a message object
+ * to all observers that have been registered to receive notification for
+ * this event. Part of the observer pattern implementation in RedBeanPHP.
+ *
+ * @param string $eventname event you want signal
+ * @param mixed $info message object to send along
+ *
+ * @return void
+ */
+ public function signal( $eventname, $info )
+ {
+ if ( !isset( $this->observers[$eventname] ) ) {
+ $this->observers[$eventname] = array();
+ }
+
+ foreach ( $this->observers[$eventname] as $observer ) {
+ $observer->onEvent( $eventname, $info );
+ }
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+/**
+ * Observer.
+ *
+ * Interface for Observer object. Implementation of the
+ * observer pattern.
+ *
+ * @file RedBeanPHP/Observer.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ * @desc Part of the observer pattern in RedBean
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+interface Observer
+{
+ /**
+ * An observer object needs to be capable of receiving
+ * notifications. Therefore the observer needs to implement the
+ * onEvent method with two parameters: the event identifier specifying the
+ * current event and a message object (in RedBeanPHP this can also be a bean).
+ *
+ * @param string $eventname event identifier
+ * @param mixed $bean a message sent along with the notification
+ *
+ * @return void
+ */
+ public function onEvent( $eventname, $bean );
+}
+}
+
+namespace RedBeanPHP {
+
+/**
+ * Adapter Interface.
+ * Describes the API for a RedBeanPHP Database Adapter.
+ * This interface defines the API contract for
+ * a RedBeanPHP Database Adapter.
+ *
+ * @file RedBeanPHP/Adapter.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+interface Adapter
+{
+ /**
+ * Should returns a string containing the most recent SQL query
+ * that has been processed by the adapter.
+ *
+ * @return string
+ */
+ public function getSQL();
+
+ /**
+ * Executes an SQL Statement using an array of values to bind
+ * If $noevent is TRUE then this function will not signal its
+ * observers to notify about the SQL execution; this to prevent
+ * infinite recursion when using observers.
+ *
+ * @param string $sql string containing SQL code for database
+ * @param array $bindings array of values to bind to parameters in query string
+ * @param boolean $noevent no event firing
+ *
+ * @return int
+ */
+ public function exec( $sql, $bindings = array(), $noevent = FALSE );
+
+ /**
+ * Executes an SQL Query and returns a resultset.
+ * This method returns a multi dimensional resultset similar to getAll
+ * The values array can be used to bind values to the place holders in the
+ * SQL query.
+ *
+ * @param string $sql string containing SQL code for database
+ * @param array $bindings array of values to bind to parameters in query string
+ *
+ * @return array
+ */
+ public function get( $sql, $bindings = array() );
+
+ /**
+ * Executes an SQL Query and returns a resultset.
+ * This method returns a single row (one array) resultset.
+ * The values array can be used to bind values to the place holders in the
+ * SQL query.
+ *
+ * @param string $sql string containing SQL code for database
+ * @param array $bindings array of values to bind to parameters in query string
+ *
+ * @return array|NULL
+ */
+ public function getRow( $sql, $bindings = array() );
+
+ /**
+ * Executes an SQL Query and returns a resultset.
+ * This method returns a single column (one array) resultset.
+ * The values array can be used to bind values to the place holders in the
+ * SQL query.
+ *
+ * @param string $sql string containing SQL code for database
+ * @param array $bindings array of values to bind to parameters in query string
+ *
+ * @return array
+ */
+ public function getCol( $sql, $bindings = array() );
+
+ /**
+ * Executes an SQL Query and returns a resultset.
+ * This method returns a single cell, a scalar value as the resultset.
+ * The values array can be used to bind values to the place holders in the
+ * SQL query.
+ *
+ * @param string $sql string containing SQL code for database
+ * @param array $bindings array of values to bind to parameters in query string
+ *
+ * @return string|NULL
+ */
+ public function getCell( $sql, $bindings = array() );
+
+ /**
+ * Executes the SQL query specified in $sql and indexes
+ * the row by the first column.
+ *
+ * @param string $sql string containing SQL code for database
+ * @param array $bindings array of values to bind to parameters in query string
+ *
+ * @return array
+ */
+ public function getAssoc( $sql, $bindings = array() );
+
+ /**
+ * Executes the SQL query specified in $sql and returns
+ * an associative array where the column names are the keys.
+ *
+ * @param string $sql String containing SQL code for databaseQL
+ * @param array $bindings values to bind
+ *
+ * @return array
+ */
+ public function getAssocRow( $sql, $bindings = array() );
+
+ /**
+ * Returns the latest insert ID.
+ *
+ * @return integer
+ */
+ public function getInsertID();
+
+ /**
+ * Returns the number of rows that have been
+ * affected by the last update statement.
+ *
+ * @return integer
+ */
+ public function getAffectedRows();
+
+ /**
+ * Returns a database agnostic Cursor object.
+ *
+ * @param string $sql string containing SQL code for database
+ * @param array $bindings array of values to bind to parameters in query string
+ *
+ * @return Cursor
+ */
+ public function getCursor( $sql, $bindings = array() );
+
+ /**
+ * Returns the original database resource. This is useful if you want to
+ * perform operations on the driver directly instead of working with the
+ * adapter. RedBean will only access the adapter and never to talk
+ * directly to the driver though.
+ *
+ * @return Driver
+ */
+ public function getDatabase();
+
+ /**
+ * This method is part of the RedBean Transaction Management
+ * mechanisms.
+ * Starts a transaction.
+ *
+ * @return void
+ */
+ public function startTransaction();
+
+ /**
+ * This method is part of the RedBean Transaction Management
+ * mechanisms.
+ * Commits the transaction.
+ *
+ * @return void
+ */
+ public function commit();
+
+ /**
+ * This method is part of the RedBean Transaction Management
+ * mechanisms.
+ * Rolls back the transaction.
+ *
+ * @return void
+ */
+ public function rollback();
+
+ /**
+ * Closes database connection.
+ *
+ * @return void
+ */
+ public function close();
+
+ /**
+ * Sets a driver specific option.
+ * Using this method you can access driver-specific functions.
+ * If the selected option exists the value will be passed and
+ * this method will return boolean TRUE, otherwise it will return
+ * boolean FALSE.
+ *
+ * @param string $optionKey option key
+ * @param string $optionValue option value
+ *
+ * @return boolean
+ */
+ public function setOption( $optionKey, $optionValue );
+
+ /**
+ * Returns the version string from the database server.
+ *
+ * @return string
+ */
+ public function getDatabaseServerVersion();
+}
+}
+
+namespace RedBeanPHP\Adapter {
+
+use RedBeanPHP\Observable as Observable;
+use RedBeanPHP\Adapter as Adapter;
+use RedBeanPHP\Driver as Driver;
+
+/**
+ * DBAdapter (Database Adapter)
+ *
+ * An adapter class to connect various database systems to RedBean
+ * Database Adapter Class. The task of the database adapter class is to
+ * communicate with the database driver. You can use all sorts of database
+ * drivers with RedBeanPHP. The default database drivers that ships with
+ * the RedBeanPHP library is the RPDO driver ( which uses the PHP Data Objects
+ * Architecture aka PDO ).
+ *
+ * @file RedBeanPHP/Adapter/DBAdapter.php
+ * @author Gabor de Mooij and the RedBeanPHP Community.
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class DBAdapter extends Observable implements Adapter
+{
+ /**
+ * @var Driver
+ */
+ private $db = NULL;
+
+ /**
+ * @var string
+ */
+ private $sql = '';
+
+ /**
+ * Constructor.
+ *
+ * Creates an instance of the RedBean Adapter Class.
+ * This class provides an interface for RedBean to work
+ * with ADO compatible DB instances.
+ *
+ * Usage:
+ *
+ *
+ * $database = new RPDO( $dsn, $user, $pass );
+ * $adapter = new DBAdapter( $database );
+ * $writer = new PostgresWriter( $adapter );
+ * $oodb = new OODB( $writer, FALSE );
+ * $bean = $oodb->dispense( 'bean' );
+ * $bean->name = 'coffeeBean';
+ * $id = $oodb->store( $bean );
+ * $bean = $oodb->load( 'bean', $id );
+ *
+ *
+ * The example above creates the 3 RedBeanPHP core objects:
+ * the Adapter, the Query Writer and the OODB instance and
+ * wires them together. The example also demonstrates some of
+ * the methods that can be used with OODB, as you see, they
+ * closely resemble their facade counterparts.
+ *
+ * The wiring process: create an RPDO instance using your database
+ * connection parameters. Create a database adapter from the RPDO
+ * object and pass that to the constructor of the writer. Next,
+ * create an OODB instance from the writer. Now you have an OODB
+ * object.
+ *
+ * @param Driver $database ADO Compatible DB Instance
+ */
+ public function __construct( $database )
+ {
+ $this->db = $database;
+ }
+
+ /**
+ * Returns a string containing the most recent SQL query
+ * processed by the database adapter, thus conforming to the
+ * interface:
+ *
+ * @see Adapter::getSQL
+ *
+ * Methods like get(), getRow() and exec() cause this SQL cache
+ * to get filled. If no SQL query has been processed yet this function
+ * will return an empty string.
+ *
+ * @return string
+ */
+ public function getSQL()
+ {
+ return $this->sql;
+ }
+
+ /**
+ * @see Adapter::exec
+ */
+ public function exec( $sql, $bindings = array(), $noevent = FALSE )
+ {
+ if ( !$noevent ) {
+ $this->sql = $sql;
+ $this->signal( 'sql_exec', $this );
+ }
+
+ return $this->db->Execute( $sql, $bindings );
+ }
+
+ /**
+ * @see Adapter::get
+ */
+ public function get( $sql, $bindings = array() )
+ {
+ $this->sql = $sql;
+ $this->signal( 'sql_exec', $this );
+
+ return $this->db->GetAll( $sql, $bindings );
+ }
+
+ /**
+ * @see Adapter::getRow
+ */
+ public function getRow( $sql, $bindings = array() )
+ {
+ $this->sql = $sql;
+ $this->signal( 'sql_exec', $this );
+
+ return $this->db->GetRow( $sql, $bindings );
+ }
+
+ /**
+ * @see Adapter::getCol
+ */
+ public function getCol( $sql, $bindings = array() )
+ {
+ $this->sql = $sql;
+ $this->signal( 'sql_exec', $this );
+
+ return $this->db->GetCol( $sql, $bindings );
+ }
+
+ /**
+ * @see Adapter::getAssoc
+ */
+ public function getAssoc( $sql, $bindings = array() )
+ {
+ $this->sql = $sql;
+
+ $this->signal( 'sql_exec', $this );
+
+ $rows = $this->db->GetAll( $sql, $bindings );
+
+ if ( !$rows ) return array();
+
+ $assoc = array();
+
+ foreach ( $rows as $row ) {
+ if ( empty( $row ) ) continue;
+
+ $key = array_shift( $row );
+ switch ( count( $row ) ) {
+ case 0:
+ $value = $key;
+ break;
+ case 1:
+ $value = reset( $row );
+ break;
+ default:
+ $value = $row;
+ }
+
+ $assoc[$key] = $value;
+ }
+
+ return $assoc;
+ }
+
+ /**
+ * @see Adapter::getAssocRow
+ */
+ public function getAssocRow($sql, $bindings = array())
+ {
+ $this->sql = $sql;
+ $this->signal( 'sql_exec', $this );
+
+ return $this->db->GetAssocRow( $sql, $bindings );
+ }
+
+ /**
+ * @see Adapter::getCell
+ */
+ public function getCell( $sql, $bindings = array(), $noSignal = NULL )
+ {
+ $this->sql = $sql;
+
+ if ( !$noSignal ) $this->signal( 'sql_exec', $this );
+
+ return $this->db->GetOne( $sql, $bindings );
+ }
+
+ /**
+ * @see Adapter::getCursor
+ */
+ public function getCursor( $sql, $bindings = array() )
+ {
+ return $this->db->GetCursor( $sql, $bindings );
+ }
+
+ /**
+ * @see Adapter::getInsertID
+ */
+ public function getInsertID()
+ {
+ return $this->db->getInsertID();
+ }
+
+ /**
+ * @see Adapter::getAffectedRows
+ */
+ public function getAffectedRows()
+ {
+ return $this->db->Affected_Rows();
+ }
+
+ /**
+ * @see Adapter::getDatabase
+ */
+ public function getDatabase()
+ {
+ return $this->db;
+ }
+
+ /**
+ * @see Adapter::startTransaction
+ */
+ public function startTransaction()
+ {
+ $this->db->StartTrans();
+ }
+
+ /**
+ * @see Adapter::commit
+ */
+ public function commit()
+ {
+ $this->db->CommitTrans();
+ }
+
+ /**
+ * @see Adapter::rollback
+ */
+ public function rollback()
+ {
+ $this->db->FailTrans();
+ }
+
+ /**
+ * @see Adapter::close.
+ */
+ public function close()
+ {
+ $this->db->close();
+ }
+
+ /**
+ * Sets initialization code for connection.
+ *
+ * @param callable $code
+ */
+ public function setInitCode($code) {
+ $this->db->setInitCode($code);
+ }
+
+ /**
+ * @see Adapter::setOption
+ */
+ public function setOption( $optionKey, $optionValue ) {
+ if ( method_exists( $this->db, $optionKey ) ) {
+ call_user_func( array( $this->db, $optionKey ), $optionValue );
+ return TRUE;
+ }
+ return FALSE;
+ }
+
+ /**
+ * @see Adapter::getDatabaseServerVersion
+ */
+ public function getDatabaseServerVersion()
+ {
+ return $this->db->DatabaseServerVersion();
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+/**
+ * Database Cursor Interface.
+ * A cursor is used by Query Writers to fetch Query Result rows
+ * one row at a time. This is useful if you expect the result set to
+ * be quite large. This interface describes the API of a database
+ * cursor. There can be multiple implementations of the Cursor,
+ * by default RedBeanPHP offers the PDOCursor for drivers shipping
+ * with RedBeanPHP and the NULLCursor.
+ *
+ * @file RedBeanPHP/Cursor.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+interface Cursor
+{
+ /**
+ * Should retrieve the next row of the result set.
+ * This method is used to iterate over the result set.
+ *
+ * @return array|NULL
+ */
+ public function getNextItem();
+
+ /**
+ * Resets the cursor by closing it and re-executing the statement.
+ * This reloads fresh data from the database for the whole collection.
+ *
+ * @return void
+ */
+ public function reset();
+
+ /**
+ * Closes the database cursor.
+ * Some databases require a cursor to be closed before executing
+ * another statement/opening a new cursor.
+ *
+ * @return void
+ */
+ public function close();
+}
+}
+
+namespace RedBeanPHP\Cursor {
+
+use RedBeanPHP\Cursor as Cursor;
+
+/**
+ * PDO Database Cursor
+ * Implementation of PDO Database Cursor.
+ * Used by the BeanCollection to fetch one bean at a time.
+ * The PDO Cursor is used by Query Writers to support retrieval
+ * of large bean collections. For instance, this class is used to
+ * implement the findCollection()/BeanCollection functionality.
+ *
+ * @file RedBeanPHP/Cursor/PDOCursor.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class PDOCursor implements Cursor
+{
+ /**
+ * @var \PDOStatement
+ */
+ protected $res;
+
+ /**
+ * @var string
+ */
+ protected $fetchStyle;
+
+ /**
+ * Constructor, creates a new instance of a PDO Database Cursor.
+ *
+ * @param \PDOStatement $res the PDO statement
+ * @param string $fetchStyle fetch style constant to use
+ *
+ * @return void
+ */
+ public function __construct( \PDOStatement $res, $fetchStyle )
+ {
+ $this->res = $res;
+ $this->fetchStyle = $fetchStyle;
+ }
+
+ /**
+ * @see Cursor::getNextItem
+ */
+ public function getNextItem()
+ {
+ return $this->res->fetch();
+ }
+
+ /**
+ * @see Cursor::reset
+ */
+ public function reset()
+ {
+ $this->close();
+ $this->res->execute();
+ }
+
+ /**
+ * @see Cursor::close
+ */
+ public function close()
+ {
+ $this->res->closeCursor();
+ }
+}
+}
+
+namespace RedBeanPHP\Cursor {
+
+use RedBeanPHP\Cursor as Cursor;
+
+/**
+ * NULL Database Cursor
+ * Implementation of the NULL Cursor.
+ * Used for an empty BeanCollection. This Cursor
+ * can be used for instance if a query fails but the interface
+ * demands a cursor to be returned.
+ *
+ * @file RedBeanPHP/Cursor/NULLCursor.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class NullCursor implements Cursor
+{
+ /**
+ * @see Cursor::getNextItem
+ */
+ public function getNextItem()
+ {
+ return NULL;
+ }
+
+ /**
+ * @see Cursor::reset
+ */
+ public function reset()
+ {
+ return NULL;
+ }
+
+ /**
+ * @see Cursor::close
+ */
+ public function close()
+ {
+ return NULL;
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\Cursor as Cursor;
+use RedBeanPHP\Repository as Repository;
+
+/**
+ * BeanCollection.
+ *
+ * The BeanCollection represents a collection of beans and
+ * makes it possible to use database cursors. The BeanCollection
+ * has a method next() to obtain the first, next and last bean
+ * in the collection. The BeanCollection does not implement the array
+ * interface nor does it try to act like an array because it cannot go
+ * backward or rewind itself.
+ *
+ * Use the BeanCollection for large datasets where skip/limit is not an
+ * option. Keep in mind that ID-marking (querying a start ID) is a decent
+ * alternative though.
+ *
+ * @file RedBeanPHP/BeanCollection.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class BeanCollection
+{
+ /**
+ * @var Cursor
+ */
+ protected $cursor = NULL;
+
+ /**
+ * @var Repository
+ */
+ protected $repository = NULL;
+
+ /**
+ * @var string
+ */
+ protected $type = NULL;
+
+ /**
+ * @var string
+ */
+ protected $mask = NULL;
+
+ /**
+ * Constructor, creates a new instance of the BeanCollection.
+ *
+ * @param string $type type of beans in this collection
+ * @param Repository $repository repository to use to generate bean objects
+ * @param Cursor $cursor cursor object to use
+ * @param string $mask meta mask to apply (optional)
+ *
+ * @return void
+ */
+ public function __construct( $type, Repository $repository, Cursor $cursor, $mask = '__meta' )
+ {
+ $this->type = $type;
+ $this->cursor = $cursor;
+ $this->repository = $repository;
+ $this->mask = $mask;
+ }
+
+ /**
+ * Returns the next bean in the collection.
+ * If called the first time, this will return the first bean in the collection.
+ * If there are no more beans left in the collection, this method
+ * will return NULL.
+ *
+ * @return OODBBean|NULL
+ */
+ public function next()
+ {
+ $row = $this->cursor->getNextItem();
+ if ( $row ) {
+ $beans = $this->repository->convertToBeans( $this->type, array( $row ), $this->mask );
+ return reset( $beans );
+ }
+ return NULL;
+ }
+
+ /**
+ * Resets the collection from the start, like a fresh() on a bean.
+ *
+ * @return void
+ */
+ public function reset()
+ {
+ $this->cursor->reset();
+ }
+
+ /**
+ * Closes the underlying cursor (needed for some databases).
+ *
+ * @return void
+ */
+ public function close()
+ {
+ $this->cursor->close();
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+/**
+ * QueryWriter
+ * Interface for QueryWriters.
+ * Describes the API for a QueryWriter.
+ *
+ * Terminology:
+ *
+ * - beautified property (a camelCased property, has to be converted first)
+ * - beautified type (a camelCased type, has to be converted first)
+ * - type (a bean type, corresponds directly to a table)
+ * - property (a bean property, corresponds directly to a column)
+ * - table (a checked and quoted type, ready for use in a query)
+ * - column (a checked and quoted property, ready for use in query)
+ * - tableNoQ (same as type, but in context of a database operation)
+ * - columnNoQ (same as property, but in context of a database operation)
+ *
+ * @file RedBeanPHP/QueryWriter.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+interface QueryWriter
+{
+ /**
+ * SQL filter constants
+ */
+ const C_SQLFILTER_READ = 'r';
+ const C_SQLFILTER_WRITE = 'w';
+
+ /**
+ * Query Writer constants.
+ */
+ const C_SQLSTATE_NO_SUCH_TABLE = 1;
+ const C_SQLSTATE_NO_SUCH_COLUMN = 2;
+ const C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION = 3;
+ const C_SQLSTATE_LOCK_TIMEOUT = 4;
+
+ /**
+ * Define data type regions
+ *
+ * 00 - 80: normal data types
+ * 80 - 99: special data types, only scan/code if requested
+ * 99 : specified by user, don't change
+ */
+ const C_DATATYPE_RANGE_SPECIAL = 80;
+ const C_DATATYPE_RANGE_SPECIFIED = 99;
+
+ /**
+ * Define GLUE types for use with glueSQLCondition methods.
+ * Determines how to prefix a snippet of SQL before appending it
+ * to other SQL (or integrating it, mixing it otherwise).
+ *
+ * WHERE - glue as WHERE condition
+ * AND - glue as AND condition
+ */
+ const C_GLUE_WHERE = 1;
+ const C_GLUE_AND = 2;
+
+ /**
+ * CTE Select Snippet
+ * Constants specifying select snippets for CTE queries
+ */
+ const C_CTE_SELECT_NORMAL = FALSE;
+ const C_CTE_SELECT_COUNT = TRUE;
+
+ /**
+ * Parses an sql string to create joins if needed.
+ *
+ * For instance with $type = 'book' and $sql = ' @joined.author.name LIKE ? OR @joined.detail.title LIKE ? '
+ * parseJoin will return the following SQL:
+ * ' LEFT JOIN `author` ON `author`.id = `book`.author_id
+ * LEFT JOIN `detail` ON `detail`.id = `book`.detail_id
+ * WHERE author.name LIKE ? OR detail.title LIKE ? '
+ *
+ * @note this feature requires Narrow Field Mode to be activated (default).
+ *
+ * @note A default implementation is available in AQueryWriter
+ * unless a database uses very different SQL this should suffice.
+ *
+ * @param string $type the source type for the join
+ * @param string $sql the sql string to be parsed
+ *
+ * @return string
+ */
+ public function parseJoin( $type, $sql );
+
+ /**
+ * Writes an SQL Snippet for a JOIN, returns the
+ * SQL snippet string.
+ *
+ * @note A default implementation is available in AQueryWriter
+ * unless a database uses very different SQL this should suffice.
+ *
+ * @param string $type source type
+ * @param string $targetType target type (type to join)
+ * @param string $leftRight type of join (possible: 'LEFT', 'RIGHT' or 'INNER')
+ * @param string $joinType relation between joined tables (possible: 'parent', 'own', 'shared')
+ * @param boolean $firstOfChain is it the join of a chain (or the only join)
+ * @param string $suffix suffix to add for aliasing tables (for joining same table multiple times)
+ *
+ * @return string
+ */
+ public function writeJoin( $type, $targetType, $leftRight, $joinType, $firstOfChain, $suffix );
+
+ /**
+ * Glues an SQL snippet to the beginning of a WHERE clause.
+ * This ensures users don't have to add WHERE to their query snippets.
+ *
+ * The snippet gets prefixed with WHERE or AND
+ * if it starts with a condition.
+ *
+ * If the snippet does NOT start with a condition (or this function thinks so)
+ * the snippet is returned as-is.
+ *
+ * The GLUE type determines the prefix:
+ *
+ * * NONE prefixes with WHERE
+ * * WHERE prefixes with WHERE and replaces AND if snippets starts with AND
+ * * AND prefixes with AND
+ *
+ * This method will never replace WHERE with AND since a snippet should never
+ * begin with WHERE in the first place. OR is not supported.
+ *
+ * Only a limited set of clauses will be recognized as non-conditions.
+ * For instance beginning a snippet with complex statements like JOIN or UNION
+ * will not work. This is too complex for use in a snippet.
+ *
+ * @note A default implementation is available in AQueryWriter
+ * unless a database uses very different SQL this should suffice.
+ *
+ * @param string $sql SQL Snippet
+ * @param integer|NULL $glue the GLUE type - how to glue (C_GLUE_WHERE or C_GLUE_AND)
+ *
+ * @return string
+ */
+ public function glueSQLCondition( $sql, $glue = NULL );
+
+ /**
+ * Determines if there is a LIMIT 1 clause in the SQL.
+ * If not, it will add a LIMIT 1. (used for findOne).
+ *
+ * @note A default implementation is available in AQueryWriter
+ * unless a database uses very different SQL this should suffice.
+ *
+ * @param string $sql query to scan and adjust
+ *
+ * @return string
+ */
+ public function glueLimitOne( $sql );
+
+ /**
+ * Returns the tables that are in the database.
+ *
+ * @return array
+ */
+ public function getTables();
+
+ /**
+ * This method will create a table for the bean.
+ * This methods accepts a type and infers the corresponding table name.
+ *
+ * @param string $type type of bean you want to create a table for
+ *
+ * @return void
+ */
+ public function createTable( $type );
+
+ /**
+ * Returns an array containing all the columns of the specified type.
+ * The format of the return array looks like this:
+ * $field => $type where $field is the name of the column and $type
+ * is a database specific description of the datatype.
+ *
+ * This methods accepts a type and infers the corresponding table name.
+ *
+ * @param string $type type of bean you want to obtain a column list of
+ *
+ * @return array
+ */
+ public function getColumns( $type );
+
+ /**
+ * Returns the Column Type Code (integer) that corresponds
+ * to the given value type. This method is used to determine the minimum
+ * column type required to represent the given value. There are two modes of
+ * operation: with or without special types. Scanning without special types
+ * requires the second parameter to be set to FALSE. This is useful when the
+ * column has already been created and prevents it from being modified to
+ * an incompatible type leading to data loss. Special types will be taken
+ * into account when a column does not exist yet (parameter is then set to TRUE).
+ *
+ * Special column types are determines by the AQueryWriter constant
+ * C_DATA_TYPE_ONLY_IF_NOT_EXISTS (usually 80). Another 'very special' type is type
+ * C_DATA_TYPE_MANUAL (usually 99) which represents a user specified type. Although
+ * no special treatment has been associated with the latter for now.
+ *
+ * @param mixed $value value
+ * @param boolean $alsoScanSpecialForTypes take special types into account
+ *
+ * @return integer
+ */
+ public function scanType( $value, $alsoScanSpecialForTypes = FALSE );
+
+ /**
+ * This method will add a column to a table.
+ * This methods accepts a type and infers the corresponding table name.
+ *
+ * @param string $type name of the table
+ * @param string $column name of the column
+ * @param integer $field data type for field
+ *
+ * @return void
+ */
+ public function addColumn( $type, $column, $field );
+
+ /**
+ * Returns the Type Code for a Column Description.
+ * Given an SQL column description this method will return the corresponding
+ * code for the writer. If the include specials flag is set it will also
+ * return codes for special columns. Otherwise special columns will be identified
+ * as specified columns.
+ *
+ * @param string $typedescription description
+ * @param boolean $includeSpecials whether you want to get codes for special columns as well
+ *
+ * @return integer
+ */
+ public function code( $typedescription, $includeSpecials = FALSE );
+
+ /**
+ * This method will widen the column to the specified data type.
+ * This methods accepts a type and infers the corresponding table name.
+ *
+ * @param string $type type / table that needs to be adjusted
+ * @param string $column column that needs to be altered
+ * @param integer $datatype target data type
+ *
+ * @return void
+ */
+ public function widenColumn( $type, $column, $datatype );
+
+ /**
+ * Selects records from the database.
+ * This methods selects the records from the database that match the specified
+ * type, conditions (optional) and additional SQL snippet (optional).
+ *
+ * @param string $type name of the table you want to query
+ * @param array $conditions criteria ( $column => array( $values ) )
+ * @param string|NULL $addSql additional SQL snippet
+ * @param array $bindings bindings for SQL snippet
+ *
+ * @return array
+ */
+ public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
+
+ /**
+ * Selects records from the database and returns a cursor.
+ * This methods selects the records from the database that match the specified
+ * type, conditions (optional) and additional SQL snippet (optional).
+ *
+ * @param string $type name of the table you want to query
+ * @param array $conditions criteria ( $column => array( $values ) )
+ * @param string|NULL $addSql additional SQL snippet
+ * @param array $bindings bindings for SQL snippet
+ *
+ * @return Cursor
+ */
+ public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() );
+
+ /**
+ * Returns records through an intermediate type. This method is used to obtain records using a link table and
+ * allows the SQL snippets to reference columns in the link table for additional filtering or ordering.
+ *
+ * @param string $sourceType source type, the reference type you want to use to fetch related items on the other side
+ * @param string $destType destination type, the target type you want to get beans of
+ * @param mixed $linkID ID to use for the link table
+ * @param string $addSql Additional SQL snippet
+ * @param array $bindings Bindings for SQL snippet
+ *
+ * @return array
+ */
+ public function queryRecordRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() );
+
+ /**
+ * Returns the row that links $sourceType $sourceID to $destType $destID in an N-M relation.
+ *
+ * @param string $sourceType source type, the first part of the link you're looking for
+ * @param string $destType destination type, the second part of the link you're looking for
+ * @param string $sourceID ID for the source
+ * @param string $destID ID for the destination
+ *
+ * @return array|null
+ */
+ public function queryRecordLink( $sourceType, $destType, $sourceID, $destID );
+
+ /**
+ * Counts the number of records in the database that match the
+ * conditions and additional SQL.
+ *
+ * @param string $type name of the table you want to query
+ * @param array $conditions criteria ( $column => array( $values ) )
+ * @param string|NULL $addSql additional SQL snippet
+ * @param array $bindings bindings for SQL snippet
+ *
+ * @return integer
+ */
+ public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() );
+
+ /**
+ * Returns the number of records linked through $linkType and satisfying the SQL in $addSQL/$bindings.
+ *
+ * @param string $sourceType source type
+ * @param string $targetType the thing you want to count
+ * @param mixed $linkID the of the source type
+ * @param string $addSQL additional SQL snippet
+ * @param array $bindings bindings for SQL snippet
+ *
+ * @return integer
+ */
+ public function queryRecordCountRelated( $sourceType, $targetType, $linkID, $addSQL = '', $bindings = array() );
+
+ /**
+ * Returns all rows of specified type that have been tagged with one of the
+ * strings in the specified tag list array.
+ *
+ * Note that the additional SQL snippet can only be used for pagination,
+ * the SQL snippet will be appended to the end of the query.
+ *
+ * @param string $type the bean type you want to query
+ * @param array $tagList an array of strings, each string containing a tag title
+ * @param boolean $all if TRUE only return records that have been associated with ALL the tags in the list
+ * @param string $addSql addition SQL snippet, for pagination
+ * @param array $bindings parameter bindings for additional SQL snippet
+ *
+ * @return array
+ */
+ public function queryTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() );
+
+ /**
+ * Like queryTagged but only counts.
+ *
+ * @param string $type the bean type you want to query
+ * @param array $tagList an array of strings, each string containing a tag title
+ * @param boolean $all if TRUE only return records that have been associated with ALL the tags in the list
+ * @param string $addSql addition SQL snippet, for pagination
+ * @param array $bindings parameter bindings for additional SQL snippet
+ *
+ * @return integer
+ */
+ public function queryCountTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() );
+
+ /**
+ * Returns all parent rows or child rows of a specified row.
+ * Given a type specifier and a primary key id, this method returns either all child rows
+ * as defined by having _id = id or all parent rows as defined per id = _id
+ * taking into account an optional SQL snippet along with parameters.
+ *
+ * The $select parameter can be used to adjust the select snippet of the query.
+ * Possible values are: C_CTE_SELECT_NORMAL (just select all columns, default), C_CTE_SELECT_COUNT
+ * (count rows) used for countParents and countChildren functions - or you can specify a
+ * string yourself like 'count(distinct brand)'.
+ *
+ * @param string $type the bean type you want to query rows for
+ * @param integer $id id of the reference row
+ * @param boolean $up TRUE to query parent rows, FALSE to query child rows
+ * @param string|NULL $addSql optional SQL snippet to embed in the query
+ * @param array $bindings parameter bindings for additional SQL snippet
+ * @param bool $select Select Snippet to use when querying (optional)
+ *
+ * @return array
+ */
+ public function queryRecursiveCommonTableExpression( $type, $id, $up = TRUE, $addSql = NULL, $bindings = array(), $select = QueryWriter::C_CTE_SELECT_NORMAL );
+
+ /**
+ * This method should update (or insert a record), it takes
+ * a table name, a list of update values ( $field => $value ) and an
+ * primary key ID (optional). If no primary key ID is provided, an
+ * INSERT will take place.
+ * Returns the new ID.
+ * This methods accepts a type and infers the corresponding table name.
+ *
+ * @param string $type name of the table to update
+ * @param array $updatevalues list of update values
+ * @param integer|NULL $id optional primary key ID value
+ *
+ * @return integer
+ */
+ public function updateRecord( $type, $updatevalues, $id = NULL );
+
+ /**
+ * Deletes records from the database.
+ * @note $addSql is always prefixed with ' WHERE ' or ' AND .'
+ *
+ * @param string $type name of the table you want to query
+ * @param array $conditions criteria ( $column => array( $values ) )
+ * @param string $addSql additional SQL
+ * @param array $bindings bindings
+ *
+ * @return int
+ */
+ public function deleteRecord( $type, $conditions = array(), $addSql = '', $bindings = array() );
+
+ /**
+ * Deletes all links between $sourceType and $destType in an N-M relation.
+ *
+ * @param string $sourceType source type
+ * @param string $destType destination type
+ * @param string $sourceID source ID
+ *
+ * @return void
+ */
+ public function deleteRelations( $sourceType, $destType, $sourceID );
+
+ /**
+ * @see QueryWriter::addUniqueConstraint
+ */
+ public function addUniqueIndex( $type, $columns );
+
+ /**
+ * This method will add a UNIQUE constraint index to a table on columns $columns.
+ * This methods accepts a type and infers the corresponding table name.
+ *
+ * @param string $type target bean type
+ * @param array $columnsPartOfIndex columns to include in index
+ *
+ * @return void
+ */
+ public function addUniqueConstraint( $type, $columns );
+
+ /**
+ * This method will check whether the SQL state is in the list of specified states
+ * and returns TRUE if it does appear in this list or FALSE if it
+ * does not. The purpose of this method is to translate the database specific state to
+ * a one of the constants defined in this class and then check whether it is in the list
+ * of standard states provided.
+ *
+ * @param string $state SQL state to consider
+ * @param array $list list of standardized SQL state constants to check against
+ * @param array $extraDriverDetails Some databases communicate state information in a driver-specific format
+ * rather than through the main sqlState code. For those databases, this extra
+ * information can be used to determine the standardized state
+ *
+ * @return boolean
+ */
+ public function sqlStateIn( $state, $list, $extraDriverDetails = array() );
+
+ /**
+ * This method will remove all beans of a certain type.
+ * This methods accepts a type and infers the corresponding table name.
+ *
+ * @param string $type bean type
+ *
+ * @return void
+ */
+ public function wipe( $type );
+
+ /**
+ * This method will add a foreign key from type and field to
+ * target type and target field.
+ * The foreign key is created without an action. On delete/update
+ * no action will be triggered. The FK is only used to allow database
+ * tools to generate pretty diagrams and to make it easy to add actions
+ * later on.
+ * This methods accepts a type and infers the corresponding table name.
+ *
+ *
+ * @param string $type type that will have a foreign key field
+ * @param string $targetType points to this type
+ * @param string $property field that contains the foreign key value
+ * @param string $targetProperty field where the fk points to
+ * @param bool $isDep whether target is dependent and should cascade on update/delete
+ *
+ * @return void
+ */
+ public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE );
+
+ /**
+ * This method will add an index to a type and field with name
+ * $name.
+ * This methods accepts a type and infers the corresponding table name.
+ *
+ * @param string $type type to add index to
+ * @param string $name name of the new index
+ * @param string $property field to index
+ *
+ * @return void
+ */
+ public function addIndex( $type, $name, $property );
+
+ /**
+ * Checks and filters a database structure element like a table of column
+ * for safe use in a query. A database structure has to conform to the
+ * RedBeanPHP DB security policy which basically means only alphanumeric
+ * symbols are allowed. This security policy is more strict than conventional
+ * SQL policies and does therefore not require database specific escaping rules.
+ *
+ * @param string $databaseStructure name of the column/table to check
+ * @param boolean $noQuotes TRUE to NOT put backticks or quotes around the string
+ *
+ * @return string
+ */
+ public function esc( $databaseStructure, $dontQuote = FALSE );
+
+ /**
+ * Removes all tables and views from the database.
+ *
+ * @return void
+ */
+ public function wipeAll();
+
+ /**
+ * Renames an association. For instance if you would like to refer to
+ * album_song as: track you can specify this by calling this method like:
+ *
+ *
+ * renameAssociation('album_song','track')
+ *
+ *
+ * This allows:
+ *
+ *
+ * $album->sharedSong
+ *
+ *
+ * to add/retrieve beans from track instead of album_song.
+ * Also works for exportAll().
+ *
+ * This method also accepts a single associative array as
+ * its first argument.
+ *
+ * @param string|array $fromType original type name, or array
+ * @param string $toType new type name (only if 1st argument is string)
+ *
+ * @return void
+ */
+ public function renameAssocTable( $fromType, $toType = NULL );
+
+ /**
+ * Returns the format for link tables.
+ * Given an array containing two type names this method returns the
+ * name of the link table to be used to store and retrieve
+ * association records. For instance, given two types: person and
+ * project, the corresponding link table might be: 'person_project'.
+ *
+ * @param array $types two types array($type1, $type2)
+ *
+ * @return string
+ */
+ public function getAssocTable( $types );
+
+}
+}
+
+namespace RedBeanPHP\QueryWriter {
+
+use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
+use RedBeanPHP\RedException as RedException;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\RedException\SQL as SQLException;
+
+/**
+ * RedBeanPHP Abstract Query Writer.
+ * Represents an abstract Database to RedBean
+ * To write a driver for a different database for RedBean
+ * Contains a number of functions all implementors can
+ * inherit or override.
+ *
+ * @file RedBeanPHP/QueryWriter/AQueryWriter.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+abstract class AQueryWriter
+{
+ /**
+ * Constant: Select Snippet 'FOR UPDATE'
+ */
+ const C_SELECT_SNIPPET_FOR_UPDATE = 'FOR UPDATE';
+ const C_DATA_TYPE_ONLY_IF_NOT_EXISTS = 80;
+ const C_DATA_TYPE_MANUAL = 99;
+
+ /**
+ * @var array
+ */
+ private static $sqlFilters = array();
+
+ /**
+ * @var boolean
+ */
+ private static $flagSQLFilterSafeMode = FALSE;
+
+ /**
+ * @var boolean
+ */
+ private static $flagNarrowFieldMode = TRUE;
+
+ /**
+ * @var boolean
+ */
+ protected static $flagUseJSONColumns = FALSE;
+
+ /**
+ * @var boolean
+ */
+ protected static $enableISNULLConditions = FALSE;
+
+ /**
+ * @var array
+ */
+ public static $renames = array();
+
+ /**
+ * @var DBAdapter
+ */
+ protected $adapter;
+
+ /**
+ * @var string
+ */
+ protected $defaultValue = 'NULL';
+
+ /**
+ * @var string
+ */
+ protected $quoteCharacter = '';
+
+ /**
+ * @var boolean
+ */
+ protected $flagUseCache = TRUE;
+
+ /**
+ * @var array
+ */
+ protected $cache = array();
+
+ /**
+ * @var integer
+ */
+ protected $maxCacheSizePerType = 20;
+
+ /**
+ * @var string
+ */
+ protected $sqlSelectSnippet = '';
+
+ /**
+ * @var array
+ */
+ public $typeno_sqltype = array();
+
+ /**
+ * @var array
+ */
+ public $sqltype_typeno = array();
+
+ /**
+ * @var array
+ */
+ public $encoding = array();
+
+ /**
+ * @var bool
+ */
+ protected static $noNuke = false;
+
+ /**
+ * @var bool
+ */
+ protected static $treatFalseAsInt = FALSE;
+
+ /**
+ * Sets a data definition template to change the data
+ * creation statements per type.
+ *
+ * For instance to add ROW_FORMAT=DYNAMIC to all MySQL tables
+ * upon creation:
+ *
+ *
+ * $sql = $writer->getDDLTemplate( 'createTable', '*' );
+ * $writer->setDDLTemplate( 'createTable', '*', $sql . ' ROW_FORMAT=DYNAMIC ' );
+ *
+ *
+ * For property-specific templates set $beanType to:
+ * account.username -- then the template will only be applied to SQL statements relating
+ * to that column/property.
+ *
+ * @param string $type ( 'createTable' | 'widenColumn' | 'addColumn' )
+ * @param string $beanType ( type of bean or '*' to apply to all types )
+ * @param string $template SQL template, contains %s for slots
+ *
+ * @return void
+ */
+ public function setDDLTemplate( $type, $beanType, $template )
+ {
+ $this->DDLTemplates[ $type ][ $beanType ] = $template;
+ }
+
+ /**
+ * Returns the specified data definition template.
+ * If no template can be found for the specified type, the template for
+ * '*' will be returned instead.
+ *
+ * @param string $type ( 'createTable' | 'widenColumn' | 'addColumn' )
+ * @param string $beanType ( type of bean or '*' to apply to all types )
+ * @param string|NULL $property specify if you're looking for a property-specific template
+ *
+ * @return string
+ */
+ public function getDDLTemplate( $type, $beanType = '*', $property = NULL )
+ {
+ $key = ( $property ) ? "{$beanType}.{$property}" : $beanType;
+ if ( isset( $this->DDLTemplates[ $type ][ $key ] ) ) {
+ return $this->DDLTemplates[ $type ][ $key ];
+ }
+ if ( isset( $this->DDLTemplates[ $type ][ $beanType ] ) ) {
+ return $this->DDLTemplates[ $type ][ $beanType ];
+ }
+ return $this->DDLTemplates[ $type ][ '*' ];
+ }
+
+ /**
+ * Toggles support for IS-NULL-conditions.
+ * If IS-NULL-conditions are enabled condition arrays
+ * for functions including findLike() are treated so that
+ * 'field' => NULL will be interpreted as field IS NULL
+ * instead of being skipped. Returns the previous
+ * value of the flag.
+ *
+ * @param boolean $flag TRUE or FALSE
+ *
+ * @return boolean
+ */
+ public static function useISNULLConditions( $flag )
+ {
+ $old = self::$enableISNULLConditions;
+ self::$enableISNULLConditions = $flag;
+ return $old;
+ }
+
+ /**
+ * Toggles support for automatic generation of JSON columns.
+ * Using JSON columns means that strings containing JSON will
+ * cause the column to be created (not modified) as a JSON column.
+ * However it might also trigger exceptions if this means the DB attempts to
+ * convert a non-json column to a JSON column. Returns the previous
+ * value of the flag.
+ *
+ * @param boolean $flag TRUE or FALSE
+ *
+ * @return boolean
+ */
+ public static function useJSONColumns( $flag )
+ {
+ $old = self::$flagUseJSONColumns;
+ self::$flagUseJSONColumns = $flag;
+ return $old;
+ }
+
+ /**
+ * Toggles support for nuke().
+ * Can be used to turn off the nuke() feature for security reasons.
+ * Returns the old flag value.
+ *
+ * @param boolean $flag TRUE or FALSE
+ *
+ * @return boolean
+ */
+ public static function forbidNuke( $flag ) {
+ $old = self::$noNuke;
+ self::$noNuke = (bool) $flag;
+ return $old;
+ }
+
+ /**
+ * If set to TRUE, this will cause SQL bindings with an
+ * explicit FALSE value to convert to 0 instead of ''.
+ * Returns the old flag value.
+ *
+ * @param boolean $flag TRUE or FALSE
+ *
+ * @return boolean
+ */
+ public static function treatFalseBindingsAsInt( $flag ) {
+ $old = self::$treatFalseAsInt;
+ self::$treatFalseAsInt = (bool) $flag;
+ return $old;
+ }
+
+ /**
+ * Checks whether a number can be treated like an int.
+ *
+ * @param string $value string representation of a certain value
+ *
+ * @return boolean
+ */
+ public static function canBeTreatedAsInt( $value )
+ {
+ return (bool) ( strval( ($value === FALSE && self::$treatFalseAsInt) ? 0 : $value ) === strval( intval( $value ) ) );
+ }
+
+ /**
+ * @see QueryWriter::getAssocTableFormat
+ */
+ public static function getAssocTableFormat( $types )
+ {
+ sort( $types );
+
+ $assoc = implode( '_', $types );
+
+ return ( isset( self::$renames[$assoc] ) ) ? self::$renames[$assoc] : $assoc;
+ }
+
+ /**
+ * @see QueryWriter::renameAssociation
+ */
+ public static function renameAssociation( $from, $to = NULL )
+ {
+ if ( is_array( $from ) ) {
+ foreach ( $from as $key => $value ) self::$renames[$key] = $value;
+
+ return;
+ }
+
+ self::$renames[$from] = $to;
+ }
+
+ /**
+ * Globally available service method for RedBeanPHP.
+ * Converts a camel cased string to a snake cased string.
+ *
+ * @param string $camel camelCased string to convert to snake case
+ *
+ * @return string
+ */
+ public static function camelsSnake( $camel )
+ {
+ return strtolower( preg_replace( '/(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])/', '_$1$2', $camel ) );
+ }
+
+ /**
+ * Globally available service method for RedBeanPHP.
+ * Converts a snake cased string to a camel cased string.
+ *
+ * @param string $snake snake_cased string to convert to camelCase
+ * @param boolean $dolphin exception for Ids - (bookId -> bookID)
+ * too complicated for the human mind, only dolphins can understand this
+ *
+ * @return string
+ */
+ public static function snakeCamel( $snake, $dolphinMode = false )
+ {
+ $camel = lcfirst( str_replace(' ', '', ucwords( str_replace('_', ' ', $snake ) ) ) );
+ if ( $dolphinMode ) {
+ $camel = preg_replace( '/(\w)Id$/', '$1ID', $camel );
+ }
+ return $camel;
+ }
+
+ /**
+ * Clears renames.
+ *
+ * @return void
+ */
+ public static function clearRenames()
+ {
+ self::$renames = array();
+ }
+
+ /**
+ * Toggles 'Narrow Field Mode'.
+ * In Narrow Field mode the queryRecord method will
+ * narrow its selection field to
+ *
+ * SELECT {table}.*
+ *
+ * instead of
+ *
+ * SELECT *
+ *
+ * This is a better way of querying because it allows
+ * more flexibility (for instance joins). However if you need
+ * the wide selector for backward compatibility; use this method
+ * to turn OFF Narrow Field Mode by passing FALSE.
+ * Default is TRUE.
+ *
+ * @param boolean $narrowField TRUE = Narrow Field FALSE = Wide Field
+ *
+ * @return void
+ */
+ public static function setNarrowFieldMode( $narrowField )
+ {
+ self::$flagNarrowFieldMode = (boolean) $narrowField;
+ }
+
+ /**
+ * Sets SQL filters.
+ * This is a lowlevel method to set the SQL filter array.
+ * The format of this array is:
+ *
+ *
+ * array(
+ * '' => array(
+ * '
' => array(
+ * '' => ''
+ * )
+ * )
+ * )
+ *
+ *
+ * Example:
+ *
+ *
+ * array(
+ * QueryWriter::C_SQLFILTER_READ => array(
+ * 'book' => array(
+ * 'title' => ' LOWER(book.title) '
+ * )
+ * )
+ *
+ *
+ * Note that you can use constants instead of magical chars
+ * as keys for the uppermost array.
+ * This is a lowlevel method. For a more friendly method
+ * please take a look at the facade: R::bindFunc().
+ *
+ * @param array list of filters to set
+ *
+ * @return void
+ */
+ public static function setSQLFilters( $sqlFilters, $safeMode = FALSE )
+ {
+ self::$flagSQLFilterSafeMode = (boolean) $safeMode;
+ self::$sqlFilters = $sqlFilters;
+ }
+
+ /**
+ * Returns current SQL Filters.
+ * This method returns the raw SQL filter array.
+ * This is a lowlevel method. For a more friendly method
+ * please take a look at the facade: R::bindFunc().
+ *
+ * @return array
+ */
+ public static function getSQLFilters()
+ {
+ return self::$sqlFilters;
+ }
+
+ /**
+ * Returns a cache key for the cache values passed.
+ * This method returns a fingerprint string to be used as a key to store
+ * data in the writer cache.
+ *
+ * @param array $keyValues key-value to generate key for
+ *
+ * @return string
+ */
+ private function getCacheKey( $keyValues )
+ {
+ return json_encode( $keyValues );
+ }
+
+ /**
+ * Returns the values associated with the provided cache tag and key.
+ *
+ * @param string $cacheTag cache tag to use for lookup
+ * @param string $key key to use for lookup
+ *
+ * @return mixed
+ */
+ private function getCached( $cacheTag, $key )
+ {
+ $sql = $this->adapter->getSQL();
+ if ($this->updateCache()) {
+ if ( isset( $this->cache[$cacheTag][$key] ) ) {
+ return $this->cache[$cacheTag][$key];
+ }
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Checks if the previous query had a keep-cache tag.
+ * If so, the cache will persist, otherwise the cache will be flushed.
+ *
+ * Returns TRUE if the cache will remain and FALSE if a flush has
+ * been performed.
+ *
+ * @return boolean
+ */
+ private function updateCache()
+ {
+ $sql = $this->adapter->getSQL();
+ if ( strpos( $sql, '-- keep-cache' ) !== strlen( $sql ) - 13 ) {
+ // If SQL has been taken place outside of this method then something else then
+ // a select query might have happened! (or instruct to keep cache)
+ $this->cache = array();
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * Stores data from the writer in the cache under a specific key and cache tag.
+ * A cache tag is used to make sure the cache remains consistent. In most cases the cache tag
+ * will be the bean type, this makes sure queries associated with a certain reference type will
+ * never contain conflicting data.
+ * Why not use the cache tag as a key? Well
+ * we need to make sure the cache contents fits the key (and key is based on the cache values).
+ * Otherwise it would be possible to store two different result sets under the same key (the cache tag).
+ *
+ * In previous versions you could only store one key-entry, I have changed this to
+ * improve caching efficiency (issue #400).
+ *
+ * @param string $cacheTag cache tag (secondary key)
+ * @param string $key key to store values under
+ * @param array|int $values rows or count to be stored
+ *
+ * @return void
+ */
+ private function putResultInCache( $cacheTag, $key, $values )
+ {
+ if ( isset( $this->cache[$cacheTag] ) ) {
+ if ( count( $this->cache[$cacheTag] ) > $this->maxCacheSizePerType ) array_shift( $this->cache[$cacheTag] );
+ } else {
+ $this->cache[$cacheTag] = array();
+ }
+ $this->cache[$cacheTag][$key] = $values;
+ }
+
+ /**
+ * Creates an SQL snippet from a list of conditions of format:
+ *
+ *
+ * array(
+ * key => array(
+ * value1, value2, value3 ....
+ * )
+ * )
+ *
+ *
+ * @param array $conditions list of conditions
+ * @param array $bindings parameter bindings for SQL snippet
+ * @param string $addSql additional SQL snippet to append to result
+ *
+ * @return string
+ */
+ private function makeSQLFromConditions( $conditions, &$bindings, $addSql = '' )
+ {
+ reset( $bindings );
+ $firstKey = key( $bindings );
+ $paramTypeIsNum = ( is_numeric( $firstKey ) );
+ $counter = 0;
+
+ $sqlConditions = array();
+ foreach ( $conditions as $column => $values ) {
+ if ( $values === NULL ) {
+ if ( self::$enableISNULLConditions ) {
+ $sqlConditions[] = $this->esc( $column ) . ' IS NULL';
+ }
+ continue;
+ }
+
+ if ( is_array( $values ) ) {
+ if ( empty( $values ) ) continue;
+ } else {
+ $values = array( $values );
+ }
+
+ $checkOODB = reset( $values );
+ if ( $checkOODB instanceof OODBBean && $checkOODB->getMeta( 'type' ) === $column && substr( $column, -3 ) != '_id' )
+ $column = $column . '_id';
+
+
+ $sql = $this->esc( $column );
+ $sql .= ' IN ( ';
+
+ if ( $paramTypeIsNum ) {
+ $sql .= implode( ',', array_fill( 0, count( $values ), '?' ) ) . ' ) ';
+
+ array_unshift($sqlConditions, $sql);
+
+ foreach ( $values as $k => $v ) {
+ if ( $v instanceof OODBBean ) {
+ $v = $v->id;
+ }
+ $values[$k] = strval( $v );
+
+ array_unshift( $bindings, $v );
+ }
+ } else {
+
+ $slots = array();
+
+ foreach( $values as $k => $v ) {
+ if ( $v instanceof OODBBean ) {
+ $v = $v->id;
+ }
+ $slot = ':slot'.$counter++;
+ $slots[] = $slot;
+ $bindings[$slot] = strval( $v );
+ }
+
+ $sql .= implode( ',', $slots ).' ) ';
+ $sqlConditions[] = $sql;
+ }
+ }
+
+ $sql = '';
+ if ( !empty( $sqlConditions ) ) {
+ $sql .= " WHERE ( " . implode( ' AND ', $sqlConditions ) . ") ";
+ }
+
+ $addSql = $this->glueSQLCondition( $addSql, !empty( $sqlConditions ) ? QueryWriter::C_GLUE_AND : NULL );
+ if ( $addSql ) $sql .= $addSql;
+
+ return $sql;
+ }
+
+ /**
+ * Returns the table names and column names for a relational query.
+ *
+ * @param string $sourceType type of the source bean
+ * @param string $destType type of the bean you want to obtain using the relation
+ * @param boolean $noQuote TRUE if you want to omit quotes
+ *
+ * @return array
+ */
+ private function getRelationalTablesAndColumns( $sourceType, $destType, $noQuote = FALSE )
+ {
+ $linkTable = $this->esc( $this->getAssocTable( array( $sourceType, $destType ) ), $noQuote );
+ $sourceCol = $this->esc( $sourceType . '_id', $noQuote );
+
+ if ( $sourceType === $destType ) {
+ $destCol = $this->esc( $destType . '2_id', $noQuote );
+ } else {
+ $destCol = $this->esc( $destType . '_id', $noQuote );
+ }
+
+ $sourceTable = $this->esc( $sourceType, $noQuote );
+ $destTable = $this->esc( $destType, $noQuote );
+
+ return array( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol );
+ }
+
+ /**
+ * Determines whether a string can be considered JSON or not.
+ * This is used by writers that support JSON columns. However
+ * we don't want that code duplicated over all JSON supporting
+ * Query Writers.
+ *
+ * @param string $value value to determine 'JSONness' of.
+ *
+ * @return boolean
+ */
+ protected function isJSON( $value )
+ {
+ return (
+ is_string($value) &&
+ is_array(json_decode($value, TRUE)) &&
+ (json_last_error() == JSON_ERROR_NONE)
+ );
+ }
+
+ /**
+ * Given a type and a property name this method
+ * returns the foreign key map section associated with this pair.
+ *
+ * @param string $type name of the type
+ * @param string $property name of the property
+ *
+ * @return array|NULL
+ */
+ protected function getForeignKeyForTypeProperty( $type, $property )
+ {
+ $property = $this->esc( $property, TRUE );
+
+ try {
+ $map = $this->getKeyMapForType( $type );
+ } catch ( SQLException $e ) {
+ return NULL;
+ }
+
+ foreach( $map as $key ) {
+ if ( $key['from'] === $property ) return $key;
+ }
+ return NULL;
+ }
+
+ /**
+ * Returns the foreign key map (FKM) for a type.
+ * A foreign key map describes the foreign keys in a table.
+ * A FKM always has the same structure:
+ *
+ *
+ * array(
+ * 'name' =>
+ * 'from' =>
+ * 'table' =>
+ * 'to' => (most of the time 'id')
+ * 'on_update' =>
+ * 'on_delete' =>
+ * )
+ *
+ *
+ * @note the keys in the result array are FKDLs, i.e. descriptive unique
+ * keys per source table. Also see: AQueryWriter::makeFKLabel for details.
+ *
+ * @param string $type the bean type you wish to obtain a key map of
+ *
+ * @return array
+ */
+ protected function getKeyMapForType( $type )
+ {
+ return array();
+ }
+
+ /**
+ * This method makes a key for a foreign key description array.
+ * This key is a readable string unique for every source table.
+ * This uniform key is called the FKDL Foreign Key Description Label.
+ * Note that the source table is not part of the FKDL because
+ * this key is supposed to be 'per source table'. If you wish to
+ * include a source table, prefix the key with 'on_table__'.
+ *
+ * @param string $from the column of the key in the source table
+ * @param string $type the type (table) where the key points to
+ * @param string $to the target column of the foreign key (mostly just 'id')
+ *
+ * @return string
+ */
+ protected function makeFKLabel($from, $type, $to)
+ {
+ return "from_{$from}_to_table_{$type}_col_{$to}";
+ }
+
+ /**
+ * Returns an SQL Filter snippet for reading.
+ *
+ * @param string $type type of bean
+ *
+ * @return string
+ */
+ protected function getSQLFilterSnippet( $type )
+ {
+ $existingCols = array();
+ if (self::$flagSQLFilterSafeMode) {
+ $existingCols = $this->getColumns( $type );
+ }
+
+ $sqlFilters = array();
+ if ( isset( self::$sqlFilters[QueryWriter::C_SQLFILTER_READ][$type] ) ) {
+ foreach( self::$sqlFilters[QueryWriter::C_SQLFILTER_READ][$type] as $property => $sqlFilter ) {
+ if ( !self::$flagSQLFilterSafeMode || isset( $existingCols[$property] ) ) {
+ $sqlFilters[] = $sqlFilter.' AS '.$property.' ';
+ }
+ }
+ }
+ $sqlFilterStr = ( count($sqlFilters) ) ? ( ','.implode( ',', $sqlFilters ) ) : '';
+ return $sqlFilterStr;
+ }
+
+ /**
+ * Generates a list of parameters (slots) for an SQL snippet.
+ * This method calculates the correct number of slots to insert in the
+ * SQL snippet and determines the correct type of slot. If the bindings
+ * array contains named parameters this method will return named ones and
+ * update the keys in the value list accordingly (that's why we use the &).
+ *
+ * If you pass an offset the bindings will be re-added to the value list.
+ * Some databases cant handle duplicate parameter names in queries.
+ *
+ * @param array &$valueList list of values to generate slots for (gets modified if needed)
+ * @param array $otherBindings list of additional bindings
+ * @param integer $offset start counter at...
+ *
+ * @return string
+ */
+ protected function getParametersForInClause( &$valueList, $otherBindings, $offset = 0 )
+ {
+ if ( is_array( $otherBindings ) && count( $otherBindings ) > 0 ) {
+ reset( $otherBindings );
+
+ $key = key( $otherBindings );
+
+ if ( !is_numeric($key) ) {
+ $filler = array();
+ $newList = (!$offset) ? array() : $valueList;
+ $counter = $offset;
+
+ foreach( $valueList as $value ) {
+ $slot = ':slot' . ( $counter++ );
+ $filler[] = $slot;
+ $newList[$slot] = $value;
+ }
+
+ // Change the keys!
+ $valueList = $newList;
+
+ return implode( ',', $filler );
+ }
+ }
+
+ return implode( ',', array_fill( 0, count( $valueList ), '?' ) );
+ }
+
+ /**
+ * Adds a data type to the list of data types.
+ * Use this method to add a new column type definition to the writer.
+ * Used for UUID support.
+ *
+ * @param integer $dataTypeID magic number constant assigned to this data type
+ * @param string $SQLDefinition SQL column definition (e.g. INT(11))
+ *
+ * @return self
+ */
+ protected function addDataType( $dataTypeID, $SQLDefinition )
+ {
+ $this->typeno_sqltype[ $dataTypeID ] = $SQLDefinition;
+ $this->sqltype_typeno[ $SQLDefinition ] = $dataTypeID;
+ return $this;
+ }
+
+ /**
+ * Returns the sql that should follow an insert statement.
+ *
+ * @param string $table name
+ *
+ * @return string
+ */
+ protected function getInsertSuffix( $table )
+ {
+ return '';
+ }
+
+ /**
+ * Checks whether a value starts with zeros. In this case
+ * the value should probably be stored using a text datatype instead of a
+ * numerical type in order to preserve the zeros.
+ *
+ * @param string $value value to be checked.
+ *
+ * @return boolean
+ */
+ protected function startsWithZeros( $value )
+ {
+ $value = strval( $value );
+
+ if ( strlen( $value ) > 1 && strpos( $value, '0' ) === 0 && strpos( $value, '0.' ) !== 0 ) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ /**
+ * Inserts a record into the database using a series of insert columns
+ * and corresponding insertvalues. Returns the insert id.
+ *
+ * @param string $table table to perform query on
+ * @param array $insertcolumns columns to be inserted
+ * @param array $insertvalues values to be inserted
+ *
+ * @return integer
+ */
+ protected function insertRecord( $type, $insertcolumns, $insertvalues )
+ {
+ $default = $this->defaultValue;
+ $suffix = $this->getInsertSuffix( $type );
+ $table = $this->esc( $type );
+
+ if ( count( $insertvalues ) > 0 && is_array( $insertvalues[0] ) && count( $insertvalues[0] ) > 0 ) {
+
+ $insertSlots = array();
+ foreach ( $insertcolumns as $k => $v ) {
+ $insertcolumns[$k] = $this->esc( $v );
+
+ if (isset(self::$sqlFilters['w'][$type][$v])) {
+ $insertSlots[] = self::$sqlFilters['w'][$type][$v];
+ } else {
+ $insertSlots[] = '?';
+ }
+ }
+
+ $insertSQL = "INSERT INTO $table ( id, " . implode( ',', $insertcolumns ) . " ) VALUES
+ ( $default, " . implode( ',', $insertSlots ) . " ) $suffix";
+
+ $ids = array();
+ foreach ( $insertvalues as $i => $insertvalue ) {
+ $ids[] = $this->adapter->getCell( $insertSQL, $insertvalue, $i );
+ }
+
+ $result = count( $ids ) === 1 ? array_pop( $ids ) : $ids;
+ } else {
+ $result = $this->adapter->getCell( "INSERT INTO $table (id) VALUES($default) $suffix" );
+ }
+
+ if ( $suffix ) return $result;
+
+ $last_id = $this->adapter->getInsertID();
+
+ return $last_id;
+ }
+
+ /**
+ * Checks table name or column name.
+ *
+ * @param string $table table string
+ *
+ * @return string
+ */
+ protected function check( $struct )
+ {
+ if ( !is_string( $struct ) || !preg_match( '/^[a-zA-Z0-9_]+$/', $struct ) ) {
+ throw new RedException( 'Identifier does not conform to RedBeanPHP security policies.' );
+ }
+
+ return $struct;
+ }
+
+ /**
+ * Checks whether the specified type (i.e. table) already exists in the database.
+ * Not part of the Object Database interface!
+ *
+ * @param string $table table name
+ *
+ * @return boolean
+ */
+ public function tableExists( $table )
+ {
+ $tables = $this->getTables();
+
+ return in_array( $table, $tables );
+ }
+
+ /**
+ * @see QueryWriter::glueSQLCondition
+ */
+ public function glueSQLCondition( $sql, $glue = NULL )
+ {
+ static $snippetCache = array();
+
+ if ( is_null( $sql ) ) {
+ return '';
+ }
+
+ if ( trim( $sql ) === '' ) {
+ return $sql;
+ }
+
+ $key = $glue . '|' . $sql;
+
+ if ( isset( $snippetCache[$key] ) ) {
+ return $snippetCache[$key];
+ }
+
+ $lsql = ltrim( $sql );
+
+ if ( preg_match( '/^(INNER|LEFT|RIGHT|JOIN|AND|OR|WHERE|ORDER|GROUP|HAVING|LIMIT|OFFSET)\s+/i', $lsql ) ) {
+ if ( $glue === QueryWriter::C_GLUE_WHERE && stripos( $lsql, 'AND' ) === 0 ) {
+ $snippetCache[$key] = ' WHERE ' . substr( $lsql, 3 );
+ } else {
+ $snippetCache[$key] = $sql;
+ }
+ } else {
+ $snippetCache[$key] = ( ( $glue === QueryWriter::C_GLUE_AND ) ? ' AND ' : ' WHERE ') . $sql;
+ }
+
+ return $snippetCache[$key];
+ }
+
+ /**
+ * @see QueryWriter::glueLimitOne
+ */
+ public function glueLimitOne( $sql = '')
+ {
+ return ( strpos( strtoupper( ' ' . $sql ), ' LIMIT ' ) === FALSE ) ? ( $sql . ' LIMIT 1 ' ) : $sql;
+ }
+
+ /**
+ * @see QueryWriter::esc
+ */
+ public function esc( $dbStructure, $dontQuote = FALSE )
+ {
+ $this->check( $dbStructure );
+
+ return ( $dontQuote ) ? $dbStructure : $this->quoteCharacter . $dbStructure . $this->quoteCharacter;
+ }
+
+ /**
+ * @see QueryWriter::addColumn
+ */
+ public function addColumn( $beanType, $column, $field )
+ {
+ $table = $beanType;
+ $type = $field;
+ $table = $this->esc( $table );
+ $column = $this->esc( $column );
+
+ $type = ( isset( $this->typeno_sqltype[$type] ) ) ? $this->typeno_sqltype[$type] : '';
+
+ $this->adapter->exec( sprintf( $this->getDDLTemplate('addColumn', $beanType, $column ), $table, $column, $type ) );
+ }
+
+ /**
+ * @see QueryWriter::updateRecord
+ */
+ public function updateRecord( $type, $updatevalues, $id = NULL )
+ {
+ $table = $type;
+
+ if ( !$id ) {
+ $insertcolumns = $insertvalues = array();
+
+ foreach ( $updatevalues as $pair ) {
+ $insertcolumns[] = $pair['property'];
+ $insertvalues[] = $pair['value'];
+ }
+
+ //Otherwise psql returns string while MySQL/SQLite return numeric causing problems with additions (array_diff)
+ return (string) $this->insertRecord( $table, $insertcolumns, array( $insertvalues ) );
+ }
+
+ if ( $id && !count( $updatevalues ) ) {
+ return $id;
+ }
+
+ $table = $this->esc( $table );
+ $sql = "UPDATE $table SET ";
+
+ $p = $v = array();
+
+ foreach ( $updatevalues as $uv ) {
+
+ if ( isset( self::$sqlFilters['w'][$type][$uv['property']] ) ) {
+ $p[] = " {$this->esc( $uv["property"] )} = ". self::$sqlFilters['w'][$type][$uv['property']];
+ } else {
+ $p[] = " {$this->esc( $uv["property"] )} = ? ";
+ }
+
+ $v[] = $uv['value'];
+ }
+
+ $sql .= implode( ',', $p ) . ' WHERE id = ? ';
+
+ $v[] = $id;
+
+ $this->adapter->exec( $sql, $v );
+
+ return $id;
+ }
+
+ /**
+ * @see QueryWriter::parseJoin
+ */
+ public function parseJoin( $type, $sql, $cteType = NULL )
+ {
+ if ( strpos( $sql, '@' ) === FALSE ) {
+ return $sql;
+ }
+
+ $sql = ' ' . $sql;
+ $joins = array();
+ $joinSql = '';
+
+ if ( !preg_match_all( '#@((shared|own|joined)\.[^\s(,=!?]+)#', $sql, $matches ) )
+ return $sql;
+
+ $expressions = $matches[1];
+ // Sort to make the joins from the longest to the shortest
+ uasort( $expressions, function($a, $b) {
+ return substr_count( $b, '.' ) - substr_count( $a, '.' );
+ });
+
+ $nsuffix = 1;
+ foreach ( $expressions as $exp ) {
+ $explosion = explode( '.', $exp );
+ $joinTable = $type;
+ $joinType = array_shift( $explosion );
+ $lastPart = array_pop( $explosion );
+ $lastJoin = end($explosion);
+ if ( ( $index = strpos( $lastJoin, '[' ) ) !== FALSE ) {
+ $lastJoin = substr( $lastJoin, 0, $index);
+ }
+ reset($explosion);
+
+ // Let's check if we already joined that chain
+ // If that's the case we skip this
+ $joinKey = implode( '.', $explosion );
+ foreach ( $joins as $chain => $suffix ) {
+ if ( strpos ( $chain, $joinKey ) === 0 ) {
+ $sql = str_replace( "@{$exp}", "{$lastJoin}__rb{$suffix}.{$lastPart}", $sql );
+ continue 2;
+ }
+ }
+ $sql = str_replace( "@{$exp}", "{$lastJoin}__rb{$nsuffix}.{$lastPart}", $sql );
+ $joins[$joinKey] = $nsuffix;
+
+ // We loop on the elements of the join
+ $i = 0;
+ while ( TRUE ) {
+ $joinInfo = $explosion[$i];
+ if ( $i ) {
+ $joinType = $explosion[$i-1];
+ $joinTable = $explosion[$i-2];
+ }
+
+ $aliases = array();
+ if ( ( $index = strpos( $joinInfo, '[' ) ) !== FALSE ) {
+ if ( preg_match_all( '#(([^\s:/\][]+)[/\]])#', $joinInfo, $matches ) ) {
+ $aliases = $matches[2];
+ $joinInfo = substr( $joinInfo, 0, $index);
+ }
+ }
+ if ( ( $index = strpos( $joinTable, '[' ) ) !== FALSE ) {
+ $joinTable = substr( $joinTable, 0, $index);
+ }
+
+ if ( $i ) {
+ $joinSql .= $this->writeJoin( $joinTable, $joinInfo, 'INNER', $joinType, FALSE, "__rb{$nsuffix}", $aliases, NULL );
+ } else {
+ $joinSql .= $this->writeJoin( $joinTable, $joinInfo, 'LEFT', $joinType, TRUE, "__rb{$nsuffix}", $aliases, $cteType );
+ }
+
+ $i += 2;
+ if ( !isset( $explosion[$i] ) ) {
+ break;
+ }
+ }
+ $nsuffix++;
+ }
+
+ $sql = str_ireplace( ' where ', ' WHERE ', $sql );
+ if ( strpos( $sql, ' WHERE ') === FALSE ) {
+ if ( preg_match( '/^(ORDER|GROUP|HAVING|LIMIT|OFFSET)\s+/i', trim($sql) ) ) {
+ $sql = "{$joinSql} {$sql}";
+ } else {
+ $sql = "{$joinSql} WHERE {$sql}";
+ }
+ } else {
+ $sqlParts = explode( ' WHERE ', $sql, 2 );
+ $sql = "{$sqlParts[0]} {$joinSql} WHERE {$sqlParts[1]}";
+ }
+
+ return $sql;
+ }
+
+ /**
+ * @see QueryWriter::writeJoin
+ */
+ public function writeJoin( $type, $targetType, $leftRight = 'LEFT', $joinType = 'parent', $firstOfChain = TRUE, $suffix = '', $aliases = array(), $cteType = NULL )
+ {
+ if ( $leftRight !== 'LEFT' && $leftRight !== 'RIGHT' && $leftRight !== 'INNER' )
+ throw new RedException( 'Invalid JOIN.' );
+
+ $globalAliases = OODBBean::getAliases();
+ if ( isset( $globalAliases[$targetType] ) ) {
+ $destType = $globalAliases[$targetType];
+ $asTargetTable = $this->esc( $targetType.$suffix );
+ } else {
+ $destType = $targetType;
+ $asTargetTable = $this->esc( $destType.$suffix );
+ }
+
+ if ( $firstOfChain ) {
+ $table = $this->esc( $type );
+ } else {
+ $table = $this->esc( $type.$suffix );
+ }
+ $targetTable = $this->esc( $destType );
+
+ if ( $joinType == 'shared' ) {
+
+ if ( isset( $globalAliases[$type] ) ) {
+ $field = $this->esc( $globalAliases[$type], TRUE );
+ if ( $aliases && count( $aliases ) === 1 ) {
+ $assocTable = reset( $aliases );
+ } else {
+ $assocTable = $this->getAssocTable( array( $cteType ? $cteType : $globalAliases[$type], $destType ) );
+ }
+ } else {
+ $field = $this->esc( $type, TRUE );
+ if ( $aliases && count( $aliases ) === 1 ) {
+ $assocTable = reset( $aliases );
+ } else {
+ $assocTable = $this->getAssocTable( array( $cteType ? $cteType : $type, $destType ) );
+ }
+ }
+ $linkTable = $this->esc( $assocTable );
+ $asLinkTable = $this->esc( $assocTable.$suffix );
+ $leftField = "id";
+ $rightField = $cteType ? "{$cteType}_id" : "{$field}_id";
+ $linkField = $this->esc( $destType, TRUE );
+ $linkLeftField = "id";
+ $linkRightField = "{$linkField}_id";
+
+ $joinSql = " {$leftRight} JOIN {$linkTable}";
+ if ( isset( $globalAliases[$targetType] ) || $suffix ) {
+ $joinSql .= " AS {$asLinkTable}";
+ }
+ $joinSql .= " ON {$table}.{$leftField} = {$asLinkTable}.{$rightField}";
+ $joinSql .= " {$leftRight} JOIN {$targetTable}";
+ if ( isset( $globalAliases[$targetType] ) || $suffix ) {
+ $joinSql .= " AS {$asTargetTable}";
+ }
+ $joinSql .= " ON {$asTargetTable}.{$linkLeftField} = {$asLinkTable}.{$linkRightField}";
+
+ } elseif ( $joinType == 'own' ) {
+
+ $field = $this->esc( $type, TRUE );
+ $rightField = "id";
+
+ $joinSql = " {$leftRight} JOIN {$targetTable}";
+ if ( isset( $globalAliases[$targetType] ) || $suffix ) {
+ $joinSql .= " AS {$asTargetTable}";
+ }
+
+ if ( $aliases ) {
+ $conditions = array();
+ foreach ( $aliases as $alias ) {
+ $conditions[] = "{$asTargetTable}.{$alias}_id = {$table}.{$rightField}";
+ }
+ $joinSql .= " ON ( " . implode( ' OR ', $conditions ) . " ) ";
+ } else {
+ $leftField = $cteType ? "{$cteType}_id" : "{$field}_id";
+ $joinSql .= " ON {$asTargetTable}.{$leftField} = {$table}.{$rightField} ";
+ }
+
+ } else {
+
+ $field = $this->esc( $targetType, TRUE );
+ $leftField = "id";
+
+ $joinSql = " {$leftRight} JOIN {$targetTable}";
+ if ( isset( $globalAliases[$targetType] ) || $suffix ) {
+ $joinSql .= " AS {$asTargetTable}";
+ }
+
+ if ( $aliases ) {
+ $conditions = array();
+ foreach ( $aliases as $alias ) {
+ $conditions[] = "{$asTargetTable}.{$leftField} = {$table}.{$alias}_id";
+ }
+ $joinSql .= " ON ( " . implode( ' OR ', $conditions ) . " ) ";
+ } else {
+ $rightField = "{$field}_id";
+ $joinSql .= " ON {$asTargetTable}.{$leftField} = {$table}.{$rightField} ";
+ }
+
+ }
+
+ return $joinSql;
+ }
+
+ /**
+ * Sets an SQL snippet to be used for the next queryRecord() operation.
+ * A select snippet will be inserted at the end of the SQL select statement and
+ * can be used to modify SQL-select commands to enable locking, for instance
+ * using the 'FOR UPDATE' snippet (this will generate an SQL query like:
+ * 'SELECT * FROM ... FOR UPDATE'. After the query has been executed the
+ * SQL snippet will be erased. Note that only the first upcoming direct or
+ * indirect invocation of queryRecord() through batch(), find() or load()
+ * will be affected. The SQL snippet will be cached.
+ *
+ * @param string $sql SQL snippet to use in SELECT statement.
+ *
+ * return self
+ */
+ public function setSQLSelectSnippet( $sqlSelectSnippet = '' ) {
+ $this->sqlSelectSnippet = $sqlSelectSnippet;
+ return $this;
+ }
+
+ /**
+ * @see QueryWriter::queryRecord
+ */
+ public function queryRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
+ {
+ if ( $this->flagUseCache && $this->sqlSelectSnippet != self::C_SELECT_SNIPPET_FOR_UPDATE ) {
+ $key = $this->getCacheKey( array( $conditions, trim("$addSql {$this->sqlSelectSnippet}"), $bindings, 'select' ) );
+ if ( $cached = $this->getCached( $type, $key ) ) {
+ return $cached;
+ }
+ }
+
+ $table = $this->esc( $type );
+
+ $sqlFilterStr = '';
+ if ( count( self::$sqlFilters ) ) {
+ $sqlFilterStr = $this->getSQLFilterSnippet( $type );
+ }
+
+ if ( is_array ( $conditions ) && !empty ( $conditions ) ) {
+ $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
+ } else {
+ $sql = $this->glueSQLCondition( $addSql );
+ }
+ $sql = $this->parseJoin( $type, $sql );
+ $fieldSelection = self::$flagNarrowFieldMode ? "{$table}.*" : '*';
+ $sql = "SELECT {$fieldSelection} {$sqlFilterStr} FROM {$table} {$sql} {$this->sqlSelectSnippet} -- keep-cache";
+ $this->sqlSelectSnippet = '';
+ $rows = $this->adapter->get( $sql, $bindings );
+
+ if ( $this->flagUseCache && !empty( $key ) ) {
+ $this->putResultInCache( $type, $key, $rows );
+ }
+
+ return $rows;
+ }
+
+ /**
+ * @see QueryWriter::queryRecordWithCursor
+ */
+ public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() )
+ {
+ $table = $this->esc( $type );
+
+ $sqlFilterStr = '';
+ if ( count( self::$sqlFilters ) ) {
+ $sqlFilterStr = $this->getSQLFilterSnippet( $type );
+ }
+
+ $sql = $this->glueSQLCondition( $addSql, NULL );
+
+ $sql = $this->parseJoin( $type, $sql );
+ $fieldSelection = self::$flagNarrowFieldMode ? "{$table}.*" : '*';
+
+ $sql = "SELECT {$fieldSelection} {$sqlFilterStr} FROM {$table} {$sql} -- keep-cache";
+
+ return $this->adapter->getCursor( $sql, $bindings );
+ }
+
+ /**
+ * @see QueryWriter::queryRecordRelated
+ */
+ public function queryRecordRelated( $sourceType, $destType, $linkIDs, $addSql = '', $bindings = array() )
+ {
+ list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
+
+ if ( $this->flagUseCache ) {
+ $key = $this->getCacheKey( array( $sourceType, implode( ',', $linkIDs ), trim($addSql), $bindings, 'selectrelated' ) );
+ if ( $cached = $this->getCached( $destType, $key ) ) {
+ return $cached;
+ }
+ }
+
+ $addSql = $this->glueSQLCondition( $addSql, QueryWriter::C_GLUE_WHERE );
+ $inClause = $this->getParametersForInClause( $linkIDs, $bindings );
+
+ $sqlFilterStr = '';
+ if ( count( self::$sqlFilters ) ) {
+ $sqlFilterStr = $this->getSQLFilterSnippet( $destType );
+ }
+
+ if ( $sourceType === $destType ) {
+ $inClause2 = $this->getParametersForInClause( $linkIDs, $bindings, count( $bindings ) ); //for some databases
+ $sql = "
+ SELECT
+ {$destTable}.* {$sqlFilterStr} ,
+ COALESCE(
+ NULLIF({$linkTable}.{$sourceCol}, {$destTable}.id),
+ NULLIF({$linkTable}.{$destCol}, {$destTable}.id)) AS linked_by
+ FROM {$linkTable}
+ INNER JOIN {$destTable} ON
+ ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) ) OR
+ ( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} IN ($inClause2) )
+ {$addSql}
+ -- keep-cache";
+
+ $linkIDs = array_merge( $linkIDs, $linkIDs );
+ } else {
+ $sql = "
+ SELECT
+ {$destTable}.* {$sqlFilterStr},
+ {$linkTable}.{$sourceCol} AS linked_by
+ FROM {$linkTable}
+ INNER JOIN {$destTable} ON
+ ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} IN ($inClause) )
+ {$addSql}
+ -- keep-cache";
+ }
+
+ $bindings = array_merge( $linkIDs, $bindings );
+
+ $rows = $this->adapter->get( $sql, $bindings );
+
+ if ( $this->flagUseCache ) {
+ $this->putResultInCache( $destType, $key, $rows );
+ }
+
+ return $rows;
+ }
+
+ /**
+ * @see QueryWriter::queryRecordLink
+ */
+ public function queryRecordLink( $sourceType, $destType, $sourceID, $destID )
+ {
+ list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
+
+ if ( $this->flagUseCache ) {
+ $key = $this->getCacheKey( array( $sourceType, $destType, $sourceID, $destID, 'selectlink' ) );
+ if ( $cached = $this->getCached( $linkTable, $key ) ) {
+ return $cached;
+ }
+ }
+
+ $sqlFilterStr = '';
+ if ( count( self::$sqlFilters ) ) {
+ $linkType = $this->getAssocTable( array( $sourceType, $destType ) );
+ $sqlFilterStr = $this->getSQLFilterSnippet( "{$linkType}" );
+ }
+
+ if ( $sourceTable === $destTable ) {
+ $sql = "SELECT {$linkTable}.* {$sqlFilterStr} FROM {$linkTable}
+ WHERE ( {$sourceCol} = ? AND {$destCol} = ? ) OR
+ ( {$destCol} = ? AND {$sourceCol} = ? ) -- keep-cache";
+ $row = $this->adapter->getRow( $sql, array( $sourceID, $destID, $sourceID, $destID ) );
+ } else {
+ $sql = "SELECT {$linkTable}.* {$sqlFilterStr} FROM {$linkTable}
+ WHERE {$sourceCol} = ? AND {$destCol} = ? -- keep-cache";
+ $row = $this->adapter->getRow( $sql, array( $sourceID, $destID ) );
+ }
+
+ if ( $this->flagUseCache ) {
+ $this->putResultInCache( $linkTable, $key, $row );
+ }
+
+ return $row;
+ }
+
+ /**
+ * Returns or counts all rows of specified type that have been tagged with one of the
+ * strings in the specified tag list array.
+ *
+ * Note that the additional SQL snippet can only be used for pagination,
+ * the SQL snippet will be appended to the end of the query.
+ *
+ * @param string $type the bean type you want to query
+ * @param array $tagList an array of strings, each string containing a tag title
+ * @param boolean $all if TRUE only return records that have been associated with ALL the tags in the list
+ * @param string $addSql addition SQL snippet, for pagination
+ * @param array $bindings parameter bindings for additional SQL snippet
+ * @param string $wrap SQL wrapper string (use %s for subquery)
+ *
+ * @return array
+ */
+ private function queryTaggedGeneric( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array(), $wrap = '%s' )
+ {
+ if ( $this->flagUseCache ) {
+ $key = $this->getCacheKey( array( implode( ',', $tagList ), $all, trim($addSql), $bindings, 'selectTagged' ) );
+ if ( $cached = $this->getCached( $type, $key ) ) {
+ return $cached;
+ }
+ }
+
+ $assocType = $this->getAssocTable( array( $type, 'tag' ) );
+ $assocTable = $this->esc( $assocType );
+ $assocField = $type . '_id';
+ $table = $this->esc( $type );
+ $slots = implode( ',', array_fill( 0, count( $tagList ), '?' ) );
+ $score = ( $all ) ? count( $tagList ) : 1;
+
+ $sql = "
+ SELECT {$table}.* FROM {$table}
+ INNER JOIN {$assocTable} ON {$assocField} = {$table}.id
+ INNER JOIN tag ON {$assocTable}.tag_id = tag.id
+ WHERE tag.title IN ({$slots})
+ GROUP BY {$table}.id
+ HAVING count({$table}.id) >= ?
+ {$addSql}
+ -- keep-cache
+ ";
+ $sql = sprintf($wrap,$sql);
+
+ $bindings = array_merge( $tagList, array( $score ), $bindings );
+ $rows = $this->adapter->get( $sql, $bindings );
+
+ if ( $this->flagUseCache ) {
+ $this->putResultInCache( $type, $key, $rows );
+ }
+
+ return $rows;
+ }
+
+ /**
+ * @see QueryWriter::queryTagged
+ */
+ public function queryTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() )
+ {
+ return $this->queryTaggedGeneric( $type, $tagList, $all, $addSql, $bindings );
+ }
+
+ /**
+ * @see QueryWriter::queryCountTagged
+ */
+ public function queryCountTagged( $type, $tagList, $all = FALSE, $addSql = '', $bindings = array() )
+ {
+ $rows = $this->queryTaggedGeneric( $type, $tagList, $all, $addSql, $bindings, 'SELECT COUNT(*) AS counted FROM (%s) AS counting' );
+ return intval($rows[0]['counted']);
+ }
+
+ /**
+ * @see QueryWriter::queryRecordCount
+ */
+ public function queryRecordCount( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
+ {
+ if ( $this->flagUseCache ) {
+ $key = $this->getCacheKey( array( $conditions, trim($addSql), $bindings, 'count' ) );
+ if ( $cached = $this->getCached( $type, $key ) ) {
+ return $cached;
+ }
+ }
+
+ $table = $this->esc( $type );
+
+ if ( is_array ( $conditions ) && !empty ( $conditions ) ) {
+ $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
+ } else {
+ $sql = $this->glueSQLCondition( $addSql );
+ }
+
+ $sql = $this->parseJoin( $type, $sql );
+
+ $sql = "SELECT COUNT(*) FROM {$table} {$sql} -- keep-cache";
+ $count = (int) $this->adapter->getCell( $sql, $bindings );
+
+ if ( $this->flagUseCache ) {
+ $this->putResultInCache( $type, $key, $count );
+ }
+
+ return $count;
+ }
+
+ /**
+ * @see QueryWriter::queryRecordCountRelated
+ */
+ public function queryRecordCountRelated( $sourceType, $destType, $linkID, $addSql = '', $bindings = array() )
+ {
+ list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
+
+ if ( $this->flagUseCache ) {
+ $cacheType = "#{$sourceType}/{$destType}";
+ $key = $this->getCacheKey( array( $sourceType, $destType, $linkID, trim($addSql), $bindings, 'countrelated' ) );
+ if ( $cached = $this->getCached( $cacheType, $key ) ) {
+ return $cached;
+ }
+ }
+
+ if ( $sourceType === $destType ) {
+ $sql = "
+ SELECT COUNT(*) FROM {$linkTable}
+ INNER JOIN {$destTable} ON
+ ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? ) OR
+ ( {$destTable}.id = {$linkTable}.{$sourceCol} AND {$linkTable}.{$destCol} = ? )
+ {$addSql}
+ -- keep-cache";
+
+ $bindings = array_merge( array( $linkID, $linkID ), $bindings );
+ } else {
+ $sql = "
+ SELECT COUNT(*) FROM {$linkTable}
+ INNER JOIN {$destTable} ON
+ ( {$destTable}.id = {$linkTable}.{$destCol} AND {$linkTable}.{$sourceCol} = ? )
+ {$addSql}
+ -- keep-cache";
+
+ $bindings = array_merge( array( $linkID ), $bindings );
+ }
+
+ $count = (int) $this->adapter->getCell( $sql, $bindings );
+
+ if ( $this->flagUseCache ) {
+ $this->putResultInCache( $cacheType, $key, $count );
+ }
+
+ return $count;
+ }
+
+ /**
+ * @see QueryWriter::queryRecursiveCommonTableExpression
+ */
+ public function queryRecursiveCommonTableExpression( $type, $id, $up = TRUE, $addSql = NULL, $bindings = array(), $selectForm = FALSE )
+ {
+ if ($selectForm === QueryWriter::C_CTE_SELECT_COUNT) {
+ $selectForm = "count(redbeantree.*)";
+ } elseif ( $selectForm === QueryWriter::C_CTE_SELECT_NORMAL ) {
+ $selectForm = "redbeantree.*";
+ }
+ $alias = $up ? 'parent' : 'child';
+ $direction = $up ? " {$alias}.{$type}_id = {$type}.id " : " {$alias}.id = {$type}.{$type}_id ";
+ /* allow numeric and named param bindings, if '0' exists then numeric */
+ if ( array_key_exists( 0,$bindings ) ) {
+ array_unshift( $bindings, $id );
+ $idSlot = '?';
+ } else {
+ $idSlot = ':slot0';
+ $bindings[$idSlot] = $id;
+ }
+ $sql = $this->glueSQLCondition( $addSql, QueryWriter::C_GLUE_WHERE );
+ $sql = $this->parseJoin( 'redbeantree', $sql, $type );
+ $rows = $this->adapter->get("
+ WITH RECURSIVE redbeantree AS
+ (
+ SELECT *
+ FROM {$type} WHERE {$type}.id = {$idSlot}
+ UNION ALL
+ SELECT {$type}.* FROM {$type}
+ INNER JOIN redbeantree {$alias} ON {$direction}
+ )
+ SELECT {$selectForm} FROM redbeantree {$sql};",
+ $bindings
+ );
+ return $rows;
+ }
+
+ /**
+ * @see QueryWriter::deleteRecord
+ */
+ public function deleteRecord( $type, $conditions = array(), $addSql = NULL, $bindings = array() )
+ {
+ $table = $this->esc( $type );
+
+ if ( is_array ( $conditions ) && !empty ( $conditions ) ) {
+ $sql = $this->makeSQLFromConditions( $conditions, $bindings, $addSql );
+ } else {
+ $sql = $this->glueSQLCondition( $addSql );
+ }
+
+ $sql = "DELETE FROM {$table} {$sql}";
+
+ return $this->adapter->exec( $sql, $bindings );
+ }
+
+ /**
+ * @see QueryWriter::deleteRelations
+ */
+ public function deleteRelations( $sourceType, $destType, $sourceID )
+ {
+ list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType );
+
+ if ( $sourceTable === $destTable ) {
+ $sql = "DELETE FROM {$linkTable}
+ WHERE ( {$sourceCol} = ? ) OR
+ ( {$destCol} = ? )
+ ";
+
+ $this->adapter->exec( $sql, array( $sourceID, $sourceID ) );
+ } else {
+ $sql = "DELETE FROM {$linkTable}
+ WHERE {$sourceCol} = ? ";
+
+ $this->adapter->exec( $sql, array( $sourceID ) );
+ }
+ }
+
+ /**
+ * @see QueryWriter::widenColumn
+ */
+ public function widenColumn( $type, $property, $dataType )
+ {
+ if ( !isset($this->typeno_sqltype[$dataType]) ) return FALSE;
+
+ $table = $this->esc( $type );
+ $column = $this->esc( $property );
+
+ $newType = $this->typeno_sqltype[$dataType];
+
+ $this->adapter->exec( sprintf( $this->getDDLTemplate( 'widenColumn', $type, $column ), $type, $column, $column, $newType ) );
+
+ return TRUE;
+ }
+
+ /**
+ * @see QueryWriter::wipe
+ */
+ public function wipe( $type )
+ {
+ $table = $this->esc( $type );
+
+ $this->adapter->exec( "TRUNCATE $table " );
+ }
+
+ /**
+ * @see QueryWriter::renameAssocTable
+ */
+ public function renameAssocTable( $from, $to = NULL )
+ {
+ self::renameAssociation( $from, $to );
+ }
+
+ /**
+ * @see QueryWriter::getAssocTable
+ */
+ public function getAssocTable( $types )
+ {
+ return self::getAssocTableFormat( $types );
+ }
+
+ /**
+ * Turns caching on or off. Default: off.
+ * If caching is turned on retrieval queries fired after eachother will
+ * use a result row cache.
+ *
+ * @param boolean
+ *
+ * @return void
+ */
+ public function setUseCache( $yesNo )
+ {
+ $this->flushCache();
+
+ $this->flagUseCache = (bool) $yesNo;
+ }
+
+ /**
+ * Flushes the Query Writer Cache.
+ * Clears the internal query cache array and returns its overall
+ * size.
+ *
+ * @return mixed
+ */
+ public function flushCache( $newMaxCacheSizePerType = NULL, $countCache = TRUE )
+ {
+ if ( !is_null( $newMaxCacheSizePerType ) && $newMaxCacheSizePerType > 0 ) {
+ $this->maxCacheSizePerType = $newMaxCacheSizePerType;
+ }
+ $count = $countCache ? count( $this->cache, COUNT_RECURSIVE ) : NULL;
+ $this->cache = array();
+ return $count;
+ }
+
+ /**
+ * @deprecated Use esc() instead.
+ *
+ * @param string $column column to be escaped
+ * @param boolean $noQuotes omit quotes
+ *
+ * @return string
+ */
+ public function safeColumn( $column, $noQuotes = FALSE )
+ {
+ return $this->esc( $column, $noQuotes );
+ }
+
+ /**
+ * @deprecated Use esc() instead.
+ *
+ * @param string $table table to be escaped
+ * @param boolean $noQuotes omit quotes
+ *
+ * @return string
+ */
+ public function safeTable( $table, $noQuotes = FALSE )
+ {
+ return $this->esc( $table, $noQuotes );
+ }
+
+ /**
+ * @see QueryWriter::addUniqueConstraint
+ */
+ public function addUniqueIndex( $type, $properties )
+ {
+ return $this->addUniqueConstraint( $type, $properties );
+ }
+}
+}
+
+namespace RedBeanPHP\QueryWriter {
+
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
+use RedBeanPHP\Adapter as Adapter;
+use RedBeanPHP\RedException\SQL as SQLException;
+
+/**
+ * RedBeanPHP MySQLWriter.
+ * This is a QueryWriter class for RedBeanPHP.
+ * This QueryWriter provides support for the MySQL/MariaDB database platform.
+ *
+ * @file RedBeanPHP/QueryWriter/MySQL.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class MySQL extends AQueryWriter implements QueryWriter
+{
+ /**
+ * Data types
+ */
+ const C_DATATYPE_BOOL = 0;
+ const C_DATATYPE_UINT32 = 2;
+ const C_DATATYPE_DOUBLE = 3;
+ const C_DATATYPE_TEXT7 = 4; //InnoDB cant index varchar(255) utf8mb4 - so keep 191 as long as possible
+ const C_DATATYPE_TEXT8 = 5;
+ const C_DATATYPE_TEXT16 = 6;
+ const C_DATATYPE_TEXT32 = 7;
+ const C_DATATYPE_SPECIAL_DATE = 80;
+ const C_DATATYPE_SPECIAL_DATETIME = 81;
+ const C_DATATYPE_SPECIAL_TIME = 83; //MySQL time column (only manual)
+ const C_DATATYPE_SPECIAL_POINT = 90;
+ const C_DATATYPE_SPECIAL_LINESTRING = 91;
+ const C_DATATYPE_SPECIAL_POLYGON = 92;
+ const C_DATATYPE_SPECIAL_MONEY = 93;
+ const C_DATATYPE_SPECIAL_JSON = 94; //JSON support (only manual)
+
+ const C_DATATYPE_SPECIFIED = 99;
+
+ /**
+ * @var DBAdapter
+ */
+ protected $adapter;
+
+ /**
+ * @var string
+ */
+ protected $quoteCharacter = '`';
+
+ /**
+ * @var array
+ */
+ protected $DDLTemplates = array(
+ 'addColumn' => array(
+ '*' => 'ALTER TABLE %s ADD %s %s '
+ ),
+ 'createTable' => array(
+ '*' => 'CREATE TABLE %s (id INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT, PRIMARY KEY ( id )) ENGINE = InnoDB DEFAULT CHARSET=%s COLLATE=%s '
+ ),
+ 'widenColumn' => array(
+ '*' => 'ALTER TABLE `%s` CHANGE %s %s %s '
+ )
+ );
+
+ /**
+ * @see AQueryWriter::getKeyMapForType
+ */
+ protected function getKeyMapForType( $type )
+ {
+ $databaseName = $this->adapter->getCell('SELECT DATABASE()');
+ $table = $this->esc( $type, TRUE );
+ $keys = $this->adapter->get('
+ SELECT
+ information_schema.key_column_usage.constraint_name AS `name`,
+ information_schema.key_column_usage.referenced_table_name AS `table`,
+ information_schema.key_column_usage.column_name AS `from`,
+ information_schema.key_column_usage.referenced_column_name AS `to`,
+ information_schema.referential_constraints.update_rule AS `on_update`,
+ information_schema.referential_constraints.delete_rule AS `on_delete`
+ FROM information_schema.key_column_usage
+ INNER JOIN information_schema.referential_constraints
+ ON information_schema.referential_constraints.constraint_name = information_schema.key_column_usage.constraint_name
+ WHERE
+ information_schema.key_column_usage.table_schema = :database
+ AND information_schema.referential_constraints.constraint_schema = :database
+ AND information_schema.key_column_usage.constraint_schema = :database
+ AND information_schema.key_column_usage.table_name = :table
+ AND information_schema.key_column_usage.constraint_name != \'PRIMARY\'
+ AND information_schema.key_column_usage.referenced_table_name IS NOT NULL
+ ', array( ':database' => $databaseName, ':table' => $table ) );
+ $keyInfoList = array();
+ foreach ( $keys as $k ) {
+ $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
+ $keyInfoList[$label] = array(
+ 'name' => $k['name'],
+ 'from' => $k['from'],
+ 'table' => $k['table'],
+ 'to' => $k['to'],
+ 'on_update' => $k['on_update'],
+ 'on_delete' => $k['on_delete']
+ );
+ }
+ return $keyInfoList;
+ }
+
+ /**
+ * Constructor
+ * Most of the time, you do not need to use this constructor,
+ * since the facade takes care of constructing and wiring the
+ * RedBeanPHP core objects. However if you would like to
+ * assemble an OODB instance yourself, this is how it works:
+ *
+ * Usage:
+ *
+ *
+ * $database = new RPDO( $dsn, $user, $pass );
+ * $adapter = new DBAdapter( $database );
+ * $writer = new PostgresWriter( $adapter );
+ * $oodb = new OODB( $writer, FALSE );
+ * $bean = $oodb->dispense( 'bean' );
+ * $bean->name = 'coffeeBean';
+ * $id = $oodb->store( $bean );
+ * $bean = $oodb->load( 'bean', $id );
+ *
+ *
+ * The example above creates the 3 RedBeanPHP core objects:
+ * the Adapter, the Query Writer and the OODB instance and
+ * wires them together. The example also demonstrates some of
+ * the methods that can be used with OODB, as you see, they
+ * closely resemble their facade counterparts.
+ *
+ * The wiring process: create an RPDO instance using your database
+ * connection parameters. Create a database adapter from the RPDO
+ * object and pass that to the constructor of the writer. Next,
+ * create an OODB instance from the writer. Now you have an OODB
+ * object.
+ *
+ * @param Adapter $adapter Database Adapter
+ * @param array $options options array
+ */
+ public function __construct( Adapter $adapter, $options = array() )
+ {
+ $this->typeno_sqltype = array(
+ MySQL::C_DATATYPE_BOOL => ' TINYINT(1) UNSIGNED ',
+ MySQL::C_DATATYPE_UINT32 => ' INT(11) UNSIGNED ',
+ MySQL::C_DATATYPE_DOUBLE => ' DOUBLE ',
+ MySQL::C_DATATYPE_TEXT7 => ' VARCHAR(191) ',
+ MYSQL::C_DATATYPE_TEXT8 => ' VARCHAR(255) ',
+ MySQL::C_DATATYPE_TEXT16 => ' TEXT ',
+ MySQL::C_DATATYPE_TEXT32 => ' LONGTEXT ',
+ MySQL::C_DATATYPE_SPECIAL_DATE => ' DATE ',
+ MySQL::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
+ MySQL::C_DATATYPE_SPECIAL_TIME => ' TIME ',
+ MySQL::C_DATATYPE_SPECIAL_POINT => ' POINT ',
+ MySQL::C_DATATYPE_SPECIAL_LINESTRING => ' LINESTRING ',
+ MySQL::C_DATATYPE_SPECIAL_POLYGON => ' POLYGON ',
+ MySQL::C_DATATYPE_SPECIAL_MONEY => ' DECIMAL(10,2) ',
+ MYSQL::C_DATATYPE_SPECIAL_JSON => ' JSON '
+ );
+
+ $this->sqltype_typeno = array();
+
+ foreach ( $this->typeno_sqltype as $k => $v ) {
+ $this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
+ }
+
+ $this->adapter = $adapter;
+ $this->encoding = $this->adapter->getDatabase()->getMysqlEncoding();
+ $me = $this;
+ if (!isset($options['noInitcode']))
+ $this->adapter->setInitCode(function($version) use(&$me) {
+ try {
+ if (strpos($version, 'maria')===FALSE && intval($version)>=8) {
+ $me->useFeature('ignoreDisplayWidth');
+ }
+ } catch( \Exception $e ){}
+ });
+ }
+
+ /**
+ * Enables certain features/dialects.
+ *
+ * - ignoreDisplayWidth required for MySQL8+
+ * (automatically set by setup() if you pass dsn instead of PDO object)
+ *
+ * @param string $name feature ID
+ *
+ * @return void
+ */
+ public function useFeature($name) {
+ if ($name == 'ignoreDisplayWidth') {
+ $this->typeno_sqltype[MySQL::C_DATATYPE_BOOL] = ' TINYINT UNSIGNED ';
+ $this->typeno_sqltype[MySQL::C_DATATYPE_UINT32] = ' INT UNSIGNED ';
+ foreach ( $this->typeno_sqltype as $k => $v ) {
+ $this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
+ }
+ }
+ }
+
+ /**
+ * This method returns the datatype to be used for primary key IDS and
+ * foreign keys. Returns one if the data type constants.
+ *
+ * @return integer
+ */
+ public function getTypeForID()
+ {
+ return self::C_DATATYPE_UINT32;
+ }
+
+ /**
+ * @see QueryWriter::getTables
+ */
+ public function getTables()
+ {
+ return $this->adapter->getCol( 'show tables' );
+ }
+
+ /**
+ * @see QueryWriter::createTable
+ */
+ public function createTable( $type )
+ {
+ $table = $this->esc( $type );
+
+ $charset_collate = $this->adapter->getDatabase()->getMysqlEncoding( TRUE );
+ $charset = $charset_collate['charset'];
+ $collate = $charset_collate['collate'];
+
+ $sql = sprintf( $this->getDDLTemplate( 'createTable', $type ), $table, $charset, $collate );
+
+ $this->adapter->exec( $sql );
+ }
+
+ /**
+ * @see QueryWriter::getColumns
+ */
+ public function getColumns( $table )
+ {
+ $columnsRaw = $this->adapter->get( "DESCRIBE " . $this->esc( $table ) );
+
+ $columns = array();
+ foreach ( $columnsRaw as $r ) {
+ $columns[$r['Field']] = $r['Type'];
+ }
+
+ return $columns;
+ }
+
+ /**
+ * @see QueryWriter::scanType
+ */
+ public function scanType( $value, $flagSpecial = FALSE )
+ {
+ if ( is_null( $value ) ) return MySQL::C_DATATYPE_BOOL;
+ if ( $value === INF ) return MySQL::C_DATATYPE_TEXT7;
+
+ if ( $flagSpecial ) {
+ if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
+ return MySQL::C_DATATYPE_SPECIAL_MONEY;
+ }
+ if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
+ return MySQL::C_DATATYPE_SPECIAL_DATE;
+ }
+ if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
+ return MySQL::C_DATATYPE_SPECIAL_DATETIME;
+ }
+ if ( preg_match( '/^POINT\(/', $value ) ) {
+ return MySQL::C_DATATYPE_SPECIAL_POINT;
+ }
+ if ( preg_match( '/^LINESTRING\(/', $value ) ) {
+ return MySQL::C_DATATYPE_SPECIAL_LINESTRING;
+ }
+ if ( preg_match( '/^POLYGON\(/', $value ) ) {
+ return MySQL::C_DATATYPE_SPECIAL_POLYGON;
+ }
+ if ( self::$flagUseJSONColumns && $this->isJSON( $value ) ) {
+ return self::C_DATATYPE_SPECIAL_JSON;
+ }
+ }
+
+ //setter turns TRUE FALSE into 0 and 1 because database has no real bools (TRUE and FALSE only for test?).
+ if ( $value === FALSE || $value === TRUE || $value === '0' || $value === '1' || $value === 0 || $value === 1 ) {
+ return MySQL::C_DATATYPE_BOOL;
+ }
+
+ if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
+
+ if ( !$this->startsWithZeros( $value ) ) {
+
+ if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= 0 && $value <= 4294967295 ) {
+ return MySQL::C_DATATYPE_UINT32;
+ }
+
+ if ( is_numeric( $value ) ) {
+ return MySQL::C_DATATYPE_DOUBLE;
+ }
+ }
+
+ if ( mb_strlen( $value, 'UTF-8' ) <= 191 ) {
+ return MySQL::C_DATATYPE_TEXT7;
+ }
+
+ if ( mb_strlen( $value, 'UTF-8' ) <= 255 ) {
+ return MySQL::C_DATATYPE_TEXT8;
+ }
+
+ if ( mb_strlen( $value, 'UTF-8' ) <= 65535 ) {
+ return MySQL::C_DATATYPE_TEXT16;
+ }
+
+ return MySQL::C_DATATYPE_TEXT32;
+ }
+
+ /**
+ * @see QueryWriter::code
+ */
+ public function code( $typedescription, $includeSpecials = FALSE )
+ {
+ if ( isset( $this->sqltype_typeno[$typedescription] ) ) {
+ $r = $this->sqltype_typeno[$typedescription];
+ } else {
+ $r = self::C_DATATYPE_SPECIFIED;
+ }
+
+ if ( $includeSpecials ) {
+ return $r;
+ }
+
+ if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
+ return self::C_DATATYPE_SPECIFIED;
+ }
+
+ return $r;
+ }
+
+ /**
+ * @see QueryWriter::addUniqueIndex
+ */
+ public function addUniqueConstraint( $type, $properties )
+ {
+ $tableNoQ = $this->esc( $type, TRUE );
+ $columns = array();
+ foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
+ $table = $this->esc( $type );
+ sort( $columns ); // Else we get multiple indexes due to order-effects
+ $name = 'UQ_' . sha1( implode( ',', $columns ) );
+ try {
+ $sql = "ALTER TABLE $table
+ ADD UNIQUE INDEX $name (" . implode( ',', $columns ) . ")";
+ $this->adapter->exec( $sql );
+ } catch ( SQLException $e ) {
+ //do nothing, don't use alter table ignore, this will delete duplicate records in 3-ways!
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * @see QueryWriter::addIndex
+ */
+ public function addIndex( $type, $name, $property )
+ {
+ try {
+ $table = $this->esc( $type );
+ $name = preg_replace( '/\W/', '', $name );
+ $column = $this->esc( $property );
+ $this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
+ return TRUE;
+ } catch ( SQLException $e ) {
+ return FALSE;
+ }
+ }
+
+ /**
+ * @see QueryWriter::addFK
+ */
+ public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
+ {
+ $table = $this->esc( $type );
+ $targetTable = $this->esc( $targetType );
+ $targetTableNoQ = $this->esc( $targetType, TRUE );
+ $field = $this->esc( $property );
+ $fieldNoQ = $this->esc( $property, TRUE );
+ $targetField = $this->esc( $targetProperty );
+ $targetFieldNoQ = $this->esc( $targetProperty, TRUE );
+ $tableNoQ = $this->esc( $type, TRUE );
+ $fieldNoQ = $this->esc( $property, TRUE );
+ if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
+
+ //Widen the column if it's incapable of representing a foreign key (at least INT).
+ $columns = $this->getColumns( $tableNoQ );
+ $idType = $this->getTypeForID();
+ if ( $this->code( $columns[$fieldNoQ] ) !== $idType ) {
+ $this->widenColumn( $type, $property, $idType );
+ }
+
+ $fkName = 'fk_'.($tableNoQ.'_'.$fieldNoQ);
+ $cName = 'c_'.$fkName;
+ try {
+ $this->adapter->exec( "
+ ALTER TABLE {$table}
+ ADD CONSTRAINT $cName
+ FOREIGN KEY $fkName ( `{$fieldNoQ}` ) REFERENCES `{$targetTableNoQ}`
+ (`{$targetFieldNoQ}`) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';');
+ } catch ( SQLException $e ) {
+ // Failure of fk-constraints is not a problem
+ }
+ return TRUE;
+ }
+
+ /**
+ * @see QueryWriter::sqlStateIn
+ */
+ public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
+ {
+ $stateMap = array(
+ '42S02' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
+ '42S22' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
+ '23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
+ );
+
+ if ( $state == 'HY000' && !empty( $extraDriverDetails[1] ) ) {
+ $driverCode = $extraDriverDetails[1];
+
+ if ( $driverCode == '1205' && in_array( QueryWriter::C_SQLSTATE_LOCK_TIMEOUT, $list ) ) {
+ return TRUE;
+ }
+ }
+
+ return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
+ }
+
+ /**
+ * @see QueryWriter::wipeAll
+ */
+ public function wipeAll()
+ {
+ if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
+ $this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 0;' );
+
+ foreach ( $this->getTables() as $t ) {
+ try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
+ try { $this->adapter->exec( "DROP VIEW IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
+ }
+
+ $this->adapter->exec( 'SET FOREIGN_KEY_CHECKS = 1;' );
+ }
+}
+}
+
+namespace RedBeanPHP\QueryWriter {
+
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
+use RedBeanPHP\Adapter as Adapter;
+use RedBeanPHP\RedException\SQL as SQLException;
+
+/**
+ * RedBeanPHP SQLiteWriter with support for SQLite types
+ * This is a QueryWriter class for RedBeanPHP.
+ * This QueryWriter provides support for the SQLite database platform.
+ *
+ * @file RedBeanPHP/QueryWriter/SQLiteT.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class SQLiteT extends AQueryWriter implements QueryWriter
+{
+ /**
+ * Data types
+ */
+ const C_DATATYPE_INTEGER = 0;
+ const C_DATATYPE_NUMERIC = 1;
+ const C_DATATYPE_TEXT = 2;
+ const C_DATATYPE_SPECIFIED = 99;
+
+ /**
+ * @var DBAdapter
+ */
+ protected $adapter;
+
+ /**
+ * @var string
+ */
+ protected $quoteCharacter = '`';
+
+ /**
+ * @var array
+ */
+ protected $tableArchive = array();
+
+ /**
+ * @var array
+ */
+ protected $DDLTemplates = array(
+ 'addColumn' => array(
+ '*' => 'ALTER TABLE `%s` ADD `%s` %s'
+ ),
+ 'createTable' => array(
+ '*' => 'CREATE TABLE %s ( id INTEGER PRIMARY KEY AUTOINCREMENT )'
+ ),
+ 'widenColumn' => array(
+ '*' => ',`%s` %s '
+ )
+ );
+
+ /**
+ * Gets all information about a table (from a type).
+ *
+ * Format:
+ * array(
+ * name => name of the table
+ * columns => array( name => datatype )
+ * indexes => array() raw index information rows from PRAGMA query
+ * keys => array() raw key information rows from PRAGMA query
+ * )
+ *
+ * @param string $type type you want to get info of
+ *
+ * @return array
+ */
+ protected function getTable( $type )
+ {
+ $tableName = $this->esc( $type, TRUE );
+ $columns = $this->getColumns( $type );
+ $indexes = $this->getIndexes( $type );
+ $keys = $this->getKeyMapForType( $type );
+
+ $table = array(
+ 'columns' => $columns,
+ 'indexes' => $indexes,
+ 'keys' => $keys,
+ 'name' => $tableName
+ );
+
+ $this->tableArchive[$tableName] = $table;
+
+ return $table;
+ }
+
+ /**
+ * Puts a table. Updates the table structure.
+ * In SQLite we can't change columns, drop columns, change or add foreign keys so we
+ * have a table-rebuild function. You simply load your table with getTable(), modify it and
+ * then store it with putTable()...
+ *
+ * @param array $tableMap information array
+ *
+ * @return void
+ */
+ protected function putTable( $tableMap )
+ {
+ $table = $tableMap['name'];
+ $q = array();
+ $q[] = "DROP TABLE IF EXISTS tmp_backup;";
+
+ $oldColumnNames = array_keys( $this->getColumns( $table ) );
+
+ foreach ( $oldColumnNames as $k => $v ) $oldColumnNames[$k] = "`$v`";
+
+ $q[] = "CREATE TEMPORARY TABLE tmp_backup(" . implode( ",", $oldColumnNames ) . ");";
+ $q[] = "INSERT INTO tmp_backup SELECT * FROM `$table`;";
+ $q[] = "PRAGMA foreign_keys = 0 ";
+ $q[] = "DROP TABLE `$table`;";
+
+ $newTableDefStr = '';
+ foreach ( $tableMap['columns'] as $column => $type ) {
+ if ( $column != 'id' ) {
+ $newTableDefStr .= sprintf( $this->getDDLTemplate( 'widenColumn', $table, $column ), $column, $type );
+ }
+ }
+
+ $fkDef = '';
+ foreach ( $tableMap['keys'] as $key ) {
+ $fkDef .= ", FOREIGN KEY(`{$key['from']}`)
+ REFERENCES `{$key['table']}`(`{$key['to']}`)
+ ON DELETE {$key['on_delete']} ON UPDATE {$key['on_update']}";
+ }
+
+ $q[] = "CREATE TABLE `$table` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT $newTableDefStr $fkDef );";
+
+ foreach ( $tableMap['indexes'] as $name => $index ) {
+ if ( strpos( $name, 'UQ_' ) === 0 ) {
+ $cols = explode( '__', substr( $name, strlen( 'UQ_' . $table ) ) );
+ foreach ( $cols as $k => $v ) $cols[$k] = "`$v`";
+ $q[] = "CREATE UNIQUE INDEX $name ON `$table` (" . implode( ',', $cols ) . ")";
+ } else $q[] = "CREATE INDEX $name ON `$table` ({$index['name']}) ";
+ }
+
+ $q[] = "INSERT INTO `$table` SELECT * FROM tmp_backup;";
+ $q[] = "DROP TABLE tmp_backup;";
+ $q[] = "PRAGMA foreign_keys = 1 ";
+
+ foreach ( $q as $sq ) $this->adapter->exec( $sq );
+ }
+
+ /**
+ * Returns the an array describing the indexes for type $type.
+ *
+ * @param string $type type to describe indexes of
+ *
+ * @return array
+ */
+ protected function getIndexes( $type )
+ {
+ $table = $this->esc( $type, TRUE );
+ $indexes = $this->adapter->get( "PRAGMA index_list('$table')" );
+
+ $indexInfoList = array();
+ foreach ( $indexes as $i ) {
+ $indexInfoList[$i['name']] = $this->adapter->getRow( "PRAGMA index_info('{$i['name']}') " );
+
+ $indexInfoList[$i['name']]['unique'] = $i['unique'];
+ }
+
+ return $indexInfoList;
+ }
+
+ /**
+ * Adds a foreign key to a type.
+ * Note: cant put this in try-catch because that can hide the fact
+ * that database has been damaged.
+ *
+ * @param string $type type you want to modify table of
+ * @param string $targetType target type
+ * @param string $field field of the type that needs to get the fk
+ * @param string $targetField field where the fk needs to point to
+ * @param integer $buildopt 0 = NO ACTION, 1 = ON DELETE CASCADE
+ *
+ * @return boolean
+ */
+ protected function buildFK( $type, $targetType, $property, $targetProperty, $constraint = FALSE )
+ {
+ $table = $this->esc( $type, TRUE );
+ $targetTable = $this->esc( $targetType, TRUE );
+ $column = $this->esc( $property, TRUE );
+ $targetColumn = $this->esc( $targetProperty, TRUE );
+
+ $tables = $this->getTables();
+ if ( !in_array( $targetTable, $tables ) ) return FALSE;
+
+ if ( !is_null( $this->getForeignKeyForTypeProperty( $table, $column ) ) ) return FALSE;
+ $t = $this->getTable( $table );
+ $consSQL = ( $constraint ? 'CASCADE' : 'SET NULL' );
+ $label = 'from_' . $column . '_to_table_' . $targetTable . '_col_' . $targetColumn;
+ $t['keys'][$label] = array(
+ 'table' => $targetTable,
+ 'from' => $column,
+ 'to' => $targetColumn,
+ 'on_update' => $consSQL,
+ 'on_delete' => $consSQL
+ );
+ $this->putTable( $t );
+ return TRUE;
+ }
+
+ /**
+ * @see AQueryWriter::getKeyMapForType
+ */
+ protected function getKeyMapForType( $type )
+ {
+ $table = $this->esc( $type, TRUE );
+ $keys = $this->adapter->get( "PRAGMA foreign_key_list('$table')" );
+ $keyInfoList = array();
+ foreach ( $keys as $k ) {
+ $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
+ $keyInfoList[$label] = array(
+ 'name' => $label,
+ 'from' => $k['from'],
+ 'table' => $k['table'],
+ 'to' => $k['to'],
+ 'on_update' => $k['on_update'],
+ 'on_delete' => $k['on_delete']
+ );
+ }
+ return $keyInfoList;
+ }
+
+ /**
+ * Constructor
+ * Most of the time, you do not need to use this constructor,
+ * since the facade takes care of constructing and wiring the
+ * RedBeanPHP core objects. However if you would like to
+ * assemble an OODB instance yourself, this is how it works:
+ *
+ * Usage:
+ *
+ *
+ * $database = new RPDO( $dsn, $user, $pass );
+ * $adapter = new DBAdapter( $database );
+ * $writer = new PostgresWriter( $adapter );
+ * $oodb = new OODB( $writer, FALSE );
+ * $bean = $oodb->dispense( 'bean' );
+ * $bean->name = 'coffeeBean';
+ * $id = $oodb->store( $bean );
+ * $bean = $oodb->load( 'bean', $id );
+ *
+ *
+ * The example above creates the 3 RedBeanPHP core objects:
+ * the Adapter, the Query Writer and the OODB instance and
+ * wires them together. The example also demonstrates some of
+ * the methods that can be used with OODB, as you see, they
+ * closely resemble their facade counterparts.
+ *
+ * The wiring process: create an RPDO instance using your database
+ * connection parameters. Create a database adapter from the RPDO
+ * object and pass that to the constructor of the writer. Next,
+ * create an OODB instance from the writer. Now you have an OODB
+ * object.
+ *
+ * @param Adapter $adapter Database Adapter
+ */
+ public function __construct( Adapter $adapter )
+ {
+ $this->typeno_sqltype = array(
+ SQLiteT::C_DATATYPE_INTEGER => 'INTEGER',
+ SQLiteT::C_DATATYPE_NUMERIC => 'NUMERIC',
+ SQLiteT::C_DATATYPE_TEXT => 'TEXT',
+ );
+
+ $this->sqltype_typeno = array();
+
+ foreach ( $this->typeno_sqltype as $k => $v ) {
+ $this->sqltype_typeno[$v] = $k;
+ }
+
+ $this->adapter = $adapter;
+ $this->adapter->setOption( 'setInitQuery', ' PRAGMA foreign_keys = 1 ' );
+ }
+
+ /**
+ * This method returns the datatype to be used for primary key IDS and
+ * foreign keys. Returns one if the data type constants.
+ *
+ * @return integer $const data type to be used for IDS.
+ */
+ public function getTypeForID()
+ {
+ return self::C_DATATYPE_INTEGER;
+ }
+
+ /**
+ * @see QueryWriter::scanType
+ */
+ public function scanType( $value, $flagSpecial = FALSE )
+ {
+ if ( $value === NULL ) return self::C_DATATYPE_INTEGER;
+ if ( $value === INF ) return self::C_DATATYPE_TEXT;
+
+ if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
+
+ if ( $value === TRUE || $value === FALSE ) return self::C_DATATYPE_INTEGER;
+
+ if ( is_numeric( $value ) && ( intval( $value ) == $value ) && $value < 2147483648 && $value > -2147483648 ) return self::C_DATATYPE_INTEGER;
+
+ if ( ( is_numeric( $value ) && $value < 2147483648 && $value > -2147483648)
+ || preg_match( '/\d{4}\-\d\d\-\d\d/', $value )
+ || preg_match( '/\d{4}\-\d\d\-\d\d\s\d\d:\d\d:\d\d/', $value )
+ ) {
+ return self::C_DATATYPE_NUMERIC;
+ }
+
+ return self::C_DATATYPE_TEXT;
+ }
+
+ /**
+ * @see QueryWriter::addColumn
+ */
+ public function addColumn( $table, $column, $type )
+ {
+ $column = $this->check( $column );
+ $table = $this->check( $table );
+ $type = $this->typeno_sqltype[$type];
+
+ $this->adapter->exec( sprintf( $this->getDDLTemplate( 'addColumn', $table, $column ), $table, $column, $type ) );
+ }
+
+ /**
+ * @see QueryWriter::code
+ */
+ public function code( $typedescription, $includeSpecials = FALSE )
+ {
+ $r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99 );
+
+ return $r;
+ }
+
+ /**
+ * @see QueryWriter::widenColumn
+ */
+ public function widenColumn( $type, $column, $datatype )
+ {
+ $t = $this->getTable( $type );
+
+ $t['columns'][$column] = $this->typeno_sqltype[$datatype];
+
+ $this->putTable( $t );
+ }
+
+ /**
+ * @see QueryWriter::getTables();
+ */
+ public function getTables()
+ {
+ return $this->adapter->getCol( "SELECT name FROM sqlite_master
+ WHERE type='table' AND name!='sqlite_sequence';" );
+ }
+
+ /**
+ * @see QueryWriter::createTable
+ */
+ public function createTable( $type )
+ {
+ $table = $this->esc( $type );
+
+ $sql = sprintf( $this->getDDLTemplate( 'createTable', $type ), $table );
+
+ $this->adapter->exec( $sql );
+ }
+
+ /**
+ * @see QueryWriter::getColumns
+ */
+ public function getColumns( $table )
+ {
+ $table = $this->esc( $table, TRUE );
+
+ $columnsRaw = $this->adapter->get( "PRAGMA table_info('$table')" );
+
+ $columns = array();
+ foreach ( $columnsRaw as $r ) $columns[$r['name']] = $r['type'];
+
+ return $columns;
+ }
+
+ /**
+ * @see QueryWriter::addUniqueIndex
+ */
+ public function addUniqueConstraint( $type, $properties )
+ {
+ $tableNoQ = $this->esc( $type, TRUE );
+ $name = 'UQ_' . $this->esc( $type, TRUE ) . implode( '__', $properties );
+ $t = $this->getTable( $type );
+ $t['indexes'][$name] = array( 'name' => $name );
+ try {
+ $this->putTable( $t );
+ } catch( SQLException $e ) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * @see QueryWriter::sqlStateIn
+ */
+ public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
+ {
+ $stateMap = array(
+ '23000' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION
+ );
+ if ( $state == 'HY000'
+ && isset($extraDriverDetails[1])
+ && $extraDriverDetails[1] == 1
+ && ( in_array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, $list )
+ || in_array( QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, $list )
+ )) {
+ return TRUE;
+ }
+ return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
+ }
+
+ /**
+ * Sets an SQL snippet to be used for the next queryRecord() operation.
+ * SQLite has no SELECT-FOR-UPDATE and filters this.
+ *
+ * @param string $sqlSelectSnippet SQL snippet to use in SELECT statement.
+ *
+ * @return self
+ */
+ public function setSQLSelectSnippet( $sqlSelectSnippet = '' ) {
+ if ( $sqlSelectSnippet === AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE) $sqlSelectSnippet = '';
+ $this->sqlSelectSnippet = $sqlSelectSnippet;
+ return $this;
+ }
+
+ /**
+ * @see QueryWriter::addIndex
+ */
+ public function addIndex( $type, $name, $column )
+ {
+ $columns = $this->getColumns( $type );
+ if ( !isset( $columns[$column] ) ) return FALSE;
+
+ $table = $this->esc( $type );
+ $name = preg_replace( '/\W/', '', $name );
+ $column = $this->esc( $column, TRUE );
+
+ try {
+ $t = $this->getTable( $type );
+ $t['indexes'][$name] = array( 'name' => $column );
+ $this->putTable( $t );
+ return TRUE;
+ } catch( SQLException $exception ) {
+ return FALSE;
+ }
+ }
+
+ /**
+ * @see QueryWriter::wipe
+ */
+ public function wipe( $type )
+ {
+ $table = $this->esc( $type );
+
+ $this->adapter->exec( "DELETE FROM $table " );
+ }
+
+ /**
+ * @see QueryWriter::addFK
+ */
+ public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
+ {
+ return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDep );
+ }
+
+ /**
+ * @see QueryWriter::wipeAll
+ */
+ public function wipeAll()
+ {
+ if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
+ $this->adapter->exec( 'PRAGMA foreign_keys = 0 ' );
+
+ foreach ( $this->getTables() as $t ) {
+ try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
+ try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); } catch ( SQLException $e ) { ; }
+ }
+
+ $this->adapter->exec( 'PRAGMA foreign_keys = 1 ' );
+ }
+}
+}
+
+namespace RedBeanPHP\QueryWriter {
+
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
+use RedBeanPHP\Adapter as Adapter;
+use RedBeanPHP\RedException\SQL as SQLException;
+
+/**
+ * RedBeanPHP PostgreSQL Query Writer.
+ * This is a QueryWriter class for RedBeanPHP.
+ * This QueryWriter provides support for the PostgreSQL database platform.
+ *
+ * @file RedBeanPHP/QueryWriter/PostgreSQL.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class PostgreSQL extends AQueryWriter implements QueryWriter
+{
+ /**
+ * Data types
+ */
+ const C_DATATYPE_INTEGER = 0;
+ const C_DATATYPE_DOUBLE = 1;
+ const C_DATATYPE_TEXT = 3;
+ const C_DATATYPE_SPECIAL_DATE = 80;
+ const C_DATATYPE_SPECIAL_DATETIME = 81;
+ const C_DATATYPE_SPECIAL_TIME = 82; //TIME (no zone) only manual
+ const C_DATATYPE_SPECIAL_TIMEZ = 83; //TIME (plus zone) only manual
+ const C_DATATYPE_SPECIAL_POINT = 90;
+ const C_DATATYPE_SPECIAL_LSEG = 91;
+ const C_DATATYPE_SPECIAL_CIRCLE = 92;
+ const C_DATATYPE_SPECIAL_MONEY = 93;
+ const C_DATATYPE_SPECIAL_POLYGON = 94;
+ const C_DATATYPE_SPECIAL_MONEY2 = 95; //Numbers only money, i.e. fixed point numeric
+ const C_DATATYPE_SPECIAL_JSON = 96; //JSON support (only manual)
+ const C_DATATYPE_SPECIFIED = 99;
+
+ /**
+ * @var DBAdapter
+ */
+ protected $adapter;
+
+ /**
+ * @var string
+ */
+ protected $quoteCharacter = '"';
+
+ /**
+ * @var string
+ */
+ protected $defaultValue = 'DEFAULT';
+
+ /**
+ * @var array
+ */
+ protected $DDLTemplates = array(
+ 'addColumn' => array(
+ '*' => 'ALTER TABLE %s ADD %s %s '
+ ),
+ 'createTable' => array(
+ '*' => 'CREATE TABLE %s (id SERIAL PRIMARY KEY) '
+ ),
+ 'widenColumn' => array(
+ '*' => 'ALTER TABLE %s ALTER COLUMN %s TYPE %s'
+ )
+ );
+
+ /**
+ * Returns the insert suffix SQL Snippet
+ *
+ * @param string $table table
+ *
+ * @return string $sql SQL Snippet
+ */
+ protected function getInsertSuffix( $table )
+ {
+ return 'RETURNING id ';
+ }
+
+ /**
+ * @see AQueryWriter::getKeyMapForType
+ */
+ protected function getKeyMapForType( $type )
+ {
+ $table = $this->esc( $type, TRUE );
+ $keys = $this->adapter->get( '
+ SELECT
+ information_schema.key_column_usage.constraint_name AS "name",
+ information_schema.key_column_usage.column_name AS "from",
+ information_schema.constraint_table_usage.table_name AS "table",
+ information_schema.constraint_column_usage.column_name AS "to",
+ information_schema.referential_constraints.update_rule AS "on_update",
+ information_schema.referential_constraints.delete_rule AS "on_delete"
+ FROM information_schema.key_column_usage
+ INNER JOIN information_schema.constraint_table_usage
+ ON (
+ information_schema.key_column_usage.constraint_name = information_schema.constraint_table_usage.constraint_name
+ AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_table_usage.constraint_schema
+ AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_table_usage.constraint_catalog
+ )
+ INNER JOIN information_schema.constraint_column_usage
+ ON (
+ information_schema.key_column_usage.constraint_name = information_schema.constraint_column_usage.constraint_name
+ AND information_schema.key_column_usage.constraint_schema = information_schema.constraint_column_usage.constraint_schema
+ AND information_schema.key_column_usage.constraint_catalog = information_schema.constraint_column_usage.constraint_catalog
+ )
+ INNER JOIN information_schema.referential_constraints
+ ON (
+ information_schema.key_column_usage.constraint_name = information_schema.referential_constraints.constraint_name
+ AND information_schema.key_column_usage.constraint_schema = information_schema.referential_constraints.constraint_schema
+ AND information_schema.key_column_usage.constraint_catalog = information_schema.referential_constraints.constraint_catalog
+ )
+ WHERE
+ information_schema.key_column_usage.table_catalog = current_database()
+ AND information_schema.key_column_usage.table_schema = ANY( current_schemas( FALSE ) )
+ AND information_schema.key_column_usage.table_name = ?
+ ', array( $type ) );
+ $keyInfoList = array();
+ foreach ( $keys as $k ) {
+ $label = $this->makeFKLabel( $k['from'], $k['table'], $k['to'] );
+ $keyInfoList[$label] = array(
+ 'name' => $k['name'],
+ 'from' => $k['from'],
+ 'table' => $k['table'],
+ 'to' => $k['to'],
+ 'on_update' => $k['on_update'],
+ 'on_delete' => $k['on_delete']
+ );
+ }
+ return $keyInfoList;
+ }
+
+ /**
+ * Constructor
+ * Most of the time, you do not need to use this constructor,
+ * since the facade takes care of constructing and wiring the
+ * RedBeanPHP core objects. However if you would like to
+ * assemble an OODB instance yourself, this is how it works:
+ *
+ * Usage:
+ *
+ *
+ * $database = new RPDO( $dsn, $user, $pass );
+ * $adapter = new DBAdapter( $database );
+ * $writer = new PostgresWriter( $adapter );
+ * $oodb = new OODB( $writer, FALSE );
+ * $bean = $oodb->dispense( 'bean' );
+ * $bean->name = 'coffeeBean';
+ * $id = $oodb->store( $bean );
+ * $bean = $oodb->load( 'bean', $id );
+ *
+ *
+ * The example above creates the 3 RedBeanPHP core objects:
+ * the Adapter, the Query Writer and the OODB instance and
+ * wires them together. The example also demonstrates some of
+ * the methods that can be used with OODB, as you see, they
+ * closely resemble their facade counterparts.
+ *
+ * The wiring process: create an RPDO instance using your database
+ * connection parameters. Create a database adapter from the RPDO
+ * object and pass that to the constructor of the writer. Next,
+ * create an OODB instance from the writer. Now you have an OODB
+ * object.
+ *
+ * @param Adapter $adapter Database Adapter
+ */
+ public function __construct( Adapter $adapter )
+ {
+ $this->typeno_sqltype = array(
+ self::C_DATATYPE_INTEGER => ' integer ',
+ self::C_DATATYPE_DOUBLE => ' double precision ',
+ self::C_DATATYPE_TEXT => ' text ',
+ self::C_DATATYPE_SPECIAL_DATE => ' date ',
+ self::C_DATATYPE_SPECIAL_TIME => ' time ',
+ self::C_DATATYPE_SPECIAL_TIMEZ => ' time with time zone ',
+ self::C_DATATYPE_SPECIAL_DATETIME => ' timestamp without time zone ',
+ self::C_DATATYPE_SPECIAL_POINT => ' point ',
+ self::C_DATATYPE_SPECIAL_LSEG => ' lseg ',
+ self::C_DATATYPE_SPECIAL_CIRCLE => ' circle ',
+ self::C_DATATYPE_SPECIAL_MONEY => ' money ',
+ self::C_DATATYPE_SPECIAL_MONEY2 => ' numeric(10,2) ',
+ self::C_DATATYPE_SPECIAL_POLYGON => ' polygon ',
+ self::C_DATATYPE_SPECIAL_JSON => ' json ',
+ );
+
+ $this->sqltype_typeno = array();
+
+ foreach ( $this->typeno_sqltype as $k => $v ) {
+ $this->sqltype_typeno[trim( strtolower( $v ) )] = $k;
+ }
+
+ $this->adapter = $adapter;
+ }
+
+ /**
+ * This method returns the datatype to be used for primary key IDS and
+ * foreign keys. Returns one if the data type constants.
+ *
+ * @return integer
+ */
+ public function getTypeForID()
+ {
+ return self::C_DATATYPE_INTEGER;
+ }
+
+ /**
+ * @see QueryWriter::getTables
+ */
+ public function getTables()
+ {
+ return $this->adapter->getCol( 'SELECT table_name FROM information_schema.tables WHERE table_schema = ANY( current_schemas( FALSE ) )' );
+ }
+
+ /**
+ * @see QueryWriter::createTable
+ */
+ public function createTable( $type )
+ {
+ $table = $this->esc( $type );
+
+ $this->adapter->exec( sprintf( $this->getDDLTemplate( 'createTable', $type ), $table ) );
+ }
+
+ /**
+ * @see QueryWriter::getColumns
+ */
+ public function getColumns( $table )
+ {
+ $table = $this->esc( $table, TRUE );
+
+ $columnsRaw = $this->adapter->get( "SELECT column_name, data_type FROM information_schema.columns WHERE table_name='$table' AND table_schema = ANY( current_schemas( FALSE ) )" );
+
+ $columns = array();
+ foreach ( $columnsRaw as $r ) {
+ $columns[$r['column_name']] = $r['data_type'];
+ }
+
+ return $columns;
+ }
+
+ /**
+ * @see QueryWriter::scanType
+ */
+ public function scanType( $value, $flagSpecial = FALSE )
+ {
+ if ( $value === INF ) return self::C_DATATYPE_TEXT;
+
+ if ( $flagSpecial && $value ) {
+ if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
+ return PostgreSQL::C_DATATYPE_SPECIAL_DATE;
+ }
+
+ if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d(\.\d{1,6})?$/', $value ) ) {
+ return PostgreSQL::C_DATATYPE_SPECIAL_DATETIME;
+ }
+
+ if ( preg_match( '/^\([\d\.]+,[\d\.]+\)$/', $value ) ) {
+ return PostgreSQL::C_DATATYPE_SPECIAL_POINT;
+ }
+
+ if ( preg_match( '/^\[\([\d\.]+,[\d\.]+\),\([\d\.]+,[\d\.]+\)\]$/', $value ) ) {
+ return PostgreSQL::C_DATATYPE_SPECIAL_LSEG;
+ }
+
+ if ( preg_match( '/^\<\([\d\.]+,[\d\.]+\),[\d\.]+\>$/', $value ) ) {
+ return PostgreSQL::C_DATATYPE_SPECIAL_CIRCLE;
+ }
+
+ if ( preg_match( '/^\((\([\d\.]+,[\d\.]+\),?)+\)$/', $value ) ) {
+ return PostgreSQL::C_DATATYPE_SPECIAL_POLYGON;
+ }
+
+ if ( preg_match( '/^\-?(\$|€|¥|£)[\d,\.]+$/', $value ) ) {
+ return PostgreSQL::C_DATATYPE_SPECIAL_MONEY;
+ }
+
+ if ( preg_match( '/^-?\d+\.\d{2}$/', $value ) ) {
+ return PostgreSQL::C_DATATYPE_SPECIAL_MONEY2;
+ }
+ if ( self::$flagUseJSONColumns && $this->isJSON( $value ) ) {
+ return self::C_DATATYPE_SPECIAL_JSON;
+ }
+ }
+
+ if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE;
+
+ if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT;
+
+ if ( $value === FALSE || $value === TRUE || $value === NULL || ( is_numeric( $value )
+ && AQueryWriter::canBeTreatedAsInt( $value )
+ && $value < 2147483648
+ && $value > -2147483648 )
+ ) {
+ return self::C_DATATYPE_INTEGER;
+ } elseif ( is_numeric( $value ) ) {
+ return self::C_DATATYPE_DOUBLE;
+ } else {
+ return self::C_DATATYPE_TEXT;
+ }
+ }
+
+ /**
+ * @see QueryWriter::code
+ */
+ public function code( $typedescription, $includeSpecials = FALSE )
+ {
+ $r = ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : 99;
+
+ if ( $includeSpecials ) return $r;
+
+ if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
+ return self::C_DATATYPE_SPECIFIED;
+ }
+
+ return $r;
+ }
+
+ /**
+ * @see QueryWriter::widenColumn
+ */
+ public function widenColumn( $beanType, $column, $datatype )
+ {
+ $table = $beanType;
+ $type = $datatype;
+
+ $table = $this->esc( $table );
+ $column = $this->esc( $column );
+
+ $newtype = $this->typeno_sqltype[$type];
+
+ $this->adapter->exec( sprintf( $this->getDDLTemplate( 'widenColumn', $beanType, $column ), $table, $column, $newtype ) );
+
+ }
+
+ /**
+ * @see QueryWriter::addUniqueIndex
+ */
+ public function addUniqueConstraint( $type, $properties )
+ {
+ $tableNoQ = $this->esc( $type, TRUE );
+ $columns = array();
+ foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
+ $table = $this->esc( $type );
+ sort( $columns ); //else we get multiple indexes due to order-effects
+ $name = "UQ_" . sha1( $table . implode( ',', $columns ) );
+ $sql = "ALTER TABLE {$table}
+ ADD CONSTRAINT $name UNIQUE (" . implode( ',', $columns ) . ")";
+ try {
+ $this->adapter->exec( $sql );
+ } catch( SQLException $e ) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * @see QueryWriter::sqlStateIn
+ */
+ public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
+ {
+ $stateMap = array(
+ '42P01' => QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
+ '42703' => QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
+ '23505' => QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
+ '55P03' => QueryWriter::C_SQLSTATE_LOCK_TIMEOUT
+ );
+ return in_array( ( isset( $stateMap[$state] ) ? $stateMap[$state] : '0' ), $list );
+ }
+
+ /**
+ * @see QueryWriter::addIndex
+ */
+ public function addIndex( $type, $name, $property )
+ {
+ $table = $this->esc( $type );
+ $name = preg_replace( '/\W/', '', $name );
+ $column = $this->esc( $property );
+
+ try {
+ $this->adapter->exec( "CREATE INDEX {$name} ON $table ({$column}) " );
+ return TRUE;
+ } catch ( SQLException $e ) {
+ return FALSE;
+ }
+ }
+
+ /**
+ * @see QueryWriter::addFK
+ */
+ public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
+ {
+ $table = $this->esc( $type );
+ $targetTable = $this->esc( $targetType );
+ $field = $this->esc( $property );
+ $targetField = $this->esc( $targetProperty );
+ $tableNoQ = $this->esc( $type, TRUE );
+ $fieldNoQ = $this->esc( $property, TRUE );
+ if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $fieldNoQ ) ) ) return FALSE;
+ try{
+ $delRule = ( $isDep ? 'CASCADE' : 'SET NULL' );
+ $this->adapter->exec( "ALTER TABLE {$table}
+ ADD FOREIGN KEY ( {$field} ) REFERENCES {$targetTable}
+ ({$targetField}) ON DELETE {$delRule} ON UPDATE {$delRule} DEFERRABLE ;" );
+ return TRUE;
+ } catch ( SQLException $e ) {
+ return FALSE;
+ }
+ }
+
+ /**
+ * @see QueryWriter::wipeAll
+ */
+ public function wipeAll()
+ {
+ if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
+ $this->adapter->exec( 'SET CONSTRAINTS ALL DEFERRED' );
+
+ foreach ( $this->getTables() as $t ) {
+ $t = $this->esc( $t );
+ //Some plugins (PostGIS have unremovable tables/views), avoid exceptions.
+ try { $this->adapter->exec( "DROP TABLE IF EXISTS $t CASCADE " ); }catch( \Exception $e ) {}
+ }
+
+ $this->adapter->exec( 'SET CONSTRAINTS ALL IMMEDIATE' );
+ }
+}
+}
+
+namespace RedBeanPHP\QueryWriter {
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
+use RedBeanPHP\Adapter as Adapter;
+use RedBeanPHP\RedException\SQL as SQLException;
+
+/**
+ * RedBeanPHP CUBRID Writer.
+ * This is a QueryWriter class for RedBeanPHP.
+ * This QueryWriter provides support for the CUBRID database platform.
+ *
+ * @file RedBeanPHP/QueryWriter/CUBRID.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class CUBRID extends AQueryWriter implements QueryWriter
+{
+ /**
+ * Data types
+ */
+ const C_DATATYPE_INTEGER = 0;
+ const C_DATATYPE_DOUBLE = 1;
+ const C_DATATYPE_STRING = 2;
+ const C_DATATYPE_SPECIAL_DATE = 80;
+ const C_DATATYPE_SPECIAL_DATETIME = 81;
+ const C_DATATYPE_SPECIFIED = 99;
+
+ /**
+ * @var DBAdapter
+ */
+ protected $adapter;
+
+ /**
+ * @var string
+ */
+ protected $quoteCharacter = '`';
+
+ /**
+ * This method adds a foreign key from type and field to
+ * target type and target field.
+ * The foreign key is created without an action. On delete/update
+ * no action will be triggered. The FK is only used to allow database
+ * tools to generate pretty diagrams and to make it easy to add actions
+ * later on.
+ * This methods accepts a type and infers the corresponding table name.
+ *
+ * @param string $type type that will have a foreign key field
+ * @param string $targetType points to this type
+ * @param string $property field that contains the foreign key value
+ * @param string $targetProperty field where the fk points to
+ * @param boolean $isDep is dependent
+ *
+ * @return bool
+ */
+ protected function buildFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE )
+ {
+ $table = $this->esc( $type );
+ $tableNoQ = $this->esc( $type, TRUE );
+ $targetTable = $this->esc( $targetType );
+ $targetTableNoQ = $this->esc( $targetType, TRUE );
+ $column = $this->esc( $property );
+ $columnNoQ = $this->esc( $property, TRUE );
+ $targetColumn = $this->esc( $targetProperty );
+ if ( !is_null( $this->getForeignKeyForTypeProperty( $tableNoQ, $columnNoQ ) ) ) return FALSE;
+ $needsToDropFK = FALSE;
+ $casc = ( $isDep ? 'CASCADE' : 'SET NULL' );
+ $sql = "ALTER TABLE $table ADD CONSTRAINT FOREIGN KEY($column) REFERENCES $targetTable($targetColumn) ON DELETE $casc ";
+ try {
+ $this->adapter->exec( $sql );
+ } catch( SQLException $e ) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * @see AQueryWriter::getKeyMapForType
+ */
+ protected function getKeyMapForType( $type )
+ {
+ $sqlCode = $this->adapter->get("SHOW CREATE TABLE `{$type}`");
+ if (!isset($sqlCode[0])) return array();
+ $matches = array();
+ 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 );
+ $list = array();
+ if (!isset($matches[0])) return $list;
+ $max = count($matches[0]);
+ for($i = 0; $i < $max; $i++) {
+ $label = $this->makeFKLabel( $matches[2][$i], $matches[3][$i], 'id' );
+ $list[ $label ] = array(
+ 'name' => $matches[1][$i],
+ 'from' => $matches[2][$i],
+ 'table' => $matches[3][$i],
+ 'to' => 'id',
+ 'on_update' => $matches[6][$i],
+ 'on_delete' => $matches[5][$i]
+ );
+ }
+ return $list;
+ }
+
+ /**
+ * Constructor
+ * Most of the time, you do not need to use this constructor,
+ * since the facade takes care of constructing and wiring the
+ * RedBeanPHP core objects. However if you would like to
+ * assemble an OODB instance yourself, this is how it works:
+ *
+ * Usage:
+ *
+ *
+ * $database = new RPDO( $dsn, $user, $pass );
+ * $adapter = new DBAdapter( $database );
+ * $writer = new PostgresWriter( $adapter );
+ * $oodb = new OODB( $writer, FALSE );
+ * $bean = $oodb->dispense( 'bean' );
+ * $bean->name = 'coffeeBean';
+ * $id = $oodb->store( $bean );
+ * $bean = $oodb->load( 'bean', $id );
+ *
+ *
+ * The example above creates the 3 RedBeanPHP core objects:
+ * the Adapter, the Query Writer and the OODB instance and
+ * wires them together. The example also demonstrates some of
+ * the methods that can be used with OODB, as you see, they
+ * closely resemble their facade counterparts.
+ *
+ * The wiring process: create an RPDO instance using your database
+ * connection parameters. Create a database adapter from the RPDO
+ * object and pass that to the constructor of the writer. Next,
+ * create an OODB instance from the writer. Now you have an OODB
+ * object.
+ *
+ * @param Adapter $adapter Database Adapter
+ */
+ public function __construct( Adapter $adapter )
+ {
+ $this->typeno_sqltype = array(
+ CUBRID::C_DATATYPE_INTEGER => ' INTEGER ',
+ CUBRID::C_DATATYPE_DOUBLE => ' DOUBLE ',
+ CUBRID::C_DATATYPE_STRING => ' STRING ',
+ CUBRID::C_DATATYPE_SPECIAL_DATE => ' DATE ',
+ CUBRID::C_DATATYPE_SPECIAL_DATETIME => ' DATETIME ',
+ );
+
+ $this->sqltype_typeno = array();
+
+ foreach ( $this->typeno_sqltype as $k => $v ) {
+ $this->sqltype_typeno[trim( ( $v ) )] = $k;
+ }
+
+ $this->sqltype_typeno['STRING(1073741823)'] = self::C_DATATYPE_STRING;
+
+ $this->adapter = $adapter;
+ }
+
+ /**
+ * This method returns the datatype to be used for primary key IDS and
+ * foreign keys. Returns one if the data type constants.
+ *
+ * @return integer
+ */
+ public function getTypeForID()
+ {
+ return self::C_DATATYPE_INTEGER;
+ }
+
+ /**
+ * @see QueryWriter::getTables
+ */
+ public function getTables()
+ {
+ $rows = $this->adapter->getCol( "SELECT class_name FROM db_class WHERE is_system_class = 'NO';" );
+
+ return $rows;
+ }
+
+ /**
+ * @see QueryWriter::createTable
+ */
+ public function createTable( $table )
+ {
+ $sql = 'CREATE TABLE '
+ . $this->esc( $table )
+ . ' ("id" integer AUTO_INCREMENT, CONSTRAINT "pk_'
+ . $this->esc( $table, TRUE )
+ . '_id" PRIMARY KEY("id"))';
+
+ $this->adapter->exec( $sql );
+ }
+
+ /**
+ * @see QueryWriter::getColumns
+ */
+ public function getColumns( $table )
+ {
+ $table = $this->esc( $table );
+
+ $columnsRaw = $this->adapter->get( "SHOW COLUMNS FROM $table" );
+
+ $columns = array();
+ foreach ( $columnsRaw as $r ) {
+ $columns[$r['Field']] = $r['Type'];
+ }
+
+ return $columns;
+ }
+
+ /**
+ * @see QueryWriter::scanType
+ */
+ public function scanType( $value, $flagSpecial = FALSE )
+ {
+ if ( is_null( $value ) ) {
+ return self::C_DATATYPE_INTEGER;
+ }
+
+ if ( $flagSpecial ) {
+ if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) {
+ return self::C_DATATYPE_SPECIAL_DATE;
+ }
+ if ( preg_match( '/^\d{4}\-\d\d-\d\d\s\d\d:\d\d:\d\d$/', $value ) ) {
+ return self::C_DATATYPE_SPECIAL_DATETIME;
+ }
+ }
+
+ $value = strval( $value );
+
+ if ( !$this->startsWithZeros( $value ) ) {
+ if ( is_numeric( $value ) && ( floor( $value ) == $value ) && $value >= -2147483647 && $value <= 2147483647 ) {
+ return self::C_DATATYPE_INTEGER;
+ }
+ if ( is_numeric( $value ) ) {
+ return self::C_DATATYPE_DOUBLE;
+ }
+ }
+
+ return self::C_DATATYPE_STRING;
+ }
+
+ /**
+ * @see QueryWriter::code
+ */
+ public function code( $typedescription, $includeSpecials = FALSE )
+ {
+ $r = ( ( isset( $this->sqltype_typeno[$typedescription] ) ) ? $this->sqltype_typeno[$typedescription] : self::C_DATATYPE_SPECIFIED );
+
+ if ( $includeSpecials ) {
+ return $r;
+ }
+
+ if ( $r >= QueryWriter::C_DATATYPE_RANGE_SPECIAL ) {
+ return self::C_DATATYPE_SPECIFIED;
+ }
+
+ return $r;
+ }
+
+ /**
+ * @see QueryWriter::addColumn
+ */
+ public function addColumn( $type, $column, $field )
+ {
+ $table = $type;
+ $type = $field;
+
+ $table = $this->esc( $table );
+ $column = $this->esc( $column );
+
+ $type = array_key_exists( $type, $this->typeno_sqltype ) ? $this->typeno_sqltype[$type] : '';
+
+ $this->adapter->exec( "ALTER TABLE $table ADD COLUMN $column $type " );
+ }
+
+ /**
+ * @see QueryWriter::addUniqueIndex
+ */
+ public function addUniqueConstraint( $type, $properties )
+ {
+ $tableNoQ = $this->esc( $type, TRUE );
+ $columns = array();
+ foreach( $properties as $key => $column ) $columns[$key] = $this->esc( $column );
+ $table = $this->esc( $type );
+ sort( $columns ); // else we get multiple indexes due to order-effects
+ $name = 'UQ_' . sha1( implode( ',', $columns ) );
+ $sql = "ALTER TABLE $table ADD CONSTRAINT UNIQUE $name (" . implode( ',', $columns ) . ")";
+ try {
+ $this->adapter->exec( $sql );
+ } catch( SQLException $e ) {
+ return FALSE;
+ }
+ return TRUE;
+ }
+
+ /**
+ * @see QueryWriter::sqlStateIn
+ */
+ public function sqlStateIn( $state, $list, $extraDriverDetails = array() )
+ {
+ return ( $state == 'HY000' ) ? ( count( array_diff( array(
+ QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION,
+ QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
+ QueryWriter::C_SQLSTATE_NO_SUCH_TABLE
+ ), $list ) ) !== 3 ) : FALSE;
+ }
+
+ /**
+ * @see QueryWriter::addIndex
+ */
+ public function addIndex( $type, $name, $column )
+ {
+ try {
+ $table = $this->esc( $type );
+ $name = preg_replace( '/\W/', '', $name );
+ $column = $this->esc( $column );
+ $this->adapter->exec( "CREATE INDEX $name ON $table ($column) " );
+ return TRUE;
+ } catch ( SQLException $e ) {
+ return FALSE;
+ }
+ }
+
+ /**
+ * @see QueryWriter::addFK
+ */
+ public function addFK( $type, $targetType, $property, $targetProperty, $isDependent = FALSE )
+ {
+ return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDependent );
+ }
+
+ /**
+ * @see QueryWriter::wipeAll
+ */
+ public function wipeAll()
+ {
+ if (AQueryWriter::$noNuke) throw new \Exception('The nuke() command has been disabled using noNuke() or R::feature(novice/...).');
+ foreach ( $this->getTables() as $t ) {
+ foreach ( $this->getKeyMapForType( $t ) as $k ) {
+ $this->adapter->exec( "ALTER TABLE \"$t\" DROP FOREIGN KEY \"{$k['name']}\"" );
+ }
+ }
+ foreach ( $this->getTables() as $t ) {
+ $this->adapter->exec( "DROP TABLE \"$t\"" );
+ }
+ }
+
+ /**
+ * @see QueryWriter::esc
+ */
+ public function esc( $dbStructure, $noQuotes = FALSE )
+ {
+ return parent::esc( strtolower( $dbStructure ), $noQuotes );
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+/**
+ * RedBean\Exception Base.
+ * Represents the base class for RedBeanPHP\Exceptions.
+ *
+ * @file RedBeanPHP/Exception.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class RedException extends \Exception
+{
+}
+}
+
+namespace RedBeanPHP\RedException {
+
+use RedBeanPHP\RedException as RedException;
+
+/**
+ * SQL Exception.
+ * Represents a generic database exception independent of the underlying driver.
+ *
+ * @file RedBeanPHP/RedException/SQL.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class SQL extends RedException
+{
+ /**
+ * @var string
+ */
+ private $sqlState;
+
+ /**
+ * @var array
+ */
+ private $driverDetails = array();
+
+ /**
+ * @return array
+ */
+ public function getDriverDetails()
+ {
+ return $this->driverDetails;
+ }
+
+ /**
+ * @param array $driverDetails
+ *
+ * @return void
+ */
+ public function setDriverDetails($driverDetails)
+ {
+ $this->driverDetails = $driverDetails;
+ }
+
+ /**
+ * Returns an ANSI-92 compliant SQL state.
+ *
+ * @return string
+ */
+ public function getSQLState()
+ {
+ return $this->sqlState;
+ }
+
+ /**
+ * Returns the raw SQL STATE, possibly compliant with
+ * ANSI SQL error codes - but this depends on database driver.
+ *
+ * @param string $sqlState SQL state error code
+ *
+ * @return void
+ */
+ public function setSQLState( $sqlState )
+ {
+ $this->sqlState = $sqlState;
+ }
+
+ /**
+ * To String prints both code and SQL state.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return '[' . $this->getSQLState() . '] - ' . $this->getMessage()."\n".
+ 'trace: ' . $this->getTraceAsString();
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\BeanHelper as BeanHelper;
+use RedBeanPHP\RedException\SQL as SQLException;
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+use RedBeanPHP\Cursor as Cursor;
+use RedBeanPHP\Cursor\NullCursor as NullCursor;
+
+/**
+ * Abstract Repository.
+ *
+ * OODB manages two repositories, a fluid one that
+ * adjust the database schema on-the-fly to accommodate for
+ * new bean types (tables) and new properties (columns) and
+ * a frozen one for use in a production environment. OODB
+ * allows you to swap the repository instances using the freeze()
+ * method.
+ *
+ * @file RedBeanPHP/Repository.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+abstract class Repository
+{
+ /**
+ * @var array
+ */
+ protected $stash = NULL;
+
+ /**
+ * @var integer
+ */
+ protected $nesting = 0;
+
+ /**
+ * @var QueryWriter
+ */
+ protected $writer;
+
+ /**
+ * @var boolean|array
+ */
+ protected $partialBeans = FALSE;
+
+ /**
+ * @var OODB
+ */
+ public $oodb = NULL;
+
+ /**
+ * Toggles 'partial bean mode'. If this mode has been
+ * selected the repository will only update the fields of a bean that
+ * have been changed rather than the entire bean.
+ * Pass the value TRUE to select 'partial mode' for all beans.
+ * Pass the value FALSE to disable 'partial mode'.
+ * Pass an array of bean types if you wish to use partial mode only
+ * for some types.
+ * This method will return the previous value.
+ *
+ * @param boolean|array $yesNoBeans List of type names or 'all'
+ *
+ * @return boolean|array
+ */
+ public function usePartialBeans( $yesNoBeans )
+ {
+ $oldValue = $this->partialBeans;
+ $this->partialBeans = $yesNoBeans;
+ return $oldValue;
+ }
+
+ /**
+ * Fully processes a bean and updates the associated records in the database.
+ * First the bean properties will be grouped as 'embedded' bean,
+ * addition, deleted 'trash can' or residue. Next, the different groups
+ * of beans will be processed accordingly and the reference bean (i.e.
+ * the one that was passed to the method as an argument) will be stored.
+ * Each type of list (own/shared) has 3 bean processors:
+ *
+ * - trashCanProcessor : removes the bean or breaks its association with the current bean
+ * - additionProcessor : associates the bean with the current one
+ * - residueProcessor : manages beans in lists that 'remain' but may need to be updated
+ *
+ * This method first groups the beans and then calls the
+ * internal processing methods.
+ *
+ * @param OODBBean $bean bean to process
+ *
+ * @return void
+ */
+ protected function storeBeanWithLists( OODBBean $bean )
+ {
+ $sharedAdditions = $sharedTrashcan = $sharedresidue = $sharedItems = $ownAdditions = $ownTrashcan = $ownresidue = $embeddedBeans = array(); //Define groups
+ foreach ( $bean as $property => $value ) {
+ $value = ( $value instanceof SimpleModelInterface ) ? $value->unbox() : $value;
+ if ( $value instanceof OODBBean ) {
+ $this->processEmbeddedBean( $embeddedBeans, $bean, $property, $value );
+ $bean->setMeta("sys.typeof.{$property}", $value->getMeta('type'));
+ } elseif ( is_array( $value ) ) {
+ foreach($value as &$item) {
+ $item = ( $item instanceof SimpleModelInterface ) ? $item->unbox() : $item;
+ }
+ $originals = $bean->moveMeta( 'sys.shadow.' . $property, array() );
+ if ( strpos( $property, 'own' ) === 0 ) {
+ list( $ownAdditions, $ownTrashcan, $ownresidue ) = $this->processGroups( $originals, $value, $ownAdditions, $ownTrashcan, $ownresidue );
+ $listName = lcfirst( substr( $property, 3 ) );
+ if ($bean->moveMeta( 'sys.exclusive-'. $listName ) ) {
+ OODBBean::setMetaAll( $ownTrashcan, 'sys.garbage', TRUE );
+ OODBBean::setMetaAll( $ownAdditions, 'sys.buildcommand.fkdependson', $bean->getMeta( 'type' ) );
+ }
+ unset( $bean->$property );
+ } elseif ( strpos( $property, 'shared' ) === 0 ) {
+ list( $sharedAdditions, $sharedTrashcan, $sharedresidue ) = $this->processGroups( $originals, $value, $sharedAdditions, $sharedTrashcan, $sharedresidue );
+ unset( $bean->$property );
+ }
+ }
+ }
+ $this->storeBean( $bean );
+ $this->processTrashcan( $bean, $ownTrashcan );
+ $this->processAdditions( $bean, $ownAdditions );
+ $this->processResidue( $ownresidue );
+ $this->processSharedTrashcan( $bean, $sharedTrashcan );
+ $this->processSharedAdditions( $bean, $sharedAdditions );
+ $this->processSharedResidue( $bean, $sharedresidue );
+ }
+
+ /**
+ * Process groups. Internal function. Processes different kind of groups for
+ * storage function. Given a list of original beans and a list of current beans,
+ * this function calculates which beans remain in the list (residue), which
+ * have been deleted (are in the trashcan) and which beans have been added
+ * (additions).
+ *
+ * @param array $originals originals
+ * @param array $current the current beans
+ * @param array $additions beans that have been added
+ * @param array $trashcan beans that have been deleted
+ * @param array $residue beans that have been left untouched
+ *
+ * @return array
+ */
+ protected function processGroups( $originals, $current, $additions, $trashcan, $residue )
+ {
+ return array(
+ array_merge( $additions, array_diff( $current, $originals ) ),
+ array_merge( $trashcan, array_diff( $originals, $current ) ),
+ array_merge( $residue, array_intersect( $current, $originals ) )
+ );
+ }
+
+ /**
+ * Processes a list of beans from a bean.
+ * A bean may contain lists. This
+ * method handles shared addition lists; i.e.
+ * the $bean->sharedObject properties.
+ * Shared beans will be associated with each other using the
+ * Association Manager.
+ *
+ * @param OODBBean $bean the bean
+ * @param array $sharedAdditions list with shared additions
+ *
+ * @return void
+ */
+ protected function processSharedAdditions( $bean, $sharedAdditions )
+ {
+ foreach ( $sharedAdditions as $addition ) {
+ if ( $addition instanceof OODBBean ) {
+ $this->oodb->getAssociationManager()->associate( $addition, $bean );
+ } else {
+ throw new RedException( 'Array may only contain OODBBeans' );
+ }
+ }
+ }
+
+ /**
+ * Processes a list of beans from a bean.
+ * A bean may contain lists. This
+ * method handles own lists; i.e.
+ * the $bean->ownObject properties.
+ * A residue is a bean in an own-list that stays
+ * where it is. This method checks if there have been any
+ * modification to this bean, in that case
+ * the bean is stored once again, otherwise the bean will be left untouched.
+ *
+ * @param array $ownresidue list to process
+ *
+ * @return void
+ */
+ protected function processResidue( $ownresidue )
+ {
+ foreach ( $ownresidue as $residue ) {
+ if ( $residue->getMeta( 'tainted' ) ) {
+ $this->store( $residue );
+ }
+ }
+ }
+
+ /**
+ * Processes a list of beans from a bean. A bean may contain lists. This
+ * method handles own lists; i.e. the $bean->ownObject properties.
+ * A trash can bean is a bean in an own-list that has been removed
+ * (when checked with the shadow). This method
+ * checks if the bean is also in the dependency list. If it is the bean will be removed.
+ * If not, the connection between the bean and the owner bean will be broken by
+ * setting the ID to NULL.
+ *
+ * @param OODBBean $bean bean to process
+ * @param array $ownTrashcan list to process
+ *
+ * @return void
+ */
+ protected function processTrashcan( $bean, $ownTrashcan )
+ {
+ foreach ( $ownTrashcan as $trash ) {
+
+ $myFieldLink = $bean->getMeta( 'type' ) . '_id';
+ $alias = $bean->getMeta( 'sys.alias.' . $trash->getMeta( 'type' ) );
+ if ( $alias ) $myFieldLink = $alias . '_id';
+
+ if ( $trash->getMeta( 'sys.garbage' ) === TRUE ) {
+ $this->trash( $trash );
+ } else {
+ $trash->$myFieldLink = NULL;
+ $this->store( $trash );
+ }
+ }
+ }
+
+ /**
+ * Unassociates the list items in the trashcan.
+ * This bean processor processes the beans in the shared trash can.
+ * This group of beans has been deleted from a shared list.
+ * The affected beans will no longer be associated with the bean
+ * that contains the shared list.
+ *
+ * @param OODBBean $bean bean to process
+ * @param array $sharedTrashcan list to process
+ *
+ * @return void
+ */
+ protected function processSharedTrashcan( $bean, $sharedTrashcan )
+ {
+ foreach ( $sharedTrashcan as $trash ) {
+ $this->oodb->getAssociationManager()->unassociate( $trash, $bean );
+ }
+ }
+
+ /**
+ * Stores all the beans in the residue group.
+ * This bean processor processes the beans in the shared residue
+ * group. This group of beans 'remains' in the list but might need
+ * to be updated or synced. The affected beans will be stored
+ * to perform the required database queries.
+ *
+ * @param OODBBean $bean bean to process
+ * @param array $sharedresidue list to process
+ *
+ * @return void
+ */
+ protected function processSharedResidue( $bean, $sharedresidue )
+ {
+ foreach ( $sharedresidue as $residue ) {
+ $this->store( $residue );
+ }
+ }
+
+ /**
+ * Determines whether the bean has 'loaded lists' or
+ * 'loaded embedded beans' that need to be processed
+ * by the store() method.
+ *
+ * @param OODBBean $bean bean to be examined
+ *
+ * @return boolean
+ */
+ protected function hasListsOrObjects( OODBBean $bean )
+ {
+ $processLists = FALSE;
+ foreach ( $bean as $value ) {
+ if ( is_array( $value ) || is_object( $value ) ) {
+ $processLists = TRUE;
+ break;
+ }
+ }
+
+ return $processLists;
+ }
+
+ /**
+ * Converts an embedded bean to an ID, removes the bean property and
+ * stores the bean in the embedded beans array. The id will be
+ * assigned to the link field property, i.e. 'bean_id'.
+ *
+ * @param array $embeddedBeans destination array for embedded bean
+ * @param OODBBean $bean target bean to process
+ * @param string $property property that contains the embedded bean
+ * @param OODBBean $value embedded bean itself
+ *
+ * @return void
+ */
+ protected function processEmbeddedBean( &$embeddedBeans, $bean, $property, OODBBean $value )
+ {
+ $linkField = $property . '_id';
+ if ( !$value->id || $value->getMeta( 'tainted' ) ) {
+ $this->store( $value );
+ }
+ $id = $value->id;
+ if ($bean->$linkField != $id) $bean->$linkField = $id;
+ $bean->setMeta( 'cast.' . $linkField, 'id' );
+ $embeddedBeans[$linkField] = $value;
+ unset( $bean->$property );
+ }
+
+ /**
+ * Constructor, requires a query writer and OODB.
+ * Creates a new instance of the bean respository class.
+ *
+ * @param OODB $oodb instance of object database
+ * @param QueryWriter $writer the Query Writer to use for this repository
+ *
+ * @return void
+ */
+ public function __construct( OODB $oodb, QueryWriter $writer )
+ {
+ $this->writer = $writer;
+ $this->oodb = $oodb;
+ }
+
+ /**
+ * Checks whether a OODBBean bean is valid.
+ * If the type is not valid or the ID is not valid it will
+ * throw an exception: Security. To be valid a bean
+ * must abide to the following rules:
+ *
+ * - It must have an primary key id property named: id
+ * - It must have a type
+ * - The type must conform to the RedBeanPHP naming policy
+ * - All properties must be valid
+ * - All values must be valid
+ *
+ * @param OODBBean $bean the bean that needs to be checked
+ *
+ * @return void
+ */
+ public function check( OODBBean $bean )
+ {
+ //Is all meta information present?
+ if ( !isset( $bean->id ) ) {
+ throw new RedException( 'Bean has incomplete Meta Information id ' );
+ }
+ if ( !( $bean->getMeta( 'type' ) ) ) {
+ throw new RedException( 'Bean has incomplete Meta Information II' );
+ }
+ //Pattern of allowed characters
+ $pattern = '/[^a-z0-9_]/i';
+ //Does the type contain invalid characters?
+ if ( preg_match( $pattern, $bean->getMeta( 'type' ) ) ) {
+ throw new RedException( 'Bean Type is invalid' );
+ }
+ //Are the properties and values valid?
+ foreach ( $bean as $prop => $value ) {
+ if (
+ is_array( $value )
+ || ( is_object( $value ) )
+ ) {
+ throw new RedException( "Invalid Bean value: property $prop" );
+ } else if (
+ strlen( $prop ) < 1
+ || preg_match( $pattern, $prop )
+ ) {
+ throw new RedException( "Invalid Bean property: property $prop" );
+ }
+ }
+ }
+
+ /**
+ * Dispenses a new bean (a OODBBean Bean Object)
+ * of the specified type. Always
+ * use this function to get an empty bean object. Never
+ * instantiate a OODBBean yourself because it needs
+ * to be configured before you can use it with RedBean. This
+ * function applies the appropriate initialization /
+ * configuration for you.
+ *
+ * To use a different class for beans (instead of OODBBean) set:
+ * REDBEAN_OODBBEAN_CLASS to the name of the class to be used.
+ *
+ * @param string $type type of bean you want to dispense
+ * @param int $number number of beans you would like to get
+ * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
+ *
+ * @return OODBBean|OODBBean[]
+ */
+ public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
+ {
+ $OODBBEAN = defined( 'REDBEAN_OODBBEAN_CLASS' ) ? REDBEAN_OODBBEAN_CLASS : '\RedBeanPHP\OODBBean';
+ $beans = array();
+ for ( $i = 0; $i < $number; $i++ ) {
+ $bean = new $OODBBEAN;
+ $bean->initializeForDispense( $type, $this->oodb->getBeanHelper() );
+ $this->check( $bean );
+ $this->oodb->signal( 'dispense', $bean );
+ $beans[] = $bean;
+ }
+
+ return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans;
+ }
+
+ /**
+ * Searches the database for a bean that matches conditions $conditions and sql $addSQL
+ * and returns an array containing all the beans that have been found.
+ *
+ * Conditions need to take form:
+ *
+ *
+ * array(
+ * 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
+ * 'PROPERTY' => array( POSSIBLE VALUES... )
+ * );
+ *
+ *
+ * All conditions are glued together using the AND-operator, while all value lists
+ * are glued using IN-operators thus acting as OR-conditions.
+ *
+ * Note that you can use property names; the columns will be extracted using the
+ * appropriate bean formatter.
+ *
+ * @param string $type type of beans you are looking for
+ * @param array $conditions list of conditions
+ * @param string $sql SQL to be used in query
+ * @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not)
+ *
+ * @return array
+ */
+ public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
+ {
+ //for backward compatibility, allow mismatch arguments:
+ if ( is_array( $sql ) ) {
+ if ( isset( $sql[1] ) ) {
+ $bindings = $sql[1];
+ }
+ $sql = $sql[0];
+ }
+ try {
+ $beans = $this->convertToBeans( $type, $this->writer->queryRecord( $type, $conditions, $sql, $bindings ) );
+
+ return $beans;
+ } catch ( SQLException $exception ) {
+ $this->handleException( $exception );
+ }
+
+ return array();
+ }
+
+ /**
+ * Finds a BeanCollection.
+ * Given a type, an SQL snippet and optionally some parameter bindings
+ * this methods returns a BeanCollection for your query.
+ *
+ * The BeanCollection represents a collection of beans and
+ * makes it possible to use database cursors. The BeanCollection
+ * has a method next() to obtain the first, next and last bean
+ * in the collection. The BeanCollection does not implement the array
+ * interface nor does it try to act like an array because it cannot go
+ * backward or rewind itself.
+ *
+ * @param string $type type of beans you are looking for
+ * @param string $sql SQL to be used in query
+ * @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not)
+ *
+ * @return BeanCollection
+ */
+ public function findCollection( $type, $sql, $bindings = array() )
+ {
+ try {
+ $cursor = $this->writer->queryRecordWithCursor( $type, $sql, $bindings );
+ return new BeanCollection( $type, $this, $cursor );
+ } catch ( SQLException $exception ) {
+ $this->handleException( $exception );
+ }
+ return new BeanCollection( $type, $this, new NullCursor );
+ }
+
+ /**
+ * Stores a bean in the database. This method takes a
+ * OODBBean Bean Object $bean and stores it
+ * in the database. If the database schema is not compatible
+ * with this bean and RedBean runs in fluid mode the schema
+ * will be altered to store the bean correctly.
+ * If the database schema is not compatible with this bean and
+ * RedBean runs in frozen mode it will throw an exception.
+ * This function returns the primary key ID of the inserted
+ * bean.
+ *
+ * The return value is an integer if possible. If it is not possible to
+ * represent the value as an integer a string will be returned. We use
+ * explicit casts instead of functions to preserve performance
+ * (0.13 vs 0.28 for 10000 iterations on Core i3).
+ *
+ * @param OODBBean|SimpleModel|SimpleModelInterface $bean bean to store
+ *
+ * @return integer|string
+ */
+ public function store( $bean )
+ {
+ $processLists = $this->hasListsOrObjects( $bean );
+ if ( !$processLists && !$bean->getMeta( 'tainted' ) ) {
+ return $bean->getID(); //bail out!
+ }
+ $this->oodb->signal( 'update', $bean );
+ $processLists = $this->hasListsOrObjects( $bean ); //check again, might have changed by model!
+ if ( $processLists ) {
+ $this->storeBeanWithLists( $bean );
+ } else {
+ $this->storeBean( $bean );
+ }
+ $this->oodb->signal( 'after_update', $bean );
+
+ return ( (string) $bean->id === (string) (int) $bean->id ) ? (int) $bean->id : (string) $bean->id;
+ }
+
+ /**
+ * Returns an array of beans. Pass a type and a series of ids and
+ * this method will bring you the corresponding beans.
+ *
+ * important note: Because this method loads beans using the load()
+ * function (but faster) it will return empty beans with ID 0 for
+ * every bean that could not be located. The resulting beans will have the
+ * passed IDs as their keys.
+ *
+ * @param string $type type of beans
+ * @param array $ids ids to load
+ *
+ * @return array
+ */
+ public function batch( $type, $ids )
+ {
+ if ( !$ids ) {
+ return array();
+ }
+ $collection = array();
+ try {
+ $rows = $this->writer->queryRecord( $type, array( 'id' => $ids ) );
+ } catch ( SQLException $e ) {
+ $this->handleException( $e );
+ $rows = FALSE;
+ }
+ $this->stash[$this->nesting] = array();
+ if ( !$rows ) {
+ return array();
+ }
+ foreach ( $rows as $row ) {
+ $this->stash[$this->nesting][$row['id']] = $row;
+ }
+ foreach ( $ids as $id ) {
+ $collection[$id] = $this->load( $type, $id );
+ }
+ $this->stash[$this->nesting] = NULL;
+
+ return $collection;
+ }
+
+ /**
+ * This is a convenience method; it converts database rows
+ * (arrays) into beans. Given a type and a set of rows this method
+ * will return an array of beans of the specified type loaded with
+ * the data fields provided by the result set from the database.
+ *
+ * New in 4.3.2: meta mask. The meta mask is a special mask to send
+ * data from raw result rows to the meta store of the bean. This is
+ * useful for bundling additional information with custom queries.
+ * Values of every column who's name starts with $mask will be
+ * transferred to the meta section of the bean under key 'data.bundle'.
+ *
+ * @param string $type type of beans you would like to have
+ * @param array $rows rows from the database result
+ * @param string $mask meta mask to apply (optional)
+ *
+ * @return array
+ */
+ public function convertToBeans( $type, $rows, $mask = '__meta' )
+ {
+ $masktype = gettype( $mask );
+ switch ( $masktype ) {
+ case 'string':
+ break;
+ case 'array':
+ $maskflip = array();
+ foreach ( $mask as $m ) {
+ if ( !is_string( $m ) ) {
+ $mask = NULL;
+ $masktype = 'NULL';
+ break 2;
+ }
+ $maskflip[$m] = TRUE;
+ }
+ $mask = $maskflip;
+ break;
+ default:
+ $mask = NULL;
+ $masktype = 'NULL';
+ }
+
+ $collection = array();
+ $this->stash[$this->nesting] = array();
+ foreach ( $rows as $row ) {
+ if ( $mask !== NULL ) {
+ $meta = array();
+ foreach( $row as $key => $value ) {
+ if ( $masktype === 'string' ) {
+ if ( strpos( $key, $mask ) === 0 ) {
+ unset( $row[$key] );
+ $meta[$key] = $value;
+ }
+ } elseif ( $masktype === 'array' ) {
+ if ( isset( $mask[$key] ) ) {
+ unset( $row[$key] );
+ $meta[$key] = $value;
+ }
+ }
+ }
+ }
+
+ $id = $row['id'];
+ $this->stash[$this->nesting][$id] = $row;
+ $collection[$id] = $this->load( $type, $id );
+
+ if ( $mask !== NULL ) {
+ $collection[$id]->setMeta( 'data.bundle', $meta );
+ }
+ }
+ $this->stash[$this->nesting] = NULL;
+
+ return $collection;
+ }
+
+ /**
+ * Counts the number of beans of type $type.
+ * This method accepts a second argument to modify the count-query.
+ * A third argument can be used to provide bindings for the SQL snippet.
+ *
+ * @param string $type type of bean we are looking for
+ * @param string $addSQL additional SQL snippet
+ * @param array $bindings parameters to bind to SQL
+ *
+ * @return integer
+ */
+ public function count( $type, $addSQL = '', $bindings = array() )
+ {
+ $type = AQueryWriter::camelsSnake( $type );
+ try {
+ $count = (int) $this->writer->queryRecordCount( $type, array(), $addSQL, $bindings );
+ } catch ( SQLException $exception ) {
+ $this->handleException( $exception );
+ $count = 0;
+ }
+ return $count;
+ }
+
+ /**
+ * Removes a bean from the database.
+ * This function will remove the specified OODBBean
+ * Bean Object from the database.
+ *
+ * @param OODBBean|SimpleModel|SimpleModelInterface $bean bean you want to remove from database
+ *
+ * @return int
+ */
+ public function trash( $bean )
+ {
+ $this->oodb->signal( 'delete', $bean );
+ foreach ( $bean as $property => $value ) {
+ if ( $value instanceof OODBBean ) {
+ unset( $bean->$property );
+ }
+ if ( is_array( $value ) ) {
+ if ( strpos( $property, 'own' ) === 0 ) {
+ unset( $bean->$property );
+ } elseif ( strpos( $property, 'shared' ) === 0 ) {
+ unset( $bean->$property );
+ }
+ }
+ }
+ try {
+ $deleted = $this->writer->deleteRecord( $bean->getMeta( 'type' ), array( 'id' => array( $bean->id ) ) );
+ } catch ( SQLException $exception ) {
+ $this->handleException( $exception );
+ }
+ $bean->id = 0;
+ $this->oodb->signal( 'after_delete', $bean );
+ return isset($deleted) ? $deleted : 0;
+ }
+
+ /**
+ * Checks whether the specified table already exists in the database.
+ * Not part of the Object Database interface!
+ *
+ * @param string $table table name
+ *
+ * @return boolean
+ */
+ public function tableExists( $table )
+ {
+ return $this->writer->tableExists( $table );
+ }
+
+ /**
+ * Trash all beans of a given type.
+ * Wipes an entire type of bean. After this operation there
+ * will be no beans left of the specified type.
+ * This method will ignore exceptions caused by database
+ * tables that do not exist.
+ *
+ * @param string $type type of bean you wish to delete all instances of
+ *
+ * @return boolean
+ */
+ public function wipe( $type )
+ {
+ try {
+ $this->writer->wipe( $type );
+
+ return TRUE;
+ } catch ( SQLException $exception ) {
+ if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE ), $exception->getDriverDetails() ) ) {
+ throw $exception;
+ }
+
+ return FALSE;
+ }
+ }
+}
+}
+
+namespace RedBeanPHP\Repository {
+
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\RedException as RedException;
+use RedBeanPHP\BeanHelper as BeanHelper;
+use RedBeanPHP\RedException\SQL as SQLException;
+use RedBeanPHP\Repository as Repository;
+
+/**
+ * Fluid Repository.
+ * OODB manages two repositories, a fluid one that
+ * adjust the database schema on-the-fly to accommodate for
+ * new bean types (tables) and new properties (columns) and
+ * a frozen one for use in a production environment. OODB
+ * allows you to swap the repository instances using the freeze()
+ * method.
+ *
+ * @file RedBeanPHP/Repository/Fluid.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Fluid extends Repository
+{
+ /**
+ * Figures out the desired type given the cast string ID.
+ * Given a cast ID, this method will return the associated
+ * type (INT(10) or VARCHAR for instance). The returned type
+ * can be processed by the Query Writer to build the specified
+ * column for you in the database. The Cast ID is actually just
+ * a superset of the QueryWriter types. In addition to default
+ * Query Writer column types you can pass the following 'cast types':
+ * 'id' and 'string'. These will map to Query Writer specific
+ * column types (probably INT and VARCHAR).
+ *
+ * @param string $cast cast identifier
+ *
+ * @return integer
+ */
+ private function getTypeFromCast( $cast )
+ {
+ if ( $cast == 'string' ) {
+ $typeno = $this->writer->scanType( 'STRING' );
+ } elseif ( $cast == 'id' ) {
+ $typeno = $this->writer->getTypeForID();
+ } elseif ( isset( $this->writer->sqltype_typeno[$cast] ) ) {
+ $typeno = $this->writer->sqltype_typeno[$cast];
+ } else {
+ throw new RedException( 'Invalid Cast' );
+ }
+
+ return $typeno;
+ }
+
+ /**
+ * Orders the Query Writer to create a table if it does not exist already and
+ * adds a note in the build report about the creation.
+ *
+ * @param OODBBean $bean bean to update report of
+ * @param string $table table to check and create if not exists
+ *
+ * @return void
+ */
+ private function createTableIfNotExists( OODBBean $bean, $table )
+ {
+ //Does table exist? If not, create
+ if ( !$this->tableExists( $this->writer->esc( $table, TRUE ) ) ) {
+ $this->writer->createTable( $table );
+ $bean->setMeta( 'buildreport.flags.created', TRUE );
+ }
+ }
+
+ /**
+ * Modifies the table to fit the bean data.
+ * Given a property and a value and the bean, this method will
+ * adjust the table structure to fit the requirements of the property and value.
+ * This may include adding a new column or widening an existing column to hold a larger
+ * or different kind of value. This method employs the writer to adjust the table
+ * structure in the database. Schema updates are recorded in meta properties of the bean.
+ *
+ * This method will also apply indexes, unique constraints and foreign keys.
+ *
+ * @param OODBBean $bean bean to get cast data from and store meta in
+ * @param string $property property to store
+ * @param mixed $value value to store
+ * @param array|NULL &$columns
+ *
+ * @return void
+ */
+ private function modifySchema( OODBBean $bean, $property, $value, &$columns = NULL )
+ {
+ $doFKStuff = FALSE;
+ $table = $bean->getMeta( 'type' );
+ if ($columns === NULL) {
+ $columns = $this->writer->getColumns( $table );
+ }
+ $columnNoQ = $this->writer->esc( $property, TRUE );
+ if ( !$this->oodb->isChilled( $bean->getMeta( 'type' ) ) ) {
+ if ( $bean->getMeta( "cast.$property", -1 ) !== -1 ) { //check for explicitly specified types
+ $cast = $bean->getMeta( "cast.$property" );
+ $typeno = $this->getTypeFromCast( $cast );
+ } else {
+ $cast = FALSE;
+ $typeno = $this->writer->scanType( $value, TRUE );
+ }
+ if ( isset( $columns[$this->writer->esc( $property, TRUE )] ) ) { //Is this property represented in the table ?
+ if ( !$cast ) { //rescan without taking into account special types >80
+ $typeno = $this->writer->scanType( $value, FALSE );
+ }
+ $sqlt = $this->writer->code( $columns[$this->writer->esc( $property, TRUE )] );
+ if ( $typeno > $sqlt ) { //no, we have to widen the database column type
+ $this->writer->widenColumn( $table, $property, $typeno );
+ $bean->setMeta( 'buildreport.flags.widen', TRUE );
+ $doFKStuff = TRUE;
+ }
+ } else {
+ $this->writer->addColumn( $table, $property, $typeno );
+ $bean->setMeta( 'buildreport.flags.addcolumn', TRUE );
+ $doFKStuff = TRUE;
+ }
+ if ($doFKStuff) {
+ if (strrpos($columnNoQ, '_id')===(strlen($columnNoQ)-3)) {
+ $destinationColumnNoQ = substr($columnNoQ, 0, strlen($columnNoQ)-3);
+ $indexName = "index_foreignkey_{$table}_{$destinationColumnNoQ}";
+ $this->writer->addIndex($table, $indexName, $columnNoQ);
+ $typeof = $bean->getMeta("sys.typeof.{$destinationColumnNoQ}", $destinationColumnNoQ);
+ $isLink = $bean->getMeta( 'sys.buildcommand.unique', FALSE );
+ //Make FK CASCADING if part of exclusive list (dependson=typeof) or if link bean
+ $isDep = ( $bean->moveMeta( 'sys.buildcommand.fkdependson' ) === $typeof || is_array( $isLink ) );
+ $result = $this->writer->addFK( $table, $typeof, $columnNoQ, 'id', $isDep );
+ //If this is a link bean and all unique columns have been added already, then apply unique constraint
+ if ( is_array( $isLink ) && !count( array_diff( $isLink, array_keys( $this->writer->getColumns( $table ) ) ) ) ) {
+ $this->writer->addUniqueConstraint( $table, $bean->moveMeta('sys.buildcommand.unique') );
+ $bean->setMeta("sys.typeof.{$destinationColumnNoQ}", NULL);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Part of the store() functionality.
+ * Handles all new additions after the bean has been saved.
+ * Stores addition bean in own-list, extracts the id and
+ * adds a foreign key. Also adds a constraint in case the type is
+ * in the dependent list.
+ *
+ * Note that this method raises a custom exception if the bean
+ * is not an instance of OODBBean. Therefore it does not use
+ * a type hint. This allows the user to take action in case
+ * invalid objects are passed in the list.
+ *
+ * @param OODBBean $bean bean to process
+ * @param array $ownAdditions list of addition beans in own-list
+ *
+ * @return void
+ */
+ protected function processAdditions( $bean, $ownAdditions )
+ {
+ $beanType = $bean->getMeta( 'type' );
+
+ foreach ( $ownAdditions as $addition ) {
+ if ( $addition instanceof OODBBean ) {
+
+ $myFieldLink = $beanType . '_id';
+ $alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
+ if ( $alias ) $myFieldLink = $alias . '_id';
+
+ $addition->$myFieldLink = $bean->id;
+ $addition->setMeta( 'cast.' . $myFieldLink, 'id' );
+
+ if ($alias) {
+ $addition->setMeta( "sys.typeof.{$alias}", $beanType );
+ } else {
+ $addition->setMeta( "sys.typeof.{$beanType}", $beanType );
+ }
+
+ $this->store( $addition );
+ } else {
+ throw new RedException( 'Array may only contain OODBBeans' );
+ }
+ }
+ }
+
+ /**
+ * Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
+ * method. When all lists and embedded beans (parent objects) have been processed and
+ * removed from the original bean the bean is passed to this method to be stored
+ * in the database.
+ *
+ * @param OODBBean $bean the clean bean
+ *
+ * @return void
+ */
+ protected function storeBean( OODBBean $bean )
+ {
+ if ( $bean->getMeta( 'changed' ) ) {
+ $this->check( $bean );
+ $table = $bean->getMeta( 'type' );
+ $this->createTableIfNotExists( $bean, $table );
+
+ $updateValues = array();
+
+ $partial = ( $this->partialBeans === TRUE || ( is_array( $this->partialBeans ) && in_array( $table, $this->partialBeans ) ) );
+ if ( $partial ) {
+ $mask = $bean->getMeta( 'changelist' );
+ $bean->setMeta( 'changelist', array() );
+ }
+
+ $columnCache = NULL;
+ foreach ( $bean as $property => $value ) {
+ if ( $partial && !in_array( $property, $mask ) ) continue;
+ if ( $property !== 'id' ) {
+ $this->modifySchema( $bean, $property, $value, $columnCache );
+ }
+ if ( $property !== 'id' ) {
+ $updateValues[] = array( 'property' => $property, 'value' => $value );
+ }
+ }
+
+ $bean->id = $this->writer->updateRecord( $table, $updateValues, $bean->id );
+ $bean->setMeta( 'changed', FALSE );
+ }
+ $bean->setMeta( 'tainted', FALSE );
+ }
+
+ /**
+ * Exception handler.
+ * Fluid and Frozen mode have different ways of handling
+ * exceptions. Fluid mode (using the fluid repository) ignores
+ * exceptions caused by the following:
+ *
+ * - missing tables
+ * - missing column
+ *
+ * In these situations, the repository will behave as if
+ * no beans could be found. This is because in fluid mode
+ * it might happen to query a table or column that has not been
+ * created yet. In frozen mode, this is not supposed to happen
+ * and the corresponding exceptions will be thrown.
+ *
+ * @param \Exception $exception exception
+ *
+ * @return void
+ */
+ protected function handleException( \Exception $exception )
+ {
+ if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
+ array(
+ QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
+ QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ),
+ $exception->getDriverDetails() )
+ ) {
+ throw $exception;
+ }
+ }
+
+ /**
+ * Loads a bean from the object database.
+ * It searches for a OODBBean Bean Object in the
+ * database. It does not matter how this bean has been stored.
+ * RedBean uses the primary key ID $id and the string $type
+ * to find the bean. The $type specifies what kind of bean you
+ * are looking for; this is the same type as used with the
+ * dispense() function. If RedBean finds the bean it will return
+ * the OODB Bean object; if it cannot find the bean
+ * RedBean will return a new bean of type $type and with
+ * primary key ID 0. In the latter case it acts basically the
+ * same as dispense().
+ *
+ * Important note:
+ * If the bean cannot be found in the database a new bean of
+ * the specified type will be generated and returned.
+ *
+ * @param string $type type of bean you want to load
+ * @param integer $id ID of the bean you want to load
+ *
+ * @return OODBBean
+ */
+ public function load( $type, $id )
+ {
+ $rows = array();
+ $bean = $this->dispense( $type );
+ if ( isset( $this->stash[$this->nesting][$id] ) ) {
+ $row = $this->stash[$this->nesting][$id];
+ } else {
+ try {
+ $rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
+ } catch ( SQLException $exception ) {
+ if (
+ $this->writer->sqlStateIn(
+ $exception->getSQLState(),
+ array(
+ QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
+ QueryWriter::C_SQLSTATE_NO_SUCH_TABLE
+ ),
+ $exception->getDriverDetails()
+ )
+ ) {
+ $rows = array();
+ } else {
+ throw $exception;
+ }
+ }
+ if ( !count( $rows ) ) {
+ return $bean;
+ }
+ $row = array_pop( $rows );
+ }
+ $bean->importRow( $row );
+ $this->nesting++;
+ $this->oodb->signal( 'open', $bean );
+ $this->nesting--;
+
+ return $bean->setMeta( 'tainted', FALSE );
+ }
+}
+}
+
+namespace RedBeanPHP\Repository {
+
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\RedException as RedException;
+use RedBeanPHP\BeanHelper as BeanHelper;
+use RedBeanPHP\RedException\SQL as SQLException;
+use RedBeanPHP\Repository as Repository;
+
+/**
+ * Frozen Repository.
+ * OODB manages two repositories, a fluid one that
+ * adjust the database schema on-the-fly to accommodate for
+ * new bean types (tables) and new properties (columns) and
+ * a frozen one for use in a production environment. OODB
+ * allows you to swap the repository instances using the freeze()
+ * method.
+ *
+ * @file RedBeanPHP/Repository/Frozen.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Frozen extends Repository
+{
+ /**
+ * Exception handler.
+ * Fluid and Frozen mode have different ways of handling
+ * exceptions. Fluid mode (using the fluid repository) ignores
+ * exceptions caused by the following:
+ *
+ * - missing tables
+ * - missing column
+ *
+ * In these situations, the repository will behave as if
+ * no beans could be found. This is because in fluid mode
+ * it might happen to query a table or column that has not been
+ * created yet. In frozen mode, this is not supposed to happen
+ * and the corresponding exceptions will be thrown.
+ *
+ * @param \Exception $exception exception
+ *
+ * @return void
+ */
+ protected function handleException( \Exception $exception )
+ {
+ throw $exception;
+ }
+
+ /**
+ * Stores a cleaned bean; i.e. only scalar values. This is the core of the store()
+ * method. When all lists and embedded beans (parent objects) have been processed and
+ * removed from the original bean the bean is passed to this method to be stored
+ * in the database.
+ *
+ * @param OODBBean $bean the clean bean
+ *
+ * @return void
+ */
+ protected function storeBean( OODBBean $bean )
+ {
+ if ( $bean->getMeta( 'changed' ) ) {
+
+ list( $properties, $table ) = $bean->getPropertiesAndType();
+ $id = $properties['id'];
+ unset($properties['id']);
+ $updateValues = array();
+ $k1 = 'property';
+ $k2 = 'value';
+
+ $partial = ( $this->partialBeans === TRUE || ( is_array( $this->partialBeans ) && in_array( $table, $this->partialBeans ) ) );
+ if ( $partial ) {
+ $mask = $bean->getMeta( 'changelist' );
+ $bean->setMeta( 'changelist', array() );
+ }
+
+ foreach( $properties as $key => $value ) {
+ if ( $partial && !in_array( $key, $mask ) ) continue;
+ $updateValues[] = array( $k1 => $key, $k2 => $value );
+ }
+ $bean->id = $this->writer->updateRecord( $table, $updateValues, $id );
+ $bean->setMeta( 'changed', FALSE );
+ }
+ $bean->setMeta( 'tainted', FALSE );
+ }
+
+ /**
+ * Part of the store() functionality.
+ * Handles all new additions after the bean has been saved.
+ * Stores addition bean in own-list, extracts the id and
+ * adds a foreign key. Also adds a constraint in case the type is
+ * in the dependent list.
+ *
+ * Note that this method raises a custom exception if the bean
+ * is not an instance of OODBBean. Therefore it does not use
+ * a type hint. This allows the user to take action in case
+ * invalid objects are passed in the list.
+ *
+ * @param OODBBean $bean bean to process
+ * @param array $ownAdditions list of addition beans in own-list
+ *
+ * @return void
+ * @throws RedException
+ */
+ protected function processAdditions( $bean, $ownAdditions )
+ {
+ $beanType = $bean->getMeta( 'type' );
+
+ $cachedIndex = array();
+ foreach ( $ownAdditions as $addition ) {
+ if ( $addition instanceof OODBBean ) {
+
+ $myFieldLink = $beanType . '_id';
+ $alias = $bean->getMeta( 'sys.alias.' . $addition->getMeta( 'type' ) );
+ if ( $alias ) $myFieldLink = $alias . '_id';
+
+ $addition->$myFieldLink = $bean->id;
+ $addition->setMeta( 'cast.' . $myFieldLink, 'id' );
+ $this->store( $addition );
+
+ } else {
+ throw new RedException( 'Array may only contain OODBBeans' );
+ }
+ }
+ }
+
+ /**
+ * Loads a bean from the object database.
+ * It searches for a OODBBean Bean Object in the
+ * database. It does not matter how this bean has been stored.
+ * RedBean uses the primary key ID $id and the string $type
+ * to find the bean. The $type specifies what kind of bean you
+ * are looking for; this is the same type as used with the
+ * dispense() function. If RedBean finds the bean it will return
+ * the OODB Bean object; if it cannot find the bean
+ * RedBean will return a new bean of type $type and with
+ * primary key ID 0. In the latter case it acts basically the
+ * same as dispense().
+ *
+ * Important note:
+ * If the bean cannot be found in the database a new bean of
+ * the specified type will be generated and returned.
+ *
+ * @param string $type type of bean you want to load
+ * @param integer $id ID of the bean you want to load
+ *
+ * @return OODBBean
+ * @throws SQLException
+ */
+ public function load( $type, $id )
+ {
+ $rows = array();
+ $bean = $this->dispense( $type );
+ if ( isset( $this->stash[$this->nesting][$id] ) ) {
+ $row = $this->stash[$this->nesting][$id];
+ } else {
+ $rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) );
+ if ( !count( $rows ) ) {
+ return $bean;
+ }
+ $row = array_pop( $rows );
+ }
+ $bean->importRow( $row );
+ $this->nesting++;
+ $this->oodb->signal( 'open', $bean );
+ $this->nesting--;
+
+ return $bean->setMeta( 'tainted', FALSE );
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\BeanHelper as BeanHelper;
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+use RedBeanPHP\Repository as Repository;
+use RedBeanPHP\Repository\Fluid as FluidRepo;
+use RedBeanPHP\Repository\Frozen as FrozenRepo;
+
+/**
+ * RedBean Object Oriented DataBase.
+ *
+ * The RedBean OODB Class is the main class of RedBeanPHP.
+ * It takes OODBBean objects and stores them to and loads them from the
+ * database as well as providing other CRUD functions. This class acts as a
+ * object database.
+ *
+ * @file RedBeanPHP/OODB.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class OODB extends Observable
+{
+ /**
+ * @var array
+ */
+ private static $sqlFilters = array();
+
+ /**
+ * @var array
+ */
+ protected $chillList = array();
+
+ /**
+ * @var array
+ */
+ protected $stash = NULL;
+
+ /*
+ * @var integer
+ */
+ protected $nesting = 0;
+
+ /**
+ * @var QueryWriter
+ */
+ protected $writer;
+
+ /**
+ * @var boolean
+ */
+ protected $isFrozen = FALSE;
+
+ /**
+ * @var BeanHelper|NULL
+ */
+ protected $beanhelper = NULL;
+
+ /**
+ * @var AssociationManager|NULL
+ */
+ protected $assocManager = NULL;
+
+ /**
+ * @var Repository
+ */
+ protected $repository = NULL;
+
+ /**
+ * @var FrozenRepo|NULL
+ */
+ protected $frozenRepository = NULL;
+
+ /**
+ * @var FluidRepo|NULL
+ */
+ protected $fluidRepository = NULL;
+
+ /**
+ * @var boolean
+ */
+ protected static $autoClearHistoryAfterStore = FALSE;
+
+ /**
+ * If set to TRUE, this method will call clearHistory every time
+ * the bean gets stored.
+ *
+ * @param boolean $autoClear auto clear option
+ *
+ * @return void
+ */
+ public static function autoClearHistoryAfterStore( $autoClear = TRUE )
+ {
+ self::$autoClearHistoryAfterStore = (boolean) $autoClear;
+ }
+
+ /**
+ * Unboxes a bean from a FUSE model if needed and checks whether the bean is
+ * an instance of OODBBean.
+ *
+ * @param OODBBean|SimpleModel|SimpleModelInterface $bean bean you wish to unbox
+ *
+ * @return OODBBean
+ */
+ protected function unboxIfNeeded( $bean )
+ {
+ if ( $bean instanceof SimpleModelInterface ) {
+ $bean = $bean->unbox();
+ }
+ if ( !( $bean instanceof OODBBean ) ) {
+ throw new RedException( 'OODB Store requires a bean, got: ' . gettype( $bean ) );
+ }
+
+ return $bean;
+ }
+
+ /**
+ * Constructor, requires a query writer.
+ * Most of the time, you do not need to use this constructor,
+ * since the facade takes care of constructing and wiring the
+ * RedBeanPHP core objects. However if you would like to
+ * assemble an OODB instance yourself, this is how it works:
+ *
+ * Usage:
+ *
+ *
+ * $database = new RPDO( $dsn, $user, $pass );
+ * $adapter = new DBAdapter( $database );
+ * $writer = new PostgresWriter( $adapter );
+ * $oodb = new OODB( $writer, FALSE );
+ * $bean = $oodb->dispense( 'bean' );
+ * $bean->name = 'coffeeBean';
+ * $id = $oodb->store( $bean );
+ * $bean = $oodb->load( 'bean', $id );
+ *
+ *
+ * The example above creates the 3 RedBeanPHP core objects:
+ * the Adapter, the Query Writer and the OODB instance and
+ * wires them together. The example also demonstrates some of
+ * the methods that can be used with OODB, as you see, they
+ * closely resemble their facade counterparts.
+ *
+ * The wiring process: create an RPDO instance using your database
+ * connection parameters. Create a database adapter from the RPDO
+ * object and pass that to the constructor of the writer. Next,
+ * create an OODB instance from the writer. Now you have an OODB
+ * object.
+ *
+ * @param QueryWriter $writer writer
+ * @param array|boolean $frozen mode of operation: TRUE (frozen), FALSE (default, fluid) or ARRAY (chilled)
+ */
+ public function __construct( QueryWriter $writer, $frozen = FALSE )
+ {
+ if ( $writer instanceof QueryWriter ) {
+ $this->writer = $writer;
+ }
+
+ $this->freeze( $frozen );
+ }
+
+ /**
+ * Toggles fluid or frozen mode. In fluid mode the database
+ * structure is adjusted to accommodate your objects. In frozen mode
+ * this is not the case.
+ *
+ * You can also pass an array containing a selection of frozen types.
+ * Let's call this chill mode, it's just like fluid mode except that
+ * certain types (i.e. tables) aren't touched.
+ *
+ * @param boolean|array $toggle TRUE if you want to use OODB instance in frozen mode
+ *
+ * @return void
+ */
+ public function freeze( $toggle )
+ {
+ if ( is_array( $toggle ) ) {
+ $this->chillList = $toggle;
+ $this->isFrozen = FALSE;
+ } else {
+ $this->isFrozen = (boolean) $toggle;
+ }
+
+ if ( $this->isFrozen ) {
+ if ( !$this->frozenRepository ) {
+ $this->frozenRepository = new FrozenRepo( $this, $this->writer );
+ }
+
+ $this->repository = $this->frozenRepository;
+
+ } else {
+ if ( !$this->fluidRepository ) {
+ $this->fluidRepository = new FluidRepo( $this, $this->writer );
+ }
+
+ $this->repository = $this->fluidRepository;
+ }
+
+ if ( count( self::$sqlFilters ) ) {
+ AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
+ }
+
+ }
+
+ /**
+ * Returns the current mode of operation of RedBean.
+ * In fluid mode the database
+ * structure is adjusted to accommodate your objects.
+ * In frozen mode
+ * this is not the case.
+ *
+ * @return boolean
+ */
+ public function isFrozen()
+ {
+ return (bool) $this->isFrozen;
+ }
+
+ /**
+ * Determines whether a type is in the chill list.
+ * If a type is 'chilled' it's frozen, so its schema cannot be
+ * changed anymore. However other bean types may still be modified.
+ * This method is a convenience method for other objects to check if
+ * the schema of a certain type is locked for modification.
+ *
+ * @param string $type the type you wish to check
+ *
+ * @return boolean
+ */
+ public function isChilled( $type )
+ {
+ return (boolean) ( in_array( $type, $this->chillList ) );
+ }
+
+ /**
+ * Dispenses a new bean (a OODBBean Bean Object)
+ * of the specified type. Always
+ * use this function to get an empty bean object. Never
+ * instantiate a OODBBean yourself because it needs
+ * to be configured before you can use it with RedBean. This
+ * function applies the appropriate initialization /
+ * configuration for you.
+ *
+ * @param string $type type of bean you want to dispense
+ * @param string $number number of beans you would like to get
+ * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
+ *
+ * @return OODBBean|OODBBean[]
+ */
+ public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
+ {
+ if ( $number < 1 ) {
+ if ( $alwaysReturnArray ) return array();
+ return NULL;
+ }
+
+ return $this->repository->dispense( $type, $number, $alwaysReturnArray );
+ }
+
+ /**
+ * Sets bean helper to be given to beans.
+ * Bean helpers assist beans in getting a reference to a toolbox.
+ *
+ * @param BeanHelper $beanhelper helper
+ *
+ * @return void
+ */
+ public function setBeanHelper( BeanHelper $beanhelper )
+ {
+ $this->beanhelper = $beanhelper;
+ }
+
+ /**
+ * Returns the current bean helper.
+ * Bean helpers assist beans in getting a reference to a toolbox.
+ *
+ * @return BeanHelper|NULL
+ */
+ public function getBeanHelper()
+ {
+ return $this->beanhelper;
+ }
+
+ /**
+ * Checks whether a OODBBean bean is valid.
+ * If the type is not valid or the ID is not valid it will
+ * throw an exception: Security.
+ *
+ * @param OODBBean $bean the bean that needs to be checked
+ *
+ * @return void
+ */
+ public function check( OODBBean $bean )
+ {
+ $this->repository->check( $bean );
+ }
+
+ /**
+ * Searches the database for a bean that matches conditions $conditions and sql $sql
+ * and returns an array containing all the beans that have been found.
+ *
+ * Conditions need to take form:
+ *
+ *
+ * array(
+ * 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
+ * 'PROPERTY' => array( POSSIBLE VALUES... )
+ * );
+ *
+ *
+ * All conditions are glued together using the AND-operator, while all value lists
+ * are glued using IN-operators thus acting as OR-conditions.
+ *
+ * Note that you can use property names; the columns will be extracted using the
+ * appropriate bean formatter.
+ *
+ * @param string $type type of beans you are looking for
+ * @param array $conditions list of conditions
+ * @param string|NULL $sql SQL to be used in query
+ * @param array $bindings a list of values to bind to query parameters
+ *
+ * @return array
+ */
+ public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
+ {
+ return $this->repository->find( $type, $conditions, $sql, $bindings );
+ }
+
+ /**
+ * Same as find() but returns a BeanCollection.
+ *
+ * @param string $type type of beans you are looking for
+ * @param string|NULL $sql SQL to be used in query
+ * @param array $bindings a list of values to bind to query parameters
+ *
+ * @return BeanCollection
+ */
+ public function findCollection( $type, $sql = NULL, $bindings = array() )
+ {
+ return $this->repository->findCollection( $type, $sql, $bindings );
+ }
+
+ /**
+ * Checks whether the specified table already exists in the database.
+ *
+ * @param string $table table name
+ *
+ * @return boolean
+ */
+ public function tableExists( $table )
+ {
+ return $this->repository->tableExists( $table );
+ }
+
+ /**
+ * Stores a bean in the database. This method takes a
+ * OODBBean Bean Object $bean and stores it
+ * in the database. If the database schema is not compatible
+ * with this bean and RedBean runs in fluid mode the schema
+ * will be altered to store the bean correctly.
+ * If the database schema is not compatible with this bean and
+ * RedBean runs in frozen mode it will throw an exception.
+ * This function returns the primary key ID of the inserted
+ * bean.
+ *
+ * The return value is an integer if possible. If it is not possible to
+ * represent the value as an integer a string will be returned. We use
+ * explicit casts instead of functions to preserve performance
+ * (0.13 vs 0.28 for 10000 iterations on Core i3).
+ *
+ * @param OODBBean|SimpleModel|SimpleModelInterface $bean bean to store
+ *
+ * @return integer|string
+ */
+ public function store( $bean )
+ {
+ $bean = $this->unboxIfNeeded( $bean );
+ $id = $this->repository->store( $bean );
+ if ( self::$autoClearHistoryAfterStore ) {
+ $bean->clearHistory();
+ }
+ return $id;
+ }
+
+ /**
+ * Loads a bean from the object database.
+ * It searches for a OODBBean Bean Object in the
+ * database. It does not matter how this bean has been stored.
+ * RedBean uses the primary key ID $id and the string $type
+ * to find the bean. The $type specifies what kind of bean you
+ * are looking for; this is the same type as used with the
+ * dispense() function. If RedBean finds the bean it will return
+ * the OODB Bean object; if it cannot find the bean
+ * RedBean will return a new bean of type $type and with
+ * primary key ID 0. In the latter case it acts basically the
+ * same as dispense().
+ *
+ * Important note:
+ * If the bean cannot be found in the database a new bean of
+ * the specified type will be generated and returned.
+ *
+ * @param string $type type of bean you want to load
+ * @param integer $id ID of the bean you want to load
+ *
+ * @return OODBBean
+ */
+ public function load( $type, $id )
+ {
+ return $this->repository->load( $type, $id );
+ }
+
+ /**
+ * Removes a bean from the database.
+ * This function will remove the specified OODBBean
+ * Bean Object from the database.
+ *
+ * @param OODBBean|SimpleModel|SimpleModelInterface $bean bean you want to remove from database
+ *
+ * @return int
+ */
+ public function trash( $bean )
+ {
+ $bean = $this->unboxIfNeeded( $bean );
+ return $this->repository->trash( $bean );
+ }
+
+ /**
+ * Returns an array of beans. Pass a type and a series of ids and
+ * this method will bring you the corresponding beans.
+ *
+ * important note: Because this method loads beans using the load()
+ * function (but faster) it will return empty beans with ID 0 for
+ * every bean that could not be located. The resulting beans will have the
+ * passed IDs as their keys.
+ *
+ * @param string $type type of beans
+ * @param array $ids ids to load
+ *
+ * @return array
+ */
+ public function batch( $type, $ids )
+ {
+ return $this->repository->batch( $type, $ids );
+ }
+
+ /**
+ * This is a convenience method; it converts database rows
+ * (arrays) into beans. Given a type and a set of rows this method
+ * will return an array of beans of the specified type loaded with
+ * the data fields provided by the result set from the database.
+ *
+ * @param string $type type of beans you would like to have
+ * @param array $rows rows from the database result
+ * @param string $mask mask to apply for meta data
+ *
+ * @return array
+ */
+ public function convertToBeans( $type, $rows, $mask = NULL )
+ {
+ return $this->repository->convertToBeans( $type, $rows, $mask );
+ }
+
+ /**
+ * Counts the number of beans of type $type.
+ * This method accepts a second argument to modify the count-query.
+ * A third argument can be used to provide bindings for the SQL snippet.
+ *
+ * @param string $type type of bean we are looking for
+ * @param string $addSQL additional SQL snippet
+ * @param array $bindings parameters to bind to SQL
+ *
+ * @return integer
+ */
+ public function count( $type, $addSQL = '', $bindings = array() )
+ {
+ return $this->repository->count( $type, $addSQL, $bindings );
+ }
+
+ /**
+ * Trash all beans of a given type. Wipes an entire type of bean.
+ *
+ * @param string $type type of bean you wish to delete all instances of
+ *
+ * @return boolean
+ */
+ public function wipe( $type )
+ {
+ return $this->repository->wipe( $type );
+ }
+
+ /**
+ * Returns an Association Manager for use with OODB.
+ * A simple getter function to obtain a reference to the association manager used for
+ * storage and more.
+ *
+ * @return AssociationManager
+ */
+ public function getAssociationManager()
+ {
+ if ( !isset( $this->assocManager ) ) {
+ throw new RedException( 'No association manager available.' );
+ }
+
+ return $this->assocManager;
+ }
+
+ /**
+ * Sets the association manager instance to be used by this OODB.
+ * A simple setter function to set the association manager to be used for storage and
+ * more.
+ *
+ * @param AssociationManager $assocManager sets the association manager to be used
+ *
+ * @return void
+ */
+ public function setAssociationManager( AssociationManager $assocManager )
+ {
+ $this->assocManager = $assocManager;
+ }
+
+ /**
+ * Returns the currently used repository instance.
+ * For testing purposes only.
+ *
+ * @return Repository
+ */
+ public function getCurrentRepository()
+ {
+ return $this->repository;
+ }
+
+ /**
+ * Clears all function bindings.
+ *
+ * @return void
+ */
+ public function clearAllFuncBindings()
+ {
+ self::$sqlFilters = array();
+ AQueryWriter::setSQLFilters( self::$sqlFilters, FALSE );
+ }
+
+ /**
+ * Binds an SQL function to a column.
+ * This method can be used to setup a decode/encode scheme or
+ * perform UUID insertion. This method is especially useful for handling
+ * MySQL spatial columns, because they need to be processed first using
+ * the asText/GeomFromText functions.
+ *
+ * @param string $mode mode to set function for, i.e. read or write
+ * @param string $field field (table.column) to bind SQL function to
+ * @param string|NULL $function SQL function to bind to field
+ * @param boolean $isTemplate TRUE if $function is an SQL string, FALSE for just a function name
+ *
+ * @return void
+ */
+ public function bindFunc( $mode, $field, $function, $isTemplate = FALSE )
+ {
+ list( $type, $property ) = explode( '.', $field );
+ $mode = ($mode === 'write') ? QueryWriter::C_SQLFILTER_WRITE : QueryWriter::C_SQLFILTER_READ;
+
+ if ( !isset( self::$sqlFilters[$mode] ) ) self::$sqlFilters[$mode] = array();
+ if ( !isset( self::$sqlFilters[$mode][$type] ) ) self::$sqlFilters[$mode][$type] = array();
+
+ if ( is_null( $function ) ) {
+ unset( self::$sqlFilters[$mode][$type][$property] );
+ } else {
+ if ($mode === QueryWriter::C_SQLFILTER_WRITE) {
+ if ($isTemplate) {
+ $code = sprintf( $function, '?' );
+ } else {
+ $code = "{$function}(?)";
+ }
+ self::$sqlFilters[$mode][$type][$property] = $code;
+ } else {
+ if ($isTemplate) {
+ $code = sprintf( $function, $field );
+ } else {
+ $code = "{$function}({$field})";
+ }
+ self::$sqlFilters[$mode][$type][$property] = $code;
+ }
+ }
+ AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
+use RedBeanPHP\Adapter as Adapter;
+
+/**
+ * ToolBox.
+ *
+ * The toolbox is an integral part of RedBeanPHP providing the basic
+ * architectural building blocks to manager objects, helpers and additional tools
+ * like plugins. A toolbox contains the three core components of RedBeanPHP:
+ * the adapter, the query writer and the core functionality of RedBeanPHP in
+ * OODB.
+ *
+ * @file RedBeanPHP/ToolBox.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class ToolBox
+{
+ /**
+ * @var OODB
+ */
+ protected $oodb;
+
+ /**
+ * @var QueryWriter
+ */
+ protected $writer;
+
+ /**
+ * @var DBAdapter
+ */
+ protected $adapter;
+
+ /**
+ * Constructor.
+ * The toolbox is an integral part of RedBeanPHP providing the basic
+ * architectural building blocks to manager objects, helpers and additional tools
+ * like plugins. A toolbox contains the three core components of RedBeanPHP:
+ * the adapter, the query writer and the core functionality of RedBeanPHP in
+ * OODB.
+ *
+ * Usage:
+ *
+ *
+ * $toolbox = new ToolBox( $oodb, $adapter, $writer );
+ * $plugin = new MyPlugin( $toolbox );
+ *
+ *
+ * The example above illustrates how the toolbox is used.
+ * The core objects are passed to the ToolBox constructor to
+ * assemble a toolbox instance. The toolbox is then passed to
+ * the plugin, helper or manager object. Instances of
+ * TagManager, AssociationManager and so on are examples of
+ * this, they all require a toolbox. The toolbox can also
+ * be obtained from the facade using: R::getToolBox();
+ *
+ * @param OODB $oodb Object Database, OODB
+ * @param DBAdapter $adapter Database Adapter
+ * @param QueryWriter $writer Query Writer
+ */
+ public function __construct( OODB $oodb, Adapter $adapter, QueryWriter $writer )
+ {
+ $this->oodb = $oodb;
+ $this->adapter = $adapter;
+ $this->writer = $writer;
+ return $this;
+ }
+
+ /**
+ * Returns the query writer in this toolbox.
+ * The Query Writer is responsible for building the queries for a
+ * specific database and executing them through the adapter.
+ *
+ * Usage:
+ *
+ *
+ * $toolbox = R::getToolBox();
+ * $redbean = $toolbox->getRedBean();
+ * $adapter = $toolbox->getDatabaseAdapter();
+ * $writer = $toolbox->getWriter();
+ *
+ *
+ * The example above illustrates how to obtain the core objects
+ * from a toolbox instance. If you are working with the R-object
+ * only, the following shortcuts exist as well:
+ *
+ * - R::getRedBean()
+ * - R::getDatabaseAdapter()
+ * - R::getWriter()
+ *
+ * @return QueryWriter
+ */
+ public function getWriter()
+ {
+ return $this->writer;
+ }
+
+ /**
+ * Returns the OODB instance in this toolbox.
+ * OODB is responsible for creating, storing, retrieving and deleting
+ * single beans. Other components rely
+ * on OODB for their basic functionality.
+ *
+ * Usage:
+ *
+ *
+ * $toolbox = R::getToolBox();
+ * $redbean = $toolbox->getRedBean();
+ * $adapter = $toolbox->getDatabaseAdapter();
+ * $writer = $toolbox->getWriter();
+ *
+ *
+ * The example above illustrates how to obtain the core objects
+ * from a toolbox instance. If you are working with the R-object
+ * only, the following shortcuts exist as well:
+ *
+ * - R::getRedBean()
+ * - R::getDatabaseAdapter()
+ * - R::getWriter()
+ *
+ * @return OODB
+ */
+ public function getRedBean()
+ {
+ return $this->oodb;
+ }
+
+ /**
+ * Returns the database adapter in this toolbox.
+ * The adapter is responsible for executing the query and binding the values.
+ * The adapter also takes care of transaction handling.
+ *
+ * Usage:
+ *
+ *
+ * $toolbox = R::getToolBox();
+ * $redbean = $toolbox->getRedBean();
+ * $adapter = $toolbox->getDatabaseAdapter();
+ * $writer = $toolbox->getWriter();
+ *
+ *
+ * The example above illustrates how to obtain the core objects
+ * from a toolbox instance. If you are working with the R-object
+ * only, the following shortcuts exist as well:
+ *
+ * - R::getRedBean()
+ * - R::getDatabaseAdapter()
+ * - R::getWriter()
+ *
+ * @return DBAdapter
+ */
+ public function getDatabaseAdapter()
+ {
+ return $this->adapter;
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+
+/**
+ * RedBeanPHP Finder.
+ * Service class to find beans. For the most part this class
+ * offers user friendly utility methods for interacting with the
+ * OODB::find() method, which is rather complex. This class can be
+ * used to find beans using plain old SQL queries.
+ *
+ * @file RedBeanPHP/Finder.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Finder
+{
+ /**
+ * @var ToolBox
+ */
+ protected $toolbox;
+
+ /**
+ * @var OODB
+ */
+ protected $redbean;
+
+ /**
+ * Constructor.
+ * The Finder requires a toolbox.
+ *
+ * @param ToolBox $toolbox
+ */
+ public function __construct( ToolBox $toolbox )
+ {
+ $this->toolbox = $toolbox;
+ $this->redbean = $toolbox->getRedBean();
+ }
+
+ /**
+ * A custom record-to-bean mapping function for findMulti.
+ *
+ * Usage:
+ *
+ *
+ * $collection = R::findMulti( 'shop,product,price',
+ * 'SELECT shop.*, product.*, price.* FROM shop
+ * LEFT JOIN product ON product.shop_id = shop.id
+ * LEFT JOIN price ON price.product_id = product.id', [], [
+ * Finder::map( 'shop', 'product' ),
+ * Finder::map( 'product', 'price' ),
+ * ]);
+ *
+ *
+ * @param string $parentName name of the parent bean
+ * @param string $childName name of the child bean
+ *
+ * @return array
+ */
+ public static function map($parentName,$childName) {
+ return array(
+ 'a' => $parentName,
+ 'b' => $childName,
+ 'matcher' => function( $parent, $child ) use ( $parentName, $childName ) {
+ $propertyName = 'own' . ucfirst( $childName );
+ if (!isset($parent[$propertyName])) {
+ $parent->noLoad()->{$propertyName} = array();
+ }
+ $property = "{$parentName}ID";
+ return ( $child->$property == $parent->id );
+ },
+ 'do' => function( $parent, $child ) use ( $childName ) {
+ $list = 'own'.ucfirst( $childName ).'List';
+ $parent->noLoad()->{$list}[$child->id] = $child;
+ }
+ );
+ }
+
+ /**
+ * A custom record-to-bean mapping function for findMulti.
+ *
+ * Usage:
+ *
+ *
+ * $collection = R::findMulti( 'book,book_tag,tag',
+ * 'SELECT book.*, book_tag.*, tag.* FROM book
+ * LEFT JOIN book_tag ON book_tag.book_id = book.id
+ * LEFT JOIN tag ON book_tag.tag_id = tag.id', [], [
+ * Finder::nmMap( 'book', 'tag' ),
+ * ]);
+ *
+ *
+ * @param string $parentName name of the parent bean
+ * @param string $childName name of the child bean
+ *
+ * @return array
+ */
+ public static function nmMap( $parentName, $childName )
+ {
+ $types = array($parentName, $childName);
+ sort( $types );
+ $link = implode( '_', $types );
+ return array(
+ 'a' => $parentName,
+ 'b' => $childName,
+ 'matcher' => function( $parent, $child, $beans ) use ( $parentName, $childName, $link ) {
+ $propertyName = 'shared' . ucfirst( $childName );
+ if (!isset($parent[$propertyName])) {
+ $parent->noLoad()->{$propertyName} = array();
+ }
+ foreach( $beans[$link] as $linkBean ) {
+ if ( $linkBean["{$parentName}ID"] == $parent->id && $linkBean["{$childName}ID"] == $child->id ) {
+ return true;
+ }
+ }
+ },
+ 'do' => function( $parent, $child ) use ( $childName ) {
+ $list = 'shared'.ucfirst( $childName ).'List';
+ $parent->noLoad()->{$list}[$child->id] = $child;
+ }
+ );
+ }
+
+ /**
+ * Finder::onMap() -> One-to-N mapping.
+ * A custom record-to-bean mapping function for findMulti.
+ * Opposite of Finder::map(). Maps child beans to parents.
+ *
+ * Usage:
+ *
+ *
+ * $collection = R::findMulti( 'shop,product',
+ * 'SELECT shop.*, product.* FROM shop
+ * LEFT JOIN product ON product.shop_id = shop.id',
+ * [], [
+ * Finder::onmap( 'product', 'shop' ),
+ * ]);
+ *
+ *
+ * Can also be used for instance to attach related beans
+ * in one-go to save some queries:
+ *
+ * Given $users that have a country_id:
+ *
+ *
+ * $all = R::findMulti('country',
+ * R::genSlots( $users,
+ * 'SELECT country.* FROM country WHERE id IN ( %s )' ),
+ * array_column( $users, 'country_id' ),
+ * [Finder::onmap('country', $users)]
+ * );
+ *
+ *
+ * For your convenience, an even shorter notation has been added:
+ *
+ * $countries = R::loadJoined( $users, 'country' );
+ *
+ * @param string $parentName name of the parent bean
+ * @param string|array $childName name of the child bean
+ *
+ * @return array
+ */
+ public static function onMap($parentName,$childNameOrBeans) {
+ return array(
+ 'a' => $parentName,
+ 'b' => $childNameOrBeans,
+ 'matcher' => array( $parentName, "{$parentName}_id" ),
+ 'do' => 'match'
+ );
+ }
+
+ /**
+ * Finds a bean using a type and a where clause (SQL).
+ * As with most Query tools in RedBean you can provide values to
+ * be inserted in the SQL statement by populating the value
+ * array parameter; you can either use the question mark notation
+ * or the slot-notation (:keyname).
+ *
+ * @param string $type type the type of bean you are looking for
+ * @param string|NULL $sql sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings values array of values to be bound to parameters in query
+ *
+ * @return array
+ */
+ public function find( $type, $sql = NULL, $bindings = array() )
+ {
+ if ( !is_array( $bindings ) ) {
+ throw new RedException(
+ 'Expected array, ' . gettype( $bindings ) . ' given.'
+ );
+ }
+
+ return $this->redbean->find( $type, array(), $sql, $bindings );
+ }
+
+ /**
+ * Like find() but also exports the beans as an array.
+ * This method will perform a find-operation. For every bean
+ * in the result collection this method will call the export() method.
+ * This method returns an array containing the array representations
+ * of every bean in the result set.
+ *
+ * @see Finder::find
+ *
+ * @param string $type type the type of bean you are looking for
+ * @param string|NULL $sql sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings values array of values to be bound to parameters in query
+ *
+ * @return array
+ */
+ public function findAndExport( $type, $sql = NULL, $bindings = array() )
+ {
+ $arr = array();
+ foreach ( $this->find( $type, $sql, $bindings ) as $key => $item ) {
+ $arr[] = $item->export();
+ }
+
+ return $arr;
+ }
+
+ /**
+ * Like find() but returns just one bean instead of an array of beans.
+ * This method will return only the first bean of the array.
+ * If no beans are found, this method will return NULL.
+ *
+ * @see Finder::find
+ *
+ * @param string $type type the type of bean you are looking for
+ * @param string|NULL $sql sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings values array of values to be bound to parameters in query
+ *
+ * @return OODBBean|NULL
+ */
+ public function findOne( $type, $sql = NULL, $bindings = array() )
+ {
+ $sql = $this->toolbox->getWriter()->glueLimitOne( $sql );
+
+ $items = $this->find( $type, $sql, $bindings );
+
+ if ( empty($items) ) {
+ return NULL;
+ }
+
+ return reset( $items );
+ }
+
+ /**
+ * Like find() but returns the last bean of the result array.
+ * Opposite of Finder::findLast().
+ * If no beans are found, this method will return NULL.
+ *
+ * @see Finder::find
+ *
+ * @param string $type the type of bean you are looking for
+ * @param string|NULL $sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings values array of values to be bound to parameters in query
+ *
+ * @return OODBBean|NULL
+ */
+ public function findLast( $type, $sql = NULL, $bindings = array() )
+ {
+ $items = $this->find( $type, $sql, $bindings );
+
+ if ( empty($items) ) {
+ return NULL;
+ }
+
+ return end( $items );
+ }
+
+ /**
+ * Tries to find beans of a certain type,
+ * if no beans are found, it dispenses a bean of that type.
+ * Note that this function always returns an array.
+ *
+ * @see Finder::find
+ *
+ * @param string $type the type of bean you are looking for
+ * @param string|NULL $sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings values array of values to be bound to parameters in query
+ *
+ * @return array
+ */
+ public function findOrDispense( $type, $sql = NULL, $bindings = array() )
+ {
+ $foundBeans = $this->find( $type, $sql, $bindings );
+
+ if ( empty( $foundBeans ) ) {
+ return array( $this->redbean->dispense( $type ) );
+ } else {
+ return $foundBeans;
+ }
+ }
+
+ /**
+ * Finds a BeanCollection using the repository.
+ * A bean collection can be used to retrieve one bean at a time using
+ * cursors - this is useful for processing large datasets. A bean collection
+ * will not load all beans into memory all at once, just one at a time.
+ *
+ * @param string $type the type of bean you are looking for
+ * @param string $sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings values array of values to be bound to parameters in query
+ *
+ * @return BeanCollection
+ */
+ public function findCollection( $type, $sql, $bindings = array() )
+ {
+ return $this->redbean->findCollection( $type, $sql, $bindings );
+ }
+
+ /**
+ * Finds or creates a bean.
+ * Tries to find a bean with certain properties specified in the second
+ * parameter ($like). If the bean is found, it will be returned.
+ * If multiple beans are found, only the first will be returned.
+ * If no beans match the criteria, a new bean will be dispensed,
+ * the criteria will be imported as properties and this new bean
+ * will be stored and returned.
+ *
+ * Format of criteria set: property => value
+ * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
+ *
+ * @param string $type type of bean to search for
+ * @param array $like criteria set describing bean to search for
+ * @param boolean $hasBeenCreated set to TRUE if bean has been created
+ *
+ * @return OODBBean
+ */
+ public function findOrCreate( $type, $like = array(), $sql = '', &$hasBeenCreated = false )
+ {
+ $sql = $this->toolbox->getWriter()->glueLimitOne( $sql );
+ $beans = $this->findLike( $type, $like, $sql );
+ if ( count( $beans ) ) {
+ $bean = reset( $beans );
+ $hasBeenCreated = false;
+ return $bean;
+ }
+
+ $bean = $this->redbean->dispense( $type );
+ $bean->import( $like );
+ $this->redbean->store( $bean );
+ $hasBeenCreated = true;
+ return $bean;
+ }
+
+ /**
+ * Finds beans by its type and a certain criteria set.
+ *
+ * Format of criteria set: property => value
+ * The criteria set also supports OR-conditions: property => array( value1, orValue2 )
+ *
+ * If the additional SQL is a condition, this condition will be glued to the rest
+ * of the query using an AND operator. Note that this is as far as this method
+ * can go, there is no way to glue additional SQL using an OR-condition.
+ * This method provides access to an underlying mechanism in the RedBeanPHP architecture
+ * to find beans using criteria sets. However, please do not use this method
+ * for complex queries, use plain SQL instead ( the regular find method ) as it is
+ * more suitable for the job. This method is
+ * meant for basic search-by-example operations.
+ *
+ * @param string $type type of bean to search for
+ * @param array $conditions criteria set describing the bean to search for
+ * @param string $sql additional SQL (for sorting)
+ * @param array $bindings bindings
+ *
+ * @return array
+ */
+ public function findLike( $type, $conditions = array(), $sql = '', $bindings = array() )
+ {
+ return $this->redbean->find( $type, $conditions, $sql, $bindings );
+ }
+
+ /**
+ * Returns a hashmap with bean arrays keyed by type using an SQL
+ * query as its resource. Given an SQL query like 'SELECT movie.*, review.* FROM movie... JOIN review'
+ * this method will return movie and review beans.
+ *
+ * Example:
+ *
+ *
+ * $stuff = $finder->findMulti('movie,review', '
+ * SELECT movie.*, review.* FROM movie
+ * LEFT JOIN review ON review.movie_id = movie.id');
+ *
+ *
+ * After this operation, $stuff will contain an entry 'movie' containing all
+ * movies and an entry named 'review' containing all reviews (all beans).
+ * You can also pass bindings.
+ *
+ * If you want to re-map your beans, so you can use $movie->ownReviewList without
+ * having RedBeanPHP executing an SQL query you can use the fourth parameter to
+ * define a selection of remapping closures.
+ *
+ * The remapping argument (optional) should contain an array of arrays.
+ * Each array in the remapping array should contain the following entries:
+ *
+ *
+ * array(
+ * 'a' => TYPE A
+ * 'b' => TYPE B OR BEANS
+ * 'matcher' =>
+ * MATCHING FUNCTION ACCEPTING A, B and ALL BEANS
+ * OR ARRAY
+ * WITH FIELD on B that should match with FIELD on A
+ * AND FIELD on A that should match with FIELD on B
+ * OR TRUE
+ * TO JUST PERFORM THE DO-FUNCTION ON EVERY A-BEAN
+ *
+ * 'do' => OPERATION FUNCTION ACCEPTING A, B, ALL BEANS, ALL REMAPPINGS
+ * (ONLY IF MATCHER IS ALSO A FUNCTION)
+ * )
+ *
+ *
+ * Using this mechanism you can build your own 'preloader' with tiny function
+ * snippets (and those can be re-used and shared online of course).
+ *
+ * Example:
+ *
+ *
+ * array(
+ * 'a' => 'movie' //define A as movie
+ * 'b' => 'review' //define B as review
+ * matcher' => function( $a, $b ) {
+ * return ( $b->movie_id == $a->id ); //Perform action if review.movie_id equals movie.id
+ * }
+ * 'do' => function( $a, $b ) {
+ * $a->noLoad()->ownReviewList[] = $b; //Add the review to the movie
+ * $a->clearHistory(); //optional, act 'as if these beans have been loaded through ownReviewList'.
+ * }
+ * )
+ *
+ *
+ * The Query Template parameter is optional as well but can be used to
+ * set a different SQL template (sprintf-style) for processing the original query.
+ *
+ * @note the SQL query provided IS NOT THE ONE used internally by this function,
+ * this function will pre-process the query to get all the data required to find the beans.
+ *
+ * @note if you use the 'book.*' notation make SURE you're
+ * selector starts with a SPACE. ' book.*' NOT ',book.*'. This is because
+ * it's actually an SQL-like template SLOT, not real SQL.
+ *
+ * @note instead of an SQL query you can pass a result array as well.
+ *
+ * @note the performance of this function is poor, if you deal with large number of records
+ * please use plain SQL instead. This function has been added as a bridge between plain SQL
+ * and bean oriented approaches but it is really on the edge of both worlds. You can safely
+ * use this function to load additional records as beans in paginated context, let's say
+ * 50-250 records. Anything above that will gradually perform worse. RedBeanPHP was never
+ * intended to replace SQL but offer tooling to integrate SQL with object oriented
+ * designs. If you have come to this function, you have reached the final border between
+ * SQL-oriented design and OOP. Anything after this will be just as good as custom mapping
+ * or plain old database querying. I recommend the latter.
+ *
+ * @param string|array $types a list of types (either array or comma separated string)
+ * @param string|array $sql optional, an SQL query or an array of prefetched records
+ * @param array $bindings optional, bindings for SQL query
+ * @param array $remappings optional, an array of remapping arrays
+ * @param string $queryTemplate optional, query template
+ *
+ * @return array
+ */
+ public function findMulti( $types, $sql = NULL, $bindings = array(), $remappings = array(), $queryTemplate = ' %s.%s AS %s__%s' )
+ {
+ if ( !is_array( $types ) ) $types = array_map( 'trim', explode( ',', $types ) );
+ if ( is_null( $sql ) ) {
+ $beans = array();
+ foreach( $types as $type ) $beans[$type] = $this->redbean->find( $type );
+ } else {
+ if ( !is_array( $sql ) ) {
+ $writer = $this->toolbox->getWriter();
+ $adapter = $this->toolbox->getDatabaseAdapter();
+
+ //Repair the query, replace book.* with book.id AS book_id etc..
+ foreach( $types as $type ) {
+ $regex = "#( (`?{$type}`?)\.\*)#";
+ if ( preg_match( $regex, $sql, $matches ) ) {
+ $pattern = $matches[1];
+ $table = $matches[2];
+ $newSelectorArray = array();
+ $columns = $writer->getColumns( $type );
+ foreach( $columns as $column => $definition ) {
+ $newSelectorArray[] = sprintf( $queryTemplate, $table, $column, $type, $column );
+ }
+ $newSelector = implode( ',', $newSelectorArray );
+ $sql = str_replace( $pattern, $newSelector, $sql );
+ }
+ }
+
+ $rows = $adapter->get( $sql, $bindings );
+ } else {
+ $rows = $sql;
+ }
+
+ //Gather the bean data from the query results using the prefix
+ $wannaBeans = array();
+ foreach( $types as $type ) {
+ $wannaBeans[$type] = array();
+ $prefix = "{$type}__";
+ foreach( $rows as $rowkey=>$row ) {
+ $wannaBean = array();
+ foreach( $row as $cell => $value ) {
+ if ( strpos( $cell, $prefix ) === 0 ) {
+ $property = substr( $cell, strlen( $prefix ) );
+ unset( $rows[$rowkey][$cell] );
+ $wannaBean[$property] = $value;
+ }
+ }
+ if ( !isset( $wannaBean['id'] ) ) continue;
+ if ( is_null( $wannaBean['id'] ) ) continue;
+ $wannaBeans[$type][$wannaBean['id']] = $wannaBean;
+ }
+ }
+
+ //Turn the rows into beans
+ $beans = array();
+ foreach( $wannaBeans as $type => $wannabees ) {
+ $beans[$type] = $this->redbean->convertToBeans( $type, $wannabees );
+ }
+ }
+
+ //Apply additional re-mappings
+ foreach($remappings as $remapping) {
+ $a = $remapping['a'];
+ $b = $remapping['b'];
+ if (is_array($b)) {
+ $firstBean = reset($b);
+ $type = $firstBean->getMeta('type');
+ $beans[$type] = $b;
+ $b = $type;
+ }
+ $matcher = $remapping['matcher'];
+ if (is_callable($matcher) || $matcher === TRUE) {
+ $do = $remapping['do'];
+ foreach( $beans[$a] as $bean ) {
+ if ( $matcher === TRUE ) {
+ $do( $bean, $beans[$b], $beans, $remapping );
+ continue;
+ }
+ foreach( $beans[$b] as $putBean ) {
+ if ( $matcher( $bean, $putBean, $beans ) ) $do( $bean, $putBean, $beans, $remapping );
+ }
+ }
+ } else {
+ list($field1, $field2) = $matcher;
+ foreach( $beans[$b] as $key => $bean ) {
+ $beans[$b][$key]->{$field1} = (isset($beans[$a][$bean->{$field2}]) ? $beans[$a][$bean->{$field2}] : NULL);
+ }
+ }
+ }
+ return $beans;
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\RedException as RedException;
+use RedBeanPHP\RedException\SQL as SQLException;
+
+/**
+ * Association Manager.
+ * The association manager can be used to create and manage
+ * many-to-many relations (for example sharedLists). In a many-to-many relation,
+ * one bean can be associated with many other beans, while each of those beans
+ * can also be related to multiple beans.
+ *
+ * @file RedBeanPHP/AssociationManager.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class AssociationManager extends Observable
+{
+ /**
+ * @var OODB
+ */
+ protected $oodb;
+
+ /**
+ * @var DBAdapter
+ */
+ protected $adapter;
+
+ /**
+ * @var QueryWriter
+ */
+ protected $writer;
+
+ /**
+ * @var ToolBox
+ */
+ public $toolbox;
+
+ /**
+ * Exception handler.
+ * Fluid and Frozen mode have different ways of handling
+ * exceptions. Fluid mode (using the fluid repository) ignores
+ * exceptions caused by the following:
+ *
+ * - missing tables
+ * - missing column
+ *
+ * In these situations, the repository will behave as if
+ * no beans could be found. This is because in fluid mode
+ * it might happen to query a table or column that has not been
+ * created yet. In frozen mode, this is not supposed to happen
+ * and the corresponding exceptions will be thrown.
+ *
+ * @param \Exception $exception exception
+ *
+ * @return void
+ */
+ private function handleException( \Exception $exception )
+ {
+ if ( $this->oodb->isFrozen() || !$this->writer->sqlStateIn( $exception->getSQLState(),
+ array(
+ QueryWriter::C_SQLSTATE_NO_SUCH_TABLE,
+ QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ),
+ $exception->getDriverDetails()
+ )
+ ) {
+ throw $exception;
+ }
+ }
+
+ /**
+ * Internal method.
+ * Returns the many-to-many related rows of table $type for bean $bean using additional SQL in $sql and
+ * $bindings bindings. If $getLinks is TRUE, link rows are returned instead.
+ *
+ * @param OODBBean $bean reference bean instance
+ * @param string $type target bean type
+ * @param string $sql additional SQL snippet
+ * @param array $bindings bindings for query
+ *
+ * @return array
+ */
+ private function relatedRows( $bean, $type, $sql = '', $bindings = array() )
+ {
+ $ids = array( $bean->id );
+ $sourceType = $bean->getMeta( 'type' );
+ try {
+ return $this->writer->queryRecordRelated( $sourceType, $type, $ids, $sql, $bindings );
+ } catch ( SQLException $exception ) {
+ $this->handleException( $exception );
+ return array();
+ }
+ }
+
+ /**
+ * Associates a pair of beans. This method associates two beans, no matter
+ * what types. Accepts a base bean that contains data for the linking record.
+ * This method is used by associate. This method also accepts a base bean to be used
+ * as the template for the link record in the database.
+ *
+ * @param OODBBean $bean1 first bean
+ * @param OODBBean $bean2 second bean
+ * @param OODBBean $bean base bean (association record)
+ *
+ * @return mixed
+ */
+ protected function associateBeans( OODBBean $bean1, OODBBean $bean2, OODBBean $bean )
+ {
+ $type = $bean->getMeta( 'type' );
+ $property1 = $bean1->getMeta( 'type' ) . '_id';
+ $property2 = $bean2->getMeta( 'type' ) . '_id';
+
+ if ( $property1 == $property2 ) {
+ $property2 = $bean2->getMeta( 'type' ) . '2_id';
+ }
+
+ $this->oodb->store( $bean1 );
+ $this->oodb->store( $bean2 );
+
+ $bean->setMeta( "cast.$property1", "id" );
+ $bean->setMeta( "cast.$property2", "id" );
+ $bean->setMeta( 'sys.buildcommand.unique', array( $property1, $property2 ) );
+
+ $bean->$property1 = $bean1->id;
+ $bean->$property2 = $bean2->id;
+
+ $results = array();
+
+ try {
+ $id = $this->oodb->store( $bean );
+ $results[] = $id;
+ } catch ( SQLException $exception ) {
+ if ( !$this->writer->sqlStateIn( $exception->getSQLState(),
+ array( QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION ),
+ $exception->getDriverDetails() )
+ ) {
+ throw $exception;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Constructor, creates a new instance of the Association Manager.
+ * The association manager can be used to create and manage
+ * many-to-many relations (for example sharedLists). In a many-to-many relation,
+ * one bean can be associated with many other beans, while each of those beans
+ * can also be related to multiple beans. To create an Association Manager
+ * instance you'll need to pass a ToolBox object.
+ *
+ * @param ToolBox $tools toolbox supplying core RedBeanPHP objects
+ */
+ public function __construct( ToolBox $tools )
+ {
+ $this->oodb = $tools->getRedBean();
+ $this->adapter = $tools->getDatabaseAdapter();
+ $this->writer = $tools->getWriter();
+ $this->toolbox = $tools;
+ }
+
+ /**
+ * Creates a table name based on a types array.
+ * Manages the get the correct name for the linking table for the
+ * types provided.
+ *
+ * @param array $types 2 types as strings
+ *
+ * @return string
+ */
+ public function getTable( $types )
+ {
+ return $this->writer->getAssocTable( $types );
+ }
+
+ /**
+ * Associates two beans in a many-to-many relation.
+ * This method will associate two beans and store the connection between the
+ * two in a link table. Instead of two single beans this method also accepts
+ * two sets of beans. Returns the ID or the IDs of the linking beans.
+ *
+ * @param OODBBean|array $beans1 one or more beans to form the association
+ * @param OODBBean|array $beans2 one or more beans to form the association
+ *
+ * @return array
+ */
+ public function associate( $beans1, $beans2 )
+ {
+ if ( !is_array( $beans1 ) ) {
+ $beans1 = array( $beans1 );
+ }
+
+ if ( !is_array( $beans2 ) ) {
+ $beans2 = array( $beans2 );
+ }
+
+ $results = array();
+ foreach ( $beans1 as $bean1 ) {
+ foreach ( $beans2 as $bean2 ) {
+ $table = $this->getTable( array( $bean1->getMeta( 'type' ), $bean2->getMeta( 'type' ) ) );
+ $bean = $this->oodb->dispense( $table );
+ $results[] = $this->associateBeans( $bean1, $bean2, $bean );
+ }
+ }
+
+ return ( count( $results ) > 1 ) ? $results : reset( $results );
+ }
+
+ /**
+ * Counts the number of related beans in an N-M relation.
+ * This method returns the number of beans of type $type associated
+ * with reference bean(s) $bean. The query can be tuned using an
+ * SQL snippet for additional filtering.
+ *
+ * @param OODBBean|array $bean a bean object or an array of beans
+ * @param string $type type of bean you're interested in
+ * @param string|NULL $sql SQL snippet (optional)
+ * @param array $bindings bindings for your SQL string
+ *
+ * @return integer
+ */
+ public function relatedCount( $bean, $type, $sql = NULL, $bindings = array() )
+ {
+ if ( !( $bean instanceof OODBBean ) ) {
+ throw new RedException(
+ 'Expected array or OODBBean but got:' . gettype( $bean )
+ );
+ }
+
+ if ( !$bean->id ) {
+ return 0;
+ }
+
+ $beanType = $bean->getMeta( 'type' );
+
+ try {
+ return $this->writer->queryRecordCountRelated( $beanType, $type, $bean->id, $sql, $bindings );
+ } catch ( SQLException $exception ) {
+ $this->handleException( $exception );
+
+ return 0;
+ }
+ }
+
+ /**
+ * Breaks the association between two beans. This method unassociates two beans. If the
+ * method succeeds the beans will no longer form an association. In the database
+ * this means that the association record will be removed. This method uses the
+ * OODB trash() method to remove the association links, thus giving FUSE models the
+ * opportunity to hook-in additional business logic. If the $fast parameter is
+ * set to boolean TRUE this method will remove the beans without their consent,
+ * bypassing FUSE. This can be used to improve performance.
+ *
+ * @param OODBBean $beans1 first bean in target association
+ * @param OODBBean $beans2 second bean in target association
+ * @param boolean $fast if TRUE, removes the entries by query without FUSE
+ *
+ * @return void
+ */
+ public function unassociate( $beans1, $beans2, $fast = NULL )
+ {
+ $beans1 = ( !is_array( $beans1 ) ) ? array( $beans1 ) : $beans1;
+ $beans2 = ( !is_array( $beans2 ) ) ? array( $beans2 ) : $beans2;
+
+ foreach ( $beans1 as $bean1 ) {
+ foreach ( $beans2 as $bean2 ) {
+ try {
+ $this->oodb->store( $bean1 );
+ $this->oodb->store( $bean2 );
+
+ $type1 = $bean1->getMeta( 'type' );
+ $type2 = $bean2->getMeta( 'type' );
+
+ $row = $this->writer->queryRecordLink( $type1, $type2, $bean1->id, $bean2->id );
+
+ if ( !$row ) return;
+
+ $linkType = $this->getTable( array( $type1, $type2 ) );
+
+ if ( $fast ) {
+ $this->writer->deleteRecord( $linkType, array( 'id' => $row['id'] ) );
+
+ return;
+ }
+
+ $beans = $this->oodb->convertToBeans( $linkType, array( $row ) );
+
+ if ( count( $beans ) > 0 ) {
+ $bean = reset( $beans );
+ $this->oodb->trash( $bean );
+ }
+ } catch ( SQLException $exception ) {
+ $this->handleException( $exception );
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes all relations for a bean. This method breaks every connection between
+ * a certain bean $bean and every other bean of type $type. Warning: this method
+ * is really fast because it uses a direct SQL query however it does not inform the
+ * models about this. If you want to notify FUSE models about deletion use a foreach-loop
+ * with unassociate() instead. (that might be slower though)
+ *
+ * @param OODBBean $bean reference bean
+ * @param string $type type of beans that need to be unassociated
+ *
+ * @return void
+ */
+ public function clearRelations( OODBBean $bean, $type )
+ {
+ $this->oodb->store( $bean );
+ try {
+ $this->writer->deleteRelations( $bean->getMeta( 'type' ), $type, $bean->id );
+ } catch ( SQLException $exception ) {
+ $this->handleException( $exception );
+ }
+ }
+
+ /**
+ * Returns all the beans associated with $bean.
+ * This method will return an array containing all the beans that have
+ * been associated once with the associate() function and are still
+ * associated with the bean specified. The type parameter indicates the
+ * type of beans you are looking for. You can also pass some extra SQL and
+ * values for that SQL to filter your results after fetching the
+ * related beans.
+ *
+ * Don't try to make use of subqueries, a subquery using IN() seems to
+ * be slower than two queries!
+ *
+ * Since 3.2, you can now also pass an array of beans instead just one
+ * bean as the first parameter.
+ *
+ * @param OODBBean $bean the bean you have
+ * @param string $type the type of beans you want
+ * @param string $sql SQL snippet for extra filtering
+ * @param array $bindings values to be inserted in SQL slots
+ *
+ * @return array
+ */
+ public function related( $bean, $type, $sql = '', $bindings = array() )
+ {
+ $sql = $this->writer->glueSQLCondition( $sql );
+ $rows = $this->relatedRows( $bean, $type, $sql, $bindings );
+ $links = array();
+
+ foreach ( $rows as $key => $row ) {
+ if ( !isset( $links[$row['id']] ) ) $links[$row['id']] = array();
+ $links[$row['id']][] = $row['linked_by'];
+ unset( $rows[$key]['linked_by'] );
+ }
+
+ $beans = $this->oodb->convertToBeans( $type, $rows );
+ foreach ( $beans as $bean ) $bean->setMeta( 'sys.belongs-to', $links[$bean->id] );
+
+ return $beans;
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\ToolBox as ToolBox;
+use RedBeanPHP\OODBBean as OODBBean;
+
+/**
+ * Bean Helper Interface.
+ *
+ * Interface for Bean Helper.
+ * A little bolt that glues the whole machinery together.
+ * The Bean Helper is passed to the OODB RedBeanPHP Object to
+ * facilitate the creation of beans and providing them with
+ * a toolbox. The Helper also facilitates the FUSE feature,
+ * determining how beans relate to their models. By overriding
+ * the getModelForBean method you can tune the FUSEing to
+ * fit your business application needs.
+ *
+ * @file RedBeanPHP/IBeanHelper.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+interface BeanHelper
+{
+ /**
+ * Returns a toolbox to empower the bean.
+ * This allows beans to perform OODB operations by themselves,
+ * as such the bean is a proxy for OODB. This allows beans to implement
+ * their magic getters and setters and return lists.
+ *
+ * @return ToolBox
+ */
+ public function getToolbox();
+
+ /**
+ * Does approximately the same as getToolbox but also extracts the
+ * toolbox for you.
+ * This method returns a list with all toolbox items in Toolbox Constructor order:
+ * OODB, adapter, writer and finally the toolbox itself!.
+ *
+ * @return array
+ */
+ public function getExtractedToolbox();
+
+ /**
+ * Given a certain bean this method will
+ * return the corresponding model.
+ *
+ * @param OODBBean $bean bean to obtain the corresponding model of
+ *
+ * @return SimpleModel|SimpleModelInterface|NULL
+ */
+ public function getModelForBean( OODBBean $bean );
+}
+}
+
+namespace RedBeanPHP\BeanHelper {
+
+use RedBeanPHP\BeanHelper as BeanHelper;
+use RedBeanPHP\Facade as Facade;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\SimpleModel;
+use RedBeanPHP\SimpleModelHelper as SimpleModelHelper;
+use RedBeanPHP\SimpleModelInterface;
+
+/**
+ * Bean Helper.
+ *
+ * The Bean helper helps beans to access access the toolbox and
+ * FUSE models. This Bean Helper makes use of the facade to obtain a
+ * reference to the toolbox.
+ *
+ * @file RedBeanPHP/BeanHelperFacade.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class SimpleFacadeBeanHelper implements BeanHelper
+{
+ /**
+ * Factory function to create instance of Simple Model, if any.
+ *
+ * @var callable|NULL
+ */
+ private static $factory = null;
+
+ /**
+ * Factory method using a customizable factory function to create
+ * the instance of the Simple Model.
+ *
+ * @param string $modelClassName name of the class
+ *
+ * @return SimpleModel|SimpleModelInterface
+ */
+ public static function factory( $modelClassName )
+ {
+ $factory = self::$factory;
+ return ( $factory ) ? $factory( $modelClassName ) : new $modelClassName();
+ }
+
+ /**
+ * Sets the factory function to create the model when using FUSE
+ * to connect a bean to a model.
+ *
+ * @param callable|NULL $factory factory function
+ *
+ * @return void
+ */
+ public static function setFactoryFunction( $factory )
+ {
+ self::$factory = $factory;
+ }
+
+ /**
+ * @see BeanHelper::getToolbox
+ */
+ public function getToolbox()
+ {
+ return Facade::getToolBox();
+ }
+
+ /**
+ * @see BeanHelper::getModelForBean
+ */
+ public function getModelForBean( OODBBean $bean )
+ {
+ $model = $bean->getMeta( 'type' );
+ $prefix = defined( 'REDBEAN_MODEL_PREFIX' ) ? REDBEAN_MODEL_PREFIX : '\\Model_';
+
+ return $this->resolveModel($prefix, $model, $bean);
+ }
+
+ /**
+ * Resolves the model associated with the bean using the model name (type),
+ * the prefix and the bean.
+ *
+ * @note
+ * If REDBEAN_CLASS_AUTOLOAD is defined this will be passed to class_exist as
+ * autoloading flag.
+ *
+ * @param string $prefix Prefix to use for resolution
+ * @param string $model Type name
+ * @param OODBBean $bean Bean to resolve model for
+ *
+ * @return SimpleModel|SimpleModelInterface|NULL
+ */
+ protected function resolveModel($prefix, $model, $bean) {
+
+ /* Determine autoloading preference */
+ $autoloadFlag = ( defined( 'REDBEAN_CLASS_AUTOLOAD' ) ? REDBEAN_CLASS_AUTOLOAD : TRUE );
+
+ if ( strpos( $model, '_' ) !== FALSE ) {
+ $modelParts = explode( '_', $model );
+ $modelName = '';
+ foreach( $modelParts as $part ) {
+ $modelName .= ucfirst( $part );
+ }
+ $modelName = $prefix . $modelName;
+ if ( !class_exists( $modelName ) ) {
+ $modelName = $prefix . ucfirst( $model );
+ if ( !class_exists( $modelName, $autoloadFlag ) ) {
+ return NULL;
+ }
+ }
+ } else {
+ $modelName = $prefix . ucfirst( $model );
+ if ( !class_exists( $modelName, $autoloadFlag ) ) {
+ return NULL;
+ }
+ }
+ $obj = self::factory( $modelName );
+ $obj->loadBean( $bean );
+ return $obj;
+ }
+
+ /**
+ * @see BeanHelper::getExtractedToolbox
+ */
+ public function getExtractedToolbox()
+ {
+ return Facade::getExtractedToolbox();
+ }
+}
+}
+
+namespace RedBeanPHP\BeanHelper {
+
+use RedBeanPHP\BeanHelper as BeanHelper;
+use RedBeanPHP\Facade as Facade;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\SimpleModelHelper as SimpleModelHelper;
+use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
+
+/**
+ * Dynamic Bean Helper.
+ *
+ * The dynamic bean helper allows you to use differently namespaced
+ * classes for models per database connection.
+ *
+ * @file RedBeanPHP/BeanHelper/DynamicBeanHelper.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * (c) copyright G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class DynamicBeanHelper extends SimpleFacadeBeanHelper implements BeanHelper
+{
+ /**
+ * Model prefix to be used for the current database connection.
+ *
+ * @var string
+ */
+ private $modelPrefix;
+
+ /**
+ * Constructor
+ *
+ * Usage:
+ *
+ *
+ * R::addDatabase( ..., new DynamicBeanHelper('Prefix1_') );
+ *
+ *
+ * @param string $modelPrefix prefix
+ */
+ public function __construct( $modelPrefix ) {
+ $this->modelPrefix = $modelPrefix;
+ }
+
+ /**
+ * @see BeanHelper::getModelForBean
+ */
+ public function getModelForBean( OODBBean $bean )
+ {
+ return $this->resolveModel( $this->modelPrefix, $bean->getMeta( 'type' ), $bean );
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\OODBBean as OODBBean;
+
+/**
+ * SimpleModel
+ * Base Model For All RedBeanPHP Models using FUSE.
+ *
+ * RedBeanPHP FUSE is a mechanism to connect beans to posthoc
+ * models. Models are connected to beans by naming conventions.
+ * Actions on beans will result in actions on models.
+ *
+ * @file RedBeanPHP/SimpleModel.php
+ * @author Gabor de Mooij and the RedBeanPHP Team
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class SimpleModel implements SimpleModelInterface
+{
+ /**
+ * @var OODBBean
+ */
+ protected $bean;
+
+ /**
+ * Used by FUSE: the ModelHelper class to connect a bean to a model.
+ * This method loads a bean in the model.
+ *
+ * @param OODBBean $bean bean to load
+ *
+ * @return void
+ */
+ public function loadBean( OODBBean $bean )
+ {
+ $this->bean = $bean;
+ }
+
+ /**
+ * Magic Getter to make the bean properties available from
+ * the $this-scope.
+ *
+ * @note this method returns a value, not a reference!
+ * To obtain a reference unbox the bean first!
+ *
+ * @param string $prop property to get
+ *
+ * @return mixed
+ */
+ public function __get( $prop )
+ {
+ return $this->bean->$prop;
+ }
+
+ /**
+ * Magic Setter.
+ * Sets the value directly as a bean property.
+ *
+ * @param string $prop property to set value of
+ * @param mixed $value value to set
+ *
+ * @return void
+ */
+ public function __set( $prop, $value )
+ {
+ $this->bean->$prop = $value;
+ }
+
+ /**
+ * Isset implementation.
+ * Implements the isset function for array-like access.
+ *
+ * @param string $key key to check
+ *
+ * @return boolean
+ */
+ public function __isset( $key )
+ {
+ return isset( $this->bean->$key );
+ }
+
+ /**
+ * Box the bean using the current model.
+ * This method wraps the current bean in this model.
+ * This method can be reached using FUSE through a simple
+ * OODBBean. The method returns a RedBeanPHP Simple Model.
+ * This is useful if you would like to rely on PHP type hinting.
+ * You can box your beans before passing them to functions or methods
+ * with typed parameters.
+ *
+ * Note about beans vs models:
+ * Use unbox to obtain the bean powering the model. If you want to use bean functionality,
+ * you should -always- unbox first. While some functionality (like magic get/set) is
+ * available in the model, this is just read-only. To use a model as a typical RedBean
+ * OODBBean you should always unbox the model to a bean. Models are meant to
+ * expose only domain logic added by the developer (business logic, no ORM logic).
+ *
+ * @return SimpleModel|SimpleModelInterface
+ */
+ public function box()
+ {
+ return $this;
+ }
+
+ /**
+ * Unbox the bean from the model.
+ * This method returns the bean inside the model.
+ *
+ * Note about beans vs models:
+ * Use unbox to obtain the bean powering the model. If you want to use bean functionality,
+ * you should -always- unbox first. While some functionality (like magic get/set) is
+ * available in the model, this is just read-only. To use a model as a typical RedBean
+ * OODBBean you should always unbox the model to a bean. Models are meant to
+ * expose only domain logic added by the developer (business logic, no ORM logic).
+ *
+ * @return OODBBean
+ */
+ public function unbox()
+ {
+ return $this->bean;
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+
+/**
+ * SimpleModelInterface
+ * Interface For All RedBeanPHP Models using FUSE.
+ *
+ * RedBeanPHP FUSE is a mechanism to connect beans to posthoc
+ * models. Models are connected to beans by naming conventions.
+ * Actions on beans will result in actions on models.
+ *
+ * @file RedBeanPHP/SimpleModelInterface.php
+ * @author Gabor de Mooij and the RedBeanPHP Team
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+interface SimpleModelInterface
+{
+ /**
+ * Used by FUSE: the ModelHelper class to connect a bean to a model.
+ * This method loads a bean in the model.
+ *
+ * @param OODBBean $bean bean to load
+ *
+ * @return void
+ */
+ public function loadBean(OODBBean $bean);
+
+ /**
+ * Magic Getter to make the bean properties available from
+ * the $this-scope.
+ *
+ * @note this method returns a value, not a reference!
+ * To obtain a reference unbox the bean first!
+ *
+ * @param string $prop property to get
+ *
+ * @return mixed
+ */
+ public function __get($prop);
+
+ /**
+ * Magic Setter.
+ * Sets the value directly as a bean property.
+ *
+ * @param string $prop property to set value of
+ * @param mixed $value value to set
+ *
+ * @return void
+ */
+ public function __set($prop, $value);
+
+ /**
+ * Isset implementation.
+ * Implements the isset function for array-like access.
+ *
+ * @param string $key key to check
+ *
+ * @return boolean
+ */
+ public function __isset($key);
+
+ /**
+ * Box the bean using the current model.
+ * This method wraps the current bean in this model.
+ * This method can be reached using FUSE through a simple
+ * OODBBean. The method returns a RedBeanPHP Simple Model.
+ * This is useful if you would like to rely on PHP type hinting.
+ * You can box your beans before passing them to functions or methods
+ * with typed parameters.
+ *
+ * Note about beans vs models:
+ * Use unbox to obtain the bean powering the model. If you want to use bean functionality,
+ * you should -always- unbox first. While some functionality (like magic get/set) is
+ * available in the model, this is just read-only. To use a model as a typical RedBean
+ * OODBBean you should always unbox the model to a bean. Models are meant to
+ * expose only domain logic added by the developer (business logic, no ORM logic).
+ *
+ * @return SimpleModel|SimpleModelInterface
+ */
+ public function box();
+
+ /**
+ * Unbox the bean from the model.
+ * This method returns the bean inside the model.
+ *
+ * Note about beans vs models:
+ * Use unbox to obtain the bean powering the model. If you want to use bean functionality,
+ * you should -always- unbox first. While some functionality (like magic get/set) is
+ * available in the model, this is just read-only. To use a model as a typical RedBean
+ * OODBBean you should always unbox the model to a bean. Models are meant to
+ * expose only domain logic added by the developer (business logic, no ORM logic).
+ *
+ * @return OODBBean
+ */
+ public function unbox();
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\Observer as Observer;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\Observable as Observable;
+
+/**
+ * RedBean Model Helper.
+ *
+ * Connects beans to models.
+ * This is the core of so-called FUSE.
+ *
+ * @file RedBeanPHP/ModelHelper.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class SimpleModelHelper implements Observer
+{
+ /**
+ * Gets notified by an observable.
+ * This method decouples the FUSE system from the actual beans.
+ * If a FUSE event happens 'update', this method will attempt to
+ * invoke the corresponding method on the bean.
+ *
+ * @param string $eventName i.e. 'delete', 'after_delete'
+ * @param OODBBean $bean affected bean
+ *
+ * @return void
+ */
+ public function onEvent( $eventName, $bean )
+ {
+ $bean->$eventName();
+ }
+
+ /**
+ * Attaches the FUSE event listeners. Now the Model Helper will listen for
+ * CRUD events. If a CRUD event occurs it will send a signal to the model
+ * that belongs to the CRUD bean and this model will take over control from
+ * there. This method will attach the following event listeners to the observable:
+ *
+ * - 'update' (gets called by R::store, before the records gets inserted / updated)
+ * - 'after_update' (gets called by R::store, after the records have been inserted / updated)
+ * - 'open' (gets called by R::load, after the record has been retrieved)
+ * - 'delete' (gets called by R::trash, before deletion of record)
+ * - 'after_delete' (gets called by R::trash, after deletion)
+ * - 'dispense' (gets called by R::dispense)
+ *
+ * For every event type, this method will register this helper as a listener.
+ * The observable will notify the listener (this object) with the event ID and the
+ * affected bean. This helper will then process the event (onEvent) by invoking
+ * the event on the bean. If a bean offers a method with the same name as the
+ * event ID, this method will be invoked.
+ *
+ * @param Observable $observable object to observe
+ *
+ * @return void
+ */
+ public function attachEventListeners( Observable $observable )
+ {
+ foreach ( array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ) as $eventID ) {
+ $observable->addEventListener( $eventID, $this );
+ }
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\ToolBox as ToolBox;
+use RedBeanPHP\AssociationManager as AssociationManager;
+use RedBeanPHP\OODBBean as OODBBean;
+
+/**
+ * RedBeanPHP Tag Manager.
+ *
+ * The tag manager offers an easy way to quickly implement basic tagging
+ * functionality.
+ *
+ * Provides methods to tag beans and perform tag-based searches in the
+ * bean database.
+ *
+ * @file RedBeanPHP/TagManager.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class TagManager
+{
+ /**
+ * @var ToolBox
+ */
+ protected $toolbox;
+
+ /**
+ * @var AssociationManager
+ */
+ protected $associationManager;
+
+ /**
+ * @var OODB
+ */
+ protected $redbean;
+
+ /**
+ * Checks if the argument is a comma separated string, in this case
+ * it will split the string into words and return an array instead.
+ * In case of an array the argument will be returned 'as is'.
+ *
+ * @param array|string|false $tagList list of tags
+ *
+ * @return array
+ */
+ private function extractTagsIfNeeded( $tagList )
+ {
+ if ( $tagList !== FALSE && !is_array( $tagList ) ) {
+ $tags = explode( ',', (string) $tagList );
+ } else {
+ $tags = $tagList;
+ }
+
+ return $tags;
+ }
+
+ /**
+ * Finds a tag bean by its title.
+ * Internal method.
+ *
+ * @param string $title title to search for
+ *
+ * @return OODBBean|NULL
+ */
+ protected function findTagByTitle( $title )
+ {
+ $beans = $this->redbean->find( 'tag', array( 'title' => array( $title ) ) );
+
+ if ( $beans ) {
+ $bean = reset( $beans );
+
+ return $bean;
+ }
+
+ return NULL;
+ }
+
+ /**
+ * Constructor.
+ * The tag manager offers an easy way to quickly implement basic tagging
+ * functionality.
+ *
+ * @param ToolBox $toolbox toolbox object
+ */
+ public function __construct( ToolBox $toolbox )
+ {
+ $this->toolbox = $toolbox;
+ $this->redbean = $toolbox->getRedBean();
+
+ $this->associationManager = $this->redbean->getAssociationManager();
+ }
+
+ /**
+ * Tests whether a bean has been associated with one or more
+ * of the listed tags. If the third parameter is TRUE this method
+ * will return TRUE only if all tags that have been specified are indeed
+ * associated with the given bean, otherwise FALSE.
+ * If the third parameter is FALSE this
+ * method will return TRUE if one of the tags matches, FALSE if none
+ * match.
+ *
+ * Tag list can be either an array with tag names or a comma separated list
+ * of tag names.
+ *
+ * Usage:
+ *
+ *
+ * R::hasTag( $blog, 'horror,movie', TRUE );
+ *
+ *
+ * The example above returns TRUE if the $blog bean has been tagged
+ * as BOTH horror and movie. If the post has only been tagged as 'movie'
+ * or 'horror' this operation will return FALSE because the third parameter
+ * has been set to TRUE.
+ *
+ * @param OODBBean $bean bean to check for tags
+ * @param array|string $tags list of tags
+ * @param boolean $all whether they must all match or just some
+ *
+ * @return boolean
+ */
+ public function hasTag( $bean, $tags, $all = FALSE )
+ {
+ $foundtags = $this->tag( $bean );
+
+ $tags = $this->extractTagsIfNeeded( $tags );
+ $same = array_intersect( $tags, $foundtags );
+
+ if ( $all ) {
+ return ( implode( ',', $same ) === implode( ',', $tags ) );
+ }
+
+ return (bool) ( count( $same ) > 0 );
+ }
+
+ /**
+ * Removes all specified tags from the bean. The tags specified in
+ * the second parameter will no longer be associated with the bean.
+ *
+ * Tag list can be either an array with tag names or a comma separated list
+ * of tag names.
+ *
+ * Usage:
+ *
+ *
+ * R::untag( $blog, 'smart,interesting' );
+ *
+ *
+ * In the example above, the $blog bean will no longer
+ * be associated with the tags 'smart' and 'interesting'.
+ *
+ * @param OODBBean $bean tagged bean
+ * @param array|string $tagList list of tags (names)
+ *
+ * @return void
+ */
+ public function untag( $bean, $tagList )
+ {
+ $tags = $this->extractTagsIfNeeded( $tagList );
+
+ foreach ( $tags as $tag ) {
+ if ( $t = $this->findTagByTitle( $tag ) ) {
+ $this->associationManager->unassociate( $bean, $t );
+ }
+ }
+ }
+
+ /**
+ * Part of RedBeanPHP Tagging API.
+ * Tags a bean or returns tags associated with a bean.
+ * If $tagList is NULL or omitted this method will return a
+ * comma separated list of tags associated with the bean provided.
+ * If $tagList is a comma separated list (string) of tags all tags will
+ * be associated with the bean.
+ * You may also pass an array instead of a string.
+ *
+ * Usage:
+ *
+ *
+ * R::tag( $meal, "TexMex,Mexican" );
+ * $tags = R::tag( $meal );
+ *
+ *
+ * The first line in the example above will tag the $meal
+ * as 'TexMex' and 'Mexican Cuisine'. The second line will
+ * retrieve all tags attached to the meal object.
+ *
+ * @param OODBBean $bean bean to tag
+ * @param mixed $tagList tags to attach to the specified bean
+ *
+ * @return string
+ */
+ public function tag( OODBBean $bean, $tagList = NULL )
+ {
+ if ( is_null( $tagList ) ) {
+
+ $tags = $bean->sharedTag;
+ $foundTags = array();
+
+ foreach ( $tags as $tag ) {
+ $foundTags[] = $tag->title;
+ }
+
+ return $foundTags;
+ }
+
+ $this->associationManager->clearRelations( $bean, 'tag' );
+ $this->addTags( $bean, $tagList );
+
+ return $tagList;
+ }
+
+ /**
+ * Part of RedBeanPHP Tagging API.
+ * Adds tags to a bean.
+ * If $tagList is a comma separated list of tags all tags will
+ * be associated with the bean.
+ * You may also pass an array instead of a string.
+ *
+ * Usage:
+ *
+ *
+ * R::addTags( $blog, ["halloween"] );
+ *
+ *
+ * The example adds the tag 'halloween' to the $blog
+ * bean.
+ *
+ * @param OODBBean $bean bean to tag
+ * @param array|string|false $tagList list of tags to add to bean
+ *
+ * @return void
+ */
+ public function addTags( OODBBean $bean, $tagList )
+ {
+ $tags = $this->extractTagsIfNeeded( $tagList );
+
+ if ( $tagList === FALSE ) {
+ return;
+ }
+
+ foreach ( $tags as $tag ) {
+ if ( !$t = $this->findTagByTitle( $tag ) ) {
+ $t = $this->redbean->dispense( 'tag' );
+ $t->title = $tag;
+
+ $this->redbean->store( $t );
+ }
+
+ $this->associationManager->associate( $bean, $t );
+ }
+ }
+
+ /**
+ * Returns all beans that have been tagged with one or more
+ * of the specified tags.
+ *
+ * Tag list can be either an array with tag names or a comma separated list
+ * of tag names.
+ *
+ * Usage:
+ *
+ *
+ * $watchList = R::tagged(
+ * 'movie',
+ * 'horror,gothic',
+ * ' ORDER BY movie.title DESC LIMIT ?',
+ * [ 10 ]
+ * );
+ *
+ *
+ * The example uses R::tagged() to find all movies that have been
+ * tagged as 'horror' or 'gothic', order them by title and limit
+ * the number of movies to be returned to 10.
+ *
+ * @param string $beanType type of bean you are looking for
+ * @param array|string $tagList list of tags to match
+ * @param string $sql additional SQL (use only for pagination)
+ * @param array $bindings bindings
+ *
+ * @return array
+ */
+ public function tagged( $beanType, $tagList, $sql = '', $bindings = array() )
+ {
+ $tags = $this->extractTagsIfNeeded( $tagList );
+ $records = $this->toolbox->getWriter()->queryTagged( $beanType, $tags, FALSE, $sql, $bindings );
+
+ return $this->redbean->convertToBeans( $beanType, $records );
+ }
+
+ /**
+ * Returns all beans that have been tagged with ALL of the tags given.
+ * This method works the same as R::tagged() except that this method only returns
+ * beans that have been tagged with all the specified labels.
+ *
+ * Tag list can be either an array with tag names or a comma separated list
+ * of tag names.
+ *
+ * Usage:
+ *
+ *
+ * $watchList = R::taggedAll(
+ * 'movie',
+ * [ 'gothic', 'short' ],
+ * ' ORDER BY movie.id DESC LIMIT ? ',
+ * [ 4 ]
+ * );
+ *
+ *
+ * The example above returns at most 4 movies (due to the LIMIT clause in the SQL
+ * Query Snippet) that have been tagged as BOTH 'short' AND 'gothic'.
+ *
+ * @param string $beanType type of bean you are looking for
+ * @param array|string $tagList list of tags to match
+ * @param string $sql additional sql snippet
+ * @param array $bindings bindings
+ *
+ * @return array
+ */
+ public function taggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
+ {
+ $tags = $this->extractTagsIfNeeded( $tagList );
+ $records = $this->toolbox->getWriter()->queryTagged( $beanType, $tags, TRUE, $sql, $bindings );
+
+ return $this->redbean->convertToBeans( $beanType, $records );
+ }
+
+ /**
+ * Like taggedAll() but only counts.
+ *
+ * @see taggedAll
+ *
+ * @param string $beanType type of bean you are looking for
+ * @param array|string $tagList list of tags to match
+ * @param string $sql additional sql snippet
+ * @param array $bindings bindings
+ *
+ * @return integer
+ */
+ public function countTaggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
+ {
+ $tags = $this->extractTagsIfNeeded( $tagList );
+ return $this->toolbox->getWriter()->queryCountTagged( $beanType, $tags, TRUE, $sql, $bindings );
+ }
+
+ /**
+ * Like tagged() but only counts.
+ *
+ * @see tagged
+ *
+ * @param string $beanType type of bean you are looking for
+ * @param array|string $tagList list of tags to match
+ * @param string $sql additional sql snippet
+ * @param array $bindings bindings
+ *
+ * @return integer
+ */
+ public function countTagged( $beanType, $tagList, $sql = '', $bindings = array() )
+ {
+ $tags = $this->extractTagsIfNeeded( $tagList );
+ return $this->toolbox->getWriter()->queryCountTagged( $beanType, $tags, FALSE, $sql, $bindings );
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\ToolBox as ToolBox;
+use RedBeanPHP\OODBBean as OODBBean;
+
+/**
+ * Label Maker.
+ * Makes so-called label beans.
+ * A label is a bean with only an id, type and name property.
+ * Labels can be used to create simple entities like categories, tags or enums.
+ * This service class provides convenience methods to deal with this kind of
+ * beans.
+ *
+ * @file RedBeanPHP/LabelMaker.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class LabelMaker
+{
+ /**
+ * @var ToolBox
+ */
+ protected $toolbox;
+
+ /**
+ * Constructor.
+ *
+ * @param ToolBox $toolbox
+ */
+ public function __construct( ToolBox $toolbox )
+ {
+ $this->toolbox = $toolbox;
+ }
+
+ /**
+ * A label is a bean with only an id, type and name property.
+ * This function will dispense beans for all entries in the array. The
+ * values of the array will be assigned to the name property of each
+ * individual bean.
+ *
+ *
+ * $people = R::dispenseLabels( 'person', [ 'Santa', 'Claus' ] );
+ *
+ *
+ * @param string $type type of beans you would like to have
+ * @param array $labels list of labels, names for each bean
+ *
+ * @return array
+ */
+ public function dispenseLabels( $type, $labels )
+ {
+ $labelBeans = array();
+ foreach ( $labels as $label ) {
+ $labelBean = $this->toolbox->getRedBean()->dispense( $type );
+ $labelBean->name = $label;
+ $labelBeans[] = $labelBean;
+ }
+
+ return $labelBeans;
+ }
+
+ /**
+ * Gathers labels from beans. This function loops through the beans,
+ * collects the value of the name property for each individual bean
+ * and stores the names in a new array. The array then gets sorted using the
+ * default sort function of PHP (sort).
+ *
+ * Usage:
+ *
+ *
+ * $o1->name = 'hamburger';
+ * $o2->name = 'pizza';
+ * implode( ',', R::gatherLabels( [ $o1, $o2 ] ) ); //hamburger,pizza
+ *
+ *
+ * Note that the return value is an array of strings, not beans.
+ *
+ * @param array $beans list of beans to loop through
+ *
+ * @return array
+ */
+ public function gatherLabels( $beans )
+ {
+ $labels = array();
+
+ foreach ( $beans as $bean ) {
+ $labels[] = $bean->name;
+ }
+
+ sort( $labels );
+
+ return $labels;
+ }
+
+ /**
+ * Fetches an ENUM from the database and creates it if necessary.
+ * An ENUM has the following format:
+ *
+ *
+ * ENUM:VALUE
+ *
+ *
+ * If you pass 'ENUM' only, this method will return an array of its
+ * values:
+ *
+ *
+ * implode( ',', R::gatherLabels( R::enum( 'flavour' ) ) ) //'BANANA,MOCCA'
+ *
+ *
+ * If you pass 'ENUM:VALUE' this method will return the specified enum bean
+ * and create it in the database if it does not exist yet:
+ *
+ *
+ * $bananaFlavour = R::enum( 'flavour:banana' );
+ * $bananaFlavour->name;
+ *
+ *
+ * So you can use this method to set an ENUM value in a bean:
+ *
+ *
+ * $shake->flavour = R::enum( 'flavour:banana' );
+ *
+ *
+ * the property flavour now contains the enum bean, a parent bean.
+ * In the database, flavour_id will point to the flavour record with name 'banana'.
+ *
+ * @param string $enum ENUM specification for label
+ *
+ * @return array|OODBBean
+ */
+ public function enum( $enum )
+ {
+ $oodb = $this->toolbox->getRedBean();
+
+ if ( strpos( $enum, ':' ) === FALSE ) {
+ $type = $enum;
+ $value = FALSE;
+ } else {
+ list( $type, $value ) = explode( ':', $enum );
+ $value = preg_replace( '/\W+/', '_', strtoupper( trim( $value ) ) );
+ }
+
+ /**
+ * We use simply find here, we could use inspect() in fluid mode etc,
+ * but this would be useless. At first sight it looks clean, you could even
+ * bake this into find(), however, find not only has to deal with the primary
+ * search type, people can also include references in the SQL part, so avoiding
+ * find failures does not matter, this is still the quickest way making use
+ * of existing functionality.
+ *
+ * @note There seems to be a bug in XDebug v2.3.2 causing suppressed
+ * exceptions like these to surface anyway, to prevent this use:
+ *
+ * "xdebug.default_enable = 0"
+ *
+ * Also see Github Issue #464
+ */
+ $values = $oodb->find( $type );
+
+ if ( $value === FALSE ) {
+ return $values;
+ }
+
+ foreach( $values as $enumItem ) {
+ if ( $enumItem->name === $value ) return $enumItem;
+ }
+
+ $newEnumItems = $this->dispenseLabels( $type, array( $value ) );
+ $newEnumItem = reset( $newEnumItems );
+
+ $oodb->store( $newEnumItem );
+
+ return $newEnumItem;
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\QueryWriter as QueryWriter;
+use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
+use RedBeanPHP\RedException\SQL as SQLException;
+use RedBeanPHP\Logger as Logger;
+use RedBeanPHP\Logger\RDefault as RDefault;
+use RedBeanPHP\Logger\RDefault\Debug as Debug;
+use RedBeanPHP\Adapter as Adapter;
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+use RedBeanPHP\RedException as RedException;
+use RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper as SimpleFacadeBeanHelper;
+use RedBeanPHP\Driver\RPDO as RPDO;
+use RedBeanPHP\Util\MultiLoader as MultiLoader;
+use RedBeanPHP\Util\Transaction as Transaction;
+use RedBeanPHP\Util\Dump as Dump;
+use RedBeanPHP\Util\DispenseHelper as DispenseHelper;
+use RedBeanPHP\Util\ArrayTool as ArrayTool;
+use RedBeanPHP\Util\QuickExport as QuickExport;
+use RedBeanPHP\Util\MatchUp as MatchUp;
+use RedBeanPHP\Util\Look as Look;
+use RedBeanPHP\Util\Diff as Diff;
+use RedBeanPHP\Util\Tree as Tree;
+use RedBeanPHP\Util\Feature;
+
+/**
+ * RedBean Facade
+ *
+ * Version Information
+ * RedBean Version @version 5.7
+ *
+ * This class hides the object landscape of
+ * RedBeanPHP behind a single letter class providing
+ * almost all functionality with simple static calls.
+ *
+ * @file RedBeanPHP/Facade.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Facade
+{
+ /**
+ * RedBeanPHP version constant.
+ */
+ const C_REDBEANPHP_VERSION = '5.7';
+
+ /**
+ * @var ToolBox
+ */
+ public static $toolbox;
+
+ /**
+ * @var OODB
+ */
+ private static $redbean;
+
+ /**
+ * @var QueryWriter
+ */
+ private static $writer;
+
+ /**
+ * @var Adapter
+ */
+ private static $adapter;
+
+ /**
+ * @var AssociationManager
+ */
+ private static $associationManager;
+
+ /**
+ * @var TagManager
+ */
+ private static $tagManager;
+
+ /**
+ * @var DuplicationManager
+ */
+ private static $duplicationManager;
+
+ /**
+ * @var LabelMaker
+ */
+ private static $labelMaker;
+
+ /**
+ * @var Finder
+ */
+ private static $finder;
+
+ /**
+ * @var Tree
+ */
+ private static $tree;
+
+ /**
+ * @var Logger
+ */
+ private static $logger;
+
+ /**
+ * @var callable[]
+ */
+ private static $plugins = array();
+
+ /**
+ * @var string
+ */
+ private static $exportCaseStyle = 'default';
+
+ /**
+ * @var bool flag allows transactions through facade in fluid mode
+ */
+ private static $allowFluidTransactions = FALSE;
+
+ /**
+ * @var bool flag allows to unfreeze if needed with store(all)
+ */
+ private static $allowHybridMode = FALSE;
+
+ /**
+ * Not in use (backward compatibility SQLHelper)
+ */
+ public static $f;
+
+ /**
+ * @var string
+ */
+ public static $currentDB = '';
+
+ /**
+ * @var ToolBox[]
+ */
+ public static $toolboxes = array();
+
+ /**
+ * Internal Query function, executes the desired query. Used by
+ * all facade query functions. This keeps things DRY.
+ *
+ * @param string $method desired query method (i.e. 'cell', 'col', 'exec' etc..)
+ * @param string $sql the sql you want to execute
+ * @param array $bindings array of values to be bound to query statement
+ *
+ * @return array|int|Cursor|NULL
+ */
+ private static function query( $method, $sql, $bindings )
+ {
+ if ( !self::$redbean->isFrozen() ) {
+ try {
+ $rs = Facade::$adapter->$method( $sql, $bindings );
+ } catch ( SQLException $exception ) {
+ if ( self::$writer->sqlStateIn( $exception->getSQLState(),
+ array(
+ QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN,
+ QueryWriter::C_SQLSTATE_NO_SUCH_TABLE )
+ ,$exception->getDriverDetails()
+ )
+ ) {
+ return ( $method === 'getCell' ) ? NULL : array();
+ } else {
+ throw $exception;
+ }
+ }
+
+ return $rs;
+ } else {
+ return Facade::$adapter->$method( $sql, $bindings );
+ }
+ }
+
+ /**
+ * Sets allow hybrid mode flag. In Hybrid mode (default off),
+ * store/storeAll take an extra argument to switch to fluid
+ * mode in case of an exception. You can use this to speed up
+ * fluid mode. This method returns the previous value of the
+ * flag.
+ *
+ * @param boolean $hybrid
+ *
+ * @return bool
+ */
+ public static function setAllowHybridMode( $hybrid )
+ {
+ $old = self::$allowHybridMode;
+ self::$allowHybridMode = $hybrid;
+ return $old;
+ }
+
+ /**
+ * Returns the RedBeanPHP version string.
+ * The RedBeanPHP version string always has the same format "X.Y"
+ * where X is the major version number and Y is the minor version number.
+ * Point releases are not mentioned in the version string.
+ *
+ * @return string
+ */
+ public static function getVersion()
+ {
+ return self::C_REDBEANPHP_VERSION;
+ }
+
+ /**
+ * Returns the version string from the database server.
+ *
+ * @return string
+ */
+ public static function getDatabaseServerVersion()
+ {
+ return self::$adapter->getDatabaseServerVersion();
+ }
+
+ /**
+ * Tests the database connection.
+ * Returns TRUE if connection has been established and
+ * FALSE otherwise. Suppresses any warnings that may
+ * occur during the testing process and catches all
+ * exceptions that might be thrown during the test.
+ *
+ * @return boolean
+ */
+ public static function testConnection()
+ {
+ if ( !isset( self::$adapter ) ) return FALSE;
+
+ $database = self::$adapter->getDatabase();
+ try {
+ @$database->connect();
+ } catch ( \Exception $e ) {}
+ return $database->isConnected();
+ }
+
+ /**
+ * Tests the database connection.
+ * Returns TRUE if connection has been established and
+ * FALSE otherwise. Suppresses any warnings that may
+ * occur during the testing process and catches all
+ * exceptions that might be thrown during the test.
+ *
+ * @param boolean $autoReconnect if the function attempts to reconnect to the server on failure
+ * @param string $sql the sql you want to execute to test the connection
+ *
+ * @return boolean
+ */
+ public static function testConnectionSQL( $autoReconnect = FALSE, $sql = 'SELECT 1' )
+ {
+ if ( !isset( self::$adapter ) ) return FALSE;
+
+ $database = self::$adapter->getDatabase();
+ try {
+ $database->getPDO()->query( $sql );
+ } catch ( \Exception $e ) {
+ if ( !$autoReconnect ) return FALSE;
+ $database->close();
+ $database->connect();
+ return self::testConnectionSQL( FALSE, $sql );
+ }
+ return TRUE;
+ }
+
+ /**
+ * Kickstarts redbean for you. This method should be called before you start using
+ * RedBeanPHP. The Setup() method can be called without any arguments, in this case it will
+ * try to create a SQLite database in /tmp called red.db (this only works on UNIX-like systems).
+ *
+ * Usage:
+ *
+ *
+ * R::setup( 'mysql:host=localhost;dbname=mydatabase', 'dba', 'dbapassword' );
+ *
+ *
+ * You can replace 'mysql:' with the name of the database you want to use.
+ * Possible values are:
+ *
+ * - pgsql (PostgreSQL database)
+ * - sqlite (SQLite database)
+ * - mysql (MySQL database)
+ * - mysql (also for Maria database)
+ * - sqlsrv (MS SQL Server - community supported experimental driver)
+ * - CUBRID (CUBRID driver - basic support provided by Plugin)
+ *
+ * Note that setup() will not immediately establish a connection to the database.
+ * Instead, it will prepare the connection and connect 'lazily', i.e. the moment
+ * a connection is really required, for instance when attempting to load
+ * a bean.
+ *
+ * @param string|\PDO|NULL $dsn Database connection string
+ * @param string|NULL $username Username for database
+ * @param string|NULL $password Password for database
+ * @param boolean|string[] $frozen TRUE if you want to setup in frozen mode
+ * @param boolean|string[] $partialBeans TRUE to enable partial bean updates
+ * @param array $options Additional (PDO) options to pass
+ *
+ * @return ToolBox
+ */
+ public static function setup( $dsn = NULL, $username = NULL, $password = NULL, $frozen = FALSE, $partialBeans = FALSE, $options = array() )
+ {
+ if ( is_null( $dsn ) ) {
+ $dsn = 'sqlite:' . DIRECTORY_SEPARATOR . sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'red.db';
+ }
+
+ self::addDatabase( 'default', $dsn, $username, $password, $frozen, $partialBeans, $options );
+ self::selectDatabase( 'default' );
+
+ return self::$toolbox;
+ }
+
+ /**
+ * Toggles 'Narrow Field Mode'.
+ * In Narrow Field mode the queryRecord method will
+ * narrow its selection field to
+ *
+ *
+ * SELECT {table}.*
+ *
+ *
+ * instead of
+ *
+ *
+ * SELECT *
+ *
+ *
+ * This is a better way of querying because it allows
+ * more flexibility (for instance joins). However if you need
+ * the wide selector for backward compatibility; use this method
+ * to turn OFF Narrow Field Mode by passing FALSE.
+ * Default is TRUE.
+ *
+ * @param boolean $narrowField TRUE = Narrow Field FALSE = Wide Field
+ *
+ * @return void
+ */
+ public static function setNarrowFieldMode( $mode )
+ {
+ AQueryWriter::setNarrowFieldMode( $mode );
+ }
+
+ /**
+ * Toggles fluid transactions. By default fluid transactions
+ * are not active. Starting, committing or rolling back a transaction
+ * through the facade in fluid mode will have no effect. If you wish
+ * to replace this standard portable behavior with behavior depending
+ * on how the used database platform handles fluid (DDL) transactions
+ * set this flag to TRUE.
+ *
+ * @param boolean $mode allow fluid transaction mode
+ *
+ * @return void
+ */
+ public static function setAllowFluidTransactions( $mode )
+ {
+ self::$allowFluidTransactions = $mode;
+ }
+
+ /**
+ * Toggles support for IS-NULL-conditions.
+ * If IS-NULL-conditions are enabled condition arrays
+ * for functions including findLike() are treated so that
+ * 'field' => NULL will be interpreted as field IS NULL
+ * instead of being skipped. Returns the previous
+ * value of the flag.
+ *
+ * @param boolean $flag TRUE or FALSE
+ *
+ * @return boolean
+ */
+ public static function useISNULLConditions( $mode )
+ {
+ self::getWriter()->flushCache(); /* otherwise same queries might fail (see Unit test XNull) */
+ return AQueryWriter::useISNULLConditions( $mode );
+ }
+
+ /**
+ * Wraps a transaction around a closure or string callback.
+ * If an Exception is thrown inside, the operation is automatically rolled back.
+ * If no Exception happens, it commits automatically.
+ * It also supports (simulated) nested transactions (that is useful when
+ * you have many methods that needs transactions but are unaware of
+ * each other).
+ *
+ * Example:
+ *
+ *
+ * $from = 1;
+ * $to = 2;
+ * $amount = 300;
+ *
+ * R::transaction(function() use($from, $to, $amount)
+ * {
+ * $accountFrom = R::load('account', $from);
+ * $accountTo = R::load('account', $to);
+ * $accountFrom->money -= $amount;
+ * $accountTo->money += $amount;
+ * R::store($accountFrom);
+ * R::store($accountTo);
+ * });
+ *
+ *
+ * @param callable $callback Closure (or other callable) with the transaction logic
+ *
+ * @return mixed
+ */
+ public static function transaction( $callback )
+ {
+ if ( !self::$allowFluidTransactions && !self::$redbean->isFrozen() ) return FALSE;
+ return Transaction::transaction( self::$adapter, $callback );
+ }
+
+ /**
+ * Adds a database to the facade, afterwards you can select the database using
+ * selectDatabase($key), where $key is the name you assigned to this database.
+ *
+ * Usage:
+ *
+ *
+ * R::addDatabase( 'database-1', 'sqlite:/tmp/db1.txt' );
+ * R::selectDatabase( 'database-1' ); //to select database again
+ *
+ *
+ * This method allows you to dynamically add (and select) new databases
+ * to the facade. Adding a database with the same key will cause an exception.
+ *
+ * @param string $key ID for the database
+ * @param string|\PDO $dsn DSN for the database
+ * @param string|NULL $user user for connection
+ * @param string|NULL $pass password for connection
+ * @param bool|string[] $frozen whether this database is frozen or not
+ * @param bool|string[] $partialBeans should we load partial beans?
+ * @param array $options additional options for the query writer
+ * @param BeanHelper|NULL $beanHelper Beanhelper to use (use this for DB specific model prefixes)
+ *
+ * @return void
+ */
+ public static function addDatabase( $key, $dsn, $user = NULL, $pass = NULL, $frozen = FALSE, $partialBeans = FALSE, $options = array(), $beanHelper = NULL )
+ {
+ if ( isset( self::$toolboxes[$key] ) ) {
+ throw new RedException( 'A database has already been specified for this key.' );
+ }
+
+ self::$toolboxes[$key] = self::createToolbox($dsn, $user, $pass, $frozen, $partialBeans, $options);
+
+ if ( !is_null( $beanHelper ) ) {
+ self::$toolboxes[$key]->getRedBean()->setBeanHelper( $beanHelper );
+ }
+ }
+
+ /**
+ * Creates a toolbox. This method can be called if you want to use redbean non-static.
+ * It has the same interface as R::setup(). The createToolbox() method can be called
+ * without any arguments, in this case it will try to create a SQLite database in
+ * /tmp called red.db (this only works on UNIX-like systems).
+ *
+ * Usage:
+ *
+ *
+ * R::createToolbox( 'mysql:host=localhost;dbname=mydatabase', 'dba', 'dbapassword' );
+ *
+ *
+ * You can replace 'mysql:' with the name of the database you want to use.
+ * Possible values are:
+ *
+ * - pgsql (PostgreSQL database)
+ * - sqlite (SQLite database)
+ * - mysql (MySQL database)
+ * - mysql (also for Maria database)
+ * - sqlsrv (MS SQL Server - community supported experimental driver)
+ * - CUBRID (CUBRID driver - basic support provided by Plugin)
+ *
+ * Note that createToolbox() will not immediately establish a connection to the database.
+ * Instead, it will prepare the connection and connect 'lazily', i.e. the moment
+ * a connection is really required, for instance when attempting to load a bean.
+ *
+ * @param string|\PDO $dsn Database connection string
+ * @param string $username Username for database
+ * @param string $password Password for database
+ * @param boolean|string[] $frozen TRUE if you want to setup in frozen mode
+ * @param boolean|string[] $partialBeans TRUE to enable partial bean updates
+ * @param array $options
+ *
+ * @return ToolBox
+ */
+ public static function createToolbox( $dsn, $username = NULL, $password = NULL, $frozen = FALSE, $partialBeans = FALSE, $options = array() )
+ {
+ if ( is_object($dsn) ) {
+ $db = new RPDO( $dsn );
+ $dbType = $db->getDatabaseType();
+ } else {
+ $db = new RPDO( $dsn, $username, $password, $options );
+ $dbType = substr( $dsn, 0, strpos( $dsn, ':' ) );
+ }
+
+ $adapter = new DBAdapter( $db );
+
+ $writers = array(
+ 'pgsql' => 'PostgreSQL',
+ 'sqlite' => 'SQLiteT',
+ 'cubrid' => 'CUBRID',
+ 'mysql' => 'MySQL',
+ 'sqlsrv' => 'SQLServer',
+ );
+
+ $wkey = trim( strtolower( $dbType ) );
+ if ( !isset( $writers[$wkey] ) ) {
+ $wkey = preg_replace( '/\W/', '' , $wkey );
+ throw new RedException( 'Unsupported database ('.$wkey.').' );
+ }
+ $writerClass = '\\RedBeanPHP\\QueryWriter\\'.$writers[$wkey];
+ /** @var AQueryWriter $writer */
+ $writer = new $writerClass( $adapter );
+ $redbean = new OODB( $writer, $frozen );
+
+ if ( $partialBeans ) {
+ $redbean->getCurrentRepository()->usePartialBeans( $partialBeans );
+ }
+
+ return new ToolBox( $redbean, $adapter, $writer );
+ }
+
+ /**
+ * Determines whether a database identified with the specified key has
+ * already been added to the facade. This function will return TRUE
+ * if the database indicated by the key is available and FALSE otherwise.
+ *
+ * @param string $key the key/name of the database to check for
+ *
+ * @return boolean
+ */
+ public static function hasDatabase( $key )
+ {
+ return ( isset( self::$toolboxes[$key] ) );
+ }
+
+ /**
+ * Selects a different database for the Facade to work with.
+ * If you use the R::setup() you don't need this method. This method is meant
+ * for multiple database setups. This method selects the database identified by the
+ * database ID ($key). Use addDatabase() to add a new database, which in turn
+ * can be selected using selectDatabase(). If you use R::setup(), the resulting
+ * database will be stored under key 'default', to switch (back) to this database
+ * use R::selectDatabase( 'default' ). This method returns TRUE if the database has been
+ * switched and FALSE otherwise (for instance if you already using the specified database).
+ *
+ * @param string $key Key of the database to select
+ * @param bool $force
+ *
+ * @return boolean
+ */
+ public static function selectDatabase( $key, $force = FALSE )
+ {
+ if ( self::$currentDB === $key && !$force ) {
+ return FALSE;
+ }
+
+ if ( !isset( self::$toolboxes[$key] ) ) {
+ throw new RedException( 'Database not found in registry. Add database using R::addDatabase().' );
+ }
+
+ self::configureFacadeWithToolbox( self::$toolboxes[$key] );
+ self::$currentDB = $key;
+
+ return TRUE;
+ }
+
+ /**
+ * Toggles DEBUG mode.
+ * In Debug mode all SQL that happens under the hood will
+ * be printed to the screen and/or logged.
+ * If no database connection has been configured using R::setup() or
+ * R::selectDatabase() this method will throw an exception.
+ *
+ * There are 2 debug styles:
+ *
+ * Classic: separate parameter bindings, explicit and complete but less readable
+ * Fancy: interspersed bindings, truncates large strings, highlighted schema changes
+ *
+ * Fancy style is more readable but sometimes incomplete.
+ *
+ * The first parameter turns debugging ON or OFF.
+ * The second parameter indicates the mode of operation:
+ *
+ * 0 Log and write to STDOUT classic style (default)
+ * 1 Log only, class style
+ * 2 Log and write to STDOUT fancy style
+ * 3 Log only, fancy style
+ *
+ * This function always returns the logger instance created to generate the
+ * debug messages.
+ *
+ * @param boolean $tf debug mode (TRUE or FALSE)
+ * @param integer $mode mode of operation
+ *
+ * @return RDefault
+ * @throws RedException
+ */
+ public static function debug( $tf = TRUE, $mode = 0 )
+ {
+ if ($mode > 1) {
+ $mode -= 2;
+ $logger = new Debug;
+ } else {
+ $logger = new RDefault;
+ }
+
+ if ( !isset( self::$adapter ) ) {
+ throw new RedException( 'Use R::setup() first.' );
+ }
+ $logger->setMode($mode);
+ self::$adapter->getDatabase()->setDebugMode( $tf, $logger );
+
+ return $logger;
+ }
+
+ /**
+ * Turns on the fancy debugger.
+ * In 'fancy' mode the debugger will output queries with bound
+ * parameters inside the SQL itself. This method has been added to
+ * offer a convenient way to activate the fancy debugger system
+ * in one call.
+ *
+ * @param boolean $toggle TRUE to activate debugger and select 'fancy' mode
+ *
+ * @return void
+ */
+ public static function fancyDebug( $toggle = TRUE )
+ {
+ self::debug( $toggle, 2 );
+ }
+
+ /**
+ * Inspects the database schema. If you pass the type of a bean this
+ * method will return the fields of its table in the database.
+ * The keys of this array will be the field names and the values will be
+ * the column types used to store their values.
+ * If no type is passed, this method returns a list of all tables in the database.
+ *
+ * @param string|NULL $type Type of bean (i.e. table) you want to inspect, or NULL for a list of all tables
+ *
+ * @return string[]
+ */
+ public static function inspect( $type = NULL )
+ {
+ return ($type === NULL) ? self::$writer->getTables() : self::$writer->getColumns( $type );
+ }
+
+ /**
+ * Stores a bean in the database. This method takes a
+ * OODBBean Bean Object $bean and stores it
+ * in the database. If the database schema is not compatible
+ * with this bean and RedBean runs in fluid mode the schema
+ * will be altered to store the bean correctly.
+ * If the database schema is not compatible with this bean and
+ * RedBean runs in frozen mode it will throw an exception.
+ * This function returns the primary key ID of the inserted
+ * bean.
+ *
+ * The return value is an integer if possible. If it is not possible to
+ * represent the value as an integer a string will be returned.
+ *
+ * Usage:
+ *
+ *
+ * $post = R::dispense('post');
+ * $post->title = 'my post';
+ * $id = R::store( $post );
+ * $post = R::load( 'post', $id );
+ * R::trash( $post );
+ *
+ *
+ * In the example above, we create a new bean of type 'post'.
+ * We then set the title of the bean to 'my post' and we
+ * store the bean. The store() method will return the primary
+ * key ID $id assigned by the database. We can now use this
+ * ID to load the bean from the database again and delete it.
+ *
+ * If the second parameter is set to TRUE and
+ * Hybrid mode is allowed (default OFF for novice), then RedBeanPHP
+ * will automatically temporarily switch to fluid mode to attempt to store the
+ * bean in case of an SQLException.
+ *
+ * @param OODBBean|SimpleModel|SimpleModelInterface $bean bean to store
+ * @param boolean $unfreezeIfNeeded retries in fluid mode in hybrid mode
+ *
+ * @return integer|string
+ */
+ public static function store( $bean, $unfreezeIfNeeded = FALSE )
+ {
+ $result = NULL;
+ try {
+ $result = self::$redbean->store( $bean );
+ } catch (SQLException $exception) {
+ $wasFrozen = self::$redbean->isFrozen();
+ if ( !self::$allowHybridMode || !$unfreezeIfNeeded ) throw $exception;
+ self::freeze( FALSE );
+ $result = self::$redbean->store( $bean );
+ self::freeze( $wasFrozen );
+ }
+ return $result;
+ }
+
+ /**
+ * Toggles fluid or frozen mode. In fluid mode the database
+ * structure is adjusted to accommodate your objects. In frozen mode
+ * this is not the case.
+ *
+ * You can also pass an array containing a selection of frozen types.
+ * Let's call this chilly mode, it's just like fluid mode except that
+ * certain types (i.e. tables) aren't touched.
+ *
+ * @param boolean|string[] $tf mode of operation (TRUE means frozen)
+ *
+ * @return void
+ */
+ public static function freeze( $tf = TRUE )
+ {
+ self::$redbean->freeze( $tf );
+ }
+
+ /**
+ * Loads multiple types of beans with the same ID.
+ * This might look like a strange method, however it can be useful
+ * for loading a one-to-one relation. In a typical 1-1 relation,
+ * you have two records sharing the same primary key.
+ * RedBeanPHP has only limited support for 1-1 relations.
+ * In general it is recommended to use 1-N for this.
+ *
+ * Usage:
+ *
+ *
+ * list( $author, $bio ) = R::loadMulti( 'author, bio', $id );
+ *
+ *
+ * @param string|string[] $types the set of types to load at once
+ * @param int $id the common ID
+ *
+ * @return OODBBean[]
+ */
+ public static function loadMulti( $types, $id )
+ {
+ return MultiLoader::load( self::$redbean, $types, $id );
+ }
+
+ /**
+ * Loads a bean from the object database.
+ * It searches for a OODBBean Bean Object in the
+ * database. It does not matter how this bean has been stored.
+ * RedBean uses the primary key ID $id and the string $type
+ * to find the bean. The $type specifies what kind of bean you
+ * are looking for; this is the same type as used with the
+ * dispense() function. If RedBean finds the bean it will return
+ * the OODB Bean object; if it cannot find the bean
+ * RedBean will return a new bean of type $type and with
+ * primary key ID 0. In the latter case it acts basically the
+ * same as dispense().
+ *
+ * Important note:
+ * If the bean cannot be found in the database a new bean of
+ * the specified type will be generated and returned.
+ *
+ * Usage:
+ *
+ *
+ * $post = R::dispense('post');
+ * $post->title = 'my post';
+ * $id = R::store( $post );
+ * $post = R::load( 'post', $id );
+ * R::trash( $post );
+ *
+ *
+ * In the example above, we create a new bean of type 'post'.
+ * We then set the title of the bean to 'my post' and we
+ * store the bean. The store() method will return the primary
+ * key ID $id assigned by the database. We can now use this
+ * ID to load the bean from the database again and delete it.
+ *
+ * @param string $type type of bean you want to load
+ * @param integer $id ID of the bean you want to load
+ * @param string|NULL $snippet string to use after select (optional)
+ *
+ * @return OODBBean
+ */
+ public static function load( $type, $id, $snippet = NULL )
+ {
+ if ( $snippet !== NULL ) self::$writer->setSQLSelectSnippet( $snippet );
+ $bean = self::$redbean->load( $type, $id );
+ return $bean;
+ }
+
+ /**
+ * Same as load, but selects the bean for update, thus locking the bean.
+ * This equals an SQL query like 'SELECT ... FROM ... FOR UPDATE'.
+ * Use this method if you want to load a bean you intend to UPDATE.
+ * This method should be used to 'LOCK a bean'.
+ *
+ * Usage:
+ *
+ *
+ * $bean = R::loadForUpdate( 'bean', $id );
+ * ...update...
+ * R::store( $bean );
+ *
+ *
+ * @param string $type type of bean you want to load
+ * @param integer $id ID of the bean you want to load
+ *
+ * @return OODBBean
+ */
+ public static function loadForUpdate( $type, $id )
+ {
+ return self::load( $type, $id, AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
+ }
+
+ /**
+ * Same as find(), but selects the beans for update, thus locking the beans.
+ * This equals an SQL query like 'SELECT ... FROM ... FOR UPDATE'.
+ * Use this method if you want to load a bean you intend to UPDATE.
+ * This method should be used to 'LOCK a bean'.
+ *
+ * Usage:
+ *
+ *
+ * $bean = R::findForUpdate(
+ * 'bean',
+ * ' title LIKE ? ',
+ * array('title')
+ * );
+ * ...update...
+ * R::store( $bean );
+ *
+ *
+ * @param string $type the type of bean you are looking for
+ * @param string|NULL $sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings array of values to be bound to parameters in query
+ *
+ * @return OODBBean[]
+ */
+ public static function findForUpdate( $type, $sql = NULL, $bindings = array() )
+ {
+ return self::find( $type, $sql, $bindings, AQueryWriter::C_SELECT_SNIPPET_FOR_UPDATE );
+ }
+
+ /**
+ * Convenience method.
+ * Same as findForUpdate but returns just one bean and adds LIMIT-clause.
+ *
+ * @param string $type the type of bean you are looking for
+ * @param string|NULL $sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings array of values to be bound to parameters in query
+ *
+ * @return OODBBean|NULL
+ */
+ public static function findOneForUpdate( $type, $sql = NULL, $bindings = array() )
+ {
+ $sql = self::getWriter()->glueLimitOne( $sql );
+ $beans = self::findForUpdate($type, $sql, $bindings);
+ return empty($beans) ? NULL : reset($beans);
+ }
+
+ /**
+ * Removes a bean from the database.
+ * This function will remove the specified OODBBean
+ * Bean Object from the database.
+ *
+ * This facade method also accepts a type-id combination,
+ * in the latter case this method will attempt to load the specified bean
+ * and THEN trash it.
+ *
+ * Usage:
+ *
+ *
+ * $post = R::dispense('post');
+ * $post->title = 'my post';
+ * $id = R::store( $post );
+ * $post = R::load( 'post', $id );
+ * R::trash( $post );
+ *
+ *
+ * In the example above, we create a new bean of type 'post'.
+ * We then set the title of the bean to 'my post' and we
+ * store the bean. The store() method will return the primary
+ * key ID $id assigned by the database. We can now use this
+ * ID to load the bean from the database again and delete it.
+ *
+ * @param string|OODBBean|SimpleModel|SimpleModelInterface $beanOrType bean you want to remove from database
+ * @param integer $id ID if the bean to trash (optional, type-id variant only)
+ *
+ * @return int
+ */
+ public static function trash( $beanOrType, $id = NULL )
+ {
+ if ( is_string( $beanOrType ) ) return self::trash( self::load( $beanOrType, $id ) );
+ return self::$redbean->trash( $beanOrType );
+ }
+
+ /**
+ * Dispenses a new RedBean OODB Bean for use with
+ * the rest of the methods. RedBeanPHP thinks in beans, the bean is the
+ * primary way to interact with RedBeanPHP and the database managed by
+ * RedBeanPHP. To load, store and delete data from the database using RedBeanPHP
+ * you exchange these RedBeanPHP OODB Beans. The only exception to this rule
+ * are the raw query methods like R::getCell() or R::exec() and so on.
+ * The dispense method is the 'preferred way' to create a new bean.
+ *
+ * Usage:
+ *
+ *
+ * $book = R::dispense( 'book' );
+ * $book->title = 'My Book';
+ * R::store( $book );
+ *
+ *
+ * This method can also be used to create an entire bean graph at once.
+ * Given an array with keys specifying the property names of the beans
+ * and a special _type key to indicate the type of bean, one can
+ * make the Dispense Helper generate an entire hierarchy of beans, including
+ * lists. To make dispense() generate a list, simply add a key like:
+ * ownXList or sharedXList where X is the type of beans it contains and
+ * a set its value to an array filled with arrays representing the beans.
+ * Note that, although the type may have been hinted at in the list name,
+ * you still have to specify a _type key for every bean array in the list.
+ * Note that, if you specify an array to generate a bean graph, the number
+ * parameter will be ignored.
+ *
+ * Usage:
+ *
+ *
+ * $book = R::dispense( [
+ * '_type' => 'book',
+ * 'title' => 'Gifted Programmers',
+ * 'author' => [ '_type' => 'author', 'name' => 'Xavier' ],
+ * 'ownPageList' => [ ['_type'=>'page', 'text' => '...'] ]
+ * ] );
+ *
+ *
+ * @param string|OODBBean[] $typeOrBeanArray type or bean array to import
+ * @param integer $num number of beans to dispense
+ * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
+ *
+ * @return OODBBean|OODBBean[]
+ */
+ public static function dispense( $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE )
+ {
+ return DispenseHelper::dispense( self::$redbean, $typeOrBeanArray, $num, $alwaysReturnArray );
+ }
+
+ /**
+ * Takes a comma separated list of bean types
+ * and dispenses these beans. For each type in the list
+ * you can specify the number of beans to be dispensed.
+ *
+ * Usage:
+ *
+ *
+ * list( $book, $page, $text ) = R::dispenseAll( 'book,page,text' );
+ *
+ *
+ * This will dispense a book, a page and a text. This way you can
+ * quickly dispense beans of various types in just one line of code.
+ *
+ * Usage:
+ *
+ *
+ * list($book, $pages) = R::dispenseAll('book,page*100');
+ *
+ *
+ * This returns an array with a book bean and then another array
+ * containing 100 page beans.
+ *
+ * @param string $order a description of the desired dispense order using the syntax above
+ * @param boolean $onlyArrays return only arrays even if amount < 2
+ *
+ * @return array
+ */
+ public static function dispenseAll( $order, $onlyArrays = FALSE )
+ {
+ return DispenseHelper::dispenseAll( self::$redbean, $order, $onlyArrays );
+ }
+
+ /**
+ * Convenience method. Tries to find beans of a certain type,
+ * if no beans are found, it dispenses a bean of that type.
+ * Note that this function always returns an array.
+ *
+ * @param string $type type of bean you are looking for
+ * @param string|NULL $sql SQL code for finding the bean
+ * @param array $bindings parameters to bind to SQL
+ *
+ * @return OODBBean[]
+ */
+ public static function findOrDispense( $type, $sql = NULL, $bindings = array() )
+ {
+ DispenseHelper::checkType( $type );
+ return self::$finder->findOrDispense( $type, $sql, $bindings );
+ }
+
+ /**
+ * Same as findOrDispense but returns just one element.
+ *
+ * @param string $type type of bean you are looking for
+ * @param string|NULL $sql SQL code for finding the bean
+ * @param array $bindings parameters to bind to SQL
+ *
+ * @return OODBBean
+ */
+ public static function findOneOrDispense( $type, $sql = NULL, $bindings = array() )
+ {
+ DispenseHelper::checkType( $type );
+ $arrayOfBeans = self::findOrDispense( $type, $sql, $bindings );
+ return reset($arrayOfBeans);
+ }
+
+ /**
+ * Finds beans using a type and optional SQL statement.
+ * As with most Query tools in RedBean you can provide values to
+ * be inserted in the SQL statement by populating the value
+ * array parameter; you can either use the question mark notation
+ * or the slot-notation (:keyname).
+ *
+ * Your SQL does not have to start with a WHERE-clause condition.
+ *
+ * @param string $type the type of bean you are looking for
+ * @param string|NULL $sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings array of values to be bound to parameters in query
+ * @param string|NULL $snippet SQL snippet to include in query (for example: FOR UPDATE)
+ *
+ * @phpstan-param literal-string|null $sql
+ * @psalm-param literal-string|null $sql
+ *
+ * @return OODBBean[]
+ */
+ public static function find( $type, $sql = NULL, $bindings = array(), $snippet = NULL )
+ {
+ if ( $snippet !== NULL ) self::$writer->setSQLSelectSnippet( $snippet );
+ return self::$finder->find( $type, $sql, $bindings );
+ }
+
+ /**
+ * Alias for find().
+ *
+ * @param string $type the type of bean you are looking for
+ * @param string|NULL $sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings array of values to be bound to parameters in query
+ *
+ * @return OODBBean[]
+ */
+ public static function findAll( $type, $sql = NULL, $bindings = array() )
+ {
+ return self::$finder->find( $type, $sql, $bindings );
+ }
+
+ /**
+ * Like find() but also exports the beans as an array.
+ * This method will perform a find-operation. For every bean
+ * in the result collection this method will call the export() method.
+ * This method returns an array containing the array representations
+ * of every bean in the result set.
+ *
+ * @see Finder::find
+ *
+ * @param string $type type the type of bean you are looking for
+ * @param string|NULL $sql sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings values array of values to be bound to parameters in query
+ *
+ * @return array
+ */
+ public static function findAndExport( $type, $sql = NULL, $bindings = array() )
+ {
+ return self::$finder->findAndExport( $type, $sql, $bindings );
+ }
+
+ /**
+ * Like R::find() but returns the first bean only.
+ *
+ * @param string $type the type of bean you are looking for
+ * @param string|NULL $sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings array of values to be bound to parameters in query
+ *
+ * @return OODBBean|NULL
+ */
+ public static function findOne( $type, $sql = NULL, $bindings = array() )
+ {
+ return self::$finder->findOne( $type, $sql, $bindings );
+ }
+
+ /**
+ * @deprecated
+ *
+ * Like find() but returns the last bean of the result array.
+ * Opposite of Finder::findLast().
+ * If no beans are found, this method will return NULL.
+ *
+ * Please do not use this function, it is horribly ineffective.
+ * Instead use a reversed ORDER BY clause and a LIMIT 1 with R::findOne().
+ * This function should never be used and only remains for
+ * the sake of backward compatibility.
+ *
+ * @see Finder::find
+ *
+ * @param string $type the type of bean you are looking for
+ * @param string|NULL $sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings values array of values to be bound to parameters in query
+ *
+ * @return OODBBean|NULL
+ */
+ public static function findLast( $type, $sql = NULL, $bindings = array() )
+ {
+ return self::$finder->findLast( $type, $sql, $bindings );
+ }
+
+ /**
+ * Finds a BeanCollection using the repository.
+ * A bean collection can be used to retrieve one bean at a time using
+ * cursors - this is useful for processing large datasets. A bean collection
+ * will not load all beans into memory all at once, just one at a time.
+ *
+ * @param string $type the type of bean you are looking for
+ * @param string|NULL $sql SQL query to find the desired bean, starting right after WHERE clause
+ * @param array $bindings values array of values to be bound to parameters in query
+ *
+ * @return BeanCollection
+ */
+ public static function findCollection( $type, $sql = NULL, $bindings = array() )
+ {
+ return self::$finder->findCollection( $type, $sql, $bindings );
+ }
+
+ /**
+ * Returns a hashmap with bean arrays keyed by type using an SQL
+ * query as its resource. Given an SQL query like 'SELECT movie.*, review.* FROM movie... JOIN review'
+ * this method will return movie and review beans.
+ *
+ * Example:
+ *
+ *
+ * $stuff = $finder->findMulti('movie,review', '
+ * SELECT movie.*, review.* FROM movie
+ * LEFT JOIN review ON review.movie_id = movie.id');
+ *
+ *
+ * After this operation, $stuff will contain an entry 'movie' containing all
+ * movies and an entry named 'review' containing all reviews (all beans).
+ * You can also pass bindings.
+ *
+ * If you want to re-map your beans, so you can use $movie->ownReviewList without
+ * having RedBeanPHP executing an SQL query you can use the fourth parameter to
+ * define a selection of remapping closures.
+ *
+ * The remapping argument (optional) should contain an array of arrays.
+ * Each array in the remapping array should contain the following entries:
+ *
+ *
+ * array(
+ * 'a' => TYPE A
+ * 'b' => TYPE B
+ * 'matcher' => MATCHING FUNCTION ACCEPTING A, B and ALL BEANS
+ * 'do' => OPERATION FUNCTION ACCEPTING A, B, ALL BEANS, ALL REMAPPINGS
+ * )
+ *
+ *
+ * Using this mechanism you can build your own 'preloader' with tiny function
+ * snippets (and those can be re-used and shared online of course).
+ *
+ * Example:
+ *
+ *
+ * array(
+ * 'a' => 'movie' //define A as movie
+ * 'b' => 'review' //define B as review
+ * 'matcher' => function( $a, $b ) {
+ * return ( $b->movie_id == $a->id ); //Perform action if review.movie_id equals movie.id
+ * }
+ * 'do' => function( $a, $b ) {
+ * $a->noLoad()->ownReviewList[] = $b; //Add the review to the movie
+ * $a->clearHistory(); //optional, act 'as if these beans have been loaded through ownReviewList'.
+ * }
+ * )
+ *
+ *
+ * @note the SQL query provided IS NOT THE ONE used internally by this function,
+ * this function will pre-process the query to get all the data required to find the beans.
+ *
+ * @note if you use the 'book.*' notation make SURE you're
+ * selector starts with a SPACE. ' book.*' NOT ',book.*'. This is because
+ * it's actually an SQL-like template SLOT, not real SQL.
+ *
+ * @note instead of an SQL query you can pass a result array as well.
+ *
+ * @param string|string[] $types a list of types (either array or comma separated string)
+ * @param string|array[]|NULL $sql an SQL query or an array of prefetched records
+ * @param array $bindings optional, bindings for SQL query
+ * @param array[] $remappings optional, an array of remapping arrays
+ *
+ * @return array
+ */
+ public static function findMulti( $types, $sql, $bindings = array(), $remappings = array() )
+ {
+ return self::$finder->findMulti( $types, $sql, $bindings, $remappings );
+ }
+
+ /**
+ * Returns an array of beans. Pass a type and a series of ids and
+ * this method will bring you the corresponding beans.
+ *
+ * important note: Because this method loads beans using the load()
+ * function (but faster) it will return empty beans with ID 0 for
+ * every bean that could not be located. The resulting beans will have the
+ * passed IDs as their keys.
+ *
+ * @param string $type type of beans
+ * @param int[] $ids ids to load
+ *
+ * @return OODBBean[]
+ */
+ public static function batch( $type, $ids )
+ {
+ return self::$redbean->batch( $type, $ids );
+ }
+
+ /**
+ * Alias for batch(). Batch method is older but since we added so-called *All
+ * methods like storeAll, trashAll, dispenseAll and findAll it seemed logical to
+ * improve the consistency of the Facade API and also add an alias for batch() called
+ * loadAll.
+ *
+ * @param string $type type of beans
+ * @param int[] $ids ids to load
+ *
+ * @return OODBBean[]
+ */
+ public static function loadAll( $type, $ids )
+ {
+ return self::$redbean->batch( $type, $ids );
+ }
+
+ /**
+ * Convenience function to execute Queries directly.
+ * Executes SQL.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings a list of values to be bound to query parameters
+ *
+ * @return integer
+ */
+ public static function exec( $sql, $bindings = array() )
+ {
+ return intval( self::query( 'exec', $sql, $bindings ) );
+ }
+
+ /**
+ * Convenience function to fire an SQL query using the RedBeanPHP
+ * database adapter. This method allows you to directly query the
+ * database without having to obtain an database adapter instance first.
+ * Executes the specified SQL query together with the specified
+ * parameter bindings and returns all rows
+ * and all columns.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings a list of values to be bound to query parameters
+ *
+ * @return string[][]
+ */
+ public static function getAll( $sql, $bindings = array() )
+ {
+ return self::query( 'get', $sql, $bindings );
+ }
+
+ /**
+ * Convenience function to fire an SQL query using the RedBeanPHP
+ * database adapter. This method allows you to directly query the
+ * database without having to obtain an database adapter instance first.
+ * Executes the specified SQL query together with the specified
+ * parameter bindings and returns a single cell.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings a list of values to be bound to query parameters
+ *
+ * @return string|NULL
+ */
+ public static function getCell( $sql, $bindings = array() )
+ {
+ return self::query( 'getCell', $sql, $bindings );
+ }
+
+ /**
+ * Convenience function to fire an SQL query using the RedBeanPHP
+ * database adapter. This method allows you to directly query the
+ * database without having to obtain an database adapter instance first.
+ * Executes the specified SQL query together with the specified
+ * parameter bindings and returns a PDOCursor instance.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings a list of values to be bound to query parameters
+ *
+ * @return Cursor
+ */
+ public static function getCursor( $sql, $bindings = array() )
+ {
+ return self::query( 'getCursor', $sql, $bindings );
+ }
+
+ /**
+ * Convenience function to fire an SQL query using the RedBeanPHP
+ * database adapter. This method allows you to directly query the
+ * database without having to obtain an database adapter instance first.
+ * Executes the specified SQL query together with the specified
+ * parameter bindings and returns a single row.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings a list of values to be bound to query parameters
+ *
+ * @return array|NULL
+ */
+ public static function getRow( $sql, $bindings = array() )
+ {
+ return self::query( 'getRow', $sql, $bindings );
+ }
+
+ /**
+ * Convenience function to fire an SQL query using the RedBeanPHP
+ * database adapter. This method allows you to directly query the
+ * database without having to obtain an database adapter instance first.
+ * Executes the specified SQL query together with the specified
+ * parameter bindings and returns a single column.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings a list of values to be bound to query parameters
+ *
+ * @return string[]
+ */
+ public static function getCol( $sql, $bindings = array() )
+ {
+ return self::query( 'getCol', $sql, $bindings );
+ }
+
+ /**
+ * Convenience function to execute Queries directly.
+ * Executes SQL.
+ * Results will be returned as an associative array. The first
+ * column in the select clause will be used for the keys in this array and
+ * the second column will be used for the values. If only one column is
+ * selected in the query, both key and value of the array will have the
+ * value of this field for each row.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings a list of values to be bound to query parameters
+ *
+ * @return string[]
+ */
+ public static function getAssoc( $sql, $bindings = array() )
+ {
+ return self::query( 'getAssoc', $sql, $bindings );
+ }
+
+ /**
+ * Convenience function to fire an SQL query using the RedBeanPHP
+ * database adapter. This method allows you to directly query the
+ * database without having to obtain an database adapter instance first.
+ * Executes the specified SQL query together with the specified
+ * parameter bindings and returns an associative array.
+ * Results will be returned as an associative array indexed by the first
+ * column in the select.
+ *
+ * @param string $sql SQL query to execute
+ * @param array $bindings a list of values to be bound to query parameters
+ *
+ * @return array
+ */
+ public static function getAssocRow( $sql, $bindings = array() )
+ {
+ return self::query( 'getAssocRow', $sql, $bindings );
+ }
+
+ /**
+ * Returns the insert ID for databases that support/require this
+ * functionality. Alias for R::getAdapter()->getInsertID().
+ *
+ * @return int
+ */
+ public static function getInsertID()
+ {
+ return self::$adapter->getInsertID();
+ }
+
+ /**
+ * Makes a copy of a bean. This method makes a deep copy
+ * of the bean.The copy will have the following features.
+ * - All beans in own-lists will be duplicated as well
+ * - All references to shared beans will be copied but not the shared beans themselves
+ * - All references to parent objects (_id fields) will be copied but not the parents themselves
+ * In most cases this is the desired scenario for copying beans.
+ * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
+ * (i.e. one that already has been processed) the ID of the bean will be returned.
+ * This should not happen though.
+ *
+ * Note:
+ * This function does a reflectional database query so it may be slow.
+ *
+ * @deprecated
+ * This function is deprecated in favour of R::duplicate().
+ * This function has a confusing method signature, the R::duplicate() function
+ * only accepts two arguments: bean and filters.
+ *
+ * @param OODBBean $bean bean to be copied
+ * @param OODBBean[] $trail for internal usage, pass array()
+ * @param boolean $pid for internal usage
+ * @param array $filters white list filter with bean types to duplicate
+ *
+ * @return OODBBean
+ */
+ public static function dup( $bean, $trail = array(), $pid = FALSE, $filters = array() )
+ {
+ self::$duplicationManager->setFilters( $filters );
+ return self::$duplicationManager->dup( $bean, $trail, $pid );
+ }
+
+ /**
+ * Makes a deep copy of a bean. This method makes a deep copy
+ * of the bean.The copy will have the following:
+ *
+ * * All beans in own-lists will be duplicated as well
+ * * All references to shared beans will be copied but not the shared beans themselves
+ * * All references to parent objects (_id fields) will be copied but not the parents themselves
+ *
+ * In most cases this is the desired scenario for copying beans.
+ * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
+ * (i.e. one that already has been processed) the ID of the bean will be returned.
+ * This should not happen though.
+ *
+ * Note:
+ * This function does a reflectional database query so it may be slow.
+ *
+ * Note:
+ * This is a simplified version of the deprecated R::dup() function.
+ *
+ * @param OODBBean $bean bean to be copied
+ * @param array $filters white list filter with bean types to duplicate
+ *
+ * @return OODBBean
+ */
+ public static function duplicate( $bean, $filters = array() )
+ {
+ return self::dup( $bean, array(), FALSE, $filters );
+ }
+
+ /**
+ * Exports a collection of beans. Handy for XML/JSON exports with a
+ * Javascript framework like Dojo or ExtJS.
+ * What will be exported:
+ *
+ * * contents of the bean
+ * * all own bean lists (recursively)
+ * * all shared beans (not THEIR own lists)
+ *
+ * @param OODBBean|OODBBean[] $beans beans to be exported
+ * @param boolean $parents whether you want parent beans to be exported
+ * @param array $filters whitelist of types
+ * @param boolean $meta export meta data as well
+ *
+ * @return array[]
+ */
+ public static function exportAll( $beans, $parents = FALSE, $filters = array(), $meta = FALSE )
+ {
+ return self::$duplicationManager->exportAll( $beans, $parents, $filters, self::$exportCaseStyle, $meta );
+ }
+
+ /**
+ * Selects case style for export.
+ * This will determine the case style for the keys of exported beans (see exportAll).
+ * The following options are accepted:
+ *
+ * * 'default' RedBeanPHP by default enforces Snake Case (i.e. book_id is_valid )
+ * * 'camel' Camel Case (i.e. bookId isValid )
+ * * 'dolphin' Dolphin Case (i.e. bookID isValid ) Like CamelCase but ID is written all uppercase
+ *
+ * @warning RedBeanPHP transforms camelCase to snake_case using a slightly different
+ * algorithm, it also converts isACL to is_acl (not is_a_c_l) and bookID to book_id.
+ * Due to information loss this cannot be corrected. However if you might try
+ * DolphinCase for IDs it takes into account the exception concerning IDs.
+ *
+ * @param string $caseStyle case style identifier
+ *
+ * @return void
+ */
+ public static function useExportCase( $caseStyle = 'default' )
+ {
+ if ( !in_array( $caseStyle, array( 'default', 'camel', 'dolphin' ) ) ) throw new RedException( 'Invalid case selected.' );
+ self::$exportCaseStyle = $caseStyle;
+ }
+
+ /**
+ * Converts a series of rows to beans.
+ * This method converts a series of rows to beans.
+ * The type of the desired output beans can be specified in the
+ * first parameter. The second parameter is meant for the database
+ * result rows.
+ *
+ * Usage:
+ *
+ *
+ * $rows = R::getAll( 'SELECT * FROM ...' )
+ * $beans = R::convertToBeans( $rows );
+ *
+ *
+ * As of version 4.3.2 you can specify a meta-mask.
+ * Data from columns with names starting with the value specified in the mask
+ * will be transferred to the meta section of a bean (under data.bundle).
+ *
+ *
+ * $rows = R::getAll( 'SELECT FROM... COUNT(*) AS extra_count ...' );
+ * $beans = R::convertToBeans( $rows, 'extra_' );
+ * $bean = reset( $beans );
+ * $data = $bean->getMeta( 'data.bundle' );
+ * $extra_count = $data['extra_count'];
+ *
+ *
+ * New in 4.3.2: meta mask. The meta mask is a special mask to send
+ * data from raw result rows to the meta store of the bean. This is
+ * useful for bundling additional information with custom queries.
+ * Values of every column who's name starts with $mask will be
+ * transferred to the meta section of the bean under key 'data.bundle'.
+ *
+ * @param string $type type of beans to produce
+ * @param string[][] $rows must contain an array of array
+ * @param string|array|NULL $metamask meta mask to apply (optional)
+ *
+ * @return OODBBean[]
+ */
+ public static function convertToBeans( $type, $rows, $metamask = NULL )
+ {
+ return self::$redbean->convertToBeans( $type, $rows, $metamask );
+ }
+
+ /**
+ * Just like convertToBeans, but for one bean.
+ *
+ * @param string $type type of bean to produce
+ * @param string[] $row one row from the database
+ * @param string|array|NULL $metamask metamask (see convertToBeans)
+ *
+ * @return OODBBean|NULL
+ */
+ public static function convertToBean( $type, $row, $metamask = NULL )
+ {
+ if ( !count( $row ) ) return NULL;
+ $beans = self::$redbean->convertToBeans( $type, array( $row ), $metamask );
+ $bean = reset( $beans );
+ return $bean;
+ }
+
+ /**
+ * Convenience function to 'find' beans from an SQL query.
+ * Used mostly to obtain a series of beans as well as
+ * pagination data (to paginate results) and optionally
+ * other data as well (that should not be considered part of
+ * a bean).
+ *
+ * Example:
+ *
+ * $books = R::findFromSQL('book',"
+ * SELECT *, count(*) OVER() AS total
+ * FROM book
+ * WHERE {$filter}
+ * OFFSET {$from} LIMIT {$to} ", ['total']);
+ *
+ * This is the same as doing (example uses PostgreSQL dialect):
+ *
+ * $rows = R::getAll("
+ * SELECT *, count(*) OVER() AS total
+ * FROM book
+ * WHERE {$filter}
+ * OFFSET {$from} LIMIT {$to}
+ * ", $params);
+ * $books = R::convertToBeans('book', $rows, ['total']);
+ *
+ * The additional data can be obtained using:
+ *
+ * $book->info('total');
+ *
+ * For further details see R::convertToBeans().
+ * If you set $autoExtract to TRUE and meta mask is an array,
+ * an array will be returned containing two nested arrays, the
+ * first of those nested arrays will contain the meta values
+ * you requested, the second array will contain the beans.
+ *
+ * @param string $type Type of bean to produce
+ * @param string $sql SQL query snippet to use
+ * @param array $bindings bindings for query (optional)
+ * @param string|array $metamask meta mask (optional, defaults to 'extra_')
+ * @param boolean $autoExtract TRUE to return meta mask values as first item of array
+ *
+ * @return array
+ */
+ public static function findFromSQL( $type, $sql, $bindings = array(), $metamask = 'extra_', $autoExtract = false) {
+ $rows = self::query( 'get', $sql, $bindings );
+ $beans = array();
+ if (count($rows)) $beans = self::$redbean->convertToBeans( $type, $rows, $metamask );
+ if ($autoExtract && is_array($metamask)) {
+ $values = array();
+ $firstBean = NULL;
+ if (count($beans)) $firstBean = reset($beans);
+ foreach($metamask as $key) {
+ $values[$key] = ($firstBean) ? $firstBean->info($key) : NULL;
+ }
+ return array( $values, $beans );
+ }
+ return $beans;
+ }
+
+ /**
+ * Tests whether a bean has been associated with one ore more
+ * of the listed tags. If the third parameter is TRUE this method
+ * will return TRUE only if all tags that have been specified are indeed
+ * associated with the given bean, otherwise FALSE.
+ * If the third parameter is FALSE this
+ * method will return TRUE if one of the tags matches, FALSE if none
+ * match.
+ *
+ * Tag list can be either an array with tag names or a comma separated list
+ * of tag names.
+ *
+ * Usage:
+ *
+ *
+ * R::hasTag( $blog, 'horror,movie', TRUE );
+ *
+ *
+ * The example above returns TRUE if the $blog bean has been tagged
+ * as BOTH horror and movie. If the post has only been tagged as 'movie'
+ * or 'horror' this operation will return FALSE because the third parameter
+ * has been set to TRUE.
+ *
+ * @param OODBBean $bean bean to check for tags
+ * @param string|string[] $tags list of tags
+ * @param boolean $all whether they must all match or just some
+ *
+ * @return boolean
+ */
+ public static function hasTag( $bean, $tags, $all = FALSE )
+ {
+ return self::$tagManager->hasTag( $bean, $tags, $all );
+ }
+
+ /**
+ * Removes all specified tags from the bean. The tags specified in
+ * the second parameter will no longer be associated with the bean.
+ *
+ * Tag list can be either an array with tag names or a comma separated list
+ * of tag names.
+ *
+ * Usage:
+ *
+ *
+ * R::untag( $blog, 'smart,interesting' );
+ *
+ *
+ * In the example above, the $blog bean will no longer
+ * be associated with the tags 'smart' and 'interesting'.
+ *
+ * @param OODBBean $bean tagged bean
+ * @param string|string[] $tagList list of tags (names)
+ *
+ * @return void
+ */
+ public static function untag( $bean, $tagList )
+ {
+ self::$tagManager->untag( $bean, $tagList );
+ }
+
+ /**
+ * Tags a bean or returns tags associated with a bean.
+ * If $tagList is NULL or omitted this method will return a
+ * comma separated list of tags associated with the bean provided.
+ * If $tagList is a comma separated list (string) of tags all tags will
+ * be associated with the bean.
+ * You may also pass an array instead of a string.
+ *
+ * Usage:
+ *
+ *
+ * R::tag( $meal, "TexMex,Mexican" );
+ * $tags = R::tag( $meal );
+ *
+ *
+ * The first line in the example above will tag the $meal
+ * as 'TexMex' and 'Mexican Cuisine'. The second line will
+ * retrieve all tags attached to the meal object.
+ *
+ * @param OODBBean $bean bean to tag
+ * @param string[]|NULL $tagList tags to attach to the specified bean
+ *
+ * @return string[]
+ */
+ public static function tag( OODBBean $bean, $tagList = NULL )
+ {
+ return self::$tagManager->tag( $bean, $tagList );
+ }
+
+ /**
+ * Adds tags to a bean.
+ * If $tagList is a comma separated list of tags all tags will
+ * be associated with the bean.
+ * You may also pass an array instead of a string.
+ *
+ * Usage:
+ *
+ *
+ * R::addTags( $blog, ["halloween"] );
+ *
+ *
+ * The example adds the tag 'halloween' to the $blog
+ * bean.
+ *
+ * @param OODBBean $bean bean to tag
+ * @param string|string[] $tagList list of tags to add to bean
+ *
+ * @return void
+ */
+ public static function addTags( OODBBean $bean, $tagList )
+ {
+ self::$tagManager->addTags( $bean, $tagList );
+ }
+
+ /**
+ * Returns all beans that have been tagged with one or more
+ * of the specified tags.
+ *
+ * Tag list can be either an array with tag names or a comma separated list
+ * of tag names.
+ *
+ * Usage:
+ *
+ *
+ * $watchList = R::tagged(
+ * 'movie',
+ * 'horror,gothic',
+ * ' ORDER BY movie.title DESC LIMIT ?',
+ * [ 10 ]
+ * );
+ *
+ *
+ * The example uses R::tagged() to find all movies that have been
+ * tagged as 'horror' or 'gothic', order them by title and limit
+ * the number of movies to be returned to 10.
+ *
+ * @param string $beanType type of bean you are looking for
+ * @param string|string[] $tagList list of tags to match
+ * @param string $sql additional SQL (use only for pagination)
+ * @param array $bindings bindings
+ *
+ * @return OODBBean[]
+ */
+ public static function tagged( $beanType, $tagList, $sql = '', $bindings = array() )
+ {
+ return self::$tagManager->tagged( $beanType, $tagList, $sql, $bindings );
+ }
+
+ /**
+ * Returns all beans that have been tagged with ALL of the tags given.
+ * This method works the same as R::tagged() except that this method only returns
+ * beans that have been tagged with all the specified labels.
+ *
+ * Tag list can be either an array with tag names or a comma separated list
+ * of tag names.
+ *
+ * Usage:
+ *
+ *
+ * $watchList = R::taggedAll(
+ * 'movie',
+ * [ 'gothic', 'short' ],
+ * ' ORDER BY movie.id DESC LIMIT ? ',
+ * [ 4 ]
+ * );
+ *
+ *
+ * The example above returns at most 4 movies (due to the LIMIT clause in the SQL
+ * Query Snippet) that have been tagged as BOTH 'short' AND 'gothic'.
+ *
+ * @param string $beanType type of bean you are looking for
+ * @param string|string[] $tagList list of tags to match
+ * @param string $sql additional sql snippet
+ * @param array $bindings bindings
+ *
+ * @return OODBBean[]
+ */
+ public static function taggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
+ {
+ return self::$tagManager->taggedAll( $beanType, $tagList, $sql, $bindings );
+ }
+
+ /**
+ * Same as taggedAll() but counts beans only (does not return beans).
+ *
+ * @see R::taggedAll
+ *
+ * @param string $beanType type of bean you are looking for
+ * @param string|string[] $tagList list of tags to match
+ * @param string $sql additional sql snippet
+ * @param array $bindings bindings
+ *
+ * @return integer
+ */
+ public static function countTaggedAll( $beanType, $tagList, $sql = '', $bindings = array() )
+ {
+ return self::$tagManager->countTaggedAll( $beanType, $tagList, $sql, $bindings );
+ }
+
+ /**
+ * Same as tagged() but counts beans only (does not return beans).
+ *
+ * @see R::tagged
+ *
+ * @param string $beanType type of bean you are looking for
+ * @param string|string[] $tagList list of tags to match
+ * @param string $sql additional sql snippet
+ * @param array $bindings bindings
+ *
+ * @return integer
+ */
+ public static function countTagged( $beanType, $tagList, $sql = '', $bindings = array() )
+ {
+ return self::$tagManager->countTagged( $beanType, $tagList, $sql, $bindings );
+ }
+
+ /**
+ * Wipes all beans of type $beanType.
+ *
+ * @param string $beanType type of bean you want to destroy entirely
+ *
+ * @return boolean
+ */
+ public static function wipe( $beanType )
+ {
+ return Facade::$redbean->wipe( $beanType );
+ }
+
+ /**
+ * Counts the number of beans of type $type.
+ * This method accepts a second argument to modify the count-query.
+ * A third argument can be used to provide bindings for the SQL snippet.
+ *
+ * @param string $type type of bean we are looking for
+ * @param string $addSQL additional SQL snippet
+ * @param array $bindings parameters to bind to SQL
+ *
+ * @return integer
+ */
+ public static function count( $type, $addSQL = '', $bindings = array() )
+ {
+ return Facade::$redbean->count( $type, $addSQL, $bindings );
+ }
+
+ /**
+ * Configures the facade, want to have a new Writer? A new Object Database or a new
+ * Adapter and you want it on-the-fly? Use this method to hot-swap your facade with a new
+ * toolbox.
+ *
+ * @param ToolBox $tb toolbox to configure facade with
+ *
+ * @return ToolBox
+ */
+ public static function configureFacadeWithToolbox( ToolBox $tb )
+ {
+ $oldTools = self::$toolbox;
+ self::$toolbox = $tb;
+ self::$writer = self::$toolbox->getWriter();
+ self::$adapter = self::$toolbox->getDatabaseAdapter();
+ self::$redbean = self::$toolbox->getRedBean();
+ self::$finder = new Finder( self::$toolbox );
+ self::$associationManager = new AssociationManager( self::$toolbox );
+ self::$tree = new Tree( self::$toolbox );
+ self::$redbean->setAssociationManager( self::$associationManager );
+ self::$labelMaker = new LabelMaker( self::$toolbox );
+ $helper = new SimpleModelHelper();
+ $helper->attachEventListeners( self::$redbean );
+ if (self::$redbean->getBeanHelper() === NULL) {
+ self::$redbean->setBeanHelper( new SimpleFacadeBeanHelper );
+ }
+ self::$duplicationManager = new DuplicationManager( self::$toolbox );
+ self::$tagManager = new TagManager( self::$toolbox );
+ return $oldTools;
+ }
+
+ /**
+ * Facade Convenience method for adapter transaction system.
+ * Begins a transaction.
+ *
+ * Usage:
+ *
+ *
+ * R::begin();
+ * try {
+ * $bean1 = R::dispense( 'bean' );
+ * R::store( $bean1 );
+ * $bean2 = R::dispense( 'bean' );
+ * R::store( $bean2 );
+ * R::commit();
+ * } catch( \Exception $e ) {
+ * R::rollback();
+ * }
+ *
+ *
+ * The example above illustrates how transactions in RedBeanPHP are used.
+ * In this example 2 beans are stored or nothing is stored at all.
+ * It's not possible for this piece of code to store only half of the beans.
+ * If an exception occurs, the transaction gets rolled back and the database
+ * will be left 'untouched'.
+ *
+ * In fluid mode transactions will be ignored and all queries will
+ * be executed as-is because database schema changes will automatically
+ * trigger the transaction system to commit everything in some database
+ * systems. If you use a database that can handle DDL changes you might wish
+ * to use setAllowFluidTransactions(TRUE). If you do this, the behavior of
+ * this function in fluid mode will depend on the database platform used.
+ *
+ * @return bool
+ */
+ public static function begin()
+ {
+ if ( !self::$allowFluidTransactions && !self::$redbean->isFrozen() ) return FALSE;
+ self::$adapter->startTransaction();
+ return TRUE;
+ }
+
+ /**
+ * Facade Convenience method for adapter transaction system.
+ * Commits a transaction.
+ *
+ * Usage:
+ *
+ *
+ * R::begin();
+ * try {
+ * $bean1 = R::dispense( 'bean' );
+ * R::store( $bean1 );
+ * $bean2 = R::dispense( 'bean' );
+ * R::store( $bean2 );
+ * R::commit();
+ * } catch( \Exception $e ) {
+ * R::rollback();
+ * }
+ *
+ *
+ * The example above illustrates how transactions in RedBeanPHP are used.
+ * In this example 2 beans are stored or nothing is stored at all.
+ * It's not possible for this piece of code to store only half of the beans.
+ * If an exception occurs, the transaction gets rolled back and the database
+ * will be left 'untouched'.
+ *
+ * In fluid mode transactions will be ignored and all queries will
+ * be executed as-is because database schema changes will automatically
+ * trigger the transaction system to commit everything in some database
+ * systems. If you use a database that can handle DDL changes you might wish
+ * to use setAllowFluidTransactions(TRUE). If you do this, the behavior of
+ * this function in fluid mode will depend on the database platform used.
+ *
+ * @return bool
+ */
+ public static function commit()
+ {
+ if ( !self::$allowFluidTransactions && !self::$redbean->isFrozen() ) return FALSE;
+ self::$adapter->commit();
+ return TRUE;
+ }
+
+ /**
+ * Facade Convenience method for adapter transaction system.
+ * Rolls back a transaction.
+ *
+ * Usage:
+ *
+ *
+ * R::begin();
+ * try {
+ * $bean1 = R::dispense( 'bean' );
+ * R::store( $bean1 );
+ * $bean2 = R::dispense( 'bean' );
+ * R::store( $bean2 );
+ * R::commit();
+ * } catch( \Exception $e ) {
+ * R::rollback();
+ * }
+ *
+ *
+ * The example above illustrates how transactions in RedBeanPHP are used.
+ * In this example 2 beans are stored or nothing is stored at all.
+ * It's not possible for this piece of code to store only half of the beans.
+ * If an exception occurs, the transaction gets rolled back and the database
+ * will be left 'untouched'.
+ *
+ * In fluid mode transactions will be ignored and all queries will
+ * be executed as-is because database schema changes will automatically
+ * trigger the transaction system to commit everything in some database
+ * systems. If you use a database that can handle DDL changes you might wish
+ * to use setAllowFluidTransactions(TRUE). If you do this, the behavior of
+ * this function in fluid mode will depend on the database platform used.
+ *
+ * @return bool
+ */
+ public static function rollback()
+ {
+ if ( !self::$allowFluidTransactions && !self::$redbean->isFrozen() ) return FALSE;
+ self::$adapter->rollback();
+ return TRUE;
+ }
+
+ /**
+ * Returns a list of columns. Format of this array:
+ * array( fieldname => type )
+ * Note that this method only works in fluid mode because it might be
+ * quite heavy on production servers!
+ *
+ * @param string $table name of the table (not type) you want to get columns of
+ *
+ * @return string[]
+ */
+ public static function getColumns( $table )
+ {
+ return self::$writer->getColumns( $table );
+ }
+
+ /**
+ * Generates question mark slots for an array of values.
+ * Given an array and an optional template string this method
+ * will produce string containing parameter slots for use in
+ * an SQL query string.
+ *
+ * Usage:
+ *
+ *
+ * R::genSlots( array( 'a', 'b' ) );
+ *
+ *
+ * The statement in the example will produce the string:
+ * '?,?'.
+ *
+ * Another example, using a template string:
+ *
+ *
+ * R::genSlots( array('a', 'b'), ' IN( %s ) ' );
+ *
+ *
+ * The statement in the example will produce the string:
+ * ' IN( ?,? ) '.
+ *
+ * @param array $array array to generate question mark slots for
+ * @param string|NULL $template template to use
+ *
+ * @return string
+ */
+ public static function genSlots( $array, $template = NULL )
+ {
+ return ArrayTool::genSlots( $array, $template );
+ }
+
+ /**
+ * Convenience method to quickly attach parent beans.
+ * Although usually this can also be done with findMulti(), that
+ * approach can be a bit verbose sometimes. This convenience method
+ * uses a default yet overridable SQL snippet to perform the
+ * operation, leveraging the power of findMulti().
+ *
+ * Usage:
+ *
+ *
+ * $users = R::find('user');
+ * $users = R::loadJoined( $users, 'country' );
+ *
+ *
+ * This is an alternative for:
+ *
+ *
+ * $all = R::findMulti('country',
+ * R::genSlots( $users,
+ * 'SELECT country.* FROM country WHERE id IN ( %s )' ),
+ * array_column( $users, 'country_id' ),
+ * [Finder::onmap('country', $gebruikers)]
+ * );
+ *
+ *
+ * @param OODBBean[]|TypedModel[] $beans a list of OODBBeans
+ * @param string $type a type string
+ * @param string $sqlTemplate an SQL template string for the SELECT-query
+ *
+ * @return OODBBean[]|TypedModel[]
+ */
+ public static function loadJoined( $beans, $type, $sqlTemplate = 'SELECT %s.* FROM %s WHERE id IN (%s)' )
+ {
+ if (!count($beans)) return array();
+ $ids = array();
+ $key = "{$type}_id";
+ foreach( $beans as $bean ) $ids[] = $bean->{$key};
+ $result = self::findMulti($type, self::genSlots( $beans,sprintf($sqlTemplate, $type, $type, '%s')), $ids, array( Finder::onmap($type, $beans) ) );
+ $bean = reset($beans);
+ return $result[ $bean->getMeta('type') ];
+ }
+
+ /**
+ * Flattens a multi dimensional bindings array for use with genSlots().
+ *
+ * Usage:
+ *
+ *
+ * R::flat( array( 'a', array( 'b' ), 'c' ) );
+ *
+ *
+ * produces an array like: [ 'a', 'b', 'c' ]
+ *
+ * @param array $array array to flatten
+ * @param array $result result array parameter (for recursion)
+ *
+ * @return array
+ */
+ public static function flat( $array, $result = array() )
+ {
+ return ArrayTool::flat( $array, $result );
+ }
+
+ /**
+ * Nukes the entire database.
+ * This will remove all schema structures from the database.
+ * Only works in fluid mode. Be careful with this method.
+ *
+ * @warning dangerous method, will remove all tables, columns etc.
+ *
+ * @return void
+ */
+ public static function nuke()
+ {
+ return self::wipeAll( TRUE );
+ }
+
+ /**
+ * Truncates or drops all database tables/views.
+ * Empties the database. If the deleteTables flag is set to TRUE
+ * this function will also remove the database structures.
+ * The latter only works in fluid mode.
+ *
+ * @param boolean $alsoDeleteTables TRUE to clear entire database.
+ *
+ * @return void
+ */
+ public static function wipeAll( $alsoDeleteTables = FALSE )
+ {
+ if ( $alsoDeleteTables ) {
+ if ( !self::$redbean->isFrozen() ) {
+ self::$writer->wipeAll();
+ }
+ } else {
+ foreach ( self::$writer->getTables() as $table ) {
+ self::wipe( $table );
+ }
+ }
+ }
+
+ /**
+ * Short hand function to store a set of beans at once, IDs will be
+ * returned as an array. For information please consult the R::store()
+ * function.
+ * A loop saver.
+ *
+ * If the second parameter is set to TRUE and
+ * Hybrid mode is allowed (default OFF for novice), then RedBeanPHP
+ * will automatically temporarily switch to fluid mode to attempt to store the
+ * bean in case of an SQLException.
+ *
+ * @param OODBBean[] $beans list of beans to be stored
+ * @param boolean $unfreezeIfNeeded retries in fluid mode in hybrid mode
+ *
+ * @return int[] ids
+ */
+ public static function storeAll( $beans, $unfreezeIfNeeded = FALSE )
+ {
+ $ids = array();
+ foreach ( $beans as $bean ) {
+ $ids[] = self::store( $bean, $unfreezeIfNeeded );
+ }
+ return $ids;
+ }
+
+ /**
+ * Short hand function to trash a set of beans at once.
+ * For information please consult the R::trash() function.
+ * A loop saver.
+ *
+ * @param OODBBean[] $beans list of beans to be trashed
+ *
+ * @return int
+ */
+ public static function trashAll( $beans )
+ {
+ $numberOfDeletion = 0;
+ foreach ( $beans as $bean ) {
+ $numberOfDeletion += self::trash( $bean );
+ }
+ return $numberOfDeletion;
+ }
+
+ /**
+ * Short hand function to trash a series of beans using
+ * only IDs. This function combines trashAll and batch loading
+ * in one call. Note that while this function accepts just
+ * bean IDs, the beans will still be loaded first. This is because
+ * the function still respects all the FUSE hooks that may have been
+ * associated with the domain logic associated with these beans.
+ * If you really want to delete just records from the database use
+ * a simple DELETE-FROM SQL query instead.
+ *
+ * @param string $type the bean type you wish to trash
+ * @param int[] $ids list of bean IDs
+ *
+ * @return void
+ */
+ public static function trashBatch( $type, $ids )
+ {
+ self::trashAll( self::batch( $type, $ids ) );
+ }
+
+ /**
+ * Short hand function to find and trash beans.
+ * This function combines trashAll and find.
+ * Given a bean type, a query snippet and optionally some parameter
+ * bindings, this function will search for the beans described in the
+ * query and its parameters and then feed them to the trashAll function
+ * to be trashed.
+ *
+ * Note that while this function accepts just
+ * a bean type and query snippet, the beans will still be loaded first. This is because
+ * the function still respects all the FUSE hooks that may have been
+ * associated with the domain logic associated with these beans.
+ * If you really want to delete just records from the database use
+ * a simple DELETE-FROM SQL query instead.
+ *
+ * Returns the number of beans deleted.
+ *
+ * @param string $type bean type to look for in database
+ * @param string|NULL $sqlSnippet an SQL query snippet
+ * @param array $bindings SQL parameter bindings
+ *
+ * @return int
+ */
+ public static function hunt( $type, $sqlSnippet = NULL, $bindings = array() )
+ {
+ $numberOfTrashedBeans = 0;
+ $beans = self::findCollection( $type, $sqlSnippet, $bindings );
+ while( $bean = $beans->next() ) {
+ self::trash( $bean );
+ $numberOfTrashedBeans++;
+ }
+ return $numberOfTrashedBeans;
+ }
+
+ /**
+ * Toggles Writer Cache.
+ * Turns the Writer Cache on or off. The Writer Cache is a simple
+ * query based caching system that may improve performance without the need
+ * for cache management. This caching system will cache non-modifying queries
+ * that are marked with special SQL comments. As soon as a non-marked query
+ * gets executed the cache will be flushed. Only non-modifying select queries
+ * have been marked therefore this mechanism is a rather safe way of caching, requiring
+ * no explicit flushes or reloads. Of course this does not apply if you intend to test
+ * or simulate concurrent querying.
+ *
+ * @param boolean $yesNo TRUE to enable cache, FALSE to disable cache
+ *
+ * @return void
+ */
+ public static function useWriterCache( $yesNo )
+ {
+ self::getWriter()->setUseCache( $yesNo );
+ }
+
+ /**
+ * A label is a bean with only an id, type and name property.
+ * This function will dispense beans for all entries in the array. The
+ * values of the array will be assigned to the name property of each
+ * individual bean.
+ *
+ * @param string $type type of beans you would like to have
+ * @param string[] $labels list of labels, names for each bean
+ *
+ * @return OODBBean[]
+ */
+ public static function dispenseLabels( $type, $labels )
+ {
+ return self::$labelMaker->dispenseLabels( $type, $labels );
+ }
+
+ /**
+ * Generates and returns an ENUM value. This is how RedBeanPHP handles ENUMs.
+ * Either returns a (newly created) bean representing the desired ENUM
+ * value or returns a list of all enums for the type.
+ *
+ * To obtain (and add if necessary) an ENUM value:
+ *
+ *
+ * $tea->flavour = R::enum( 'flavour:apple' );
+ *
+ *
+ * Returns a bean of type 'flavour' with name = apple.
+ * This will add a bean with property name (set to APPLE) to the database
+ * if it does not exist yet.
+ *
+ * To obtain all flavours:
+ *
+ *
+ * R::enum('flavour');
+ *
+ *
+ * To get a list of all flavour names:
+ *
+ *
+ * R::gatherLabels( R::enum( 'flavour' ) );
+ *
+ *
+ * @param string $enum either type or type-value
+ *
+ * @return OODBBean|OODBBean[]
+ */
+ public static function enum( $enum )
+ {
+ return self::$labelMaker->enum( $enum );
+ }
+
+ /**
+ * Gathers labels from beans. This function loops through the beans,
+ * collects the values of the name properties of each individual bean
+ * and stores the names in a new array. The array then gets sorted using the
+ * default sort function of PHP (sort).
+ *
+ * @param OODBBean[] $beans list of beans to loop
+ *
+ * @return string[]
+ */
+ public static function gatherLabels( $beans )
+ {
+ return self::$labelMaker->gatherLabels( $beans );
+ }
+
+ /**
+ * Closes the database connection.
+ * While database connections are closed automatically at the end of the PHP script,
+ * closing database connections is generally recommended to improve performance.
+ * Closing a database connection will immediately return the resources to PHP.
+ *
+ * Usage:
+ *
+ *
+ * R::setup( ... );
+ * ... do stuff ...
+ * R::close();
+ *
+ *
+ * @return void
+ */
+ public static function close()
+ {
+ if ( isset( self::$adapter ) ) {
+ self::$adapter->close();
+ }
+ }
+
+ /**
+ * Simple convenience function, returns ISO date formatted representation
+ * of $time.
+ *
+ * @param int|NULL $time UNIX timestamp
+ *
+ * @return string
+ */
+ public static function isoDate( $time = NULL )
+ {
+ if ( !$time ) {
+ $time = time();
+ }
+
+ return @date( 'Y-m-d', $time );
+ }
+
+ /**
+ * Simple convenience function, returns ISO date time
+ * formatted representation
+ * of $time.
+ *
+ * @param int|NULL $time UNIX timestamp
+ *
+ * @return string
+ */
+ public static function isoDateTime( $time = NULL )
+ {
+ if ( !$time ) $time = time();
+ return @date( 'Y-m-d H:i:s', $time );
+ }
+
+ /**
+ * Sets the database adapter you want to use.
+ * The database adapter manages the connection to the database
+ * and abstracts away database driver specific interfaces.
+ *
+ * @param Adapter $adapter Database Adapter for facade to use
+ *
+ * @return void
+ */
+ public static function setDatabaseAdapter( Adapter $adapter )
+ {
+ self::$adapter = $adapter;
+ }
+
+ /**
+ * Sets the Query Writer you want to use.
+ * The Query Writer writes and executes database queries using
+ * the database adapter. It turns RedBeanPHP 'commands' into
+ * database 'statements'.
+ *
+ * @param QueryWriter $writer Query Writer instance for facade to use
+ *
+ * @return void
+ */
+ public static function setWriter( QueryWriter $writer )
+ {
+ self::$writer = $writer;
+ }
+
+ /**
+ * Sets the OODB you want to use.
+ * The RedBeanPHP Object oriented database is the main RedBeanPHP
+ * interface that allows you to store and retrieve RedBeanPHP
+ * objects (i.e. beans).
+ *
+ * @param OODB $redbean Object Database for facade to use
+ */
+ public static function setRedBean( OODB $redbean )
+ {
+ self::$redbean = $redbean;
+ }
+
+ /**
+ * Optional accessor for neat code.
+ * Sets the database adapter you want to use.
+ *
+ * @return Adapter
+ */
+ public static function getDatabaseAdapter()
+ {
+ return self::$adapter;
+ }
+
+ /**
+ * In case you use PDO (which is recommended and the default but not mandatory, hence
+ * the database adapter), you can use this method to obtain the PDO object directly.
+ * This is a convenience method, it will do the same as:
+ *
+ *
+ * R::getDatabaseAdapter()->getDatabase()->getPDO();
+ *
+ *
+ * If the PDO object could not be found, for whatever reason, this method
+ * will return NULL instead.
+ *
+ * @return NULL|\PDO
+ */
+ public static function getPDO()
+ {
+ $databaseAdapter = self::getDatabaseAdapter();
+ if ( is_null( $databaseAdapter ) ) return NULL;
+ $database = $databaseAdapter->getDatabase();
+ if ( is_null( $database ) ) return NULL;
+ if ( !method_exists( $database, 'getPDO' ) ) return NULL;
+
+ /** @var RPDO $database */
+ return $database->getPDO();
+ }
+
+ /**
+ * Returns the current duplication manager instance.
+ *
+ * @return DuplicationManager
+ */
+ public static function getDuplicationManager()
+ {
+ return self::$duplicationManager;
+ }
+
+ /**
+ * Optional accessor for neat code.
+ * Sets the database adapter you want to use.
+ *
+ * @return QueryWriter
+ */
+ public static function getWriter()
+ {
+ return self::$writer;
+ }
+
+ /**
+ * Optional accessor for neat code.
+ * Sets the database adapter you want to use.
+ *
+ * @return OODB
+ */
+ public static function getRedBean()
+ {
+ return self::$redbean;
+ }
+
+ /**
+ * Returns the toolbox currently used by the facade.
+ * To set the toolbox use R::setup() or R::configureFacadeWithToolbox().
+ * To create a toolbox use Setup::kickstart(). Or create a manual
+ * toolbox using the ToolBox class.
+ *
+ * @return ToolBox
+ */
+ public static function getToolBox()
+ {
+ return self::$toolbox;
+ }
+
+ /**
+ * Mostly for internal use, but might be handy
+ * for some users.
+ * This returns all the components of the currently
+ * selected toolbox.
+ *
+ * Returns the components in the following order:
+ *
+ * # OODB instance (getRedBean())
+ * # Database Adapter
+ * # Query Writer
+ * # Toolbox itself
+ *
+ * @return array
+ */
+ public static function getExtractedToolbox()
+ {
+ return array( self::$redbean, self::$adapter, self::$writer, self::$toolbox );
+ }
+
+ /**
+ * Facade method for AQueryWriter::renameAssociation()
+ *
+ * @param string|string[] $from
+ * @param string $to
+ *
+ * @return void
+ */
+ public static function renameAssociation( $from, $to = NULL )
+ {
+ AQueryWriter::renameAssociation( $from, $to );
+ }
+
+ /**
+ * Little helper method for Resty Bean Can server and others.
+ * Takes an array of beans and exports each bean.
+ * Unlike exportAll this method does not recurse into own lists
+ * and shared lists, the beans are exported as-is, only loaded lists
+ * are exported.
+ *
+ * @param OODBBean[] $beans beans
+ *
+ * @return array[]
+ */
+ public static function beansToArray( $beans )
+ {
+ $list = array();
+ foreach( $beans as $bean ) $list[] = $bean->export();
+ return $list;
+ }
+
+ /**
+ * Sets the error mode for FUSE.
+ * What to do if a FUSE model method does not exist?
+ * You can set the following options:
+ *
+ * * OODBBean::C_ERR_IGNORE (default), ignores the call, returns NULL
+ * * OODBBean::C_ERR_LOG, logs the incident using error_log
+ * * OODBBean::C_ERR_NOTICE, triggers a E_USER_NOTICE
+ * * OODBBean::C_ERR_WARN, triggers a E_USER_WARNING
+ * * OODBBean::C_ERR_EXCEPTION, throws an exception
+ * * OODBBean::C_ERR_FUNC, allows you to specify a custom handler (function)
+ * * OODBBean::C_ERR_FATAL, triggers a E_USER_ERROR
+ *
+ *
+ * Custom handler method signature: handler( array (
+ * 'message' => string
+ * 'bean' => OODBBean
+ * 'method' => string
+ * ) )
+ *
+ *
+ * This method returns the old mode and handler as an array.
+ *
+ * @param integer $mode mode, determines how to handle errors
+ * @param callable|NULL $func custom handler (if applicable)
+ *
+ * @return array
+ */
+ public static function setErrorHandlingFUSE( $mode, $func = NULL )
+ {
+ return OODBBean::setErrorHandlingFUSE( $mode, $func );
+ }
+
+ /**
+ * Dumps bean data to array.
+ * Given a one or more beans this method will
+ * return an array containing first part of the string
+ * representation of each item in the array.
+ *
+ * Usage:
+ *
+ *
+ * echo R::dump( $bean );
+ *
+ *
+ * The example shows how to echo the result of a simple
+ * dump. This will print the string representation of the
+ * specified bean to the screen, limiting the output per bean
+ * to 35 characters to improve readability. Nested beans will
+ * also be dumped.
+ *
+ * @param OODBBean|OODBBean[] $data either a bean or an array of beans
+ *
+ * @return string|string[]
+ */
+ public static function dump( $data )
+ {
+ return Dump::dump( $data );
+ }
+
+ /**
+ * Binds an SQL function to a column.
+ * This method can be used to setup a decode/encode scheme or
+ * perform UUID insertion. This method is especially useful for handling
+ * MySQL spatial columns, because they need to be processed first using
+ * the asText/GeomFromText functions.
+ *
+ * Example:
+ *
+ *
+ * R::bindFunc( 'read', 'location.point', 'asText' );
+ * R::bindFunc( 'write', 'location.point', 'GeomFromText' );
+ *
+ *
+ * Passing NULL as the function will reset (clear) the function
+ * for this column/mode.
+ *
+ * @param string $mode mode for function: i.e. read or write
+ * @param string $field field (table.column) to bind function to
+ * @param string $function SQL function to bind to specified column
+ * @param boolean $isTemplate TRUE if $function is an SQL string, FALSE for just a function name
+ *
+ * @return void
+ */
+ public static function bindFunc( $mode, $field, $function, $isTemplate = FALSE )
+ {
+ self::$redbean->bindFunc( $mode, $field, $function, $isTemplate );
+ }
+
+ /**
+ * Sets global aliases.
+ * Registers a batch of aliases in one go. This works the same as
+ * fetchAs but explicitly. For instance if you register
+ * the alias 'cover' for 'page' a property containing a reference to a
+ * page bean called 'cover' will correctly return the page bean and not
+ * a (non-existent) cover bean.
+ *
+ *
+ * R::aliases( array( 'cover' => 'page' ) );
+ * $book = R::dispense( 'book' );
+ * $page = R::dispense( 'page' );
+ * $book->cover = $page;
+ * R::store( $book );
+ * $book = $book->fresh();
+ * $cover = $book->cover;
+ * echo $cover->getMeta( 'type' ); //page
+ *
+ *
+ * The format of the aliases registration array is:
+ *
+ * {alias} => {actual type}
+ *
+ * In the example above we use:
+ *
+ * cover => page
+ *
+ * From that point on, every bean reference to a cover
+ * will return a 'page' bean.
+ *
+ * @param string[] $list list of global aliases to use
+ *
+ * @return void
+ */
+ public static function aliases( $list )
+ {
+ OODBBean::aliases( $list );
+ }
+
+ /**
+ * Tries to find a bean matching a certain type and
+ * criteria set. If no beans are found a new bean
+ * will be created, the criteria will be imported into this
+ * bean and the bean will be stored and returned.
+ * If multiple beans match the criteria only the first one
+ * will be returned.
+ *
+ * @param string $type type of bean to search for
+ * @param array $like criteria set describing the bean to search for
+ * @param boolean &$hasBeenCreated set to TRUE if bean has been created
+ *
+ * @return OODBBean
+ */
+ public static function findOrCreate( $type, $like = array(), $sql = '', &$hasBeenCreated = false )
+ {
+ return self::$finder->findOrCreate( $type, $like, $sql, $hasBeenCreated );
+ }
+
+ /**
+ * Tries to find beans matching the specified type and
+ * criteria set.
+ *
+ * If the optional additional SQL snippet is a condition, it will
+ * be glued to the rest of the query using the AND operator.
+ *
+ * @param string $type type of bean to search for
+ * @param array $like optional criteria set describing the bean to search for
+ * @param string $sql optional additional SQL for sorting
+ * @param array $bindings bindings
+ *
+ * @return OODBBean[]
+ */
+ public static function findLike( $type, $like = array(), $sql = '', $bindings = array() )
+ {
+ return self::$finder->findLike( $type, $like, $sql, $bindings );
+ }
+
+ /**
+ * Starts logging queries.
+ * Use this method to start logging SQL queries being
+ * executed by the adapter. Logging queries will not
+ * print them on the screen. Use R::getLogs() to
+ * retrieve the logs.
+ *
+ * Usage:
+ *
+ *
+ * R::startLogging();
+ * R::store( R::dispense( 'book' ) );
+ * R::find('book', 'id > ?',[0]);
+ * $logs = R::getLogs();
+ * $count = count( $logs );
+ * print_r( $logs );
+ * R::stopLogging();
+ *
+ *
+ * In the example above we start a logging session during
+ * which we store an empty bean of type book. To inspect the
+ * logs we invoke R::getLogs() after stopping the logging.
+ *
+ * @note you cannot use R::debug and R::startLogging
+ * at the same time because R::debug is essentially a
+ * special kind of logging.
+ *
+ * @return void
+ */
+ public static function startLogging()
+ {
+ self::debug( TRUE, RDefault::C_LOGGER_ARRAY );
+ }
+
+ /**
+ * Stops logging and flushes the logs,
+ * convenient method to stop logging of queries.
+ * Use this method to stop logging SQL queries being
+ * executed by the adapter. Logging queries will not
+ * print them on the screen. Use R::getLogs() to
+ * retrieve the logs.
+ *
+ *
+ * R::startLogging();
+ * R::store( R::dispense( 'book' ) );
+ * R::find('book', 'id > ?',[0]);
+ * $logs = R::getLogs();
+ * $count = count( $logs );
+ * print_r( $logs );
+ * R::stopLogging();
+ *
+ *
+ * In the example above we start a logging session during
+ * which we store an empty bean of type book. To inspect the
+ * logs we invoke R::getLogs() after stopping the logging.
+ *
+ * @note you cannot use R::debug and R::startLogging
+ * at the same time because R::debug is essentially a
+ * special kind of logging.
+ *
+ * @note by stopping the logging you also flush the logs.
+ * Therefore, only stop logging AFTER you have obtained the
+ * query logs using R::getLogs()
+ *
+ * @return void
+ */
+ public static function stopLogging()
+ {
+ self::debug( FALSE );
+ }
+
+ /**
+ * Returns the log entries written after the startLogging.
+ *
+ * Use this method to obtain the query logs gathered
+ * by the logging mechanisms.
+ * Logging queries will not
+ * print them on the screen. Use R::getLogs() to
+ * retrieve the logs.
+ *
+ *
+ * R::startLogging();
+ * R::store( R::dispense( 'book' ) );
+ * R::find('book', 'id > ?',[0]);
+ * $logs = R::getLogs();
+ * $count = count( $logs );
+ * print_r( $logs );
+ * R::stopLogging();
+ *
+ *
+ * In the example above we start a logging session during
+ * which we store an empty bean of type book. To inspect the
+ * logs we invoke R::getLogs() after stopping the logging.
+ *
+ * The logs may look like:
+ *
+ * [1] => SELECT `book`.* FROM `book` WHERE id > ? -- keep-cache
+ * [2] => array ( 0 => 0, )
+ * [3] => resultset: 1 rows
+ *
+ * Basically, element in the array is a log entry.
+ * Parameter bindings are represented as nested arrays (see 2).
+ *
+ * @note you cannot use R::debug and R::startLogging
+ * at the same time because R::debug is essentially a
+ * special kind of logging.
+ *
+ * @note by stopping the logging you also flush the logs.
+ * Therefore, only stop logging AFTER you have obtained the
+ * query logs using R::getLogs()
+ *
+ * @return string[]
+ */
+ public static function getLogs()
+ {
+ return self::getLogger()->getLogs();
+ }
+
+ /**
+ * Resets the query counter.
+ * The query counter can be used to monitor the number
+ * of database queries that have
+ * been processed according to the database driver. You can use this
+ * to monitor the number of queries required to render a page.
+ *
+ * Usage:
+ *
+ *
+ * R::resetQueryCount();
+ * echo R::getQueryCount() . ' queries processed.';
+ *
+ *
+ * @return void
+ */
+ public static function resetQueryCount()
+ {
+ self::$adapter->getDatabase()->resetCounter();
+ }
+
+ /**
+ * Returns the number of SQL queries processed.
+ * This method returns the number of database queries that have
+ * been processed according to the database driver. You can use this
+ * to monitor the number of queries required to render a page.
+ *
+ * Usage:
+ *
+ *
+ * echo R::getQueryCount() . ' queries processed.';
+ *
+ *
+ * @return integer
+ */
+ public static function getQueryCount()
+ {
+ return self::$adapter->getDatabase()->getQueryCount();
+ }
+
+ /**
+ * Returns the current logger instance being used by the
+ * database object.
+ *
+ * @return Logger|NULL
+ */
+ public static function getLogger()
+ {
+ return self::$adapter->getDatabase()->getLogger();
+ }
+
+ /**
+ * @deprecated
+ */
+ public static function setAutoResolve( $automatic = TRUE ){}
+
+ /**
+ * Toggles 'partial bean mode'. If this mode has been
+ * selected the repository will only update the fields of a bean that
+ * have been changed rather than the entire bean.
+ * Pass the value TRUE to select 'partial mode' for all beans.
+ * Pass the value FALSE to disable 'partial mode'.
+ * Pass an array of bean types if you wish to use partial mode only
+ * for some types.
+ * This method will return the previous value.
+ *
+ * @param boolean|string[] $yesNoBeans List of type names or 'all'
+ *
+ * @return boolean|string[]
+ */
+ public static function usePartialBeans( $yesNoBeans )
+ {
+ return self::$redbean->getCurrentRepository()->usePartialBeans( $yesNoBeans );
+ }
+
+ /**
+ * Exposes the result of the specified SQL query as a CSV file.
+ *
+ * Usage:
+ *
+ *
+ * R::csv( 'SELECT
+ * `name`,
+ * population
+ * FROM city
+ * WHERE region = :region ',
+ * array( ':region' => 'Denmark' ),
+ * array( 'city', 'population' ),
+ * '/tmp/cities.csv'
+ * );
+ *
+ *
+ * The command above will select all cities in Denmark
+ * and create a CSV with columns 'city' and 'population' and
+ * populate the cells under these column headers with the
+ * names of the cities and the population numbers respectively.
+ *
+ * @param string $sql SQL query to expose result of
+ * @param array $bindings parameter bindings
+ * @param array $columns column headers for CSV file
+ * @param string $path path to save CSV file to
+ * @param boolean $output TRUE to output CSV directly using readfile
+ * @param array $options delimiter, quote and escape character respectively
+ *
+ * @return void
+ */
+ public static function csv( $sql = '', $bindings = array(), $columns = NULL, $path = '/tmp/redexport_%s.csv', $output = TRUE )
+ {
+ $quickExport = new QuickExport( self::$toolbox );
+ $quickExport->csv( $sql, $bindings, $columns, $path, $output );
+ }
+
+ /**
+ * MatchUp is a powerful productivity boosting method that can replace simple control
+ * scripts with a single RedBeanPHP command. Typically, matchUp() is used to
+ * replace login scripts, token generation scripts and password reset scripts.
+ * The MatchUp method takes a bean type, an SQL query snippet (starting at the WHERE clause),
+ * SQL bindings, a pair of task arrays and a bean reference.
+ *
+ * If the first 3 parameters match a bean, the first task list will be considered,
+ * otherwise the second one will be considered. On consideration, each task list,
+ * an array of keys and values will be executed. Every key in the task list should
+ * correspond to a bean property while every value can either be an expression to
+ * be evaluated or a closure (PHP 5.3+). After applying the task list to the bean
+ * it will be stored. If no bean has been found, a new bean will be dispensed.
+ *
+ * This method will return TRUE if the bean was found and FALSE if not AND
+ * there was a NOT-FOUND task list. If no bean was found AND there was also
+ * no second task list, NULL will be returned.
+ *
+ * To obtain the bean, pass a variable as the sixth parameter.
+ * The function will put the matching bean in the specified variable.
+ *
+ * @param string $type type of bean you're looking for
+ * @param string $sql SQL snippet (starting at the WHERE clause, omit WHERE-keyword)
+ * @param array $bindings array of parameter bindings for SQL snippet
+ * @param array $onFoundDo task list to be considered on finding the bean
+ * @param array $onNotFoundDo task list to be considered on NOT finding the bean
+ * @param OODBBean &$bean reference to obtain the found bean
+ *
+ * @return bool|NULL
+ */
+ public static function matchUp( $type, $sql, $bindings = array(), $onFoundDo = NULL, $onNotFoundDo = NULL, &$bean = NULL ) {
+ $matchUp = new MatchUp( self::$toolbox );
+ return $matchUp->matchUp( $type, $sql, $bindings, $onFoundDo, $onNotFoundDo, $bean );
+ }
+
+ /**
+ * @deprecated
+ *
+ * Returns an instance of the Look Helper class.
+ * The instance will be configured with the current toolbox.
+ *
+ * In previous versions of RedBeanPHP you had to use:
+ * R::getLook()->look() instead of R::look(). However to improve useability of the
+ * library the look() function can now directly be invoked from the facade.
+ *
+ * For more details regarding the Look functionality, please consult R::look().
+ * @see Facade::look
+ * @see Look::look
+ *
+ * @return Look
+ */
+ public static function getLook()
+ {
+ return new Look( self::$toolbox );
+ }
+
+ /**
+ * Takes an full SQL query with optional bindings, a series of keys, a template
+ * and optionally a filter function and glue and assembles a view from all this.
+ * This is the fastest way from SQL to view. Typically this function is used to
+ * generate pulldown (select tag) menus with options queried from the database.
+ *
+ * Usage:
+ *
+ *
+ * $htmlPulldown = R::look(
+ * 'SELECT * FROM color WHERE value != ? ORDER BY value ASC',
+ * [ 'g' ],
+ * [ 'value', 'name' ],
+ * '',
+ * 'strtoupper',
+ * "\n"
+ * );
+ *
+ *
+ * The example above creates an HTML fragment like this:
+ *
+ *
+ *
+ *
+ * to pick a color from a palette. The HTML fragment gets constructed by
+ * an SQL query that selects all colors that do not have value 'g' - this
+ * excludes green. Next, the bean properties 'value' and 'name' are mapped to the
+ * HTML template string, note that the order here is important. The mapping and
+ * the HTML template string follow vsprintf-rules. All property values are then
+ * passed through the specified filter function 'strtoupper' which in this case
+ * is a native PHP function to convert strings to uppercase characters only.
+ * Finally the resulting HTML fragment strings are glued together using a
+ * newline character specified in the last parameter for readability.
+ *
+ * In previous versions of RedBeanPHP you had to use:
+ * R::getLook()->look() instead of R::look(). However to improve useability of the
+ * library the look() function can now directly be invoked from the facade.
+ *
+ * @param string $sql query to execute
+ * @param array $bindings parameters to bind to slots mentioned in query or an empty array
+ * @param array $keys names in result collection to map to template
+ * @param string $template HTML template to fill with values associated with keys, use printf notation (i.e. %s)
+ * @param callable $filter function to pass values through (for translation for instance)
+ * @param string $glue optional glue to use when joining resulting strings
+ *
+ * @return string
+ */
+ public static function look( $sql, $bindings = array(), $keys = array( 'selected', 'id', 'name' ), $template = '', $filter = 'trim', $glue = '' )
+ {
+ return self::getLook()->look( $sql, $bindings, $keys, $template, $filter, $glue );
+ }
+
+ /**
+ * Calculates a diff between two beans (or arrays of beans).
+ * The result of this method is an array describing the differences of the second bean compared to
+ * the first, where the first bean is taken as reference. The array is keyed by type/property, id and property name, where
+ * type/property is either the type (in case of the root bean) or the property of the parent bean where the type resides.
+ * The diffs are mainly intended for logging, you cannot apply these diffs as patches to other beans.
+ * However this functionality might be added in the future.
+ *
+ * The keys of the array can be formatted using the $format parameter.
+ * A key will be composed of a path (1st), id (2nd) and property (3rd).
+ * Using printf-style notation you can determine the exact format of the key.
+ * The default format will look like:
+ *
+ * 'book.1.title' => array( , )
+ *
+ * If you only want a simple diff of one bean and you don't care about ids,
+ * you might pass a format like: '%1$s.%3$s' which gives:
+ *
+ * 'book.1.title' => array( , )
+ *
+ * The filter parameter can be used to set filters, it should be an array
+ * of property names that have to be skipped. By default this array is filled with
+ * two strings: 'created' and 'modified'.
+ *
+ * @param OODBBean|OODBBean[] $bean reference beans
+ * @param OODBBean|OODBBean[] $other beans to compare
+ * @param array $filters names of properties of all beans to skip
+ * @param string $pattern the format of the key, defaults to '%s.%s.%s'
+ *
+ * @return array
+ */
+ public static function diff( $bean, $other, $filters = array( 'created', 'modified' ), $pattern = '%s.%s.%s' )
+ {
+ $diff = new Diff( self::$toolbox );
+ return $diff->diff( $bean, $other, $filters, $pattern );
+ }
+
+ /**
+ * The gentleman's way to register a RedBeanPHP ToolBox instance
+ * with the facade. Stores the toolbox in the static toolbox
+ * registry of the facade class. This allows for a neat and
+ * explicit way to register a toolbox.
+ *
+ * @param string $key key to store toolbox instance under
+ * @param ToolBox $toolbox toolbox to register
+ *
+ * @return void
+ */
+ public static function addToolBoxWithKey( $key, ToolBox $toolbox )
+ {
+ self::$toolboxes[$key] = $toolbox;
+ }
+
+ /**
+ * The gentleman's way to remove a RedBeanPHP ToolBox instance
+ * from the facade. Removes the toolbox identified by
+ * the specified key in the static toolbox
+ * registry of the facade class. This allows for a neat and
+ * explicit way to remove a toolbox.
+ * Returns TRUE if the specified toolbox was found and removed.
+ * Returns FALSE otherwise.
+ *
+ * @param string $key identifier of the toolbox to remove
+ *
+ * @return boolean
+ */
+ public static function removeToolBoxByKey( $key )
+ {
+ if ( !array_key_exists( $key, self::$toolboxes ) ) {
+ return FALSE;
+ }
+ unset( self::$toolboxes[$key] );
+ return TRUE;
+ }
+
+ /**
+ * Returns the toolbox associated with the specified key.
+ *
+ * @param string $key key to store toolbox instance under
+ *
+ * @return ToolBox|NULL
+ */
+ public static function getToolBoxByKey( $key )
+ {
+ if ( !array_key_exists( $key, self::$toolboxes ) ) {
+ return NULL;
+ }
+ return self::$toolboxes[$key];
+ }
+
+ /**
+ * Toggles JSON column features.
+ * Invoking this method with boolean TRUE causes 2 JSON features to be enabled.
+ * Beans will automatically JSONify any array that's not in a list property and
+ * the Query Writer (if capable) will attempt to create a JSON column for strings that
+ * appear to contain JSON.
+ *
+ * Feature #1:
+ * AQueryWriter::useJSONColumns
+ *
+ * Toggles support for automatic generation of JSON columns.
+ * Using JSON columns means that strings containing JSON will
+ * cause the column to be created (not modified) as a JSON column.
+ * However it might also trigger exceptions if this means the DB attempts to
+ * convert a non-json column to a JSON column.
+ *
+ * Feature #2:
+ * OODBBean::convertArraysToJSON
+ *
+ * Toggles array to JSON conversion. If set to TRUE any array
+ * set to a bean property that's not a list will be turned into
+ * a JSON string. Used together with AQueryWriter::useJSONColumns this
+ * extends the data type support for JSON columns.
+ *
+ * So invoking this method is the same as:
+ *
+ *
+ * AQueryWriter::useJSONColumns( $flag );
+ * OODBBean::convertArraysToJSON( $flag );
+ *
+ *
+ * Unlike the methods above, that return the previous state, this
+ * method does not return anything (void).
+ *
+ * @param boolean $flag feature flag (either TRUE or FALSE)
+ *
+ * @return void
+ */
+ public static function useJSONFeatures( $flag )
+ {
+ AQueryWriter::useJSONColumns( $flag );
+ OODBBean::convertArraysToJSON( $flag );
+ }
+
+ /**
+ * Given a bean and an optional SQL snippet,
+ * this method will return the bean together with all
+ * child beans in a hierarchically structured
+ * bean table.
+ *
+ * @note that not all database support this functionality. You'll need
+ * at least MariaDB 10.2.2 or Postgres. This method does not include
+ * a warning mechanism in case your database does not support this
+ * functionality.
+ *
+ * @param OODBBean $bean bean to find children of
+ * @param string|NULL $sql optional SQL snippet
+ * @param array $bindings SQL snippet parameter bindings
+ *
+ * @return OODBBean[]
+ */
+ public static function children( OODBBean $bean, $sql = NULL, $bindings = array() )
+ {
+ return self::$tree->children( $bean, $sql, $bindings );
+ }
+
+ /**
+ * Given a bean and an optional SQL snippet,
+ * this method will count all child beans in a hierarchically structured
+ * bean table.
+ *
+ * @note that not all database support this functionality. You'll need
+ * at least MariaDB 10.2.2 or Postgres. This method does not include
+ * a warning mechanism in case your database does not support this
+ * functionality.
+ *
+ * @note:
+ * You are allowed to use named parameter bindings as well as
+ * numeric parameter bindings (using the question mark notation).
+ * However, you can not mix. Also, if using named parameter bindings,
+ * parameter binding key ':slot0' is reserved for the ID of the bean
+ * and used in the query.
+ *
+ * @note:
+ * By default, if no select is given or select=TRUE this method will subtract 1 of
+ * the total count to omit the starting bean. If you provide your own select,
+ * this method assumes you take control of the resulting total yourself since
+ * it cannot 'predict' what or how you are trying to 'count'.
+ *
+ * @param OODBBean $bean bean to find children of
+ * @param string|NULL $sql optional SQL snippet
+ * @param array $bindings SQL snippet parameter bindings
+ * @param string|boolean $select select snippet to use (advanced, optional, see QueryWriter::queryRecursiveCommonTableExpression)
+ *
+ * @return int
+ */
+ public static function countChildren( OODBBean $bean, $sql = NULL, $bindings = array(), $select = QueryWriter::C_CTE_SELECT_COUNT )
+ {
+ return self::$tree->countChildren( $bean, $sql, $bindings, $select );
+ }
+
+ /**
+ * Given a bean and an optional SQL snippet,
+ * this method will count all parent beans in a hierarchically structured
+ * bean table.
+ *
+ * @note that not all database support this functionality. You'll need
+ * at least MariaDB 10.2.2 or Postgres. This method does not include
+ * a warning mechanism in case your database does not support this
+ * functionality.
+ *
+ * @note:
+ * You are allowed to use named parameter bindings as well as
+ * numeric parameter bindings (using the question mark notation).
+ * However, you can not mix. Also, if using named parameter bindings,
+ * parameter binding key ':slot0' is reserved for the ID of the bean
+ * and used in the query.
+ *
+ * @note:
+ * By default, if no select is given or select=TRUE this method will subtract 1 of
+ * the total count to omit the starting bean. If you provide your own select,
+ * this method assumes you take control of the resulting total yourself since
+ * it cannot 'predict' what or how you are trying to 'count'.
+ *
+ * @param OODBBean $bean bean to find children of
+ * @param string|NULL $sql optional SQL snippet
+ * @param array $bindings SQL snippet parameter bindings
+ * @param string|boolean $select select snippet to use (advanced, optional, see QueryWriter::queryRecursiveCommonTableExpression)
+ *
+ * @return int
+ */
+ public static function countParents( OODBBean $bean, $sql = NULL, $bindings = array(), $select = QueryWriter::C_CTE_SELECT_COUNT )
+ {
+ return self::$tree->countParents( $bean, $sql, $bindings, $select );
+ }
+
+ /**
+ * Given a bean and an optional SQL snippet,
+ * this method will return the bean along with all parent beans
+ * in a hierarchically structured bean table.
+ *
+ * @note that not all database support this functionality. You'll need
+ * at least MariaDB 10.2.2 or Postgres. This method does not include
+ * a warning mechanism in case your database does not support this
+ * functionality.
+ *
+ * @param OODBBean $bean bean to find parents of
+ * @param string|NULL $sql optional SQL snippet
+ * @param array $bindings SQL snippet parameter bindings
+ *
+ * @return OODBBean[]
+ */
+ public static function parents( OODBBean $bean, $sql = NULL, $bindings = array() )
+ {
+ return self::$tree->parents( $bean, $sql, $bindings );
+ }
+
+ /**
+ * Toggles support for nuke().
+ * Can be used to turn off the nuke() feature for security reasons.
+ * Returns the old flag value.
+ *
+ * @param boolean $flag TRUE or FALSE
+ *
+ * @return boolean
+ */
+ public static function noNuke( $yesNo ) {
+ return AQueryWriter::forbidNuke( $yesNo );
+ }
+
+ /**
+ * Globally available service method for RedBeanPHP.
+ * Converts a snake cased string to a camel cased string.
+ * If the parameter is an array, the keys will be converted.
+ *
+ * @param string|array $snake snake_cased string to convert to camelCase
+ * @param boolean $dolphin exception for Ids - (bookId -> bookID)
+ * too complicated for the human mind, only dolphins can understand this
+ *
+ * @return string|array
+ */
+ public static function camelfy( $snake, $dolphin = false )
+ {
+ if ( is_array( $snake ) ) {
+ $newArray = array();
+ foreach( $snake as $key => $value ) {
+ $newKey = self::camelfy( $key, $dolphin );
+ if ( is_array( $value ) ) {
+ $value = self::camelfy( $value, $dolphin );
+ }
+ $newArray[ $newKey ] = $value;
+ }
+ return $newArray;
+ }
+ return AQueryWriter::snakeCamel( $snake, $dolphin );
+ }
+
+ /**
+ * Globally available service method for RedBeanPHP.
+ * Converts a camel cased string to a snake cased string.
+ * If the parameter is an array, the keys will be converted.
+ *
+ * @param string|array $camel camelCased string to convert to snake case
+ *
+ * @return string|array
+ */
+ public static function uncamelfy( $camel )
+ {
+ if ( is_array( $camel ) ) {
+ $newArray = array();
+ foreach( $camel as $key => $value ) {
+ $newKey = self::uncamelfy( $key );
+ if ( is_array( $value ) ) {
+ $value = self::uncamelfy( $value );
+ }
+ $newArray[ $newKey ] = $value;
+ }
+ return $newArray;
+ }
+ return AQueryWriter::camelsSnake( $camel );
+ }
+
+ /**
+ * Selects the feature set you want as specified by
+ * the label.
+ *
+ * Usage:
+ *
+ *
+ * R::useFeatureSet( 'novice/latest' );
+ *
+ *
+ * @param string $label label
+ *
+ * @return void
+ */
+ public static function useFeatureSet( $label ) {
+ return Feature::feature($label);
+ }
+
+ /**
+ * Dynamically extends the facade with a plugin.
+ * Using this method you can register your plugin with the facade and then
+ * use the plugin by invoking the name specified plugin name as a method on
+ * the facade.
+ *
+ * Usage:
+ *
+ *
+ * R::ext( 'makeTea', function() { ... } );
+ *
+ *
+ * Now you can use your makeTea plugin like this:
+ *
+ *
+ * R::makeTea();
+ *
+ *
+ * @param string $pluginName name of the method to call the plugin
+ * @param callable $callable a PHP callable
+ *
+ * @return void
+ */
+ public static function ext( $pluginName, $callable )
+ {
+ if ( !preg_match( '#^[a-zA-Z_][a-zA-Z0-9_]*$#', $pluginName ) ) {
+ throw new RedException( 'Plugin name may only contain alphanumeric characters and underscores and cannot start with a number.' );
+ }
+ self::$plugins[$pluginName] = $callable;
+ }
+
+ /**
+ * Call static for use with dynamic plugins. This magic method will
+ * intercept static calls and route them to the specified plugin.
+ *
+ * @param string $pluginName name of the plugin
+ * @param array $params list of arguments to pass to plugin method
+ *
+ * @return mixed
+ */
+ public static function __callStatic( $pluginName, $params )
+ {
+ if ( !isset( self::$plugins[$pluginName] ) ) {
+ if ( !preg_match( '#^[a-zA-Z_][a-zA-Z0-9_]*$#', $pluginName ) ) {
+ throw new RedException( 'Plugin name may only contain alphanumeric characters and underscores and cannot start with a number.' );
+ }
+ throw new RedException( 'Plugin \''.$pluginName.'\' does not exist, add this plugin using: R::ext(\''.$pluginName.'\')' );
+ }
+ return call_user_func_array( self::$plugins[$pluginName], $params );
+ }
+}
+
+}
+
+namespace RedBeanPHP {
+
+use RedBeanPHP\ToolBox as ToolBox;
+use RedBeanPHP\AssociationManager as AssociationManager;
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
+
+/**
+ * Duplication Manager
+ * The Duplication Manager creates deep copies from beans, this means
+ * it can duplicate an entire bean hierarchy. You can use this feature to
+ * implement versioning for instance. Because duplication and exporting are
+ * closely related this class is also used to export beans recursively
+ * (i.e. we make a duplicate and then convert to array). This class allows
+ * you to tune the duplication process by specifying filters determining
+ * which relations to take into account and by specifying tables
+ * (in which case no reflective queries have to be issued thus improving
+ * performance). This class also hosts the Camelfy function used to
+ * reformat the keys of an array, this method is publicly available and
+ * used internally by exportAll().
+ *
+ * @file RedBeanPHP/DuplicationManager.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class DuplicationManager
+{
+ /**
+ * @var ToolBox
+ */
+ protected $toolbox;
+
+ /**
+ * @var AssociationManager
+ */
+ protected $associationManager;
+
+ /**
+ * @var OODB
+ */
+ protected $redbean;
+
+ /**
+ * @var array
+ */
+ protected $tables = array();
+
+ /**
+ * @var array
+ */
+ protected $columns = array();
+
+ /**
+ * @var array
+ */
+ protected $filters = array();
+
+ /**
+ * @var boolean
+ */
+ protected $cacheTables = FALSE;
+
+ /**
+ * @var boolean
+ */
+ protected $copyMeta = FALSE;
+
+ /**
+ * Copies the shared beans in a bean, i.e. all the sharedBean-lists.
+ *
+ * @param OODBBean $copy target bean to copy lists to
+ * @param string $shared name of the shared list
+ * @param array $beans array with shared beans to copy
+ *
+ * @return void
+ */
+ private function copySharedBeans( OODBBean $copy, $shared, $beans )
+ {
+ $copy->$shared = array();
+
+ foreach ( $beans as $subBean ) {
+ array_push( $copy->$shared, $subBean );
+ }
+ }
+
+ /**
+ * Copies the own beans in a bean, i.e. all the ownBean-lists.
+ * Each bean in the own-list belongs exclusively to its owner so
+ * we need to invoke the duplicate method again to duplicate each bean here.
+ *
+ * @param OODBBean $copy target bean to copy lists to
+ * @param string $owned name of the own list
+ * @param array $beans array with shared beans to copy
+ * @param array $trail array with former beans to detect recursion
+ * @param boolean $preserveIDs TRUE means preserve IDs, for export only
+ *
+ * @return void
+ */
+ private function copyOwnBeans( OODBBean $copy, $owned, $beans, $trail, $preserveIDs )
+ {
+ $copy->$owned = array();
+ foreach ( $beans as $subBean ) {
+ array_push( $copy->$owned, $this->duplicate( $subBean, $trail, $preserveIDs ) );
+ }
+ }
+
+ /**
+ * Creates a copy of bean $bean and copies all primitive properties (not lists)
+ * and the parents beans to the newly created bean. Also sets the ID of the bean
+ * to 0.
+ *
+ * @param OODBBean $bean bean to copy
+ *
+ * @return OODBBean
+ */
+ private function createCopy( OODBBean $bean )
+ {
+ $type = $bean->getMeta( 'type' );
+
+ $copy = $this->redbean->dispense( $type );
+ $copy->setMeta( 'sys.dup-from-id', $bean->id );
+ $copy->setMeta( 'sys.old-id', $bean->id );
+ $copy->importFrom( $bean );
+ if ($this->copyMeta) $copy->copyMetaFrom($bean);
+ $copy->id = 0;
+
+ return $copy;
+ }
+
+ /**
+ * Generates a key from the bean type and its ID and determines if the bean
+ * occurs in the trail, if not the bean will be added to the trail.
+ * Returns TRUE if the bean occurs in the trail and FALSE otherwise.
+ *
+ * @param array $trail list of former beans
+ * @param OODBBean $bean currently selected bean
+ *
+ * @return boolean
+ */
+ private function inTrailOrAdd( &$trail, OODBBean $bean )
+ {
+ $type = $bean->getMeta( 'type' );
+ $key = $type . $bean->getID();
+
+ if ( isset( $trail[$key] ) ) {
+ return TRUE;
+ }
+
+ $trail[$key] = $bean;
+
+ return FALSE;
+ }
+
+ /**
+ * Given the type name of a bean this method returns the canonical names
+ * of the own-list and the shared-list properties respectively.
+ * Returns a list with two elements: name of the own-list, and name
+ * of the shared list.
+ *
+ * @param string $typeName bean type name
+ *
+ * @return array
+ */
+ private function getListNames( $typeName )
+ {
+ $owned = 'own' . ucfirst( $typeName );
+ $shared = 'shared' . ucfirst( $typeName );
+
+ return array( $owned, $shared );
+ }
+
+ /**
+ * Determines whether the bean has an own list based on
+ * schema inspection from realtime schema or cache.
+ *
+ * @param string $type bean type to get list for
+ * @param string $target type of list you want to detect
+ *
+ * @return boolean
+ */
+ protected function hasOwnList( $type, $target )
+ {
+ return isset( $this->columns[$target][$type . '_id'] );
+ }
+
+ /**
+ * Determines whether the bea has a shared list based on
+ * schema inspection from realtime schema or cache.
+ *
+ * @param string $type bean type to get list for
+ * @param string $target type of list you are looking for
+ *
+ * @return boolean
+ */
+ protected function hasSharedList( $type, $target )
+ {
+ return in_array( AQueryWriter::getAssocTableFormat( array( $type, $target ) ), $this->tables );
+ }
+
+ /**
+ * @see DuplicationManager::dup
+ *
+ * @param OODBBean $bean bean to be copied
+ * @param array $trail trail to prevent infinite loops
+ * @param boolean $preserveIDs preserve IDs
+ *
+ * @return OODBBean
+ */
+ protected function duplicate( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
+ {
+ if ( $this->inTrailOrAdd( $trail, $bean ) ) return $bean;
+
+ $type = $bean->getMeta( 'type' );
+
+ $copy = $this->createCopy( $bean );
+ foreach ( $this->tables as $table ) {
+
+ if ( !empty( $this->filters ) ) {
+ if ( !in_array( $table, $this->filters ) ) continue;
+ }
+
+ list( $owned, $shared ) = $this->getListNames( $table );
+
+ if ( $this->hasSharedList( $type, $table ) ) {
+ if ( $beans = $bean->$shared ) {
+ $this->copySharedBeans( $copy, $shared, $beans );
+ }
+ } elseif ( $this->hasOwnList( $type, $table ) ) {
+ if ( $beans = $bean->$owned ) {
+ $this->copyOwnBeans( $copy, $owned, $beans, $trail, $preserveIDs );
+ }
+
+ $copy->setMeta( 'sys.shadow.' . $owned, NULL );
+ }
+
+ $copy->setMeta( 'sys.shadow.' . $shared, NULL );
+ }
+
+ $copy->id = ( $preserveIDs ) ? $bean->id : $copy->id;
+
+ return $copy;
+ }
+
+ /**
+ * Constructor,
+ * creates a new instance of DupManager.
+ *
+ * @param ToolBox $toolbox
+ */
+ public function __construct( ToolBox $toolbox )
+ {
+ $this->toolbox = $toolbox;
+ $this->redbean = $toolbox->getRedBean();
+ $this->associationManager = $this->redbean->getAssociationManager();
+ }
+
+ /**
+ * Recursively turns the keys of an array into
+ * camelCase.
+ *
+ * @param array $array array to camelize
+ * @param boolean $dolphinMode whether you want the exception for IDs.
+ *
+ * @return array
+ */
+ public function camelfy( $array, $dolphinMode = FALSE ) {
+ $newArray = array();
+ foreach( $array as $key => $element ) {
+ $newKey = preg_replace_callback( '/_(\w)/', function( $matches ){
+ return strtoupper( $matches[1] );
+ }, $key);
+
+ if ( $dolphinMode ) {
+ $newKey = preg_replace( '/(\w)Id$/', '$1ID', $newKey );
+ }
+
+ $newArray[$newKey] = ( is_array($element) ) ? $this->camelfy( $element, $dolphinMode ) : $element;
+ }
+ return $newArray;
+ }
+
+ /**
+ * For better performance you can pass the tables in an array to this method.
+ * If the tables are available the duplication manager will not query them so
+ * this might be beneficial for performance.
+ *
+ * This method allows two array formats:
+ *
+ *
+ * array( TABLE1, TABLE2 ... )
+ *
+ *
+ * or
+ *
+ *
+ * array( TABLE1 => array( COLUMN1, COLUMN2 ... ) ... )
+ *
+ *
+ * @param array $tables a table cache array
+ *
+ * @return void
+ */
+ public function setTables( $tables )
+ {
+ foreach ( $tables as $key => $value ) {
+ if ( is_numeric( $key ) ) {
+ $this->tables[] = $value;
+ } else {
+ $this->tables[] = $key;
+ $this->columns[$key] = $value;
+ }
+ }
+
+ $this->cacheTables = TRUE;
+ }
+
+ /**
+ * Returns a schema array for cache.
+ * You can use the return value of this method as a cache,
+ * store it in RAM or on disk and pass it to setTables later.
+ *
+ * @return array
+ */
+ public function getSchema()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Indicates whether you want the duplication manager to cache the database schema.
+ * If this flag is set to TRUE the duplication manager will query the database schema
+ * only once. Otherwise the duplicationmanager will, by default, query the schema
+ * every time a duplication action is performed (dup()).
+ *
+ * @param boolean $yesNo TRUE to use caching, FALSE otherwise
+ *
+ * @return void
+ */
+ public function setCacheTables( $yesNo )
+ {
+ $this->cacheTables = $yesNo;
+ }
+
+ /**
+ * A filter array is an array with table names.
+ * By setting a table filter you can make the duplication manager only take into account
+ * certain bean types. Other bean types will be ignored when exporting or making a
+ * deep copy. If no filters are set all types will be taking into account, this is
+ * the default behavior.
+ *
+ * @param array $filters list of tables to be filtered
+ *
+ * @return void
+ */
+ public function setFilters( $filters )
+ {
+ if ( !is_array( $filters ) ) {
+ $filters = array( $filters );
+ }
+
+ $this->filters = $filters;
+ }
+
+ /**
+ * Makes a copy of a bean. This method makes a deep copy
+ * of the bean.The copy will have the following features.
+ * - All beans in own-lists will be duplicated as well
+ * - All references to shared beans will be copied but not the shared beans themselves
+ * - All references to parent objects (_id fields) will be copied but not the parents themselves
+ * In most cases this is the desired scenario for copying beans.
+ * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
+ * (i.e. one that already has been processed) the ID of the bean will be returned.
+ * This should not happen though.
+ *
+ * Note:
+ * This function does a reflectional database query so it may be slow.
+ *
+ * Note:
+ * this function actually passes the arguments to a protected function called
+ * duplicate() that does all the work. This method takes care of creating a clone
+ * of the bean to avoid the bean getting tainted (triggering saving when storing it).
+ *
+ * @param OODBBean $bean bean to be copied
+ * @param array $trail for internal usage, pass array()
+ * @param boolean $preserveIDs for internal usage
+ *
+ * @return OODBBean
+ */
+ public function dup( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
+ {
+ if ( !count( $this->tables ) ) {
+ $this->tables = $this->toolbox->getWriter()->getTables();
+ }
+
+ if ( !count( $this->columns ) ) {
+ foreach ( $this->tables as $table ) {
+ $this->columns[$table] = $this->toolbox->getWriter()->getColumns( $table );
+ }
+ }
+
+ $rs = $this->duplicate( ( clone $bean ), $trail, $preserveIDs );
+
+ if ( !$this->cacheTables ) {
+ $this->tables = array();
+ $this->columns = array();
+ }
+
+ return $rs;
+ }
+
+ /**
+ * Exports a collection of beans recursively.
+ * This method will export an array of beans in the first argument to a
+ * set of arrays. This can be used to send JSON or XML representations
+ * of bean hierarchies to the client.
+ *
+ * For every bean in the array this method will export:
+ *
+ * - contents of the bean
+ * - all own bean lists (recursively)
+ * - all shared beans (but not THEIR own lists)
+ *
+ * If the second parameter is set to TRUE the parents of the beans in the
+ * array will be exported as well (but not THEIR parents).
+ *
+ * The third parameter can be used to provide a white-list array
+ * for filtering. This is an array of strings representing type names,
+ * only the type names in the filter list will be exported.
+ *
+ * The fourth parameter can be used to change the keys of the resulting
+ * export arrays. The default mode is 'snake case' but this leaves the
+ * keys as-is, because 'snake' is the default case style used by
+ * RedBeanPHP in the database. You can set this to 'camel' for
+ * camel cased keys or 'dolphin' (same as camelcase but id will be
+ * converted to ID instead of Id).
+ *
+ * @param array|OODBBean $beans beans to be exported
+ * @param boolean $parents also export parents
+ * @param array $filters only these types (whitelist)
+ * @param string $caseStyle case style identifier
+ * @param boolean $meta export meta data as well
+ *
+ * @return array
+ */
+ public function exportAll( $beans, $parents = FALSE, $filters = array(), $caseStyle = 'snake', $meta = FALSE)
+ {
+ $array = array();
+ if ( !is_array( $beans ) ) {
+ $beans = array( $beans );
+ }
+ $this->copyMeta = $meta;
+ foreach ( $beans as $bean ) {
+ $this->setFilters( $filters );
+ $duplicate = $this->dup( $bean, array(), TRUE );
+ $array[] = $duplicate->export( $meta, $parents, FALSE, $filters );
+ }
+ if ( $caseStyle === 'camel' ) $array = $this->camelfy( $array );
+ if ( $caseStyle === 'dolphin' ) $array = $this->camelfy( $array, TRUE );
+ return $array;
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\RedException as RedException;
+
+/**
+ * Array Tool Helper
+ *
+ * This code was originally part of the facade, however it has
+ * been decided to remove unique features to service classes like
+ * this to make them available to developers not using the facade class.
+ *
+ * This is a helper or service class containing frequently used
+ * array functions for dealing with SQL queries.
+ *
+ * @file RedBeanPHP/Util/ArrayTool.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class ArrayTool
+{
+ /**
+ * Generates question mark slots for an array of values.
+ * Given an array and an optional template string this method
+ * will produce string containing parameter slots for use in
+ * an SQL query string.
+ *
+ * Usage:
+ *
+ *
+ * R::genSlots( array( 'a', 'b' ) );
+ *
+ *
+ * The statement in the example will produce the string:
+ * '?,?'.
+ *
+ * Another example, using a template string:
+ *
+ *
+ * R::genSlots( array('a', 'b'), ' IN( %s ) ' );
+ *
+ *
+ * The statement in the example will produce the string:
+ * ' IN( ?,? ) '.
+ *
+ * @param array $array array to generate question mark slots for
+ * @param string|NULL $template template to use
+ *
+ * @return string
+ */
+ public static function genSlots( $array, $template = NULL )
+ {
+ $str = count( $array ) ? implode( ',', array_fill( 0, count( $array ), '?' ) ) : '';
+ return ( is_null( $template ) || $str === '' ) ? $str : sprintf( $template, $str );
+ }
+
+ /**
+ * Flattens a multi dimensional bindings array for use with genSlots().
+ *
+ * Usage:
+ *
+ *
+ * R::flat( array( 'a', array( 'b' ), 'c' ) );
+ *
+ *
+ * produces an array like: [ 'a', 'b', 'c' ]
+ *
+ * @param array $array array to flatten
+ * @param array $result result array parameter (for recursion)
+ *
+ * @return array
+ */
+ public static function flat( $array, $result = array() )
+ {
+ foreach( $array as $value ) {
+ if ( is_array( $value ) ) $result = self::flat( $value, $result );
+ else $result[] = $value;
+ }
+ return $result;
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\RedException as RedException;
+
+/**
+ * Dispense Helper
+ *
+ * A helper class containing a dispense utility.
+ *
+ * @file RedBeanPHP/Util/DispenseHelper.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class DispenseHelper
+{
+ /**
+ * @var boolean
+ */
+ private static $enforceNamingPolicy = TRUE;
+
+ /**
+ * Sets the enforce naming policy flag. If set to
+ * TRUE the RedBeanPHP naming policy will be enforced.
+ * Otherwise it will not. Use at your own risk.
+ * Setting this to FALSE is not recommended.
+ *
+ * @param boolean $yesNo whether to enforce RB name policy
+ *
+ * @return void
+ */
+ public static function setEnforceNamingPolicy( $yesNo )
+ {
+ self::$enforceNamingPolicy = (boolean) $yesNo;
+ }
+
+ /**
+ * Checks whether the bean type conforms to the RedbeanPHP
+ * naming policy. This method will throw an exception if the
+ * type does not conform to the RedBeanPHP database column naming
+ * policy.
+ *
+ * The RedBeanPHP naming policy for beans states that valid
+ * bean type names contain only:
+ *
+ * - lowercase alphanumeric characters a-z
+ * - numbers 0-9
+ * - at least one character
+ *
+ * Although there are no restrictions on length, database
+ * specific implementations may apply further restrictions
+ * regarding the length of a table which means these restrictions
+ * also apply to bean types.
+ *
+ * The RedBeanPHP naming policy ensures that, without any
+ * configuration, the core functionalities work across many
+ * databases and operating systems, including those that are
+ * case insensitive or restricted to the ASCII character set.
+ *
+ * Although these restrictions can be bypassed, this is not
+ * recommended.
+ *
+ * @param string $type type of bean
+ *
+ * @return void
+ */
+ public static function checkType( $type )
+ {
+ if ( !preg_match( '/^[a-z0-9]+$/', $type ) ) {
+ throw new RedException( 'Invalid bean type: ' . $type );
+ }
+ }
+
+ /**
+ * Dispenses a new RedBean OODB Bean for use with
+ * the rest of the methods. RedBeanPHP thinks in beans, the bean is the
+ * primary way to interact with RedBeanPHP and the database managed by
+ * RedBeanPHP. To load, store and delete data from the database using RedBeanPHP
+ * you exchange these RedBeanPHP OODB Beans. The only exception to this rule
+ * are the raw query methods like R::getCell() or R::exec() and so on.
+ * The dispense method is the 'preferred way' to create a new bean.
+ *
+ * Usage:
+ *
+ *
+ * $book = R::dispense( 'book' );
+ * $book->title = 'My Book';
+ * R::store( $book );
+ *
+ *
+ * This method can also be used to create an entire bean graph at once.
+ * Given an array with keys specifying the property names of the beans
+ * and a special _type key to indicate the type of bean, one can
+ * make the Dispense Helper generate an entire hierarchy of beans, including
+ * lists. To make dispense() generate a list, simply add a key like:
+ * ownXList or sharedXList where X is the type of beans it contains and
+ * a set its value to an array filled with arrays representing the beans.
+ * Note that, although the type may have been hinted at in the list name,
+ * you still have to specify a _type key for every bean array in the list.
+ * Note that, if you specify an array to generate a bean graph, the number
+ * parameter will be ignored.
+ *
+ * Usage:
+ *
+ *
+ * $book = R::dispense( [
+ * '_type' => 'book',
+ * 'title' => 'Gifted Programmers',
+ * 'author' => [ '_type' => 'author', 'name' => 'Xavier' ],
+ * 'ownPageList' => [ ['_type'=>'page', 'text' => '...'] ]
+ * ] );
+ *
+ *
+ * @param string|array $typeOrBeanArray type or bean array to import
+ * @param integer $num number of beans to dispense
+ * @param boolean $alwaysReturnArray if TRUE always returns the result as an array
+ *
+ * @return OODBBean|OODBBean[]
+ */
+ public static function dispense( OODB $oodb, $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE ) {
+
+ if ( is_array($typeOrBeanArray) ) {
+
+ if ( !isset( $typeOrBeanArray['_type'] ) ) {
+ $list = array();
+ foreach( $typeOrBeanArray as $beanArray ) {
+ if (
+ !( is_array( $beanArray )
+ && isset( $beanArray['_type'] ) ) ) {
+ throw new RedException( 'Invalid Array Bean' );
+ }
+ }
+ foreach( $typeOrBeanArray as $beanArray ) $list[] = self::dispense( $oodb, $beanArray );
+ return $list;
+ }
+
+ $import = $typeOrBeanArray;
+ $type = $import['_type'];
+ unset( $import['_type'] );
+ } else {
+ $type = $typeOrBeanArray;
+ }
+
+ if (self::$enforceNamingPolicy) self::checkType( $type );
+
+ $beanOrBeans = $oodb->dispense( $type, $num, $alwaysReturnArray );
+
+ if ( isset( $import ) ) {
+ $beanOrBeans->import( $import );
+ }
+
+ return $beanOrBeans;
+ }
+
+
+ /**
+ * Takes a comma separated list of bean types
+ * and dispenses these beans. For each type in the list
+ * you can specify the number of beans to be dispensed.
+ *
+ * Usage:
+ *
+ *
+ * list( $book, $page, $text ) = R::dispenseAll( 'book,page,text' );
+ *
+ *
+ * This will dispense a book, a page and a text. This way you can
+ * quickly dispense beans of various types in just one line of code.
+ *
+ * Usage:
+ *
+ *
+ * list($book, $pages) = R::dispenseAll('book,page*100');
+ *
+ *
+ * This returns an array with a book bean and then another array
+ * containing 100 page beans.
+ *
+ * @param OODB $oodb OODB
+ * @param string $order a description of the desired dispense order using the syntax above
+ * @param boolean $onlyArrays return only arrays even if amount < 2
+ *
+ * @return array
+ */
+ public static function dispenseAll( OODB $oodb, $order, $onlyArrays = FALSE )
+ {
+ $list = array();
+
+ foreach( explode( ',', $order ) as $order ) {
+ if ( strpos( $order, '*' ) !== FALSE ) {
+ list( $type, $amount ) = explode( '*', $order );
+ } else {
+ $type = $order;
+ $amount = 1;
+ }
+
+ $list[] = self::dispense( $oodb, $type, $amount, $onlyArrays );
+ }
+
+ return $list;
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\OODBBean as OODBBean;
+
+/**
+ * Dump helper
+ *
+ * This code was originally part of the facade, however it has
+ * been decided to remove unique features to service classes like
+ * this to make them available to developers not using the facade class.
+ *
+ * Dumps the contents of a bean in an array for
+ * debugging purposes.
+ *
+ * @file RedBeanPHP/Util/Dump.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Dump
+{
+ /**
+ * Dumps bean data to array.
+ * Given a one or more beans this method will
+ * return an array containing first part of the string
+ * representation of each item in the array.
+ *
+ * Usage:
+ *
+ *
+ * echo R::dump( $bean );
+ *
+ *
+ * The example shows how to echo the result of a simple
+ * dump. This will print the string representation of the
+ * specified bean to the screen, limiting the output per bean
+ * to 35 characters to improve readability. Nested beans will
+ * also be dumped.
+ *
+ * @param OODBBean|array $data either a bean or an array of beans
+ *
+ * @return array
+ */
+ public static function dump( $data )
+ {
+ $array = array();
+ if ( $data instanceof OODBBean ) {
+ $str = strval( $data );
+ if (strlen($str) > 35) {
+ $beanStr = substr( $str, 0, 35 ).'... ';
+ } else {
+ $beanStr = $str;
+ }
+ return $beanStr;
+ }
+ if ( is_array( $data ) ) {
+ foreach( $data as $key => $item ) {
+ $array[$key] = self::dump( $item );
+ }
+ }
+ return $array;
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\OODBBean as OODBBean;
+
+/**
+ * Multi Bean Loader Helper
+ *
+ * This code was originally part of the facade, however it has
+ * been decided to remove unique features to service classes like
+ * this to make them available to developers not using the facade class.
+ *
+ * This helper class offers limited support for one-to-one
+ * relations by providing a service to load a set of beans
+ * with different types and a common ID.
+ *
+ * @file RedBeanPHP/Util/MultiLoader.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class MultiLoader
+{
+ /**
+ * Loads multiple types of beans with the same ID.
+ * This might look like a strange method, however it can be useful
+ * for loading a one-to-one relation. In a typical 1-1 relation,
+ * you have two records sharing the same primary key.
+ * RedBeanPHP has only limited support for 1-1 relations.
+ * In general it is recommended to use 1-N for this.
+ *
+ * Usage:
+ *
+ *
+ * list( $author, $bio ) = R::loadMulti( 'author, bio', $id );
+ *
+ *
+ * @param OODB $oodb OODB object
+ * @param string|array $types the set of types to load at once
+ * @param mixed $id the common ID
+ *
+ * @return OODBBean[]
+ */
+ public static function load( OODB $oodb, $types, $id )
+ {
+ if ( is_string( $types ) ) $types = explode( ',', $types );
+ if ( !is_array( $types ) ) return array();
+ foreach ( $types as $k => $typeItem ) {
+ $types[$k] = $oodb->load( $typeItem, $id );
+ }
+ return $types;
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\RedException as RedException;
+use RedBeanPHP\Adapter as Adapter;
+
+/**
+ * Transaction Helper
+ *
+ * This code was originally part of the facade, however it has
+ * been decided to remove unique features to service classes like
+ * this to make them available to developers not using the facade class.
+ *
+ * Database transaction helper. This is a convenience class
+ * to perform a callback in a database transaction. This class
+ * contains a method to wrap your callback in a transaction.
+ *
+ * @file RedBeanPHP/Util/Transaction.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Transaction
+{
+ /**
+ * Wraps a transaction around a closure or string callback.
+ * If an Exception is thrown inside, the operation is automatically rolled back.
+ * If no Exception happens, it commits automatically.
+ * It also supports (simulated) nested transactions (that is useful when
+ * you have many methods that needs transactions but are unaware of
+ * each other).
+ *
+ * Example:
+ *
+ *
+ * $from = 1;
+ * $to = 2;
+ * $amount = 300;
+ *
+ * R::transaction(function() use($from, $to, $amount)
+ * {
+ * $accountFrom = R::load('account', $from);
+ * $accountTo = R::load('account', $to);
+ * $accountFrom->money -= $amount;
+ * $accountTo->money += $amount;
+ * R::store($accountFrom);
+ * R::store($accountTo);
+ * });
+ *
+ *
+ * @param Adapter $adapter Database Adapter providing transaction mechanisms.
+ * @param callable $callback Closure (or other callable) with the transaction logic
+ *
+ * @return mixed
+ */
+ public static function transaction( Adapter $adapter, $callback )
+ {
+ if ( !is_callable( $callback ) ) {
+ throw new RedException( 'R::transaction needs a valid callback.' );
+ }
+
+ static $depth = 0;
+ $result = null;
+ try {
+ if ( $depth == 0 ) {
+ $adapter->startTransaction();
+ }
+ $depth++;
+ $result = call_user_func( $callback ); //maintain 5.2 compatibility
+ $depth--;
+ if ( $depth == 0 ) {
+ $adapter->commit();
+ }
+ } catch ( \Exception $exception ) {
+ $depth--;
+ if ( $depth == 0 ) {
+ $adapter->rollback();
+ }
+ throw $exception;
+ }
+ return $result;
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\ToolBox as ToolBox;
+
+/**
+ * Quick Export Utility
+ *
+ * The Quick Export Utility Class provides functionality to easily
+ * expose the result of SQL queries as well-known formats like CSV.
+ *
+ * @file RedBeanPHP/Util/QuickExport.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class QuickExport
+{
+ /**
+ * @var ToolBox
+ */
+ protected $toolbox;
+
+ /**
+ * @var boolean
+ */
+ private static $test = FALSE;
+
+ /**
+ * Constructor.
+ * The Quick Export requires a toolbox.
+ *
+ * @param ToolBox $toolbox
+ */
+ public function __construct( ToolBox $toolbox )
+ {
+ $this->toolbox = $toolbox;
+ }
+
+ /**
+ * Makes csv() testable.
+ *
+ * @param string $name
+ * @param mixed $arg1
+ * @param boolean $arg2
+ *
+ * @return mixed
+ */
+ public static function operation( $name, $arg1, $arg2 = TRUE ) {
+ $out = '';
+ switch( $name ) {
+ case 'test':
+ self::$test = (boolean) $arg1;
+ break;
+ case 'header':
+ $out = ( self::$test ) ? $arg1 : header( $arg1, $arg2 );
+ break;
+ case 'readfile':
+ $out = ( self::$test ) ? file_get_contents( $arg1 ) : readfile( $arg1 );
+ break;
+ case 'exit':
+ $out = ( self::$test ) ? 'exit' : exit();
+ break;
+ }
+ return $out;
+ }
+
+ /**
+ * Exposes the result of the specified SQL query as a CSV file.
+ *
+ * Usage:
+ *
+ *
+ * R::csv( 'SELECT
+ * `name`,
+ * population
+ * FROM city
+ * WHERE region = :region ',
+ * array( ':region' => 'Denmark' ),
+ * array( 'city', 'population' ),
+ * '/tmp/cities.csv'
+ * );
+ *
+ *
+ * The command above will select all cities in Denmark
+ * and create a CSV with columns 'city' and 'population' and
+ * populate the cells under these column headers with the
+ * names of the cities and the population numbers respectively.
+ *
+ * @param string $sql SQL query to expose result of
+ * @param array $bindings parameter bindings
+ * @param array $columns column headers for CSV file
+ * @param string $path path to save CSV file to
+ * @param boolean $output TRUE to output CSV directly using readfile
+ * @param array $options delimiter, quote and escape character respectively
+ *
+ * @return string|NULL
+ */
+ public function csv( $sql = '', $bindings = array(), $columns = NULL, $path = '/tmp/redexport_%s.csv', $output = TRUE, $options = array(',','"','\\') )
+ {
+ list( $delimiter, $enclosure, $escapeChar ) = $options;
+ $path = sprintf( $path, date('Ymd_his') );
+ $handle = fopen( $path, 'w' );
+ if ($columns) if (PHP_VERSION_ID>=505040) fputcsv($handle, $columns, $delimiter, $enclosure, $escapeChar ); else fputcsv($handle, $columns, $delimiter, $enclosure );
+ $cursor = $this->toolbox->getDatabaseAdapter()->getCursor( $sql, $bindings );
+ while( $row = $cursor->getNextItem() ) {
+ if (PHP_VERSION_ID>=505040) fputcsv($handle, $row, $delimiter, $enclosure, $escapeChar ); else fputcsv($handle, $row, $delimiter, $enclosure );
+ }
+ fclose($handle);
+ if ( $output ) {
+ $file = basename($path);
+ $out = self::operation('header',"Pragma: public");
+ $out .= self::operation('header',"Expires: 0");
+ $out .= self::operation('header',"Cache-Control: must-revalidate, post-check=0, pre-check=0");
+ $out .= self::operation('header',"Cache-Control: private", FALSE );
+ $out .= self::operation('header',"Content-Type: text/csv");
+ $out .= self::operation('header',"Content-Disposition: attachment; filename={$file}" );
+ $out .= self::operation('header',"Content-Transfer-Encoding: binary");
+ $out .= self::operation('readfile',$path );
+ @unlink( $path );
+ self::operation('exit', FALSE);
+ return $out;
+ }
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\ToolBox as ToolBox;
+use RedBeanPHP\Finder;
+
+/**
+ * MatchUp Utility
+ *
+ * Tired of creating login systems and password-forget systems?
+ * MatchUp is an ORM-translation of these kind of problems.
+ * A matchUp is a match-and-update combination in terms of beans.
+ * Typically login related problems are all about a match and
+ * a conditional update.
+ *
+ * @file RedBeanPHP/Util/MatchUp.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class MatchUp
+{
+ /**
+ * @var Toolbox
+ */
+ protected $toolbox;
+
+ /**
+ * Constructor.
+ * The MatchUp class requires a toolbox
+ *
+ * @param ToolBox $toolbox
+ */
+ public function __construct( ToolBox $toolbox )
+ {
+ $this->toolbox = $toolbox;
+ }
+
+ /**
+ * MatchUp is a powerful productivity boosting method that can replace simple control
+ * scripts with a single RedBeanPHP command. Typically, matchUp() is used to
+ * replace login scripts, token generation scripts and password reset scripts.
+ * The MatchUp method takes a bean type, an SQL query snippet (starting at the WHERE clause),
+ * SQL bindings, a pair of task arrays and a bean reference.
+ *
+ * If the first 3 parameters match a bean, the first task list will be considered,
+ * otherwise the second one will be considered. On consideration, each task list,
+ * an array of keys and values will be executed. Every key in the task list should
+ * correspond to a bean property while every value can either be an expression to
+ * be evaluated or a closure (PHP 5.3+). After applying the task list to the bean
+ * it will be stored. If no bean has been found, a new bean will be dispensed.
+ *
+ * This method will return TRUE if the bean was found and FALSE if not AND
+ * there was a NOT-FOUND task list. If no bean was found AND there was also
+ * no second task list, NULL will be returned.
+ *
+ * To obtain the bean, pass a variable as the sixth parameter.
+ * The function will put the matching bean in the specified variable.
+ *
+ * Usage (this example resets a password in one go):
+ *
+ *
+ * $newpass = '1234';
+ * $didResetPass = R::matchUp(
+ * 'account', ' token = ? AND tokentime > ? ',
+ * [ $token, time()-100 ],
+ * [ 'pass' => $newpass, 'token' => '' ],
+ * NULL,
+ * $account );
+ *
+ *
+ * @param string $type type of bean you're looking for
+ * @param string $sql SQL snippet (starting at the WHERE clause, omit WHERE-keyword)
+ * @param array $bindings array of parameter bindings for SQL snippet
+ * @param array|NULL $onFoundDo task list to be considered on finding the bean
+ * @param array|NULL $onNotFoundDo task list to be considered on NOT finding the bean
+ * @param OODBBean|NULL &$bean reference to obtain the found bean
+ *
+ * @return bool|NULL
+ */
+ public function matchUp( $type, $sql, $bindings = array(), $onFoundDo = NULL, $onNotFoundDo = NULL, &$bean = NULL )
+ {
+ $finder = new Finder( $this->toolbox );
+ $oodb = $this->toolbox->getRedBean();
+ $bean = $finder->findOne( $type, $sql, $bindings );
+ if ( $bean && $onFoundDo ) {
+ foreach( $onFoundDo as $property => $value ) {
+ if ( function_exists('is_callable') && is_callable( $value ) ) {
+ $bean[$property] = call_user_func_array( $value, array( $bean ) );
+ } else {
+ $bean[$property] = $value;
+ }
+ }
+ $oodb->store( $bean );
+ return TRUE;
+ }
+ if ( $onNotFoundDo ) {
+ $bean = $oodb->dispense( $type );
+ foreach( $onNotFoundDo as $property => $value ) {
+ if ( function_exists('is_callable') && is_callable( $value ) ) {
+ $bean[$property] = call_user_func_array( $value, array( $bean ) );
+ } else {
+ $bean[$property] = $value;
+ }
+ }
+ $oodb->store( $bean );
+ return FALSE;
+ }
+ return NULL;
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\ToolBox as ToolBox;
+use RedBeanPHP\Finder;
+
+/**
+ * Look Utility
+ *
+ * The Look Utility class provides an easy way to generate
+ * tables and selects (pulldowns) from the database.
+ *
+ * @file RedBeanPHP/Util/Look.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Look
+{
+ /**
+ * @var Toolbox
+ */
+ protected $toolbox;
+
+ /**
+ * Constructor.
+ * The MatchUp class requires a toolbox
+ *
+ * @param ToolBox $toolbox
+ */
+ public function __construct( ToolBox $toolbox )
+ {
+ $this->toolbox = $toolbox;
+ }
+
+ /**
+ * Takes an full SQL query with optional bindings, a series of keys, a template
+ * and optionally a filter function and glue and assembles a view from all this.
+ * This is the fastest way from SQL to view. Typically this function is used to
+ * generate pulldown (select tag) menus with options queried from the database.
+ *
+ * Usage:
+ *
+ *
+ * $htmlPulldown = R::look(
+ * 'SELECT * FROM color WHERE value != ? ORDER BY value ASC',
+ * [ 'g' ],
+ * [ 'value', 'name' ],
+ * '',
+ * 'strtoupper',
+ * "\n"
+ * );
+ *
+ *
+ * The example above creates an HTML fragment like this:
+ *
+ *
+ *
+ *
+ * to pick a color from a palette. The HTML fragment gets constructed by
+ * an SQL query that selects all colors that do not have value 'g' - this
+ * excludes green. Next, the bean properties 'value' and 'name' are mapped to the
+ * HTML template string, note that the order here is important. The mapping and
+ * the HTML template string follow vsprintf-rules. All property values are then
+ * passed through the specified filter function 'strtoupper' which in this case
+ * is a native PHP function to convert strings to uppercase characters only.
+ * Finally the resulting HTML fragment strings are glued together using a
+ * newline character specified in the last parameter for readability.
+ *
+ * In previous versions of RedBeanPHP you had to use:
+ * R::getLook()->look() instead of R::look(). However to improve useability of the
+ * library the look() function can now directly be invoked from the facade.
+ *
+ * @param string $sql query to execute
+ * @param array $bindings parameters to bind to slots mentioned in query or an empty array
+ * @param array $keys names in result collection to map to template
+ * @param string $template HTML template to fill with values associated with keys, use printf notation (i.e. %s)
+ * @param callable $filter function to pass values through (for translation for instance)
+ * @param string $glue optional glue to use when joining resulting strings
+ *
+ * @return string
+ */
+ public function look( $sql, $bindings = array(), $keys = array( 'selected', 'id', 'name' ), $template = '', $filter = 'trim', $glue = '' )
+ {
+ $adapter = $this->toolbox->getDatabaseAdapter();
+ $lines = array();
+ $rows = $adapter->get( $sql, $bindings );
+ foreach( $rows as $row ) {
+ $values = array();
+ foreach( $keys as $key ) {
+ if (!empty($filter)) {
+ $values[] = call_user_func_array( $filter, array( $row[$key] ) );
+ } else {
+ $values[] = $row[$key];
+ }
+ }
+ $lines[] = vsprintf( $template, $values );
+ }
+ $string = implode( $glue, $lines );
+ return $string;
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+use RedBeanPHP\OODB as OODB;
+use RedBeanPHP\OODBBean as OODBBean;
+use RedBeanPHP\ToolBox as ToolBox;
+use RedBeanPHP\Finder;
+
+/**
+ * Diff Utility
+ *
+ * The Look Utility class provides an easy way to generate
+ * tables and selects (pulldowns) from the database.
+ *
+ * @file RedBeanPHP/Util/Diff.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Diff
+{
+ /**
+ * @var Toolbox
+ */
+ protected $toolbox;
+
+ /**
+ * Constructor.
+ * The MatchUp class requires a toolbox
+ *
+ * @param ToolBox $toolbox
+ */
+ public function __construct( ToolBox $toolbox )
+ {
+ $this->toolbox = $toolbox;
+ }
+
+ /**
+ * Calculates a diff between two beans (or arrays of beans).
+ * The result of this method is an array describing the differences of the second bean compared to
+ * the first, where the first bean is taken as reference. The array is keyed by type/property, id and property name, where
+ * type/property is either the type (in case of the root bean) or the property of the parent bean where the type resides.
+ * The diffs are mainly intended for logging, you cannot apply these diffs as patches to other beans.
+ * However this functionality might be added in the future.
+ *
+ * The keys of the array can be formatted using the $format parameter.
+ * A key will be composed of a path (1st), id (2nd) and property (3rd).
+ * Using printf-style notation you can determine the exact format of the key.
+ * The default format will look like:
+ *
+ * 'book.1.title' => array( , )
+ *
+ * If you only want a simple diff of one bean and you don't care about ids,
+ * you might pass a format like: '%1$s.%3$s' which gives:
+ *
+ * 'book.1.title' => array( , )
+ *
+ * The filter parameter can be used to set filters, it should be an array
+ * of property names that have to be skipped. By default this array is filled with
+ * two strings: 'created' and 'modified'.
+ *
+ * @param OODBBean|array $beans reference beans
+ * @param OODBBean|array $others beans to compare
+ * @param array $filters names of properties of all beans to skip
+ * @param string $format the format of the key, defaults to '%s.%s.%s'
+ * @param string|NULL $type type/property of bean to use for key generation
+ *
+ * @return array
+ */
+ public function diff( $beans, $others, $filters = array( 'created', 'modified' ), $format = '%s.%s.%s', $type = NULL )
+ {
+ $diff = array();
+
+ if ( !is_array( $beans ) ) $beans = array( $beans );
+ $beansI = array();
+ foreach ( $beans as $bean ) {
+ if ( !( $bean instanceof OODBBean ) ) continue;
+ $beansI[$bean->id] = $bean;
+ }
+
+ if ( !is_array( $others ) ) $others = array( $others );
+ $othersI = array();
+ foreach ( $others as $other ) {
+ if ( !( $other instanceof OODBBean ) ) continue;
+ $othersI[$other->id] = $other;
+ }
+
+ if ( count( $beansI ) == 0 || count( $othersI ) == 0 ) {
+ return array();
+ }
+
+ $type = $type != NULL ? $type : reset($beansI)->getMeta( 'type' );
+
+ foreach( $beansI as $id => $bean ) {
+ if ( !isset( $othersI[$id] ) ) continue;
+ $other = $othersI[$id];
+ foreach( $bean as $property => $value ) {
+ if ( in_array( $property, $filters ) ) continue;
+ $key = vsprintf( $format, array( $type, $bean->id, $property ) );
+ $compare = $other->{$property};
+ if ( !is_object( $value ) && !is_array( $value ) && $value != $compare ) {
+ $diff[$key] = array( $value, $compare );
+ } else {
+ $diff = array_merge( $diff, $this->diff( $value, $compare, $filters, $format, $key ) );
+ }
+ }
+ }
+
+ return $diff;
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+use RedBeanPHP\ToolBox;
+use RedBeanPHP\OODB;
+use RedBeanPHP\OODBBean;
+use RedBeanPHP\QueryWriter;
+
+/**
+ * Tree
+ *
+ * Given a bean, finds it children or parents
+ * in a hierarchical structure.
+ *
+ * @experimental feature
+ *
+ * @file RedBeanPHP/Util/Tree.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Tree {
+
+ /**
+ * @var ToolBox
+ */
+ protected $toolbox;
+
+ /**
+ * @var QueryWriter
+ */
+ protected $writer;
+
+ /**
+ * @var OODB
+ */
+ protected $oodb;
+
+ /**
+ * Constructor, creates a new instance of
+ * the Tree.
+ *
+ * @param ToolBox $toolbox toolbox
+ */
+ public function __construct( ToolBox $toolbox )
+ {
+ $this->toolbox = $toolbox;
+ $this->writer = $toolbox->getWriter();
+ $this->oodb = $toolbox->getRedBean();
+ }
+
+ /**
+ * Returns all child beans associates with the specified
+ * bean in a tree structure.
+ *
+ * @note this only works for databases that support
+ * recursive common table expressions.
+ *
+ * Usage:
+ *
+ *
+ * $newsArticles = R::children( $newsPage, ' ORDER BY title ASC ' )
+ * $newsArticles = R::children( $newsPage, ' WHERE title = ? ', [ $t ] );
+ * $newsArticles = R::children( $newsPage, ' WHERE title = :t ', [ ':t' => $t ] );
+ *
+ *
+ * Note:
+ * You are allowed to use named parameter bindings as well as
+ * numeric parameter bindings (using the question mark notation).
+ * However, you can not mix. Also, if using named parameter bindings,
+ * parameter binding key ':slot0' is reserved for the ID of the bean
+ * and used in the query.
+ *
+ * @param OODBBean $bean reference bean to find children of
+ * @param string|NULL $sql optional SQL snippet
+ * @param array $bindings optional parameter bindings for SQL snippet
+ *
+ * @return array
+ */
+ public function children( OODBBean $bean, $sql = NULL, $bindings = array() )
+ {
+ $type = $bean->getMeta('type');
+ $id = $bean->id;
+
+ $rows = $this->writer->queryRecursiveCommonTableExpression( $type, $id, FALSE, $sql, $bindings );
+
+ return $this->oodb->convertToBeans( $type, $rows );
+ }
+
+ /**
+ * Returns all parent beans associates with the specified
+ * bean in a tree structure.
+ *
+ * @note this only works for databases that support
+ * recursive common table expressions.
+ *
+ *
+ * $newsPages = R::parents( $newsArticle, ' ORDER BY title ASC ' );
+ * $newsPages = R::parents( $newsArticle, ' WHERE title = ? ', [ $t ] );
+ * $newsPages = R::parents( $newsArticle, ' WHERE title = :t ', [ ':t' => $t ] );
+ *
+ *
+ * Note:
+ * You are allowed to use named parameter bindings as well as
+ * numeric parameter bindings (using the question mark notation).
+ * However, you can not mix. Also, if using named parameter bindings,
+ * parameter binding key ':slot0' is reserved for the ID of the bean
+ * and used in the query.
+ *
+ * @param OODBBean $bean reference bean to find parents of
+ * @param string|NULL $sql optional SQL snippet
+ * @param array $bindings optional parameter bindings for SQL snippet
+ *
+ * @return array
+ */
+ public function parents( OODBBean $bean, $sql = NULL, $bindings = array() )
+ {
+ $type = $bean->getMeta('type');
+ $id = $bean->id;
+
+ $rows = $this->writer->queryRecursiveCommonTableExpression( $type, $id, TRUE, $sql, $bindings );
+
+ return $this->oodb->convertToBeans( $type, $rows );
+ }
+
+ /**
+ * Counts all children beans associates with the specified
+ * bean in a tree structure.
+ *
+ * @note this only works for databases that support
+ * recursive common table expressions.
+ *
+ *
+ * $count = R::countChildren( $newsArticle );
+ * $count = R::countChildren( $newsArticle, ' WHERE title = ? ', [ $t ] );
+ * $count = R::countChildren( $newsArticle, ' WHERE title = :t ', [ ':t' => $t ] );
+ *
+ *
+ * @note:
+ * You are allowed to use named parameter bindings as well as
+ * numeric parameter bindings (using the question mark notation).
+ * However, you can not mix. Also, if using named parameter bindings,
+ * parameter binding key ':slot0' is reserved for the ID of the bean
+ * and used in the query.
+ *
+ * @note:
+ * By default, if no SQL or select is given or select=TRUE this method will subtract 1 of
+ * the total count to omit the starting bean. If you provide your own select,
+ * this method assumes you take control of the resulting total yourself since
+ * it cannot 'predict' what or how you are trying to 'count'.
+ *
+ * @param OODBBean $bean reference bean to find children of
+ * @param string|NULL $sql optional SQL snippet
+ * @param array $bindings optional parameter bindings for SQL snippet
+ * @param string|boolean $select select snippet to use (advanced, optional, see QueryWriter::queryRecursiveCommonTableExpression)
+ *
+ * @return integer
+ */
+ public function countChildren( OODBBean $bean, $sql = NULL, $bindings = array(), $select = TRUE ) {
+ $type = $bean->getMeta('type');
+ $id = $bean->id;
+ $rows = $this->writer->queryRecursiveCommonTableExpression( $type, $id, FALSE, $sql, $bindings, $select );
+ $first = reset($rows);
+ $cell = reset($first);
+ return (intval($cell) - (($select === TRUE && is_null($sql)) ? 1 : 0));
+ }
+
+ /**
+ * Counts all parent beans associates with the specified
+ * bean in a tree structure.
+ *
+ * @note this only works for databases that support
+ * recursive common table expressions.
+ *
+ *
+ * $count = R::countParents( $newsArticle );
+ * $count = R::countParents( $newsArticle, ' WHERE title = ? ', [ $t ] );
+ * $count = R::countParents( $newsArticle, ' WHERE title = :t ', [ ':t' => $t ] );
+ *
+ *
+ * Note:
+ * You are allowed to use named parameter bindings as well as
+ * numeric parameter bindings (using the question mark notation).
+ * However, you can not mix. Also, if using named parameter bindings,
+ * parameter binding key ':slot0' is reserved for the ID of the bean
+ * and used in the query.
+ *
+ * Note:
+ * By default, if no SQL or select is given or select=TRUE this method will subtract 1 of
+ * the total count to omit the starting bean. If you provide your own select,
+ * this method assumes you take control of the resulting total yourself since
+ * it cannot 'predict' what or how you are trying to 'count'.
+ *
+ * @param OODBBean $bean reference bean to find parents of
+ * @param string|NULL $sql optional SQL snippet
+ * @param array $bindings optional parameter bindings for SQL snippet
+ * @param string|boolean $select select snippet to use (advanced, optional, see QueryWriter::queryRecursiveCommonTableExpression)
+ *
+ * @return integer
+ */
+ public function countParents( OODBBean $bean, $sql = NULL, $bindings = array(), $select = TRUE ) {
+ $type = $bean->getMeta('type');
+ $id = $bean->id;
+ $rows = $this->writer->queryRecursiveCommonTableExpression( $type, $id, TRUE, $sql, $bindings, $select );
+ $first = reset($rows);
+ $cell = reset($first);
+ return (intval($cell) - (($select === TRUE && is_null($sql)) ? 1 : 0));
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+use RedBeanPHP\Facade as R;
+use RedBeanPHP\OODBBean;
+
+/**
+ * Feature Utility
+ *
+ * The Feature Utility class provides an easy way to turn
+ * on or off features. This allows us to introduce new features
+ * without accidentally breaking backward compatibility.
+ *
+ * @file RedBeanPHP/Util/Feature.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Feature
+{
+ /* Feature set constants */
+ const C_FEATURE_NOVICE_LATEST = 'novice/latest';
+ const C_FEATURE_LATEST = 'latest';
+ const C_FEATURE_NOVICE_5_5 = 'novice/5.5';
+ const C_FEATURE_5_5 = '5.5';
+ const C_FEATURE_NOVICE_5_4 = 'novice/5.4';
+ const C_FEATURE_5_4 = '5.4';
+ const C_FEATURE_NOVICE_5_3 = 'novice/5.3';
+ const C_FEATURE_5_3 = '5.3';
+ const C_FEATURE_ORIGINAL = 'original';
+
+ /**
+ * Selects the feature set you want as specified by
+ * the label.
+ *
+ * Available labels:
+ *
+ * novice/latest:
+ * - forbid R::nuke()
+ * - enable automatic relation resolver based on foreign keys
+ * - forbid R::store(All)( $bean, TRUE ) (Hybrid mode)
+ * - use IS-NULL conditions in findLike() etc
+ *
+ * latest:
+ * - allow R::nuke()
+ * - enable auto resolve
+ * - allow hybrid mode
+ * - use IS-NULL conditions in findLike() etc
+ *
+ * novice/X or X:
+ * - keep everything as it was in version X
+ *
+ * Usage:
+ *
+ *
+ * R::useFeatureSet( 'novice/latest' );
+ *
+ *
+ * @param string $label label
+ *
+ * @return void
+ */
+ public static function feature( $label ) {
+ switch( $label ) {
+ case self::C_FEATURE_NOVICE_LATEST:
+ case self::C_FEATURE_NOVICE_5_4:
+ case self::C_FEATURE_NOVICE_5_5:
+ OODBBean::useFluidCount( TRUE );
+ R::noNuke( TRUE );
+ R::setAllowHybridMode( FALSE );
+ R::useISNULLConditions( TRUE );
+ break;
+ case self::C_FEATURE_LATEST:
+ case self::C_FEATURE_5_4:
+ case self::C_FEATURE_5_5:
+ OODBBean::useFluidCount( TRUE );
+ R::noNuke( FALSE );
+ R::setAllowHybridMode( TRUE );
+ R::useISNULLConditions( TRUE );
+ break;
+ case self::C_FEATURE_NOVICE_5_3:
+ OODBBean::useFluidCount( TRUE );
+ R::noNuke( TRUE );
+ R::setAllowHybridMode( FALSE );
+ R::useISNULLConditions( FALSE );
+ break;
+ case self::C_FEATURE_5_3:
+ OODBBean::useFluidCount( TRUE );
+ R::noNuke( FALSE );
+ R::setAllowHybridMode( FALSE );
+ R::useISNULLConditions( FALSE );
+ break;
+ case self::C_FEATURE_ORIGINAL:
+ OODBBean::useFluidCount( TRUE );
+ R::noNuke( FALSE );
+ R::setAllowHybridMode( FALSE );
+ R::useISNULLConditions( FALSE );
+ break;
+ default:
+ throw new \Exception("Unknown feature set label.");
+ break;
+ }
+ }
+}
+}
+
+namespace RedBeanPHP\Util {
+
+/**
+ * Either Utility
+ *
+ * The Either Utility class provides an easy way to
+ * substitute the NULL coalesce operator in RedBeanPHP
+ * (since the lazy loading interface interferes with the ??
+ * operator) in a way that can also be used in older PHP-versions.
+ *
+ * @file RedBeanPHP/Util/Either.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+class Either {
+
+ /**
+ * @var mixed
+ */
+ private $result;
+
+ /**
+ * Constructs a new Either-instance.
+ *
+ * Example usage:
+ *
+ *
+ * $author = $text
+ * ->either()
+ * ->page
+ * ->book
+ * ->autor
+ * ->name
+ * ->or('unknown');
+ *
+ *
+ * The Either-class lets you access bean properties without having to do
+ * NULL-checks. The mechanism resembles the use of the ?? somewhat but
+ * offers backward compatibility with older PHP versions. The mechanism also works
+ * on arrays.
+ *
+ *
+ * $budget = $company
+ * ->either()
+ * ->sharedProject
+ * ->first()
+ * ->budget
+ * ->or(0);
+ *
+ */
+ public function __construct($result) {
+ $this->result = $result;
+ }
+
+ /**
+ * Extracts a value from the wrapped object and stores
+ * it in the internal result object. If the desired
+ * value cannot be found, the internal result object will be set
+ * to NULL. Chainable.
+ *
+ * @param string $something name of the property you wish to extract the value of
+ *
+ * @return self
+ */
+ public function __get($something) {
+ if (is_object($this->result)) {
+ $this->result = $this->result->{$something};
+ } else {
+ $this->result = NULL;
+ }
+ return $this;
+ }
+
+ /**
+ * Extracts the first element of the array in the internal result
+ * object and stores it as the new value of the internal result object.
+ * Chainable.
+ *
+ * @return self
+ */
+ public function first() {
+ if (is_array($this->result)) {
+ reset($this->result);
+ $key = key($this->result);
+ if (isset($this->result[$key])) {
+ $this->result = $this->result[$key];
+ } else {
+ $this->result = NULL;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Extracts the last element of the array in the internal result
+ * object and stores it as the new value of the internal result object.
+ * Chainable.
+ *
+ * @return self
+ */
+ public function last() {
+ if (is_array($this->result)) {
+ end($this->result);
+ $key = key($this->result);
+ if (isset($this->result[$key])) {
+ $this->result = $this->result[$key];
+ } else {
+ $this->result = NULL;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Extracts the specified element of the array in the internal result
+ * object and stores it as the new value of the internal result object.
+ * Chainable.
+ *
+ * @return self
+ */
+ public function index( $key = 0 ) {
+ if (is_array($this->result)) {
+ if (isset($this->result[$key])) {
+ $this->result = $this->result[$key];
+ } else {
+ $this->result = NULL;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Resolves the Either-instance to a final value, either the value
+ * contained in the internal result object or the value specified
+ * in the or() function.
+ *
+ * @param mixed $value value to resolve to if internal result equals NULL
+ *
+ * @return mixed
+ */
+ public function _or( $value ) {
+ $reference = (is_null($this->result)) ? $value : $this->result;
+ return $reference;
+ }
+}
+}
+
+namespace RedBeanPHP {
+
+/**
+ * RedBean Plugin.
+ * Marker interface for plugins.
+ * Use this interface when defining new plugins, it's an
+ * easy way for the rest of the application to recognize your
+ * plugin. This plugin interface does not require you to
+ * implement a specific API.
+ *
+ * @file RedBean/Plugin.php
+ * @author Gabor de Mooij and the RedBeanPHP Community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+interface Plugin
+{
+}
+
+;
+}
+namespace {
+
+//make some classes available for backward compatibility
+class RedBean_SimpleModel extends \RedBeanPHP\SimpleModel {};
+
+if (!class_exists('R')) {
+ class R extends \RedBeanPHP\Facade{};
+}
+
+
+
+/**
+ * Support functions for RedBeanPHP.
+ * Additional convenience shortcut functions for RedBeanPHP.
+ *
+ * @file RedBeanPHP/Functions.php
+ * @author Gabor de Mooij and the RedBeanPHP community
+ * @license BSD/GPLv2
+ *
+ * @copyright
+ * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
+ * This source file is subject to the BSD/GPLv2 License that is bundled
+ * with this source code in the file license.txt.
+ */
+
+/**
+ * Convenience function for ENUM short syntax in queries.
+ *
+ * Usage:
+ *
+ *
+ * R::find( 'paint', ' color_id = ? ', [ EID('color:yellow') ] );
+ *
+ *
+ * If a function called EID() already exists you'll have to write this
+ * wrapper yourself ;)
+ *
+ * @param string $enumName enum code as you would pass to R::enum()
+ *
+ * @return mixed
+ */
+if (!function_exists('EID')) {
+
+ function EID($enumName)
+ {
+ return \RedBeanPHP\Facade::enum( $enumName )->id;
+ }
+
+}
+
+/**
+ * Prints the result of R::dump() to the screen using
+ * print_r.
+ *
+ * @param mixed $data data to dump
+ *
+ * @return void
+ */
+if ( !function_exists( 'dmp' ) ) {
+
+ function dmp( $list )
+ {
+ print_r( \RedBeanPHP\Facade::dump( $list ) );
+ }
+}
+
+/**
+ * Function alias for R::genSlots().
+ */
+if ( !function_exists( 'genslots' ) ) {
+
+ function genslots( $slots, $tpl = NULL )
+ {
+ return \RedBeanPHP\Facade::genSlots( $slots, $tpl );
+ }
+}
+
+/**
+ * Function alias for R::flat().
+ */
+if ( !function_exists( 'array_flatten' ) ) {
+
+ function array_flatten( $array )
+ {
+ return \RedBeanPHP\Facade::flat( $array );
+ }
+}
+
+/**
+ * Function pstr() generates [ $value, \PDO::PARAM_STR ]
+ * Ensures that your parameter is being treated as a string.
+ *
+ * Usage:
+ *
+ *
+ * R::find('book', 'title = ?', [ pstr('1') ]);
+ *
+ */
+if ( !function_exists( 'pstr' ) ) {
+
+ function pstr( $value )
+ {
+ return array( strval( $value ) , \PDO::PARAM_STR );
+ }
+}
+
+
+/**
+ * Function pint() generates [ $value, \PDO::PARAM_INT ]
+ * Ensures that your parameter is being treated as an integer.
+ *
+ * Usage:
+ *
+ *
+ * R::find('book', ' pages > ? ', [ pint(2) ] );
+ *
+ */
+if ( !function_exists( 'pint' ) ) {
+
+ function pint( $value )
+ {
+ return array( intval( $value ) , \PDO::PARAM_INT );
+ }
+}
+
+/**
+ * Function DBPrefix() is a simple function to allow you to
+ * quickly set a different namespace for FUSE model resolution
+ * per database connection. It works by creating a new DynamicBeanHelper
+ * with the specified string as model prefix.
+ *
+ * Usage:
+ *
+ *
+ * R::addDatabase( ..., DBPrefix( 'Prefix1_' ) );
+ *
+ */
+if ( !function_exists( 'DBPrefix' ) ) {
+
+ function DBPrefix( $prefix = '\\Model' ) {
+ return new \RedBeanPHP\BeanHelper\DynamicBeanHelper( $prefix );
+ }
+}
+
+
+}