From 1e4914403782b4433e825e9a0aa0efdb97790bbc Mon Sep 17 00:00:00 2001 From: kiswa Date: Tue, 7 Apr 2015 20:03:43 -0400 Subject: [PATCH] Update RedBeanPHP to 4.2 --- api/lib/license.txt | 2 +- api/lib/rb.php | 3494 +++++++++++++++++++++++++++---------------- 2 files changed, 2239 insertions(+), 1257 deletions(-) mode change 100644 => 100755 api/lib/license.txt diff --git a/api/lib/license.txt b/api/lib/license.txt old mode 100644 new mode 100755 index 4a01aac..ca0ae35 --- a/api/lib/license.txt +++ b/api/lib/license.txt @@ -30,7 +30,7 @@ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -RedBeanPHP is Written by Gabor de Mooij (G.J.G.T de Mooij) Copyright (c) 2014. +RedBeanPHP is Written by Gabor de Mooij (G.J.G.T de Mooij) Copyright (c) 2015. GPLv2 LICENSE diff --git a/api/lib/rb.php b/api/lib/rb.php index 1c7c904..d9edcf1 100644 --- a/api/lib/rb.php +++ b/api/lib/rb.php @@ -8,10 +8,10 @@ namespace RedBeanPHP { * interface throughout RedBeanPHP. * * @file RedBean/Logging.php - * @desc Logging interface for RedBeanPHP ORM * @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. @@ -43,19 +43,22 @@ use RedBeanPHP\RedException\Security as Security; /** * Logger. Provides a basic logging function for RedBeanPHP. * - * @file RedBean/Logger.php - * @desc Logger + * @file RedBeanPHP/Logger.php * @author Gabor de Mooij and the RedBeanPHP Community * @license BSD/GPLv2 * - * Provides a basic logging function for RedBeanPHP. - * + * @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 RDefault implements Logger { + /** + * Logger modes + */ + const C_LOGGER_ECHO = 0; + const C_LOGGER_ARRAY = 1; /** * @var integer @@ -70,9 +73,10 @@ class RDefault implements Logger /** * Default logger method logging to STDOUT. * This is the default/reference implementation of a logger. - * This method will write the message value to STDOUT (screen). + * This method will write the message value to STDOUT (screen) unless + * you have changed the mode of operation to C_LOGGER_ARRAY. * - * @param $message (optional) + * @param $message (optional) message to log (might also be data or output) * * @return void */ @@ -83,25 +87,26 @@ class RDefault implements Logger foreach ( func_get_args() as $argument ) { if ( is_array( $argument ) ) { $log = print_r( $argument, TRUE ); - if ( $this->mode === 0 ) { + if ( $this->mode === self::C_LOGGER_ECHO ) { echo $log; } else { $this->logs[] = $log; } } else { - if ( $this->mode === 0 ) { + if ( $this->mode === self::C_LOGGER_ECHO ) { echo $argument; } else { $this->logs[] = $argument; } } - if ($this->mode === 0) echo "
\n"; + if ( $this->mode === self::C_LOGGER_ECHO ) echo "
\n"; } } /** - * Returns the logs array. + * Returns the internal log array. + * The internal log array is where all log messages are stored. * * @return array */ @@ -111,7 +116,8 @@ class RDefault implements Logger } /** - * Empties the logs array. + * Clears the internal log array, removing all + * previously stored entries. * * @return self */ @@ -123,17 +129,19 @@ class RDefault implements Logger /** * Selects a logging mode. - * Mode 0 means echoing all statements, while mode 1 - * means populating the logs array. + * There are several options available. * - * @param integer $mode mode + * 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 !== 0 && $mode !== 1) { - throw new RedException( 'Invalid mode selected for logger, use 1 or 0.' ); + 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; @@ -142,16 +150,18 @@ class RDefault implements Logger /** * Searches for all log entries in internal log array * for $needle and returns those entries. - * - * @param string $needle needle - * + * 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; + if ( strpos( $logEntry, $needle ) !== FALSE ) $found[] = $logEntry; } return $found; } @@ -168,20 +178,23 @@ use RedBeanPHP\RedException\Security as Security; /** * Debug logger. * A special logger for debugging purposes. + * Provides debugging logging functions for RedBeanPHP. * - * @file RedBean/Logger/RDefault/Debug.php - * @desc Debug Logger + * @file RedBeanPHP/Logger/RDefault/Debug.php * @author Gabor de Mooij and the RedBeanPHP Community * @license BSD/GPLv2 * - * Provides a debugging logging functions for RedBeanPHP. - * + * @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 + */ + private $strLen = 40; /** * Writes a query for logging with all bindings / params filled @@ -221,8 +234,8 @@ class Debug extends RDefault implements Logger if ( is_null( $value ) ) $value = 'NULL'; $value = strval( $value ); - if ( strlen( $value ) > 20 ) { - $value = substr( $value, 0, 20 ).'... '; + if ( strlen( $value ) > ( $this->strLen ) ) { + $value = substr( $value, 0, ( $this->strLen ) ).'... '; } if ( !is_numeric( $value ) && $value !== 'NULL') { @@ -323,29 +336,42 @@ class Debug extends RDefault implements Logger $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; + } } } namespace RedBeanPHP { /** - * Interface for database drivers - * - * @file RedBean/Driver.php - * @desc Describes the API for database classes - * @author Gabor de Mooij and the RedBeanPHP Community - * @license BSD/GPLv2 - * + * 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. * @@ -374,7 +400,7 @@ interface Driver * * @return mixed */ - public function GetCell( $sql, $bindings = array() ); + public function GetOne( $sql, $bindings = array() ); /** * Runs a query and returns results as an associative array @@ -431,6 +457,16 @@ interface Driver */ public function Affected_Rows(); + /** + * Returns a cursor-like object from the database. + * + * @param string $sql SQL code to execute + * @param array $bindings Bindings + * + * @return mixed + */ + 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 @@ -464,6 +500,20 @@ interface Driver * @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(); } } @@ -475,17 +525,20 @@ use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter; 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 + * PDO Driver + * This Driver implements the RedBean Driver API. + * for RedBeanPHP. This is the standard / default database driver + * for RedBeanPHP. * - * @file RedBean/PDO.php - * @desc \PDO Driver + * @file RedBeanPHP/PDO.php * @author Gabor de Mooij and the RedBeanPHP Community, Desfrenes * @license BSD/GPLv2 * - * (c) copyright Desfrenes & Gabor de Mooij and the RedBeanPHP community + * @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. */ @@ -504,7 +557,7 @@ class RPDO implements Driver /** * @var boolean */ - protected $debug = FALSE; + protected $loggingEnabled = FALSE; /** * @var Logger @@ -512,7 +565,7 @@ class RPDO implements Driver protected $logger = NULL; /** - * @var\PDO + * @var PDO */ protected $pdo; @@ -541,18 +594,23 @@ class RPDO implements Driver */ protected $flagUseStringOnlyBinding = FALSE; + /** + * @var integer + */ + protected $queryCounter = 0; + /** * @var string */ protected $mysqlEncoding = ''; /** - * Binds parameters. This method binds parameters to a\PDOStatement for + * 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 + * @param PDOStatement $statement PDO Statement instance + * @param array $bindings values that need to get bound to the statement * * @return void */ @@ -561,19 +619,19 @@ class RPDO implements Driver foreach ( $bindings as $key => &$value ) { if ( is_integer( $key ) ) { if ( is_null( $value ) ) { - $statement->bindValue( $key + 1, NULL,\PDO::PARAM_NULL ); - } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && $value <= $this->max ) { - $statement->bindParam( $key + 1, $value,\PDO::PARAM_INT ); + $statement->bindValue( $key + 1, NULL, \PDO::PARAM_NULL ); + } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) { + $statement->bindParam( $key + 1, $value, \PDO::PARAM_INT ); } else { - $statement->bindParam( $key + 1, $value,\PDO::PARAM_STR ); + $statement->bindParam( $key + 1, $value, \PDO::PARAM_STR ); } } else { if ( is_null( $value ) ) { - $statement->bindValue( $key, NULL,\PDO::PARAM_NULL ); - } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && $value <= $this->max ) { - $statement->bindParam( $key, $value,\PDO::PARAM_INT ); + $statement->bindValue( $key, NULL, \PDO::PARAM_NULL ); + } elseif ( !$this->flagUseStringOnlyBinding && AQueryWriter::canBeTreatedAsInt( $value ) && abs( $value ) <= $this->max ) { + $statement->bindParam( $key, $value, \PDO::PARAM_INT ); } else { - $statement->bindParam( $key, $value,\PDO::PARAM_STR ); + $statement->bindParam( $key, $value, \PDO::PARAM_STR ); } } } @@ -596,31 +654,27 @@ class RPDO implements Driver protected function runQuery( $sql, $bindings, $options = array() ) { $this->connect(); - - if ( $this->debug && $this->logger ) { + if ( $this->loggingEnabled && $this->logger ) { $this->logger->log( $sql, $bindings ); } - try { if ( strpos( 'pgsql', $this->dsn ) === 0 ) { - $statement = $this->pdo->prepare( $sql, array(\PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => TRUE ) ); + $statement = $this->pdo->prepare( $sql, array( \PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT => TRUE ) ); } else { $statement = $this->pdo->prepare( $sql ); } - $this->bindParams( $statement, $bindings ); - $statement->execute(); - + $this->queryCounter ++; $this->affectedRows = $statement->rowCount(); - if ( $statement->columnCount() ) { - $fetchStyle = ( isset( $options['fetchStyle'] ) ) ? $options['fetchStyle'] : NULL; - + if ( isset( $options['noFetch'] ) && $options['noFetch'] ) { + $this->resultArray = array(); + return $statement; + } $this->resultArray = $statement->fetchAll( $fetchStyle ); - - if ( $this->debug && $this->logger ) { + if ( $this->loggingEnabled && $this->logger ) { $this->logger->log( 'resultset: ' . count( $this->resultArray ) . ' rows' ); } } else { @@ -630,12 +684,9 @@ class RPDO implements Driver //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->debug && $this->logger ) $this->logger->log( 'An error occurred: ' . $err ); - + if ( $this->loggingEnabled && $this->logger ) $this->logger->log( 'An error occurred: ' . $err ); $exception = new SQL( $err, 0 ); $exception->setSQLState( $e->getCode() ); - throw $exception; } } @@ -650,7 +701,6 @@ class RPDO implements Driver { $driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME ); $version = floatval( $this->pdo->getAttribute(\PDO::ATTR_SERVER_VERSION ) ); - if ($driver === 'mysql') { $encoding = ($version >= 5.5) ? 'utf8mb4' : 'utf8'; $this->pdo->setAttribute(\PDO::MYSQL_ATTR_INIT_COMMAND, 'SET NAMES '.$encoding ); //on every re-connect @@ -659,6 +709,44 @@ class RPDO implements Driver } } + /** + * Constructor. You may either specify dsn, user and password or + * just give an existing PDO connection. + * + * Examples: + * $driver = new RPDO($dsn, $user, $password); + * $driver = new RPDO($existingConnection); + * + * @param string|object $dsn database connection string + * @param string $user optional, usename to sign in + * @param string $pass optional, password for connection login + * + */ + public function __construct( $dsn, $user = NULL, $pass = NULL ) + { + 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 ); + } + + //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 ) || $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) + } + } + /** * Returns the best possible encoding for MySQL based on version data. * @@ -669,49 +757,10 @@ class RPDO implements Driver return $this->mysqlEncoding; } - /** - * Constructor. You may either specify dsn, user and password or - * just give an existing\PDO connection. - * Examples: - * $driver = new RPDO($dsn, $user, $password); - * $driver = new RPDO($existingConnection); - * - * @param string|object $dsn database connection string - * @param string $user optional, usename to sign in - * @param string $pass optional, password for connection login - * - */ - public function __construct( $dsn, $user = NULL, $pass = NULL ) - { - 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 ); - } - - //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) { - $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) - } - } - /** * 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. * @@ -726,8 +775,8 @@ class RPDO implements Driver * 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. + * UTF8 for the database and PDO-ERRMODE-EXCEPTION as well as + * PDO-FETCH-ASSOC. * * @throws\PDOException * @@ -739,26 +788,21 @@ class RPDO implements Driver try { $user = $this->connectInfo['user']; $pass = $this->connectInfo['pass']; - - $this->pdo = new\PDO( + $this->pdo = new \PDO( $this->dsn, $user, $pass ); - $this->setEncoding(); - $this->pdo->setAttribute(\PDO::ATTR_STRINGIFY_FETCHES, TRUE ); + $this->pdo->setAttribute( \PDO::ATTR_STRINGIFY_FETCHES, TRUE ); //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->pdo->setAttribute( \PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION ); + $this->pdo->setAttribute( \PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC ); $this->isConnected = TRUE; - } catch (\PDOException $exception ) { + } 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() ); + throw new \PDOException( 'Could not connect to database (' . $dbname . ').', $exception->getCode() ); } } @@ -783,7 +827,6 @@ class RPDO implements Driver public function GetAll( $sql, $bindings = array() ) { $this->runQuery( $sql, $bindings ); - return $this->resultArray; } @@ -796,7 +839,6 @@ class RPDO implements Driver 'fetchStyle' => \PDO::FETCH_ASSOC ) ); - return $this->resultArray; } @@ -806,7 +848,6 @@ class RPDO implements Driver public function GetCol( $sql, $bindings = array() ) { $rows = $this->GetAll( $sql, $bindings ); - $cols = array(); if ( $rows && is_array( $rows ) && count( $rows ) > 0 ) { foreach ( $rows as $row ) { @@ -818,16 +859,33 @@ class RPDO implements Driver } /** - * @see Driver::GetCell + * @see Driver::GetOne + */ + public function GetOne( $sql, $bindings = array() ) + { + $arr = $this->GetAll( $sql, $bindings ); + $res = NULL; + if ( !is_array( $arr ) ) return NULL; + if ( count( $arr ) === 0 ) return NULL; + $row1 = array_shift( $arr ); + if ( !is_array( $row1 ) ) return NULL; + if ( count( $row1 ) === 0 ) return NULL; + $col1 = array_shift( $row1 ); + return $col1; + } + + /** + * Alias for getOne(). + * Backward compatibility. + * + * @param string $sql SQL + * @param array $bindings bindings + * + * @return mixed */ public function GetCell( $sql, $bindings = array() ) { - $arr = $this->GetAll( $sql, $bindings ); - - $row1 = array_shift( $arr ); - $col1 = array_shift( $row1 ); - - return $col1; + return $this->GetOne( $sql, $bindings ); } /** @@ -836,7 +894,6 @@ class RPDO implements Driver public function GetRow( $sql, $bindings = array() ) { $arr = $this->GetAll( $sql, $bindings ); - return array_shift( $arr ); } @@ -846,7 +903,6 @@ class RPDO implements Driver public function Execute( $sql, $bindings = array() ) { $this->runQuery( $sql, $bindings ); - return $this->affectedRows; } @@ -860,13 +916,22 @@ class RPDO implements Driver 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; } @@ -875,21 +940,18 @@ class RPDO implements Driver * SQL to the screen together with some information about the * results. * - * @param boolean $trueFalse turn on/off - * @param Logger $logger logger instance + * @param boolean $trueFalse turn on/off + * @param Logger $logger logger instance * * @return void */ public function setDebugMode( $tf, $logger = NULL ) { $this->connect(); - - $this->debug = (bool) $tf; - - if ( $this->debug and !$logger ) { + $this->loggingEnabled = (bool) $tf; + if ( $this->loggingEnabled and !$logger ) { $logger = new RDefault(); } - $this->setLogger( $logger ); } @@ -898,6 +960,8 @@ class RPDO implements Driver * Sets the logger instance you wish to use. * * @param Logger $logger the logger instance to be used for logging + * + * @return void */ public function setLogger( Logger $logger ) { @@ -921,7 +985,6 @@ class RPDO implements Driver public function StartTrans() { $this->connect(); - $this->pdo->beginTransaction(); } @@ -931,7 +994,6 @@ class RPDO implements Driver public function CommitTrans() { $this->connect(); - $this->pdo->commit(); } @@ -941,14 +1003,13 @@ class RPDO implements Driver 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. + * Returns the name of database driver for PDO. + * Uses the PDO attribute DRIVER NAME to obtain the name of the + * PDO driver. * * @return string */ @@ -967,19 +1028,17 @@ class RPDO implements Driver public function getDatabaseVersion() { $this->connect(); - return $this->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION ); } /** - * Returns the underlying PHP\PDO instance. + * Returns the underlying PHP PDO instance. * - * @return\PDO + * @return PDO */ public function getPDO() { $this->connect(); - return $this->pdo; } @@ -1003,6 +1062,53 @@ class RPDO implements Driver { 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; + } + + /** + * Resets the internal Query Counter. + * + * @return self + */ + public function resetCounter() + { + $this->queryCounter = 0; + return $this; + } + + /** + * Returns the number of SQL 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; + } } } @@ -1015,19 +1121,35 @@ use RedBeanPHP\RedException as RedException; use RedBeanPHP\OODBBean as OODBBean; /** - * OODBBean (Object Oriented DataBase Bean) + * 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 RedBean/OODBBean.php - * @desc The Bean class used for passing information + * @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 { + /** + * 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 */ @@ -1039,15 +1161,14 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable protected static $errorHandler = NULL; /** - * FUSE error modes. + * @var array */ - 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; + protected static $aliases = array(); + + /** + * @var boolean + */ + protected static $autoResolve = FALSE; /** * Sets the error mode for FUSE. @@ -1099,7 +1220,34 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable } return $old; } - + + /** + * Sets aliases. + * + * @param array $list + * + * @return void + */ + public static function aliases( $list ) + { + self::$aliases = $list; + } + + /** + * Enables or disables auto-resolving fetch types. + * Auto-resolving aliased parent beans is convenient but can + * be slower and can create infinite recursion if you + * used aliases to break cyclic relations in your domain. + * + * @param boolean $automatic TRUE to enable automatic resolving aliased parents + * + * @return void + */ + public static function setAutoResolve( $automatic = TRUE ) + { + self::$autoResolve = (boolean) $automatic; + } + /** * This is where the real properties of the bean live. They are stored and retrieved * by the magic getter and setter (__get and __set). @@ -1159,6 +1307,27 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable */ protected $all = FALSE; + /** + * 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; + } + + return $beans; + } + /** * Parses the join in the with-snippet. * For instance: @@ -1210,7 +1379,6 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable */ private function getSharedList( $type, $redbean, $toolbox ) { - $writer = $toolbox->getWriter(); if ( $this->via ) { @@ -1295,27 +1463,6 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable return $beans; } - /** - * 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 ) { - $bean->__info[ $property ] = $value; - } - - return $beans; - } - /** * Initializes a bean. Used by OODB for dispensing beans. * It is not recommended to use this method to initialize beans. Instead @@ -1360,11 +1507,11 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable * 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; + * $bean[$key] = $value; * * Note that not all PHP functions work with the array interface. * - * @return\ArrayIterator + * @return \ArrayIterator */ public function getIterator() { @@ -1562,10 +1709,11 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable } /** - * Unsets a property. This method will load the property first using - * __get. - * - * @param string $property property + * 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 */ @@ -1640,7 +1788,14 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable } /** - * When prefix for a list, this causes the list to reload. + * 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 */ @@ -1725,6 +1880,7 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable /** * Turns a camelcase property name into an underscored property name. + * * Examples: * oneACLRoute -> one_acl_route * camelCase -> camel_case @@ -1742,9 +1898,9 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable if ( ctype_lower( $property ) ) return $property; if ( - strpos( $property, 'own' ) === 0 - || strpos( $property, 'xown' ) === 0 - || strpos( $property, 'shared' ) === 0 + ( 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 ); @@ -1758,8 +1914,6 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable return $beautifulColumns[$property]; } - - /** * Clears all modifiers. * @@ -1878,24 +2032,36 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable if ( isset( $this->__info["sys.parentcache.$property"] ) ) { $bean = $this->__info["sys.parentcache.$property"]; } else { - if ( $this->fetchType ) { - $type = $this->fetchType; + if ( isset( self::$aliases[$property] ) ) { + $type = self::$aliases[$property]; + } elseif ( $this->fetchType ) { + $type = $this->fetchType; $this->fetchType = NULL; } else { $type = $property; } - $bean = $redbean->load( $type, $this->properties[$fieldLink] ); + $bean = NULL; + if ( !is_null( $this->properties[$fieldLink] ) ) { + $bean = $redbean->load( $type, $this->properties[$fieldLink] ); + //If the IDs dont match, we failed to load, so try autoresolv in that case... + if ( $bean->id !== $this->properties[$fieldLink] && self::$autoResolve ) { + $type = $this->beanHelper->getToolbox()->getWriter()->inferFetchType( $this->__info['type'], $property ); + if ( !is_null( $type) ) { + $bean = $redbean->load( $type, $this->properties[$fieldLink] ); + $this->__info["sys.autoresolved.{$property}"] = $type; + } + } + } } $this->properties[$property] = $bean; - - $this->withSql = ''; - $this->withParams = array(); - $this->aliasName = NULL; - $this->fetchType = NULL; - $this->noLoad = FALSE; - $this->all = FALSE; - $this->via = NULL; + $this->withSql = ''; + $this->withParams = array(); + $this->aliasName = NULL; + $this->fetchType = NULL; + $this->noLoad = FALSE; + $this->all = FALSE; + $this->via = NULL; return $this->properties[$property]; @@ -1909,7 +2075,7 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox ); } - $this->properties[$property] = $beans; + $this->properties[$property] = $beans; $this->__info["sys.shadow.$property"] = $beans; $this->__info['tainted'] = TRUE; @@ -2040,16 +2206,18 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable /** * Returns the value of a meta property. A meta property - * contains extra information about the bean object that will not - * get stored in the database. Meta information is used to instruct - * RedBean as well as other systems how to deal with the bean. - * For instance: $bean->setMeta("buildcommand.unique", array( - * array("column1", "column2", "column3") ) ); - * Will add a UNIQUE constraint for the bean on columns: column1, column2 and - * column 3. - * To access a Meta property we use a dot separated notation. + * 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 * @param mixed $default default value * @@ -2061,13 +2229,35 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable } /** - * Stores a value in the specified Meta information property. $value contains - * the value you want to store in the Meta section of the bean and $path - * specifies the dot separated path to the property. For instance "my.meta.property". - * If "my" and "meta" do not exist they will be created automatically. + * 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 - * @param mixed $value value + * @param string $path path + * @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 */ @@ -2136,15 +2326,13 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable } elseif ( self::$errorHandlingFUSE === self::C_ERR_EXCEPTION ) { throw new \Exception( $message ); } elseif ( self::$errorHandlingFUSE === self::C_ERR_FUNC ) { - if ( is_callable( self::$errorHandler ) ) { - $func = self::$errorHandler; - return $func(array( - 'message' => $message, - 'method' => $method, - 'args' => $args, - 'bean' => $this - )); - } + $func = self::$errorHandler; + return $func(array( + 'message' => $message, + 'method' => $method, + 'args' => $args, + 'bean' => $this + )); } trigger_error( $message, E_USER_ERROR ); return NULL; @@ -2427,7 +2615,12 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable * Returns TRUE if the value of a certain property of the bean has been changed and * FALSE otherwise. * - * @param string $property name of the property you want the change-status of + * 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 it's 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 */ @@ -2437,6 +2630,44 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable $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. + * + * @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. + * + * @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; + } + } + return $this; + } + /** * Creates a N-M relation by linking an intermediate bean. * This method can be used to quickly connect beans using indirect @@ -2667,8 +2898,6 @@ class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable && ( (string) $this->__info['type'] === (string) $bean->__info['type'] ) ); } - - } } @@ -2680,11 +2909,11 @@ use RedBeanPHP\Observer as Observer; * Observable * Base class for Observables * - * @file RedBean/Observable.php - * @description Part of the observer pattern in RedBean + * @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. @@ -2750,26 +2979,27 @@ abstract class Observable { //bracket must be here - otherwise coverage software namespace RedBeanPHP { /** - * Observer + * Observer. + * * Interface for Observer object. Implementation of the * observer pattern. * - * @file RedBean/Observer.php - * @desc Part of the observer pattern in RedBean - * @author Gabor de Mooijand the RedBeanPHP community + * @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 + * 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 @@ -2784,20 +3014,22 @@ interface Observer namespace RedBeanPHP { /** - * Adapter Interface + * Adapter Interface. + * Describes the API for a RedBeanPHP Database Adapter. + * This interface defines the API contract for + * a RedBeanPHP Database Adapter. * - * @file RedBean/Adapter.php - * @desc Describes the API for a RedBean 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 { - /** * Returns the latest SQL statement * @@ -2910,6 +3142,16 @@ interface Adapter */ public function getAffectedRows(); + /** + * Returns a database agnostic Cursor object. + * + * @param string $sql SQL + * @param array $bindings bindings + * + * @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 @@ -2965,20 +3207,24 @@ use RedBeanPHP\Driver as Driver; /** * DBAdapter (Database Adapter) * - * @file RedBean/Adapter/DBAdapter.php - * @desc An adapter class to connect various database systems to RedBean + * 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 * - * Database Adapter Class. - * + * @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 */ @@ -3076,7 +3322,10 @@ class DBAdapter extends Observable implements Adapter foreach ( $rows as $row ) { if ( empty( $row ) ) continue; - if ( count( $row ) > 1 ) { + if ( count( $row ) > 2 ) { + $key = array_shift( $row ); + $value = $row; + } elseif ( count( $row ) > 1 ) { $key = array_shift( $row ); $value = array_shift( $row ); } else { @@ -3110,13 +3359,15 @@ class DBAdapter extends Observable implements Adapter if ( !$noSignal ) $this->signal( 'sql_exec', $this ); - $arr = $this->db->getCol( $sql, $bindings ); + return $this->db->GetOne( $sql, $bindings ); + } - if ( $arr && is_array( $arr ) && isset( $arr[0] ) ) { - return ( $arr[0] ); - } - - return NULL; + /** + * @see Adapter::getCursor + */ + public function getCursor( $sql, $bindings = array() ) + { + return $this->db->GetCursor( $sql, $bindings ); } /** @@ -3180,23 +3431,254 @@ class DBAdapter extends Observable implements Adapter namespace RedBeanPHP { /** - * QueryWriter - * Interface for QueryWriters + * Database Cursor Interface. + * Represents a simple database cursor. + * Cursors make it possible to create lightweight BeanCollections. * - * @file RedBean/QueryWriter.php - * @desc Describes the API for a QueryWriter + * @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 +{ + /** + * Retrieves the next row from the result set. + * + * @return array + */ + public function getNextItem(); + + /** + * 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. + * + * @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::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. + * + * @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::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 * - * Notes: - * - Whenever you see a parameter called $table or $type you should always - * be aware of the fact that this argument contains a Bean Type string, not the - * actual table name. These raw type names are passed to safeTable() to obtain the - * actual name of the database table. Don't let the names confuse you $type/$table - * refers to Bean Type, not physical database table names! - * - This is the interface for FLUID database drivers. Drivers intended to support - * just FROZEN mode should implement the IceWriter instead. + * @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; + + /** + * 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 + * + * @return void + */ + public function __construct( $type, Repository $repository, Cursor $cursor ) + { + $this->type = $type; + $this->cursor = $cursor; + $this->repository = $repository; + } + + /** + * 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 ) ); + $bean = array_shift( $beans ); + return $bean; + } + return NULL; + } + + /** + * 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. @@ -3241,6 +3723,9 @@ interface QueryWriter * 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'). @@ -3272,6 +3757,9 @@ interface QueryWriter * 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 $glue the GLUE type - how to glue (C_GLUE_WHERE or C_GLUE_AND) * @@ -3283,6 +3771,9 @@ interface QueryWriter * 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 @@ -3383,6 +3874,20 @@ interface QueryWriter */ 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 $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. @@ -3492,6 +3997,11 @@ interface QueryWriter */ public function deleteRelations( $sourceType, $destType, $sourceID ); + /** + * @see QueryWriter::addUniqueConstaint + */ + 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. @@ -3501,7 +4011,7 @@ interface QueryWriter * * @return void */ - public function addUniqueIndex( $type, $columns ); + public function addUniqueConstraint( $type, $columns ); /** * This method will check whether the SQL state is in the list of specified states @@ -3527,16 +4037,6 @@ interface QueryWriter */ public function wipe( $type ); - /** - * Given two types this method will add a foreign key constraint. - * - * @param string $sourceType source type - * @param string $destType destination type - * - * @return void - */ - public function addConstraintForTypes( $sourceType, $destType ); - /** * This method will add a foreign key from type and field to * target type and target field. @@ -3547,28 +4047,28 @@ interface QueryWriter * 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 $field field that contains the foreign key value - * @param string $targetField field where the fk points to - * @param string $isDep whether target is dependent and should cascade on update/delete + * @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 string $isDep whether target is dependent and should cascade on update/delete * * @return void */ - public function addFK( $type, $targetType, $field, $targetField, $isDep = false ); + 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 $column field to index + * @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, $column ); + public function addIndex( $type, $name, $property ); /** * Checks and filters a database structure element like a table of column @@ -3607,24 +4107,42 @@ interface QueryWriter * This method also accepts a single associative array as * its first argument. * - * @param string|array $from - * @param string $to (optional) + * @param string|array $fromType + * @param string $toType (optional) * * @return void */ - public function renameAssocTable( $from, $to = NULL ); + 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. + * 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 ); + + /** + * Given a bean type and a property, this method + * tries to infer the fetch type using the foreign key + * definitions in the database. + * For instance: project, student -> person. + * If no fetchType can be inferred, this method will return NULL. + * + * @note QueryWriters do not have to implement this method, + * it's optional. A default version is available in AQueryWriter. + * + * @param $type the source type to fetch a target type for + * @param $property the property to fetch the type of + * + * @return string|NULL + */ + public function inferFetchType( $type, $property ); } } @@ -3634,25 +4152,46 @@ 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; /** - * RedBean Abstract Query Writer - * - * @file RedBean/QueryWriter/AQueryWriter.php - * @desc Query Writer (abstract class) - * @author Gabor de Mooij and the RedBeanPHP Community - * @license BSD/GPLv2 - * + * 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 { //bracket must be here - otherwise coverage software does not understand. +abstract class AQueryWriter +{ + /** + * @var array + */ + private static $sqlFilters = array(); + + /** + * @var boolean + */ + private static $flagSQLFilterSafeMode = false; + + /** + * @var boolean + */ + private static $flagNarrowFieldMode = true; + + /** + * @var array + */ + public static $renames = array(); + /** * @var DBAdapter */ @@ -3679,30 +4218,66 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa protected $cache = array(); /** - * @var array + * @var integer */ - public static $renames = array(); - - /** - * @var array - */ - private static $sqlFilters = array(); - - /** - * @var boolean - */ - private static $flagSQLFilterSafeMode = false; - - /** - * @var boolean - */ - private static $flagNarrowFieldMode = true; + protected $maxCacheSizePerType = 20; /** * @var array */ public $typeno_sqltype = array(); + /** + * 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 ) === 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 a camelCased string + * + * @return string + */ + public static function camelsSnake( $camel ) + { + return strtolower( preg_replace( '/(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])/', '_$1$2', $camel ) ); + } + /** * Clears renames. * @@ -3786,76 +4361,6 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa return self::$sqlFilters; } - /** - * 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 ), '?' ) ); - } - /** * Returns a cache key for the cache values passed. * This method returns a fingerprint string to be used as a key to store @@ -3932,7 +4437,12 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa */ private function putResultInCache( $cacheTag, $key, $values ) { - if (!isset($this->cache[$cacheTag])) $this->cache[$cacheTag] = array(); + 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; } @@ -4040,6 +4550,146 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa return array( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ); } + /** + * 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. @@ -4149,64 +4799,13 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa */ protected function check( $struct ) { - if ( !preg_match( '/^[a-zA-Z0-9_]+$/', $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 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) ( ctype_digit( strval( $value ) ) && strval( $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 a camelCased string - * - * @return string - */ - public static function camelsSnake( $camel ) - { - return strtolower( preg_replace( '/(?<=[a-z])([A-Z])|([A-Z])(?=[a-z])/', '_$1$2', $camel ) ); - } - /** * Checks whether the specified type (i.e. table) already exists in the database. * Not part of the Object Database interface! @@ -4257,10 +4856,10 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa /** * @see QueryWriter::glueLimitOne */ - public function glueLimitOne( $sql = '') - { - return ( strpos( $sql, 'LIMIT' ) === FALSE ) ? ( $sql . ' LIMIT 1 ' ) : $sql; - } + public function glueLimitOne( $sql = '') + { + return ( strpos( $sql, 'LIMIT' ) === FALSE ) ? ( $sql . ' LIMIT 1 ' ) : $sql; + } /** * @see QueryWriter::esc @@ -4387,6 +4986,17 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa return $rows; } + /** + * @see QueryWriter::queryRecordWithCursor + */ + public function queryRecordWithCursor( $type, $addSql = NULL, $bindings = array() ) + { + $sql = $this->glueSQLCondition( $addSql, NULL ); + $table = $this->esc( $type ); + $sql = "SELECT {$table}.* FROM {$table} {$sql}"; + return $this->adapter->getCursor( $sql, $bindings ); + } + /** * @see QueryWriter::queryRecordRelated */ @@ -4597,19 +5207,18 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa /** * @see QueryWriter::widenColumn */ - public function widenColumn( $type, $column, $datatype ) + public function widenColumn( $type, $property, $dataType ) { - if ( !isset($this->typeno_sqltype[$datatype]) ) return; + if ( !isset($this->typeno_sqltype[$dataType]) ) return FALSE; - $table = $type; - $type = $datatype; + $table = $this->esc( $type ); + $column = $this->esc( $property ); - $table = $this->esc( $table ); - $column = $this->esc( $column ); + $newType = $this->typeno_sqltype[$dataType]; - $newtype = $this->typeno_sqltype[$type]; + $this->adapter->exec( "ALTER TABLE $table CHANGE $column $column $newType " ); - $this->adapter->exec( "ALTER TABLE $table CHANGE $column $column $newtype " ); + return TRUE; } /** @@ -4638,16 +5247,6 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa return self::getAssocTableFormat( $types ); } - /** - * @see QueryWriter::addConstraintForTypes - */ - public function addConstraintForTypes( $sourceType, $destType ) - { - list( $sourceTable, $destTable, $linkTable, $sourceCol, $destCol ) = $this->getRelationalTablesAndColumns( $sourceType, $destType, TRUE ); - - return $this->constrain( $linkTable, $sourceTable, $destTable, $sourceCol, $destCol ); - } - /** * Turns caching on or off. Default: off. * If caching is turned on retrieval queries fired after eachother will @@ -4664,12 +5263,19 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa /** * Flushes the Query Writer Cache. + * Clears the internal query cache array and returns its overall + * size. * - * @return void + * @return integer */ - public function flushCache() + public function flushCache( $newMaxCacheSizePerType = NULL ) { + if ( !is_null( $newMaxCacheSizePerType ) && $newMaxCacheSizePerType > 0 ) { + $this->maxCacheSizePerType = $newMaxCacheSizePerType; + } + $count = count( $this->cache, COUNT_RECURSIVE ); $this->cache = array(); + return $count; } /** @@ -4697,6 +5303,31 @@ abstract class AQueryWriter { //bracket must be here - otherwise coverage softwa { return $this->esc( $table, $noQuotes ); } + + /** + * @see QueryWriter::inferFetchType + */ + public function inferFetchType( $type, $property ) + { + $type = $this->esc( $type, TRUE ); + $field = $this->esc( $property, TRUE ) . '_id'; + $keys = $this->getKeyMapForType( $type ); + + foreach( $keys as $key ) { + if ( + $key['from'] === $field + ) return $key['table']; + } + return NULL; + } + + /** + * @see QueryWriter::addUniqueConstraint + */ + public function addUniqueIndex( $type, $properties ) + { + return $this->addUniqueConstraint( $type, $properties ); + } } } @@ -4706,22 +5337,24 @@ 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; /** - * RedBean MySQLWriter + * RedBeanPHP MySQLWriter. + * This is a QueryWriter class for RedBeanPHP. + * This QueryWriter provides support for the MySQL/MariaDB database platform. * - * @file RedBean/QueryWriter/MySQL.php - * @desc Represents a MySQL Database to RedBean + * @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 */ @@ -4751,64 +5384,45 @@ class MySQL extends AQueryWriter implements QueryWriter protected $quoteCharacter = '`'; /** - * Add the constraints for a specific database driver: MySQL. - * - * @param string $table table table to add constrains to - * @param string $table1 table1 first reference table - * @param string $table2 table2 second reference table - * @param string $property1 property1 first column - * @param string $property2 property2 second column - * - * @return boolean $succes whether the constraint has been applied + * @see AQueryWriter::getKeyMapForType */ - protected function constrain( $table, $table1, $table2, $property1, $property2 ) + protected function getKeyMapForType( $type ) { - try { - $db = $this->adapter->getCell( 'SELECT database()' ); - - $fks = $this->adapter->getCell( - "SELECT count(*) - FROM information_schema.KEY_COLUMN_USAGE - WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND - CONSTRAINT_NAME <>'PRIMARY' AND REFERENCED_TABLE_NAME IS NOT NULL", - array( $db, $table ) + $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 + AND information_schema.referential_constraints.constraint_schema = information_schema.key_column_usage.constraint_schema + AND information_schema.referential_constraints.constraint_catalog = information_schema.key_column_usage.constraint_catalog + ) + WHERE + information_schema.key_column_usage.table_schema IN ( SELECT DATABASE() ) + AND information_schema.key_column_usage.table_name = ? + AND information_schema.key_column_usage.constraint_name != \'PRIMARY\' + AND information_schema.key_column_usage.referenced_table_name IS NOT NULL + ', array($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'] ); - - // already foreign keys added in this association table - if ( $fks > 0 ) { - return FALSE; - } - - $columns = $this->getColumns( $table ); - - $idType = $this->getTypeForID(); - - if ( $this->code( $columns[$property1] ) !== $idType ) { - $this->widenColumn( $table, $property1, $idType ); - } - - if ( $this->code( $columns[$property2] ) !== $idType ) { - $this->widenColumn( $table, $property2, $idType ); - } - - $sql = " - ALTER TABLE " . $this->esc( $table ) . " - ADD FOREIGN KEY($property1) references `$table1`(id) ON DELETE CASCADE ON UPDATE CASCADE; - "; - - $this->adapter->exec( $sql ); - - $sql = " - ALTER TABLE " . $this->esc( $table ) . " - ADD FOREIGN KEY($property2) references `$table2`(id) ON DELETE CASCADE ON UPDATE CASCADE - "; - - $this->adapter->exec( $sql ); - - return TRUE; - } catch ( \Exception $e ) { - return FALSE; } + return $keyInfoList; } /** @@ -4899,7 +5513,7 @@ class MySQL extends AQueryWriter implements QueryWriter $this->svalue = $value; if ( is_null( $value ) ) return MySQL::C_DATATYPE_BOOL; - if ( $value === INF ) return MySQL::C_DATATYPE_TEXT8; + if ( $value === INF ) return MySQL::C_DATATYPE_TEXT7; if ( $flagSpecial ) { if ( preg_match( '/^\d{4}\-\d\d-\d\d$/', $value ) ) { @@ -4924,6 +5538,8 @@ class MySQL extends AQueryWriter implements QueryWriter 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 ) { @@ -4975,88 +5591,73 @@ class MySQL extends AQueryWriter implements QueryWriter /** * @see QueryWriter::addUniqueIndex */ - public function addUniqueIndex( $table, $columns ) + public function addUniqueConstraint( $type, $properties ) { - $table = $this->esc( $table ); - + $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 - - foreach ( $columns as $k => $v ) { - $columns[$k] = $this->esc( $v ); - } - - $r = $this->adapter->get( "SHOW INDEX FROM $table" ); - $name = 'UQ_' . sha1( implode( ',', $columns ) ); - - if ( $r ) { - foreach ( $r as $i ) { - if ( $i['Key_name'] == $name ) { - return; - } - } - } - try { $sql = "ALTER TABLE $table ADD UNIQUE INDEX $name (" . implode( ',', $columns ) . ")"; - } catch ( \Exception $e ) { + $this->adapter->exec( $sql ); + } catch ( SQLException $e ) { //do nothing, dont use alter table ignore, this will delete duplicate records in 3-ways! + return FALSE; } - - $this->adapter->exec( $sql ); + return TRUE; } /** * @see QueryWriter::addIndex */ - public function addIndex( $type, $name, $column ) + public function addIndex( $type, $name, $property ) { - $table = $type; - $table = $this->esc( $table ); - - $name = preg_replace( '/\W/', '', $name ); - - $column = $this->esc( $column ); - try { - foreach ( $this->adapter->get( "SHOW INDEX FROM $table " ) as $ind ) if ( $ind['Key_name'] === $name ) return; + $table = $this->esc( $type ); + $name = preg_replace( '/\W/', '', $name ); + $column = $this->esc( $property ); $this->adapter->exec( "CREATE INDEX $name ON $table ($column) " ); - } catch (\Exception $e ) { + return TRUE; + } catch ( SQLException $e ) { + return FALSE; } } /** * @see QueryWriter::addFK */ - public function addFK( $type, $targetType, $field, $targetField, $isDependent = FALSE ) + 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; - $db = $this->adapter->getCell( 'SELECT DATABASE()' ); - - $cfks = $this->adapter->getCell(' - SELECT CONSTRAINT_NAME - FROM information_schema.KEY_COLUMN_USAGE - WHERE - TABLE_SCHEMA = ? - AND TABLE_NAME = ? - AND COLUMN_NAME = ? AND - CONSTRAINT_NAME != \'PRIMARY\' - AND REFERENCED_TABLE_NAME IS NOT NULL - ', array($db, $type, $field)); - - if ($cfks) return; + //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 { - $fkName = 'fk_'.($type.'_'.$field); - $cName = 'c_'.$fkName; $this->adapter->exec( " - ALTER TABLE {$this->esc($type)} + ALTER TABLE {$table} ADD CONSTRAINT $cName - FOREIGN KEY $fkName ( {$this->esc($field)} ) REFERENCES {$this->esc($targetType)} ( - {$this->esc($targetField)}) ON DELETE " . ( $isDependent ? 'CASCADE' : 'SET NULL' ) . ' ON UPDATE '.( $isDependent ? 'CASCADE' : 'SET NULL' ).';'); - - } catch (\Exception $e ) { + 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 } } @@ -5085,12 +5686,12 @@ class MySQL extends AQueryWriter implements QueryWriter foreach ( $this->getTables() as $t ) { try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); - } catch (\Exception $e ) { + } catch ( SQLException $e ) { } try { $this->adapter->exec( "DROP VIEW IF EXISTS `$t`" ); - } catch (\Exception $e ) { + } catch ( SQLException $e ) { } } @@ -5105,32 +5706,24 @@ 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; /** - * RedBean SQLiteWriter with support for SQLite types + * RedBeanPHP SQLiteWriter with support for SQLite types + * This is a QueryWriter class for RedBeanPHP. + * This QueryWriter provides support for the SQLite database platform. * - * @file RedBean/QueryWriter/SQLiteT.php - * @desc Represents a SQLite Database to RedBean + * @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 { - /** - * @var DBAdapter - */ - - protected $adapter; - - /** - * @var string - */ - protected $quoteCharacter = '`'; - /** * Data types */ @@ -5139,6 +5732,16 @@ class SQLiteT extends AQueryWriter implements QueryWriter const C_DATATYPE_TEXT = 2; const C_DATATYPE_SPECIFIED = 99; + /** + * @var DBAdapter + */ + protected $adapter; + + /** + * @var string + */ + protected $quoteCharacter = '`'; + /** * Gets all information about a table (from a type). * @@ -5159,7 +5762,7 @@ class SQLiteT extends AQueryWriter implements QueryWriter $tableName = $this->esc( $type, TRUE ); $columns = $this->getColumns( $type ); $indexes = $this->getIndexes( $type ); - $keys = $this->getKeys( $type ); + $keys = $this->getKeyMapForType( $type ); $table = array( 'columns' => $columns, @@ -5249,26 +5852,6 @@ class SQLiteT extends AQueryWriter implements QueryWriter return $indexInfoList; } - /** - * Returns the keys for type $type. - * - * @param string $type - * - * @return array $keysInfo keys information - */ - protected function getKeys( $type ) - { - $table = $this->esc( $type, TRUE ); - $keys = $this->adapter->get( "PRAGMA foreign_key_list('$table')" ); - - $keyInfoList = array(); - foreach ( $keys as $k ) { - $keyInfoList['from_' . $k['from'] . '_to_table_' . $k['table'] . '_col_' . $k['to']] = $k; - } - - return $keyInfoList; - } - /** * Adds a foreign key to a type * @@ -5283,47 +5866,51 @@ class SQLiteT extends AQueryWriter implements QueryWriter * @note: cant put this in try-catch because that can hide the fact * that database has been damaged. */ - protected function buildFK( $type, $targetType, $field, $targetField, $constraint = FALSE ) + 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' ); - - $t = $this->getTable( $type ); - $label = 'from_' . $field . '_to_table_' . $targetType . '_col_' . $targetField; - - foreach($t['keys'] as $key) { - if ($key['from'] === $field) return FALSE; - } - + $label = 'from_' . $column . '_to_table_' . $targetTable . '_col_' . $targetColumn; $t['keys'][$label] = array( - 'table' => $targetType, - 'from' => $field, - 'to' => $targetField, + 'table' => $targetTable, + 'from' => $column, + 'to' => $targetColumn, 'on_update' => $consSQL, 'on_delete' => $consSQL ); - $this->putTable( $t ); - return TRUE; } /** - * Add the constraints for a specific database driver: SQLite. - * - * @param string $table table to add fk constrains to - * @param string $table1 first reference table - * @param string $table2 second reference table - * @param string $property1 first reference column - * @param string $property2 second reference column - * - * @return boolean $success whether the constraint has been applied + * @see AQueryWriter::getKeyMapForType */ - protected function constrain( $table, $table1, $table2, $property1, $property2 ) + protected function getKeyMapForType( $type ) { - $firstState = $this->buildFK( $table, $table1, $property1, 'id', TRUE ); - $secondState = $this->buildFK( $table, $table2, $property2, 'id', TRUE ); - - return ( $firstState && $secondState ); + $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; } /** @@ -5458,17 +6045,18 @@ class SQLiteT extends AQueryWriter implements QueryWriter /** * @see QueryWriter::addUniqueIndex */ - public function addUniqueIndex( $type, $columns ) + public function addUniqueConstraint( $type, $properties ) { - $name = 'UQ_' . $this->esc( $type, TRUE ) . implode( '__', $columns ); - + $tableNoQ = $this->esc( $type, TRUE ); + $name = 'UQ_' . $this->esc( $type, TRUE ) . implode( '__', $properties ); $t = $this->getTable( $type ); - - if ( isset( $t['indexes'][$name] ) ) return; - $t['indexes'][$name] = array( 'name' => $name ); - - $this->putTable( $t ); + try { + $this->putTable( $t ); + } catch( SQLException $e ) { + return FALSE; + } + return TRUE; } /** @@ -5489,24 +6077,20 @@ class SQLiteT extends AQueryWriter implements QueryWriter */ public function addIndex( $type, $name, $column ) { - $table = $type; - $table = $this->esc( $table ); + $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 { - - foreach ( $this->adapter->get( "PRAGMA INDEX_LIST($table) " ) as $ind ) { - if ( $ind['name'] === $name ) return; - } - $t = $this->getTable( $type ); $t['indexes'][$name] = array( 'name' => $column ); - $this->putTable( $t ); - } catch( \Exception $exception ) { - //do nothing + return TRUE; + } catch( SQLException $exception ) { + return FALSE; } } @@ -5523,9 +6107,9 @@ class SQLiteT extends AQueryWriter implements QueryWriter /** * @see QueryWriter::addFK */ - public function addFK( $type, $targetType, $field, $targetField, $isDep = FALSE ) + public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE ) { - return $this->buildFK( $type, $targetType, $field, $targetField, $isDep ); + return $this->buildFK( $type, $targetType, $property, $targetProperty, $isDep ); } /** @@ -5538,12 +6122,12 @@ class SQLiteT extends AQueryWriter implements QueryWriter foreach ( $this->getTables() as $t ) { try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); - } catch (\Exception $e ) { + } catch ( SQLException $e ) { } try { $this->adapter->exec( "DROP TABLE IF EXISTS `$t`" ); - } catch (\Exception $e ) { + } catch ( SQLException $e ) { } } @@ -5558,15 +6142,18 @@ 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; /** - * RedBean PostgreSQL Query Writer + * RedBeanPHP PostgreSQL Query Writer. + * This is a QueryWriter class for RedBeanPHP. + * This QueryWriter provides support for the PostgreSQL database platform. * - * @file RedBean/QueryWriter/PostgreSQL.php - * @desc QueryWriter for the PostgreSQL database system. + * @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. @@ -5616,56 +6203,56 @@ class PostgreSQL extends AQueryWriter implements QueryWriter } /** - * Add the constraints for a specific database driver: PostgreSQL. - * - * @param string $table table to add fk constraints to - * @param string $table1 first reference table - * @param string $table2 second reference table - * @param string $property1 first reference column - * @param string $property2 second reference column - * - * @return boolean + * @see AQueryWriter::getKeyMapForType */ - protected function constrain( $table, $table1, $table2, $property1, $property2 ) + protected function getKeyMapForType( $type ) { - try { - $adapter = $this->adapter; - - $fkCode = 'fk' . md5( $table . $property1 . $property2 ); - - $sql = "SELECT c.oid, n.nspname, c.relname, - n2.nspname, c2.relname, cons.conname - FROM pg_class c - JOIN pg_namespace n ON n.oid = c.relnamespace - LEFT OUTER JOIN pg_constraint cons ON cons.conrelid = c.oid - LEFT OUTER JOIN pg_class c2 ON cons.confrelid = c2.oid - LEFT OUTER JOIN pg_namespace n2 ON n2.oid = c2.relnamespace - WHERE c.relkind = 'r' - AND n.nspname = ANY( current_schemas( FALSE ) ) - AND (cons.contype = 'f' OR cons.contype IS NULL) - AND ( cons.conname = '{$fkCode}a' OR cons.conname = '{$fkCode}b' ) - "; - - $rows = $adapter->get( $sql ); - if ( !count( $rows ) ) { - $sql1 = "ALTER TABLE \"$table\" ADD CONSTRAINT - {$fkCode}a FOREIGN KEY ($property1) - REFERENCES \"$table1\" (id) ON DELETE CASCADE ON UPDATE CASCADE "; - - $sql2 = "ALTER TABLE \"$table\" ADD CONSTRAINT - {$fkCode}b FOREIGN KEY ($property2) - REFERENCES \"$table2\" (id) ON DELETE CASCADE ON UPDATE CASCADE "; - - $adapter->exec( $sql1 ); - - $adapter->exec( $sql2 ); - return TRUE; - } - - return FALSE; - } catch (\Exception $e ) { - return FALSE; + $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; } /** @@ -5782,10 +6369,12 @@ class PostgreSQL extends AQueryWriter implements QueryWriter } } - if ( $this->startsWithZeros( $value ) ) return self::C_DATATYPE_TEXT; + if ( is_float( $value ) ) return self::C_DATATYPE_DOUBLE; - if ( $value === FALSE || $value === TRUE || $value === NULL || ( $value instanceof NULL ) || ( is_numeric( $value ) - && floor( $value ) == $value + 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 ) ) { @@ -5832,40 +6421,22 @@ class PostgreSQL extends AQueryWriter implements QueryWriter /** * @see QueryWriter::addUniqueIndex */ - public function addUniqueIndex( $table, $columns ) + public function addUniqueConstraint( $type, $properties ) { - $table = $this->esc( $table, TRUE ); - + $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 - - foreach ( $columns as $k => $v ) { - $columns[$k] = $this->esc( $v ); - } - - $r = $this->adapter->get( "SELECT i.relname AS index_name - FROM pg_class t,pg_class i,pg_index ix,pg_attribute a - WHERE t.oid = ix.indrelid - AND i.oid = ix.indexrelid - AND a.attrelid = t.oid - AND a.attnum = ANY(ix.indkey) - AND t.relkind = 'r' - AND t.relname = '$table' - ORDER BY t.relname, i.relname;" ); - $name = "UQ_" . sha1( $table . implode( ',', $columns ) ); - - if ( $r ) { - foreach ( $r as $i ) { - if ( strtolower( $i['index_name'] ) == strtolower( $name ) ) { - return; - } - } - } - - $sql = "ALTER TABLE \"$table\" + $sql = "ALTER TABLE {$table} ADD CONSTRAINT $name UNIQUE (" . implode( ',', $columns ) . ")"; - - $this->adapter->exec( $sql ); + try { + $this->adapter->exec( $sql ); + } catch( SQLException $e ) { + return FALSE; + } + return TRUE; } /** @@ -5885,49 +6456,39 @@ class PostgreSQL extends AQueryWriter implements QueryWriter /** * @see QueryWriter::addIndex */ - public function addIndex( $type, $name, $column ) + public function addIndex( $type, $name, $property ) { - $table = $type; - $table = $this->esc( $table ); - + $table = $this->esc( $type ); $name = preg_replace( '/\W/', '', $name ); - $column = $this->esc( $column ); - - if ( $this->adapter->getCell( "SELECT COUNT(*) FROM pg_class WHERE relname = '$name'" ) ) { - return; - } + $column = $this->esc( $property ); try { - $this->adapter->exec( "CREATE INDEX $name ON $table ($column) " ); - } catch (\Exception $e ) { - //do nothing + $this->adapter->exec( "CREATE INDEX {$name} ON $table ({$column}) " ); + return TRUE; + } catch ( SQLException $e ) { + return FALSE; } } /** * @see QueryWriter::addFK */ - public function addFK( $type, $targetType, $field, $targetField, $isDep = FALSE ) + public function addFK( $type, $targetType, $property, $targetProperty, $isDep = FALSE ) { - $db = $this->adapter->getCell( 'SELECT current_database()' ); - $cfks = $this->adapter->getCell(' - SELECT constraint_name - FROM information_schema.KEY_COLUMN_USAGE - WHERE - table_catalog = ? - AND table_schema = ANY( current_schemas( FALSE ) ) - AND table_name = ? - AND column_name = ? - ', array($db, $type, $field)); - + $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{ - if (!$cfks) { - $delRule = ( $isDep ? 'CASCADE' : 'SET NULL' ); - $this->adapter->exec( "ALTER TABLE {$this->esc($type)} - ADD FOREIGN KEY ( {$this->esc($field)} ) REFERENCES {$this->esc($targetType)} ( - {$this->esc($targetField)}) ON DELETE $delRule ON UPDATE $delRule DEFERRABLE ;" ); - } - } catch (\Exception $e ) { + $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; } } @@ -5947,20 +6508,20 @@ class PostgreSQL extends AQueryWriter implements QueryWriter $this->adapter->exec( 'SET CONSTRAINTS ALL IMMEDIATE' ); } - } } namespace RedBeanPHP { /** - * RedBean\Exception Base + * RedBean\Exception Base. + * Represents the base class for RedBeanPHP\Exceptions. * - * @file RedBean/Exception.php - * @desc Represents the base class for RedBean\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. @@ -5975,20 +6536,20 @@ namespace RedBeanPHP\RedException { use RedBeanPHP\RedException as RedException; /** - * RedBean\Exception SQL + * SQL Exception. + * Represents a generic database exception independent of the underlying driver. * - * @file RedBean/Exception/SQL.php - * @desc 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 */ @@ -6040,17 +6601,14 @@ use RedBeanPHP\QueryWriter as QueryWriter; use RedBeanPHP\RedException\Security as Security; use RedBeanPHP\SimpleModel as SimpleModel; use RedBeanPHP\BeanHelper as BeanHelper; -use RedBeanPHP\RedException\SQL as SQL; +use RedBeanPHP\RedException\SQL as SQLException; use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter; use RedBeanPHP\OODB as OODB; +use RedBeanPHP\Cursor as Cursor; +use RedBeanPHP\Cursor\NullCursor as NullCursor; /** - * Abstract Repository - * - * @file RedBean/Repository.php - * @desc RedBean Object Database - * @author Gabor de Mooij and the RedBeanPHP community - * @license BSD/GPLv2 + * Abstract Repository. * * OODB manages two repositories, a fluid one that * adjust the database schema on-the-fly to accomodate for @@ -6059,6 +6617,11 @@ use RedBeanPHP\OODB as 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. @@ -6080,6 +6643,46 @@ abstract class Repository */ protected $writer; + /** + * Stores a bean and its lists in one run. + * + * @param OODBBean $bean + * + * @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 SimpleModel ) ? $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 ) ) { + $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, @@ -6345,7 +6948,6 @@ abstract class Repository * * @return array * - * @throws SQL */ public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() ) { @@ -6360,13 +6962,33 @@ abstract class Repository $beans = $this->convertToBeans( $type, $this->writer->queryRecord( $type, $conditions, $sql, $bindings ) ); return $beans; - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { $this->handleException( $exception ); } return array(); } + /** + * Finds a BeanCollection. + * + * @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 @@ -6386,8 +7008,6 @@ abstract class Repository * @param OODBBean|SimpleModel $bean bean to store * * @return integer|string - * - * @throws Security */ public function store( $bean ) { @@ -6398,7 +7018,7 @@ abstract class Repository $this->oodb->signal( 'update', $bean ); $processLists = $this->hasListsOrObjects( $bean ); //check again, might have changed by model! if ( $processLists ) { - $this->processLists( $bean ); + $this->storeBeanWithLists( $bean ); } else { $this->storeBean( $bean ); } @@ -6429,7 +7049,7 @@ abstract class Repository $collection = array(); try { $rows = $this->writer->queryRecord( $type, array( 'id' => $ids ) ); - } catch ( SQL $e ) { + } catch ( SQLException $e ) { $this->handleException( $e ); $rows = FALSE; } @@ -6484,7 +7104,7 @@ abstract class Repository * * @return integer * - * @throws SQL + * @throws SQLException */ public function count( $type, $addSQL = '', $bindings = array() ) { @@ -6495,7 +7115,7 @@ abstract class Repository try { return (int) $this->writer->queryRecordCount( $type, array(), $addSQL, $bindings ); - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE, QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN ) ) ) { @@ -6515,7 +7135,7 @@ abstract class Repository * * @return void * - * @throws Security + * @throws SQLException */ public function trash( $bean ) { @@ -6534,7 +7154,7 @@ abstract class Repository } try { $this->writer->deleteRecord( $bean->getMeta( 'type' ), array( 'id' => array( $bean->id ) ), NULL ); - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { $this->handleException( $exception ); } $bean->id = 0; @@ -6563,7 +7183,7 @@ abstract class Repository * * @return boolean * - * @throws SQL + * @throws SQLException */ public function wipe( $type ) { @@ -6571,7 +7191,7 @@ abstract class Repository $this->writer->wipe( $type ); return TRUE; - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE ) ) ) { throw $exception; } @@ -6579,8 +7199,6 @@ abstract class Repository return FALSE; } } - - } } @@ -6595,18 +7213,12 @@ use RedBeanPHP\RedException as RedException; use RedBeanPHP\RedException\Security as Security; use RedBeanPHP\SimpleModel as SimpleModel; use RedBeanPHP\BeanHelper as BeanHelper; -use RedBeanPHP\RedException\SQL as SQL; +use RedBeanPHP\RedException\SQL as SQLException; use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter; use RedBeanPHP\Repository as Repository; /** - * Fluid Repository - * - * @file RedBean/Repository/Fluid.php - * @desc RedBean Object Database - * @author Gabor de Mooij and the RedBeanPHP community - * @license BSD/GPLv2 - * + * Fluid Repository. * OODB manages two repositories, a fluid one that * adjust the database schema on-the-fly to accomodate for * new bean types (tables) and new properties (columns) and @@ -6614,13 +7226,17 @@ use RedBeanPHP\Repository as Repository; * 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. * @@ -6664,62 +7280,27 @@ class Fluid extends Repository } /** - * Processes all column based build commands. - * A build command is an additional instruction for the Query Writer. It is processed only when - * a column gets created. The build command is often used to instruct the writer to write some - * extra SQL to create indexes or constraints. Build commands are stored in meta data of the bean. - * They are only for internal use, try to refrain from using them in your code directly. - * - * @param string $table name of the table to process build commands for - * @param string $property name of the property to process build commands for - * @param OODBBean $bean bean that contains the build commands - * - * @return void - */ - private function processBuildCommands( $table, $property, OODBBean $bean ) - { - if ( $inx = ( $bean->getMeta( 'buildcommand.indexes' ) ) ) { - if ( isset( $inx[$property] ) ) { - $this->writer->addIndex( $table, $inx[$property], $property ); - } - } - } - - /** - * Adds the unique constraints described in the meta data. - * - * @param OODBBean $bean bean - * - * @return void - */ - private function addUniqueConstraints( OODBBean $bean ) - { - if ( $uniques = $bean->getMeta( 'buildcommand.unique' ) ) { - $table = $bean->getMeta( 'type' ); - foreach ( $uniques as $unique ) { - if ( !$this->oodb->isChilled( $table ) ) $this->writer->addUniqueIndex( $table, $unique ); - } - } - } - - /** - * Molds the table to fit the bean data. + * 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 string $property property to store + * @param mixed $value value to store * * @return void */ - private function moldTable( OODBBean $bean, $property, $value ) + private function modifySchema( OODBBean $bean, $property, $value ) { + $doFKStuff = FALSE; $table = $bean->getMeta( 'type' ); $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" ); @@ -6736,38 +7317,29 @@ class Fluid extends Repository 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 ); - $this->processBuildCommands( $table, $property, $bean ); + $doFKStuff = TRUE; } - } - } - - /** - * Processes embedded beans. - * Each embedded bean will be indexed and foreign keys will - * be created if the bean is in the dependency list. - * - * @param OODBBean $bean bean - * @param array $embeddedBeans embedded beans - * - * @return void - */ - private function addForeignKeysForParentBeans( $bean, $embeddedBeans ) - { - $cachedIndex = array(); - foreach ( $embeddedBeans as $linkField => $embeddedBean ) { - $beanType = $bean->getMeta( 'type' ); - $embeddedType = $embeddedBean->getMeta( 'type' ); - $key = $beanType . '|' . $embeddedType . '>' . $linkField; - if ( !isset( $cachedIndex[$key] ) ) { - $this->writer->addIndex( $bean->getMeta( 'type' ), - 'index_foreignkey_' . $beanType . '_' . $embeddedType, - $linkField ); - $this->writer->addFK( $beanType, $embeddedType, $linkField, 'id', FALSE ); - $cachedIndex[$key] = 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); + } + } } } } @@ -6786,11 +7358,10 @@ class Fluid extends Repository * * @throws Security */ - private function processAdditions( $bean, $ownAdditions ) + protected function processAdditions( $bean, $ownAdditions ) { $beanType = $bean->getMeta( 'type' ); - $cachedIndex = array(); foreach ( $ownAdditions as $addition ) { if ( $addition instanceof OODBBean ) { @@ -6800,18 +7371,14 @@ class Fluid extends Repository $addition->$myFieldLink = $bean->id; $addition->setMeta( 'cast.' . $myFieldLink, 'id' ); - $this->store( $addition ); - $additionType = $addition->getMeta( 'type' ); - $key = $additionType . '|' . $beanType . '>' . $myFieldLink; - if ( !isset( $cachedIndex[$key] ) ) { - $this->writer->addIndex( $additionType, - 'index_foreignkey_' . $additionType . '_' . $beanType, - $myFieldLink ); - $isDep = $bean->getMeta( 'sys.exclusive-'.$additionType ); - $this->writer->addFK( $additionType, $beanType, $myFieldLink, 'id', $isDep ); - $cachedIndex[$key] = TRUE; + + 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' ); } @@ -6834,40 +7401,23 @@ class Fluid extends Repository $this->check( $bean ); $table = $bean->getMeta( 'type' ); $this->createTableIfNotExists( $bean, $table ); - $updateValues = $this->getUpdateValues( $bean ); - $this->addUniqueConstraints( $bean ); + + $updateValues = array(); + foreach ( $bean as $property => $value ) { + if ( $property !== 'id' ) { + $this->modifySchema( $bean, $property, $value ); + } + 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 ); } - /** - * Returns a structured array of update values using the following format: - * array( - * property => $property, - * value => $value - * ); - * - * @param OODBBean $bean bean to extract update values from - * - * @return array - */ - protected function getUpdateValues( OODBBean $bean ) - { - $updateValues = array(); - foreach ( $bean as $property => $value ) { - if ( $property !== 'id' ) { - $this->moldTable( $bean, $property, $value ); - } - if ( $property !== 'id' ) { - $updateValues[] = array( 'property' => $property, 'value' => $value ); - } - } - - return $updateValues; - } - /** * Handles\Exceptions. Suppresses exceptions caused by missing structures. * @@ -6888,47 +7438,6 @@ class Fluid extends Repository } } - /** - * Stores a bean and its lists in one run. - * - * @param OODBBean $bean - * - * @return void - */ - protected function processLists( OODBBean $bean ) - { - $sharedAdditions = $sharedTrashcan = $sharedresidue = $sharedItems = $ownAdditions = $ownTrashcan = $ownresidue = $embeddedBeans = array(); //Define groups - foreach ( $bean as $property => $value ) { - $value = ( $value instanceof SimpleModel ) ? $value->unbox() : $value; - if ( $value instanceof OODBBean ) { - $this->processEmbeddedBean( $embeddedBeans, $bean, $property, $value ); - } elseif ( is_array( $value ) ) { - $originals = $bean->getMeta( 'sys.shadow.' . $property, array() ); - $bean->setMeta( 'sys.shadow.' . $property, NULL ); //clear shadow - if ( strpos( $property, 'own' ) === 0 ) { - list( $ownAdditions, $ownTrashcan, $ownresidue ) = $this->processGroups( $originals, $value, $ownAdditions, $ownTrashcan, $ownresidue ); - $listName = lcfirst( substr( $property, 3 ) ); - if ($bean->getMeta( 'sys.exclusive-'. $listName ) ) { - OODBBean::setMetaAll( $ownTrashcan, 'sys.garbage', TRUE ); - } - 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->addForeignKeysForParentBeans( $bean, $embeddedBeans ); - $this->processTrashcan( $bean, $ownTrashcan ); - $this->processAdditions( $bean, $ownAdditions ); - $this->processResidue( $ownresidue ); - $this->processSharedTrashcan( $bean, $sharedTrashcan ); - $this->processSharedAdditions( $bean, $sharedAdditions ); - $this->processSharedResidue( $bean, $sharedresidue ); - } - - /** * Dispenses a new bean (a OODBBean Bean Object) * of the specified type. Always @@ -6992,7 +7501,7 @@ class Fluid extends Repository } else { try { $rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) ); - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { if ( $this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, @@ -7029,18 +7538,12 @@ use RedBeanPHP\RedException as RedException; use RedBeanPHP\RedException\Security as Security; use RedBeanPHP\SimpleModel as SimpleModel; use RedBeanPHP\BeanHelper as BeanHelper; -use RedBeanPHP\RedException\SQL as SQL; +use RedBeanPHP\RedException\SQL as SQLException; use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter; use RedBeanPHP\Repository as Repository; /** - * Frozen Repository - * - * @file RedBean/Repository/Frozen.php - * @desc RedBean Object Database - * @author Gabor de Mooij and the RedBeanPHP community - * @license BSD/GPLv2 - * + * Frozen Repository. * OODB manages two repositories, a fluid one that * adjust the database schema on-the-fly to accomodate for * new bean types (tables) and new properties (columns) and @@ -7048,6 +7551,11 @@ use RedBeanPHP\Repository as Repository; * 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. @@ -7133,45 +7641,6 @@ class Frozen extends Repository } } - /** - * Stores a bean and its lists in one run. - * - * @param OODBBean $bean - * - * @return void - */ - protected function processLists( OODBBean $bean ) - { - $sharedAdditions = $sharedTrashcan = $sharedresidue = $sharedItems = $ownAdditions = $ownTrashcan = $ownresidue = $embeddedBeans = array(); //Define groups - foreach ( $bean as $property => $value ) { - $value = ( $value instanceof SimpleModel ) ? $value->unbox() : $value; - if ( $value instanceof OODBBean ) { - $this->processEmbeddedBean( $embeddedBeans, $bean, $property, $value ); - } elseif ( is_array( $value ) ) { - $originals = $bean->getMeta( 'sys.shadow.' . $property, array() ); - $bean->setMeta( 'sys.shadow.' . $property, NULL ); //clear shadow - if ( strpos( $property, 'own' ) === 0 ) { - list( $ownAdditions, $ownTrashcan, $ownresidue ) = $this->processGroups( $originals, $value, $ownAdditions, $ownTrashcan, $ownresidue ); - $listName = lcfirst( substr( $property, 3 ) ); - if ($bean->getMeta( 'sys.exclusive-'. $listName ) ) { - OODBBean::setMetaAll( $ownTrashcan, 'sys.garbage', TRUE ); - } - 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 ); - } - /** * Dispenses a new bean (a OODBBean Bean Object) * of the specified type. Always @@ -7234,7 +7703,7 @@ class Frozen extends Repository } else { try { $rows = $this->writer->queryRecord( $type, array( 'id' => array( $id ) ) ); - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { if ( $this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, @@ -7242,7 +7711,6 @@ class Frozen extends Repository ) ) { throw $exception; //only throw if frozen - } } if ( empty( $rows ) ) { @@ -7277,20 +7745,19 @@ use RedBeanPHP\Repository as Repository; use RedBeanPHP\Repository\Fluid as FluidRepo; use RedBeanPHP\Repository\Frozen as FrozenRepo; - /** - * RedBean Object Oriented DataBase - * - * @file RedBean/OODB.php - * @desc RedBean Object Database - * @author Gabor de Mooij and the RedBeanPHP community - * @license BSD/GPLv2 - * + * 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. @@ -7378,15 +7845,16 @@ class OODB extends Observable /** * Constructor, requires a query writer. * - * @param QueryWriter $writer writer + * @param QueryWriter $writer writer + * @param array|boolean $frozen mode of operation: TRUE (frozen), FALSE (default, fluid) or ARRAY (chilled) */ - public function __construct( QueryWriter $writer ) + public function __construct( QueryWriter $writer, $frozen = FALSE ) { if ( $writer instanceof QueryWriter ) { $this->writer = $writer; } - $this->freeze( FALSE ); + $this->freeze( $frozen ); } /** @@ -7558,6 +8026,22 @@ class OODB extends Observable 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 $addSQL SQL to be used in query + * @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not) + * + * @return array + * + * @throws SQL + */ + 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. * Not part of the Object Database interface! @@ -7800,18 +8284,19 @@ use RedBeanPHP\Adapter\DBAdapter as DBAdapter; use RedBeanPHP\Adapter as Adapter; /** - * @file RedBean/ToolBox.php - * @desc A RedBeanPHP-wide service locator - * @author Gabor de Mooij and the RedBeanPHP community - * @license BSD/GPLv2 - * * 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. @@ -7904,20 +8389,23 @@ use RedBeanPHP\RedException\Security as Security; use RedBeanPHP\OODBBean as OODBBean; /** - * RedBean Finder + * 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 RedBean/Finder.php - * @desc Helper class to harmonize APIs. + * @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 */ @@ -7967,8 +8455,9 @@ class Finder } /** - * @see Finder::find - * The variation also exports the beans (i.e. it returns arrays). + * Like find() but also exports the beans as an array. + * + * @see Finder::findAndExport * * @param string $type type the type of bean you are looking for * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause @@ -7987,8 +8476,10 @@ class Finder } /** + * Like find() but returns just one bean instead of an array of beans. + * This method will return only the first bean of the array. + * * @see Finder::find - * This variation returns the first bean only. * * @param string $type type the type of bean you are looking for * @param string $sql sql SQL query to find the desired bean, starting right after WHERE clause @@ -8010,8 +8501,10 @@ class Finder } /** + * Like find() but returns the last bean of the result array. + * Opposite of Finder::findLast(). + * * @see Finder::find - * This variation returns the last bean only. * * @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 @@ -8031,9 +8524,10 @@ class Finder } /** + * Tries to find beans of a certain type, + * if no beans are found, it dispenses a bean of that type. + * * @see Finder::find - * Convience method. Tries to find beans of a certain type, - * if no beans are found, it dispenses a bean of that type. * * @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 @@ -8051,6 +8545,219 @@ class Finder 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 + * + * @return OODBBean + */ + public function findOrCreate( $type, $like = array() ) + { + $beans = $this->findLike( $type, $like ); + if ( count( $beans ) ) { + $bean = reset( $beans ); + return $bean; + } + + $bean = $this->redbean->dispense( $type ); + $bean->import( $like ); + $this->redbean->store( $bean ); + 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) + * + * @return array + */ + public function findLike( $type, $conditions = array(), $sql = '' ) + { + if ( count( $conditions ) > 0 ) { + foreach( $conditions as $key => $condition ) { + if ( !count( $condition ) ) unset( $conditions[$key] ); + } + } + + return $this->redbean->find( $type, $conditions, $sql ); + } + + /** + * 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'. + * } + * ) + * + * 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. + * + * @param string|array $types a list of types (either array or comma separated string) + * @param string|array $sqlOrArr 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, $bindings = array(), $remappings = array(), $queryTemplate = ' %s.%s AS %s__%s' ) + { + if ( !is_array( $types ) ) $types = explode( ',', $types ); + 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 ) { + $pattern = " {$type}.*"; + if ( strpos( $sql, $pattern ) !== FALSE ) { + $newSelectorArray = array(); + $columns = $writer->getColumns( $type ); + foreach( $columns as $column => $definition ) { + $newSelectorArray[] = sprintf( $queryTemplate, $type, $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']; + $matcher = $remapping['matcher']; + $do = $remapping['do']; + foreach( $beans[$a] as $bean ) { + foreach( $beans[$b] as $putBean ) { + if ( $matcher( $bean, $putBean, $beans ) ) $do( $bean, $putBean, $beans, $remapping ); + } + } + } + return $beans; + } } } @@ -8063,24 +8770,24 @@ use RedBeanPHP\QueryWriter as QueryWriter; use RedBeanPHP\OODBBean as OODBBean; use RedBeanPHP\RedException as RedException; use RedBeanPHP\RedException\Security as Security; -use RedBeanPHP\RedException\SQL as SQL; +use RedBeanPHP\RedException\SQL as SQLException; use RedBeanPHP\ToolBox as ToolBox; /** - * Association Manager + * Association Manager. + * Manages simple bean associations. * - * @file RedBean/AssociationManager.php - * @desc Manages simple bean associations. + * @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 */ @@ -8138,7 +8845,7 @@ class AssociationManager extends Observable $sourceType = $bean->getMeta( 'type' ); try { return $this->writer->queryRecordRelated( $sourceType, $type, $ids, $sql, $bindings ); - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { $this->handleException( $exception ); return array(); } @@ -8160,61 +8867,30 @@ class AssociationManager extends Observable */ 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'; } - - if ( !$this->oodb->isFrozen() ) { - //Dont mess with other tables, only add the unique constraint if: - //1. the table exists (otherwise we cant inspect it) - //2. the table only contains N-M fields: ID, N-ID, M-ID. - $unique = array( $property1, $property2 ); - $type = $bean->getMeta( 'type' ); - $tables = $this->writer->getTables(); - if ( in_array( $type, $tables ) && !$this->oodb->isChilled( $type ) ) { - $columns = ( $this->writer->getColumns( $type ) ); - if ( count( $columns ) === 3 - && isset( $columns[ 'id' ] ) - && isset( $columns[ $property1 ] ) - && isset( $columns[ $property2 ] ) ) { - $bean->setMeta( 'buildcommand.unique', array( $unique ) ); - } - } - - //add a build command for Single Column Index (to improve performance in case unqiue cant be used) - $indexName1 = 'index_for_' . $bean->getMeta( 'type' ) . '_' . $property1; - $indexName2 = 'index_for_' . $bean->getMeta( 'type' ) . '_' . $property2; - - $bean->setMeta( 'buildcommand.indexes', array( $property1 => $indexName1, $property2 => $indexName2 ) ); - } $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 ); - - //On creation, add constraints.... - if ( !$this->oodb->isFrozen() && - $bean->getMeta( 'buildreport.flags.created' ) - ) { - $bean->setMeta( 'buildreport.flags.created', 0 ); - if ( !$this->oodb->isFrozen() ) { - $this->writer->addConstraintForTypes( $bean1->getMeta( 'type' ), $bean2->getMeta( 'type' ) ); - } - } $results[] = $id; - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_INTEGRITY_CONSTRAINT_VIOLATION ) ) ) { @@ -8318,7 +8994,7 @@ class AssociationManager extends Observable try { return $this->writer->queryRecordCountRelated( $beanType, $type, $bean->id, $sql, $bindings ); - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { $this->handleException( $exception ); return 0; @@ -8369,7 +9045,7 @@ class AssociationManager extends Observable $bean = reset( $beans ); $this->oodb->trash( $bean ); } - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { $this->handleException( $exception ); } } @@ -8393,7 +9069,7 @@ class AssociationManager extends Observable $this->oodb->store( $bean ); try { $this->writer->deleteRelations( $bean->getMeta( 'type' ), $type, $bean->id ); - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { $this->handleException( $exception ); } } @@ -8455,23 +9131,22 @@ use RedBeanPHP\ToolBox as ToolBox; use RedBeanPHP\OODBBean as OODBBean; /** - * Bean Helper Interface - * - * @file RedBean/IBeanHelper.php - * @desc Interface for Bean Helper. - * @author Gabor de Mooij and the RedBeanPHP Community - * @license BSD/GPLv2 - * + * Bean Helper Interface. + * * Interface for Bean Helper. * A little bolt that glues the whole machinery together. * + * @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, @@ -8513,22 +9188,22 @@ use RedBeanPHP\SimpleModelHelper as SimpleModelHelper; /** * 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 RedBean/BeanHelperFacade.php - * @desc Finds the toolbox for the bean. + * @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. * @@ -8536,6 +9211,33 @@ class SimpleFacadeBeanHelper implements BeanHelper */ 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 + */ + 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 closure $factory + * + * @return void + */ + public static function setFactoryFunction( $factory ) + { + self::$factory = $factory; + } + /** * @see BeanHelper::getToolbox */ @@ -8589,34 +9291,6 @@ class SimpleFacadeBeanHelper implements BeanHelper { return Facade::getExtractedToolbox(); } - - /** - * Factory method using a customizable factory function to create - * the instance of the Simple Model. - * - * @param string $modelClassName name of the class - * - * @return SimpleModel - */ - 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 closure $factory - * - * @return void - */ - public static function setFactoryFunction( $factory ) - { - self::$factory = $factory; - } - } } @@ -8628,18 +9302,21 @@ use RedBeanPHP\OODBBean as OODBBean; * SimpleModel * Base Model For All RedBeanPHP Models using FUSE. * - * @file RedBean/SimpleModel.php - * @desc Part of 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 { - /** * @var OODBBean */ @@ -8737,15 +9414,16 @@ use RedBeanPHP\OODBBean as OODBBean; use RedBeanPHP\Observable as Observable; /** - * RedBean Model Helper + * RedBean Model Helper. * - * @file RedBean/ModelHelper.php - * @desc Connects beans to models, in essence + * 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 * - * This is the core of so-called FUSE. - * + * @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. @@ -8787,25 +9465,25 @@ use RedBeanPHP\AssociationManager as AssociationManager; use RedBeanPHP\OODBBean as OODBBean; /** - * RedBean Tag Manager. + * RedBeanPHP Tag Manager. + * * The tag manager offers an easy way to quickly implement basic tagging * functionality. * - * @file RedBean/TagManager.php - * @desc RedBean Tag Manager - * @author Gabor de Mooij and the RedBeanPHP community - * @license BSD/GPLv2 - * * 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 */ @@ -8841,21 +9519,6 @@ class TagManager return $tags; } - /** - * Constructor. - * The tag manager offers an easy way to quickly implement basic tagging - * functionality. - * - * @param ToolBox $toolbox - */ - public function __construct( ToolBox $toolbox ) - { - $this->toolbox = $toolbox; - $this->redbean = $toolbox->getRedBean(); - - $this->associationManager = $this->redbean->getAssociationManager(); - } - /** * Finds a tag bean by it's title. * Internal method. @@ -8877,6 +9540,21 @@ class TagManager return NULL; } + /** + * Constructor. + * The tag manager offers an easy way to quickly implement basic tagging + * functionality. + * + * @param ToolBox $toolbox + */ + 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 ore more * of the listed tags. If the third parameter is TRUE this method @@ -9051,20 +9729,24 @@ use RedBeanPHP\ToolBox as ToolBox; use RedBeanPHP\OODBBean as OODBBean; /** - * Label Maker + * 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 RedBean/LabelMaker.php - * @desc Makes so-called label 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 */ @@ -9086,6 +9768,8 @@ class LabelMaker * 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 * @@ -9105,11 +9789,19 @@ class LabelMaker /** * Gathers labels from beans. This function loops through the beans, - * collects the values of the name properties of each individual bean + * 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). * - * @param array $beans list of beans to loop + * 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 */ @@ -9127,8 +9819,29 @@ class LabelMaker } /** - * Returns a label or an array of labels for use as ENUMs. - * + * 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 @@ -9176,7 +9889,7 @@ use RedBeanPHP\TagManager as TagManager; use RedBeanPHP\DuplicationManager as DuplicationManager; use RedBeanPHP\LabelMaker as LabelMaker; use RedBeanPHP\Finder as Finder; -use RedBeanPHP\RedException\SQL as SQL; +use RedBeanPHP\RedException\SQL as SQLException; use RedBeanPHP\RedException\Security as Security; use RedBeanPHP\Logger as Logger; use RedBeanPHP\Logger\RDefault as RDefault; @@ -9194,17 +9907,17 @@ use RedBeanPHP\Driver\RPDO as RPDO; * RedBean Facade * * Version Information - * RedBean Version @version 4.1 - * - * @file RedBean/Facade.php - * @desc Convenience class for RedBeanPHP. - * @author Gabor de Mooij and the RedBeanPHP Community - * @license BSD/GPLv2 - * + * RedBean Version @version 4.2 + * * 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. @@ -9214,12 +9927,7 @@ class Facade /** * RedBeanPHP version constant. */ - const C_REDBEANPHP_VERSION = '4.1'; - - /** - * @var array - */ - public static $toolboxes = array(); + const C_REDBEANPHP_VERSION = '4.2'; /** * @var ToolBox @@ -9267,15 +9975,20 @@ class Facade private static $finder; /** - * @var string + * @var Logger */ - public static $currentDB = ''; + private static $logger; /** * @var array */ private static $plugins = array(); + /** + * @var string + */ + private static $exportCaseStyle = 'default'; + /** * Not in use (backward compatibility SQLHelper) */ @@ -9284,7 +9997,12 @@ class Facade /** * @var string */ - private static $exportCaseStyle = 'default'; + public static $currentDB = ''; + + /** + * @var array + */ + public static $toolboxes = array(); /** * Internal Query function, executes the desired query. Used by @@ -9303,7 +10021,7 @@ class Facade if ( !self::$redbean->isFrozen() ) { try { $rs = Facade::$adapter->$method( $sql, $bindings ); - } catch ( SQL $exception ) { + } catch ( SQLException $exception ) { if ( self::$writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_COLUMN, @@ -9497,9 +10215,8 @@ class Facade if ( !isset( $writers[$wkey] ) ) trigger_error( 'Unsupported DSN: '.$wkey ); $writerClass = '\\RedBeanPHP\\QueryWriter\\'.$writers[$wkey]; $writer = new $writerClass( $adapter ); - $redbean = new OODB( $writer ); + $redbean = new OODB( $writer, $frozen ); - $redbean->freeze( ( $frozen === TRUE ) ); self::$toolboxes[$key] = new ToolBox( $redbean, $adapter, $writer ); } @@ -9562,6 +10279,14 @@ class Facade return $logger; } + /** + * Turns on the fancy debugger. + */ + public static function fancyDebug( $toggle ) + { + 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. @@ -9683,15 +10408,19 @@ class Facade * This function will remove the specified OODBBean * Bean Object from the database. * - * @param OODBBean|SimpleModel $bean bean you want to remove from 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. + * + * @param string|OODBBean|SimpleModel $bean bean you want to remove from database + * @param integer $id (optional) * * @return void - * - * @throws Security */ - public static function trash( $bean ) + public static function trash( $beanOrType, $id = NULL ) { - self::$redbean->trash( $bean ); + if ( is_string( $beanOrType ) ) return self::trash( self::load( $beanOrType, $id ) ); + return self::$redbean->trash( $beanOrType ); } /** @@ -9709,7 +10438,14 @@ class Facade public static function dispense( $typeOrBeanArray, $num = 1, $alwaysReturnArray = FALSE ) { if ( is_array($typeOrBeanArray) ) { - if ( !isset( $typeOrBeanArray['_type'] ) ) throw new RedException('Missing _type field.'); + + 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( $beanArray ); + return $list; + } + $import = $typeOrBeanArray; $type = $import['_type']; unset( $import['_type'] ); @@ -9871,6 +10607,40 @@ class Facade return self::$finder->findLast( $type, $sql, $bindings ); } + /** + * Finds a bean collection. + * Use this for large datasets. + * + * @param string $type type the type of bean you are looking for + * @param string $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 BeanCollection + */ + public static function findCollection( $type, $sql = NULL, $bindings = array() ) + { + return self::$finder->findCollection( $type, $sql, $bindings ); + } + + /** + * Finds multiple types of beans at once and offers additional + * remapping functionality. This is a very powerful yet complex function. + * For details see Finder::findMulti(). + * + * @see Finder::findMulti() + * + * @param array|string $types a list of bean types to find + * @param string|array $sqlOrArr SQL query string or result set array + * @param array $bindings SQL bindings + * @param array $remappings An array of remapping arrays containing closures + * + * @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. @@ -10013,6 +10783,17 @@ class Facade return self::query( 'getAssocRow', $sql, $bindings ); } + /** + * Returns the insert ID for databases that support/require this + * functionality. Alias for R::getAdapter()->getInsertID(). + * + * @return mixed + */ + 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. @@ -10369,13 +11150,30 @@ class Facade /** * Generates question mark slots for an array of values. * - * @param array $array + * @param array $array array to generate question mark slots for * * @return string */ - public static function genSlots( $array ) + public static function genSlots( $array, $template = NULL ) { - return ( count( $array ) ) ? implode( ',', array_fill( 0, count( $array ), '?' ) ) : ''; + $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(). + * + * @param array $array array to flatten + * + * @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; } /** @@ -10793,10 +11591,142 @@ class Facade * @param string $function * */ - public static function bindFunc( $mode, $field, $function ) { + public static function bindFunc( $mode, $field, $function ) + { self::$redbean->bindFunc( $mode, $field, $function ); } + /** + * Sets global aliases. + * + * @param array $list + * + * @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 + * + * @return OODBBean + */ + public static function findOrCreate( $type, $like = array() ) + { + return self::$finder->findOrCreate( $type, $like ); + } + + /** + * 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 + * + * @return array + */ + public static function findLike( $type, $like = array(), $sql = '' ) + { + return self::$finder->findLike( $type, $like, $sql ); + } + + /** + * Starts logging queries. + * Use this method to start logging SQL queries being + * executed by the adapter. + * + * @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, comfortable method to stop logging of queries. + * + * @return void + */ + public static function stopLogging() + { + self::debug( FALSE ); + } + + /** + * Returns the log entries written after the startLogging. + * + * @return array + */ + public static function getLogs() + { + return self::getLogger()->getLogs(); + } + + /** + * Resets the Query counter. + * + * @return integer + */ + public static function resetQueryCount() + { + self::$adapter->getDatabase()->resetCounter(); + } + + /** + * Returns the number of SQL 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 + */ + public static function getLogger() + { + return self::$adapter->getDatabase()->getLogger(); + } + + /** + * Alias for setAutoResolve() method on OODBBean. + * Enables or disables auto-resolving fetch types. + * Auto-resolving aliased parent beans is convenient but can + * be slower and can create infinite recursion if you + * used aliases to break cyclic relations in your domain. + * + * @param boolean $automatic TRUE to enable automatic resolving aliased parents + * + * @return void + */ + public static function setAutoResolve( $automatic = TRUE ) + { + OODBBean::setAutoResolve( (boolean) $automatic ); + } + /** * Dynamically extends the facade with a plugin. * Using this method you can register your plugin with the facade and then @@ -10854,20 +11784,20 @@ use RedBeanPHP\OODBBean as OODBBean; use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter; /** - * Duplication Manager + * Duplication Manager. + * Creates deep copies of beans. * - * @file RedBean/DuplicationManager.php - * @desc Creates deep copies of beans + * @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 */ @@ -10903,37 +11833,12 @@ class DuplicationManager */ protected $cacheTables = FALSE; - /** - * 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; - } - /** * 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 + * @param string $shared name of the shared list + * @param array $beans array with shared beans to copy * * @return void */ @@ -10952,10 +11857,10 @@ class DuplicationManager * 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 + * @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 */ @@ -10994,7 +11899,7 @@ class DuplicationManager * 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 array $trail list of former beans * @param OODBBean $bean currently selected bean * * @return boolean @@ -11062,9 +11967,9 @@ class DuplicationManager /** * @see DuplicationManager::dup * - * @param OODBBean $bean bean to be copied - * @param array $trail trail to prevent infinite loops - * @param boolean $preserveIDs preserve IDs + * @param OODBBean $bean bean to be copied + * @param array $trail trail to prevent infinite loops + * @param boolean $preserveIDs preserve IDs * * @return OODBBean */ @@ -11116,12 +12021,45 @@ class DuplicationManager $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. * - * @param array $tables + * This method allows two array formats: + * + * array( TABLE1, TABLE2 ... ) + * + * or + * + * array( TABLE1 => array( COLUMN1, COLUMN2 ... ) ... ) + * + * @param array $tables a table cache array * * @return void */ @@ -11141,6 +12079,8 @@ class DuplicationManager /** * 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 */ @@ -11155,7 +12095,7 @@ class DuplicationManager * only once. Otherwise the duplicationmanager will, by default, query the schema * every time a duplication action is performed (dup()). * - * @param boolean $yesNo + * @param boolean $yesNo TRUE to use caching, FALSE otherwise */ public function setCacheTables( $yesNo ) { @@ -11169,7 +12109,7 @@ class DuplicationManager * deep copy. If no filters are set all types will be taking into account, this is * the default behavior. * - * @param array $filters + * @param array $filters list of tables to be filtered */ public function setFilters( $filters ) { @@ -11199,9 +12139,9 @@ class DuplicationManager * 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 + * @param OODBBean $bean bean to be copied + * @param array $trail for internal usage, pass array() + * @param boolean $preserveIDs for internal usage * * @return OODBBean */ @@ -11228,12 +12168,30 @@ class DuplicationManager } /** - * Exports a collection of beans. Handy for XML/JSON exports with a - * Javascript framework like Dojo or ExtJS. - * What will be exported: + * 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 (not THEIR own lists) + * - 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 @@ -11269,13 +12227,14 @@ class DuplicationManager namespace RedBeanPHP { /** - * RedBean Plugin + * RedBean Plugin. + * Marker interface for plugins. * * @file RedBean/Plugin.php - * @desc Marker interface for plugins. * @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. @@ -11299,12 +12258,13 @@ interface Plugin /** * Support functions for RedBeanPHP. + * Additional convenience shortcut functions for RedBeanPHP. * * @file RedBeanPHP/Functions.php - * @desc Additional convenience shortcut functions for RedBeanPHP * @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. @@ -11349,6 +12309,28 @@ if ( !function_exists( 'dump' ) ) { } } +/** + * 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 ); + } +} + } \ No newline at end of file