Overview

Namespaces

  • None
  • Wei
    • Validator

Classes

  • Wei\Validator\All
  • Wei\Validator\AllOf
  • Wei\Validator\Alnum
  • Wei\Validator\Alpha
  • Wei\Validator\BaseValidator
  • Wei\Validator\Between
  • Wei\Validator\Blank
  • Wei\Validator\Callback
  • Wei\Validator\CharLength
  • Wei\Validator\Chinese
  • Wei\Validator\Color
  • Wei\Validator\Contains
  • Wei\Validator\CreditCard
  • Wei\Validator\Date
  • Wei\Validator\DateTime
  • Wei\Validator\Decimal
  • Wei\Validator\Digit
  • Wei\Validator\Dir
  • Wei\Validator\DivisibleBy
  • Wei\Validator\DoubleByte
  • Wei\Validator\Email
  • Wei\Validator\EndsWith
  • Wei\Validator\EqualTo
  • Wei\Validator\Exists
  • Wei\Validator\FieldExists
  • Wei\Validator\File
  • Wei\Validator\GreaterThan
  • Wei\Validator\GreaterThanOrEqual
  • Wei\Validator\IdCardCn
  • Wei\Validator\IdCardHk
  • Wei\Validator\IdCardMo
  • Wei\Validator\IdCardTw
  • Wei\Validator\IdenticalTo
  • Wei\Validator\Image
  • Wei\Validator\In
  • Wei\Validator\Ip
  • Wei\Validator\Length
  • Wei\Validator\LessThan
  • Wei\Validator\LessThanOrEqual
  • Wei\Validator\Lowercase
  • Wei\Validator\Luhn
  • Wei\Validator\MaxLength
  • Wei\Validator\MinLength
  • Wei\Validator\MobileCn
  • Wei\Validator\NaturalNumber
  • Wei\Validator\NoneOf
  • Wei\Validator\Null
  • Wei\Validator\Number
  • Wei\Validator\OneOf
  • Wei\Validator\Password
  • Wei\Validator\Phone
  • Wei\Validator\PhoneCn
  • Wei\Validator\PlateNumberCn
  • Wei\Validator\PositiveInteger
  • Wei\Validator\PostcodeCn
  • Wei\Validator\Present
  • Wei\Validator\QQ
  • Wei\Validator\RecordExists
  • Wei\Validator\Regex
  • Wei\Validator\Required
  • Wei\Validator\SomeOf
  • Wei\Validator\StartsWith
  • Wei\Validator\Time
  • Wei\Validator\Tld
  • Wei\Validator\Type
  • Wei\Validator\Uppercase
  • Wei\Validator\Url
  • Wei\Validator\Uuid
  • Overview
  • Namespace
  • Function
   1: <?php
   2: /**
   3:  * Wei Framework
   4:  *
   5:  * @copyright   Copyright (c) 2008-2015 Twin Huang
   6:  * @license     http://opensource.org/licenses/mit-license.php MIT License
   7:  */
   8: 
   9: namespace Wei;
  10: 
  11: /**
  12:  * A base database record class
  13:  *
  14:  * @author      Twin Huang <twinhuang@qq.com>
  15:  * @property    Db $db A database service inspired by Doctrine DBAL
  16:  * @method      \Wei\Record db($table = null) Create a new record object
  17:  */
  18: class Record extends Base implements \ArrayAccess, \IteratorAggregate, \Countable
  19: {
  20:     /* The query types. */
  21:     const SELECT = 0;
  22:     const DELETE = 1;
  23:     const UPDATE = 2;
  24: 
  25:     /** The builder states. */
  26:     const STATE_DIRTY = 0;
  27:     const STATE_CLEAN = 1;
  28: 
  29:     /**
  30:      * The record table name
  31:      *
  32:      * @var string
  33:      */
  34:     protected $table;
  35: 
  36:     /**
  37:      * The complete record table name with table prefix
  38:      *
  39:      * @var string
  40:      */
  41:     protected $fullTable;
  42: 
  43:     /**
  44:      * The table fields
  45:      * If leave it blank, it will automatic generate form the database table,
  46:      * or fill it to speed up the record
  47:      *
  48:      * @var array
  49:      */
  50:     protected $fields = array();
  51: 
  52:     /**
  53:      * The primary key field
  54:      *
  55:      * @var string
  56:      */
  57:     protected $primaryKey = 'id';
  58: 
  59:     /**
  60:      * Whether it's a new record and has not save to database
  61:      *
  62:      * @var bool
  63:      */
  64:     protected $isNew = true;
  65: 
  66:     /**
  67:      * The record data
  68:      *
  69:      * @var array|$this[]
  70:      */
  71:     protected $data = array();
  72: 
  73:     /**
  74:      * Whether the record's data is changed
  75:      *
  76:      * @var bool
  77:      */
  78:     protected $isChanged = false;
  79: 
  80:     /**
  81:      * The record data before changed
  82:      *
  83:      * @var array
  84:      */
  85:     protected $changedData = array();
  86: 
  87:     /**
  88:      * Whether the record has been removed from database
  89:      *
  90:      * @var bool
  91:      */
  92:     protected $isDestroyed = false;
  93: 
  94:     /**
  95:      * Whether the record is waiting to remove from database
  96:      *
  97:      * @var bool
  98:      */
  99:     protected $detached = false;
 100: 
 101:     /**
 102:      * Whether the data is loaded
 103:      *
 104:      * @var bool
 105:      */
 106:     protected $loaded = false;
 107: 
 108:     /**
 109:      * Whether it contains multiple or single row data
 110:      *
 111:      * @var bool
 112:      */
 113:     protected $isColl;
 114: 
 115:     /**
 116:      * The parts of SQL
 117:      *
 118:      * @var array
 119:      */
 120:     protected $sqlParts = array(
 121:         'select' => array(),
 122:         'from' => null,
 123:         'join' => array(),
 124:         'set' => array(),
 125:         'where' => null,
 126:         'groupBy' => array(),
 127:         'having' => null,
 128:         'orderBy' => array(),
 129:         'limit' => null,
 130:         'offset' => null,
 131:     );
 132: 
 133:     /**
 134:      * A field to be the key of the fetched array, if not provided, return
 135:      * default number index array
 136:      *
 137:      * @var string
 138:      */
 139:     protected $indexBy;
 140: 
 141:     /**
 142:      * @var string The complete SQL string for this query.
 143:      */
 144:     protected $sql;
 145: 
 146:     /**
 147:      * The query parameters
 148:      *
 149:      * @var array
 150:      */
 151:     protected $params = array();
 152: 
 153:     /**
 154:      * The parameter type map of this query
 155:      *
 156:      * @var array
 157:      */
 158:     protected $paramTypes = array();
 159: 
 160:     /**
 161:      * The type of query this is. Can be select, update or delete
 162:      *
 163:      * @var integer
 164:      */
 165:     protected $type = self::SELECT;
 166: 
 167:     /**
 168:      * The state of the query object. Can be dirty or clean
 169:      *
 170:      * @var integer
 171:      */
 172:     protected $state = self::STATE_CLEAN;
 173: 
 174:     /**
 175:      * The callback triggered after load a record
 176:      *
 177:      * @var callable
 178:      */
 179:     protected $afterLoad;
 180: 
 181:     /**
 182:      * The callback triggered after fetch a record from database
 183:      *
 184:      * @var callable
 185:      */
 186:     protected $afterFind;
 187: 
 188:     /**
 189:      * The callback triggered before save a record
 190:      *
 191:      * @var callable
 192:      */
 193:     protected $beforeSave;
 194: 
 195:     /**
 196:      * The callback triggered after save a record
 197:      *
 198:      * @var callable
 199:      */
 200:     protected $afterSave;
 201: 
 202:     /**
 203:      * The callback triggered before insert a record
 204:      *
 205:      * @var callable
 206:      */
 207:     protected $beforeCreate;
 208: 
 209:     /**
 210:      * The callback triggered after insert a record
 211:      *
 212:      * @var callable
 213:      */
 214:     protected $afterCreate;
 215: 
 216:     /**
 217:      * The callback triggered after update a record
 218:      *
 219:      * @var callable
 220:      */
 221:     protected $beforeUpdate;
 222: 
 223:     /**
 224:      * The callback triggered after update a record
 225:      *
 226:      * @var callable
 227:      */
 228:     protected $afterUpdate;
 229: 
 230:     /**
 231:      * The callback triggered before delete a record
 232:      *
 233:      * @var callable
 234:      */
 235:     protected $beforeDestroy;
 236: 
 237:     /**
 238:      * The callback triggered after delete a record
 239:      *
 240:      * @var callable
 241:      */
 242:     protected $afterDestroy;
 243: 
 244:     /**
 245:      * Constructor
 246:      *
 247:      * @param array $options
 248:      */
 249:     public function __construct(array $options = array())
 250:     {
 251:         parent::__construct($options);
 252: 
 253:         // Clear changed status after created
 254:         $this->changedData = array();
 255:         $this->isChanged = false;
 256: 
 257:         $this->triggerCallback('afterLoad');
 258:     }
 259: 
 260:     /**
 261:      * Return the record table name
 262:      *
 263:      * @return string
 264:      */
 265:     public function getTable()
 266:     {
 267:         return $this->table;
 268:     }
 269: 
 270:     /**
 271:      * Set the record table name
 272:      *
 273:      * @param string $table
 274:      * @return $this
 275:      */
 276:     public function setTable($table)
 277:     {
 278:         return $this->from($table);
 279:     }
 280: 
 281:     /**
 282:      * Returns the record data as array
 283:      *
 284:      * @param array $returnFields A indexed array specified the fields to return
 285:      * @return array
 286:      */
 287:     public function toArray($returnFields = array())
 288:     {
 289:         if (!$this->isColl) {
 290:             if (!$returnFields) {
 291:                 $fields = $this->getFields();
 292:                 return $this->data + array_combine($fields, array_pad(array(), count($fields), null));
 293:             } else {
 294:                 return array_intersect_key($this->data, array_flip($returnFields));
 295:             }
 296:         } else {
 297:             $data = array();
 298:             /** @var $record Record */
 299:             foreach ($this->data as $key => $record) {
 300:                 $data[$key] = $record->toArray($returnFields);
 301:             }
 302:             return $data;
 303:         }
 304:     }
 305: 
 306:     /**
 307:      * Returns the record and relative records data as JSON string
 308:      *
 309:      * @param array $returnFields A indexed array specified the fields to return
 310:      * @return array
 311:      */
 312:     public function toJson($returnFields = array())
 313:     {
 314:         return json_encode($this->toArray($returnFields));
 315:     }
 316: 
 317:     /**
 318:      * Import a PHP array in this record
 319:      *
 320:      * @param array|\ArrayAccess $data
 321:      * @return $this
 322:      */
 323:     public function fromArray($data)
 324:     {
 325:         foreach ($data as $key => $value) {
 326:             $this->set($key, $value);
 327:         }
 328:         return $this;
 329:     }
 330: 
 331:     /**
 332:      * Import a PHP array in this record
 333:      *
 334:      * @param array $data
 335:      * @return $this
 336:      */
 337:     protected function setData(array $data)
 338:     {
 339:         return $this->fromArray($data);
 340:     }
 341: 
 342:     /**
 343:      * Save the record or data to database
 344:      *
 345:      * @param array $data
 346:      * @return $this
 347:      */
 348:     public function save($data = array())
 349:     {
 350:         // 1. Merges data from parameters
 351:         $data && $this->fromArray($data);
 352: 
 353:         // 2.1 Saves single record
 354:         if (!$this->isColl) {
 355: 
 356:             // 2.1.1 Returns when record has been destroy to avoid store dirty data
 357:             if ($this->isDestroyed) {
 358:                 return $this;
 359:             }
 360: 
 361:             // Deletes the record when it's waiting to remove from database
 362:             if ($this->detached) {
 363:                 $this->db->delete($this->table, array($this->primaryKey => $this->data[$this->primaryKey]));
 364:                 $this->isDestroyed = true;
 365:                 return $this;
 366:             }
 367: 
 368:             // 2.1.2 Triggers before callbacks
 369:             $isNew = $this->isNew;
 370:             $this->triggerCallback('beforeSave');
 371:             $this->triggerCallback($isNew ? 'beforeCreate' : 'beforeUpdate');
 372: 
 373:             // 2.1.3.1 Inserts new record
 374:             if ($isNew) {
 375:                 // Removes primary key value when it's empty to avoid SQL error
 376:                 if (array_key_exists($this->primaryKey, $this->data) && !$this->data[$this->primaryKey]) {
 377:                     unset($this->data[$this->primaryKey]);
 378:                 }
 379: 
 380:                 $this->db->insert($this->table, $this->data);
 381:                 $this->isNew = false;
 382: 
 383:                 // Receives primary key value when it's empty
 384:                 if (!isset($this->data[$this->primaryKey]) || !$this->data[$this->primaryKey]) {
 385:                     // Prepare sequence name for PostgreSQL
 386:                     $sequence = sprintf('%s_%s_seq', $this->fullTable, $this->primaryKey);
 387:                     $this->data[$this->primaryKey] = $this->db->lastInsertId($sequence);
 388:                 }
 389:             // 2.1.3.2 Updates existing record
 390:             } else {
 391:                 if ($this->isChanged) {
 392:                     $data = array_intersect_key($this->data, $this->changedData);
 393:                     $this->db->update($this->table, $data, array(
 394:                         $this->primaryKey => $this->data[$this->primaryKey]
 395:                     ));
 396:                 }
 397:             }
 398: 
 399:             // 2.1.4 Reset changed data and changed status
 400:             $this->changedData = array();
 401:             $this->isChanged = false;
 402: 
 403:             // 2.1.5. Triggers after callbacks
 404:             $this->triggerCallback($isNew ? 'afterCreate' : 'afterUpdate');
 405:             $this->triggerCallback('afterSave');
 406:         // 2.2 Loop and save collection records
 407:         } else {
 408:             foreach ($this->data as $record) {
 409:                 $record->save();
 410:             }
 411:         }
 412: 
 413:         return $this;
 414:     }
 415: 
 416:     /**
 417:      * Delete the current record and trigger the beforeDestroy and afterDestroy callback
 418:      *
 419:      * @param mixed $conditions
 420:      * @return $this
 421:      */
 422:     public function destroy($conditions = false)
 423:     {
 424:         $this->andWhere($conditions);
 425:         !$this->loaded && $this->loadData(0);
 426: 
 427:         if (!$this->isColl) {
 428:             $this->triggerCallback('beforeDestroy');
 429:             $this->db->delete($this->table, array($this->primaryKey => $this->data[$this->primaryKey]));
 430:             $this->isDestroyed = true;
 431:             $this->triggerCallback('afterDestroy');
 432:         } else {
 433:             foreach ($this->data as $record) {
 434:                 $record->destroy();
 435:             }
 436:         }
 437: 
 438:         return $this;
 439:     }
 440: 
 441:     /**
 442:      * Reload the record data from database
 443:      *
 444:      * @return $this
 445:      */
 446:     public function reload()
 447:     {
 448:         $this->data = (array)$this->db->select($this->table, $this->get($this->primaryKey));
 449:         $this->changedData = array();
 450:         $this->isChanged = false;
 451:         $this->triggerCallback('afterLoad');
 452:         return $this;
 453:     }
 454: 
 455:     /**
 456:      * Merges data into collection and save to database, including insert, update and delete
 457:      *
 458:      * @param array $data A two-dimensional array
 459:      * @param array $extraData The extra data for new rows
 460:      * @param bool $sort
 461:      * @return $this
 462:      */
 463:     public function saveColl($data, $extraData = array(), $sort = false)
 464:     {
 465:         if (!is_array($data)) {
 466:             return $this;
 467:         }
 468: 
 469:         // 1. Uses primary key as data index
 470:         $newData = array();
 471:         foreach ($this->data as $key => $record) {
 472:             unset($this->data[$key]);
 473:             // Ignore default data
 474:             if ($record instanceof $this) {
 475:                 $newData[$record['id']] = $record;
 476:             }
 477:         }
 478:         $this->data = $newData;
 479: 
 480:         // 2. Removes empty rows from data
 481:         foreach ($data as $index => $row) {
 482:             if (!array_filter($row)) {
 483:                 unset($data[$index]);
 484:             }
 485:         }
 486: 
 487:         // 3. Removes missing rows
 488:         $existIds = array();
 489:         foreach ($data as $row) {
 490:             if (isset($row['id']) && $row['id'] !== null) {
 491:                 $existIds[] = $row['id'];
 492:             }
 493:         }
 494:         /** @var $record Record */
 495:         foreach ($this->data as $key => $record) {
 496:             if (!in_array($record['id'], $existIds)) {
 497:                 $record->destroy();
 498:                 unset($this->data[$key]);
 499:             }
 500:         }
 501: 
 502:         // 4. Merges existing rows or create new rows
 503:         foreach ($data as $index => $row) {
 504:             if ($sort) {
 505:                 $row[$sort] = $index;
 506:             }
 507:             if (isset($row['id']) && isset($this->data[$row['id']])) {
 508:                 $this->data[$row['id']]->fromArray($row);
 509:             } else {
 510:                 $this[] = $this->db($this->table)->fromArray($extraData + $row);
 511:             }
 512:         }
 513: 
 514:         // 5. Save and return
 515:         return $this->save();
 516:     }
 517: 
 518:     /**
 519:      * Receives the record field value
 520:      *
 521:      * @param string $name
 522:      * @throws \InvalidArgumentException When field not found
 523:      * @return string
 524:      */
 525:     public function get($name)
 526:     {
 527:         // Check if field exists when it is not a collection
 528:         if (!$this->isColl && !in_array($name, $this->getFields())) {
 529:             throw new \InvalidArgumentException(sprintf(
 530:                 'Field "%s" not found in record class "%s"',
 531:                 $name,
 532:                 get_class($this)
 533:             ));
 534:         }
 535:         return isset($this->data[$name]) ? $this->data[$name] : null;
 536:     }
 537: 
 538:     /**
 539:      * Set the record field value
 540:      *
 541:      * @param string $name
 542:      * @param mixed $value
 543:      * @throws \InvalidArgumentException
 544:      * @return $this
 545:      */
 546:     public function set($name, $value = null)
 547:     {
 548:         $this->loaded = true;
 549: 
 550:         // Set record for collection
 551:         if (!$this->data && $value instanceof static) {
 552:             $this->isColl = true;
 553:         }
 554: 
 555:         if (!$this->isColl) {
 556:             if (in_array($name, $this->getFields())) {
 557:                 $this->changedData[$name] = isset($this->data[$name]) ? $this->data[$name] : null;
 558:                 $this->data[$name] = $value;
 559:                 $this->isChanged = true;
 560:             }
 561:         } else {
 562:             if (!$value instanceof static) {
 563:                 throw new \InvalidArgumentException('Value for collection must be an instance of Wei\Record');
 564:             } else {
 565:                 // Support $coll[] = $value;
 566:                 if ($name === null) {
 567:                     $this->data[] = $value;
 568:                 } else {
 569:                     $this->data[$name] = $value;
 570:                 }
 571:             }
 572:         }
 573:         return $this;
 574:     }
 575: 
 576:     /**
 577:      * Set field value for every record in collection
 578:      *
 579:      * @param string $name
 580:      * @param mixed $value
 581:      * @return $this
 582:      */
 583:     public function setAll($name, $value)
 584:     {
 585:         foreach ($this->data as $record) {
 586:             $record[$name] = $value;
 587:         }
 588:         return $this;
 589:     }
 590: 
 591:     /**
 592:      * Return the value of field from every record in collection
 593:      *
 594:      * @param string $name
 595:      * @return array
 596:      */
 597:     public function getAll($name)
 598:     {
 599:         $data = array();
 600:         foreach ($this->data as $record) {
 601:             $data[] = $record[$name];
 602:         }
 603:         return $data;
 604:     }
 605: 
 606:     /**
 607:      * Remove field value
 608:      *
 609:      * @param string $name The name of field
 610:      * @return $this
 611:      */
 612:     public function remove($name)
 613:     {
 614:         if (!$this->isColl) {
 615:             if (array_key_exists($name, $this->data)) {
 616:                 $this->data[$name] = null;
 617:             }
 618:         } else {
 619:             unset($this->data[$name]);
 620:         }
 621:         return $this;
 622:     }
 623: 
 624:     /**
 625:      * Increment a field
 626:      *
 627:      * @param string $name
 628:      * @param int $offset
 629:      * @return $this
 630:      */
 631:     public function incr($name, $offset)
 632:     {
 633:         $this[$name] = (object)($name . ' + ' . $offset);
 634:         return $this;
 635:     }
 636: 
 637:     /**
 638:      * Decrement a field
 639:      *
 640:      * @param string $name
 641:      * @param int $offset
 642:      * @return $this
 643:      */
 644:     public function decr($name, $offset)
 645:     {
 646:         $this[$name] = (object)($name . ' - ' . $offset);
 647:         return $this;
 648:     }
 649: 
 650:     /**
 651:      * Set the detach status for current record
 652:      *
 653:      * @param bool $bool
 654:      * @return $this
 655:      */
 656:     public function detach($bool = true)
 657:     {
 658:         $this->detached = (bool)$bool;
 659:         return $this;
 660:     }
 661: 
 662:     /**
 663:      * Check if it's a new record and has not save to database
 664:      *
 665:      * @return bool
 666:      */
 667:     public function isNew()
 668:     {
 669:         return $this->isNew;
 670:     }
 671: 
 672:     /**
 673:      * Check if the record's data or specified field is changed
 674:      *
 675:      * @param string $field
 676:      * @return bool
 677:      */
 678:     public function isChanged($field = null)
 679:     {
 680:         if ($field) {
 681:             return array_key_exists($field, $this->changedData);
 682:         }
 683:         return $this->isChanged;
 684:     }
 685: 
 686:     /**
 687:      * Check if the record has been removed from the database
 688:      *
 689:      * @return bool
 690:      */
 691:     public function isDestroyed()
 692:     {
 693:         return $this->isDestroyed;
 694:     }
 695: 
 696:     /**
 697:      * Check if the record is waiting to remove from database
 698:      *
 699:      * @return bool
 700:      */
 701:     public function isDetached()
 702:     {
 703:         return $this->detached;
 704:     }
 705: 
 706:     public function isColl()
 707:     {
 708:         return $this->isColl;
 709:     }
 710: 
 711:     /**
 712:      * Returns whether the data is loaded
 713:      *
 714:      * @return bool
 715:      */
 716:     public function isLoaded()
 717:     {
 718:         return $this->loaded;
 719:     }
 720: 
 721:     /**
 722:      * Sets the primary key field
 723:      *
 724:      * @param string $primaryKey
 725:      * @return $this
 726:      */
 727:     public function setPrimaryKey($primaryKey)
 728:     {
 729:         $this->primaryKey = $primaryKey;
 730:         return $this;
 731:     }
 732: 
 733:     /**
 734:      * Returns the primary key field
 735:      *
 736:      * @return string
 737:      */
 738:     public function getPrimaryKey()
 739:     {
 740:         return $this->primaryKey;
 741:     }
 742: 
 743:     /**
 744:      * Returns the name of fields of current table
 745:      *
 746:      * @return array
 747:      */
 748:     public function getFields()
 749:     {
 750:         if (empty($this->fields)) {
 751:             $this->fields = $this->db->getTableFields($this->fullTable, true);
 752:         }
 753:         return $this->fields;
 754:     }
 755: 
 756:     /**
 757:      * Return the field data before changed
 758:      *
 759:      * @param string $field
 760:      * @return string|array
 761:      */
 762:     public function getChangedData($field = null)
 763:     {
 764:         if ($field) {
 765:             return isset($this->changedData[$field]) ? $this->changedData[$field] : null;
 766:         }
 767:         return $this->changedData;
 768:     }
 769: 
 770:     /**
 771:      * Get the state of this query builder instance
 772:      *
 773:      * @return integer
 774:      */
 775:     public function getState()
 776:     {
 777:         return $this->state;
 778:     }
 779: 
 780:     /**
 781:      * Execute this query using the bound parameters and their types
 782:      *
 783:      * @return mixed
 784:      */
 785:     public function execute()
 786:     {
 787:         if ($this->type == self::SELECT) {
 788:             $this->loaded = true;
 789:             return $this->db->fetchAll($this->getSql(), $this->params, $this->paramTypes);
 790:         } else {
 791:             return $this->db->executeUpdate($this->getSql(), $this->params, $this->paramTypes);
 792:         }
 793:     }
 794: 
 795:     /**
 796:      * Executes the generated SQL and returns the found record object or false
 797:      *
 798:      * @param mixed $conditions
 799:      * @return $this|false
 800:      */
 801:     public function find($conditions = false)
 802:     {
 803:         $this->isColl = false;
 804:         $data = $this->fetch($conditions);
 805:         if ($data) {
 806:             $this->data = $data + $this->data;
 807:             $this->triggerCallback('afterFind');
 808:             return $this;
 809:         } else {
 810:             return false;
 811:         }
 812:     }
 813: 
 814:     /**
 815:      * Find a record by specified conditions and init with the specified data if record not found
 816:      *
 817:      * @param mixed $conditions
 818:      * @param array $data
 819:      * @return $this
 820:      */
 821:     public function findOrInit($conditions, $data = array())
 822:     {
 823:         if (!$this->find($conditions)) {
 824:             // Reset status when record not found
 825:             $this->isNew = true;
 826: 
 827:             !is_array($conditions) && $conditions = array($this->primaryKey => $conditions);
 828: 
 829:             // Convert to object to array
 830:             if (is_object($data) && method_exists($data, 'toArray')) {
 831:                 $data = $data->toArray();
 832:             }
 833: 
 834:             $this->fromArray($conditions + $data);
 835:         }
 836:         return $this;
 837:     }
 838: 
 839:     /**
 840:      * Find a record by specified conditions and throws 404 exception if record not found
 841:      *
 842:      * @param mixed $conditions
 843:      * @throws \Exception
 844:      * @return $this
 845:      */
 846:     public function findOne($conditions = false)
 847:     {
 848:         if ($this->find($conditions)) {
 849:             return $this;
 850:         } else {
 851:             throw new \Exception('Record not found', 404);
 852:         }
 853:     }
 854: 
 855:     /**
 856:      * Executes the generated SQL and returns the found record collection object or false
 857:      *
 858:      * @param mixed $conditions
 859:      * @return $this|$this[]
 860:      */
 861:     public function findAll($conditions = false)
 862:     {
 863:         $this->isColl = true;
 864:         $data = $this->fetchAll($conditions);
 865: 
 866:         $records = array();
 867:         foreach ($data as $key => $row) {
 868:             /** @var $records Record[] */
 869:             $records[$key] = $this->db->init($this->table, $row, false);
 870:             $records[$key]->triggerCallback('afterFind');
 871:         }
 872: 
 873:         $this->data = $records;
 874:         return $this;
 875:     }
 876: 
 877:     /**
 878:      * Find a record by primary key value
 879:      *
 880:      * @param mixed $value
 881:      * @return $this|false
 882:      */
 883:     public function findById($value)
 884:     {
 885:         return $this->find(array($this->primaryKey => $value));
 886:     }
 887: 
 888:     /**
 889:      * Find a record by primary key value and throws 404 exception if record not found
 890:      *
 891:      * @param mixed $value
 892:      * @return $this
 893:      */
 894:     public function findOneById($value)
 895:     {
 896:         return $this->findOne(array($this->primaryKey => $value));
 897:     }
 898: 
 899:     /**
 900:      * Find a record by primary key value and init with the specified data if record not found
 901:      *
 902:      * @param mixed $value
 903:      * @param array $data
 904:      * @return $this
 905:      */
 906:     public function findOrInitById($value, $data = array())
 907:     {
 908:         return $this->findOrInit(array($this->primaryKey => $value), $data);
 909:     }
 910: 
 911:     /**
 912:      * Executes the generated query and returns the first array result
 913:      *
 914:      * @param mixed $conditions
 915:      * @return array|false
 916:      */
 917:     public function fetch($conditions = false)
 918:     {
 919:         $this->andWhere($conditions);
 920:         $this->limit(1);
 921:         $data = $this->execute();
 922:         return $data ? $data[0] : false;
 923:     }
 924: 
 925:     /**
 926:      * Executes the generated query and returns a column value of the first row
 927:      *
 928:      * @param mixed $conditions
 929:      * @return array|false
 930:      */
 931:     public function fetchColumn($conditions = false)
 932:     {
 933:         $data = $this->fetch($conditions);
 934:         return $data ? current($data) : false;
 935:     }
 936: 
 937:     /**
 938:      * Executes the generated query and returns all array results
 939:      *
 940:      * @param mixed $conditions
 941:      * @return array|false
 942:      */
 943:     public function fetchAll($conditions = false)
 944:     {
 945:         $this->andWhere($conditions);
 946:         $data = $this->execute();
 947:         if ($this->indexBy) {
 948:             $data = $this->executeIndexBy($data, $this->indexBy);
 949:         }
 950:         return $data;
 951:     }
 952: 
 953:     /**
 954:      * Executes a COUNT query to receive the rows number
 955:      *
 956:      * @param mixed $conditions
 957:      * @param string $count
 958:      * @return int
 959:      */
 960:     public function count($conditions = false, $count = '1')
 961:     {
 962:         $this->andWhere($conditions);
 963: 
 964:         $select = $this->sqlParts['select'];
 965:         $this->select('COUNT(' . $count . ')');
 966:         $count = (int)$this->db->fetchColumn($this->getSqlForSelect(true), $this->params);
 967:         $this->sqlParts['select'] = $select;
 968: 
 969:         return $count;
 970:     }
 971: 
 972:     /**
 973:      * Executes a sub query to receive the rows number
 974:      *
 975:      * @param mixed $conditions
 976:      * @return int
 977:      */
 978:     public function countBySubQuery($conditions = false)
 979:     {
 980:         $this->andWhere($conditions);
 981:         return (int)$this->db->fetchColumn($this->getSqlForCount(), $this->params);
 982:     }
 983: 
 984:     /**
 985:      * Returns the record number in collection
 986:      *
 987:      * @return int
 988:      */
 989:     public function length()
 990:     {
 991:         return $this->size();
 992:     }
 993: 
 994:     /**
 995:      * Returns the record number in collection
 996:      *
 997:      * @return int
 998:      */
 999:     public function size()
1000:     {
1001:         $this->loadData(0);
1002:         return count($this->data);
1003:     }
1004: 
1005:     /**
1006:      * Execute a update query with specified data
1007:      *
1008:      * @param array $set
1009:      * @return int
1010:      */
1011:     public function update($set = array())
1012:     {
1013:         $this->add('set', $set, true);
1014:         $this->type = self::UPDATE;
1015:         return $this->execute();
1016:     }
1017: 
1018:     /**
1019:      * Execute a delete query with specified conditions
1020:      *
1021:      * @param mixed $conditions
1022:      * @return mixed
1023:      */
1024:     public function delete($conditions = false)
1025:     {
1026:         $this->andWhere($conditions);
1027:         $this->type = self::DELETE;
1028:         return $this->execute();
1029:     }
1030: 
1031:     /**
1032:      * Sets the position of the first result to retrieve (the "offset")
1033:      *
1034:      * @param integer $offset The first result to return
1035:      * @return $this
1036:      */
1037:     public function offset($offset)
1038:     {
1039:         $offset < 0 && $offset = 0;
1040:         return $this->add('offset', (int)$offset);
1041:     }
1042: 
1043:     /**
1044:      * Sets the maximum number of results to retrieve (the "limit")
1045:      *
1046:      * @param integer $limit The maximum number of results to retrieve
1047:      * @return $this
1048:      */
1049:     public function limit($limit)
1050:     {
1051:         $limit < 1 && $limit = 1;
1052:         return $this->add('limit', (int)$limit);
1053:     }
1054: 
1055:     /**
1056:      * Sets the page number, the "OFFSET" value is equals "($page - 1) * LIMIT"
1057:      *
1058:      * @param int $page The page number
1059:      * @return $this
1060:      */
1061:     public function page($page)
1062:     {
1063:         $limit = $this->getSqlPart('limit');
1064:         if (!$limit) {
1065:             $limit = 10;
1066:             $this->add('limit', $limit);
1067:         }
1068:         return $this->offset(($page - 1) * $limit);
1069:     }
1070: 
1071:     /**
1072:      * Specifies an item that is to be returned in the query result.
1073:      * Replaces any previously specified selections, if any.
1074:      *
1075:      * @param mixed $select The selection expressions.
1076:      * @return $this
1077:      */
1078:     public function select($select = null)
1079:     {
1080:         $this->type = self::SELECT;
1081: 
1082:         if (empty($select)) {
1083:             return $this;
1084:         }
1085: 
1086:         $selects = is_array($select) ? $select : func_get_args();
1087:         return $this->add('select', $selects, false);
1088:     }
1089: 
1090:     /**
1091:      * Adds an item that is to be returned in the query result.
1092:      *
1093:      * @param mixed $select The selection expression.
1094:      * @return $this
1095:      */
1096:     public function addSelect($select = null)
1097:     {
1098:         $this->type = self::SELECT;
1099: 
1100:         if (empty($select)) {
1101:             return $this;
1102:         }
1103: 
1104:         $selects = is_array($select) ? $select : func_get_args();
1105:         return $this->add('select', $selects, true);
1106:     }
1107: 
1108:     /**
1109:      * Sets table for FROM query
1110:      *
1111:      * @param string $from The table
1112:      * @return $this
1113:      */
1114:     public function from($from)
1115:     {
1116:         $pos = strpos($from, ' ');
1117:         if (false !== $pos) {
1118:             $this->table = substr($from, 0, $pos);
1119:         } else {
1120:             $this->table = $from;
1121:         }
1122:         $this->fullTable = $this->db->getTable($this->table);
1123:         return $this->add('from', $this->db->getTable($from));
1124:     }
1125: 
1126:     /**
1127:      * Adds a inner join to the query
1128:      *
1129:      * @param string $table The table name to join
1130:      * @param string $on The condition for the join
1131:      * @return $this
1132:      */
1133:     public function join($table, $on = null)
1134:     {
1135:         return $this->innerJoin($table, $on);
1136:     }
1137: 
1138:     /**
1139:      * Adds a inner join to the query
1140:      *
1141:      * @param string $table The table name to join
1142:      * @param string $on The condition for the join
1143:      * @return $this
1144:      */
1145:     public function innerJoin($table, $on = null)
1146:     {
1147:         return $this->add('join', array('type' => 'inner', 'table' => $table, 'on' => $on), true);
1148:     }
1149: 
1150:     /**
1151:      * Adds a left join to the query
1152:      *
1153:      * @param string $table The table name to join
1154:      * @param string $on The condition for the join
1155:      * @return $this
1156:      */
1157:     public function leftJoin($table, $on = null)
1158:     {
1159:         return $this->add('join', array('type' => 'left', 'table' => $table, 'on' => $on), true);
1160:     }
1161: 
1162:     /**
1163:      * Adds a right join to the query
1164:      *
1165:      * @param string $table The table name to join
1166:      * @param string $on The condition for the join
1167:      * @return $this
1168:      */
1169:     public function rightJoin($table, $on = null)
1170:     {
1171:         return $this->add('join', array('type' => 'right', 'table' => $table, 'on' => $on), true);
1172:     }
1173: 
1174:     /**
1175:      * Specifies one or more restrictions to the query result.
1176:      * Replaces any previously specified restrictions, if any.
1177:      *
1178:      * ```php
1179:      * $user = wei()->db('user')->where('id = 1');
1180:      * $user = wei()->db('user')->where('id = ?', 1);
1181:      * $users = wei()->db('user')->where(array('id' => '1', 'username' => 'twin'));
1182:      * $users = wei()->where(array('id' => array('1', '2', '3')));
1183:      * ```
1184:      *
1185:      * @param mixed $conditions The WHERE conditions
1186:      * @param array $params The condition parameters
1187:      * @param array $types The parameter types
1188:      * @return $this
1189:      */
1190:     public function where($conditions, $params = array(), $types = array())
1191:     {
1192:         if ($conditions === false) {
1193:             return $this;
1194:         } else {
1195:             $conditions = $this->processCondition($conditions, $params, $types);
1196:             return $this->add('where', $conditions);
1197:         }
1198:     }
1199: 
1200:     /**
1201:      * Adds one or more restrictions to the query results, forming a logical
1202:      * conjunction with any previously specified restrictions
1203:      *
1204:      * @param string $conditions The WHERE conditions
1205:      * @param array $params The condition parameters
1206:      * @param array $types The parameter types
1207:      * @return $this
1208:      */
1209:     public function andWhere($conditions, $params = array(), $types = array())
1210:     {
1211:         if ($conditions === false) {
1212:             return $this;
1213:         } else {
1214:             $conditions = $this->processCondition($conditions, $params, $types);
1215:             return $this->add('where', $conditions, true, 'AND');
1216:         }
1217:     }
1218: 
1219:     /**
1220:      * Adds one or more restrictions to the query results, forming a logical
1221:      * disjunction with any previously specified restrictions.
1222:      *
1223:      * @param string $conditions The WHERE conditions
1224:      * @param array $params The condition parameters
1225:      * @param array $types The parameter types
1226:      * @return $this
1227:      */
1228:     public function orWhere($conditions, $params = array(), $types = array())
1229:     {
1230:         $conditions = $this->processCondition($conditions, $params, $types);
1231:         return $this->add('where', $conditions, true, 'OR');
1232:     }
1233: 
1234:     /**
1235:      * Specifies a grouping over the results of the query.
1236:      * Replaces any previously specified groupings, if any.
1237:      *
1238:      * @param mixed $groupBy The grouping expression.
1239:      * @return $this
1240:      */
1241:     public function groupBy($groupBy)
1242:     {
1243:         if (empty($groupBy)) {
1244:             return $this;
1245:         }
1246: 
1247:         $groupBy = is_array($groupBy) ? $groupBy : func_get_args();
1248:         return $this->add('groupBy', $groupBy, false);
1249:     }
1250: 
1251:     /**
1252:      * Adds a grouping expression to the query.
1253:      *
1254:      * @param mixed $groupBy The grouping expression.
1255:      * @return $this
1256:      */
1257:     public function addGroupBy($groupBy)
1258:     {
1259:         if (empty($groupBy)) {
1260:             return $this;
1261:         }
1262:         $groupBy = is_array($groupBy) ? $groupBy : func_get_args();
1263:         return $this->add('groupBy', $groupBy, true);
1264:     }
1265: 
1266:     /**
1267:      * Specifies a restriction over the groups of the query.
1268:      * Replaces any previous having restrictions, if any.
1269:      *
1270:      * @param string $conditions The having conditions
1271:      * @param array $params The condition parameters
1272:      * @param array $types The parameter types
1273:      * @return $this
1274:      */
1275:     public function having($conditions, $params = array(), $types = array())
1276:     {
1277:         $conditions = $this->processCondition($conditions, $params, $types);
1278:         return $this->add('having', $conditions);
1279:     }
1280: 
1281:     /**
1282:      * Adds a restriction over the groups of the query, forming a logical
1283:      * conjunction with any existing having restrictions.
1284:      *
1285:      * @param string $conditions The HAVING conditions to append
1286:      * @param array $params The condition parameters
1287:      * @param array $types The parameter types
1288:      * @return $this
1289:      */
1290:     public function andHaving($conditions, $params = array(), $types = array())
1291:     {
1292:         $conditions = $this->processCondition($conditions, $params, $types);
1293:         return $this->add('having', $conditions, true, 'AND');
1294:     }
1295: 
1296:     /**
1297:      * Adds a restriction over the groups of the query, forming a logical
1298:      * disjunction with any existing having restrictions.
1299:      *
1300:      * @param string $conditions The HAVING conditions to add
1301:      * @param array $params The condition parameters
1302:      * @param array $types The parameter types
1303:      * @return $this
1304:      */
1305:     public function orHaving($conditions, $params = array(), $types = array())
1306:     {
1307:         $conditions = $this->processCondition($conditions, $params, $types);
1308:         return $this->add('having', $conditions, true, 'OR');
1309:     }
1310: 
1311:     /**
1312:      * Specifies an ordering for the query results.
1313:      * Replaces any previously specified orderings, if any.
1314:      *
1315:      * @param string $sort The ordering expression.
1316:      * @param string $order The ordering direction.
1317:      * @return $this
1318:      */
1319:     public function orderBy($sort, $order = 'ASC')
1320:     {
1321:         return $this->add('orderBy', $sort . ' ' . ($order ? : 'ASC'), false);
1322:     }
1323: 
1324:     /**
1325:      * Adds an ordering to the query results.
1326:      *
1327:      * @param string $sort The ordering expression.
1328:      * @param string $order The ordering direction.
1329:      * @return $this
1330:      */
1331:     public function addOrderBy($sort, $order = 'ASC')
1332:     {
1333:         return $this->add('orderBy', $sort . ' ' . ($order ? : 'ASC'), true);
1334:     }
1335: 
1336:     /**
1337:      * Adds a DESC ordering to the query
1338:      *
1339:      * @param string $field The name of field
1340:      * @return $this
1341:      */
1342:     public function desc($field)
1343:     {
1344:         return $this->addOrderBy($field, 'DESC');
1345:     }
1346: 
1347:     /**
1348:      * Add an ASC ordering to the query
1349:      *
1350:      * @param string $field The name of field
1351:      * @return $this
1352:      */
1353:     public function asc($field)
1354:     {
1355:         return $this->addOrderBy($field, 'ASC');
1356:     }
1357: 
1358:     /**
1359:      * Specifies a field to be the key of the fetched array
1360:      *
1361:      * @param string $field
1362:      * @return $this
1363:      */
1364:     public function indexBy($field)
1365:     {
1366:         // Real time index after data loaded
1367:         if (!empty($this->data)) {
1368:             $this->data = $this->executeIndexBy($this->data, $field);
1369:         }
1370:         $this->indexBy = $field;
1371:         return $this;
1372:     }
1373: 
1374:     /**
1375:      * @param array $data
1376:      * @param string $field
1377:      * @return array
1378:      * @throws \RuntimeException
1379:      */
1380:     protected function executeIndexBy($data, $field)
1381:     {
1382:         if (!$data) {
1383:             return $data;
1384:         }
1385: 
1386:         if (!array_key_exists($field, $data[0]) && !($data[0] instanceof \ArrayAccess && $data[0]->offsetExists($field))) {
1387:             throw new \RuntimeException(sprintf('Index field "%s" not found in fetched data', $field));
1388:         }
1389: 
1390:         foreach ($data as $key => $row) {
1391:             $data[$row[$field]] = $row;
1392:             unset($data[$key]);
1393:         }
1394: 
1395:         return $data;
1396:     }
1397: 
1398:     /**
1399:      * Returns a SQL query part by its name
1400:      *
1401:      * @param string $name The name of SQL part
1402:      * @return mixed
1403:      */
1404:     public function getSqlPart($name)
1405:     {
1406:         return isset($this->sqlParts[$name]) ? $this->sqlParts[$name] : false;
1407:     }
1408: 
1409:     /**
1410:      * Get all SQL parts
1411:      *
1412:      * @return array $sqlParts
1413:      */
1414:     public function getSqlParts()
1415:     {
1416:         return $this->sqlParts;
1417:     }
1418: 
1419:     /**
1420:      * Reset all SQL parts
1421:      *
1422:      * @param array $name
1423:      * @return $this
1424:      */
1425:     public function resetSqlParts($name = null)
1426:     {
1427:         if (is_null($name)) {
1428:             $name = array_keys($this->sqlParts);
1429:         }
1430:         foreach ($name as $queryPartName) {
1431:             $this->resetSqlPart($queryPartName);
1432:         }
1433:         return $this;
1434:     }
1435: 
1436:     /**
1437:      * Reset single SQL part
1438:      *
1439:      * @param string $name
1440:      * @return $this
1441:      */
1442:     public function resetSqlPart($name)
1443:     {
1444:         $this->sqlParts[$name] = is_array($this->sqlParts[$name]) ? array() : null;
1445:         $this->state = self::STATE_DIRTY;
1446:         return $this;
1447:     }
1448: 
1449:     /**
1450:      * Sets a query parameter for the query being constructed
1451:      *
1452:      * @param string|integer $key The parameter position or name
1453:      * @param mixed $value The parameter value
1454:      * @param string|null $type PDO::PARAM_*
1455:      * @return $this
1456:      */
1457:     public function setParameter($key, $value, $type = null)
1458:     {
1459:         if ($type !== null) {
1460:             $this->paramTypes[$key] = $type;
1461:         }
1462: 
1463:         $this->params[$key] = $value;
1464:         return $this;
1465:     }
1466: 
1467:     /**
1468:      * Gets a (previously set) query parameter of the query being constructed
1469:      *
1470:      * @param mixed $key The key (index or name) of the bound parameter
1471:      * @return mixed The value of the bound parameter
1472:      */
1473:     public function getParameter($key)
1474:     {
1475:         return isset($this->params[$key]) ? $this->params[$key] : null;
1476:     }
1477: 
1478:     /**
1479:      * Sets a collection of query parameters for the query being constructed
1480:      *
1481:      * @param array $params The query parameters to set
1482:      * @param array $types The query parameters types to set
1483:      * @return $this
1484:      */
1485:     public function setParameters(array $params, array $types = array())
1486:     {
1487:         $this->paramTypes = $types;
1488:         $this->params = $params;
1489:         return $this;
1490:     }
1491: 
1492:     /**
1493:      * Gets all defined query parameters for the query being constructed.
1494:      *
1495:      * @return array The currently defined query parameters.
1496:      */
1497:     public function getParameters()
1498:     {
1499:         return $this->params;
1500:     }
1501: 
1502:     /**
1503:      * Get the complete SQL string formed by the current specifications of this QueryBuilder
1504:      *
1505:      * @return string The sql query string
1506:      */
1507:     public function getSql()
1508:     {
1509:         if ($this->sql !== null && $this->state === self::STATE_CLEAN) {
1510:             return $this->sql;
1511:         }
1512: 
1513:         switch ($this->type) {
1514:             case self::DELETE:
1515:                 $this->sql = $this->getSqlForDelete();
1516:                 break;
1517: 
1518:             case self::UPDATE:
1519:                 $this->sql = $this->getSqlForUpdate();
1520:                 break;
1521: 
1522:             case self::SELECT:
1523:             default:
1524:                 $this->sql = $this->getSqlForSelect();
1525:                 break;
1526:         }
1527: 
1528:         $this->state = self::STATE_CLEAN;
1529: 
1530:         return $this->sql;
1531:     }
1532: 
1533:     /**
1534:      * Converts this instance into an SELECT string in SQL
1535:      *
1536:      * @param bool $count
1537:      * @return string
1538:      */
1539:     protected function getSqlForSelect($count = false)
1540:     {
1541:         $parts = $this->sqlParts;
1542: 
1543:         if (!$parts['select']) {
1544:             $parts['select'] = array('*');
1545:         }
1546: 
1547:         $query = 'SELECT ' . implode(', ', $parts['select']) . ' FROM ' . $parts['from'];
1548: 
1549:         // JOIN
1550:         foreach ($parts['join'] as $join) {
1551:             $query .= ' ' . strtoupper($join['type'])
1552:                 . ' JOIN ' . $join['table']
1553:                 . ' ON ' . $join['on'];
1554:         }
1555: 
1556:         $query .= ($parts['where'] !== null ? ' WHERE ' . ((string)$parts['where']) : '')
1557:             . ($parts['groupBy'] ? ' GROUP BY ' . implode(', ', $parts['groupBy']) : '')
1558:             . ($parts['having'] !== null ? ' HAVING ' . ((string)$parts['having']) : '');
1559: 
1560:         if (false === $count) {
1561:             $query .= ($parts['orderBy'] ? ' ORDER BY ' . implode(', ', $parts['orderBy']) : '')
1562:                 . ($parts['limit'] !== null ? ' LIMIT ' . $parts['limit'] : '')
1563:                 . ($parts['offset'] !== null ? ' OFFSET ' . $parts['offset'] : '');
1564:         }
1565: 
1566:         return $query;
1567:     }
1568: 
1569:     /**
1570:      * Converts this instance into an SELECT COUNT string in SQL
1571:      */
1572:     protected function getSqlForCount()
1573:     {
1574:         return "SELECT COUNT(*) FROM (" . $this->getSqlForSelect(true) . ") wei_count";
1575:     }
1576: 
1577:     /**
1578:      * Converts this instance into an UPDATE string in SQL.
1579:      *
1580:      * @return string
1581:      */
1582:     protected function getSqlForUpdate()
1583:     {
1584:         $query = 'UPDATE ' . $this->sqlParts['from']
1585:             . ' SET ' . implode(", ", $this->sqlParts['set'])
1586:             . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string)$this->sqlParts['where']) : '');
1587:         return $query;
1588:     }
1589: 
1590:     /**
1591:      * Converts this instance into a DELETE string in SQL.
1592:      *
1593:      * @return string
1594:      */
1595:     protected function getSqlForDelete()
1596:     {
1597:         return 'DELETE FROM ' . $this->sqlParts['from'] . ($this->sqlParts['where'] !== null ? ' WHERE ' . ((string)$this->sqlParts['where']) : '');
1598:     }
1599: 
1600:     /**
1601:      * Check if the offset exists
1602:      *
1603:      * @param  string $offset
1604:      * @return bool
1605:      */
1606:     public function offsetExists($offset)
1607:     {
1608:         $this->loadData($offset);
1609:         return isset($this->data[$offset]);
1610:     }
1611: 
1612:     /**
1613:      * Get the offset value
1614:      *
1615:      * @param  string $offset
1616:      * @return mixed
1617:      */
1618:     public function offsetGet($offset)
1619:     {
1620:         $this->loadData($offset);
1621:         return $this->get($offset);
1622:     }
1623: 
1624:     /**
1625:      * Set the offset value
1626:      *
1627:      * @param string $offset
1628:      * @param mixed $value
1629:      */
1630:     public function offsetSet($offset, $value)
1631:     {
1632:         $this->loadData($offset);
1633:         $this->set($offset, $value);
1634:     }
1635: 
1636:     /**
1637:      * Unset the offset
1638:      *
1639:      * @param string $offset
1640:      */
1641:     public function offsetUnset($offset)
1642:     {
1643:         $this->loadData($offset);
1644:         $this->remove($offset);
1645:     }
1646: 
1647:     /**
1648:      * Retrieve an array iterator
1649:      *
1650:      * @return \ArrayIterator
1651:      */
1652:     public function getIterator()
1653:     {
1654:         $this->loadData(0);
1655:         return new \ArrayIterator($this->data);
1656:     }
1657: 
1658:     /**
1659:      * Either appends to or replaces a single, generic query part.
1660:      *
1661:      * The available parts are: 'select', 'from', 'set', 'where',
1662:      * 'groupBy', 'having', 'orderBy', 'limit' and 'offset'.
1663:      *
1664:      * @param string $sqlPartName
1665:      * @param string $sqlPart
1666:      * @param boolean $append
1667:      * @param string $type
1668:      * @return $this
1669:      */
1670:     protected function add($sqlPartName, $sqlPart, $append = false, $type = null)
1671:     {
1672:         $this->isNew = false;
1673: 
1674:         if (!$sqlPart) {
1675:             return $this;
1676:         }
1677: 
1678:         $isArray = is_array($sqlPart);
1679:         $isMultiple = is_array($this->sqlParts[$sqlPartName]);
1680: 
1681:         if ($isMultiple && !$isArray) {
1682:             $sqlPart = array($sqlPart);
1683:         }
1684: 
1685:         $this->state = self::STATE_DIRTY;
1686: 
1687:         if ($append) {
1688:             if ($sqlPartName == 'where' || $sqlPartName == 'having') {
1689:                 if ($this->sqlParts[$sqlPartName]) {
1690:                     $this->sqlParts[$sqlPartName] = '(' . $this->sqlParts[$sqlPartName] . ') ' . $type . ' (' . $sqlPart . ')';
1691:                 } else {
1692:                     $this->sqlParts[$sqlPartName] = $sqlPart;
1693:                 }
1694:             } elseif ($sqlPartName == 'orderBy' || $sqlPartName == 'groupBy' || $sqlPartName == 'select' || $sqlPartName == 'set') {
1695:                 foreach ($sqlPart as $part) {
1696:                     $this->sqlParts[$sqlPartName][] = $part;
1697:                 }
1698:             } elseif ($isMultiple) {
1699:                 $this->sqlParts[$sqlPartName][] = $sqlPart;
1700:             }
1701:             return $this;
1702:         }
1703: 
1704:         $this->sqlParts[$sqlPartName] = $sqlPart;
1705:         return $this;
1706:     }
1707: 
1708:     /**
1709:      * Generate condition string for WHERE or Having statement
1710:      *
1711:      * @param mixed $conditions
1712:      * @param array $params
1713:      * @param array $types
1714:      * @return string
1715:      */
1716:     protected function processCondition($conditions, $params, $types)
1717:     {
1718:         // Regard numeric and null as primary key value
1719:         if (is_numeric($conditions) || empty($conditions)) {
1720:             $conditions = array($this->primaryKey => $conditions);
1721:         }
1722: 
1723:         if (is_array($conditions)) {
1724:             $where = array();
1725:             $params = array();
1726:             foreach ($conditions as $field => $condition) {
1727:                 if (is_array($condition)) {
1728:                     $where[] = $field . ' IN (' . implode(', ', array_pad(array(), count($condition), '?')) . ')';
1729:                     $params = array_merge($params, $condition);
1730:                 } else {
1731:                     $where[] = $field . " = ?";
1732:                     $params[] = $condition;
1733:                 }
1734:             }
1735:             $conditions = implode(' AND ', $where);
1736:         }
1737: 
1738:         if ($params !== false) {
1739:             if (is_array($params)) {
1740:                 $this->params = array_merge($this->params, $params);
1741:                 $this->paramTypes = array_merge($this->paramTypes, $types);
1742:             } else {
1743:                 $this->params[] = $params;
1744:                 if ($types) {
1745:                     $this->paramTypes[] = $types;
1746:                 }
1747:             }
1748:         }
1749: 
1750:         return $conditions;
1751:     }
1752: 
1753:     /**
1754:      * Load record by array offset
1755:      *
1756:      * @param int|string $offset
1757:      */
1758:     protected function loadData($offset)
1759:     {
1760:         if (!$this->loaded && !$this->isNew) {
1761:             if (is_numeric($offset) || is_null($offset)) {
1762:                 $this->findAll();
1763:             } else {
1764:                 $this->find();
1765:             }
1766:         }
1767:     }
1768: 
1769:     /**
1770:      * Filters elements of the collection using a callback function
1771:      *
1772:      * @param \Closure $fn
1773:      * @return $this
1774:      */
1775:     public function filter(\Closure $fn)
1776:     {
1777:         $data = array_filter($this->data, $fn);
1778:         return $this->db->init($this->table, $data, $this->isNew);
1779:     }
1780: 
1781:     /**
1782:      * Trigger a callback
1783:      *
1784:      * @param string $name
1785:      */
1786:     protected function triggerCallback($name)
1787:     {
1788:         $this->$name();
1789:         $this->$name && call_user_func($this->$name, $this, $this->wei);
1790:     }
1791: 
1792:     /**
1793:      * The method called after load a record
1794:      */
1795:     protected function afterLoad()
1796:     {
1797:     }
1798: 
1799:     /**
1800:      * The method called after find a record
1801:      */
1802:     protected function afterFind()
1803:     {
1804:     }
1805: 
1806:     /**
1807:      * The method called before save a record
1808:      */
1809:     public function beforeSave()
1810:     {
1811:     }
1812: 
1813:     /**
1814:      * The method called after save a record
1815:      */
1816:     public function afterSave()
1817:     {
1818:     }
1819: 
1820:     /**
1821:      * The method called before insert a record
1822:      */
1823:     public function beforeCreate()
1824:     {
1825:     }
1826: 
1827:     /**
1828:      * The method called after insert a record
1829:      */
1830:     public function afterCreate()
1831:     {
1832:     }
1833: 
1834:     /**
1835:      * The method called before update a record
1836:      */
1837:     public function beforeUpdate()
1838:     {
1839:     }
1840: 
1841:     /**
1842:      * The method called after update a record
1843:      */
1844:     public function afterUpdate()
1845:     {
1846:     }
1847: 
1848:     /**
1849:      * The method called before delete a record
1850:      */
1851:     public function beforeDestroy()
1852:     {
1853:     }
1854: 
1855:     /**
1856:      * The method called after delete a record
1857:      */
1858:     public function afterDestroy()
1859:     {
1860:     }
1861: }
1862: 
Wei Framework API documentation generated by ApiGen