PHP Classes

File: vendor/gabordemooij/redbean/RedBeanPHP/Repository.php

Recommend this page to a friend!
  Classes of Adrian M   upMVC   vendor/gabordemooij/redbean/RedBeanPHP/Repository.php   Download  
File: vendor/gabordemooij/redbean/RedBeanPHP/Repository.php
Role: Class source
Content type: text/plain
Description: Class source
Class: upMVC
Pure PHP web development without other frameworks
Author: By
Last change:
Date: 1 month ago
Size: 23,557 bytes
 

Contents

Class file image Download
<?php namespace RedBeanPHP; use RedBeanPHP\Adapter\DBAdapter as DBAdapter; use RedBeanPHP\QueryWriter as QueryWriter; use RedBeanPHP\BeanHelper as BeanHelper; use RedBeanPHP\RedException\SQL as SQLException; use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter; use RedBeanPHP\Cursor as Cursor; use RedBeanPHP\Cursor\NullCursor as NullCursor; /** * Abstract Repository. * * OODB manages two repositories, a fluid one that * adjust the database schema on-the-fly to accommodate for * new bean types (tables) and new properties (columns) and * a frozen one for use in a production environment. OODB * allows you to swap the repository instances using the freeze() * method. * * @file RedBeanPHP/Repository.php * @author Gabor de Mooij and the RedBeanPHP community * @license BSD/GPLv2 * * @copyright * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community * This source file is subject to the BSD/GPLv2 License that is bundled * with this source code in the file license.txt. */ abstract class Repository { /** * @var array */ protected $stash = NULL; /** * @var integer */ protected $nesting = 0; /** * @var QueryWriter */ protected $writer; /** * @var boolean|array */ protected $partialBeans = FALSE; /** * @var OODB */ public $oodb = NULL; /** * Toggles 'partial bean mode'. If this mode has been * selected the repository will only update the fields of a bean that * have been changed rather than the entire bean. * Pass the value TRUE to select 'partial mode' for all beans. * Pass the value FALSE to disable 'partial mode'. * Pass an array of bean types if you wish to use partial mode only * for some types. * This method will return the previous value. * * @param boolean|array $yesNoBeans List of type names or 'all' * * @return boolean|array */ public function usePartialBeans( $yesNoBeans ) { $oldValue = $this->partialBeans; $this->partialBeans = $yesNoBeans; return $oldValue; } /** * Fully processes a bean and updates the associated records in the database. * First the bean properties will be grouped as 'embedded' bean, * addition, deleted 'trash can' or residue. Next, the different groups * of beans will be processed accordingly and the reference bean (i.e. * the one that was passed to the method as an argument) will be stored. * Each type of list (own/shared) has 3 bean processors: * * - trashCanProcessor : removes the bean or breaks its association with the current bean * - additionProcessor : associates the bean with the current one * - residueProcessor : manages beans in lists that 'remain' but may need to be updated * * This method first groups the beans and then calls the * internal processing methods. * * @param OODBBean $bean bean to process * * @return void */ protected function storeBeanWithLists( OODBBean $bean ) { $sharedAdditions = $sharedTrashcan = $sharedresidue = $sharedItems = $ownAdditions = $ownTrashcan = $ownresidue = $embeddedBeans = array(); //Define groups foreach ( $bean as $property => $value ) { $value = ( $value instanceof SimpleModelInterface ) ? $value->unbox() : $value; if ( $value instanceof OODBBean ) { $this->processEmbeddedBean( $embeddedBeans, $bean, $property, $value ); $bean->setMeta("sys.typeof.{$property}", $value->getMeta('type')); } elseif ( is_array( $value ) ) { foreach($value as &$item) { $item = ( $item instanceof SimpleModelInterface ) ? $item->unbox() : $item; } $originals = $bean->moveMeta( 'sys.shadow.' . $property, array() ); if ( strpos( $property, 'own' ) === 0 ) { list( $ownAdditions, $ownTrashcan, $ownresidue ) = $this->processGroups( $originals, $value, $ownAdditions, $ownTrashcan, $ownresidue ); $listName = lcfirst( substr( $property, 3 ) ); if ($bean->moveMeta( 'sys.exclusive-'. $listName ) ) { OODBBean::setMetaAll( $ownTrashcan, 'sys.garbage', TRUE ); OODBBean::setMetaAll( $ownAdditions, 'sys.buildcommand.fkdependson', $bean->getMeta( 'type' ) ); } unset( $bean->$property ); } elseif ( strpos( $property, 'shared' ) === 0 ) { list( $sharedAdditions, $sharedTrashcan, $sharedresidue ) = $this->processGroups( $originals, $value, $sharedAdditions, $sharedTrashcan, $sharedresidue ); unset( $bean->$property ); } } } $this->storeBean( $bean ); $this->processTrashcan( $bean, $ownTrashcan ); $this->processAdditions( $bean, $ownAdditions ); $this->processResidue( $ownresidue ); $this->processSharedTrashcan( $bean, $sharedTrashcan ); $this->processSharedAdditions( $bean, $sharedAdditions ); $this->processSharedResidue( $bean, $sharedresidue ); } /** * Process groups. Internal function. Processes different kind of groups for * storage function. Given a list of original beans and a list of current beans, * this function calculates which beans remain in the list (residue), which * have been deleted (are in the trashcan) and which beans have been added * (additions). * * @param array $originals originals * @param array $current the current beans * @param array $additions beans that have been added * @param array $trashcan beans that have been deleted * @param array $residue beans that have been left untouched * * @return array */ protected function processGroups( $originals, $current, $additions, $trashcan, $residue ) { return array( array_merge( $additions, array_diff( $current, $originals ) ), array_merge( $trashcan, array_diff( $originals, $current ) ), array_merge( $residue, array_intersect( $current, $originals ) ) ); } /** * Processes a list of beans from a bean. * A bean may contain lists. This * method handles shared addition lists; i.e. * the $bean->sharedObject properties. * Shared beans will be associated with each other using the * Association Manager. * * @param OODBBean $bean the bean * @param array $sharedAdditions list with shared additions * * @return void */ protected function processSharedAdditions( $bean, $sharedAdditions ) { foreach ( $sharedAdditions as $addition ) { if ( $addition instanceof OODBBean ) { $this->oodb->getAssociationManager()->associate( $addition, $bean ); } else { throw new RedException( 'Array may only contain OODBBeans' ); } } } /** * Processes a list of beans from a bean. * A bean may contain lists. This * method handles own lists; i.e. * the $bean->ownObject properties. * A residue is a bean in an own-list that stays * where it is. This method checks if there have been any * modification to this bean, in that case * the bean is stored once again, otherwise the bean will be left untouched. * * @param array $ownresidue list to process * * @return void */ protected function processResidue( $ownresidue ) { foreach ( $ownresidue as $residue ) { if ( $residue->getMeta( 'tainted' ) ) { $this->store( $residue ); } } } /** * Processes a list of beans from a bean. A bean may contain lists. This * method handles own lists; i.e. the $bean->ownObject properties. * A trash can bean is a bean in an own-list that has been removed * (when checked with the shadow). This method * checks if the bean is also in the dependency list. If it is the bean will be removed. * If not, the connection between the bean and the owner bean will be broken by * setting the ID to NULL. * * @param OODBBean $bean bean to process * @param array $ownTrashcan list to process * * @return void */ protected function processTrashcan( $bean, $ownTrashcan ) { foreach ( $ownTrashcan as $trash ) { $myFieldLink = $bean->getMeta( 'type' ) . '_id'; $alias = $bean->getMeta( 'sys.alias.' . $trash->getMeta( 'type' ) ); if ( $alias ) $myFieldLink = $alias . '_id'; if ( $trash->getMeta( 'sys.garbage' ) === TRUE ) { $this->trash( $trash ); } else { $trash->$myFieldLink = NULL; $this->store( $trash ); } } } /** * Unassociates the list items in the trashcan. * This bean processor processes the beans in the shared trash can. * This group of beans has been deleted from a shared list. * The affected beans will no longer be associated with the bean * that contains the shared list. * * @param OODBBean $bean bean to process * @param array $sharedTrashcan list to process * * @return void */ protected function processSharedTrashcan( $bean, $sharedTrashcan ) { foreach ( $sharedTrashcan as $trash ) { $this->oodb->getAssociationManager()->unassociate( $trash, $bean ); } } /** * Stores all the beans in the residue group. * This bean processor processes the beans in the shared residue * group. This group of beans 'remains' in the list but might need * to be updated or synced. The affected beans will be stored * to perform the required database queries. * * @param OODBBean $bean bean to process * @param array $sharedresidue list to process * * @return void */ protected function processSharedResidue( $bean, $sharedresidue ) { foreach ( $sharedresidue as $residue ) { $this->store( $residue ); } } /** * Determines whether the bean has 'loaded lists' or * 'loaded embedded beans' that need to be processed * by the store() method. * * @param OODBBean $bean bean to be examined * * @return boolean */ protected function hasListsOrObjects( OODBBean $bean ) { $processLists = FALSE; foreach ( $bean as $value ) { if ( is_array( $value ) || is_object( $value ) ) { $processLists = TRUE; break; } } return $processLists; } /** * Converts an embedded bean to an ID, removes the bean property and * stores the bean in the embedded beans array. The id will be * assigned to the link field property, i.e. 'bean_id'. * * @param array $embeddedBeans destination array for embedded bean * @param OODBBean $bean target bean to process * @param string $property property that contains the embedded bean * @param OODBBean $value embedded bean itself * * @return void */ protected function processEmbeddedBean( &$embeddedBeans, $bean, $property, OODBBean $value ) { $linkField = $property . '_id'; if ( !$value->id || $value->getMeta( 'tainted' ) ) { $this->store( $value ); } $id = $value->id; if ($bean->$linkField != $id) $bean->$linkField = $id; $bean->setMeta( 'cast.' . $linkField, 'id' ); $embeddedBeans[$linkField] = $value; unset( $bean->$property ); } /** * Constructor, requires a query writer and OODB. * Creates a new instance of the bean respository class. * * @param OODB $oodb instance of object database * @param QueryWriter $writer the Query Writer to use for this repository * * @return void */ public function __construct( OODB $oodb, QueryWriter $writer ) { $this->writer = $writer; $this->oodb = $oodb; } /** * Checks whether a OODBBean bean is valid. * If the type is not valid or the ID is not valid it will * throw an exception: Security. To be valid a bean * must abide to the following rules: * * - It must have an primary key id property named: id * - It must have a type * - The type must conform to the RedBeanPHP naming policy * - All properties must be valid * - All values must be valid * * @param OODBBean $bean the bean that needs to be checked * * @return void */ public function check( OODBBean $bean ) { //Is all meta information present? if ( !isset( $bean->id ) ) { throw new RedException( 'Bean has incomplete Meta Information id ' ); } if ( !( $bean->getMeta( 'type' ) ) ) { throw new RedException( 'Bean has incomplete Meta Information II' ); } //Pattern of allowed characters $pattern = '/[^a-z0-9_]/i'; //Does the type contain invalid characters? if ( preg_match( $pattern, $bean->getMeta( 'type' ) ) ) { throw new RedException( 'Bean Type is invalid' ); } //Are the properties and values valid? foreach ( $bean as $prop => $value ) { if ( is_array( $value ) || ( is_object( $value ) ) ) { throw new RedException( "Invalid Bean value: property $prop" ); } else if ( strlen( $prop ) < 1 || preg_match( $pattern, $prop ) ) { throw new RedException( "Invalid Bean property: property $prop" ); } } } /** * Dispenses a new bean (a OODBBean Bean Object) * of the specified type. Always * use this function to get an empty bean object. Never * instantiate a OODBBean yourself because it needs * to be configured before you can use it with RedBean. This * function applies the appropriate initialization / * configuration for you. * * To use a different class for beans (instead of OODBBean) set: * REDBEAN_OODBBEAN_CLASS to the name of the class to be used. * * @param string $type type of bean you want to dispense * @param int $number number of beans you would like to get * @param boolean $alwaysReturnArray if TRUE always returns the result as an array * * @return OODBBean|OODBBean[] */ public function dispense( $type, $number = 1, $alwaysReturnArray = FALSE ) { $OODBBEAN = defined( 'REDBEAN_OODBBEAN_CLASS' ) ? REDBEAN_OODBBEAN_CLASS : '\RedBeanPHP\OODBBean'; $beans = array(); for ( $i = 0; $i < $number; $i++ ) { $bean = new $OODBBEAN; $bean->initializeForDispense( $type, $this->oodb->getBeanHelper() ); $this->check( $bean ); $this->oodb->signal( 'dispense', $bean ); $beans[] = $bean; } return ( count( $beans ) === 1 && !$alwaysReturnArray ) ? array_pop( $beans ) : $beans; } /** * Searches the database for a bean that matches conditions $conditions and sql $addSQL * and returns an array containing all the beans that have been found. * * Conditions need to take form: * * <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 $sql SQL to be used in query * @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not) * * @return array */ public function find( $type, $conditions = array(), $sql = NULL, $bindings = array() ) { //for backward compatibility, allow mismatch arguments: if ( is_array( $sql ) ) { if ( isset( $sql[1] ) ) { $bindings = $sql[1]; } $sql = $sql[0]; } try { $beans = $this->convertToBeans( $type, $this->writer->queryRecord( $type, $conditions, $sql, $bindings ) ); return $beans; } catch ( SQLException $exception ) { $this->handleException( $exception ); } return array(); } /** * Finds a BeanCollection. * Given a type, an SQL snippet and optionally some parameter bindings * this methods returns a BeanCollection for your query. * * The BeanCollection represents a collection of beans and * makes it possible to use database cursors. The BeanCollection * has a method next() to obtain the first, next and last bean * in the collection. The BeanCollection does not implement the array * interface nor does it try to act like an array because it cannot go * backward or rewind itself. * * @param string $type type of beans you are looking for * @param string $sql SQL to be used in query * @param array $bindings whether you prefer to use a WHERE clause or not (TRUE = not) * * @return BeanCollection */ public function findCollection( $type, $sql, $bindings = array() ) { try { $cursor = $this->writer->queryRecordWithCursor( $type, $sql, $bindings ); return new BeanCollection( $type, $this, $cursor ); } catch ( SQLException $exception ) { $this->handleException( $exception ); } return new BeanCollection( $type, $this, new NullCursor ); } /** * Stores a bean in the database. This method takes a * OODBBean Bean Object $bean and stores it * in the database. If the database schema is not compatible * with this bean and RedBean runs in fluid mode the schema * will be altered to store the bean correctly. * If the database schema is not compatible with this bean and * RedBean runs in frozen mode it will throw an exception. * This function returns the primary key ID of the inserted * bean. * * The return value is an integer if possible. If it is not possible to * represent the value as an integer a string will be returned. We use * explicit casts instead of functions to preserve performance * (0.13 vs 0.28 for 10000 iterations on Core i3). * * @param OODBBean|SimpleModel|SimpleModelInterface $bean bean to store * * @return integer|string */ public function store( $bean ) { $processLists = $this->hasListsOrObjects( $bean ); if ( !$processLists && !$bean->getMeta( 'tainted' ) ) { return $bean->getID(); //bail out! } $this->oodb->signal( 'update', $bean ); $processLists = $this->hasListsOrObjects( $bean ); //check again, might have changed by model! if ( $processLists ) { $this->storeBeanWithLists( $bean ); } else { $this->storeBean( $bean ); } $this->oodb->signal( 'after_update', $bean ); return ( (string) $bean->id === (string) (int) $bean->id ) ? (int) $bean->id : (string) $bean->id; } /** * Returns an array of beans. Pass a type and a series of ids and * this method will bring you the corresponding beans. * * important note: Because this method loads beans using the load() * function (but faster) it will return empty beans with ID 0 for * every bean that could not be located. The resulting beans will have the * passed IDs as their keys. * * @param string $type type of beans * @param array $ids ids to load * * @return array */ public function batch( $type, $ids ) { if ( !$ids ) { return array(); } $collection = array(); try { $rows = $this->writer->queryRecord( $type, array( 'id' => $ids ) ); } catch ( SQLException $e ) { $this->handleException( $e ); $rows = FALSE; } $this->stash[$this->nesting] = array(); if ( !$rows ) { return array(); } foreach ( $rows as $row ) { $this->stash[$this->nesting][$row['id']] = $row; } foreach ( $ids as $id ) { $collection[$id] = $this->load( $type, $id ); } $this->stash[$this->nesting] = NULL; return $collection; } /** * This is a convenience method; it converts database rows * (arrays) into beans. Given a type and a set of rows this method * will return an array of beans of the specified type loaded with * the data fields provided by the result set from the database. * * New in 4.3.2: meta mask. The meta mask is a special mask to send * data from raw result rows to the meta store of the bean. This is * useful for bundling additional information with custom queries. * Values of every column who's name starts with $mask will be * transferred to the meta section of the bean under key 'data.bundle'. * * @param string $type type of beans you would like to have * @param array $rows rows from the database result * @param string $mask meta mask to apply (optional) * * @return array */ public function convertToBeans( $type, $rows, $mask = '__meta' ) { $masktype = gettype( $mask ); switch ( $masktype ) { case 'string': break; case 'array': $maskflip = array(); foreach ( $mask as $m ) { if ( !is_string( $m ) ) { $mask = NULL; $masktype = 'NULL'; break 2; } $maskflip[$m] = TRUE; } $mask = $maskflip; break; default: $mask = NULL; $masktype = 'NULL'; } $collection = array(); $this->stash[$this->nesting] = array(); foreach ( $rows as $row ) { if ( $mask !== NULL ) { $meta = array(); foreach( $row as $key => $value ) { if ( $masktype === 'string' ) { if ( strpos( $key, $mask ) === 0 ) { unset( $row[$key] ); $meta[$key] = $value; } } elseif ( $masktype === 'array' ) { if ( isset( $mask[$key] ) ) { unset( $row[$key] ); $meta[$key] = $value; } } } } $id = $row['id']; $this->stash[$this->nesting][$id] = $row; $collection[$id] = $this->load( $type, $id ); if ( $mask !== NULL ) { $collection[$id]->setMeta( 'data.bundle', $meta ); } } $this->stash[$this->nesting] = NULL; return $collection; } /** * Counts the number of beans of type $type. * This method accepts a second argument to modify the count-query. * A third argument can be used to provide bindings for the SQL snippet. * * @param string $type type of bean we are looking for * @param string $addSQL additional SQL snippet * @param array $bindings parameters to bind to SQL * * @return integer */ public function count( $type, $addSQL = '', $bindings = array() ) { $type = AQueryWriter::camelsSnake( $type ); try { $count = (int) $this->writer->queryRecordCount( $type, array(), $addSQL, $bindings ); } catch ( SQLException $exception ) { $this->handleException( $exception ); $count = 0; } return $count; } /** * Removes a bean from the database. * This function will remove the specified OODBBean * Bean Object from the database. * * @param OODBBean|SimpleModel|SimpleModelInterface $bean bean you want to remove from database * * @return int */ public function trash( $bean ) { $this->oodb->signal( 'delete', $bean ); foreach ( $bean as $property => $value ) { if ( $value instanceof OODBBean ) { unset( $bean->$property ); } if ( is_array( $value ) ) { if ( strpos( $property, 'own' ) === 0 ) { unset( $bean->$property ); } elseif ( strpos( $property, 'shared' ) === 0 ) { unset( $bean->$property ); } } } try { $deleted = $this->writer->deleteRecord( $bean->getMeta( 'type' ), array( 'id' => array( $bean->id ) ) ); } catch ( SQLException $exception ) { $this->handleException( $exception ); } $bean->id = 0; $this->oodb->signal( 'after_delete', $bean ); return isset($deleted) ? $deleted : 0; } /** * Checks whether the specified table already exists in the database. * Not part of the Object Database interface! * * @param string $table table name * * @return boolean */ public function tableExists( $table ) { return $this->writer->tableExists( $table ); } /** * Trash all beans of a given type. * Wipes an entire type of bean. After this operation there * will be no beans left of the specified type. * This method will ignore exceptions caused by database * tables that do not exist. * * @param string $type type of bean you wish to delete all instances of * * @return boolean */ public function wipe( $type ) { try { $this->writer->wipe( $type ); return TRUE; } catch ( SQLException $exception ) { if ( !$this->writer->sqlStateIn( $exception->getSQLState(), array( QueryWriter::C_SQLSTATE_NO_SUCH_TABLE ), $exception->getDriverDetails() ) ) { throw $exception; } return FALSE; } } }