<?php
namespace RedBeanPHP;
use RedBeanPHP\Adapter\DBAdapter as DBAdapter;
use RedBeanPHP\QueryWriter as QueryWriter;
use RedBeanPHP\BeanHelper as BeanHelper;
use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
use RedBeanPHP\Repository as Repository;
use RedBeanPHP\Repository\Fluid as FluidRepo;
use RedBeanPHP\Repository\Frozen as FrozenRepo;
/**
* RedBean Object Oriented DataBase.
*
* The RedBean OODB Class is the main class of RedBeanPHP.
* It takes OODBBean objects and stores them to and loads them from the
* database as well as providing other CRUD functions. This class acts as a
* object database.
*
* @file RedBeanPHP/OODB.php
* @author Gabor de Mooij and the RedBeanPHP community
* @license BSD/GPLv2
*
* @copyright
* copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
* This source file is subject to the BSD/GPLv2 License that is bundled
* with this source code in the file license.txt.
*/
class OODB extends Observable
{
/**
* @var array
*/
private static $sqlFilters = array();
/**
* @var array
*/
protected $chillList = array();
/**
* @var array
*/
protected $stash = NULL;
/*
* @var integer
*/
protected $nesting = 0;
/**
* @var QueryWriter
*/
protected $writer;
/**
* @var boolean
*/
protected $isFrozen = FALSE;
/**
* @var BeanHelper|NULL
*/
protected $beanhelper = NULL;
/**
* @var AssociationManager|NULL
*/
protected $assocManager = NULL;
/**
* @var Repository
*/
protected $repository = NULL;
/**
* @var FrozenRepo|NULL
*/
protected $frozenRepository = NULL;
/**
* @var FluidRepo|NULL
*/
protected $fluidRepository = NULL;
/**
* @var boolean
*/
protected static $autoClearHistoryAfterStore = FALSE;
/**
* If set to TRUE, this method will call clearHistory every time
* the bean gets stored.
*
* @param boolean $autoClear auto clear option
*
* @return void
*/
public static function autoClearHistoryAfterStore( $autoClear = TRUE )
{
self::$autoClearHistoryAfterStore = (boolean) $autoClear;
}
/**
* Unboxes a bean from a FUSE model if needed and checks whether the bean is
* an instance of OODBBean.
*
* @param OODBBean|SimpleModel|SimpleModelInterface $bean bean you wish to unbox
*
* @return OODBBean
*/
protected function unboxIfNeeded( $bean )
{
if ( $bean instanceof SimpleModelInterface ) {
$bean = $bean->unbox();
}
if ( !( $bean instanceof OODBBean ) ) {
throw new RedException( 'OODB Store requires a bean, got: ' . gettype( $bean ) );
}
return $bean;
}
/**
* Constructor, requires a query writer.
* Most of the time, you do not need to use this constructor,
* since the facade takes care of constructing and wiring the
* RedBeanPHP core objects. However if you would like to
* assemble an OODB instance yourself, this is how it works:
*
* Usage:
*
* <code>
* $database = new RPDO( $dsn, $user, $pass );
* $adapter = new DBAdapter( $database );
* $writer = new PostgresWriter( $adapter );
* $oodb = new OODB( $writer, FALSE );
* $bean = $oodb->dispense( 'bean' );
* $bean->name = 'coffeeBean';
* $id = $oodb->store( $bean );
* $bean = $oodb->load( 'bean', $id );
* </code>
*
* The example above creates the 3 RedBeanPHP core objects:
* the Adapter, the Query Writer and the OODB instance and
* wires them together. The example also demonstrates some of
* the methods that can be used with OODB, as you see, they
* closely resemble their facade counterparts.
*
* The wiring process: create an RPDO instance using your database
* connection parameters. Create a database adapter from the RPDO
* object and pass that to the constructor of the writer. Next,
* create an OODB instance from the writer. Now you have an OODB
* object.
*
* @param QueryWriter $writer writer
* @param array|boolean $frozen mode of operation: TRUE (frozen), FALSE (default, fluid) or ARRAY (chilled)
*/
public function __construct( QueryWriter $writer, $frozen = FALSE )
{
if ( $writer instanceof QueryWriter ) {
$this->writer = $writer;
}
$this->freeze( $frozen );
}
/**
* Toggles fluid or frozen mode. In fluid mode the database
* structure is adjusted to accommodate your objects. In frozen mode
* this is not the case.
*
* You can also pass an array containing a selection of frozen types.
* Let's call this chill mode, it's just like fluid mode except that
* certain types (i.e. tables) aren't touched.
*
* @param boolean|array $toggle TRUE if you want to use OODB instance in frozen mode
*
* @return void
*/
public function freeze( $toggle )
{
if ( is_array( $toggle ) ) {
$this->chillList = $toggle;
$this->isFrozen = FALSE;
} else {
$this->isFrozen = (boolean) $toggle;
}
if ( $this->isFrozen ) {
if ( !$this->frozenRepository ) {
$this->frozenRepository = new FrozenRepo( $this, $this->writer );
}
$this->repository = $this->frozenRepository;
} else {
if ( !$this->fluidRepository ) {
$this->fluidRepository = new FluidRepo( $this, $this->writer );
}
$this->repository = $this->fluidRepository;
}
if ( count( self::$sqlFilters ) ) {
AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
}
}
/**
* Returns the current mode of operation of RedBean.
* In fluid mode the database
* structure is adjusted to accommodate your objects.
* In frozen mode
* this is not the case.
*
* @return boolean
*/
public function isFrozen()
{
return (bool) $this->isFrozen;
}
/**
* Determines whether a type is in the chill list.
* If a type is 'chilled' it's frozen, so its schema cannot be
* changed anymore. However other bean types may still be modified.
* This method is a convenience method for other objects to check if
* the schema of a certain type is locked for modification.
*
* @param string $type the type you wish to check
*
* @return boolean
*/
public function isChilled( $type )
{
return (boolean) ( in_array( $type, $this->chillList ) );
}
/**
* Dispenses a new bean (a OODBBean Bean Object)
* of the specified type. Always
* use this function to get an empty bean object. Never
* instantiate a OODBBean yourself because it needs
* to be configured before you can use it with RedBean. This
* function applies the appropriate initialization /
* configuration for you.
*
* @param string $type type of bean you want to dispense
* @param string $number number of beans you would like to get
* @param boolean $alwaysReturnArray if TRUE always returns the result as an array
*
* @return OODBBean|OODBBean[]
*/
public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE )
{
if ( $number < 1 ) {
if ( $alwaysReturnArray ) return array();
return NULL;
}
return $this->repository->dispense( $type, $number, $alwaysReturnArray );
}
/**
* Sets bean helper to be given to beans.
* Bean helpers assist beans in getting a reference to a toolbox.
*
* @param BeanHelper $beanhelper helper
*
* @return void
*/
public function setBeanHelper( BeanHelper $beanhelper )
{
$this->beanhelper = $beanhelper;
}
/**
* Returns the current bean helper.
* Bean helpers assist beans in getting a reference to a toolbox.
*
* @return BeanHelper|NULL
*/
public function getBeanHelper()
{
return $this->beanhelper;
}
/**
* Checks whether a OODBBean bean is valid.
* If the type is not valid or the ID is not valid it will
* throw an exception: Security.
*
* @param OODBBean $bean the bean that needs to be checked
*
* @return void
*/
public function check( OODBBean $bean )
{
$this->repository->check( $bean );
}
/**
* Searches the database for a bean that matches conditions $conditions and sql $sql
* and returns an array containing all the beans that have been found.
*
* Conditions need to take form:
*
* <code>
* array(
* 'PROPERTY' => array( POSSIBLE VALUES... 'John', 'Steve' )
* 'PROPERTY' => array( POSSIBLE VALUES... )
* );
* </code>
*
* All conditions are glued together using the AND-operator, while all value lists
* are glued using IN-operators thus acting as OR-conditions.
*
* Note that you can use property names; the columns will be extracted using the
* appropriate bean formatter.
*
* @param string $type type of beans you are looking for
* @param array $conditions list of conditions
* @param string|NULL $sql SQL to be used in query
* @param array $bindings a list of values to bind to query parameters
*
* @return array
*/
public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() )
{
return $this->repository->find( $type, $conditions, $sql, $bindings );
}
/**
* Same as find() but returns a BeanCollection.
*
* @param string $type type of beans you are looking for
* @param string|NULL $sql SQL to be used in query
* @param array $bindings a list of values to bind to query parameters
*
* @return BeanCollection
*/
public function findCollection( $type, $sql = NULL, $bindings = array() )
{
return $this->repository->findCollection( $type, $sql, $bindings );
}
/**
* Checks whether the specified table already exists in the database.
*
* @param string $table table name
*
* @return boolean
*/
public function tableExists( $table )
{
return $this->repository->tableExists( $table );
}
/**
* Stores a bean in the database. This method takes a
* OODBBean Bean Object $bean and stores it
* in the database. If the database schema is not compatible
* with this bean and RedBean runs in fluid mode the schema
* will be altered to store the bean correctly.
* If the database schema is not compatible with this bean and
* RedBean runs in frozen mode it will throw an exception.
* This function returns the primary key ID of the inserted
* bean.
*
* The return value is an integer if possible. If it is not possible to
* represent the value as an integer a string will be returned. We use
* explicit casts instead of functions to preserve performance
* (0.13 vs 0.28 for 10000 iterations on Core i3).
*
* @param OODBBean|SimpleModel|SimpleModelInterface $bean bean to store
*
* @return integer|string
*/
public function store( $bean )
{
$bean = $this->unboxIfNeeded( $bean );
$id = $this->repository->store( $bean );
if ( self::$autoClearHistoryAfterStore ) {
$bean->clearHistory();
}
return $id;
}
/**
* Loads a bean from the object database.
* It searches for a OODBBean Bean Object in the
* database. It does not matter how this bean has been stored.
* RedBean uses the primary key ID $id and the string $type
* to find the bean. The $type specifies what kind of bean you
* are looking for; this is the same type as used with the
* dispense() function. If RedBean finds the bean it will return
* the OODB Bean object; if it cannot find the bean
* RedBean will return a new bean of type $type and with
* primary key ID 0. In the latter case it acts basically the
* same as dispense().
*
* Important note:
* If the bean cannot be found in the database a new bean of
* the specified type will be generated and returned.
*
* @param string $type type of bean you want to load
* @param integer $id ID of the bean you want to load
*
* @return OODBBean
*/
public function load( $type, $id )
{
return $this->repository->load( $type, $id );
}
/**
* Removes a bean from the database.
* This function will remove the specified OODBBean
* Bean Object from the database.
*
* @param OODBBean|SimpleModel|SimpleModelInterface $bean bean you want to remove from database
*
* @return int
*/
public function trash( $bean )
{
$bean = $this->unboxIfNeeded( $bean );
return $this->repository->trash( $bean );
}
/**
* Returns an array of beans. Pass a type and a series of ids and
* this method will bring you the corresponding beans.
*
* important note: Because this method loads beans using the load()
* function (but faster) it will return empty beans with ID 0 for
* every bean that could not be located. The resulting beans will have the
* passed IDs as their keys.
*
* @param string $type type of beans
* @param array $ids ids to load
*
* @return array
*/
public function batch( $type, $ids )
{
return $this->repository->batch( $type, $ids );
}
/**
* This is a convenience method; it converts database rows
* (arrays) into beans. Given a type and a set of rows this method
* will return an array of beans of the specified type loaded with
* the data fields provided by the result set from the database.
*
* @param string $type type of beans you would like to have
* @param array $rows rows from the database result
* @param string $mask mask to apply for meta data
*
* @return array
*/
public function convertToBeans( $type, $rows, $mask = NULL )
{
return $this->repository->convertToBeans( $type, $rows, $mask );
}
/**
* Counts the number of beans of type $type.
* This method accepts a second argument to modify the count-query.
* A third argument can be used to provide bindings for the SQL snippet.
*
* @param string $type type of bean we are looking for
* @param string $addSQL additional SQL snippet
* @param array $bindings parameters to bind to SQL
*
* @return integer
*/
public function count( $type, $addSQL = '', $bindings = array() )
{
return $this->repository->count( $type, $addSQL, $bindings );
}
/**
* Trash all beans of a given type. Wipes an entire type of bean.
*
* @param string $type type of bean you wish to delete all instances of
*
* @return boolean
*/
public function wipe( $type )
{
return $this->repository->wipe( $type );
}
/**
* Returns an Association Manager for use with OODB.
* A simple getter function to obtain a reference to the association manager used for
* storage and more.
*
* @return AssociationManager
*/
public function getAssociationManager()
{
if ( !isset( $this->assocManager ) ) {
throw new RedException( 'No association manager available.' );
}
return $this->assocManager;
}
/**
* Sets the association manager instance to be used by this OODB.
* A simple setter function to set the association manager to be used for storage and
* more.
*
* @param AssociationManager $assocManager sets the association manager to be used
*
* @return void
*/
public function setAssociationManager( AssociationManager $assocManager )
{
$this->assocManager = $assocManager;
}
/**
* Returns the currently used repository instance.
* For testing purposes only.
*
* @return Repository
*/
public function getCurrentRepository()
{
return $this->repository;
}
/**
* Clears all function bindings.
*
* @return void
*/
public function clearAllFuncBindings()
{
self::$sqlFilters = array();
AQueryWriter::setSQLFilters( self::$sqlFilters, FALSE );
}
/**
* Binds an SQL function to a column.
* This method can be used to setup a decode/encode scheme or
* perform UUID insertion. This method is especially useful for handling
* MySQL spatial columns, because they need to be processed first using
* the asText/GeomFromText functions.
*
* @param string $mode mode to set function for, i.e. read or write
* @param string $field field (table.column) to bind SQL function to
* @param string|NULL $function SQL function to bind to field
* @param boolean $isTemplate TRUE if $function is an SQL string, FALSE for just a function name
*
* @return void
*/
public function bindFunc( $mode, $field, $function, $isTemplate = FALSE )
{
list( $type, $property ) = explode( '.', $field );
$mode = ($mode === 'write') ? QueryWriter::C_SQLFILTER_WRITE : QueryWriter::C_SQLFILTER_READ;
if ( !isset( self::$sqlFilters[$mode] ) ) self::$sqlFilters[$mode] = array();
if ( !isset( self::$sqlFilters[$mode][$type] ) ) self::$sqlFilters[$mode][$type] = array();
if ( is_null( $function ) ) {
unset( self::$sqlFilters[$mode][$type][$property] );
} else {
if ($mode === QueryWriter::C_SQLFILTER_WRITE) {
if ($isTemplate) {
$code = sprintf( $function, '?' );
} else {
$code = "{$function}(?)";
}
self::$sqlFilters[$mode][$type][$property] = $code;
} else {
if ($isTemplate) {
$code = sprintf( $function, $field );
} else {
$code = "{$function}({$field})";
}
self::$sqlFilters[$mode][$type][$property] = $code;
}
}
AQueryWriter::setSQLFilters( self::$sqlFilters, ( !$this->isFrozen ) );
}
}
|