PageRenderTime 28ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/solar/source/solar/Solar/Sql/Model.php

https://bitbucket.org/Sanakan/noise
PHP | 2664 lines | 1023 code | 266 blank | 1375 comment | 145 complexity | ba94ff6e5dd45cce2a55ffdade4dcc48 MD5 | raw file
Possible License(s): MIT

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. *
  4. * An SQL-centric Model class based on TableDataGateway, using Collection and
  5. * Record objects for returns, with integrated caching of versioned result
  6. * data.
  7. *
  8. * @category Solar
  9. *
  10. * @package Solar_Sql_Model An SQL-oriented ORM system using TableDataGateway
  11. * and DataMapper patterns.
  12. *
  13. * @author Paul M. Jones <pmjones@solarphp.com>
  14. *
  15. * @author Jeff Moore <jeff@procata.com>
  16. *
  17. * @license http://opensource.org/licenses/bsd-license.php BSD
  18. *
  19. * @version $Id: Model.php 4694 2010-09-06 14:33:18Z pmjones $
  20. *
  21. */
  22. abstract class Solar_Sql_Model extends Solar_Base
  23. {
  24. /**
  25. *
  26. * Default configuration values.
  27. *
  28. * @config dependency sql A Solar_Sql dependency.
  29. *
  30. * @config dependency cache A Solar_Cache dependency for the
  31. * Solar_Sql_Model_Cache object.
  32. *
  33. * @config dependency catalog A Solar_Sql_Model_Catalog to find other
  34. * models with.
  35. *
  36. * @config bool table_scan Connect to the database and scan the table for
  37. * its column descriptions, creating the table and indexes if not already
  38. * present.
  39. *
  40. * @config bool auto_cache Automatically maintain the data cache.
  41. *
  42. * @var array
  43. *
  44. */
  45. protected $_Solar_Sql_Model = array(
  46. 'catalog' => 'model_catalog',
  47. 'sql' => 'sql',
  48. 'cache' => array(
  49. 'adapter' => 'Solar_Cache_Adapter_None',
  50. ),
  51. 'table_scan' => true,
  52. 'auto_cache' => false,
  53. );
  54. /**
  55. *
  56. * The number of rows affected by the last INSERT, UPDATE, or DELETE.
  57. *
  58. * @var int
  59. *
  60. * @see getAffectedRows()
  61. *
  62. */
  63. protected $_affected_rows;
  64. /**
  65. *
  66. * A Solar_Sql_Model_Catalog dependency object.
  67. *
  68. * @var Solar_Sql_Model_Catalog
  69. *
  70. */
  71. protected $_catalog = null;
  72. /**
  73. *
  74. * A Solar_Sql dependency object.
  75. *
  76. * @var Solar_Sql_Adapter
  77. *
  78. */
  79. protected $_sql = null;
  80. /**
  81. *
  82. * A Solar_Sql_Model_Cache object.
  83. *
  84. * @var Solar_Sql_Model_Cache
  85. *
  86. */
  87. protected $_cache = null;
  88. /**
  89. *
  90. * The model name is the short form of the class name; this is generally
  91. * a plural.
  92. *
  93. * When inheritance is enabled, the default is the $_inherit_name value,
  94. * otherwise, the default is the $_table_name.
  95. *
  96. * @var string
  97. *
  98. */
  99. protected $_model_name;
  100. /**
  101. *
  102. * When a record from this model is part of an form element array, use
  103. * this name as the array key for it; by default, this is the singular
  104. * of the model name.
  105. *
  106. * @var string
  107. *
  108. */
  109. protected $_array_name;
  110. // -----------------------------------------------------------------
  111. //
  112. // Classes
  113. //
  114. // -----------------------------------------------------------------
  115. /**
  116. *
  117. * A Solar_Class_Stack object for fallback hierarchy.
  118. *
  119. * @var Solar_Class_Stack
  120. *
  121. */
  122. protected $_stack;
  123. /**
  124. *
  125. * The results of get_class($this) so we don't call get_class() all the
  126. * time.
  127. *
  128. * @var string
  129. *
  130. */
  131. protected $_class;
  132. /**
  133. *
  134. * The final fallback class for an individual record.
  135. *
  136. * Default is Solar_Sql_Model_Record.
  137. *
  138. * @var string
  139. *
  140. */
  141. protected $_record_class = 'Solar_Sql_Model_Record';
  142. /**
  143. *
  144. * A blank instance of the Record class for this model.
  145. *
  146. * We keep this so we don't keep looking for a record class once we know
  147. * what the proper class is. Not used when inheritance is in effect.
  148. *
  149. * @var Solar_Sql_Model_Record
  150. *
  151. */
  152. protected $_record_prototype;
  153. /**
  154. *
  155. * The final fallback class for collections of records.
  156. *
  157. * Default is Solar_Sql_Model_Collection.
  158. *
  159. * @var string
  160. *
  161. */
  162. protected $_collection_class = 'Solar_Sql_Model_Collection';
  163. /**
  164. *
  165. * A blank instance of the Collection class for this model.
  166. *
  167. * We keep this so we don't keep looking for a collection class once we
  168. * know what the proper class is.
  169. *
  170. * @var Solar_Sql_Model_Record
  171. *
  172. */
  173. protected $_collection_prototype;
  174. /**
  175. *
  176. * The class to use for building SELECT statements.
  177. *
  178. * @var string
  179. *
  180. */
  181. protected $_select_class = 'Solar_Sql_Select';
  182. /**
  183. *
  184. * The class to use for filter chains.
  185. *
  186. * @var string
  187. *
  188. */
  189. protected $_filter_class = null;
  190. /**
  191. *
  192. * The class to use for the cache object.
  193. *
  194. * @var string
  195. *
  196. * @see $_cache
  197. *
  198. */
  199. protected $_cache_class = 'Solar_Sql_Model_Cache';
  200. // -----------------------------------------------------------------
  201. //
  202. // Table and index definition
  203. //
  204. // -----------------------------------------------------------------
  205. /**
  206. *
  207. * The table name.
  208. *
  209. * @var string
  210. *
  211. */
  212. protected $_table_name = null;
  213. /**
  214. *
  215. * The column specification array for all columns in this table.
  216. *
  217. * Used in auto-creation, and for sync-checks.
  218. *
  219. * Will be overridden by _fixTableCols() when it reads the table info, so
  220. * you don't *have* to enter anything here ... but if it's empty, you
  221. * won't get auto-creation.
  222. *
  223. * Each element in this array looks like this...
  224. *
  225. * {{code: php
  226. * $_table_cols = array(
  227. * 'col_name' => array(
  228. * 'name' => (string) the col_name, same as the key
  229. * 'type' => (string) char, varchar, date, etc
  230. * 'size' => (int) column size
  231. * 'scope' => (int) decimal places
  232. * 'default' => (string) default value
  233. * 'require' => (bool) is this a required (non-null) column?
  234. * 'primary' => (bool) is this part of the primary key?
  235. * 'autoinc' => (bool) auto-incremented?
  236. * ),
  237. * );
  238. * }}
  239. *
  240. * @var array
  241. *
  242. */
  243. protected $_table_cols = array();
  244. /**
  245. *
  246. * The index specification array for all indexes on this table.
  247. *
  248. * Used only in auto-creation.
  249. *
  250. * The array should be in this format ...
  251. *
  252. * {{code: php
  253. * // the index type: 'normal' or 'unique'
  254. * $type = 'normal';
  255. *
  256. * // index on a single column:
  257. * // CREATE INDEX idx_name ON table_name (col_name)
  258. * $this->_index_info['idx_name'] = array(
  259. * 'type' => $type,
  260. * 'cols' => 'col_name'
  261. * );
  262. *
  263. * // index on multiple columns:
  264. * // CREATE INDEX idx_name ON table_name (col_1, col_2, ... col_N)
  265. * $this->_index_info['idx_name'] = array(
  266. * 'type' => $type,
  267. * 'cols' => array('col_1', 'col_2', ..., 'col_N')
  268. * );
  269. *
  270. * // easy shorthand for an index on a single column,
  271. * // giving the index the same name as the column:
  272. * // CREATE INDEX col_name ON table_name (col_name)
  273. * $this->_index_info['col_name'] = $type;
  274. * }}
  275. *
  276. * The $type may be 'normal' or 'unique'.
  277. *
  278. * @var array
  279. *
  280. */
  281. protected $_index_info = array();
  282. // -----------------------------------------------------------------
  283. //
  284. // Special columns and column behaviors
  285. //
  286. // -----------------------------------------------------------------
  287. /**
  288. *
  289. * A list of column names that don't exist in the table, but should be
  290. * calculated by the model as-needed.
  291. *
  292. * @var array
  293. *
  294. */
  295. protected $_calculate_cols = array();
  296. /**
  297. *
  298. * A list of column names that use sequence values.
  299. *
  300. * When the column is present in a data array, but its value is null,
  301. * a sequence value will automatically be added.
  302. *
  303. * @var array
  304. *
  305. */
  306. protected $_sequence_cols = array();
  307. /**
  308. *
  309. * A list of column names on which to apply serialize() and unserialize()
  310. * automatically.
  311. *
  312. * Will be unserialized by the Record class as the values are loaded,
  313. * then re-serialized just before insert/update in the Model class.
  314. *
  315. * @var array
  316. *
  317. * @see [[php::serialize() | ]]
  318. *
  319. * @see [[php::unserialize() | ]]
  320. *
  321. */
  322. protected $_serialize_cols = array();
  323. /**
  324. *
  325. * A list of column names storing XML strings to convert back and forth to
  326. * Solar_Struct_Xml objects.
  327. *
  328. * @var array
  329. *
  330. * @see $_xmlstruct_class
  331. *
  332. * @see Solar_Struct_Xml
  333. *
  334. */
  335. protected $_xmlstruct_cols = array();
  336. /**
  337. *
  338. * The class to use for $_xmlstruct_cols conversion objects.
  339. *
  340. * @var string
  341. *
  342. * @var array
  343. *
  344. * @see $_xmlstruct_cols
  345. *
  346. */
  347. protected $_xmlstruct_class = 'Solar_Struct_Xml';
  348. /**
  349. *
  350. * The column name for the primary key.
  351. *
  352. * @var string
  353. *
  354. */
  355. protected $_primary_col = null;
  356. /**
  357. *
  358. * The column name for 'created' timestamps; default is 'created'.
  359. *
  360. * @var string
  361. *
  362. */
  363. protected $_created_col = 'created';
  364. /**
  365. *
  366. * The column name for 'updated' timestamps; default is 'updated'.
  367. *
  368. * @var string
  369. *
  370. */
  371. protected $_updated_col = 'updated';
  372. /**
  373. *
  374. * Other models that relate to this model should use this as the
  375. * foreign-key column name.
  376. *
  377. * @var string
  378. *
  379. */
  380. protected $_foreign_col = null;
  381. // -----------------------------------------------------------------
  382. //
  383. // Other/misc
  384. //
  385. // -----------------------------------------------------------------
  386. /**
  387. *
  388. * Relationships to other Model classes.
  389. *
  390. * Keyed on a "virtual" column name, which will be used as a property
  391. * name in returned records.
  392. *
  393. * @var array
  394. *
  395. */
  396. protected $_related = array();
  397. /**
  398. *
  399. * Filters to validate and sanitize column data.
  400. *
  401. * Default is to use validate*() and sanitize*() methods in the filter
  402. * class, but if the method exists locally, it will be used instead.
  403. *
  404. * The filters apply only to Record objects from the model; if you use
  405. * the model insert() and update() methods directly, the filters are not
  406. * applied.
  407. *
  408. * Example usage follows; note that "_validate" and "_sanitize" refer
  409. * to internal (protected) filtering methods that have access to the
  410. * entire data set being filtered.
  411. *
  412. * {{code: php
  413. * // filter 'col_1' to have only alpha chars, with a max length of
  414. * // 32 chars
  415. * $this->_filters['col_1'][] = 'sanitizeStringAlpha';
  416. * $this->_filters['col_1'][] = array('validateMaxLength', 32);
  417. *
  418. * // filter 'col_2' to have only numeric chars, validate as an
  419. * // integer, in a range of -10 to +10.
  420. * $this->_filters['col_2'][] = 'sanitizeNumeric';
  421. * $this->_filters['col_2'][] = 'validateInteger';
  422. * $this->_filters['col_2'][] = array('validateRange', -10, +10);
  423. *
  424. * // filter 'handle' to have only alpha-numeric chars, with a length
  425. * // of 6-14 chars, and unique in the table.
  426. * $this->_filters['handle'][] = 'sanitizeStringAlnum';
  427. * $this->_filters['handle'][] = array('validateRangeLength', 6, 14);
  428. * $this->_filters['handle'][] = 'validateUnique';
  429. *
  430. * // filter 'email' to have only emails-allowed chars, validate as an
  431. * // email address, and be unique in the table.
  432. * $this->_filters['email'][] = 'sanitizeStringEmail';
  433. * $this->_filters['email'][] = 'validateEmail';
  434. * $this->_filters['email'][] = 'validateUnique';
  435. *
  436. * // filter 'passwd' to be not-blank, and should match any existing
  437. * // 'passwd_confirm' value.
  438. * $this->_filters['passwd'][] = 'validateNotBlank';
  439. * $this->_filters['passwd'][] = 'validateConfirm';
  440. * }}
  441. *
  442. * @var array
  443. *
  444. * @see $_filter_class
  445. *
  446. * @see _addFilter()
  447. *
  448. */
  449. protected $_filters;
  450. // -----------------------------------------------------------------
  451. //
  452. // Single-table inheritance
  453. //
  454. // -----------------------------------------------------------------
  455. /**
  456. *
  457. * The base model this class is inherited from, in single-table
  458. * inheritance.
  459. *
  460. * @var string
  461. *
  462. */
  463. protected $_inherit_base = null;
  464. /**
  465. *
  466. * When inheritance is turned on, the class name value for this class
  467. * in $_inherit_col.
  468. *
  469. * @var string
  470. *
  471. */
  472. protected $_inherit_name = false;
  473. /**
  474. *
  475. * The column name that tracks single-table inheritance; default is
  476. * 'inherit'.
  477. *
  478. * @var string
  479. *
  480. */
  481. protected $_inherit_col = 'inherit';
  482. // -----------------------------------------------------------------
  483. //
  484. // Select options
  485. //
  486. // -----------------------------------------------------------------
  487. /**
  488. *
  489. * Only fetch these columns from the table.
  490. *
  491. * @var array
  492. *
  493. */
  494. protected $_fetch_cols;
  495. /**
  496. *
  497. * By default, order by this column when fetching rows.
  498. *
  499. * @var array
  500. *
  501. */
  502. protected $_order;
  503. /**
  504. *
  505. * The default number of rows per page when selecting.
  506. *
  507. * @var int
  508. *
  509. */
  510. protected $_paging = 10;
  511. /**
  512. *
  513. * The registered Solar_Inflect object.
  514. *
  515. * @var Solar_Inflect
  516. *
  517. */
  518. protected $_inflect;
  519. // -----------------------------------------------------------------
  520. //
  521. // Constructor and magic methods
  522. //
  523. // -----------------------------------------------------------------
  524. /**
  525. *
  526. * Post-construction tasks to complete object construction.
  527. *
  528. * @return void
  529. *
  530. */
  531. protected function _postConstruct()
  532. {
  533. parent::_postConstruct();
  534. // Establish the state of this object before _setup
  535. $this->_preSetup();
  536. // user-defined setup
  537. $this->_setup();
  538. // Complete the setup of this model
  539. $this->_postSetup();
  540. }
  541. /**
  542. *
  543. * Call this before you unset the instance so that you release the memory
  544. * from all the internal child objects.
  545. *
  546. * @return void
  547. *
  548. */
  549. public function free()
  550. {
  551. foreach ($this->_related as $key => $val) {
  552. unset($this->_related[$key]);
  553. }
  554. unset($this->_cache);
  555. }
  556. /**
  557. *
  558. * User-defined setup.
  559. *
  560. * @return void
  561. *
  562. */
  563. protected function _setup()
  564. {
  565. }
  566. // -----------------------------------------------------------------
  567. //
  568. // Getters and setters
  569. //
  570. // -----------------------------------------------------------------
  571. /**
  572. *
  573. * Read-only access to protected model properties.
  574. *
  575. * @param string $key The requested property; e.g., `'foo'` will read from
  576. * `$_foo`.
  577. *
  578. * @return mixed
  579. *
  580. */
  581. public function __get($key)
  582. {
  583. $var = "_$key";
  584. if (property_exists($this, $var)) {
  585. return $this->$var;
  586. } else {
  587. throw $this->_exception('ERR_NO_SUCH_PROPERTY', array(
  588. 'class' => get_class($this),
  589. 'property' => $key,
  590. ));
  591. }
  592. }
  593. /**
  594. *
  595. * Gets the number of records per page.
  596. *
  597. * @return int The number of records per page.
  598. *
  599. */
  600. public function getPaging()
  601. {
  602. return $this->_paging;
  603. }
  604. /**
  605. *
  606. * Sets the number of records per page.
  607. *
  608. * @param int $paging The number of records per page.
  609. *
  610. * @return void
  611. *
  612. */
  613. public function setPaging($paging)
  614. {
  615. $this->_paging = (int) $paging;
  616. }
  617. /**
  618. *
  619. * Returns the fully-qualified primary key name.
  620. *
  621. * @return string
  622. *
  623. */
  624. public function getPrimary()
  625. {
  626. return "{$this->_model_name}.{$this->_primary_col}";
  627. }
  628. /**
  629. *
  630. * Returns the number of rows affected by the last INSERT, UPDATE, or
  631. * DELETE.
  632. *
  633. * @return int
  634. *
  635. */
  636. public function getAffectedRows()
  637. {
  638. return $this->_affected_rows;
  639. }
  640. // -----------------------------------------------------------------
  641. //
  642. // Fetch
  643. //
  644. // -----------------------------------------------------------------
  645. /**
  646. *
  647. * Magic call implements "fetchOneBy...()" and "fetchAllBy...()" for
  648. * columns listed in the method name.
  649. *
  650. * You *have* to specify params for all of the named columns.
  651. *
  652. * Optionally, you can pass a final array for the "extra" paramters to the
  653. * fetch ('order', 'group', 'having', etc.)
  654. *
  655. * Example:
  656. *
  657. * {{code: php
  658. * // fetches one record by status
  659. * $model->fetchOneByStatus('draft');
  660. *
  661. * // fetches all records by area_id and owner_handle
  662. * $model->fetchAllByAreaIdAndOwnerHandle($area_id, $owner_handle);
  663. *
  664. * // fetches all records by area_id and owner_handle,
  665. * // with ordering and page-limiting
  666. * $extra = array('order' => 'area_id DESC', 'page' => 2);
  667. * $model->fetchAllByAreaIdAndOwnerHandle($area_id, $owner_handle, $extra);
  668. * }}
  669. *
  670. * @param string $method The virtual method name, composed of "fetchOneBy"
  671. * or "fetchAllBy", with a series of column names joined by "And".
  672. *
  673. * @param array $params Parameters to pass to the method: one for each
  674. * column, plus an optional one for extra fetch parameters.
  675. *
  676. * @return mixed
  677. *
  678. * @todo Expand to cover assoc, col, pairs, and value.
  679. *
  680. */
  681. public function __call($method, $params)
  682. {
  683. // fetch a record, or a collection?
  684. if (substr($method, 0, 7) == 'fetchBy') {
  685. // fetch a record
  686. $fetch = 'fetchOne';
  687. $method = substr($method, 7);
  688. } elseif (substr($method, 0, 10) == 'fetchOneBy') {
  689. // fetch a record
  690. $fetch = 'fetchOne';
  691. $method = substr($method, 10);
  692. } elseif (substr($method, 0, 10) == 'fetchAllBy') {
  693. // fetch a collection
  694. $fetch = 'fetchAll';
  695. $method = substr($method, 10);
  696. } else {
  697. throw $this->_exception('ERR_METHOD_NOT_IMPLEMENTED', array(
  698. 'method' => $method,
  699. ));
  700. }
  701. // get the list of columns from the remainder of the method name
  702. // e.g., fetchAllByParentIdAndAreaId => ParentId, AreaId
  703. $list = explode('And', $method);
  704. // build the fetch params
  705. $where = array();
  706. foreach ($list as $key => $col) {
  707. // convert from ColName to col_name
  708. $col = strtolower(
  709. $this->_inflect->camelToUnder($col)
  710. );
  711. $where["{$this->_model_name}.$col = ?"] = $params[$key];
  712. }
  713. // add the last param after last column name as the "extra" settings
  714. // (order, group, having, page, paging, etc).
  715. $k = count($list);
  716. if (count($params) > $k) {
  717. $opts = (array) $params[$k];
  718. } else {
  719. $opts = array();
  720. }
  721. // merge the where with the base fetch params
  722. $opts = array_merge($opts, array(
  723. 'where' => $where,
  724. ));
  725. // do the fetch
  726. return $this->$fetch($opts);
  727. }
  728. /**
  729. *
  730. * Fetches a record or collection by primary key value(s).
  731. *
  732. * @param int|array $spec The primary key value for a single record, or an
  733. * array of primary key values for a collection of records.
  734. *
  735. * @param array $fetch An array of parameters for the fetch, with keys
  736. * for 'cols', 'group', 'having', 'order', etc. Note that the 'where'
  737. * and 'order' elements are overridden and have no effect.
  738. *
  739. * @return Solar_Sql_Model_Record|Solar_Sql_Model_Collection A record or
  740. * record-set object.
  741. *
  742. */
  743. public function fetch($spec, $fetch = null)
  744. {
  745. $col = "{$this->_model_name}.{$this->_primary_col}";
  746. if (is_array($spec)) {
  747. $fetch['where'] = array("$col IN (?)" => $spec);
  748. $fetch['order'] = $col;
  749. return $this->fetchAll($fetch);
  750. } else {
  751. $fetch['where'] = array("$col = ?" => $spec);
  752. $fetch['order'] = $col;
  753. return $this->fetchOne($fetch);
  754. }
  755. }
  756. /**
  757. *
  758. * Fetches a collection of all records by arbitrary parameters.
  759. *
  760. * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
  761. * fetch.
  762. *
  763. * @return Solar_Sql_Model_Collection A collection object.
  764. *
  765. */
  766. public function fetchAll($fetch = null)
  767. {
  768. // fetch the result array and select object
  769. $fetch = $this->_fixFetchParams($fetch);
  770. list($result, $select) = $this->_fetchResultSelect('all', $fetch);
  771. if (! $result) {
  772. return array();
  773. }
  774. // create a collection from the result
  775. $coll = $this->newCollection($result);
  776. // add pager-info to the collection
  777. if ($fetch['count_pages']) {
  778. $this->_setCollectionPagerInfo($coll, $fetch);
  779. }
  780. // done
  781. return $coll;
  782. }
  783. /**
  784. *
  785. * Fetches an array of rows by arbitrary parameters.
  786. *
  787. * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
  788. * fetch.
  789. *
  790. * @return array
  791. *
  792. */
  793. public function fetchAllAsArray($fetch = null)
  794. {
  795. // fetch the result array and select object
  796. $fetch = $this->_fixFetchParams($fetch);
  797. list($result, $select) = $this->_fetchResultSelect('all', $fetch);
  798. if (! $result) {
  799. return array();
  800. } else {
  801. return $result;
  802. }
  803. }
  804. /**
  805. *
  806. * The same as fetchAll(), except the record collection is keyed on the
  807. * first column of the results (instead of being a strictly sequential
  808. * array.)
  809. *
  810. * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
  811. * fetch.
  812. *
  813. * @return Solar_Sql_Model_Collection A collection object.
  814. *
  815. */
  816. public function fetchAssoc($fetch = null)
  817. {
  818. // fetch the result array and select object
  819. $fetch = $this->_fixFetchParams($fetch);
  820. list($result, $select) = $this->_fetchResultSelect('assoc', $fetch);
  821. if (! $result) {
  822. return array();
  823. }
  824. // create a collection from the result
  825. $coll = $this->newCollection($result);
  826. // add pager-info to the collection
  827. if ($fetch['count_pages']) {
  828. $this->_setCollectionPagerInfo($coll, $fetch);
  829. }
  830. // done
  831. return $coll;
  832. }
  833. /**
  834. *
  835. * The same as fetchAssoc(), except it returns an array, not a collection.
  836. *
  837. * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
  838. * fetch.
  839. *
  840. * @return array An array of rows.
  841. *
  842. */
  843. public function fetchAssocAsArray($fetch = null)
  844. {
  845. // fetch the result array and select object
  846. $fetch = $this->_fixFetchParams($fetch);
  847. list($result, $select) = $this->_fetchResultSelect('assoc', $fetch);
  848. if (! $result) {
  849. return array();
  850. } else {
  851. return $result;
  852. }
  853. }
  854. /**
  855. *
  856. * Sets the pager info in a collection, calling countPages() along the
  857. * way.
  858. *
  859. * @param Solar_Sql_Model_Collection $coll The record collection to set
  860. * pager info on.
  861. *
  862. * @param array $fetch The params for the original fetchAll() or
  863. * fetchAssoc().
  864. *
  865. * @return void
  866. */
  867. protected function _setCollectionPagerInfo($coll, $fetch)
  868. {
  869. $total = $this->countPages($fetch);
  870. $info = array(
  871. 'count' => (int) $total['count'],
  872. 'pages' => (int) $total['pages'],
  873. 'paging' => (int) $fetch['paging'],
  874. );
  875. if (! $info['count']) {
  876. $info['page'] = 0;
  877. $info['begin'] = 0;
  878. $info['end'] = 0;
  879. } elseif (! $fetch['page']) {
  880. $info['page'] = 1;
  881. $info['begin'] = 1;
  882. $info['end'] = $info['count'];
  883. } else {
  884. $start = (int) ($fetch['page'] - 1) * $fetch['paging'];
  885. $info['page'] = $fetch['page'];
  886. $info['begin'] = $start + 1;
  887. $info['end'] = $start + $info['count'];
  888. }
  889. $info['is_first'] = (bool) ($info['page'] == 1);
  890. $info['is_last'] = (bool) ($info['end'] == $info['count']);
  891. $coll->setPagerInfo($info);
  892. }
  893. /**
  894. *
  895. * Fetches one record by arbitrary parameters.
  896. *
  897. * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
  898. * fetch.
  899. *
  900. * @return Solar_Sql_Model_Record A record object.
  901. *
  902. */
  903. public function fetchOne($fetch = null)
  904. {
  905. // fetch the result array and select object
  906. $fetch = $this->_fixFetchParams($fetch);
  907. list($result, $select) = $this->_fetchResultSelect('one', $fetch);
  908. if (! $result) {
  909. return null;
  910. }
  911. // get the main record, which sets the to-one data
  912. $record = $this->newRecord($result);
  913. // done
  914. return $record;
  915. }
  916. /**
  917. *
  918. * The same as fetchOne(), but returns an array instead of a record object.
  919. *
  920. * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
  921. * fetch.
  922. *
  923. * @return array
  924. *
  925. */
  926. public function fetchOneAsArray($fetch = null)
  927. {
  928. // fetch the result array and select object
  929. $fetch = $this->_fixFetchParams($fetch);
  930. list($result, $select) = $this->_fetchResultSelect('one', $fetch);
  931. if (! $result) {
  932. return array();
  933. } else {
  934. return $result;
  935. }
  936. }
  937. /**
  938. *
  939. * Fetches a sequential array of values from the model, using only the
  940. * first column of the results.
  941. *
  942. * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
  943. * fetch.
  944. *
  945. * @return array
  946. *
  947. */
  948. public function fetchCol($fetch = null)
  949. {
  950. // fetch the result array and select object
  951. $fetch = $this->_fixFetchParams($fetch);
  952. list($result, $select) = $this->_fetchResultSelect('col', $fetch);
  953. if ($result) {
  954. return $result;
  955. } else {
  956. return array();
  957. }
  958. }
  959. /**
  960. *
  961. * Fetches an array of key-value pairs from the model, where the first
  962. * column is the key and the second column is the value.
  963. *
  964. * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
  965. * fetch.
  966. *
  967. * @return array
  968. *
  969. */
  970. public function fetchPairs($fetch = null)
  971. {
  972. // fetch the result array and select object
  973. $fetch = $this->_fixFetchParams($fetch);
  974. list($result, $select) = $this->_fetchResultSelect('pairs', $fetch);
  975. if ($result) {
  976. return $result;
  977. } else {
  978. return array();
  979. }
  980. }
  981. /**
  982. *
  983. * Fetches a single value from the model (i.e., the first column of the
  984. * first record of the returned page set).
  985. *
  986. * @param array|Solar_Sql_Model_Params_Fetch $fetch Parameters for the
  987. * fetch.
  988. *
  989. * @return mixed The single value from the model query, or null.
  990. *
  991. */
  992. public function fetchValue($fetch = null)
  993. {
  994. // fetch the result array and select object
  995. $fetch = $this->_fixFetchParams($fetch);
  996. list($result, $select) = $this->_fetchResultSelect('value', $fetch);
  997. return $result;
  998. }
  999. /**
  1000. *
  1001. * Returns a data result and the select used to fetch the data.
  1002. *
  1003. * If caching is turned on, this will fetch from the cache (if available)
  1004. * and save the result back to the cache (if needed).
  1005. *
  1006. * @param string $type The type of fetch to perform: 'all', 'one', etc.
  1007. *
  1008. * @param Solar_Sql_Model_Params_Fetch $fetch The params for the fetch.
  1009. *
  1010. * @return array An array of two elements; element 0 is the result data,
  1011. * element 1 is the Solar_Sql_Select object used to fetch the data.
  1012. *
  1013. */
  1014. protected function _fetchResultSelect($type, Solar_Sql_Model_Params_Fetch $fetch)
  1015. {
  1016. $select = $this->newSelect($fetch);
  1017. // attempt to fetch from cache?
  1018. if ($fetch['cache']) {
  1019. $key = $this->_cache->entry($fetch);
  1020. $result = $this->_cache->fetch($key);
  1021. if ($result !== false) {
  1022. // found some data!
  1023. return array($result, $select);
  1024. }
  1025. }
  1026. // attempt to fetch from database
  1027. $result = $select->fetch($type);
  1028. // now process the results through the eagers
  1029. foreach ($fetch['eager'] as $name => $eager) {
  1030. $related = $this->getRelated($name);
  1031. $related->modEagerResult($eager, $result, $type, $fetch);
  1032. }
  1033. // add to cache?
  1034. if ($fetch['cache']) {
  1035. $this->_cache->add($key, $result);
  1036. }
  1037. // done
  1038. return array($result, $select);
  1039. }
  1040. /**
  1041. *
  1042. * Returns a new record with default values.
  1043. *
  1044. * @param array $spec An array of user-specified data to place into the
  1045. * new record, if any.
  1046. *
  1047. * @return Solar_Sql_Model_Record A record object.
  1048. *
  1049. */
  1050. public function fetchNew($spec = null)
  1051. {
  1052. $record = $this->_newRecord();
  1053. $data = $this->_fetchNewData($spec);
  1054. $record->initNew($this, $data);
  1055. return $record;
  1056. }
  1057. /**
  1058. *
  1059. * Support method to generate the data for a new, blank record.
  1060. *
  1061. * @param array $spec An array of user-specified data to place into the
  1062. * new record, if any.
  1063. *
  1064. * @return array An array of data for loading into a a new, blank record.
  1065. *
  1066. */
  1067. protected function _fetchNewData($spec = null)
  1068. {
  1069. // the user-specifed data
  1070. settype($spec, 'array');
  1071. // the array of data for the record
  1072. $data = array();
  1073. // loop through each table column and collect default data
  1074. foreach ($this->_table_cols as $key => $val) {
  1075. if (array_key_exists($key, $spec)) {
  1076. // user-specified
  1077. $data[$key] = $spec[$key];
  1078. } else {
  1079. // default value
  1080. $data[$key] = $val['default'];
  1081. }
  1082. }
  1083. // loop through each calculate column and collect default data
  1084. foreach ($this->_calculate_cols as $key => $val) {
  1085. if (array_key_exists($key, $spec)) {
  1086. // user-specified
  1087. $data[$key] = $spec[$key];
  1088. } else {
  1089. // default value
  1090. $data[$key] = $val['default'];
  1091. }
  1092. }
  1093. // add Solar_Xml_Struct objects
  1094. foreach ($this->_xmlstruct_cols as $key) {
  1095. $data[$key] = Solar::factory($this->_xmlstruct_class);
  1096. }
  1097. // if we have inheritance, set that too
  1098. if ($this->_inherit_name) {
  1099. $key = $this->_inherit_col;
  1100. $data[$key] = $this->_inherit_name;
  1101. }
  1102. // done
  1103. return $data;
  1104. }
  1105. /**
  1106. *
  1107. * Fetches count and pages of available records.
  1108. *
  1109. * @param array $fetch An array of clauses for the SELECT COUNT()
  1110. * statement, including 'where', 'group, and 'having'.
  1111. *
  1112. * @return array An array with keys 'count' and 'pages'; 'count' is the
  1113. * number of records, 'pages' is the number of pages.
  1114. *
  1115. */
  1116. public function countPages($fetch = null)
  1117. {
  1118. // fix up the parameters
  1119. $fetch = $this->_fixFetchParams($fetch);
  1120. // add a fake param called 'count' to make this different from the
  1121. // orginating query (for cache deconfliction).
  1122. $fetch['__count__'] = true;
  1123. // check the cache
  1124. if ($fetch['cache']) {
  1125. $key = $this->_cache->entry($fetch);
  1126. $result = $this->_cache->fetch($key);
  1127. if ($result !== false) {
  1128. // cache hit
  1129. return $result;
  1130. }
  1131. }
  1132. // clone the fetch for only the "keep" joins
  1133. $clone = $fetch->cloneForKeeps();
  1134. // get the base select
  1135. $select = $this->newSelect($clone);
  1136. // count on the primary column
  1137. $col = "{$this->_model_name}.{$this->_primary_col}";
  1138. $result = $select->countPages($col);
  1139. // save in cache?
  1140. if ($fetch['cache']) {
  1141. $this->_cache->add($key, $result);
  1142. }
  1143. // done
  1144. return $result;
  1145. }
  1146. // -----------------------------------------------------------------
  1147. //
  1148. // Select
  1149. //
  1150. // -----------------------------------------------------------------
  1151. /**
  1152. *
  1153. * Converts and cleans-up fetch params from arrays to instances of
  1154. * Solar_Sql_Model_Params_Fetch.
  1155. *
  1156. * @param array $spec The parameters for the fetch.
  1157. *
  1158. * @return Solar_Sql_Model_Params_Fetch
  1159. *
  1160. */
  1161. protected function _fixFetchParams($spec)
  1162. {
  1163. if ($spec instanceof Solar_Sql_Model_Params_Fetch) {
  1164. // already a params object, pre-empt further modification
  1165. return $spec;
  1166. }
  1167. // baseline object
  1168. $fetch = Solar::factory('Solar_Sql_Model_Params_Fetch');
  1169. // defaults
  1170. $fetch->load(array(
  1171. 'cache' => $this->_config['auto_cache'],
  1172. 'paging' => $this->_paging,
  1173. 'alias' => $this->_model_name,
  1174. ));
  1175. // user specification
  1176. $fetch->load($spec);
  1177. // add columns if none already specified
  1178. if (! $fetch['cols']) {
  1179. $fetch->cols($this->_fetch_cols);
  1180. }
  1181. // done
  1182. return $fetch;
  1183. }
  1184. /**
  1185. *
  1186. * Returns a WHERE clause array of conditions to use when fetching
  1187. * from this model; e.g., single-table inheritance.
  1188. *
  1189. * @param array $where The WHERE array being modified.
  1190. *
  1191. * @param string $alias The current name of the table for this model
  1192. * in the query being constructed; defaults to the model name.
  1193. *
  1194. * @return array The modified WHERE array.
  1195. *
  1196. */
  1197. public function getConditions($alias = null)
  1198. {
  1199. // default to the model name for the alias
  1200. if (! $alias) {
  1201. $alias = $this->_model_name;
  1202. }
  1203. // the array of where clauses
  1204. $where = array();
  1205. // is inheritance on?
  1206. if ($this->isInherit()) {
  1207. $key = "{$alias}.{$this->_inherit_col} = ?";
  1208. $val = $this->_inherit_name;
  1209. $where = array($key => $val);
  1210. }
  1211. // done!
  1212. return $where;
  1213. }
  1214. /**
  1215. *
  1216. * Returns a new Solar_Sql_Select tool, with the proper SQL object
  1217. * injected automatically.
  1218. *
  1219. * @param Solar_Sql_Model_Params_Fetch|array $fetch Parameters for the
  1220. * fetch.
  1221. *
  1222. * @return Solar_Sql_Select
  1223. *
  1224. */
  1225. public function newSelect($fetch = null)
  1226. {
  1227. $fetch = $this->_fixFetchParams($fetch);
  1228. if (! $fetch['alias']) {
  1229. $fetch->alias($this->_model_name);
  1230. }
  1231. foreach ($fetch['eager'] as $name => $eager) {
  1232. $related = $this->getRelated($name);
  1233. $related->modEagerFetch($eager, $fetch);
  1234. }
  1235. $use_default_order = ! $fetch['order'] && $fetch['order'] !== false;
  1236. if ($use_default_order && $this->_order) {
  1237. $fetch->order("{$fetch['alias']}.{$this->_order}");
  1238. };
  1239. // get the select object
  1240. $select = Solar::factory(
  1241. $this->_select_class,
  1242. array('sql' => $this->_sql)
  1243. );
  1244. // add the explicitly asked-for columns before the eager-join cols.
  1245. // this is to make sure the fetchPairs() method works right, because
  1246. // adding the eager columns first will mess that up.
  1247. $select->from(
  1248. "{$this->_table_name} AS {$fetch['alias']}",
  1249. $fetch['cols']
  1250. );
  1251. $select->multiWhere($this->getConditions($fetch['alias']));
  1252. // all the other pieces
  1253. $select->distinct($fetch['distinct'])
  1254. ->multiJoin($fetch['join'])
  1255. ->multiWhere($fetch['where'])
  1256. ->group($fetch['group'])
  1257. ->multiHaving($fetch['having'])
  1258. ->order($fetch['order'])
  1259. ->setPaging($fetch['paging'])
  1260. ->bind($fetch['bind']);
  1261. // limit by count/offset, or by page?
  1262. if ($fetch['limit']) {
  1263. list($count, $offset) = $fetch['limit'];
  1264. $select->limit($count, $offset);
  1265. } else {
  1266. $select->limitPage($fetch['page']);
  1267. }
  1268. // done!
  1269. return $select;
  1270. }
  1271. // -----------------------------------------------------------------
  1272. //
  1273. // Record and Collection factories
  1274. //
  1275. // -----------------------------------------------------------------
  1276. /**
  1277. *
  1278. * Returns the appropriate record object, honoring inheritance.
  1279. *
  1280. * @param array $data The data to load into the record.
  1281. *
  1282. * @return Solar_Sql_Model_Record A record object.
  1283. *
  1284. */
  1285. public function newRecord($data)
  1286. {
  1287. // the record to return, eventually
  1288. $record = null;
  1289. // look for an inheritance in relation to $data
  1290. $inherit = null;
  1291. if ($this->_inherit_col && ! empty($data[$this->_inherit_col])) {
  1292. // inheritance is available, and a value is set for the
  1293. // inheritance column in the data
  1294. $inherit = trim($data[$this->_inherit_col]);
  1295. }
  1296. // did we find an inheritance value?
  1297. if ($inherit) {
  1298. // try to find a model class based on inheritance, going up the
  1299. // stack as needed. this checks for Current_Model_Type,
  1300. // Parent_Model_Type, Grandparent_Model_Type, etc.
  1301. //
  1302. // blow up if we can't find it, since this is explicitly noted
  1303. // as the inheritance class.
  1304. $inherit_class = $this->_catalog->getClass($inherit);
  1305. // if different from the current class, reset the model object.
  1306. if ($inherit_class != $this->_class) {
  1307. // use the inherited model class, it's different from the
  1308. // current model. if it's not different, fall through, leaving
  1309. // $record == null. that will invoke the logic below.
  1310. $model = $this->_catalog->getModelByClass($inherit_class);
  1311. $record = $model->newRecord($data);
  1312. }
  1313. }
  1314. // do we have a record yet?
  1315. if (! $record) {
  1316. // no, because an inheritance model was not specified, or was of
  1317. // the same class as this class.
  1318. $record = $this->_newRecord();
  1319. $record->init($this, $data);
  1320. }
  1321. return $record;
  1322. }
  1323. /**
  1324. *
  1325. * Returns a new record object for this model only.
  1326. *
  1327. * @return Solar_Sql_Model_Record A record object.
  1328. *
  1329. */
  1330. protected function _newRecord()
  1331. {
  1332. if (empty($this->_record_prototype)) {
  1333. // find the record class
  1334. $record_class = $this->_stack->load('Record', false);
  1335. if (! $record_class) {
  1336. // use the default record class
  1337. $record_class = $this->_record_class;
  1338. }
  1339. $this->_record_prototype = Solar::factory($record_class);
  1340. }
  1341. $record = clone $this->_record_prototype;
  1342. return $record;
  1343. }
  1344. /**
  1345. *
  1346. * Returns the appropriate collection object for this model.
  1347. *
  1348. * @param array $data The data to load into the collection, if any.
  1349. *
  1350. * @return Solar_Sql_Model_Collection A collection object.
  1351. *
  1352. */
  1353. public function newCollection($data = null)
  1354. {
  1355. $collection = $this->_newCollection();
  1356. $collection->setModel($this);
  1357. $collection->load($data);
  1358. return $collection;
  1359. }
  1360. /**
  1361. *
  1362. * Returns a new collection object for this model only.
  1363. *
  1364. * @return Solar_Sql_Model_Collection A collection object.
  1365. *
  1366. */
  1367. protected function _newCollection()
  1368. {
  1369. if (empty($this->_collection_prototype)) {
  1370. // find the collection class
  1371. $collection_class = $this->_stack->load('Collection', false);
  1372. if (! $collection_class) {
  1373. // use the default collection class
  1374. $collection_class = $this->_collection_class;
  1375. }
  1376. $this->_collection_prototype = Solar::factory($collection_class);
  1377. }
  1378. $collection = clone $this->_collection_prototype;
  1379. return $collection;
  1380. }
  1381. // -----------------------------------------------------------------
  1382. //
  1383. // Insert, update, or delete rows in the model.
  1384. //
  1385. // -----------------------------------------------------------------
  1386. /**
  1387. *
  1388. * Inserts one row to the model table and deletes cache entries.
  1389. *
  1390. * @param array $data The row data to insert.
  1391. *
  1392. * @return int|bool On success, the last inserted ID if there is an
  1393. * auto-increment column on the model (otherwise boolean true). On failure
  1394. * an exception from PDO bubbles up.
  1395. *
  1396. * @throws Solar_Sql_Exception on failure of any sort.
  1397. *
  1398. * @see Solar_Sql_Model_Cache::deleteAll()
  1399. *
  1400. */
  1401. public function insert($data)
  1402. {
  1403. if (! is_array($data)) {
  1404. throw $this->_exception('ERR_DATA_NOT_ARRAY', array(
  1405. 'method' => 'insert',
  1406. ));
  1407. }
  1408. // reset affected rows
  1409. $this->_affected_rows;
  1410. // remove non-existent table columns from the data
  1411. foreach ($data as $key => $val) {
  1412. if (empty($this->_table_cols[$key])) {
  1413. unset($data[$key]);
  1414. // not in the table, so no need to check for autoinc
  1415. continue;
  1416. }
  1417. // remove empty autoinc columns to soothe postgres, which won't
  1418. // take explicit NULLs in SERIAL cols.
  1419. if ($this->_table_cols[$key]['autoinc'] && empty($val)) {
  1420. unset($data[$key]);
  1421. }
  1422. }
  1423. // perform the insert and track affected rows
  1424. $this->_affected_rows = $this->_sql->insert(
  1425. $this->_table_name,
  1426. $data
  1427. );
  1428. // does the table have an autoincrement column?
  1429. $autoinc = null;
  1430. foreach ($this->_table_cols as $name => $info) {
  1431. if ($info['autoinc']) {
  1432. $autoinc = $name;
  1433. break;
  1434. }
  1435. }
  1436. // return the last insert id, or just "true" ?
  1437. if ($autoinc) {
  1438. $id = $this->_sql->lastInsertId($this->_table_name, $autoinc);
  1439. }
  1440. // clear the cache for this model and related models
  1441. $this->_cache->deleteAll();
  1442. if ($autoinc) {
  1443. return $id;
  1444. } else {
  1445. return true;
  1446. }
  1447. }
  1448. /**
  1449. *
  1450. * Updates rows in the model table and deletes cache entries.
  1451. *
  1452. * @param array $data The row data to insert.
  1453. *
  1454. * @param string|array $where The WHERE clause to identify which rows to
  1455. * update.
  1456. *
  1457. * @return int The number of rows affected.
  1458. *
  1459. * @throws Solar_Sql_Exception on failure of any sort.
  1460. *
  1461. * @see Solar_Sql_Model_Cache::deleteAll()
  1462. *
  1463. */
  1464. public function update($data, $where)
  1465. {
  1466. if (! is_array($data)) {
  1467. throw $this->_exception('ERR_DATA_NOT_ARRAY', array(
  1468. 'method' => 'update',
  1469. ));
  1470. }
  1471. // reset affected rows
  1472. $this->_affected_rows = null;
  1473. // don't update the primary key
  1474. unset($data[$this->_primary_col]);
  1475. // remove non-existent table columns from the data
  1476. foreach ($data as $key => $val) {
  1477. if (empty($this->_table_cols[$key])) {
  1478. unset($data[$key]);
  1479. }
  1480. }
  1481. // perform the update and track affected rows
  1482. $this->_affected_rows = $this->_sql->update(
  1483. $this->_table_name,
  1484. $data,
  1485. $where
  1486. );
  1487. // clear the cache for this model and related models
  1488. $this->_cache->deleteAll();
  1489. // done!
  1490. return $this->_affected_rows;
  1491. }
  1492. /**
  1493. *
  1494. * Deletes rows from the model table and deletes cache entries.
  1495. *
  1496. * @param string|array $where The WHERE clause to identify which rows to
  1497. * delete.
  1498. *
  1499. * @return int The number of rows affected.
  1500. *
  1501. * @see Solar_Sql_Model_Cache::deleteAll()
  1502. *
  1503. */
  1504. public function delete($where)
  1505. {
  1506. // perform the deletion and track affected rows
  1507. $this->_affected_rows = $this->_sql->delete(
  1508. $this->_table_name,
  1509. $where
  1510. );
  1511. // clear the cache for this model and related models
  1512. $this->_cache->deleteAll();
  1513. // done!
  1514. return $this->_affected_rows;
  1515. }
  1516. /**
  1517. *
  1518. * Serializes data values in-place based on $this->_serialize_cols and
  1519. * $this->_xmlstruct_cols.
  1520. *
  1521. * Does not attempt to serialize null values.
  1522. *
  1523. * If serializing fails, stores 'null' in the data.
  1524. *
  1525. * @param array &$data Record data.
  1526. *
  1527. * @return void
  1528. *
  1529. */
  1530. public function serializeCols(&$data)
  1531. {
  1532. foreach ($this->_serialize_cols as $key) {
  1533. // Don't process columns not in $data
  1534. if (! array_key_exists($key, $data)) {
  1535. continue;
  1536. }
  1537. // don't work on empty cols
  1538. if (empty($data[$key])) {
  1539. // Any empty value is canonicalized as null
  1540. $data[$key] = null;
  1541. continue;
  1542. }
  1543. $data[$key] = serialize($data[$key]);
  1544. if (! $data[$key]) {
  1545. // serializing failed
  1546. $data[$key] = null;
  1547. }
  1548. }
  1549. foreach ($this->_xmlstruct_cols as $key) {
  1550. // Don't process columns not in $data
  1551. if (! array_key_exists($key, $data)) {
  1552. continue;
  1553. }
  1554. // don't work on empty cols
  1555. if (empty($data[$

Large files files are truncated, but you can click here to view the full file