Yaffs site version 1.1
[yaffs-website] / vendor / gabordemooij / redbean / RedBeanPHP / OODBBean.php
1 <?php
2
3 namespace RedBeanPHP;
4
5 use RedBeanPHP\QueryWriter\AQueryWriter as AQueryWriter;
6 use RedBeanPHP\BeanHelper as BeanHelper;
7 use RedBeanPHP\RedException as RedException;
8
9 /* PHP 5.3 compatibility */
10 if (interface_exists('\JsonSerializable')) {
11                 /* We extend JsonSerializable to avoid namespace conflicts,
12                 can't define interface with special namespace in PHP */
13                 interface Jsonable extends \JsonSerializable {};
14 } else {
15         interface Jsonable {};
16 }
17
18 /**
19  * OODBBean (Object Oriented DataBase Bean).
20  *
21  * to exchange information with the database. A bean represents
22  * a single table row and offers generic services for interaction
23  * with databases systems as well as some meta-data.
24  *
25  * @file    RedBeanPHP/OODBBean.php
26  * @author  Gabor de Mooij and the RedBeanPHP community
27  * @license BSD/GPLv2
28  * @desc    OODBBean represents a bean. RedBeanPHP uses beans
29  *
30  * @copyright
31  * copyright (c) G.J.G.T. (Gabor) de Mooij and the RedBeanPHP Community.
32  * This source file is subject to the BSD/GPLv2 License that is bundled
33  * with this source code in the file license.txt.
34  */
35 class OODBBean implements\IteratorAggregate,\ArrayAccess,\Countable,Jsonable
36 {
37         /**
38          * FUSE error modes.
39          */
40         const C_ERR_IGNORE    = FALSE;
41         const C_ERR_LOG       = 1;
42         const C_ERR_NOTICE    = 2;
43         const C_ERR_WARN      = 3;
44         const C_ERR_EXCEPTION = 4;
45         const C_ERR_FUNC      = 5;
46         const C_ERR_FATAL     = 6;
47
48         /**
49          * @var boolean
50          */
51         protected static $errorHandlingFUSE = FALSE;
52
53         /**
54          * @var callable|NULL
55          */
56         protected static $errorHandler = NULL;
57
58         /**
59          * @var array
60          */
61         protected static $aliases = array();
62
63         /**
64          * @var boolean
65          */
66         protected static $autoResolve = FALSE;
67
68         /**
69          * This is where the real properties of the bean live. They are stored and retrieved
70          * by the magic getter and setter (__get and __set).
71          *
72          * @var array $properties
73          */
74         protected $properties = array();
75
76         /**
77          * Here we keep the meta data of a bean.
78          *
79          * @var array
80          */
81         protected $__info = array();
82
83         /**
84          * The BeanHelper allows the bean to access the toolbox objects to implement
85          * rich functionality, otherwise you would have to do everything with R or
86          * external objects.
87          *
88          * @var BeanHelper
89          */
90         protected $beanHelper = NULL;
91
92         /**
93          * @var null
94          */
95         protected $fetchType = NULL;
96
97         /**
98          * @var string
99          */
100         protected $withSql = '';
101
102         /**
103          * @var array
104          */
105         protected $withParams = array();
106
107         /**
108          * @var string
109          */
110         protected $aliasName = NULL;
111
112         /**
113          * @var string
114          */
115         protected $via = NULL;
116
117         /**
118          * @var boolean
119          */
120         protected $noLoad = FALSE;
121
122         /**
123          * @var boolean
124          */
125         protected $all = FALSE;
126
127         /**
128          * Sets the error mode for FUSE.
129          * What to do if a FUSE model method does not exist?
130          * You can set the following options:
131          *
132          * * OODBBean::C_ERR_IGNORE (default), ignores the call, returns NULL
133          * * OODBBean::C_ERR_LOG, logs the incident using error_log
134          * * OODBBean::C_ERR_NOTICE, triggers a E_USER_NOTICE
135          * * OODBBean::C_ERR_WARN, triggers a E_USER_WARNING
136          * * OODBBean::C_ERR_EXCEPTION, throws an exception
137          * * OODBBean::C_ERR_FUNC, allows you to specify a custom handler (function)
138          * * OODBBean::C_ERR_FATAL, triggers a E_USER_ERROR
139          *
140          * <code>
141          * Custom handler method signature: handler( array (
142          *      'message' => string
143          *      'bean' => OODBBean
144          *      'method' => string
145          * ) )
146          * </code>
147          *
148          * This method returns the old mode and handler as an array.
149          *
150          * @param integer       $mode error handling mode
151          * @param callable|NULL $func custom handler
152          *
153          * @return array
154          */
155         public static function setErrorHandlingFUSE($mode, $func = NULL) {
156                 if (
157                            $mode !== self::C_ERR_IGNORE
158                         && $mode !== self::C_ERR_LOG
159                         && $mode !== self::C_ERR_NOTICE
160                         && $mode !== self::C_ERR_WARN
161                         && $mode !== self::C_ERR_EXCEPTION
162                         && $mode !== self::C_ERR_FUNC
163                         && $mode !== self::C_ERR_FATAL
164                 ) throw new \Exception( 'Invalid error mode selected' );
165
166                 if ( $mode === self::C_ERR_FUNC && !is_callable( $func ) ) {
167                         throw new \Exception( 'Invalid error handler' );
168                 }
169
170                 $old = array( self::$errorHandlingFUSE, self::$errorHandler );
171                 self::$errorHandlingFUSE = $mode;
172                 if ( is_callable( $func ) ) {
173                         self::$errorHandler = $func;
174                 } else {
175                         self::$errorHandler = NULL;
176                 }
177                 return $old;
178         }
179
180         /**
181          * Sets global aliases.
182          * Registers a batch of aliases in one go. This works the same as
183          * fetchAs and setAutoResolve but explicitly. For instance if you register
184          * the alias 'cover' for 'page' a property containing a reference to a
185          * page bean called 'cover' will correctly return the page bean and not
186          * a (non-existant) cover bean.
187          *
188          * <code>
189          * R::aliases( array( 'cover' => 'page' ) );
190          * $book = R::dispense( 'book' );
191          * $page = R::dispense( 'page' );
192          * $book->cover = $page;
193          * R::store( $book );
194          * $book = $book->fresh();
195          * $cover = $book->cover;
196          * echo $cover->getMeta( 'type' ); //page
197          * </code>
198          *
199          * The format of the aliases registration array is:
200          *
201          * {alias} => {actual type}
202          *
203          * In the example above we use:
204          *
205          * cover => page
206          *
207          * From that point on, every bean reference to a cover
208          * will return a 'page' bean. Note that with autoResolve this
209          * feature along with fetchAs() is no longer very important, although
210          * relying on explicit aliases can be a bit faster.
211          *
212          * @param array $list list of global aliases to use
213          *
214          * @return void
215          */
216         public static function aliases( $list )
217         {
218                 self::$aliases = $list;
219         }
220
221         /**
222          * Enables or disables auto-resolving fetch types.
223          * Auto-resolving aliased parent beans is convenient but can
224          * be slower and can create infinite recursion if you
225          * used aliases to break cyclic relations in your domain.
226          *
227          * @param boolean $automatic TRUE to enable automatic resolving aliased parents
228          *
229          * @return void
230          */
231         public static function setAutoResolve( $automatic = TRUE )
232         {
233                 self::$autoResolve = (boolean) $automatic;
234         }
235
236         /**
237          * Sets a meta property for all beans. This is a quicker way to set
238          * the meta properties for a collection of beans because this method
239          * can directly access the property arrays of the beans.
240          * This method returns the beans.
241          *
242          * @param array  $beans    beans to set the meta property of
243          * @param string $property property to set
244          * @param mixed  $value    value
245          *
246          * @return array
247          */
248         public static function setMetaAll( $beans, $property, $value )
249         {
250                 foreach( $beans as $bean ) {
251                         if ( $bean instanceof OODBBean ) $bean->__info[ $property ] = $value;
252                 }
253
254                 return $beans;
255         }
256
257         /**
258          * Parses the join in the with-snippet.
259          * For instance:
260          *
261          * <code>
262          * $author
263          *      ->withCondition(' @joined.detail.title LIKE ? ')
264          *  ->ownBookList;
265          * </code>
266          *
267          * will automatically join 'detail' on book to
268          * access the title field.
269          *
270          * @note this feature requires Narrow Field Mode and Join Feature
271          * to be both activated (default).
272          *
273          * @param string $type the source type for the join
274          *
275          * @return string
276          */
277         private function parseJoin( $type )
278         {
279                 $joinSql = '';
280                 $joins = array();
281                 if ( strpos($this->withSql, '@joined.' ) !== FALSE ) {
282                         $writer   = $this->beanHelper->getToolBox()->getWriter();
283                         $oldParts = $parts = explode( '@joined.', $this->withSql );
284                         array_shift( $parts );
285                         foreach($parts as $part) {
286                                 $explosion = explode( '.', $part );
287                                 $joinInfo  = array_shift( $explosion );
288                                 //Dont join more than once..
289                                 if ( !isset( $joins[$joinInfo] ) ) {
290                                         $joins[ $joinInfo ] = true;
291                                         $joinSql  .= $writer->writeJoin( $type, $joinInfo, 'LEFT' );
292                                 }
293                         }
294                         $this->withSql = implode( '', $oldParts );
295                         $joinSql      .= ' WHERE ';
296                 }
297                 return $joinSql;
298         }
299
300         /**
301          * Internal method.
302          * Obtains a shared list for a certain type.
303          *
304          * @param string $type the name of the list you want to retrieve.
305          *
306          * @return array
307          */
308         private function getSharedList( $type, $redbean, $toolbox )
309         {
310                 $writer = $toolbox->getWriter();
311
312                 if ( $this->via ) {
313                         $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
314                         if ( $oldName !== $this->via ) {
315                                 //set the new renaming rule
316                                 $writer->renameAssocTable( $oldName, $this->via );
317                         }
318                         $this->via = NULL;
319                 }
320
321                 $beans = array();
322                 if ($this->getID()) {
323                         $type             = $this->beau( $type );
324                         $assocManager     = $redbean->getAssociationManager();
325                         $beans            = $assocManager->related( $this, $type, $this->withSql, $this->withParams );
326                 }
327
328                 $this->withSql    = '';
329                 $this->withParams = array();
330
331                 return $beans;
332         }
333
334         /**
335          * Internal method.
336          * Obtains the own list of a certain type.
337          *
338          * @param string      $type   name of the list you want to retrieve
339          * @param OODB        $oodb   The RB OODB object database instance
340          *
341          * @return array
342          */
343         private function getOwnList( $type, $redbean )
344         {
345                 $type = $this->beau( $type );
346
347                 if ( $this->aliasName ) {
348                         $parentField = $this->aliasName;
349                         $myFieldLink = $parentField . '_id';
350
351                         $this->__info['sys.alias.' . $type] = $this->aliasName;
352
353                         $this->aliasName = NULL;
354                 } else {
355                         $parentField = $this->__info['type'];
356                         $myFieldLink = $parentField . '_id';
357                 }
358
359                 $beans = array();
360
361                 if ( $this->getID() ) {
362
363                         $firstKey = NULL;
364                         if ( count( $this->withParams ) > 0 ) {
365                                 reset( $this->withParams );
366
367                                 $firstKey = key( $this->withParams );
368                         }
369
370                         $joinSql = $this->parseJoin( $type );
371
372                         if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
373                                 $bindings           = $this->withParams;
374                                 $bindings[':slot0'] = $this->getID();
375
376                                 $beans = $redbean->find( $type, array(), " {$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
377                         } else {
378                                 $bindings = array_merge( array( $this->getID() ), $this->withParams );
379
380                                 $beans = $redbean->find( $type, array(), " {$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
381                         }
382                 }
383
384                 $this->withSql    = '';
385                 $this->withParams = array();
386
387                 foreach ( $beans as $beanFromList ) {
388                         $beanFromList->__info['sys.parentcache.' . $parentField] = $this;
389                 }
390
391                 return $beans;
392         }
393
394         /**
395          * Initializes a bean. Used by OODB for dispensing beans.
396          * It is not recommended to use this method to initialize beans. Instead
397          * use the OODB object to dispense new beans. You can use this method
398          * if you build your own bean dispensing mechanism.
399          *
400          * @param string     $type       type of the new bean
401          * @param BeanHelper $beanhelper bean helper to obtain a toolbox and a model
402          *
403          * @return void
404          */
405         public function initializeForDispense( $type, BeanHelper $beanhelper )
406         {
407                 $this->beanHelper         = $beanhelper;
408                 $this->__info['type']     = $type;
409                 $this->__info['sys.id']   = 'id';
410                 $this->__info['sys.orig'] = array( 'id' => 0 );
411                 $this->__info['tainted']  = TRUE;
412                 $this->__info['changed']  = TRUE;
413                 $this->properties['id']   = 0;
414         }
415
416         /**
417          * Sets the Bean Helper. Normally the Bean Helper is set by OODB.
418          * Here you can change the Bean Helper. The Bean Helper is an object
419          * providing access to a toolbox for the bean necessary to retrieve
420          * nested beans (bean lists: ownBean, sharedBean) without the need to
421          * rely on static calls to the facade (or make this class dep. on OODB).
422          *
423          * @param BeanHelper $helper helper to use for this bean
424          *
425          * @return void
426          */
427         public function setBeanHelper( BeanHelper $helper )
428         {
429                 $this->beanHelper = $helper;
430         }
431
432         /**
433          * Returns an ArrayIterator so you can treat the bean like
434          * an array with the properties container as its contents.
435          * This method is meant for PHP and allows you to access beans as if
436          * they were arrays, i.e. using array notation:
437          *
438          * $bean[$key] = $value;
439          *
440          * Note that not all PHP functions work with the array interface.
441          *
442          * @return ArrayIterator
443          */
444         public function getIterator()
445         {
446                 return new \ArrayIterator( $this->properties );
447         }
448
449         /**
450          * Imports all values from an associative array $array. Chainable.
451          * This method imports the values in the first argument as bean
452          * propery and value pairs. Use the second parameter to provide a
453          * selection. If a selection array is passed, only the entries
454          * having keys mentioned in the selection array will be imported.
455          * Set the third parameter to TRUE to preserve spaces in selection keys.
456          *
457          * @param array        $array     what you want to import
458          * @param string|array $selection selection of values
459          * @param boolean      $notrim    if TRUE selection keys will NOT be trimmed
460          *
461          * @return OODBBean
462          */
463         public function import( $array, $selection = FALSE, $notrim = FALSE )
464         {
465                 if ( is_string( $selection ) ) {
466                         $selection = explode( ',', $selection );
467                 }
468
469                 if ( !$notrim && is_array( $selection ) ) {
470                         foreach ( $selection as $key => $selected ) {
471                                 $selection[$key] = trim( $selected );
472                         }
473                 }
474
475                 foreach ( $array as $key => $value ) {
476                         if ( $key != '__info' ) {
477                                 if ( !$selection || ( $selection && in_array( $key, $selection ) ) ) {
478                                         if ( is_array($value ) ) {
479                                                 if ( isset( $value['_type'] ) ) {
480                                                         $bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $value['_type'] );
481                                                         unset( $value['_type'] );
482                                                         $bean->import($value);
483                                                         $this->$key = $bean;
484                                                 } else {
485                                                         $listBeans = array();
486                                                         foreach( $value as $listKey => $listItem ) {
487                                                                 $bean = $this->beanHelper->getToolbox()->getRedBean()->dispense( $listItem['_type'] );
488                                                                 unset( $listItem['_type'] );
489                                                                 $bean->import($listItem);
490                                                                 $list = &$this->$key;
491                                                                 $list[ $listKey ] = $bean;
492                                                         }
493                                                 }
494                                         } else {
495                                                 $this->$key = $value;
496                                         }
497                                 }
498                         }
499                 }
500
501                 return $this;
502         }
503
504         /**
505         * Fast way to import a row.
506         * Does not perform any checks.
507         *
508         * @param array $row a database row
509         *
510         * @return self
511         */
512         public function importRow( $row )
513         {
514                 $this->properties = $row;
515                 $this->__info['sys.orig'] = $row;
516                 $this->__info['changed'] = FALSE;
517                 return $this;
518         }
519
520         /**
521          * Imports data from another bean. Chainable.
522          * Copies the properties from the source bean to the internal
523          * property list.
524          *
525          * @param OODBBean $sourceBean the source bean to take properties from
526          *
527          * @return OODBBean
528          */
529         public function importFrom( OODBBean $sourceBean )
530         {
531                 $this->__info['tainted'] = TRUE;
532                 $this->__info['changed'] = TRUE;
533                 $this->properties = $sourceBean->properties;
534
535                 return $this;
536         }
537
538         /**
539          * Injects the properties of another bean but keeps the original ID.
540          * Just like import() but keeps the original ID.
541          * Chainable.
542          *
543          * @param OODBBean $otherBean the bean whose properties you would like to copy
544          *
545          * @return OODBBean
546          */
547         public function inject( OODBBean $otherBean )
548         {
549                 $myID = $this->properties['id'];
550
551                 $this->import( $otherBean->export( FALSE, FALSE, TRUE ) );
552
553                 $this->id = $myID;
554
555                 return $this;
556         }
557
558         /**
559          * Exports the bean as an array.
560          * This function exports the contents of a bean to an array and returns
561          * the resulting array.
562          *
563          * @param boolean $meta    set to TRUE if you want to export meta data as well
564          * @param boolean $parents set to TRUE if you want to export parents as well
565          * @param boolean $onlyMe  set to TRUE if you want to export only this bean
566          * @param array   $filters optional whitelist for export
567          *
568          * @return array
569          */
570         public function export( $meta = FALSE, $parents = FALSE, $onlyMe = FALSE, $filters = array() )
571         {
572                 $arr = array();
573
574                 if ( $parents ) {
575                         foreach ( $this as $key => $value ) {
576                                 if ( substr( $key, -3 ) != '_id' ) continue;
577
578                                 $prop = substr( $key, 0, strlen( $key ) - 3 );
579                                 $this->$prop;
580                         }
581                 }
582
583                 $hasFilters = is_array( $filters ) && count( $filters );
584
585                 foreach ( $this as $key => $value ) {
586                         if ( !$onlyMe && is_array( $value ) ) {
587                                 $vn = array();
588
589                                 foreach ( $value as $i => $b ) {
590                                         if ( !( $b instanceof OODBBean ) ) continue;
591                                         $vn[] = $b->export( $meta, FALSE, FALSE, $filters );
592                                         $value = $vn;
593                                 }
594                         } elseif ( $value instanceof OODBBean ) {
595                                 if ( $hasFilters ) {
596                                         if ( !in_array( strtolower( $value->getMeta( 'type' ) ), $filters ) ) continue;
597                                 }
598
599                                 $value = $value->export( $meta, $parents, FALSE, $filters );
600                         }
601
602                         $arr[$key] = $value;
603                 }
604
605                 if ( $meta ) {
606                         $arr['__info'] = $this->__info;
607                 }
608
609                 return $arr;
610         }
611
612         /**
613          * Implements isset() function for use as an array.
614          *
615          * @param string $property name of the property you want to check
616          *
617          * @return boolean
618          */
619         public function __isset( $property )
620         {
621                 $property = $this->beau( $property );
622
623                 if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
624                         $property = substr($property, 1);
625                 }
626                 return isset( $this->properties[$property] );
627         }
628
629         /**
630          * Returns the ID of the bean no matter what the ID field is.
631          *
632          * @return string|null
633          */
634         public function getID()
635         {
636                 return ( isset( $this->properties['id'] ) ) ? (string) $this->properties['id'] : NULL;
637         }
638
639         /**
640          * Unsets a property of a bean.
641          * Magic method, gets called implicitly when performing the unset() operation
642          * on a bean property.
643          *
644          * @param  string $property property to unset
645          *
646          * @return void
647          */
648         public function __unset( $property )
649         {
650                 $property = $this->beau( $property );
651
652                 if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
653                         $property = substr($property, 1);
654                 }
655
656                 unset( $this->properties[$property] );
657
658                 $shadowKey = 'sys.shadow.'.$property;
659                 if ( isset( $this->__info[ $shadowKey ] ) ) unset( $this->__info[$shadowKey] );
660
661                 //also clear modifiers
662                 $this->withSql    = '';
663                 $this->withParams = array();
664                 $this->aliasName  = NULL;
665                 $this->fetchType  = NULL;
666                 $this->noLoad     = FALSE;
667                 $this->all        = FALSE;
668                 $this->via        = NULL;
669
670                 return;
671         }
672
673         /**
674          * Adds WHERE clause conditions to ownList retrieval.
675          * For instance to get the pages that belong to a book you would
676          * issue the following command: $book->ownPage
677          * However, to order these pages by number use:
678          *
679          * <code>
680          * $book->with(' ORDER BY `number` ASC ')->ownPage
681          * </code>
682          *
683          * the additional SQL snippet will be merged into the final
684          * query.
685          *
686          * @param string $sql      SQL to be added to retrieval query.
687          * @param array  $bindings array with parameters to bind to SQL snippet
688          *
689          * @return OODBBean
690          */
691         public function with( $sql, $bindings = array() )
692         {
693                 $this->withSql    = $sql;
694                 $this->withParams = $bindings;
695                 return $this;
696         }
697
698         /**
699          * Just like with(). Except that this method prepends the SQL query snippet
700          * with AND which makes it slightly more comfortable to use a conditional
701          * SQL snippet. For instance to filter an own-list with pages (belonging to
702          * a book) on specific chapters you can use:
703          *
704          * $book->withCondition(' chapter = 3 ')->ownPage
705          *
706          * This will return in the own list only the pages having 'chapter == 3'.
707          *
708          * @param string $sql      SQL to be added to retrieval query (prefixed by AND)
709          * @param array  $bindings array with parameters to bind to SQL snippet
710          *
711          * @return OODBBean
712          */
713         public function withCondition( $sql, $bindings = array() )
714         {
715                 $this->withSql    = ' AND ' . $sql;
716                 $this->withParams = $bindings;
717                 return $this;
718         }
719
720         /**
721          * Tells the bean to (re)load the following list without any
722          * conditions. If you have an ownList or sharedList with a
723          * condition you can use this method to reload the entire list.
724          *
725          * Usage:
726          *
727          * <code>
728          * $bean->with( ' LIMIT 3 ' )->ownPage; //Just 3
729          * $bean->all()->ownPage; //Reload all pages
730          * </code>
731          *
732          * @return self
733          */
734         public function all()
735         {
736                 $this->all = TRUE;
737                 return $this;
738         }
739
740         /**
741          * Tells the bean to only access the list but not load
742          * its contents. Use this if you only want to add something to a list
743          * and you have no interest in retrieving its contents from the database.
744          *
745          * @return self
746          */
747         public function noLoad()
748         {
749                 $this->noLoad = TRUE;
750                 return $this;
751         }
752
753         /**
754          * Prepares an own-list to use an alias. This is best explained using
755          * an example. Imagine a project and a person. The project always involves
756          * two persons: a teacher and a student. The person beans have been aliased in this
757          * case, so to the project has a teacher_id pointing to a person, and a student_id
758          * also pointing to a person. Given a project, we obtain the teacher like this:
759          *
760          * <code>
761          * $project->fetchAs('person')->teacher;
762          * </code>
763          *
764          * Now, if we want all projects of a teacher we cant say:
765          *
766          * <code>
767          * $teacher->ownProject
768          * </code>
769          *
770          * because the $teacher is a bean of type 'person' and no project has been
771          * assigned to a person. Instead we use the alias() method like this:
772          *
773          * <code>
774          * $teacher->alias('teacher')->ownProject
775          * </code>
776          *
777          * now we get the projects associated with the person bean aliased as
778          * a teacher.
779          *
780          * @param string $aliasName the alias name to use
781          *
782          * @return OODBBean
783          */
784         public function alias( $aliasName )
785         {
786                 $this->aliasName = $this->beau( $aliasName );
787
788                 return $this;
789         }
790
791         /**
792          * Returns properties of bean as an array.
793          * This method returns the raw internal property list of the
794          * bean. Only use this method for optimization purposes. Otherwise
795          * use the export() method to export bean data to arrays.
796          *
797          * @return array
798          */
799         public function getProperties()
800         {
801                 return $this->properties;
802         }
803
804         /**
805          * Returns properties of bean as an array.
806          * This method returns the raw internal property list of the
807          * bean. Only use this method for optimization purposes. Otherwise
808          * use the export() method to export bean data to arrays.
809          * This method returns an array with the properties array and
810          * the type (string).
811          *
812          * @return array
813          */
814         public function getPropertiesAndType()
815         {
816                 return array( $this->properties, $this->__info['type'] );
817         }
818
819         /**
820          * Turns a camelcase property name into an underscored property name.
821          *
822          * Examples:
823          *
824          * * oneACLRoute -> one_acl_route
825          * * camelCase -> camel_case
826          *
827          * Also caches the result to improve performance.
828          *
829          * @param string $property property to un-beautify
830          *
831          * @return string
832          */
833         public function beau( $property )
834         {
835                 static $beautifulColumns = array();
836
837                 if ( ctype_lower( $property ) ) return $property;
838
839                 if (
840                         ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) )
841                         || ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) )
842                         || ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) )
843                 ) {
844
845                         $property = preg_replace( '/List$/', '', $property );
846                         return $property;
847                 }
848
849                 if ( !isset( $beautifulColumns[$property] ) ) {
850                         $beautifulColumns[$property] = AQueryWriter::camelsSnake( $property );
851                 }
852
853                 return $beautifulColumns[$property];
854         }
855
856         /**
857          * Clears all modifiers.
858          *
859          * @return self
860          */
861         public function clearModifiers()
862         {
863                 $this->withSql    = '';
864                 $this->withParams = array();
865                 $this->aliasName  = NULL;
866                 $this->fetchType  = NULL;
867                 $this->noLoad     = FALSE;
868                 $this->all        = FALSE;
869                 $this->via        = NULL;
870                 return $this;
871         }
872
873         /**
874          * Determines whether a list is opened in exclusive mode or not.
875          * If a list has been opened in exclusive mode this method will return TRUE,
876          * othwerwise it will return FALSE.
877          *
878          * @param string $listName name of the list to check
879          *
880          * @return boolean
881          */
882         public function isListInExclusiveMode( $listName )
883         {
884                 $listName = $this->beau( $listName );
885
886                 if ( strpos( $listName, 'xown' ) === 0 && ctype_upper( substr( $listName, 4, 1 ) ) ) {
887                         $listName = substr($listName, 1);
888                 }
889
890                 $listName = lcfirst( substr( $listName, 3 ) );
891
892                 return ( isset( $this->__info['sys.exclusive-'.$listName] ) && $this->__info['sys.exclusive-'.$listName] );
893         }
894
895         /**
896          * Magic Getter. Gets the value for a specific property in the bean.
897          * If the property does not exist this getter will make sure no error
898          * occurs. This is because RedBean allows you to query (probe) for
899          * properties. If the property can not be found this method will
900          * return NULL instead.
901          *
902          * @param string $property name of the property you wish to obtain the value of
903          *
904          * @return mixed
905          */
906         public function &__get( $property )
907         {
908                 $isEx          = FALSE;
909                 $isOwn         = FALSE;
910                 $isShared      = FALSE;
911
912                 if ( !ctype_lower( $property ) ) {
913                         $property = $this->beau( $property );
914                         if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
915                                 $property = substr($property, 1);
916                                 $listName = lcfirst( substr( $property, 3 ) );
917                                 $isEx     = TRUE;
918                                 $isOwn    = TRUE;
919                                 $this->__info['sys.exclusive-'.$listName] = TRUE;
920                         } elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) )  {
921                                 $isOwn    = TRUE;
922                                 $listName = lcfirst( substr( $property, 3 ) );
923                         } elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
924                                 $isShared = TRUE;
925                         }
926                 }
927
928                 $fieldLink      = $property . '_id';
929                 $exists         = isset( $this->properties[$property] );
930
931                 //If not exists and no field link and no list, bail out.
932                 if ( !$exists && !isset($this->$fieldLink) && (!$isOwn && !$isShared )) {
933
934                         $this->withSql    = '';
935                         $this->withParams = array();
936                         $this->aliasName  = NULL;
937                         $this->fetchType  = NULL;
938                         $this->noLoad     = FALSE;
939                         $this->all        = FALSE;
940                         $this->via        = NULL;
941
942                         $NULL = NULL;
943                         return $NULL;
944                 }
945
946                 $hasAlias       = (!is_null($this->aliasName));
947                 $differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
948                                                                 ($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
949                 $hasSQL         = ($this->withSql !== '' || $this->via !== NULL);
950                 $hasAll         = (boolean) ($this->all);
951
952                 //If exists and no list or exits and list not changed, bail out.
953                 if ( $exists && ((!$isOwn && !$isShared ) ||  (!$hasSQL && !$differentAlias && !$hasAll)) ) {
954
955                         $this->withSql    = '';
956                         $this->withParams = array();
957                         $this->aliasName  = NULL;
958                         $this->fetchType  = NULL;
959                         $this->noLoad     = FALSE;
960                         $this->all        = FALSE;
961                         $this->via        = NULL;
962                         return $this->properties[$property];
963                 }
964
965                 list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
966
967                 if ( isset( $this->$fieldLink ) ) {
968                         $this->__info['tainted'] = TRUE;
969
970                         if ( isset( $this->__info["sys.parentcache.$property"] ) ) {
971                                 $bean = $this->__info["sys.parentcache.$property"];
972                         } else {
973                                 if ( isset( self::$aliases[$property] ) ) {
974                                         $type = self::$aliases[$property];
975                                 } elseif ( $this->fetchType ) {
976                                         $type = $this->fetchType;
977                                         $this->fetchType = NULL;
978                                 } else {
979                                         $type = $property;
980                                 }
981                                 $bean = NULL;
982                                 if ( !is_null( $this->properties[$fieldLink] ) ) {
983                                         $bean = $redbean->load( $type, $this->properties[$fieldLink] );
984                                         //If the IDs dont match, we failed to load, so try autoresolv in that case...
985                                         if ( $bean->id !== $this->properties[$fieldLink] && self::$autoResolve ) {
986                                                 $type = $this->beanHelper->getToolbox()->getWriter()->inferFetchType( $this->__info['type'], $property );
987                                                 if ( !is_null( $type) ) {
988                                                         $bean = $redbean->load( $type, $this->properties[$fieldLink] );
989                                                         $this->__info["sys.autoresolved.{$property}"] = $type;
990                                                 }
991                                         }
992                                 }
993                         }
994
995                         $this->properties[$property] = $bean;
996                         $this->withSql               = '';
997                         $this->withParams            = array();
998                         $this->aliasName             = NULL;
999                         $this->fetchType             = NULL;
1000                         $this->noLoad                = FALSE;
1001                         $this->all                   = FALSE;
1002                         $this->via                   = NULL;
1003
1004                         return $this->properties[$property];
1005
1006                 }
1007                 //Implicit: elseif ( $isOwn || $isShared ) {
1008                 if ( $this->noLoad ) {
1009                         $beans = array();
1010                 } elseif ( $isOwn ) {
1011                         $beans = $this->getOwnList( $listName, $redbean );
1012                 } else {
1013                         $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
1014                 }
1015
1016                 $this->properties[$property]          = $beans;
1017                 $this->__info["sys.shadow.$property"] = $beans;
1018                 $this->__info['tainted']              = TRUE;
1019
1020                 $this->withSql    = '';
1021                 $this->withParams = array();
1022                 $this->aliasName  = NULL;
1023                 $this->fetchType  = NULL;
1024                 $this->noLoad     = FALSE;
1025                 $this->all        = FALSE;
1026                 $this->via        = NULL;
1027
1028                 return $this->properties[$property];
1029         }
1030
1031         /**
1032          * Magic Setter. Sets the value for a specific property.
1033          * This setter acts as a hook for OODB to mark beans as tainted.
1034          * The tainted meta property can be retrieved using getMeta("tainted").
1035          * The tainted meta property indicates whether a bean has been modified and
1036          * can be used in various caching mechanisms.
1037          *
1038          * @param string $property name of the property you wish to assign a value to
1039          * @param  mixed $value    the value you want to assign
1040          *
1041          * @return void
1042          */
1043         public function __set( $property, $value )
1044         {
1045                 $isEx          = FALSE;
1046                 $isOwn         = FALSE;
1047                 $isShared      = FALSE;
1048
1049                 if ( !ctype_lower( $property ) ) {
1050                         $property = $this->beau( $property );
1051                         if ( strpos( $property, 'xown' ) === 0 && ctype_upper( substr( $property, 4, 1 ) ) ) {
1052                                 $property = substr($property, 1);
1053                                 $listName = lcfirst( substr( $property, 3 ) );
1054                                 $isEx     = TRUE;
1055                                 $isOwn    = TRUE;
1056                                 $this->__info['sys.exclusive-'.$listName] = TRUE;
1057                         } elseif ( strpos( $property, 'own' ) === 0 && ctype_upper( substr( $property, 3, 1 ) ) )  {
1058                                 $isOwn    = TRUE;
1059                                 $listName = lcfirst( substr( $property, 3 ) );
1060                         } elseif ( strpos( $property, 'shared' ) === 0 && ctype_upper( substr( $property, 6, 1 ) ) ) {
1061                                 $isShared = TRUE;
1062                         }
1063                 }
1064
1065                 $hasAlias       = (!is_null($this->aliasName));
1066                 $differentAlias = ($hasAlias && $isOwn && isset($this->__info['sys.alias.'.$listName])) ?
1067                                                                 ($this->__info['sys.alias.'.$listName] !== $this->aliasName) : FALSE;
1068                 $hasSQL         = ($this->withSql !== '' || $this->via !== NULL);
1069                 $exists         = isset( $this->properties[$property] );
1070                 $fieldLink      = $property . '_id';
1071                 $isFieldLink    = (($pos = strrpos($property, '_id')) !== FALSE) && array_key_exists( ($fieldName = substr($property, 0, $pos)), $this->properties );
1072
1073
1074                 if ( ($isOwn || $isShared) &&  (!$exists || $hasSQL || $differentAlias) ) {
1075
1076                         if ( !$this->noLoad ) {
1077                                 list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
1078                                 if ( $isOwn ) {
1079                                         $beans = $this->getOwnList( $listName, $redbean );
1080                                 } else {
1081                                         $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
1082                                 }
1083                                 $this->__info["sys.shadow.$property"] = $beans;
1084                         }
1085                 }
1086
1087                 $this->withSql    = '';
1088                 $this->withParams = array();
1089                 $this->aliasName  = NULL;
1090                 $this->fetchType  = NULL;
1091                 $this->noLoad     = FALSE;
1092                 $this->all        = FALSE;
1093                 $this->via        = NULL;
1094
1095                 $this->__info['tainted'] = TRUE;
1096                 $this->__info['changed'] = TRUE;
1097
1098                 if ( array_key_exists( $fieldLink, $this->properties ) && !( $value instanceof OODBBean ) ) {
1099                         if ( is_null( $value ) || $value === FALSE ) {
1100
1101                                 unset( $this->properties[ $property ]);
1102                                 $this->properties[ $fieldLink ] = NULL;
1103
1104                                 return;
1105                         } else {
1106                                 throw new RedException( 'Cannot cast to bean.' );
1107                         }
1108                 }
1109                 
1110                 if ( $isFieldLink ){
1111                         unset( $this->properties[ $fieldName ]);
1112                         $this->properties[ $property ] = NULL;
1113                 }
1114
1115
1116                 if ( $value === FALSE ) {
1117                         $value = '0';
1118                 } elseif ( $value === TRUE ) {
1119                         $value = '1';
1120                 } elseif ( $value instanceof \DateTime ) {
1121                         $value = $value->format( 'Y-m-d H:i:s' );
1122                 }
1123
1124                 $this->properties[$property] = $value;
1125         }
1126
1127         /**
1128          * Sets a property directly, for internal use only.
1129          *
1130          * @param string  $property     property
1131          * @param mixed   $value        value
1132          * @param boolean $updateShadow whether you want to update the shadow
1133          * @param boolean $taint        whether you want to mark the bean as tainted
1134          *
1135          * @return void
1136          */
1137         public function setProperty( $property, $value, $updateShadow = FALSE, $taint = FALSE )
1138         {
1139                 $this->properties[$property] = $value;
1140
1141                 if ( $updateShadow ) {
1142                         $this->__info['sys.shadow.' . $property] = $value;
1143                 }
1144
1145                 if ( $taint ) {
1146                         $this->__info['tainted'] = TRUE;
1147                         $this->__info['changed'] = TRUE;
1148                 }
1149         }
1150
1151         /**
1152          * Returns the value of a meta property. A meta property
1153          * contains additional information about the bean object that will not
1154          * be stored in the database. Meta information is used to instruct
1155          * RedBeanPHP as well as other systems how to deal with the bean.
1156          * If the property cannot be found this getter will return NULL instead.
1157          *
1158          * Example:
1159          *
1160          * <code>
1161          * $bean->setMeta( 'flush-cache', TRUE );
1162          * </code>
1163          *
1164          * RedBeanPHP also stores meta data in beans, this meta data uses
1165          * keys prefixed with 'sys.' (system).
1166          *
1167          * @param string $path    path to property in meta data
1168          * @param mixed  $default default value
1169          *
1170          * @return mixed
1171          */
1172         public function getMeta( $path, $default = NULL )
1173         {
1174                 return ( isset( $this->__info[$path] ) ) ? $this->__info[$path] : $default;
1175         }
1176
1177         /**
1178          * Gets and unsets a meta property.
1179          * Moves a meta property out of the bean.
1180          * This is a short-cut method that can be used instead
1181          * of combining a get/unset.
1182          *
1183          * @param string $path    path to property in meta data
1184          * @param mixed  $default default value
1185          *
1186          * @return mixed
1187          */
1188         public function moveMeta( $path, $value = NULL )
1189         {
1190                 if ( isset( $this->__info[$path] ) ) {
1191                         $value = $this->__info[ $path ];
1192                         unset( $this->__info[ $path ] );
1193                 }
1194                 return $value;
1195         }
1196
1197         /**
1198          * Stores a value in the specified Meta information property.
1199          * The first argument should be the key to store the value under,
1200          * the second argument should be the value. It is common to use
1201          * a path-like notation for meta data in RedBeanPHP like:
1202          * 'my.meta.data', however the dots are purely for readability, the
1203          * meta data methods do not store nested structures or hierarchies.
1204          *
1205          * @param string $path  path / key to store value under
1206          * @param mixed  $value value to store in bean (not in database) as meta data
1207          *
1208          * @return OODBBean
1209          */
1210         public function setMeta( $path, $value )
1211         {
1212                 $this->__info[$path] = $value;
1213
1214                 return $this;
1215         }
1216
1217         /**
1218          * Copies the meta information of the specified bean
1219          * This is a convenience method to enable you to
1220          * exchange meta information easily.
1221          *
1222          * @param OODBBean $bean bean to copy meta data of
1223          *
1224          * @return OODBBean
1225          */
1226         public function copyMetaFrom( OODBBean $bean )
1227         {
1228                 $this->__info = $bean->__info;
1229
1230                 return $this;
1231         }
1232
1233         /**
1234          * Sends the call to the registered model.
1235          * This method can also be used to override bean behaviour.
1236          * In that case you don't want an error or exception to be triggered
1237          * if the method does not exist in the model (because it's optional).
1238          * Unfortunately we cannot add an extra argument to __call() for this
1239          * because the signature is fixed. Another option would be to set
1240          * a special flag ( i.e. $this->isOptionalCall ) but that would
1241          * cause additional complexity because we have to deal with extra temporary state.
1242          * So, instead I allowed the method name to be prefixed with '@', in practice
1243          * nobody creates methods like that - however the '@' symbol in PHP is widely known
1244          * to suppress error handling, so we can reuse the semantics of this symbol.
1245          * If a method name gets passed starting with '@' the overrideDontFail variable
1246          * will be set to TRUE and the '@' will be stripped from the function name before
1247          * attempting to invoke the method on the model. This way, we have all the
1248          * logic in one place.
1249          *
1250          * @param string $method name of the method
1251          * @param array  $args   argument list
1252          *
1253          * @return mixed
1254          */
1255         public function __call( $method, $args )
1256         {
1257                 $overrideDontFail = FALSE;
1258                 if ( strpos( $method, '@' ) === 0 ) {
1259                         $method = substr( $method, 1 );
1260                         $overrideDontFail = TRUE;
1261                 }
1262
1263                 if ( !isset( $this->__info['model'] ) ) {
1264                         $model = $this->beanHelper->getModelForBean( $this );
1265
1266                         if ( !$model ) {
1267                                 return NULL;
1268                         }
1269
1270                         $this->__info['model'] = $model;
1271                 }
1272                 if ( !method_exists( $this->__info['model'], $method ) ) {
1273
1274                         if ( self::$errorHandlingFUSE === FALSE || $overrideDontFail ) {
1275                                 return NULL;
1276                         }
1277
1278                         if ( in_array( $method, array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ), TRUE ) ) {
1279                                 return NULL;
1280                         }
1281
1282                         $message = "FUSE: method does not exist in model: $method";
1283                         if ( self::$errorHandlingFUSE === self::C_ERR_LOG ) {
1284                                 error_log( $message );
1285                                 return NULL;
1286                         } elseif ( self::$errorHandlingFUSE === self::C_ERR_NOTICE ) {
1287                                 trigger_error( $message, E_USER_NOTICE );
1288                                 return NULL;
1289                         } elseif ( self::$errorHandlingFUSE === self::C_ERR_WARN ) {
1290                                 trigger_error( $message, E_USER_WARNING );
1291                                 return NULL;
1292                         } elseif ( self::$errorHandlingFUSE === self::C_ERR_EXCEPTION ) {
1293                                 throw new \Exception( $message );
1294                         } elseif ( self::$errorHandlingFUSE === self::C_ERR_FUNC ) {
1295                                 $func = self::$errorHandler;
1296                                 return $func(array(
1297                                         'message' => $message,
1298                                         'method' => $method,
1299                                         'args' => $args,
1300                                         'bean' => $this
1301                                 ));
1302                         }
1303                         trigger_error( $message, E_USER_ERROR );
1304                         return NULL;
1305                 }
1306
1307                 return call_user_func_array( array( $this->__info['model'], $method ), $args );
1308         }
1309
1310         /**
1311          * Implementation of __toString Method
1312          * Routes call to Model. If the model implements a __toString() method this
1313          * method will be called and the result will be returned. In case of an
1314          * echo-statement this result will be printed. If the model does not
1315          * implement a __toString method, this method will return a JSON
1316          * representation of the current bean.
1317          *
1318          * @return string
1319          */
1320         public function __toString()
1321         {
1322                 $string = $this->__call( '@__toString', array() );
1323
1324                 if ( $string === NULL ) {
1325                         $list = array();
1326                         foreach($this->properties as $property => $value) {
1327                                 if (is_scalar($value)) {
1328                                         $list[$property] = $value;
1329                                 }
1330                         }
1331                         return json_encode( $list );
1332                 } else {
1333                         return $string;
1334                 }
1335         }
1336
1337         /**
1338          * Implementation of Array Access Interface, you can access bean objects
1339          * like an array.
1340          * Call gets routed to __set.
1341          *
1342          * @param  mixed $offset offset string
1343          * @param  mixed $value  value
1344          *
1345          * @return void
1346          */
1347         public function offsetSet( $offset, $value )
1348         {
1349                 $this->__set( $offset, $value );
1350         }
1351
1352         /**
1353          * Implementation of Array Access Interface, you can access bean objects
1354          * like an array.
1355          *
1356          * Array functions do not reveal x-own-lists and list-alias because
1357          * you dont want duplicate entries in foreach-loops.
1358          * Also offers a slight performance improvement for array access.
1359          *
1360          * @param  mixed $offset property
1361          *
1362          * @return boolean
1363          */
1364         public function offsetExists( $offset )
1365         {
1366                 return $this->__isset( $offset );
1367         }
1368
1369         /**
1370          * Implementation of Array Access Interface, you can access bean objects
1371          * like an array.
1372          * Unsets a value from the array/bean.
1373          *
1374          * Array functions do not reveal x-own-lists and list-alias because
1375          * you dont want duplicate entries in foreach-loops.
1376          * Also offers a slight performance improvement for array access.
1377          *
1378          * @param  mixed $offset property
1379          *
1380          * @return void
1381          */
1382         public function offsetUnset( $offset )
1383         {
1384                 $this->__unset( $offset );
1385         }
1386
1387         /**
1388          * Implementation of Array Access Interface, you can access bean objects
1389          * like an array.
1390          * Returns value of a property.
1391          *
1392          * Array functions do not reveal x-own-lists and list-alias because
1393          * you dont want duplicate entries in foreach-loops.
1394          * Also offers a slight performance improvement for array access.
1395          *
1396          * @param  mixed $offset property
1397          *
1398          * @return mixed
1399          */
1400         public function &offsetGet( $offset )
1401         {
1402                 return $this->__get( $offset );
1403         }
1404
1405         /**
1406          * Chainable method to cast a certain ID to a bean; for instance:
1407          * $person = $club->fetchAs('person')->member;
1408          * This will load a bean of type person using member_id as ID.
1409          *
1410          * @param  string $type preferred fetch type
1411          *
1412          * @return OODBBean
1413          */
1414         public function fetchAs( $type )
1415         {
1416                 $this->fetchType = $type;
1417
1418                 return $this;
1419         }
1420
1421         /**
1422          * For polymorphic bean relations.
1423          * Same as fetchAs but uses a column instead of a direct value.
1424          *
1425          * @param string $field field name to use for mapping
1426          *
1427          * @return OODBBean
1428          */
1429         public function poly( $field )
1430         {
1431                 return $this->fetchAs( $this->$field );
1432         }
1433
1434         /**
1435          * Traverses a bean property with the specified function.
1436          * Recursively iterates through the property invoking the
1437          * function for each bean along the way passing the bean to it.
1438          *
1439          * Can be used together with with, withCondition, alias and fetchAs.
1440          *
1441          * @param string $property property
1442          * @param callable $function function
1443          * @param integer $maxDepth maximum depth for traversal
1444          *
1445          * @return OODBBean
1446          * @throws RedException
1447          */
1448         public function traverse( $property, $function, $maxDepth = NULL )
1449         {
1450                 $this->via = NULL;
1451                 if ( strpos( $property, 'shared' ) !== FALSE ) {
1452                         throw new RedException( 'Traverse only works with (x)own-lists.' );
1453                 }
1454
1455                 if ( !is_null( $maxDepth ) ) {
1456                         if ( !$maxDepth-- ) return $this;
1457                 }
1458
1459                 $oldFetchType = $this->fetchType;
1460                 $oldAliasName = $this->aliasName;
1461                 $oldWith      = $this->withSql;
1462                 $oldBindings  = $this->withParams;
1463
1464                 $beans = $this->$property;
1465
1466                 if ( $beans === NULL ) return $this;
1467
1468                 if ( !is_array( $beans ) ) $beans = array( $beans );
1469
1470                 foreach( $beans as $bean ) {
1471                         /** @var OODBBean $bean */
1472                         $function( $bean );
1473
1474                         $bean->fetchType  = $oldFetchType;
1475                         $bean->aliasName  = $oldAliasName;
1476                         $bean->withSql    = $oldWith;
1477                         $bean->withParams = $oldBindings;
1478
1479                         $bean->traverse( $property, $function, $maxDepth );
1480                 }
1481
1482                 return $this;
1483         }
1484
1485         /**
1486          * Implementation of Countable interface. Makes it possible to use
1487          * count() function on a bean.
1488          *
1489          * @return integer
1490          */
1491         public function count()
1492         {
1493                 return count( $this->properties );
1494         }
1495
1496         /**
1497          * Checks whether a bean is empty or not.
1498          * A bean is empty if it has no other properties than the id field OR
1499          * if all the other property are empty().
1500          *
1501          * @return boolean
1502          */
1503         public function isEmpty()
1504         {
1505                 $empty = TRUE;
1506                 foreach ( $this->properties as $key => $value ) {
1507                         if ( $key == 'id' ) {
1508                                 continue;
1509                         }
1510                         if ( !empty( $value ) ) {
1511                                 $empty = FALSE;
1512                         }
1513                 }
1514
1515                 return $empty;
1516         }
1517
1518         /**
1519          * Chainable setter.
1520          *
1521          * @param string $property the property of the bean
1522          * @param mixed  $value    the value you want to set
1523          *
1524          * @return OODBBean
1525          */
1526         public function setAttr( $property, $value )
1527         {
1528                 $this->$property = $value;
1529
1530                 return $this;
1531         }
1532
1533         /**
1534          * Comfort method.
1535          * Unsets all properties in array.
1536          *
1537          * @param array $properties properties you want to unset.
1538          *
1539          * @return OODBBean
1540          */
1541         public function unsetAll( $properties )
1542         {
1543                 foreach ( $properties as $prop ) {
1544                         if ( isset( $this->properties[$prop] ) ) {
1545                                 unset( $this->properties[$prop] );
1546                         }
1547                 }
1548
1549                 return $this;
1550         }
1551
1552         /**
1553          * Returns original (old) value of a property.
1554          * You can use this method to see what has changed in a
1555          * bean.
1556          *
1557          * @param string $property name of the property you want the old value of
1558          *
1559          * @return mixed
1560          */
1561         public function old( $property )
1562         {
1563                 $old = $this->getMeta( 'sys.orig', array() );
1564
1565                 if ( array_key_exists( $property, $old ) ) {
1566                         return $old[$property];
1567                 }
1568
1569                 return NULL;
1570         }
1571
1572         /**
1573          * Convenience method.
1574          * Returns TRUE if the bean has been changed, or FALSE otherwise.
1575          * Same as $bean->getMeta('tainted');
1576          * Note that a bean becomes tainted as soon as you retrieve a list from
1577          * the bean. This is because the bean lists are arrays and the bean cannot
1578          * determine whether you have made modifications to a list so RedBeanPHP
1579          * will mark the whole bean as tainted.
1580          *
1581          * @return boolean
1582          */
1583         public function isTainted()
1584         {
1585                 return $this->getMeta( 'tainted' );
1586         }
1587
1588         /**
1589          * Returns TRUE if the value of a certain property of the bean has been changed and
1590          * FALSE otherwise.
1591          *
1592          * Note that this method will return TRUE if applied to a loaded list.
1593          * Also note that this method keeps track of the bean's history regardless whether
1594          * it has been stored or not. Storing a bean does not undo it's history,
1595          * to clean the history of a bean use: clearHistory().
1596          *
1597          * @param string  $property name of the property you want the change-status of
1598          *
1599          * @return boolean
1600          */
1601         public function hasChanged( $property )
1602         {
1603                 return ( array_key_exists( $property, $this->properties ) ) ?
1604                         $this->old( $property ) != $this->properties[$property] : FALSE;
1605         }
1606
1607         /**
1608          * Returns TRUE if the specified list exists, has been loaded and has been changed:
1609          * beans have been added or deleted. This method will not tell you anything about
1610          * the state of the beans in the list.
1611          *
1612          * @param string $property name of the list to check
1613          *
1614          * @return boolean
1615          */
1616         public function hasListChanged( $property )
1617         {
1618                 if ( !array_key_exists( $property, $this->properties ) ) return FALSE;
1619                 $diffAdded = array_diff_assoc( $this->properties[$property], $this->__info['sys.shadow.'.$property] );
1620                 if ( count( $diffAdded ) ) return TRUE;
1621                 $diffMissing = array_diff_assoc( $this->__info['sys.shadow.'.$property], $this->properties[$property] );
1622                 if ( count( $diffMissing ) ) return TRUE;
1623                 return FALSE;
1624         }
1625
1626         /**
1627          * Clears (syncs) the history of the bean.
1628          * Resets all shadow values of the bean to their current value.
1629          *
1630          * @return self
1631          */
1632         public function clearHistory()
1633         {
1634                 $this->__info['sys.orig'] = array();
1635                 foreach( $this->properties as $key => $value ) {
1636                         if ( is_scalar($value) ) {
1637                                 $this->__info['sys.orig'][$key] = $value;
1638                         } else {
1639                                 $this->__info['sys.shadow.'.$key] = $value;
1640                         }
1641                 }
1642                 return $this;
1643         }
1644
1645         /**
1646          * Creates a N-M relation by linking an intermediate bean.
1647          * This method can be used to quickly connect beans using indirect
1648          * relations. For instance, given an album and a song you can connect the two
1649          * using a track with a number like this:
1650          *
1651          * Usage:
1652          *
1653          * <code>
1654          * $album->link('track', array('number'=>1))->song = $song;
1655          * </code>
1656          *
1657          * or:
1658          *
1659          * <code>
1660          * $album->link($trackBean)->song = $song;
1661          * </code>
1662          *
1663          * What this method does is adding the link bean to the own-list, in this case
1664          * ownTrack. If the first argument is a string and the second is an array or
1665          * a JSON string then the linking bean gets dispensed on-the-fly as seen in
1666          * example #1. After preparing the linking bean, the bean is returned thus
1667          * allowing the chained setter: ->song = $song.
1668          *
1669          * @param string|OODBBean $type          type of bean to dispense or the full bean
1670          * @param string|array    $qualification JSON string or array (optional)
1671          *
1672          * @return OODBBean
1673          */
1674         public function link( $typeOrBean, $qualification = array() )
1675         {
1676                 if ( is_string( $typeOrBean ) ) {
1677
1678                         $typeOrBean = AQueryWriter::camelsSnake( $typeOrBean );
1679
1680                         $bean = $this->beanHelper->getToolBox()->getRedBean()->dispense( $typeOrBean );
1681
1682                         if ( is_string( $qualification ) ) {
1683                                 $data = json_decode( $qualification, TRUE );
1684                         } else {
1685                                 $data = $qualification;
1686                         }
1687
1688                         foreach ( $data as $key => $value ) {
1689                                 $bean->$key = $value;
1690                         }
1691                 } else {
1692                         $bean = $typeOrBean;
1693                 }
1694
1695                 $list = 'own' . ucfirst( $bean->getMeta( 'type' ) );
1696
1697                 array_push( $this->$list, $bean );
1698
1699                 return $bean;
1700         }
1701
1702         /**
1703          * Returns a bean of the given type with the same ID of as
1704          * the current one. This only happens in a one-to-one relation.
1705          * This is as far as support for 1-1 goes in RedBeanPHP. This
1706          * method will only return a reference to the bean, changing it
1707          * and storing the bean will not update the related one-bean.
1708          *
1709          * @return OODBBean
1710          */
1711         public function one( $type ) {
1712                 return $this->beanHelper->getToolBox()->getRedBean()->load( $type, $this->id );
1713         }
1714
1715         /**
1716          * Returns the same bean freshly loaded from the database.
1717          *
1718          * @return OODBBean
1719          */
1720         public function fresh()
1721         {
1722                 return $this->beanHelper->getToolbox()->getRedBean()->load( $this->getMeta( 'type' ), $this->properties['id'] );
1723         }
1724
1725         /**
1726          * Registers a association renaming globally.
1727          *
1728          * @param string $via type you wish to use for shared lists
1729          *
1730          * @return OODBBean
1731          */
1732         public function via( $via )
1733         {
1734                 $this->via = AQueryWriter::camelsSnake( $via );
1735
1736                 return $this;
1737         }
1738
1739         /**
1740          * Counts all own beans of type $type.
1741          * Also works with alias(), with() and withCondition().
1742          *
1743          * @param string $type the type of bean you want to count
1744          *
1745          * @return integer
1746          */
1747         public function countOwn( $type )
1748         {
1749                 $type = $this->beau( $type );
1750
1751                 if ( $this->aliasName ) {
1752                         $myFieldLink     = $this->aliasName . '_id';
1753
1754                         $this->aliasName = NULL;
1755                 } else {
1756                         $myFieldLink = $this->__info['type'] . '_id';
1757                 }
1758
1759                 $count = 0;
1760
1761                 if ( $this->getID() ) {
1762
1763                         $firstKey = NULL;
1764                         if ( count( $this->withParams ) > 0 ) {
1765                                 reset( $this->withParams );
1766                                 $firstKey = key( $this->withParams );
1767                         }
1768
1769                         $joinSql = $this->parseJoin( $type );
1770
1771                         if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
1772                                         $bindings           = $this->withParams;
1773                                         $bindings[':slot0'] = $this->getID();
1774                                         $count              = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " {$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
1775                         } else {
1776                                         $bindings = array_merge( array( $this->getID() ), $this->withParams );
1777                                         $count    = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " {$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
1778                         }
1779
1780                 }
1781
1782                 $this->clearModifiers();
1783                 return (int) $count;
1784         }
1785
1786         /**
1787          * Counts all shared beans of type $type.
1788          * Also works with via(), with() and withCondition().
1789          *
1790          * @param string $type type of bean you wish to count
1791          *
1792          * @return integer
1793          */
1794         public function countShared( $type )
1795         {
1796                 $toolbox = $this->beanHelper->getToolbox();
1797                 $redbean = $toolbox->getRedBean();
1798                 $writer  = $toolbox->getWriter();
1799
1800                 if ( $this->via ) {
1801                         $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
1802
1803                         if ( $oldName !== $this->via ) {
1804                                 //set the new renaming rule
1805                                 $writer->renameAssocTable( $oldName, $this->via );
1806                                 $this->via = NULL;
1807                         }
1808                 }
1809
1810                 $type  = $this->beau( $type );
1811                 $count = 0;
1812
1813                 if ( $this->getID() ) {
1814                         $count = $redbean->getAssociationManager()->relatedCount( $this, $type, $this->withSql, $this->withParams );
1815                 }
1816
1817                 $this->clearModifiers();
1818                 return (integer) $count;
1819         }
1820
1821         /**
1822          * Iterates through the specified own-list and
1823          * fetches all properties (with their type) and
1824          * returns the references.
1825          * Use this method to quickly load indirectly related
1826          * beans in an own-list. Whenever you cannot use a
1827          * shared-list this method offers the same convenience
1828          * by aggregating the parent beans of all children in
1829          * the specified own-list.
1830          *
1831          * Example:
1832          *
1833          * <code>
1834          * $quest->aggr( 'xownQuestTarget', 'target', 'quest' );
1835          * </code>
1836          *
1837          * Loads (in batch) and returns references to all
1838          * quest beans residing in the $questTarget->target properties
1839          * of each element in the xownQuestTargetList.
1840          *
1841          * @param string $list     the list you wish to process
1842          * @param string $property the property to load
1843          * @param string $type     the type of bean residing in this property (optional)
1844          *
1845          * @return array
1846          */
1847         public function &aggr( $list, $property, $type = NULL )
1848         {
1849                 $this->via = NULL;
1850                 $ids = $beanIndex = $references = array();
1851
1852                 if ( strlen( $list ) < 4 ) throw new RedException('Invalid own-list.');
1853                 if ( strpos( $list, 'own') !== 0 ) throw new RedException('Only own-lists can be aggregated.');
1854                 if ( !ctype_upper( substr( $list, 3, 1 ) ) ) throw new RedException('Invalid own-list.');
1855
1856                 if ( is_null( $type ) ) $type = $property;
1857
1858                 foreach( $this->$list as $bean ) {
1859                         $field = $property . '_id';
1860                         if ( isset( $bean->$field)  ) {
1861                                 $ids[] = $bean->$field;
1862                                 $beanIndex[$bean->$field] = $bean;
1863                         }
1864                 }
1865
1866                 $beans = $this->beanHelper->getToolBox()->getRedBean()->batch( $type, $ids );
1867
1868                 //now preload the beans as well
1869                 foreach( $beans as $bean ) {
1870                         $beanIndex[$bean->id]->setProperty( $property, $bean );
1871                 }
1872
1873                 foreach( $beanIndex as $indexedBean ) {
1874                         $references[] = $indexedBean->$property;
1875                 }
1876
1877                 return $references;
1878         }
1879
1880         /**
1881          * Tests whether the database identities of two beans are equal.
1882          *
1883          * @param OODBBean $bean other bean
1884          *
1885          * @return boolean
1886          */
1887         public function equals(OODBBean $bean)
1888         {
1889                 return (bool) (
1890                            ( (string) $this->properties['id'] === (string) $bean->properties['id'] )
1891                         && ( (string) $this->__info['type']   === (string) $bean->__info['type']   )
1892                 );
1893         }
1894
1895         /**
1896          * Magic method jsonSerialize, implementation for the \JsonSerializable interface,
1897          * this method gets called by json_encode and facilitates a better JSON representation
1898          * of the bean. Exports the bean on JSON serialization, for the JSON fans.
1899          *
1900          * @see  http://php.net/manual/en/class.jsonserializable.php
1901          *
1902          * @return array
1903          */
1904         public function jsonSerialize()
1905         {
1906                 return $this->export();
1907         }
1908 }