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

/lib/scargeneric.php

https://github.com/Jakobo/scar
PHP | 1158 lines | 509 code | 238 blank | 411 comment | 88 complexity | a844c4eb2af86f9d5c1df8310f0f2d59 MD5 | raw file
Possible License(s): JSON
  1. <?php
  2. class SCAR_Generic implements Iterator {
  3. /**
  4. * Defines a list of keys that identifies a collection of
  5. * SCAR_Generic objects (from SQL Select)
  6. * @var array
  7. **/
  8. protected $_keys = array();
  9. /**
  10. * Defines a SCAR_Generic object as a unique record
  11. * @var string
  12. **/
  13. protected $_whoami = false;
  14. /**
  15. * Contains the datastore of all SCAR objects
  16. * @var object implements SCAR_Datastore_Interface
  17. **/
  18. protected $_datastore = null;
  19. /**
  20. * Contains a checksum of the last RS, saves on redundant
  21. * queries when there are complex WHERE statements
  22. * @var string
  23. **/
  24. protected $_cksum = null;
  25. /**
  26. * Contains the RS of the last ran query. Works with checksum
  27. * to prevent unnessesary duplicate queries
  28. * @var array
  29. **/
  30. protected $_lastrs = null;
  31. /**
  32. * Contains the filter object being used for iteration. During
  33. * loops, this filter will be checked against the object to see
  34. * if they relate. If they don't relate, it will be skipped
  35. * @var SCAR_Generic
  36. **/
  37. protected $_filter = null;
  38. /**
  39. * An internal storage array for all the parameters that
  40. * are carried between instances of the SCAR_Generic
  41. * @var array
  42. **/
  43. protected $_data = array(
  44. 'name' => null,
  45. 'dsn' => null,
  46. 'table' => null,
  47. 'stmt' => null,
  48. 'fields' => array(),
  49. 'primary_key' => array(),
  50. 'columns' => array(),
  51. 'relations' => array(),
  52. 'where' => array(),
  53. 'set' => array(),
  54. 'order' => array(),
  55. 'limit' => array(),
  56. 'scar' => 'SCAR',
  57. 'acts_as_materialized_path' => false,
  58. );
  59. // ----------------------------------------------------------------------
  60. // Structural Methods
  61. // Designed to provide a clean interface to auto-populate new instances
  62. // of SCAR objects. These methods all have getters and setters, the
  63. // setter returns $this for method chaning, while the getter returns
  64. // the value from class variables.
  65. // ----------------------------------------------------------------------
  66. /**
  67. * Set the SCAR Prefix. This is the prefix used for all Static calls
  68. * @param string $scar
  69. * @return SCAR_Generic
  70. **/
  71. public function SCAR($scar) { $this->_data['scar'] = $scar; return $this; }
  72. /**
  73. * Get the SCAR Prefix
  74. * @return string
  75. **/
  76. public function getSCAR() { return $this->_data['scar']; }
  77. /**
  78. * Set the DSN connection string. This is used for escaping, connecting,
  79. * and running queries
  80. * @param string $dsn
  81. * @return SCAR_Generic
  82. **/
  83. public function dsn($dsn) { $this->_data['dsn'] = $dsn; return $this; }
  84. /**
  85. * Get the DSN Connection String
  86. * @return string
  87. **/
  88. public function getDsn() { return $this->_data['dsn']; }
  89. /**
  90. * Set the SQL statement for reading later
  91. * not used internally
  92. * @param string Statement of SQL
  93. * @return SCAR_Generic
  94. **/
  95. public function stmt($stmt) { $this->_data['stmt'] = $stmt; return $this; }
  96. /**
  97. * get the stmt used in the query for this object
  98. * @return string
  99. **/
  100. public function getStmt() { return $this->_data['stmt']; }
  101. /**
  102. * Set the Name of the Class. Required because get_class($this) is not
  103. * reliable for objects built out of SCAR_Generic
  104. * @param string $name
  105. * @return SCAR_Generic
  106. **/
  107. public function name($name) { $this->_data['name'] = $name; return $this; }
  108. /**
  109. * Get the name of the class
  110. * @return string
  111. **/
  112. public function getName() { return ($this->_data['name']) ? $this->_data['name'] : get_class($this); }
  113. /**
  114. * Set the fields for the SCAR object
  115. * @param array $fields
  116. * @return SCAR_Generic
  117. **/
  118. public function fields($fields) { $this->_data['fields'] = $fields; return $this; }
  119. /**
  120. * Get the field list for the SCAR objects
  121. * @return array
  122. **/
  123. public function getFields() { return $this->_data['fields']; }
  124. /**
  125. * Set the primary key as either a param string or an array
  126. * When using, you can either pass in all of the keys as parameters (for readability)
  127. * or as an array (for automation):
  128. * <code>
  129. * $scar->primaryKey('foo', 'bar');
  130. * $scar->primaryKey(array(('foo', 'bar')));
  131. * </code>
  132. * @param string...|array
  133. * @return SCAR_Generic
  134. **/
  135. public function primaryKey() {
  136. $keys = func_get_args();
  137. $this->_data['primary_key'] = (is_array($keys[0])) ? $keys[0] : $keys;
  138. return $this;
  139. }
  140. /**
  141. * get the list of primary keys for the class
  142. * @return array
  143. **/
  144. public function getPrimaryKey() { return $this->_data['primary_key']; }
  145. /**
  146. * Set the associated table name
  147. * @param string $table
  148. * @return SCAR_Generic
  149. **/
  150. public function table($table) { $this->_data['table'] = $table; return $this; }
  151. /**
  152. * Get the associated table name
  153. * @return string
  154. **/
  155. public function getTable() { return ($this->_data['table']) ? $this->_data['table'] : Inflector::tableize($this->getName()); }
  156. /**
  157. * Set the Datastore, an object that maintains a reference to all retrieved
  158. * objects to date
  159. * @param object implements SCAR_Datastore_Interface
  160. * @return SCAR_Generic
  161. **/
  162. public function datastore(SCAR_Datastore_Interface $pl) { $this->_datastore = $pl; return $this; }
  163. /**
  164. * Get the datastore object
  165. * @return object implements SCAR_Datastore_Interface
  166. **/
  167. public function getDatastore() { return $this->_datastore; }
  168. /**
  169. * Set a unique identifier for this instance of a SCAR object
  170. * When set, the role of the object changes from a collection to a
  171. * specific instance, able to return members for that row.
  172. * @param string $key
  173. * @return SCAR_Generic
  174. **/
  175. public function iAm($key) { $this->_whoami = $key; return $this; }
  176. /**
  177. * Returns the unique identifier for this instance if it is set
  178. * @return string
  179. **/
  180. public function whoAmI() { return $this->_whoami; }
  181. /**
  182. * Sets the columns to select during a select statement
  183. * This is reccomended to be avoided since it mucks up the
  184. * datastore a bit. You will need to be aware that your object
  185. * will not have all of the columns until you select them all
  186. * @param string $col
  187. * @return SCAR_Generic
  188. **/
  189. public function columns($col) {
  190. if ($col === null) {
  191. $this->_data['columns'] = null;
  192. return $this;
  193. }
  194. $this->_data['columns'] = (is_array($col)) ? $col : array($col);
  195. return $this;
  196. }
  197. /**
  198. * get the list of columns for the query
  199. * @return array
  200. **/
  201. public function getColumns() { return $this->_data['columns']; }
  202. /**
  203. * Get the WHERE components that were set into the object
  204. * @return array
  205. * @see SCAR_Generic::by()
  206. **/
  207. public function getWhere() { return $this->_data['where']; }
  208. /**
  209. * Set the ORDER component
  210. * @return SCAR_Generic
  211. * @param $column the column to sort by
  212. * @param $order (default: ASC) the sort order
  213. **/
  214. public function order($column, $order = SORT_ASC) {
  215. if ($order !== SORT_ASC) {
  216. $order = SORT_DESC;
  217. }
  218. $this->_data['order'][] = array('col' => $column, 'order' => $order);
  219. return $this;
  220. }
  221. /**
  222. * Get the order statements that have been submitted
  223. * @return array
  224. **/
  225. public function getOrder() {
  226. return $this->_data['order'];
  227. }
  228. /**
  229. * Set the LIMIT compontent for sql statements
  230. * @return SCAR_Generic
  231. * @param $start the start value, or (if 1 param) the limit
  232. * @param $limit the limit
  233. **/
  234. public function limit() {
  235. $args = func_get_args();
  236. if (count($args) === 0) {
  237. return $this;
  238. }
  239. // args 1 = 2 params = start + limit
  240. // else, only limit
  241. if (isset($args[1])) {
  242. $start = $args[0];
  243. $limit = $args[1];
  244. }
  245. else {
  246. $start = 0;
  247. $limit = $args[0];
  248. }
  249. $this->_data['limit'] = array('start' => $start, 'count' => $limit);
  250. return $this;
  251. }
  252. /**
  253. * Get the LIMIT component
  254. * @return array
  255. **/
  256. public function getLimit() {
  257. return $this->_data['limit'];
  258. }
  259. /**
  260. * Get the SET components put into the object
  261. * @return array
  262. * @see SCAR_Generic::set()
  263. **/
  264. public function getSet() { return $this->_data['set']; }
  265. /**
  266. * Define a relationship of this object as a one to many
  267. * The object passed in is what this object "has one" of.
  268. * The second parameter defines the foriegn key relationship
  269. * @param string $what
  270. * @param string $key_relation
  271. * @return SCAR_Generic
  272. **/
  273. public function hasOne($what, $key_relation = null) { return $this->has(RELATION_HASONE, $what, $key_relation); }
  274. /**
  275. * Defines a relationship of this object as many to one
  276. * The object passed in is what this object "has many" of.
  277. * @param string $what
  278. * @param string $key_relation
  279. * @return SCAR_Generic
  280. **/
  281. public function hasMany($what, $key_relation = null) { return $this->has(RELATION_HASMANY, $what, $key_relation); }
  282. /**
  283. * returns the relationship between this object and the passed in class name
  284. * This method returns FALSE if no relation exists.
  285. * @param string $klass
  286. * @return array|false
  287. **/
  288. public function getRelation($klass) {
  289. $klass = strtolower($klass);
  290. return (isset($this->_data['relations'][$klass])) ? $this->_data['relations'][$klass] : false;
  291. }
  292. /**
  293. * Says that this object acts with the Materialized Path pattern
  294. * enables the calls for hasChildren and getChildren()
  295. * @param string $col the column that contains path
  296. * @return SCAR_Generic
  297. **/
  298. public function actsAsMaterializedPath($col) {
  299. $this->_data['acts_as_materialized_path'] = $col;
  300. }
  301. /**
  302. * Says a boolean if this object supports children or not
  303. * @return boolean
  304. **/
  305. public function hasChildren() {
  306. return ($this->_data['acts_as_materialized_path']) ? true : false;
  307. }
  308. /**
  309. * get the children for this object based on the model type
  310. * @return mixed SCAR_Generic or array()
  311. **/
  312. public function getChildren() {
  313. if (!$this->hasChildren()) {
  314. return array();
  315. }
  316. // do sql
  317. $this->doSQL();
  318. // if we don't know who we are, throw an exception
  319. // can't multiplex children
  320. if (!$this->whoAmI()) {
  321. throw new SCAR_Method_Call_Exception('getChildren', $this);
  322. }
  323. if ($this->_data['acts_as_materialized_path']) {
  324. $scar = call_user_func_array(array($this->getSCAR(), 'get'), array($this->getName()));
  325. $call = 'by' . Inflector::Camelize($this->_data['acts_as_materialized_path']);
  326. $scar->$call('LIKE', $this->path.'%');
  327. return $scar;
  328. }
  329. }
  330. /**
  331. * looks at criteria to determine if a given object can have a parent
  332. * @return boolean
  333. **/
  334. public function hasParent() {
  335. if (!$this->_data['acts_as_materialized_path']) {
  336. return false;
  337. }
  338. // do SQL
  339. $this->doSQL();
  340. // multiplex or 0 rows not valid
  341. if (!$this->whoAmI()) {
  342. return false;
  343. }
  344. if (strrpos($this->path, '/', -2) === 0) {
  345. return false;
  346. }
  347. return true;
  348. }
  349. /**
  350. * get the parent for this object based on the nesting model types
  351. * @return SCAR_Generic or false
  352. **/
  353. public function getParent() {
  354. if (!$this->hasParent()) {
  355. return false;
  356. }
  357. // do sql
  358. $this->doSQL();
  359. // if we don't know who we are, throw an exception
  360. // can't multiplex children
  361. if (!$this->whoAmI()) {
  362. throw new SCAR_Method_Call_Exception('getParent', $this);
  363. }
  364. if ($this->_data['acts_as_materialized_path']) {
  365. $scar = call_user_func_array(array($this->getSCAR(), 'get'), array($this->getName()));
  366. $call = 'by' . Inflector::Camelize($this->_data['acts_as_materialized_path']);
  367. $path = substr($this->path, 0, strrpos($this->path, '/', -2) + 1);
  368. var_dump($this->id.' '.$path);
  369. $scar->$call($path); // path minus last node
  370. return $scar;
  371. }
  372. }
  373. /**
  374. * Saves a SCAR object into the database
  375. * @return SCAR_Generic
  376. **/
  377. public function save() {
  378. return $this->doSQL();
  379. }
  380. /**
  381. * A Magic Method, Handling WHERE clause pieces and table joins
  382. * @see SCAR_Generic::by()
  383. * @see SCAR_Generic::join()
  384. **/
  385. public function __call($method, $args) {
  386. if (substr($method, 0, 2) == 'by') {
  387. array_unshift($args, Inflector::columnize(substr($method, 2)));
  388. return $this->by($args);
  389. }
  390. return $this->join($method);
  391. }
  392. // ----------------------------------------------------------------------
  393. // Magic Methods
  394. // these methods are called implicitly by the __call method
  395. // ----------------------------------------------------------------------
  396. /**
  397. * Modify the WHERE clause of the object.
  398. * If two parameters are provided in $args, then it will be either an = or an IN.
  399. * If three parameters are provided in $args, it will be a comparison operation.
  400. * If the last parameter in either is a SCAR_Generic object, then a relation will
  401. * be determined, and that object will be used as the link.
  402. * @param array $args
  403. * @return SCAR_Generic
  404. * @throws SCAR_Relation_Exception
  405. **/
  406. protected function by($args) {
  407. // is $args[1] an object?
  408. if (is_object($args[1]) && $args[1] instanceof SCAR_Generic) {
  409. list($column, $scar) = $args;
  410. $relation = $this->getRelation($scar->getName());
  411. // if it has a relation, use a join
  412. if ($relation) {
  413. return ($this->join($scar->getTable()));
  414. }
  415. else {
  416. // attempt to find the relation via intermediate table
  417. $relation_name = $this->getName().$scar->getName();
  418. $relation = $this->getRelation($relation_name);
  419. if (!$relation) {
  420. $relation_name = $scar->getName().$this->getName();
  421. $relation = $this->getRelation($relation_name);
  422. if (!$relation) {
  423. throw new SCAR_Relation_Exception($this->getName(), $scar->getName());
  424. }
  425. }
  426. // relation_name is the object we need
  427. // relation describes that relation
  428. $m = Inflector::tableize($relation_name);
  429. $results = $scar->$m();
  430. $value = array();
  431. foreach ($results as $result) {
  432. $remote = $relation['keys'][$column];
  433. $value[] = $result->$remote;
  434. }
  435. $operator = 'IN';
  436. }
  437. }
  438. else {
  439. // not a relational object, that makes life easier
  440. if (isset($args[2])) {
  441. list($column, $operator, $value) = $args;
  442. }
  443. else {
  444. list($column, $value) = $args;
  445. $operator = (is_array($value) && count($value) > 1) ? 'IN' : '=';
  446. }
  447. }
  448. // optimize for a single value array
  449. if (is_array($value) && count($value) == 1) {
  450. $value = $value[0];
  451. $operator = '=';
  452. }
  453. $this->_data['where'][] = array('col' => $column, 'oper' => $operator, 'value' => $value);
  454. return $this;
  455. }
  456. /**
  457. * Modify the SET clause for the object. called by __set()
  458. * $args coming in has two pieces, the column and the value
  459. * @param array $args
  460. * @return SCAR_Generic
  461. * @throws SCAR_RelationException
  462. * @see SCAR_Generic::__set()
  463. **/
  464. protected function set($args) {
  465. list($column, $value) = $args;
  466. $fields = $this->getFields();
  467. $type = $fields[$column];
  468. if ($type === TYPE_INT_AUTOINCREMENT) {
  469. // TODO exception
  470. }
  471. switch ($type) {
  472. case TYPE_INT:
  473. $value = (string)$value;
  474. $value = (ctype_digit($value)) ? $value : 0;
  475. break;
  476. case TYPE_STRING:
  477. default:
  478. }
  479. $this->_data['set'][] = array('col' => $column, 'value' => $value);
  480. return $this;
  481. }
  482. /**
  483. * Joins the current SCAR_Generic object to another SCAR_Generic Object
  484. * Upon completing the join, the new SCAR_Generic object is returned to
  485. * continue the chaining. Called by __call() when the prefix "by" isn't used
  486. * @param string $table
  487. * @return SCAR_Generic
  488. * @throws SCAR_Relation_Exception
  489. * @see SCAR_Generic::__call()
  490. **/
  491. protected function join($table) {
  492. // get the class
  493. $klass = call_user_func_array(array($this->getSCAR(), 'get'), array($table));
  494. $klassname = $klass->getName();
  495. // get the relation
  496. $relation = $this->getRelation($klassname);
  497. if (!$relation) {
  498. // attempt to find relational table for Many Many
  499. $relation_name = $this->getName().$klass->getName();
  500. $relation = $this->getRelation($relation_name);
  501. if (!$relation) {
  502. $relation_name = $klass->getName().$this->getName();
  503. $relation = $this->getRelation($relation_name);
  504. if (!$relation) {
  505. throw new SCAR_Relation_Exception($this->getName(), $klassname);
  506. }
  507. }
  508. // relates via many/many
  509. $link_table = Inflector::tableize($relation_name);
  510. return $this->$link_table()->$table();
  511. }
  512. // join by SQL many records
  513. if (!$this->whoAmI()) {
  514. // do SQL up to now
  515. $rs = $this->doSQL();
  516. foreach($relation['keys'] as $local_key => $foreign_key) {
  517. // find local key in key index
  518. $ids = array();
  519. foreach ($rs['keys'] as $keyspace => $match) {
  520. $keys = array_flip(explode(',', $keyspace));
  521. foreach ($match as $key_list) {
  522. $list = explode(',', $key_list);
  523. $ids[] = $list[$keys[$local_key]];
  524. }
  525. }
  526. // var_dump($ids);
  527. // if ($local_key == 'tag_id') { var_dump($relation); var_dump($rs); }
  528. // $ids = $rs['keys'][$local_key];
  529. $klass_method = Inflector::byMethodize($foreign_key);
  530. $klass->$klass_method($ids);
  531. }
  532. }
  533. // join by one to many records, need ID
  534. else {
  535. $id = $this->whoAmI();
  536. $row = $this->getDatastore()->get($this->getName(), $id);
  537. $f_keys = array();
  538. foreach($relation['keys'] as $local_key => $foreign_key) {
  539. $f_keys[] = $row[$local_key];
  540. $klass_method = Inflector::byMethodize($foreign_key);
  541. $klass->$klass_method($row[$local_key]);
  542. }
  543. // does this thing already exist in our datastore? if so
  544. // we can iAm() it and it will live on its own
  545. $f_keys = implode(',', $f_keys);
  546. if ($this->getDatastore()->get($klass->getName(), $f_keys)) {
  547. $klass->iAm($f_keys);
  548. }
  549. }
  550. // share the data storage love
  551. $klass->datastore($this->getDatastore());
  552. // now calls are on the new object
  553. return $klass;
  554. }
  555. // ----------------------------------------------------------------------
  556. // SQL Operations
  557. // These can be overriden in sub classes to provide functionality such
  558. // as bucketing, sharding, and caching
  559. // ----------------------------------------------------------------------
  560. /**
  561. * Main SQL entry point.
  562. * This is the entry point for doing all SQL operations
  563. * @return boolean|array
  564. **/
  565. protected function doSQL() {
  566. // update
  567. if (count($this->getSet()) > 0 && $this->whoAmI()) {
  568. return $this->doSQLUpdate();
  569. }
  570. // things who know who they are don't need SQL
  571. elseif ($this->whoAmI()) {
  572. return true;
  573. }
  574. // insert
  575. elseif (count($this->getSet()) > 0) {
  576. return $this->doSQLInsert();
  577. }
  578. // select
  579. else {
  580. return $this->doSQLSelect();
  581. }
  582. }
  583. /**
  584. * Perform an SQL Update
  585. * @return SCAR_Generic|boolean
  586. **/
  587. protected function doSQLUpdate() {
  588. $sql = new SCAR_SQL('update', $this->getDSN(), $this->getSCAR());
  589. $sql->table = $this->getTable();
  590. // where by pkey
  591. $where = array();
  592. $values = explode(',', $this->whoAmI());
  593. $keys = $this->getPrimaryKey();
  594. foreach ($keys as $idx => $key) {
  595. $where[] = array('col' => $key, 'oper' => '=', 'value' => $values[$idx]);
  596. }
  597. $sql->where = $where;
  598. $sql->set = $this->getSet();
  599. $stmt = $sql->render();
  600. $this->stmt($stmt);
  601. $rs = call_user_func_array(array($this->getSCAR(), 'query'), array($this->getDSN(), $stmt, $this->getPrimaryKey()));
  602. // okay, update datastore
  603. if ($rs['affected'] === 1) {
  604. $row = $this->getDatastore()->get($this->getName(), $this->whoAmI());
  605. foreach ($this->getSet() as $set_piece) {
  606. $row[$set_piece['col']] = $set_piece['value'];
  607. }
  608. $this->getDatastore()->set($this->getName(), $this->whoAmI(), $row);
  609. return $this;
  610. }
  611. return false;
  612. }
  613. /**
  614. * Perform an SQL Insert
  615. * @return SCAR_Generic|boolean
  616. **/
  617. protected function doSQLInsert() {
  618. $sql = new SCAR_SQL('insert', $this->getDSN(), $this->getSCAR());
  619. $sql->table = $this->getTable();
  620. $sql->set = $this->getSet();
  621. $stmt = $sql->render();
  622. $this->stmt($stmt);
  623. $rs = call_user_func_array(array($this->getSCAR(), 'query'), array($this->getDSN(), $stmt, $this->getPrimaryKey()));
  624. if ($rs['affected'] === 1) {
  625. // get a new one and select it
  626. $id = $rs['next'];
  627. $klass = call_user_func_array(array($this->getSCAR(), 'get'), array($this->getName()));
  628. // change set to key/value pairs
  629. $set = array();
  630. foreach ($this->getSet() as $set_piece) {
  631. $set[$set_piece['col']] = $set_piece['value'];
  632. }
  633. foreach($klass->getPrimaryKey() as $pk) {
  634. if (isset($set[$pk])) {
  635. $klass_method = Inflector::byMethodize($pk);
  636. $klass->$klass_method($set[$pk]);
  637. }
  638. else {
  639. $klass_method = Inflector::byMethodize($pk);
  640. $klass->$klass_method($id);
  641. }
  642. }
  643. $klass = $klass->current();
  644. // return a current iteration of the class
  645. return $klass;
  646. }
  647. return false;
  648. }
  649. /**
  650. * Perform an SQL Select
  651. * @return array
  652. **/
  653. protected function doSQLSelect() {
  654. if (md5(serialize($this->_data)) == $this->_cksum) {
  655. return $this->_lastrs;
  656. }
  657. // if where count is 1 and where value is an array and where col == primary key (where IN pkey)
  658. $where = $this->getWhere();
  659. if (count($where) == 1 && is_array($where[0]['value']) && array($where[0]['col']) == $this->getPrimaryKey()) {
  660. // clear the where condition completely
  661. $old_values = $where[0]['value'];
  662. $byColumn = Inflector::byMethodize($where[0]['col']);
  663. $values = array();
  664. $keys = array();
  665. // for each where value
  666. foreach ($old_values as $key) {
  667. // if in datastore, add to key
  668. if ($this->getDatastore()->get($this->getName(), $key)) {
  669. $keys[] = $key;
  670. }
  671. // else, add to value
  672. else {
  673. $values[] = $key;
  674. }
  675. }
  676. // if there are no missing values
  677. // then our key list is valid
  678. if (count($values) == 0) {
  679. if ($this->_keys == array()) {
  680. $this->_keys = $keys;
  681. }
  682. return true;
  683. }
  684. // clear our where clause
  685. // set the keys to everything we have
  686. // set new byKeyName() with only missing values
  687. $this->_data['where'] = array();
  688. $this->_keys = $keys;
  689. $this->$byColumn($values);
  690. }
  691. // else if where count == pkey count (where a=b, where a=b and c=d)
  692. elseif (count($where) == count($this->getPrimaryKey())) {
  693. // if every where matches a pkey and none of the where values are an array
  694. $key_check = array_flip($this->getPrimaryKey()); // keycheck is now 'key' => $position
  695. $lookup = array();
  696. foreach ($where as $w) {
  697. if (!isset($key_check[$w['col']])) {
  698. break;
  699. }
  700. $lookup[$key_check[$w['col']]] = $w['value'];
  701. }
  702. if (count($lookup) == count($where)) {
  703. // (we are looking for one row, either pk or cpk)
  704. ksort($lookup); // lookup is now in order
  705. $lookup = implode(',', $lookup); // turned into key
  706. // check datastore
  707. $result = $this->getDatastore()->get($this->getName(), $lookup);
  708. if ($result) {
  709. $this->iAm($lookup);
  710. // prevent infinite loop, only set keys if not set
  711. if ($this->_keys == array()) {
  712. $this->_keys = array($lookup);
  713. }
  714. return true;
  715. }
  716. }
  717. }
  718. // not in datastore (ordered things by default won't)
  719. $sql = new SCAR_SQL('select', $this->getDSN(), $this->getSCAR());
  720. $sql->table = $this->getTable();
  721. $sql->columns = ($this->getColumns()) ? array_unique(array_merge($this->getColumns(), $this->getPrimaryKey())) : '*';
  722. $sql->where = $this->getWhere();
  723. $sql->order = $this->getOrder();
  724. $sql->limit = $this->getLimit();
  725. $stmt = $sql->render();
  726. $this->stmt($stmt);
  727. $rs = call_user_func_array(array($this->getSCAR(), 'query'), array($this->getDSN(), $stmt, $this->getPrimaryKey()));
  728. // write these keys to the datastore
  729. foreach ($rs['keys'] as $key) {
  730. foreach ($key as $key_value) {
  731. $this->_keys[] = $key_value;
  732. $this->getDatastore()->set($this->getName(), $key_value, $rs['data'][$key_value]);
  733. }
  734. }
  735. // one row, set iAm()
  736. if ($rs['rows'] === 1) {
  737. $this->iAm($this->_keys[0]);
  738. }
  739. // no rows, iAm becomes null
  740. if ($rs['rows'] === 0) {
  741. $this->iAm(null);
  742. }
  743. $this->_cksum = md5(serialize($this->_data));
  744. $this->_lastrs = $rs;
  745. return $rs;
  746. }
  747. // ----------------------------------------------------------------------
  748. // Table Relationships
  749. // ----------------------------------------------------------------------
  750. /**
  751. * Define a relationship between this object and another
  752. * Provides information about what kind of relation, what it relates to,
  753. * and how it relates (key to foriegn key)
  754. * @param int $relation
  755. * @param string $what
  756. * @param string $key_relation
  757. * @return SCAR_Generic
  758. * @see SCAR_Generic::hasOne()
  759. * @see SCAR_Generic::hasMany()
  760. **/
  761. protected function has($relation, $what, $key_relation) {
  762. // key relations are in local => remote form
  763. $what = strtolower($what);
  764. // One To Many
  765. if ($relation == RELATION_HASMANY || $relation == RELATION_HASMANYMANY) {
  766. // primary key maps to foriegn key
  767. if ($key_relation === null) {
  768. $key_relation = array('id' => Inflector::foreignify($this->getName()));
  769. }
  770. if (!is_array($key_relation)) {
  771. $key_relation = array('id' => $key_relation);
  772. }
  773. }
  774. // Many to One
  775. elseif($relation == RELATION_HASONE) {
  776. // foreign key to primary key
  777. if ($key_relation === null) {
  778. $key_relation = array(Inflector::foreignify($what) => 'id');
  779. }
  780. if (!is_array($key_relation)) {
  781. $key_relation = array($key_relation => 'id');
  782. }
  783. }
  784. $this->_data['relations'][$what] = array('type' => $relation,
  785. 'keys' => $key_relation,
  786. );
  787. return $this;
  788. }
  789. // ----------------------------------------------------------------------
  790. // ----------------------------------------------------------------------
  791. // Iterator Interface
  792. // This satisifies the implementation of Iterator, making all SCAR
  793. // objects able to be looped over in foreach() and other constructs.
  794. // ----------------------------------------------------------------------
  795. // ----------------------------------------------------------------------
  796. /**
  797. * Filters the array loop by a passed in object.
  798. * @param null|SCAR_Generic $filter
  799. * @return SCAR_Generic
  800. **/
  801. public function filter($filter = null) {
  802. $this->_filter = $filter;
  803. return $this;
  804. }
  805. /**
  806. * Checks if the class provided matches against the filter
  807. * @param SCAR_Generic $klass
  808. * @return boolean
  809. **/
  810. public function filterMatch($klass) {
  811. if (!$this->_filter) {
  812. return true;
  813. }
  814. $relation = $this->getRelation($this->_filter->getName());
  815. $match = true;
  816. foreach ($relation['keys'] as $local_key => $foreign_key) {
  817. if ($this->_filter->$foreign_key != $klass->$local_key) {
  818. $match = false;
  819. }
  820. }
  821. return $match;
  822. }
  823. /**
  824. * Returns the current object
  825. * @return SCAR_Generic
  826. **/
  827. public function current() {
  828. $this->doSQL();
  829. $key = current($this->_keys);
  830. $klass = call_user_func_array(array($this->getSCAR(), 'make'), array($this->getName()));
  831. $klass->iAm($key);
  832. $klass->datastore($this->getDatastore());
  833. return ($this->filterMatch($klass)) ? $klass : $this->next();
  834. }
  835. /**
  836. * Returns the current key for the array
  837. * @return int
  838. **/
  839. public function key() {
  840. $this->doSQL();
  841. return current($this->_keys);
  842. }
  843. /**
  844. * Returns the next object in iteration
  845. * @return SCAR_Generic|false
  846. **/
  847. public function next() {
  848. $this->doSQL();
  849. // get our first legit next
  850. do {
  851. $key = next($this->_keys);
  852. if (!$key) {
  853. return false;
  854. }
  855. $klass_name = $this->getName();
  856. $klass = call_user_func_array(array($this->getSCAR(), 'make'), array($this->getName()));
  857. $klass->iAm($key);
  858. $klass->datastore($this->getDatastore());
  859. } while (!$this->filterMatch($klass));
  860. return $klass;
  861. }
  862. /**
  863. * Resets the Iterator
  864. * @return null
  865. **/
  866. public function rewind() {
  867. $this->doSQL();
  868. reset($this->_keys);
  869. }
  870. /**
  871. * Ensures that $this->current() is a valid
  872. * @return boolean
  873. **/
  874. public function valid() {
  875. $this->doSQL();
  876. return (current($this->_keys) !== false);
  877. }
  878. // ----------------------------------------------------------------------
  879. // ----------------------------------------------------------------------
  880. // Data Member Access
  881. // These methods detail getting object records in and out of the SCAR
  882. // object. They use PHP's magic methods, and check for the $this->whoAmI
  883. // to determine the record from the Datastore to analyze
  884. // ----------------------------------------------------------------------
  885. // ----------------------------------------------------------------------
  886. public function __get($n) {
  887. // if I have where, but don't know who I am, do SQL
  888. if (!$this->whoAmI() && count($this->getWhere()) > 0) {
  889. $this->doSQL();
  890. }
  891. if ($this->whoAmI() === false) {
  892. // calling get on collection of objects
  893. throw new SCAR_Method_Call_Exception($this->getName().'->'.$n, $this);
  894. }
  895. $id = $this->whoAmI();
  896. if ($id === null) {
  897. return null;
  898. }
  899. $row = $this->getDatastore()->get($this->getName(), $id);
  900. return (isset($row[$n])) ? $row[$n] : null;
  901. }
  902. public function __set($n, $v) {
  903. // must be in field
  904. $fields = $this->getFields();
  905. if (!isset($fields[$n])) {
  906. // check for a relationship with $n
  907. $relation = $this->getRelation(Inflector::camelize($n));
  908. if (!$relation) {
  909. throw new SCAR_Relation_Exception($this->getName(), Inflector::camelize($n));
  910. }
  911. if ($v->getName() != Inflector::camelize($n)) {
  912. throw new SCAR_Relation_Exception($v->getName(), $this->getName());
  913. }
  914. foreach ($relation['keys'] as $local => $remote) {
  915. return $this->set(array($local, $v->$remote));
  916. }
  917. }
  918. return $this->set(array($n, $v));
  919. }
  920. }