5 use RedBeanPHP\ToolBox as ToolBox;
6 use RedBeanPHP\AssociationManager as AssociationManager;
7 use RedBeanPHP\OODB as OODB;
8 use RedBeanPHP\OODBBean as OODBBean;
9 use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
13 * The Duplication Manager creates deep copies from beans, this means
14 * it can duplicate an entire bean hierarchy. You can use this feature to
15 * implement versioning for instance. Because duplication and exporting are
16 * closely related this class is also used to export beans recursively
17 * (i.e. we make a duplicate and then convert to array). This class allows
18 * you to tune the duplication process by specifying filters determining
19 * which relations to take into account and by specifying tables
20 * (in which case no reflective queries have to be issued thus improving
21 * performance). This class also hosts the Camelfy function used to
22 * reformat the keys of an array, this method is publicly available and
23 * used internally by exportAll().
25 * @file RedBeanPHP/DuplicationManager.php
26 * @author Gabor de Mooij and the RedBeanPHP Community
30 * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community
31 * This source file is subject to the BSD/GPLv2 License that is bundled
32 * with this source code in the file license.txt.
34 class DuplicationManager
42 * @var AssociationManager
44 protected $associationManager;
54 protected $tables = array();
59 protected $columns = array();
64 protected $filters = array();
69 protected $cacheTables = FALSE;
72 * Copies the shared beans in a bean, i.e. all the sharedBean-lists.
74 * @param OODBBean $copy target bean to copy lists to
75 * @param string $shared name of the shared list
76 * @param array $beans array with shared beans to copy
80 private function copySharedBeans( OODBBean $copy, $shared, $beans )
82 $copy->$shared = array();
84 foreach ( $beans as $subBean ) {
85 array_push( $copy->$shared, $subBean );
90 * Copies the own beans in a bean, i.e. all the ownBean-lists.
91 * Each bean in the own-list belongs exclusively to its owner so
92 * we need to invoke the duplicate method again to duplicate each bean here.
94 * @param OODBBean $copy target bean to copy lists to
95 * @param string $owned name of the own list
96 * @param array $beans array with shared beans to copy
97 * @param array $trail array with former beans to detect recursion
98 * @param boolean $preserveIDs TRUE means preserve IDs, for export only
102 private function copyOwnBeans( OODBBean $copy, $owned, $beans, $trail, $preserveIDs )
104 $copy->$owned = array();
105 foreach ( $beans as $subBean ) {
106 array_push( $copy->$owned, $this->duplicate( $subBean, $trail, $preserveIDs ) );
111 * Creates a copy of bean $bean and copies all primitive properties (not lists)
112 * and the parents beans to the newly created bean. Also sets the ID of the bean
115 * @param OODBBean $bean bean to copy
119 private function createCopy( OODBBean $bean )
121 $type = $bean->getMeta( 'type' );
123 $copy = $this->redbean->dispense( $type );
124 $copy->setMeta( 'sys.dup-from-id', $bean->id );
125 $copy->setMeta( 'sys.old-id', $bean->id );
126 $copy->importFrom( $bean );
133 * Generates a key from the bean type and its ID and determines if the bean
134 * occurs in the trail, if not the bean will be added to the trail.
135 * Returns TRUE if the bean occurs in the trail and FALSE otherwise.
137 * @param array $trail list of former beans
138 * @param OODBBean $bean currently selected bean
142 private function inTrailOrAdd( &$trail, OODBBean $bean )
144 $type = $bean->getMeta( 'type' );
145 $key = $type . $bean->getID();
147 if ( isset( $trail[$key] ) ) {
151 $trail[$key] = $bean;
157 * Given the type name of a bean this method returns the canonical names
158 * of the own-list and the shared-list properties respectively.
159 * Returns a list with two elements: name of the own-list, and name
160 * of the shared list.
162 * @param string $typeName bean type name
166 private function getListNames( $typeName )
168 $owned = 'own' . ucfirst( $typeName );
169 $shared = 'shared' . ucfirst( $typeName );
171 return array( $owned, $shared );
175 * Determines whether the bean has an own list based on
176 * schema inspection from realtime schema or cache.
178 * @param string $type bean type to get list for
179 * @param string $target type of list you want to detect
183 protected function hasOwnList( $type, $target )
185 return isset( $this->columns[$target][$type . '_id'] );
189 * Determines whether the bea has a shared list based on
190 * schema inspection from realtime schema or cache.
192 * @param string $type bean type to get list for
193 * @param string $target type of list you are looking for
197 protected function hasSharedList( $type, $target )
199 return in_array( AQueryWriter::getAssocTableFormat( array( $type, $target ) ), $this->tables );
203 * @see DuplicationManager::dup
205 * @param OODBBean $bean bean to be copied
206 * @param array $trail trail to prevent infinite loops
207 * @param boolean $preserveIDs preserve IDs
211 protected function duplicate( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
213 if ( $this->inTrailOrAdd( $trail, $bean ) ) return $bean;
215 $type = $bean->getMeta( 'type' );
217 $copy = $this->createCopy( $bean );
218 foreach ( $this->tables as $table ) {
220 if ( !empty( $this->filters ) ) {
221 if ( !in_array( $table, $this->filters ) ) continue;
224 list( $owned, $shared ) = $this->getListNames( $table );
226 if ( $this->hasSharedList( $type, $table ) ) {
227 if ( $beans = $bean->$shared ) {
228 $this->copySharedBeans( $copy, $shared, $beans );
230 } elseif ( $this->hasOwnList( $type, $table ) ) {
231 if ( $beans = $bean->$owned ) {
232 $this->copyOwnBeans( $copy, $owned, $beans, $trail, $preserveIDs );
235 $copy->setMeta( 'sys.shadow.' . $owned, NULL );
238 $copy->setMeta( 'sys.shadow.' . $shared, NULL );
241 $copy->id = ( $preserveIDs ) ? $bean->id : $copy->id;
248 * creates a new instance of DupManager.
250 * @param ToolBox $toolbox
252 public function __construct( ToolBox $toolbox )
254 $this->toolbox = $toolbox;
255 $this->redbean = $toolbox->getRedBean();
256 $this->associationManager = $this->redbean->getAssociationManager();
260 * Recursively turns the keys of an array into
263 * @param array $array array to camelize
264 * @param boolean $dolphinMode whether you want the exception for IDs.
268 public function camelfy( $array, $dolphinMode = false ) {
270 foreach( $array as $key => $element ) {
271 $newKey = preg_replace_callback( '/_(\w)/', function( &$matches ){
272 return strtoupper( $matches[1] );
275 if ( $dolphinMode ) {
276 $newKey = preg_replace( '/(\w)Id$/', '$1ID', $newKey );
279 $newArray[$newKey] = ( is_array($element) ) ? $this->camelfy( $element, $dolphinMode ) : $element;
285 * For better performance you can pass the tables in an array to this method.
286 * If the tables are available the duplication manager will not query them so
287 * this might be beneficial for performance.
289 * This method allows two array formats:
292 * array( TABLE1, TABLE2 ... )
298 * array( TABLE1 => array( COLUMN1, COLUMN2 ... ) ... )
301 * @param array $tables a table cache array
305 public function setTables( $tables )
307 foreach ( $tables as $key => $value ) {
308 if ( is_numeric( $key ) ) {
309 $this->tables[] = $value;
311 $this->tables[] = $key;
312 $this->columns[$key] = $value;
316 $this->cacheTables = TRUE;
320 * Returns a schema array for cache.
321 * You can use the return value of this method as a cache,
322 * store it in RAM or on disk and pass it to setTables later.
326 public function getSchema()
328 return $this->columns;
332 * Indicates whether you want the duplication manager to cache the database schema.
333 * If this flag is set to TRUE the duplication manager will query the database schema
334 * only once. Otherwise the duplicationmanager will, by default, query the schema
335 * every time a duplication action is performed (dup()).
337 * @param boolean $yesNo TRUE to use caching, FALSE otherwise
339 public function setCacheTables( $yesNo )
341 $this->cacheTables = $yesNo;
345 * A filter array is an array with table names.
346 * By setting a table filter you can make the duplication manager only take into account
347 * certain bean types. Other bean types will be ignored when exporting or making a
348 * deep copy. If no filters are set all types will be taking into account, this is
349 * the default behavior.
351 * @param array $filters list of tables to be filtered
355 public function setFilters( $filters )
357 if ( !is_array( $filters ) ) {
358 $filters = array( $filters );
361 $this->filters = $filters;
365 * Makes a copy of a bean. This method makes a deep copy
366 * of the bean.The copy will have the following features.
367 * - All beans in own-lists will be duplicated as well
368 * - All references to shared beans will be copied but not the shared beans themselves
369 * - All references to parent objects (_id fields) will be copied but not the parents themselves
370 * In most cases this is the desired scenario for copying beans.
371 * This function uses a trail-array to prevent infinite recursion, if a recursive bean is found
372 * (i.e. one that already has been processed) the ID of the bean will be returned.
373 * This should not happen though.
376 * This function does a reflectional database query so it may be slow.
379 * this function actually passes the arguments to a protected function called
380 * duplicate() that does all the work. This method takes care of creating a clone
381 * of the bean to avoid the bean getting tainted (triggering saving when storing it).
383 * @param OODBBean $bean bean to be copied
384 * @param array $trail for internal usage, pass array()
385 * @param boolean $preserveIDs for internal usage
389 public function dup( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
391 if ( !count( $this->tables ) ) {
392 $this->tables = $this->toolbox->getWriter()->getTables();
395 if ( !count( $this->columns ) ) {
396 foreach ( $this->tables as $table ) {
397 $this->columns[$table] = $this->toolbox->getWriter()->getColumns( $table );
401 $rs = $this->duplicate( ( clone $bean ), $trail, $preserveIDs );
403 if ( !$this->cacheTables ) {
404 $this->tables = array();
405 $this->columns = array();
412 * Exports a collection of beans recursively.
413 * This method will export an array of beans in the first argument to a
414 * set of arrays. This can be used to send JSON or XML representations
415 * of bean hierarchies to the client.
417 * For every bean in the array this method will export:
419 * - contents of the bean
420 * - all own bean lists (recursively)
421 * - all shared beans (but not THEIR own lists)
423 * If the second parameter is set to TRUE the parents of the beans in the
424 * array will be exported as well (but not THEIR parents).
426 * The third parameter can be used to provide a white-list array
427 * for filtering. This is an array of strings representing type names,
428 * only the type names in the filter list will be exported.
430 * The fourth parameter can be used to change the keys of the resulting
431 * export arrays. The default mode is 'snake case' but this leaves the
432 * keys as-is, because 'snake' is the default case style used by
433 * RedBeanPHP in the database. You can set this to 'camel' for
434 * camel cased keys or 'dolphin' (same as camelcase but id will be
435 * converted to ID instead of Id).
437 * @param array|OODBBean $beans beans to be exported
438 * @param boolean $parents also export parents
439 * @param array $filters only these types (whitelist)
440 * @param string $caseStyle case style identifier
444 public function exportAll( $beans, $parents = FALSE, $filters = array(), $caseStyle = 'snake')
448 if ( !is_array( $beans ) ) {
449 $beans = array( $beans );
452 foreach ( $beans as $bean ) {
453 $this->setFilters( $filters );
455 $duplicate = $this->dup( $bean, array(), TRUE );
457 $array[] = $duplicate->export( FALSE, $parents, FALSE, $filters );
460 if ( $caseStyle === 'camel' ) $array = $this->camelfy( $array );
461 if ( $caseStyle === 'dolphin' ) $array = $this->camelfy( $array, true );