* Custom handler method signature: handler( array (
* 'message' => string
* 'bean' => OODBBean
* 'method' => string
* ) )
*
*
* This method returns the old mode and handler as an array.
*
* @param integer $mode error handling mode
* @param callable|NULL $func custom handler
*
* @return array
*/
public static function setErrorHandlingFUSE($mode, $func = NULL) {
if (
$mode !== self::C_ERR_IGNORE
&& $mode !== self::C_ERR_LOG
&& $mode !== self::C_ERR_NOTICE
&& $mode !== self::C_ERR_WARN
&& $mode !== self::C_ERR_EXCEPTION
&& $mode !== self::C_ERR_FUNC
&& $mode !== self::C_ERR_FATAL
) throw new \Exception( 'Invalid error mode selected' );
if ( $mode === self::C_ERR_FUNC && !is_callable( $func ) ) {
throw new \Exception( 'Invalid error handler' );
}
$old = array( self::$errorHandlingFUSE, self::$errorHandler );
self::$errorHandlingFUSE = $mode;
if ( is_callable( $func ) ) {
self::$errorHandler = $func;
} else {
self::$errorHandler = NULL;
}
return $old;
}
/**
* Sets global aliases.
* Registers a batch of aliases in one go. This works the same as
* fetchAs and setAutoResolve but explicitly. For instance if you register
* the alias 'cover' for 'page' a property containing a reference to a
* page bean called 'cover' will correctly return the page bean and not
* a (non-existant) cover bean.
*
*
* R::aliases( array( 'cover' => 'page' ) );
* $book = R::dispense( 'book' );
* $page = R::dispense( 'page' );
* $book->cover = $page;
* R::store( $book );
* $book = $book->fresh();
* $cover = $book->cover;
* echo $cover->getMeta( 'type' ); //page
*
*
* The format of the aliases registration array is:
*
* {alias} => {actual type}
*
* In the example above we use:
*
* cover => page
*
* From that point on, every bean reference to a cover
* will return a 'page' bean. Note that with autoResolve this
* feature along with fetchAs() is no longer very important, although
* relying on explicit aliases can be a bit faster.
*
* @param array $list list of global aliases to use
*
* @return void
*/
public static function aliases( $list )
{
self::$aliases = $list;
}
/**
* 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;
}
/**
* 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:
*
*
* $author
* ->withCondition(' @joined.detail.title LIKE ? ')
* ->ownBookList;
*
*
* will automatically join 'detail' on book to
* access the title field.
*
* @note this feature requires Narrow Field Mode and Join Feature
* to be both activated (default).
*
* @param string $type the source type for the join
*
* @return string
*/
private function parseJoin( $type )
{
$joinSql = '';
$joins = array();
if ( strpos($this->withSql, '@joined.' ) !== FALSE ) {
$writer = $this->beanHelper->getToolBox()->getWriter();
$oldParts = $parts = explode( '@joined.', $this->withSql );
array_shift( $parts );
foreach($parts as $part) {
$explosion = explode( '.', $part );
$joinInfo = array_shift( $explosion );
//Dont join more than once..
if ( !isset( $joins[$joinInfo] ) ) {
$joins[ $joinInfo ] = true;
$joinSql .= $writer->writeJoin( $type, $joinInfo, 'LEFT' );
}
}
$this->withSql = implode( '', $oldParts );
$joinSql .= ' WHERE ';
}
return $joinSql;
}
/**
* Internal method.
* Obtains a shared list for a certain type.
*
* @param string $type the name of the list you want to retrieve.
*
* @return array
*/
private function getSharedList( $type, $redbean, $toolbox )
{
$writer = $toolbox->getWriter();
if ( $this->via ) {
$oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
if ( $oldName !== $this->via ) {
//set the new renaming rule
$writer->renameAssocTable( $oldName, $this->via );
}
$this->via = NULL;
}
$beans = array();
if ($this->getID()) {
$type = $this->beau( $type );
$assocManager = $redbean->getAssociationManager();
$beans = $assocManager->related( $this, $type, $this->withSql, $this->withParams );
}
$this->withSql = '';
$this->withParams = array();
return $beans;
}
/**
* Internal method.
* Obtains the own list of a certain type.
*
* @param string $type name of the list you want to retrieve
* @param OODB $oodb The RB OODB object database instance
*
* @return array
*/
private function getOwnList( $type, $redbean )
{
$type = $this->beau( $type );
if ( $this->aliasName ) {
$parentField = $this->aliasName;
$myFieldLink = $parentField . '_id';
$this->__info['sys.alias.' . $type] = $this->aliasName;
$this->aliasName = NULL;
} else {
$parentField = $this->__info['type'];
$myFieldLink = $parentField . '_id';
}
$beans = array();
if ( $this->getID() ) {
$firstKey = NULL;
if ( count( $this->withParams ) > 0 ) {
reset( $this->withParams );
$firstKey = key( $this->withParams );
}
$joinSql = $this->parseJoin( $type );
if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
$bindings = $this->withParams;
$bindings[':slot0'] = $this->getID();
$beans = $redbean->find( $type, array(), " {$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
} else {
$bindings = array_merge( array( $this->getID() ), $this->withParams );
$beans = $redbean->find( $type, array(), " {$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
}
}
$this->withSql = '';
$this->withParams = array();
foreach ( $beans as $beanFromList ) {
$beanFromList->__info['sys.parentcache.' . $parentField] = $this;
}
return $beans;
}
/**
* Initializes a bean. Used by OODB for dispensing beans.
* It is not recommended to use this method to initialize beans. Instead
* use the OODB object to dispense new beans. You can use this method
* if you build your own bean dispensing mechanism.
*
* @param string $type type of the new bean
* @param BeanHelper $beanhelper bean helper to obtain a toolbox and a model
*
* @return void
*/
public function initializeForDispense( $type, BeanHelper $beanhelper )
{
$this->beanHelper = $beanhelper;
$this->__info['type'] = $type;
$this->__info['sys.id'] = 'id';
$this->__info['sys.orig'] = array( 'id' => 0 );
$this->__info['tainted'] = TRUE;
$this->__info['changed'] = TRUE;
$this->properties['id'] = 0;
}
/**
* Sets the Bean Helper. Normally the Bean Helper is set by OODB.
* Here you can change the Bean Helper. The Bean Helper is an object
* providing access to a toolbox for the bean necessary to retrieve
* nested beans (bean lists: ownBean, sharedBean) without the need to
* rely on static calls to the facade (or make this class dep. on OODB).
*
* @param BeanHelper $helper helper to use for this bean
*
* @return void
*/
public function setBeanHelper( BeanHelper $helper )
{
$this->beanHelper = $helper;
}
/**
* Returns an ArrayIterator so you can treat the bean like
* an array with the properties container as its contents.
* This method is meant for PHP and allows you to access beans as if
* they were arrays, i.e. using array notation:
*
* $bean[$key] = $value;
*
* Note that not all PHP functions work with the array interface.
*
* @return ArrayIterator
*/
public function getIterator()
{
return new \ArrayIterator( $this->properties );
}
/**
* Imports all values from an associative array $array. Chainable.
* This method imports the values in the first argument as bean
* propery and value pairs. Use the second parameter to provide a
* selection. If a selection array is passed, only the entries
* having keys mentioned in the selection array will be imported.
* Set the third parameter to TRUE to preserve spaces in selection keys.
*
* @param array $array what you want to import
* @param string|array $selection selection of values
* @param boolean $notrim if TRUE selection keys will NOT be trimmed
*
* @return OODBBean
*/
public function import( $array, $selection = FALSE, $notrim = FALSE )
{
if ( is_string( $selection ) ) {
$selection = explode( ',', $selection );
}
if ( !$notrim && is_array( $selection ) ) {
foreach ( $selection as $key => $selected ) {
$selection[$key] = trim( $selected );
}
}
foreach ( $array as $key => $value ) {
if ( $key != '__info' ) {
if ( !$selection || ( $selection && in_array( $key, $selection ) ) ) {
if ( is_array($value ) ) {
if ( isset( $value['_type'] ) ) {
$bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $value['_type'] );
unset( $value['_type'] );
$bean->import($value);
$this->$key = $bean;
} else {
$listBeans = array();
foreach( $value as $listKey => $listItem ) {
$bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $listItem['_type'] );
unset( $listItem['_type'] );
$bean->import($listItem);
$list = &$this->$key;
$list[ $listKey ] = $bean;
}
}
} else {
$this->$key = $value;
}
}
}
}
return $this;
}
/**
* Fast way to import a row.
* Does not perform any checks.
*
* @param array $row a database row
*
* @return self
*/
public function importRow( $row )
{
$this->properties = $row;
$this->__info['sys.orig'] = $row;
$this->__info['changed'] = FALSE;
return $this;
}
/**
* Imports data from another bean. Chainable.
* Copies the properties from the source bean to the internal
* property list.
*
* @param OODBBean $sourceBean the source bean to take properties from
*
* @return OODBBean
*/
public function importFrom( OODBBean $sourceBean )
{
$this->__info['tainted'] = TRUE;
$this->__info['changed'] = TRUE;
$this->properties = $sourceBean->properties;
return $this;
}
/**
* Injects the properties of another bean but keeps the original ID.
* Just like import() but keeps the original ID.
* Chainable.
*
* @param OODBBean $otherBean the bean whose properties you would like to copy
*
* @return OODBBean
*/
public function inject( OODBBean $otherBean )
{
$myID = $this->properties['id'];
$this->import( $otherBean->export( FALSE, FALSE, TRUE ) );
$this->id = $myID;
return $this;
}
/**
* Exports the bean as an array.
* This function exports the contents of a bean to an array and returns
* the resulting array.
*
* @param boolean $meta set to TRUE if you want to export meta data as well
* @param boolean $parents set to TRUE if you want to export parents as well
* @param boolean $onlyMe set to TRUE if you want to export only this bean
* @param array $filters optional whitelist for export
*
* @return array
*/
public function export( $meta = FALSE, $parents = FALSE, $onlyMe = FALSE, $filters = array() )
{
$arr = array();
if ( $parents ) {
foreach ( $this as $key => $value ) {
if ( substr( $key, -3 ) != '_id' ) continue;
$prop = substr( $key, 0, strlen( $key ) - 3 );
$this->$prop;
}
}
$hasFilters = is_array( $filters ) && count( $filters );
foreach ( $this as $key => $value ) {
if ( !$onlyMe && is_array( $value ) ) {
$vn = array();
foreach ( $value as $i => $b ) {
if ( !( $b instanceof OODBBean ) ) continue;
$vn[] = $b->export( $meta, FALSE, FALSE, $filters );
$value = $vn;
}
} elseif ( $value instanceof OODBBean ) {
if ( $hasFilters ) {
if ( !in_array( strtolower( $value->getMeta( 'type' ) ), $filters ) ) continue;
}
$value = $value->export( $meta, $parents, FALSE, $filters );
}
$arr[$key] = $value;
}
if ( $meta ) {
$arr['__info'] = $this->__info;
}
return $arr;
}
/**
* Implements isset() function for use as an array.
*
* @param string $property name of the property you want to check
*
* @return boolean
*/
public function __isset( $property )
{
$property = $this->beau( $property );
if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
$property = substr($property, 1);
}
return isset( $this->properties[$property] );
}
/**
* Returns the ID of the bean no matter what the ID field is.
*
* @return string|null
*/
public function getID()
{
return ( isset( $this->properties['id'] ) ) ? (string) $this->properties['id'] : NULL;
}
/**
* Unsets a property of a bean.
* Magic method, gets called implicitly when performing the unset() operation
* on a bean property.
*
* @param string $property property to unset
*
* @return void
*/
public function __unset( $property )
{
$property = $this->beau( $property );
if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
$property = substr($property, 1);
}
unset( $this->properties[$property] );
$shadowKey = 'sys.shadow.'.$property;
if ( isset( $this->__info[ $shadowKey ] ) ) unset( $this->__info[$shadowKey] );
//also clear modifiers
$this->withSql = '';
$this->withParams = array();
$this->aliasName = NULL;
$this->fetchType = NULL;
$this->noLoad = FALSE;
$this->all = FALSE;
$this->via = NULL;
return;
}
/**
* Adds WHERE clause conditions to ownList retrieval.
* For instance to get the pages that belong to a book you would
* issue the following command: $book->ownPage
* However, to order these pages by number use:
*
*
* $book->with(' ORDER BY `number` ASC ')->ownPage
*
*
* the additional SQL snippet will be merged into the final
* query.
*
* @param string $sql SQL to be added to retrieval query.
* @param array $bindings array with parameters to bind to SQL snippet
*
* @return OODBBean
*/
public function with( $sql, $bindings = array() )
{
$this->withSql = $sql;
$this->withParams = $bindings;
return $this;
}
/**
* Just like with(). Except that this method prepends the SQL query snippet
* with AND which makes it slightly more comfortable to use a conditional
* SQL snippet. For instance to filter an own-list with pages (belonging to
* a book) on specific chapters you can use:
*
* $book->withCondition(' chapter = 3 ')->ownPage
*
* This will return in the own list only the pages having 'chapter == 3'.
*
* @param string $sql SQL to be added to retrieval query (prefixed by AND)
* @param array $bindings array with parameters to bind to SQL snippet
*
* @return OODBBean
*/
public function withCondition( $sql, $bindings = array() )
{
$this->withSql = ' AND ' . $sql;
$this->withParams = $bindings;
return $this;
}
/**
* Tells the bean to (re)load the following list without any
* conditions. If you have an ownList or sharedList with a
* condition you can use this method to reload the entire list.
*
* Usage:
*
*
* $bean->with( ' LIMIT 3 ' )->ownPage; //Just 3
* $bean->all()->ownPage; //Reload all pages
*
*
* @return self
*/
public function all()
{
$this->all = TRUE;
return $this;
}
/**
* Tells the bean to only access the list but not load
* its contents. Use this if you only want to add something to a list
* and you have no interest in retrieving its contents from the database.
*
* @return self
*/
public function noLoad()
{
$this->noLoad = TRUE;
return $this;
}
/**
* Prepares an own-list to use an alias. This is best explained using
* an example. Imagine a project and a person. The project always involves
* two persons: a teacher and a student. The person beans have been aliased in this
* case, so to the project has a teacher_id pointing to a person, and a student_id
* also pointing to a person. Given a project, we obtain the teacher like this:
*
*
* $project->fetchAs('person')->teacher;
*
*
* Now, if we want all projects of a teacher we cant say:
*
*
* $teacher->ownProject
*
*
* because the $teacher is a bean of type 'person' and no project has been
* assigned to a person. Instead we use the alias() method like this:
*
*
* $teacher->alias('teacher')->ownProject
*
*
* now we get the projects associated with the person bean aliased as
* a teacher.
*
* @param string $aliasName the alias name to use
*
* @return OODBBean
*/
public function alias( $aliasName )
{
$this->aliasName = $this->beau( $aliasName );
return $this;
}
/**
* Returns properties of bean as an array.
* This method returns the raw internal property list of the
* bean. Only use this method for optimization purposes. Otherwise
* use the export() method to export bean data to arrays.
*
* @return array
*/
public function getProperties()
{
return $this->properties;
}
/**
* Returns properties of bean as an array.
* This method returns the raw internal property list of the
* bean. Only use this method for optimization purposes. Otherwise
* use the export() method to export bean data to arrays.
* This method returns an array with the properties array and
* the type (string).
*
* @return array
*/
public function getPropertiesAndType()
{
return array( $this->properties, $this->__info['type'] );
}
/**
* Turns a camelcase property name into an underscored property name.
*
* Examples:
*
* * oneACLRoute -> one_acl_route
* * camelCase -> camel_case
*
* Also caches the result to improve performance.
*
* @param string $property property to un-beautify
*
* @return string
*/
public function beau( $property )
{
static $beautifulColumns = array();
if ( ctype_lower( $property ) ) return $property;
if (
( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) )
|| ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) )
|| ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) )
) {
$property = preg_replace( '/List$/', '', $property );
return $property;
}
if ( !isset( $beautifulColumns[$property] ) ) {
$beautifulColumns[$property] = AQueryWriter::camelsSnake( $property );
}
return $beautifulColumns[$property];
}
/**
* Clears all modifiers.
*
* @return self
*/
public function clearModifiers()
{
$this->withSql = '';
$this->withParams = array();
$this->aliasName = NULL;
$this->fetchType = NULL;
$this->noLoad = FALSE;
$this->all = FALSE;
$this->via = NULL;
return $this;
}
/**
* Determines whether a list is opened in exclusive mode or not.
* If a list has been opened in exclusive mode this method will return TRUE,
* othwerwise it will return FALSE.
*
* @param string $listName name of the list to check
*
* @return boolean
*/
public function isListInExclusiveMode( $listName )
{
$listName = $this->beau( $listName );
if ( strpos( $listName, 'xown' ) === 0 && ctype_upper( substr( $listName, 4, 1 ) ) ) {
$listName = substr($listName, 1);
}
$listName = lcfirst( substr( $listName, 3 ) );
return ( isset( $this->__info['sys.exclusive-'.$listName] ) && $this->__info['sys.exclusive-'.$listName] );
}
/**
* Magic Getter. Gets the value for a specific property in the bean.
* If the property does not exist this getter will make sure no error
* occurs. This is because RedBean allows you to query (probe) for
* properties. If the property can not be found this method will
* return NULL instead.
*
* @param string $property name of the property you wish to obtain the value of
*
* @return mixed
*/
public function &__get( $property )
{
$isEx = FALSE;
$isOwn = FALSE;
$isShared = FALSE;
if ( !ctype_lower( $property ) ) {
$property = $this->beau( $property );
if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
$property = substr($property, 1);
$listName = lcfirst( substr( $property, 3 ) );
$isEx = TRUE;
$isOwn = TRUE;
$this->__info['sys.exclusive-'.$listName] = TRUE;
} elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) ) {
$isOwn = TRUE;
$listName = lcfirst( substr( $property, 3 ) );
} elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
$isShared = TRUE;
}
}
$fieldLink = $property . '_id';
$exists = isset( $this->properties[$property] );
//If not exists and no field link and no list, bail out.
if ( !$exists && !isset($this->$fieldLink) && (!$isOwn && !$isShared )) {
$this->withSql = '';
$this->withParams = array();
$this->aliasName = NULL;
$this->fetchType = NULL;
$this->noLoad = FALSE;
$this->all = FALSE;
$this->via = NULL;
$NULL = NULL;
return $NULL;
}
$hasAlias = (!is_null($this->aliasName));
$differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
$hasSQL = ($this->withSql !== '' || $this->via !== NULL);
$hasAll = (boolean) ($this->all);
//If exists and no list or exits and list not changed, bail out.
if ( $exists && ((!$isOwn && !$isShared ) || (!$hasSQL && !$differentAlias && !$hasAll)) ) {
$this->withSql = '';
$this->withParams = array();
$this->aliasName = NULL;
$this->fetchType = NULL;
$this->noLoad = FALSE;
$this->all = FALSE;
$this->via = NULL;
return $this->properties[$property];
}
list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
if ( isset( $this->$fieldLink ) ) {
$this->__info['tainted'] = TRUE;
if ( isset( $this->__info["sys.parentcache.$property"] ) ) {
$bean = $this->__info["sys.parentcache.$property"];
} else {
if ( isset( self::$aliases[$property] ) ) {
$type = self::$aliases[$property];
} elseif ( $this->fetchType ) {
$type = $this->fetchType;
$this->fetchType = NULL;
} else {
$type = $property;
}
$bean = NULL;
if ( !is_null( $this->properties[$fieldLink] ) ) {
$bean = $redbean->load( $type, $this->properties[$fieldLink] );
//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;
return $this->properties[$property];
}
//Implicit: elseif ( $isOwn || $isShared ) {
if ( $this->noLoad ) {
$beans = array();
} elseif ( $isOwn ) {
$beans = $this->getOwnList( $listName, $redbean );
} else {
$beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
}
$this->properties[$property] = $beans;
$this->__info["sys.shadow.$property"] = $beans;
$this->__info['tainted'] = TRUE;
$this->withSql = '';
$this->withParams = array();
$this->aliasName = NULL;
$this->fetchType = NULL;
$this->noLoad = FALSE;
$this->all = FALSE;
$this->via = NULL;
return $this->properties[$property];
}
/**
* Magic Setter. Sets the value for a specific property.
* This setter acts as a hook for OODB to mark beans as tainted.
* The tainted meta property can be retrieved using getMeta("tainted").
* The tainted meta property indicates whether a bean has been modified and
* can be used in various caching mechanisms.
*
* @param string $property name of the property you wish to assign a value to
* @param mixed $value the value you want to assign
*
* @return void
*/
public function __set( $property, $value )
{
$isEx = FALSE;
$isOwn = FALSE;
$isShared = FALSE;
if ( !ctype_lower( $property ) ) {
$property = $this->beau( $property );
if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
$property = substr($property, 1);
$listName = lcfirst( substr( $property, 3 ) );
$isEx = TRUE;
$isOwn = TRUE;
$this->__info['sys.exclusive-'.$listName] = TRUE;
} elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) ) {
$isOwn = TRUE;
$listName = lcfirst( substr( $property, 3 ) );
} elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
$isShared = TRUE;
}
}
$hasAlias = (!is_null($this->aliasName));
$differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
$hasSQL = ($this->withSql !== '' || $this->via !== NULL);
$exists = isset( $this->properties[$property] );
$fieldLink = $property . '_id';
$isFieldLink = (($pos = strrpos($property, '_id')) !== FALSE) && array_key_exists( ($fieldName = substr($property, 0, $pos)), $this->properties );
if ( ($isOwn || $isShared) && (!$exists || $hasSQL || $differentAlias) ) {
if ( !$this->noLoad ) {
list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
if ( $isOwn ) {
$beans = $this->getOwnList( $listName, $redbean );
} else {
$beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
}
$this->__info["sys.shadow.$property"] = $beans;
}
}
$this->withSql = '';
$this->withParams = array();
$this->aliasName = NULL;
$this->fetchType = NULL;
$this->noLoad = FALSE;
$this->all = FALSE;
$this->via = NULL;
$this->__info['tainted'] = TRUE;
$this->__info['changed'] = TRUE;
if ( array_key_exists( $fieldLink, $this->properties ) && !( $value instanceof OODBBean ) ) {
if ( is_null( $value ) || $value === FALSE ) {
unset( $this->properties[ $property ]);
$this->properties[ $fieldLink ] = NULL;
return;
} else {
throw new RedException( 'Cannot cast to bean.' );
}
}
if ( $isFieldLink ){
unset( $this->properties[ $fieldName ]);
$this->properties[ $property ] = NULL;
}
if ( $value === FALSE ) {
$value = '0';
} elseif ( $value === TRUE ) {
$value = '1';
} elseif ( $value instanceof \DateTime ) {
$value = $value->format( 'Y-m-d H:i:s' );
}
$this->properties[$property] = $value;
}
/**
* Sets a property directly, for internal use only.
*
* @param string $property property
* @param mixed $value value
* @param boolean $updateShadow whether you want to update the shadow
* @param boolean $taint whether you want to mark the bean as tainted
*
* @return void
*/
public function setProperty( $property, $value, $updateShadow = FALSE, $taint = FALSE )
{
$this->properties[$property] = $value;
if ( $updateShadow ) {
$this->__info['sys.shadow.' . $property] = $value;
}
if ( $taint ) {
$this->__info['tainted'] = TRUE;
$this->__info['changed'] = TRUE;
}
}
/**
* Returns the value of a meta property. A meta property
* contains additional information about the bean object that will not
* be stored in the database. Meta information is used to instruct
* RedBeanPHP as well as other systems how to deal with the bean.
* If the property cannot be found this getter will return NULL instead.
*
* Example:
*
*
* $bean->setMeta( 'flush-cache', TRUE );
*
*
* RedBeanPHP also stores meta data in beans, this meta data uses
* keys prefixed with 'sys.' (system).
*
* @param string $path path to property in meta data
* @param mixed $default default value
*
* @return mixed
*/
public function getMeta( $path, $default = NULL )
{
return ( isset( $this->__info[$path] ) ) ? $this->__info[$path] : $default;
}
/**
* Gets and unsets a meta property.
* Moves a meta property out of the bean.
* This is a short-cut method that can be used instead
* of combining a get/unset.
*
* @param string $path path to property in meta data
* @param mixed $default default value
*
* @return mixed
*/
public function moveMeta( $path, $value = NULL )
{
if ( isset( $this->__info[$path] ) ) {
$value = $this->__info[ $path ];
unset( $this->__info[ $path ] );
}
return $value;
}
/**
* Stores a value in the specified Meta information property.
* The first argument should be the key to store the value under,
* the second argument should be the value. It is common to use
* a path-like notation for meta data in RedBeanPHP like:
* 'my.meta.data', however the dots are purely for readability, the
* meta data methods do not store nested structures or hierarchies.
*
* @param string $path path / key to store value under
* @param mixed $value value to store in bean (not in database) as meta data
*
* @return OODBBean
*/
public function setMeta( $path, $value )
{
$this->__info[$path] = $value;
return $this;
}
/**
* Copies the meta information of the specified bean
* This is a convenience method to enable you to
* exchange meta information easily.
*
* @param OODBBean $bean bean to copy meta data of
*
* @return OODBBean
*/
public function copyMetaFrom( OODBBean $bean )
{
$this->__info = $bean->__info;
return $this;
}
/**
* Sends the call to the registered model.
* This method can also be used to override bean behaviour.
* In that case you don't want an error or exception to be triggered
* if the method does not exist in the model (because it's optional).
* Unfortunately we cannot add an extra argument to __call() for this
* because the signature is fixed. Another option would be to set
* a special flag ( i.e. $this->isOptionalCall ) but that would
* cause additional complexity because we have to deal with extra temporary state.
* So, instead I allowed the method name to be prefixed with '@', in practice
* nobody creates methods like that - however the '@' symbol in PHP is widely known
* to suppress error handling, so we can reuse the semantics of this symbol.
* If a method name gets passed starting with '@' the overrideDontFail variable
* will be set to TRUE and the '@' will be stripped from the function name before
* attempting to invoke the method on the model. This way, we have all the
* logic in one place.
*
* @param string $method name of the method
* @param array $args argument list
*
* @return mixed
*/
public function __call( $method, $args )
{
$overrideDontFail = FALSE;
if ( strpos( $method, '@' ) === 0 ) {
$method = substr( $method, 1 );
$overrideDontFail = TRUE;
}
if ( !isset( $this->__info['model'] ) ) {
$model = $this->beanHelper->getModelForBean( $this );
if ( !$model ) {
return NULL;
}
$this->__info['model'] = $model;
}
if ( !method_exists( $this->__info['model'], $method ) ) {
if ( self::$errorHandlingFUSE === FALSE || $overrideDontFail ) {
return NULL;
}
if ( in_array( $method, array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ), TRUE ) ) {
return NULL;
}
$message = "FUSE: method does not exist in model: $method";
if ( self::$errorHandlingFUSE === self::C_ERR_LOG ) {
error_log( $message );
return NULL;
} elseif ( self::$errorHandlingFUSE === self::C_ERR_NOTICE ) {
trigger_error( $message, E_USER_NOTICE );
return NULL;
} elseif ( self::$errorHandlingFUSE === self::C_ERR_WARN ) {
trigger_error( $message, E_USER_WARNING );
return NULL;
} elseif ( self::$errorHandlingFUSE === self::C_ERR_EXCEPTION ) {
throw new \Exception( $message );
} elseif ( self::$errorHandlingFUSE === self::C_ERR_FUNC ) {
$func = self::$errorHandler;
return $func(array(
'message' => $message,
'method' => $method,
'args' => $args,
'bean' => $this
));
}
trigger_error( $message, E_USER_ERROR );
return NULL;
}
return call_user_func_array( array( $this->__info['model'], $method ), $args );
}
/**
* Implementation of __toString Method
* Routes call to Model. If the model implements a __toString() method this
* method will be called and the result will be returned. In case of an
* echo-statement this result will be printed. If the model does not
* implement a __toString method, this method will return a JSON
* representation of the current bean.
*
* @return string
*/
public function __toString()
{
$string = $this->__call( '@__toString', array() );
if ( $string === NULL ) {
$list = array();
foreach($this->properties as $property => $value) {
if (is_scalar($value)) {
$list[$property] = $value;
}
}
return json_encode( $list );
} else {
return $string;
}
}
/**
* Implementation of Array Access Interface, you can access bean objects
* like an array.
* Call gets routed to __set.
*
* @param mixed $offset offset string
* @param mixed $value value
*
* @return void
*/
public function offsetSet( $offset, $value )
{
$this->__set( $offset, $value );
}
/**
* Implementation of Array Access Interface, you can access bean objects
* like an array.
*
* Array functions do not reveal x-own-lists and list-alias because
* you dont want duplicate entries in foreach-loops.
* Also offers a slight performance improvement for array access.
*
* @param mixed $offset property
*
* @return boolean
*/
public function offsetExists( $offset )
{
return $this->__isset( $offset );
}
/**
* Implementation of Array Access Interface, you can access bean objects
* like an array.
* Unsets a value from the array/bean.
*
* Array functions do not reveal x-own-lists and list-alias because
* you dont want duplicate entries in foreach-loops.
* Also offers a slight performance improvement for array access.
*
* @param mixed $offset property
*
* @return void
*/
public function offsetUnset( $offset )
{
$this->__unset( $offset );
}
/**
* Implementation of Array Access Interface, you can access bean objects
* like an array.
* Returns value of a property.
*
* Array functions do not reveal x-own-lists and list-alias because
* you dont want duplicate entries in foreach-loops.
* Also offers a slight performance improvement for array access.
*
* @param mixed $offset property
*
* @return mixed
*/
public function &offsetGet( $offset )
{
return $this->__get( $offset );
}
/**
* Chainable method to cast a certain ID to a bean; for instance:
* $person = $club->fetchAs('person')->member;
* This will load a bean of type person using member_id as ID.
*
* @param string $type preferred fetch type
*
* @return OODBBean
*/
public function fetchAs( $type )
{
$this->fetchType = $type;
return $this;
}
/**
* For polymorphic bean relations.
* Same as fetchAs but uses a column instead of a direct value.
*
* @param string $field field name to use for mapping
*
* @return OODBBean
*/
public function poly( $field )
{
return $this->fetchAs( $this->$field );
}
/**
* Traverses a bean property with the specified function.
* Recursively iterates through the property invoking the
* function for each bean along the way passing the bean to it.
*
* Can be used together with with, withCondition, alias and fetchAs.
*
* @param string $property property
* @param callable $function function
* @param integer $maxDepth maximum depth for traversal
*
* @return OODBBean
* @throws RedException
*/
public function traverse( $property, $function, $maxDepth = NULL )
{
$this->via = NULL;
if ( strpos( $property, 'shared' ) !== FALSE ) {
throw new RedException( 'Traverse only works with (x)own-lists.' );
}
if ( !is_null( $maxDepth ) ) {
if ( !$maxDepth-- ) return $this;
}
$oldFetchType = $this->fetchType;
$oldAliasName = $this->aliasName;
$oldWith = $this->withSql;
$oldBindings = $this->withParams;
$beans = $this->$property;
if ( $beans === NULL ) return $this;
if ( !is_array( $beans ) ) $beans = array( $beans );
foreach( $beans as $bean ) {
/** @var OODBBean $bean */
$function( $bean );
$bean->fetchType = $oldFetchType;
$bean->aliasName = $oldAliasName;
$bean->withSql = $oldWith;
$bean->withParams = $oldBindings;
$bean->traverse( $property, $function, $maxDepth );
}
return $this;
}
/**
* Implementation of Countable interface. Makes it possible to use
* count() function on a bean.
*
* @return integer
*/
public function count()
{
return count( $this->properties );
}
/**
* Checks whether a bean is empty or not.
* A bean is empty if it has no other properties than the id field OR
* if all the other property are empty().
*
* @return boolean
*/
public function isEmpty()
{
$empty = TRUE;
foreach ( $this->properties as $key => $value ) {
if ( $key == 'id' ) {
continue;
}
if ( !empty( $value ) ) {
$empty = FALSE;
}
}
return $empty;
}
/**
* Chainable setter.
*
* @param string $property the property of the bean
* @param mixed $value the value you want to set
*
* @return OODBBean
*/
public function setAttr( $property, $value )
{
$this->$property = $value;
return $this;
}
/**
* Comfort method.
* Unsets all properties in array.
*
* @param array $properties properties you want to unset.
*
* @return OODBBean
*/
public function unsetAll( $properties )
{
foreach ( $properties as $prop ) {
if ( isset( $this->properties[$prop] ) ) {
unset( $this->properties[$prop] );
}
}
return $this;
}
/**
* Returns original (old) value of a property.
* You can use this method to see what has changed in a
* bean.
*
* @param string $property name of the property you want the old value of
*
* @return mixed
*/
public function old( $property )
{
$old = $this->getMeta( 'sys.orig', array() );
if ( array_key_exists( $property, $old ) ) {
return $old[$property];
}
return NULL;
}
/**
* Convenience method.
* Returns TRUE if the bean has been changed, or FALSE otherwise.
* Same as $bean->getMeta('tainted');
* Note that a bean becomes tainted as soon as you retrieve a list from
* the bean. This is because the bean lists are arrays and the bean cannot
* determine whether you have made modifications to a list so RedBeanPHP
* will mark the whole bean as tainted.
*
* @return boolean
*/
public function isTainted()
{
return $this->getMeta( 'tainted' );
}
/**
* Returns TRUE if the value of a certain property of the bean has been changed and
* FALSE otherwise.
*
* Note that this method will return TRUE if applied to a loaded list.
* Also note that this method keeps track of the bean's history regardless whether
* it has been stored or not. Storing a bean does not undo 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
*/
public function hasChanged( $property )
{
return ( array_key_exists( $property, $this->properties ) ) ?
$this->old( $property ) != $this->properties[$property] : FALSE;
}
/**
* Returns TRUE if the specified list exists, has been loaded and has been changed:
* beans have been added or deleted. This method will not tell you anything about
* the state of the beans in the list.
*
* @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
* relations. For instance, given an album and a song you can connect the two
* using a track with a number like this:
*
* Usage:
*
*
* $album->link('track', array('number'=>1))->song = $song;
*
*
* or:
*
*
* $album->link($trackBean)->song = $song;
*
*
* What this method does is adding the link bean to the own-list, in this case
* ownTrack. If the first argument is a string and the second is an array or
* a JSON string then the linking bean gets dispensed on-the-fly as seen in
* example #1. After preparing the linking bean, the bean is returned thus
* allowing the chained setter: ->song = $song.
*
* @param string|OODBBean $type type of bean to dispense or the full bean
* @param string|array $qualification JSON string or array (optional)
*
* @return OODBBean
*/
public function link( $typeOrBean, $qualification = array() )
{
if ( is_string( $typeOrBean ) ) {
$typeOrBean = AQueryWriter::camelsSnake( $typeOrBean );
$bean = $this->beanHelper->getToolBox()->getRedBean()->dispense( $typeOrBean );
if ( is_string( $qualification ) ) {
$data = json_decode( $qualification, TRUE );
} else {
$data = $qualification;
}
foreach ( $data as $key => $value ) {
$bean->$key = $value;
}
} else {
$bean = $typeOrBean;
}
$list = 'own' . ucfirst( $bean->getMeta( 'type' ) );
array_push( $this->$list, $bean );
return $bean;
}
/**
* Returns a bean of the given type with the same ID of as
* the current one. This only happens in a one-to-one relation.
* This is as far as support for 1-1 goes in RedBeanPHP. This
* method will only return a reference to the bean, changing it
* and storing the bean will not update the related one-bean.
*
* @return OODBBean
*/
public function one( $type ) {
return $this->beanHelper->getToolBox()->getRedBean()->load( $type, $this->id );
}
/**
* Returns the same bean freshly loaded from the database.
*
* @return OODBBean
*/
public function fresh()
{
return $this->beanHelper->getToolbox()->getRedBean()->load( $this->getMeta( 'type' ), $this->properties['id'] );
}
/**
* Registers a association renaming globally.
*
* @param string $via type you wish to use for shared lists
*
* @return OODBBean
*/
public function via( $via )
{
$this->via = AQueryWriter::camelsSnake( $via );
return $this;
}
/**
* Counts all own beans of type $type.
* Also works with alias(), with() and withCondition().
*
* @param string $type the type of bean you want to count
*
* @return integer
*/
public function countOwn( $type )
{
$type = $this->beau( $type );
if ( $this->aliasName ) {
$myFieldLink = $this->aliasName . '_id';
$this->aliasName = NULL;
} else {
$myFieldLink = $this->__info['type'] . '_id';
}
$count = 0;
if ( $this->getID() ) {
$firstKey = NULL;
if ( count( $this->withParams ) > 0 ) {
reset( $this->withParams );
$firstKey = key( $this->withParams );
}
$joinSql = $this->parseJoin( $type );
if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
$bindings = $this->withParams;
$bindings[':slot0'] = $this->getID();
$count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " {$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
} else {
$bindings = array_merge( array( $this->getID() ), $this->withParams );
$count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " {$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
}
}
$this->clearModifiers();
return (int) $count;
}
/**
* Counts all shared beans of type $type.
* Also works with via(), with() and withCondition().
*
* @param string $type type of bean you wish to count
*
* @return integer
*/
public function countShared( $type )
{
$toolbox = $this->beanHelper->getToolbox();
$redbean = $toolbox->getRedBean();
$writer = $toolbox->getWriter();
if ( $this->via ) {
$oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
if ( $oldName !== $this->via ) {
//set the new renaming rule
$writer->renameAssocTable( $oldName, $this->via );
$this->via = NULL;
}
}
$type = $this->beau( $type );
$count = 0;
if ( $this->getID() ) {
$count = $redbean->getAssociationManager()->relatedCount( $this, $type, $this->withSql, $this->withParams );
}
$this->clearModifiers();
return (integer) $count;
}
/**
* Iterates through the specified own-list and
* fetches all properties (with their type) and
* returns the references.
* Use this method to quickly load indirectly related
* beans in an own-list. Whenever you cannot use a
* shared-list this method offers the same convenience
* by aggregating the parent beans of all children in
* the specified own-list.
*
* Example:
*
*
* $quest->aggr( 'xownQuestTarget', 'target', 'quest' );
*
*
* Loads (in batch) and returns references to all
* quest beans residing in the $questTarget->target properties
* of each element in the xownQuestTargetList.
*
* @param string $list the list you wish to process
* @param string $property the property to load
* @param string $type the type of bean residing in this property (optional)
*
* @return array
*/
public function &aggr( $list, $property, $type = NULL )
{
$this->via = NULL;
$ids = $beanIndex = $references = array();
if ( strlen( $list ) < 4 ) throw new RedException('Invalid own-list.');
if ( strpos( $list, 'own') !== 0 ) throw new RedException('Only own-lists can be aggregated.');
if ( !ctype_upper( substr( $list, 3, 1 ) ) ) throw new RedException('Invalid own-list.');
if ( is_null( $type ) ) $type = $property;
foreach( $this->$list as $bean ) {
$field = $property . '_id';
if ( isset( $bean->$field) ) {
$ids[] = $bean->$field;
$beanIndex[$bean->$field] = $bean;
}
}
$beans = $this->beanHelper->getToolBox()->getRedBean()->batch( $type, $ids );
//now preload the beans as well
foreach( $beans as $bean ) {
$beanIndex[$bean->id]->setProperty( $property, $bean );
}
foreach( $beanIndex as $indexedBean ) {
$references[] = $indexedBean->$property;
}
return $references;
}
/**
* Tests whether the database identities of two beans are equal.
*
* @param OODBBean $bean other bean
*
* @return boolean
*/
public function equals(OODBBean $bean)
{
return (bool) (
( (string) $this->properties['id'] === (string) $bean->properties['id'] )
&& ( (string) $this->__info['type'] === (string) $bean->__info['type'] )
);
}
/**
* Magic method jsonSerialize, implementation for the \JsonSerializable interface,
* this method gets called by json_encode and facilitates a better JSON representation
* of the bean. Exports the bean on JSON serialization, for the JSON fans.
*
* @see http://php.net/manual/en/class.jsonserializable.php
*
* @return array
*/
public function jsonSerialize()
{
return $this->export();
}
}