/lib/scargeneric.php
PHP | 1158 lines | 509 code | 238 blank | 411 comment | 88 complexity | a844c4eb2af86f9d5c1df8310f0f2d59 MD5 | raw file
Possible License(s): JSON
- <?php
- class SCAR_Generic implements Iterator {
- /**
- * Defines a list of keys that identifies a collection of
- * SCAR_Generic objects (from SQL Select)
- * @var array
- **/
- protected $_keys = array();
-
- /**
- * Defines a SCAR_Generic object as a unique record
- * @var string
- **/
- protected $_whoami = false;
-
- /**
- * Contains the datastore of all SCAR objects
- * @var object implements SCAR_Datastore_Interface
- **/
- protected $_datastore = null;
-
- /**
- * Contains a checksum of the last RS, saves on redundant
- * queries when there are complex WHERE statements
- * @var string
- **/
- protected $_cksum = null;
-
- /**
- * Contains the RS of the last ran query. Works with checksum
- * to prevent unnessesary duplicate queries
- * @var array
- **/
- protected $_lastrs = null;
-
- /**
- * Contains the filter object being used for iteration. During
- * loops, this filter will be checked against the object to see
- * if they relate. If they don't relate, it will be skipped
- * @var SCAR_Generic
- **/
- protected $_filter = null;
-
- /**
- * An internal storage array for all the parameters that
- * are carried between instances of the SCAR_Generic
- * @var array
- **/
- protected $_data = array(
- 'name' => null,
- 'dsn' => null,
- 'table' => null,
- 'stmt' => null,
- 'fields' => array(),
- 'primary_key' => array(),
- 'columns' => array(),
- 'relations' => array(),
-
- 'where' => array(),
- 'set' => array(),
- 'order' => array(),
- 'limit' => array(),
-
- 'scar' => 'SCAR',
-
- 'acts_as_materialized_path' => false,
- );
- // ----------------------------------------------------------------------
- // Structural Methods
- // Designed to provide a clean interface to auto-populate new instances
- // of SCAR objects. These methods all have getters and setters, the
- // setter returns $this for method chaning, while the getter returns
- // the value from class variables.
- // ----------------------------------------------------------------------
- /**
- * Set the SCAR Prefix. This is the prefix used for all Static calls
- * @param string $scar
- * @return SCAR_Generic
- **/
- public function SCAR($scar) { $this->_data['scar'] = $scar; return $this; }
-
- /**
- * Get the SCAR Prefix
- * @return string
- **/
- public function getSCAR() { return $this->_data['scar']; }
-
-
- /**
- * Set the DSN connection string. This is used for escaping, connecting,
- * and running queries
- * @param string $dsn
- * @return SCAR_Generic
- **/
- public function dsn($dsn) { $this->_data['dsn'] = $dsn; return $this; }
-
- /**
- * Get the DSN Connection String
- * @return string
- **/
- public function getDsn() { return $this->_data['dsn']; }
-
- /**
- * Set the SQL statement for reading later
- * not used internally
- * @param string Statement of SQL
- * @return SCAR_Generic
- **/
- public function stmt($stmt) { $this->_data['stmt'] = $stmt; return $this; }
-
- /**
- * get the stmt used in the query for this object
- * @return string
- **/
- public function getStmt() { return $this->_data['stmt']; }
-
- /**
- * Set the Name of the Class. Required because get_class($this) is not
- * reliable for objects built out of SCAR_Generic
- * @param string $name
- * @return SCAR_Generic
- **/
- public function name($name) { $this->_data['name'] = $name; return $this; }
-
- /**
- * Get the name of the class
- * @return string
- **/
- public function getName() { return ($this->_data['name']) ? $this->_data['name'] : get_class($this); }
-
-
- /**
- * Set the fields for the SCAR object
- * @param array $fields
- * @return SCAR_Generic
- **/
- public function fields($fields) { $this->_data['fields'] = $fields; return $this; }
-
- /**
- * Get the field list for the SCAR objects
- * @return array
- **/
- public function getFields() { return $this->_data['fields']; }
- /**
- * Set the primary key as either a param string or an array
- * When using, you can either pass in all of the keys as parameters (for readability)
- * or as an array (for automation):
- * <code>
- * $scar->primaryKey('foo', 'bar');
- * $scar->primaryKey(array(('foo', 'bar')));
- * </code>
- * @param string...|array
- * @return SCAR_Generic
- **/
- public function primaryKey() {
- $keys = func_get_args();
- $this->_data['primary_key'] = (is_array($keys[0])) ? $keys[0] : $keys;
- return $this;
- }
-
- /**
- * get the list of primary keys for the class
- * @return array
- **/
- public function getPrimaryKey() { return $this->_data['primary_key']; }
-
-
- /**
- * Set the associated table name
- * @param string $table
- * @return SCAR_Generic
- **/
- public function table($table) { $this->_data['table'] = $table; return $this; }
-
- /**
- * Get the associated table name
- * @return string
- **/
- public function getTable() { return ($this->_data['table']) ? $this->_data['table'] : Inflector::tableize($this->getName()); }
-
-
- /**
- * Set the Datastore, an object that maintains a reference to all retrieved
- * objects to date
- * @param object implements SCAR_Datastore_Interface
- * @return SCAR_Generic
- **/
- public function datastore(SCAR_Datastore_Interface $pl) { $this->_datastore = $pl; return $this; }
-
- /**
- * Get the datastore object
- * @return object implements SCAR_Datastore_Interface
- **/
- public function getDatastore() { return $this->_datastore; }
-
-
- /**
- * Set a unique identifier for this instance of a SCAR object
- * When set, the role of the object changes from a collection to a
- * specific instance, able to return members for that row.
- * @param string $key
- * @return SCAR_Generic
- **/
- public function iAm($key) { $this->_whoami = $key; return $this; }
-
- /**
- * Returns the unique identifier for this instance if it is set
- * @return string
- **/
- public function whoAmI() { return $this->_whoami; }
-
-
- /**
- * Sets the columns to select during a select statement
- * This is reccomended to be avoided since it mucks up the
- * datastore a bit. You will need to be aware that your object
- * will not have all of the columns until you select them all
- * @param string $col
- * @return SCAR_Generic
- **/
- public function columns($col) {
- if ($col === null) {
- $this->_data['columns'] = null;
- return $this;
- }
-
- $this->_data['columns'] = (is_array($col)) ? $col : array($col);
- return $this;
- }
-
- /**
- * get the list of columns for the query
- * @return array
- **/
- public function getColumns() { return $this->_data['columns']; }
-
-
- /**
- * Get the WHERE components that were set into the object
- * @return array
- * @see SCAR_Generic::by()
- **/
- public function getWhere() { return $this->_data['where']; }
-
- /**
- * Set the ORDER component
- * @return SCAR_Generic
- * @param $column the column to sort by
- * @param $order (default: ASC) the sort order
- **/
- public function order($column, $order = SORT_ASC) {
- if ($order !== SORT_ASC) {
- $order = SORT_DESC;
- }
-
- $this->_data['order'][] = array('col' => $column, 'order' => $order);
- return $this;
- }
-
- /**
- * Get the order statements that have been submitted
- * @return array
- **/
- public function getOrder() {
- return $this->_data['order'];
- }
-
- /**
- * Set the LIMIT compontent for sql statements
- * @return SCAR_Generic
- * @param $start the start value, or (if 1 param) the limit
- * @param $limit the limit
- **/
- public function limit() {
- $args = func_get_args();
-
- if (count($args) === 0) {
- return $this;
- }
-
- // args 1 = 2 params = start + limit
- // else, only limit
- if (isset($args[1])) {
- $start = $args[0];
- $limit = $args[1];
- }
- else {
- $start = 0;
- $limit = $args[0];
- }
-
- $this->_data['limit'] = array('start' => $start, 'count' => $limit);
-
- return $this;
- }
-
- /**
- * Get the LIMIT component
- * @return array
- **/
- public function getLimit() {
- return $this->_data['limit'];
- }
-
- /**
- * Get the SET components put into the object
- * @return array
- * @see SCAR_Generic::set()
- **/
- public function getSet() { return $this->_data['set']; }
-
-
- /**
- * Define a relationship of this object as a one to many
- * The object passed in is what this object "has one" of.
- * The second parameter defines the foriegn key relationship
- * @param string $what
- * @param string $key_relation
- * @return SCAR_Generic
- **/
- public function hasOne($what, $key_relation = null) { return $this->has(RELATION_HASONE, $what, $key_relation); }
-
- /**
- * Defines a relationship of this object as many to one
- * The object passed in is what this object "has many" of.
- * @param string $what
- * @param string $key_relation
- * @return SCAR_Generic
- **/
- public function hasMany($what, $key_relation = null) { return $this->has(RELATION_HASMANY, $what, $key_relation); }
-
- /**
- * returns the relationship between this object and the passed in class name
- * This method returns FALSE if no relation exists.
- * @param string $klass
- * @return array|false
- **/
- public function getRelation($klass) {
- $klass = strtolower($klass);
- return (isset($this->_data['relations'][$klass])) ? $this->_data['relations'][$klass] : false;
- }
-
- /**
- * Says that this object acts with the Materialized Path pattern
- * enables the calls for hasChildren and getChildren()
- * @param string $col the column that contains path
- * @return SCAR_Generic
- **/
- public function actsAsMaterializedPath($col) {
- $this->_data['acts_as_materialized_path'] = $col;
- }
-
- /**
- * Says a boolean if this object supports children or not
- * @return boolean
- **/
- public function hasChildren() {
- return ($this->_data['acts_as_materialized_path']) ? true : false;
- }
-
- /**
- * get the children for this object based on the model type
- * @return mixed SCAR_Generic or array()
- **/
- public function getChildren() {
- if (!$this->hasChildren()) {
- return array();
- }
-
- // do sql
- $this->doSQL();
-
- // if we don't know who we are, throw an exception
- // can't multiplex children
- if (!$this->whoAmI()) {
- throw new SCAR_Method_Call_Exception('getChildren', $this);
- }
-
- if ($this->_data['acts_as_materialized_path']) {
- $scar = call_user_func_array(array($this->getSCAR(), 'get'), array($this->getName()));
- $call = 'by' . Inflector::Camelize($this->_data['acts_as_materialized_path']);
- $scar->$call('LIKE', $this->path.'%');
- return $scar;
- }
- }
-
- /**
- * looks at criteria to determine if a given object can have a parent
- * @return boolean
- **/
- public function hasParent() {
- if (!$this->_data['acts_as_materialized_path']) {
- return false;
- }
-
- // do SQL
- $this->doSQL();
-
- // multiplex or 0 rows not valid
- if (!$this->whoAmI()) {
- return false;
- }
-
- if (strrpos($this->path, '/', -2) === 0) {
- return false;
- }
-
- return true;
- }
-
- /**
- * get the parent for this object based on the nesting model types
- * @return SCAR_Generic or false
- **/
- public function getParent() {
- if (!$this->hasParent()) {
- return false;
- }
-
- // do sql
- $this->doSQL();
-
- // if we don't know who we are, throw an exception
- // can't multiplex children
- if (!$this->whoAmI()) {
- throw new SCAR_Method_Call_Exception('getParent', $this);
- }
-
- if ($this->_data['acts_as_materialized_path']) {
- $scar = call_user_func_array(array($this->getSCAR(), 'get'), array($this->getName()));
- $call = 'by' . Inflector::Camelize($this->_data['acts_as_materialized_path']);
- $path = substr($this->path, 0, strrpos($this->path, '/', -2) + 1);
- var_dump($this->id.' '.$path);
- $scar->$call($path); // path minus last node
- return $scar;
- }
- }
- /**
- * Saves a SCAR object into the database
- * @return SCAR_Generic
- **/
- public function save() {
- return $this->doSQL();
- }
- /**
- * A Magic Method, Handling WHERE clause pieces and table joins
- * @see SCAR_Generic::by()
- * @see SCAR_Generic::join()
- **/
- public function __call($method, $args) {
- if (substr($method, 0, 2) == 'by') {
- array_unshift($args, Inflector::columnize(substr($method, 2)));
- return $this->by($args);
- }
- return $this->join($method);
- }
-
-
-
-
-
-
-
-
-
-
-
- // ----------------------------------------------------------------------
- // Magic Methods
- // these methods are called implicitly by the __call method
- // ----------------------------------------------------------------------
- /**
- * Modify the WHERE clause of the object.
- * If two parameters are provided in $args, then it will be either an = or an IN.
- * If three parameters are provided in $args, it will be a comparison operation.
- * If the last parameter in either is a SCAR_Generic object, then a relation will
- * be determined, and that object will be used as the link.
- * @param array $args
- * @return SCAR_Generic
- * @throws SCAR_Relation_Exception
- **/
- protected function by($args) {
- // is $args[1] an object?
- if (is_object($args[1]) && $args[1] instanceof SCAR_Generic) {
-
- list($column, $scar) = $args;
- $relation = $this->getRelation($scar->getName());
-
- // if it has a relation, use a join
- if ($relation) {
- return ($this->join($scar->getTable()));
- }
- else {
- // attempt to find the relation via intermediate table
- $relation_name = $this->getName().$scar->getName();
- $relation = $this->getRelation($relation_name);
- if (!$relation) {
- $relation_name = $scar->getName().$this->getName();
- $relation = $this->getRelation($relation_name);
- if (!$relation) {
- throw new SCAR_Relation_Exception($this->getName(), $scar->getName());
- }
- }
-
- // relation_name is the object we need
- // relation describes that relation
- $m = Inflector::tableize($relation_name);
- $results = $scar->$m();
- $value = array();
- foreach ($results as $result) {
- $remote = $relation['keys'][$column];
- $value[] = $result->$remote;
- }
- $operator = 'IN';
- }
- }
- else {
- // not a relational object, that makes life easier
- if (isset($args[2])) {
- list($column, $operator, $value) = $args;
- }
- else {
- list($column, $value) = $args;
- $operator = (is_array($value) && count($value) > 1) ? 'IN' : '=';
- }
- }
-
- // optimize for a single value array
- if (is_array($value) && count($value) == 1) {
- $value = $value[0];
- $operator = '=';
- }
-
- $this->_data['where'][] = array('col' => $column, 'oper' => $operator, 'value' => $value);
- return $this;
- }
-
- /**
- * Modify the SET clause for the object. called by __set()
- * $args coming in has two pieces, the column and the value
- * @param array $args
- * @return SCAR_Generic
- * @throws SCAR_RelationException
- * @see SCAR_Generic::__set()
- **/
- protected function set($args) {
- list($column, $value) = $args;
-
- $fields = $this->getFields();
- $type = $fields[$column];
-
- if ($type === TYPE_INT_AUTOINCREMENT) {
- // TODO exception
- }
-
- switch ($type) {
- case TYPE_INT:
- $value = (string)$value;
- $value = (ctype_digit($value)) ? $value : 0;
- break;
- case TYPE_STRING:
- default:
- }
-
- $this->_data['set'][] = array('col' => $column, 'value' => $value);
- return $this;
- }
-
- /**
- * Joins the current SCAR_Generic object to another SCAR_Generic Object
- * Upon completing the join, the new SCAR_Generic object is returned to
- * continue the chaining. Called by __call() when the prefix "by" isn't used
- * @param string $table
- * @return SCAR_Generic
- * @throws SCAR_Relation_Exception
- * @see SCAR_Generic::__call()
- **/
- protected function join($table) {
- // get the class
- $klass = call_user_func_array(array($this->getSCAR(), 'get'), array($table));
- $klassname = $klass->getName();
-
- // get the relation
- $relation = $this->getRelation($klassname);
- if (!$relation) {
- // attempt to find relational table for Many Many
- $relation_name = $this->getName().$klass->getName();
- $relation = $this->getRelation($relation_name);
- if (!$relation) {
- $relation_name = $klass->getName().$this->getName();
- $relation = $this->getRelation($relation_name);
- if (!$relation) {
- throw new SCAR_Relation_Exception($this->getName(), $klassname);
- }
- }
- // relates via many/many
- $link_table = Inflector::tableize($relation_name);
- return $this->$link_table()->$table();
- }
-
- // join by SQL many records
- if (!$this->whoAmI()) {
- // do SQL up to now
- $rs = $this->doSQL();
- foreach($relation['keys'] as $local_key => $foreign_key) {
- // find local key in key index
- $ids = array();
- foreach ($rs['keys'] as $keyspace => $match) {
- $keys = array_flip(explode(',', $keyspace));
- foreach ($match as $key_list) {
- $list = explode(',', $key_list);
- $ids[] = $list[$keys[$local_key]];
- }
- }
- // var_dump($ids);
- // if ($local_key == 'tag_id') { var_dump($relation); var_dump($rs); }
- // $ids = $rs['keys'][$local_key];
- $klass_method = Inflector::byMethodize($foreign_key);
- $klass->$klass_method($ids);
- }
- }
- // join by one to many records, need ID
- else {
- $id = $this->whoAmI();
- $row = $this->getDatastore()->get($this->getName(), $id);
- $f_keys = array();
- foreach($relation['keys'] as $local_key => $foreign_key) {
- $f_keys[] = $row[$local_key];
- $klass_method = Inflector::byMethodize($foreign_key);
- $klass->$klass_method($row[$local_key]);
- }
-
- // does this thing already exist in our datastore? if so
- // we can iAm() it and it will live on its own
- $f_keys = implode(',', $f_keys);
- if ($this->getDatastore()->get($klass->getName(), $f_keys)) {
- $klass->iAm($f_keys);
- }
- }
-
- // share the data storage love
- $klass->datastore($this->getDatastore());
-
- // now calls are on the new object
- return $klass;
- }
-
-
-
-
-
-
-
-
-
-
-
- // ----------------------------------------------------------------------
- // SQL Operations
- // These can be overriden in sub classes to provide functionality such
- // as bucketing, sharding, and caching
- // ----------------------------------------------------------------------
- /**
- * Main SQL entry point.
- * This is the entry point for doing all SQL operations
- * @return boolean|array
- **/
- protected function doSQL() {
- // update
- if (count($this->getSet()) > 0 && $this->whoAmI()) {
- return $this->doSQLUpdate();
- }
- // things who know who they are don't need SQL
- elseif ($this->whoAmI()) {
- return true;
- }
- // insert
- elseif (count($this->getSet()) > 0) {
- return $this->doSQLInsert();
- }
- // select
- else {
- return $this->doSQLSelect();
- }
- }
-
- /**
- * Perform an SQL Update
- * @return SCAR_Generic|boolean
- **/
- protected function doSQLUpdate() {
- $sql = new SCAR_SQL('update', $this->getDSN(), $this->getSCAR());
- $sql->table = $this->getTable();
- // where by pkey
- $where = array();
- $values = explode(',', $this->whoAmI());
- $keys = $this->getPrimaryKey();
- foreach ($keys as $idx => $key) {
- $where[] = array('col' => $key, 'oper' => '=', 'value' => $values[$idx]);
- }
- $sql->where = $where;
- $sql->set = $this->getSet();
- $stmt = $sql->render();
-
- $this->stmt($stmt);
-
- $rs = call_user_func_array(array($this->getSCAR(), 'query'), array($this->getDSN(), $stmt, $this->getPrimaryKey()));
-
- // okay, update datastore
- if ($rs['affected'] === 1) {
- $row = $this->getDatastore()->get($this->getName(), $this->whoAmI());
- foreach ($this->getSet() as $set_piece) {
- $row[$set_piece['col']] = $set_piece['value'];
- }
- $this->getDatastore()->set($this->getName(), $this->whoAmI(), $row);
-
- return $this;
- }
-
- return false;
- }
-
- /**
- * Perform an SQL Insert
- * @return SCAR_Generic|boolean
- **/
- protected function doSQLInsert() {
- $sql = new SCAR_SQL('insert', $this->getDSN(), $this->getSCAR());
- $sql->table = $this->getTable();
-
- $sql->set = $this->getSet();
- $stmt = $sql->render();
-
- $this->stmt($stmt);
- $rs = call_user_func_array(array($this->getSCAR(), 'query'), array($this->getDSN(), $stmt, $this->getPrimaryKey()));
-
- if ($rs['affected'] === 1) {
- // get a new one and select it
- $id = $rs['next'];
-
- $klass = call_user_func_array(array($this->getSCAR(), 'get'), array($this->getName()));
- // change set to key/value pairs
- $set = array();
- foreach ($this->getSet() as $set_piece) {
- $set[$set_piece['col']] = $set_piece['value'];
- }
- foreach($klass->getPrimaryKey() as $pk) {
- if (isset($set[$pk])) {
- $klass_method = Inflector::byMethodize($pk);
- $klass->$klass_method($set[$pk]);
- }
- else {
- $klass_method = Inflector::byMethodize($pk);
- $klass->$klass_method($id);
- }
- }
- $klass = $klass->current();
- // return a current iteration of the class
- return $klass;
- }
-
- return false;
- }
-
- /**
- * Perform an SQL Select
- * @return array
- **/
- protected function doSQLSelect() {
- if (md5(serialize($this->_data)) == $this->_cksum) {
- return $this->_lastrs;
- }
- // if where count is 1 and where value is an array and where col == primary key (where IN pkey)
- $where = $this->getWhere();
- if (count($where) == 1 && is_array($where[0]['value']) && array($where[0]['col']) == $this->getPrimaryKey()) {
- // clear the where condition completely
- $old_values = $where[0]['value'];
- $byColumn = Inflector::byMethodize($where[0]['col']);
-
- $values = array();
- $keys = array();
- // for each where value
- foreach ($old_values as $key) {
- // if in datastore, add to key
- if ($this->getDatastore()->get($this->getName(), $key)) {
- $keys[] = $key;
- }
- // else, add to value
- else {
- $values[] = $key;
- }
- }
- // if there are no missing values
- // then our key list is valid
- if (count($values) == 0) {
- if ($this->_keys == array()) {
- $this->_keys = $keys;
- }
- return true;
- }
- // clear our where clause
- // set the keys to everything we have
- // set new byKeyName() with only missing values
- $this->_data['where'] = array();
- $this->_keys = $keys;
- $this->$byColumn($values);
- }
- // else if where count == pkey count (where a=b, where a=b and c=d)
- elseif (count($where) == count($this->getPrimaryKey())) {
- // if every where matches a pkey and none of the where values are an array
- $key_check = array_flip($this->getPrimaryKey()); // keycheck is now 'key' => $position
- $lookup = array();
- foreach ($where as $w) {
- if (!isset($key_check[$w['col']])) {
- break;
- }
- $lookup[$key_check[$w['col']]] = $w['value'];
- }
- if (count($lookup) == count($where)) {
- // (we are looking for one row, either pk or cpk)
- ksort($lookup); // lookup is now in order
- $lookup = implode(',', $lookup); // turned into key
-
- // check datastore
- $result = $this->getDatastore()->get($this->getName(), $lookup);
- if ($result) {
- $this->iAm($lookup);
-
- // prevent infinite loop, only set keys if not set
- if ($this->_keys == array()) {
- $this->_keys = array($lookup);
- }
- return true;
- }
- }
- }
- // not in datastore (ordered things by default won't)
-
- $sql = new SCAR_SQL('select', $this->getDSN(), $this->getSCAR());
- $sql->table = $this->getTable();
- $sql->columns = ($this->getColumns()) ? array_unique(array_merge($this->getColumns(), $this->getPrimaryKey())) : '*';
- $sql->where = $this->getWhere();
- $sql->order = $this->getOrder();
- $sql->limit = $this->getLimit();
- $stmt = $sql->render();
-
- $this->stmt($stmt);
- $rs = call_user_func_array(array($this->getSCAR(), 'query'), array($this->getDSN(), $stmt, $this->getPrimaryKey()));
- // write these keys to the datastore
- foreach ($rs['keys'] as $key) {
- foreach ($key as $key_value) {
- $this->_keys[] = $key_value;
- $this->getDatastore()->set($this->getName(), $key_value, $rs['data'][$key_value]);
- }
- }
-
- // one row, set iAm()
- if ($rs['rows'] === 1) {
- $this->iAm($this->_keys[0]);
- }
-
- // no rows, iAm becomes null
- if ($rs['rows'] === 0) {
- $this->iAm(null);
- }
-
- $this->_cksum = md5(serialize($this->_data));
- $this->_lastrs = $rs;
- return $rs;
- }
- // ----------------------------------------------------------------------
- // Table Relationships
- // ----------------------------------------------------------------------
- /**
- * Define a relationship between this object and another
- * Provides information about what kind of relation, what it relates to,
- * and how it relates (key to foriegn key)
- * @param int $relation
- * @param string $what
- * @param string $key_relation
- * @return SCAR_Generic
- * @see SCAR_Generic::hasOne()
- * @see SCAR_Generic::hasMany()
- **/
- protected function has($relation, $what, $key_relation) {
- // key relations are in local => remote form
- $what = strtolower($what);
-
- // One To Many
- if ($relation == RELATION_HASMANY || $relation == RELATION_HASMANYMANY) {
- // primary key maps to foriegn key
- if ($key_relation === null) {
- $key_relation = array('id' => Inflector::foreignify($this->getName()));
- }
- if (!is_array($key_relation)) {
- $key_relation = array('id' => $key_relation);
- }
- }
- // Many to One
- elseif($relation == RELATION_HASONE) {
- // foreign key to primary key
- if ($key_relation === null) {
- $key_relation = array(Inflector::foreignify($what) => 'id');
- }
-
- if (!is_array($key_relation)) {
- $key_relation = array($key_relation => 'id');
- }
- }
-
- $this->_data['relations'][$what] = array('type' => $relation,
- 'keys' => $key_relation,
- );
-
- return $this;
- }
- // ----------------------------------------------------------------------
- // ----------------------------------------------------------------------
- // Iterator Interface
- // This satisifies the implementation of Iterator, making all SCAR
- // objects able to be looped over in foreach() and other constructs.
- // ----------------------------------------------------------------------
- // ----------------------------------------------------------------------
- /**
- * Filters the array loop by a passed in object.
- * @param null|SCAR_Generic $filter
- * @return SCAR_Generic
- **/
- public function filter($filter = null) {
- $this->_filter = $filter;
- return $this;
- }
-
- /**
- * Checks if the class provided matches against the filter
- * @param SCAR_Generic $klass
- * @return boolean
- **/
- public function filterMatch($klass) {
- if (!$this->_filter) {
- return true;
- }
-
- $relation = $this->getRelation($this->_filter->getName());
-
- $match = true;
- foreach ($relation['keys'] as $local_key => $foreign_key) {
- if ($this->_filter->$foreign_key != $klass->$local_key) {
- $match = false;
- }
- }
-
- return $match;
- }
-
- /**
- * Returns the current object
- * @return SCAR_Generic
- **/
- public function current() {
- $this->doSQL();
- $key = current($this->_keys);
- $klass = call_user_func_array(array($this->getSCAR(), 'make'), array($this->getName()));
- $klass->iAm($key);
- $klass->datastore($this->getDatastore());
-
- return ($this->filterMatch($klass)) ? $klass : $this->next();
- }
-
- /**
- * Returns the current key for the array
- * @return int
- **/
- public function key() {
- $this->doSQL();
- return current($this->_keys);
- }
-
- /**
- * Returns the next object in iteration
- * @return SCAR_Generic|false
- **/
- public function next() {
- $this->doSQL();
- // get our first legit next
- do {
- $key = next($this->_keys);
- if (!$key) {
- return false;
- }
- $klass_name = $this->getName();
- $klass = call_user_func_array(array($this->getSCAR(), 'make'), array($this->getName()));
- $klass->iAm($key);
- $klass->datastore($this->getDatastore());
- } while (!$this->filterMatch($klass));
-
- return $klass;
- }
-
- /**
- * Resets the Iterator
- * @return null
- **/
- public function rewind() {
- $this->doSQL();
- reset($this->_keys);
- }
-
- /**
- * Ensures that $this->current() is a valid
- * @return boolean
- **/
- public function valid() {
- $this->doSQL();
- return (current($this->_keys) !== false);
- }
- // ----------------------------------------------------------------------
- // ----------------------------------------------------------------------
- // Data Member Access
- // These methods detail getting object records in and out of the SCAR
- // object. They use PHP's magic methods, and check for the $this->whoAmI
- // to determine the record from the Datastore to analyze
- // ----------------------------------------------------------------------
- // ----------------------------------------------------------------------
- public function __get($n) {
- // if I have where, but don't know who I am, do SQL
- if (!$this->whoAmI() && count($this->getWhere()) > 0) {
- $this->doSQL();
- }
-
- if ($this->whoAmI() === false) {
- // calling get on collection of objects
- throw new SCAR_Method_Call_Exception($this->getName().'->'.$n, $this);
- }
-
- $id = $this->whoAmI();
- if ($id === null) {
- return null;
- }
- $row = $this->getDatastore()->get($this->getName(), $id);
- return (isset($row[$n])) ? $row[$n] : null;
-
- }
- public function __set($n, $v) {
- // must be in field
- $fields = $this->getFields();
- if (!isset($fields[$n])) {
-
- // check for a relationship with $n
- $relation = $this->getRelation(Inflector::camelize($n));
- if (!$relation) {
- throw new SCAR_Relation_Exception($this->getName(), Inflector::camelize($n));
- }
-
- if ($v->getName() != Inflector::camelize($n)) {
- throw new SCAR_Relation_Exception($v->getName(), $this->getName());
- }
-
- foreach ($relation['keys'] as $local => $remote) {
- return $this->set(array($local, $v->$remote));
- }
- }
-
- return $this->set(array($n, $v));
- }
- }