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() );
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:
1072: if ( ($isOwn || $isShared) && (!$exists || $hasSQL || $differentAlias) ) {
1073:
1074: if ( !$this->noLoad ) {
1075: list( $redbean, , , $toolbox ) = $this->beanHelper->getExtractedToolbox();
1076: if ( $isOwn ) {
1077: $beans = $this->getOwnList( $listName, $redbean );
1078: } else {
1079: $beans = $this->getSharedList( lcfirst( substr( $property, 6 ) ), $redbean, $toolbox );
1080: }
1081: $this->__info["sys.shadow.$property"] = $beans;
1082: }
1083: }
1084:
1085: $this->withSql = '';
1086: $this->withParams = array();
1087: $this->aliasName = NULL;
1088: $this->fetchType = NULL;
1089: $this->noLoad = FALSE;
1090: $this->all = FALSE;
1091: $this->via = NULL;
1092:
1093: $this->__info['tainted'] = TRUE;
1094: $this->__info['changed'] = TRUE;
1095:
1096: if ( array_key_exists( $fieldLink, $this->properties ) && !( $value instanceof OODBBean ) ) {
1097: if ( is_null( $value ) || $value === FALSE ) {
1098:
1099: unset( $this->properties[ $property ]);
1100: $this->properties[ $fieldLink ] = NULL;
1101:
1102: return;
1103: } else {
1104: throw new RedException( 'Cannot cast to bean.' );
1105: }
1106: }
1107:
1108: if ( $value === FALSE ) {
1109: $value = '0';
1110: } elseif ( $value === TRUE ) {
1111: $value = '1';
1112: } elseif ( $value instanceof \DateTime ) {
1113: $value = $value->format( 'Y-m-d H:i:s' );
1114: }
1115:
1116: $this->properties[$property] = $value;
1117: }
1118:
1119: /**
1120: * Sets a property directly, for internal use only.
1121: *
1122: * @param string $property property
1123: * @param mixed $value value
1124: * @param boolean $updateShadow whether you want to update the shadow
1125: * @param boolean $taint whether you want to mark the bean as tainted
1126: *
1127: * @return void
1128: */
1129: public function setProperty( $property, $value, $updateShadow = FALSE, $taint = FALSE )
1130: {
1131: $this->properties[$property] = $value;
1132:
1133: if ( $updateShadow ) {
1134: $this->__info['sys.shadow.' . $property] = $value;
1135: }
1136:
1137: if ( $taint ) {
1138: $this->__info['tainted'] = TRUE;
1139: $this->__info['changed'] = TRUE;
1140: }
1141: }
1142:
1143: /**
1144: * Returns the value of a meta property. A meta property
1145: * contains additional information about the bean object that will not
1146: * be stored in the database. Meta information is used to instruct
1147: * RedBeanPHP as well as other systems how to deal with the bean.
1148: * If the property cannot be found this getter will return NULL instead.
1149: *
1150: * Example:
1151: *
1152: * <code>
1153: * $bean->setMeta( 'flush-cache', TRUE );
1154: * </code>
1155: *
1156: * RedBeanPHP also stores meta data in beans, this meta data uses
1157: * keys prefixed with 'sys.' (system).
1158: *
1159: * @param string $path path to property in meta data
1160: * @param mixed $default default value
1161: *
1162: * @return mixed
1163: */
1164: public function getMeta( $path, $default = NULL )
1165: {
1166: return ( isset( $this->__info[$path] ) ) ? $this->__info[$path] : $default;
1167: }
1168:
1169: /**
1170: * Gets and unsets a meta property.
1171: * Moves a meta property out of the bean.
1172: * This is a short-cut method that can be used instead
1173: * of combining a get/unset.
1174: *
1175: * @param string $path path to property in meta data
1176: * @param mixed $default default value
1177: *
1178: * @return mixed
1179: */
1180: public function moveMeta( $path, $value = NULL )
1181: {
1182: if ( isset( $this->__info[$path] ) ) {
1183: $value = $this->__info[ $path ];
1184: unset( $this->__info[ $path ] );
1185: }
1186: return $value;
1187: }
1188:
1189: /**
1190: * Stores a value in the specified Meta information property.
1191: * The first argument should be the key to store the value under,
1192: * the second argument should be the value. It is common to use
1193: * a path-like notation for meta data in RedBeanPHP like:
1194: * 'my.meta.data', however the dots are purely for readability, the
1195: * meta data methods do not store nested structures or hierarchies.
1196: *
1197: * @param string $path path / key to store value under
1198: * @param mixed $value value to store in bean (not in database) as meta data
1199: *
1200: * @return OODBBean
1201: */
1202: public function setMeta( $path, $value )
1203: {
1204: $this->__info[$path] = $value;
1205:
1206: return $this;
1207: }
1208:
1209: /**
1210: * Copies the meta information of the specified bean
1211: * This is a convenience method to enable you to
1212: * exchange meta information easily.
1213: *
1214: * @param OODBBean $bean bean to copy meta data of
1215: *
1216: * @return OODBBean
1217: */
1218: public function copyMetaFrom( OODBBean $bean )
1219: {
1220: $this->__info = $bean->__info;
1221:
1222: return $this;
1223: }
1224:
1225: /**
1226: * Sends the call to the registered model.
1227: *
1228: * @param string $method name of the method
1229: * @param array $args argument list
1230: *
1231: * @return mixed
1232: */
1233: public function __call( $method, $args )
1234: {
1235: if ( !isset( $this->__info['model'] ) ) {
1236: $model = $this->beanHelper->getModelForBean( $this );
1237:
1238: if ( !$model ) {
1239: return NULL;
1240: }
1241:
1242: $this->__info['model'] = $model;
1243: }
1244: if ( !method_exists( $this->__info['model'], $method ) ) {
1245:
1246: if ( self::$errorHandlingFUSE === FALSE ) {
1247: return NULL;
1248: }
1249:
1250: if ( in_array( $method, array( 'update', 'open', 'delete', 'after_delete', 'after_update', 'dispense' ), TRUE ) ) {
1251: return NULL;
1252: }
1253:
1254: $message = "FUSE: method does not exist in model: $method";
1255: if ( self::$errorHandlingFUSE === self::C_ERR_LOG ) {
1256: error_log( $message );
1257: return NULL;
1258: } elseif ( self::$errorHandlingFUSE === self::C_ERR_NOTICE ) {
1259: trigger_error( $message, E_USER_NOTICE );
1260: return NULL;
1261: } elseif ( self::$errorHandlingFUSE === self::C_ERR_WARN ) {
1262: trigger_error( $message, E_USER_WARNING );
1263: return NULL;
1264: } elseif ( self::$errorHandlingFUSE === self::C_ERR_EXCEPTION ) {
1265: throw new \Exception( $message );
1266: } elseif ( self::$errorHandlingFUSE === self::C_ERR_FUNC ) {
1267: $func = self::$errorHandler;
1268: return $func(array(
1269: 'message' => $message,
1270: 'method' => $method,
1271: 'args' => $args,
1272: 'bean' => $this
1273: ));
1274: }
1275: trigger_error( $message, E_USER_ERROR );
1276: return NULL;
1277: }
1278:
1279: return call_user_func_array( array( $this->__info['model'], $method ), $args );
1280: }
1281:
1282: /**
1283: * Implementation of __toString Method
1284: * Routes call to Model. If the model implements a __toString() method this
1285: * method will be called and the result will be returned. In case of an
1286: * echo-statement this result will be printed. If the model does not
1287: * implement a __toString method, this method will return a JSON
1288: * representation of the current bean.
1289: *
1290: * @return string
1291: */
1292: public function __toString()
1293: {
1294: $string = $this->__call( '__toString', array() );
1295:
1296: if ( $string === NULL ) {
1297: $list = array();
1298: foreach($this->properties as $property => $value) {
1299: if (is_scalar($value)) {
1300: $list[$property] = $value;
1301: }
1302: }
1303: return json_encode( $list );
1304: } else {
1305: return $string;
1306: }
1307: }
1308:
1309: /**
1310: * Implementation of Array Access Interface, you can access bean objects
1311: * like an array.
1312: * Call gets routed to __set.
1313: *
1314: * @param mixed $offset offset string
1315: * @param mixed $value value
1316: *
1317: * @return void
1318: */
1319: public function offsetSet( $offset, $value )
1320: {
1321: $this->__set( $offset, $value );
1322: }
1323:
1324: /**
1325: * Implementation of Array Access Interface, you can access bean objects
1326: * like an array.
1327: *
1328: * Array functions do not reveal x-own-lists and list-alias because
1329: * you dont want duplicate entries in foreach-loops.
1330: * Also offers a slight performance improvement for array access.
1331: *
1332: * @param mixed $offset property
1333: *
1334: * @return boolean
1335: */
1336: public function offsetExists( $offset )
1337: {
1338: return $this->__isset( $offset );
1339: }
1340:
1341: /**
1342: * Implementation of Array Access Interface, you can access bean objects
1343: * like an array.
1344: * Unsets a value from the array/bean.
1345: *
1346: * Array functions do not reveal x-own-lists and list-alias because
1347: * you dont want duplicate entries in foreach-loops.
1348: * Also offers a slight performance improvement for array access.
1349: *
1350: * @param mixed $offset property
1351: *
1352: * @return void
1353: */
1354: public function offsetUnset( $offset )
1355: {
1356: $this->__unset( $offset );
1357: }
1358:
1359: /**
1360: * Implementation of Array Access Interface, you can access bean objects
1361: * like an array.
1362: * Returns value of a property.
1363: *
1364: * Array functions do not reveal x-own-lists and list-alias because
1365: * you dont want duplicate entries in foreach-loops.
1366: * Also offers a slight performance improvement for array access.
1367: *
1368: * @param mixed $offset property
1369: *
1370: * @return mixed
1371: */
1372: public function &offsetGet( $offset )
1373: {
1374: return $this->__get( $offset );
1375: }
1376:
1377: /**
1378: * Chainable method to cast a certain ID to a bean; for instance:
1379: * $person = $club->fetchAs('person')->member;
1380: * This will load a bean of type person using member_id as ID.
1381: *
1382: * @param string $type preferred fetch type
1383: *
1384: * @return OODBBean
1385: */
1386: public function fetchAs( $type )
1387: {
1388: $this->fetchType = $type;
1389:
1390: return $this;
1391: }
1392:
1393: /**
1394: * For polymorphic bean relations.
1395: * Same as fetchAs but uses a column instead of a direct value.
1396: *
1397: * @param string $field field name to use for mapping
1398: *
1399: * @return OODBBean
1400: */
1401: public function poly( $field )
1402: {
1403: return $this->fetchAs( $this->$field );
1404: }
1405:
1406: /**
1407: * Traverses a bean property with the specified function.
1408: * Recursively iterates through the property invoking the
1409: * function for each bean along the way passing the bean to it.
1410: *
1411: * Can be used together with with, withCondition, alias and fetchAs.
1412: *
1413: * @param string $property property
1414: * @param callable $function function
1415: * @param integer $maxDepth maximum depth for traversal
1416: *
1417: * @return OODBBean
1418: * @throws RedException
1419: */
1420: public function traverse( $property, $function, $maxDepth = NULL )
1421: {
1422: $this->via = NULL;
1423: if ( strpos( $property, 'shared' ) !== FALSE ) {
1424: throw new RedException( 'Traverse only works with (x)own-lists.' );
1425: }
1426:
1427: if ( !is_null( $maxDepth ) ) {
1428: if ( !$maxDepth-- ) return $this;
1429: }
1430:
1431: $oldFetchType = $this->fetchType;
1432: $oldAliasName = $this->aliasName;
1433: $oldWith = $this->withSql;
1434: $oldBindings = $this->withParams;
1435:
1436: $beans = $this->$property;
1437:
1438: if ( $beans === NULL ) return $this;
1439:
1440: if ( !is_array( $beans ) ) $beans = array( $beans );
1441:
1442: foreach( $beans as $bean ) {
1443: /** @var OODBBean $bean */
1444: $function( $bean );
1445:
1446: $bean->fetchType = $oldFetchType;
1447: $bean->aliasName = $oldAliasName;
1448: $bean->withSql = $oldWith;
1449: $bean->withParams = $oldBindings;
1450:
1451: $bean->traverse( $property, $function, $maxDepth );
1452: }
1453:
1454: return $this;
1455: }
1456:
1457: /**
1458: * Implementation of Countable interface. Makes it possible to use
1459: * count() function on a bean.
1460: *
1461: * @return integer
1462: */
1463: public function count()
1464: {
1465: return count( $this->properties );
1466: }
1467:
1468: /**
1469: * Checks whether a bean is empty or not.
1470: * A bean is empty if it has no other properties than the id field OR
1471: * if all the other property are empty().
1472: *
1473: * @return boolean
1474: */
1475: public function isEmpty()
1476: {
1477: $empty = TRUE;
1478: foreach ( $this->properties as $key => $value ) {
1479: if ( $key == 'id' ) {
1480: continue;
1481: }
1482: if ( !empty( $value ) ) {
1483: $empty = FALSE;
1484: }
1485: }
1486:
1487: return $empty;
1488: }
1489:
1490: /**
1491: * Chainable setter.
1492: *
1493: * @param string $property the property of the bean
1494: * @param mixed $value the value you want to set
1495: *
1496: * @return OODBBean
1497: */
1498: public function setAttr( $property, $value )
1499: {
1500: $this->$property = $value;
1501:
1502: return $this;
1503: }
1504:
1505: /**
1506: * Comfort method.
1507: * Unsets all properties in array.
1508: *
1509: * @param array $properties properties you want to unset.
1510: *
1511: * @return OODBBean
1512: */
1513: public function unsetAll( $properties )
1514: {
1515: foreach ( $properties as $prop ) {
1516: if ( isset( $this->properties[$prop] ) ) {
1517: unset( $this->properties[$prop] );
1518: }
1519: }
1520:
1521: return $this;
1522: }
1523:
1524: /**
1525: * Returns original (old) value of a property.
1526: * You can use this method to see what has changed in a
1527: * bean.
1528: *
1529: * @param string $property name of the property you want the old value of
1530: *
1531: * @return mixed
1532: */
1533: public function old( $property )
1534: {
1535: $old = $this->getMeta( 'sys.orig', array() );
1536:
1537: if ( array_key_exists( $property, $old ) ) {
1538: return $old[$property];
1539: }
1540:
1541: return NULL;
1542: }
1543:
1544: /**
1545: * Convenience method.
1546: * Returns TRUE if the bean has been changed, or FALSE otherwise.
1547: * Same as $bean->getMeta('tainted');
1548: * Note that a bean becomes tainted as soon as you retrieve a list from
1549: * the bean. This is because the bean lists are arrays and the bean cannot
1550: * determine whether you have made modifications to a list so RedBeanPHP
1551: * will mark the whole bean as tainted.
1552: *
1553: * @return boolean
1554: */
1555: public function isTainted()
1556: {
1557: return $this->getMeta( 'tainted' );
1558: }
1559:
1560: /**
1561: * Returns TRUE if the value of a certain property of the bean has been changed and
1562: * FALSE otherwise.
1563: *
1564: * Note that this method will return TRUE if applied to a loaded list.
1565: * Also note that this method keeps track of the bean's history regardless whether
1566: * it has been stored or not. Storing a bean does not undo it's history,
1567: * to clean the history of a bean use: clearHistory().
1568: *
1569: * @param string $property name of the property you want the change-status of
1570: *
1571: * @return boolean
1572: */
1573: public function hasChanged( $property )
1574: {
1575: return ( array_key_exists( $property, $this->properties ) ) ?
1576: $this->old( $property ) != $this->properties[$property] : FALSE;
1577: }
1578:
1579: /**
1580: * Returns TRUE if the specified list exists, has been loaded and has been changed:
1581: * beans have been added or deleted. This method will not tell you anything about
1582: * the state of the beans in the list.
1583: *
1584: * @param string $property name of the list to check
1585: *
1586: * @return boolean
1587: */
1588: public function hasListChanged( $property )
1589: {
1590: if ( !array_key_exists( $property, $this->properties ) ) return FALSE;
1591: $diffAdded = array_diff_assoc( $this->properties[$property], $this->__info['sys.shadow.'.$property] );
1592: if ( count( $diffAdded ) ) return TRUE;
1593: $diffMissing = array_diff_assoc( $this->__info['sys.shadow.'.$property], $this->properties[$property] );
1594: if ( count( $diffMissing ) ) return TRUE;
1595: return FALSE;
1596: }
1597:
1598: /**
1599: * Clears (syncs) the history of the bean.
1600: * Resets all shadow values of the bean to their current value.
1601: *
1602: * @return self
1603: */
1604: public function clearHistory()
1605: {
1606: $this->__info['sys.orig'] = array();
1607: foreach( $this->properties as $key => $value ) {
1608: if ( is_scalar($value) ) {
1609: $this->__info['sys.orig'][$key] = $value;
1610: } else {
1611: $this->__info['sys.shadow.'.$key] = $value;
1612: }
1613: }
1614: return $this;
1615: }
1616:
1617: /**
1618: * Creates a N-M relation by linking an intermediate bean.
1619: * This method can be used to quickly connect beans using indirect
1620: * relations. For instance, given an album and a song you can connect the two
1621: * using a track with a number like this:
1622: *
1623: * Usage:
1624: *
1625: * <code>
1626: * $album->link('track', array('number'=>1))->song = $song;
1627: * </code>
1628: *
1629: * or:
1630: *
1631: * <code>
1632: * $album->link($trackBean)->song = $song;
1633: * </code>
1634: *
1635: * What this method does is adding the link bean to the own-list, in this case
1636: * ownTrack. If the first argument is a string and the second is an array or
1637: * a JSON string then the linking bean gets dispensed on-the-fly as seen in
1638: * example #1. After preparing the linking bean, the bean is returned thus
1639: * allowing the chained setter: ->song = $song.
1640: *
1641: * @param string|OODBBean $type type of bean to dispense or the full bean
1642: * @param string|array $qualification JSON string or array (optional)
1643: *
1644: * @return OODBBean
1645: */
1646: public function link( $typeOrBean, $qualification = array() )
1647: {
1648: if ( is_string( $typeOrBean ) ) {
1649:
1650: $typeOrBean = AQueryWriter::camelsSnake( $typeOrBean );
1651:
1652: $bean = $this->beanHelper->getToolBox()->getRedBean()->dispense( $typeOrBean );
1653:
1654: if ( is_string( $qualification ) ) {
1655: $data = json_decode( $qualification, TRUE );
1656: } else {
1657: $data = $qualification;
1658: }
1659:
1660: foreach ( $data as $key => $value ) {
1661: $bean->$key = $value;
1662: }
1663: } else {
1664: $bean = $typeOrBean;
1665: }
1666:
1667: $list = 'own' . ucfirst( $bean->getMeta( 'type' ) );
1668:
1669: array_push( $this->$list, $bean );
1670:
1671: return $bean;
1672: }
1673:
1674: /**
1675: * Returns a bean of the given type with the same ID of as
1676: * the current one. This only happens in a one-to-one relation.
1677: * This is as far as support for 1-1 goes in RedBeanPHP. This
1678: * method will only return a reference to the bean, changing it
1679: * and storing the bean will not update the related one-bean.
1680: *
1681: * @return OODBBean
1682: */
1683: public function one( $type ) {
1684: return $this->beanHelper->getToolBox()->getRedBean()->load( $type, $this->id );
1685: }
1686:
1687: /**
1688: * Returns the same bean freshly loaded from the database.
1689: *
1690: * @return OODBBean
1691: */
1692: public function fresh()
1693: {
1694: return $this->beanHelper->getToolbox()->getRedBean()->load( $this->getMeta( 'type' ), $this->properties['id'] );
1695: }
1696:
1697: /**
1698: * Registers a association renaming globally.
1699: *
1700: * @param string $via type you wish to use for shared lists
1701: *
1702: * @return OODBBean
1703: */
1704: public function via( $via )
1705: {
1706: $this->via = AQueryWriter::camelsSnake( $via );
1707:
1708: return $this;
1709: }
1710:
1711: /**
1712: * Counts all own beans of type $type.
1713: * Also works with alias(), with() and withCondition().
1714: *
1715: * @param string $type the type of bean you want to count
1716: *
1717: * @return integer
1718: */
1719: public function countOwn( $type )
1720: {
1721: $type = $this->beau( $type );
1722:
1723: if ( $this->aliasName ) {
1724: $myFieldLink = $this->aliasName . '_id';
1725:
1726: $this->aliasName = NULL;
1727: } else {
1728: $myFieldLink = $this->__info['type'] . '_id';
1729: }
1730:
1731: $count = 0;
1732:
1733: if ( $this->getID() ) {
1734:
1735: $firstKey = NULL;
1736: if ( count( $this->withParams ) > 0 ) {
1737: reset( $this->withParams );
1738: $firstKey = key( $this->withParams );
1739: }
1740:
1741: $joinSql = $this->parseJoin( $type );
1742:
1743: if ( !is_numeric( $firstKey ) || $firstKey === NULL ) {
1744: $bindings = $this->withParams;
1745: $bindings[':slot0'] = $this->getID();
1746: $count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " {$joinSql} $myFieldLink = :slot0 " . $this->withSql, $bindings );
1747: } else {
1748: $bindings = array_merge( array( $this->getID() ), $this->withParams );
1749: $count = $this->beanHelper->getToolbox()->getWriter()->queryRecordCount( $type, array(), " {$joinSql} $myFieldLink = ? " . $this->withSql, $bindings );
1750: }
1751:
1752: }
1753:
1754: $this->clearModifiers();
1755: return (int) $count;
1756: }
1757:
1758: /**
1759: * Counts all shared beans of type $type.
1760: * Also works with via(), with() and withCondition().
1761: *
1762: * @param string $type type of bean you wish to count
1763: *
1764: * @return integer
1765: */
1766: public function countShared( $type )
1767: {
1768: $toolbox = $this->beanHelper->getToolbox();
1769: $redbean = $toolbox->getRedBean();
1770: $writer = $toolbox->getWriter();
1771:
1772: if ( $this->via ) {
1773: $oldName = $writer->getAssocTable( array( $this->__info['type'], $type ) );
1774:
1775: if ( $oldName !== $this->via ) {
1776: //set the new renaming rule
1777: $writer->renameAssocTable( $oldName, $this->via );
1778: $this->via = NULL;
1779: }
1780: }
1781:
1782: $type = $this->beau( $type );
1783: $count = 0;
1784:
1785: if ( $this->getID() ) {
1786: $count = $redbean->getAssociationManager()->relatedCount( $this, $type, $this->withSql, $this->withParams );
1787: }
1788:
1789: $this->clearModifiers();
1790: return (integer) $count;
1791: }
1792:
1793: /**
1794: * Iterates through the specified own-list and
1795: * fetches all properties (with their type) and
1796: * returns the references.
1797: * Use this method to quickly load indirectly related
1798: * beans in an own-list. Whenever you cannot use a
1799: * shared-list this method offers the same convenience
1800: * by aggregating the parent beans of all children in
1801: * the specified own-list.
1802: *
1803: * Example:
1804: *
1805: * <code>
1806: * $quest->aggr( 'xownQuestTarget', 'target', 'quest' );
1807: * </code>
1808: *
1809: * Loads (in batch) and returns references to all
1810: * quest beans residing in the $questTarget->target properties
1811: * of each element in the xownQuestTargetList.
1812: *
1813: * @param string $list the list you wish to process
1814: * @param string $property the property to load
1815: * @param string $type the type of bean residing in this property (optional)
1816: *
1817: * @return array
1818: */
1819: public function &aggr( $list, $property, $type = NULL )
1820: {
1821: $this->via = NULL;
1822: $ids = $beanIndex = $references = array();
1823:
1824: if ( strlen( $list ) < 4 ) throw new RedException('Invalid own-list.');
1825: if ( strpos( $list, 'own') !== 0 ) throw new RedException('Only own-lists can be aggregated.');
1826: if ( !ctype_upper( substr( $list, 3, 1 ) ) ) throw new RedException('Invalid own-list.');
1827:
1828: if ( is_null( $type ) ) $type = $property;
1829:
1830: foreach( $this->$list as $bean ) {
1831: $field = $property . '_id';
1832: if ( isset( $bean->$field) ) {
1833: $ids[] = $bean->$field;
1834: $beanIndex[$bean->$field] = $bean;
1835: }
1836: }
1837:
1838: $beans = $this->beanHelper->getToolBox()->getRedBean()->batch( $type, $ids );
1839:
1840: //now preload the beans as well
1841: foreach( $beans as $bean ) {
1842: $beanIndex[$bean->id]->setProperty( $property, $bean );
1843: }
1844:
1845: foreach( $beanIndex as $indexedBean ) {
1846: $references[] = $indexedBean->$property;
1847: }
1848:
1849: return $references;
1850: }
1851:
1852: /**
1853: * Tests whether the database identities of two beans are equal.
1854: *
1855: * @param OODBBean $bean other bean
1856: *
1857: * @return boolean
1858: */
1859: public function equals(OODBBean $bean)
1860: {
1861: return (bool) (
1862: ( (string) $this->properties['id'] === (string) $bean->properties['id'] )
1863: && ( (string) $this->__info['type'] === (string) $bean->__info['type'] )
1864: );
1865: }
1866:
1867: /**
1868: * Magic method jsonSerialize, implementation for the \JsonSerializable interface,
1869: * this method gets called by json_encode and facilitates a better JSON representation
1870: * of the bean. Exports the bean on JSON serialization, for the JSON fans.
1871: *
1872: * @see http://php.net/manual/en/class.jsonserializable.php
1873: *
1874: * @return array
1875: */
1876: public function jsonSerialize()
1877: {
1878: return $this->export();
1879: }
1880: }
1881: