Overview

Namespaces

  • None
  • RedBeanPHP
    • Adapter
    • BeanHelper
    • Cursor
    • Driver
    • Logger
      • RDefault
    • QueryWriter
    • RedException
    • Repository
    • Util

Classes

  • R
  • RedBean_SimpleModel
  • RedBeanPHP\Adapter\DBAdapter
  • RedBeanPHP\AssociationManager
  • RedBeanPHP\BeanCollection
  • RedBeanPHP\BeanHelper\SimpleFacadeBeanHelper
  • RedBeanPHP\Cursor\NullCursor
  • RedBeanPHP\Cursor\PDOCursor
  • RedBeanPHP\Driver\RPDO
  • RedBeanPHP\DuplicationManager
  • RedBeanPHP\Facade
  • RedBeanPHP\Finder
  • RedBeanPHP\Jsonable
  • RedBeanPHP\LabelMaker
  • RedBeanPHP\Logger\RDefault
  • RedBeanPHP\Logger\RDefault\Debug
  • RedBeanPHP\Observable
  • RedBeanPHP\OODB
  • RedBeanPHP\OODBBean
  • RedBeanPHP\QueryWriter\AQueryWriter
  • RedBeanPHP\QueryWriter\CUBRID
  • RedBeanPHP\QueryWriter\MySQL
  • RedBeanPHP\QueryWriter\PostgreSQL
  • RedBeanPHP\QueryWriter\SQLiteT
  • RedBeanPHP\R
  • RedBeanPHP\Repository
  • RedBeanPHP\Repository\Fluid
  • RedBeanPHP\Repository\Frozen
  • RedBeanPHP\SimpleModel
  • RedBeanPHP\SimpleModelHelper
  • RedBeanPHP\TagManager
  • RedBeanPHP\ToolBox
  • RedBeanPHP\Util\ArrayTool
  • RedBeanPHP\Util\DispenseHelper
  • RedBeanPHP\Util\Dump
  • RedBeanPHP\Util\MultiLoader
  • RedBeanPHP\Util\Transaction

Interfaces

  • RedBeanPHP\Adapter
  • RedBeanPHP\BeanHelper
  • RedBeanPHP\Cursor
  • RedBeanPHP\Driver
  • RedBeanPHP\Logger
  • RedBeanPHP\Observer
  • RedBeanPHP\Plugin
  • RedBeanPHP\QueryWriter

Exceptions

  • RedBeanPHP\RedException
  • RedBeanPHP\RedException\SQL

Functions

  • array_flatten
  • dmp
  • EID
  • genslots
  • Overview
  • Namespace
  • Class
   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: 
API documentation generated by ApiGen