Yaffs site version 1.1
[yaffs-website] / vendor / gabordemooij / redbean / RedBeanPHP / DuplicationManager.php
1 <?php
2
3 namespace RedBeanPHP;
4
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;
10
11 /**
12  * Duplication Manager
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().
24  *
25  * @file    RedBeanPHP/DuplicationManager.php
26  * @author  Gabor de Mooij and the RedBeanPHP Community
27  * @license BSD/GPLv2
28  *
29  * @copyright
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.
33  */
34 class DuplicationManager
35 {
36         /**
37          * @var ToolBox
38          */
39         protected $toolbox;
40
41         /**
42          * @var AssociationManager
43          */
44         protected $associationManager;
45
46         /**
47          * @var OODB
48          */
49         protected $redbean;
50
51         /**
52          * @var array
53          */
54         protected $tables = array();
55
56         /**
57          * @var array
58          */
59         protected $columns = array();
60
61         /**
62          * @var array
63          */
64         protected $filters = array();
65
66         /**
67          * @var array
68          */
69         protected $cacheTables = FALSE;
70
71         /**
72          * Copies the shared beans in a bean, i.e. all the sharedBean-lists.
73          *
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
77          *
78          * @return void
79          */
80         private function copySharedBeans( OODBBean $copy, $shared, $beans )
81         {
82                 $copy->$shared = array();
83
84                 foreach ( $beans as $subBean ) {
85                         array_push( $copy->$shared, $subBean );
86                 }
87         }
88
89         /**
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.
93          *
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
99          *
100          * @return void
101          */
102         private function copyOwnBeans( OODBBean $copy, $owned, $beans, $trail, $preserveIDs )
103         {
104                 $copy->$owned = array();
105                 foreach ( $beans as $subBean ) {
106                         array_push( $copy->$owned, $this->duplicate( $subBean, $trail, $preserveIDs ) );
107                 }
108         }
109
110         /**
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
113          * to 0.
114          *
115          * @param OODBBean $bean bean to copy
116          *
117          * @return OODBBean
118          */
119         private function createCopy( OODBBean $bean )
120         {
121                 $type = $bean->getMeta( 'type' );
122
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 );
127                 $copy->id = 0;
128
129                 return $copy;
130         }
131
132         /**
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.
136          *
137          * @param array    $trail list of former beans
138          * @param OODBBean $bean  currently selected bean
139          *
140          * @return boolean
141          */
142         private function inTrailOrAdd( &$trail, OODBBean $bean )
143         {
144                 $type = $bean->getMeta( 'type' );
145                 $key  = $type . $bean->getID();
146
147                 if ( isset( $trail[$key] ) ) {
148                         return TRUE;
149                 }
150
151                 $trail[$key] = $bean;
152
153                 return FALSE;
154         }
155
156         /**
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.
161          *
162          * @param string $typeName bean type name
163          *
164          * @return array
165          */
166         private function getListNames( $typeName )
167         {
168                 $owned  = 'own' . ucfirst( $typeName );
169                 $shared = 'shared' . ucfirst( $typeName );
170
171                 return array( $owned, $shared );
172         }
173
174         /**
175          * Determines whether the bean has an own list based on
176          * schema inspection from realtime schema or cache.
177          *
178          * @param string $type   bean type to get list for
179          * @param string $target type of list you want to detect
180          *
181          * @return boolean
182          */
183         protected function hasOwnList( $type, $target )
184         {
185                 return isset( $this->columns[$target][$type . '_id'] );
186         }
187
188         /**
189          * Determines whether the bea has a shared list based on
190          * schema inspection from realtime schema or cache.
191          *
192          * @param string $type   bean type to get list for
193          * @param string $target type of list you are looking for
194          *
195          * @return boolean
196          */
197         protected function hasSharedList( $type, $target )
198         {
199                 return in_array( AQueryWriter::getAssocTableFormat( array( $type, $target ) ), $this->tables );
200         }
201
202         /**
203          * @see DuplicationManager::dup
204          *
205          * @param OODBBean $bean        bean to be copied
206          * @param array    $trail       trail to prevent infinite loops
207          * @param boolean  $preserveIDs preserve IDs
208          *
209          * @return OODBBean
210          */
211         protected function duplicate( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
212         {
213                 if ( $this->inTrailOrAdd( $trail, $bean ) ) return $bean;
214
215                 $type = $bean->getMeta( 'type' );
216
217                 $copy = $this->createCopy( $bean );
218                 foreach ( $this->tables as $table ) {
219
220                         if ( !empty( $this->filters ) ) {
221                                 if ( !in_array( $table, $this->filters ) ) continue;
222                         }
223
224                         list( $owned, $shared ) = $this->getListNames( $table );
225
226                         if ( $this->hasSharedList( $type, $table ) ) {
227                                 if ( $beans = $bean->$shared ) {
228                                         $this->copySharedBeans( $copy, $shared, $beans );
229                                 }
230                         } elseif ( $this->hasOwnList( $type, $table ) ) {
231                                 if ( $beans = $bean->$owned ) {
232                                         $this->copyOwnBeans( $copy, $owned, $beans, $trail, $preserveIDs );
233                                 }
234
235                                 $copy->setMeta( 'sys.shadow.' . $owned, NULL );
236                         }
237
238                         $copy->setMeta( 'sys.shadow.' . $shared, NULL );
239                 }
240
241                 $copy->id = ( $preserveIDs ) ? $bean->id : $copy->id;
242
243                 return $copy;
244         }
245
246         /**
247          * Constructor,
248          * creates a new instance of DupManager.
249          *
250          * @param ToolBox $toolbox
251          */
252         public function __construct( ToolBox $toolbox )
253         {
254                 $this->toolbox            = $toolbox;
255                 $this->redbean            = $toolbox->getRedBean();
256                 $this->associationManager = $this->redbean->getAssociationManager();
257         }
258
259         /**
260          * Recursively turns the keys of an array into
261          * camelCase.
262          *
263          * @param array   $array       array to camelize
264          * @param boolean $dolphinMode whether you want the exception for IDs.
265          *
266          * @return array
267          */
268         public function camelfy( $array, $dolphinMode = false ) {
269                 $newArray = array();
270                 foreach( $array as $key => $element ) {
271                         $newKey = preg_replace_callback( '/_(\w)/', function( &$matches ){
272                                 return strtoupper( $matches[1] );
273                         }, $key);
274
275                         if ( $dolphinMode ) {
276                                 $newKey = preg_replace( '/(\w)Id$/', '$1ID', $newKey );
277                         }
278
279                         $newArray[$newKey] = ( is_array($element) ) ? $this->camelfy( $element, $dolphinMode ) : $element;
280                 }
281                 return $newArray;
282         }
283
284         /**
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.
288          *
289          * This method allows two array formats:
290          *
291          * <code>
292          * array( TABLE1, TABLE2 ... )
293          * </code>
294          *
295          * or
296          *
297          * <code>
298          * array( TABLE1 => array( COLUMN1, COLUMN2 ... ) ... )
299          * </code>
300          *
301          * @param array $tables a table cache array
302          *
303          * @return void
304          */
305         public function setTables( $tables )
306         {
307                 foreach ( $tables as $key => $value ) {
308                         if ( is_numeric( $key ) ) {
309                                 $this->tables[] = $value;
310                         } else {
311                                 $this->tables[]      = $key;
312                                 $this->columns[$key] = $value;
313                         }
314                 }
315
316                 $this->cacheTables = TRUE;
317         }
318
319         /**
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.
323          *
324          * @return array
325          */
326         public function getSchema()
327         {
328                 return $this->columns;
329         }
330
331         /**
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()).
336          *
337          * @param boolean $yesNo TRUE to use caching, FALSE otherwise
338          */
339         public function setCacheTables( $yesNo )
340         {
341                 $this->cacheTables = $yesNo;
342         }
343
344         /**
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.
350          *
351          * @param array $filters list of tables to be filtered
352          *
353          * @return void
354          */
355         public function setFilters( $filters )
356         {
357                 if ( !is_array( $filters ) ) {
358                         $filters = array( $filters );
359                 }
360
361                 $this->filters = $filters;
362         }
363
364         /**
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.
374          *
375          * Note:
376          * This function does a reflectional database query so it may be slow.
377          *
378          * Note:
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).
382          *
383          * @param OODBBean $bean        bean to be copied
384          * @param array    $trail       for internal usage, pass array()
385          * @param boolean  $preserveIDs for internal usage
386          *
387          * @return OODBBean
388          */
389         public function dup( OODBBean $bean, $trail = array(), $preserveIDs = FALSE )
390         {
391                 if ( !count( $this->tables ) ) {
392                         $this->tables = $this->toolbox->getWriter()->getTables();
393                 }
394
395                 if ( !count( $this->columns ) ) {
396                         foreach ( $this->tables as $table ) {
397                                 $this->columns[$table] = $this->toolbox->getWriter()->getColumns( $table );
398                         }
399                 }
400
401                 $rs = $this->duplicate( ( clone $bean ), $trail, $preserveIDs );
402
403                 if ( !$this->cacheTables ) {
404                         $this->tables  = array();
405                         $this->columns = array();
406                 }
407
408                 return $rs;
409         }
410
411         /**
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.
416          *
417          * For every bean in the array this method will export:
418          *
419          * - contents of the bean
420          * - all own bean lists (recursively)
421          * - all shared beans (but not THEIR own lists)
422          *
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).
425          *
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.
429          *
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).
436          *
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
441          *
442          * @return array
443          */
444         public function exportAll( $beans, $parents = FALSE, $filters = array(), $caseStyle = 'snake')
445         {
446                 $array = array();
447
448                 if ( !is_array( $beans ) ) {
449                         $beans = array( $beans );
450                 }
451
452                 foreach ( $beans as $bean ) {
453                         $this->setFilters( $filters );
454
455                         $duplicate = $this->dup( $bean, array(), TRUE );
456
457                         $array[]   = $duplicate->export( FALSE, $parents, FALSE, $filters );
458                 }
459
460                 if ( $caseStyle === 'camel' ) $array = $this->camelfy( $array );
461                 if ( $caseStyle === 'dolphin' ) $array = $this->camelfy( $array, true );
462
463                 return $array;
464         }
465 }