_fieldNames())) { return 'rank'; } elseif (in_array('name', $this->_fieldNames())) { return 'name'; } return null; } /** * _dbh * * @return PDO the database handle to use when manipulating this item, * */ public function _dbh() { if (! $this->_dbh) { $this->_dbh = self::dbhS(); } $this->_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); try { $this->_dbh->beginTransaction(); } catch (PDOException $e) { if ($e->getMessage() != 'There is already an active transaction') { throw $e; } } return $this->_dbh; } /** * Retrieve the database handle to use for persistence. If no dbh is * available, create one and cache it. * * @return PDO */ public static function dbhS() { if (! self::$dbhS) { Logger::info("+++++++++++ ESTABLISHING A NEW DB CONNECTION ++++++++++++++"); try { error_log("connecting to the db..."); self::$dbhS = new PDO('mysql:host=' . DB_HOSTNAME . ';dbname=' . DB_DATABASE, DB_USERNAME, DB_PASSWORD, array( PDO::ATTR_AUTOCOMMIT => false, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION )); } catch (PDOException $e) { error_log("ERROR: db connection failed."); throw $e; } } return self::$dbhS; } /** * parse a credentials file into a hash. lines are formatted as follows: * * username = user * password = pass * database = db * * * @param string $app name of app to read credentials for. The app-name * should directly correspond to a filename within DB_CREDENTIALS_DIR. * * @return $hash mapping credential keys to their values * */ private function credentialsParse() { $list = file(DB_CREDENTIALS_DIR . DIRECTORY_SEPARATOR . "$app", FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if (! $list) { throw new DbCredentialsException("Credentials for the application '$app' could not be read."); } foreach ($list as $line) { if (preg_match_all("/^(\w+) = (.*)$/", $line, $matches, PREG_SET_ORDER)) { // pull out the captured matches, ignoring the full string ($val[0]) foreach ($matches as $val) { $lines[ $val[1] ] = $val[2]; } } } return $lines; } /** * Creates a new object with the given database handle. If you're * trying to instantiate a new object, you should be using the * class's static getInstance method, not this constructor. * * The only reason this method isn't private is so we can automatically * transform rows fetched from the DB directly into objects. * * @param PDO $dbh: database handle to use * * @return void * */ public function __construct(PDO $dbh = null) { // set this object's manager, if one was provided. if ($dbh) { $this->_dbh = $dbh; } } /** * This is a magic method that handles the setting of property values * on a class when the named property is not explicitly defined in the * class. This makes it very easy to transform a row from a database * into an object in a generic way. Note that the values are set in * $this->fields[] to make sure __get and __set are always called when * manipulating these values; unless they are pushed into a non-public * field, they become public instance variables and __get and __set * will be ignored after the variable is first set. * * @see __get * * @param string $name: name of property to set * @param mixed $value: value of propert to set * * @return void * */ public function __set($name, $value) { if (! in_array($name, $this->_fieldNames())) { throw new Exception("Cannot set $name on an instance of " . get_class($this) . "; that field is undefined."); } // if the field was already set and we're resetting it, log it as an update if (array_key_exists($name, $this->fields) && ($this->$name != $value)) { $this->setHasUpdates(true); } $this->fields[$name] = $value; } /** * Return true if the named field has been set. * * @param string $name * @return boolean true if the field has been set; false otherwise */ public function __isset($name){ return isset($this->fields[$name]); } /** * This is a magic method that handles the getting of property values * on a class when the named property is not explicitly defined in the * class. This makes it very easy to transform a row from a database * into an object in a generic way. * * @param string $name: name of the property to return * * @return mixed: property corresponding to $name, or null if no property is set. * */ public function __get($name) { return isset($this->$name) ? $this->fields[$name] : null; } /** * Retrieve the next value of the named sequence. * * @throws PDOException if there are any problems retrieving the sequence. * * @return int next value in the named sequence * */ protected function getSequence() { $dbh = $this->_dbh(); $sth = $dbh->prepare('update sequence set id=LAST_INSERT_ID(id+1)'); $sth->execute(); $sth = $dbh->prepare("select LAST_INSERT_ID() as 'id'"); $sth->execute(); $id = null; $row = $sth->fetch(PDO::FETCH_NUM); $id = $row[0]; if (! $id) { throw new PDOException("value retrieved from sequence was empty!"); } return $id; } /** * Insert a new object into the DB given its tablename and a map of fields * to values. * * @param PDO $dbh DB handle to operate on * @param string $tableName name of the table to insert into * @param map[string] => mixed key-value hash mapping fieldnames to values * * @throws PDOException on error * */ protected static function simpleInsert(PDO $dbh, $tableName, array $map) { $bindKeys = array(); $bindValues = array(); $values = array(); foreach ($map as $key => $value) { if ($value) { $bindKeys[] = "$key"; $values[$key] = $value; $bindValues[] = ":$key"; } } $s = $dbh->prepare("insert into " . $tableName . " (" . join(", ", $bindKeys) . ") values ( " . join(", ", $bindValues) . ")"); $s->execute($values); } /** * Insert this object into the DB. * * @param PDO $dbh DB handle to operate on. * * @throws PDOException on error * */ protected function insert() { $this->validateForSave(); $bindKeys = array(); $bindValues = array(); $values = array(); foreach ($this->fields as $key => $value) { if ($value !== null) { $bindKeys[] = "$key"; $values[$key] = $value; $bindValues[] = ":$key"; } else { Logger::debug("value for $key is null!"); } } $sql = "insert into " . $this->_tableName() . " (" . join(", ", $bindKeys) . ") " . " values " . " ( " . join(", ", $bindValues) . ")"; Logger::debug("INSERT SQL : $sql"); $s = $this->_dbh()->prepare($sql); $s->execute($values); } /** * Update all fields. * */ public function update() { $this->cache = array(); $this->validateForSave(); list($setClause, $bindings) = $this->simpleUpdateSetBindings(); // build the update clause ... $sql = "update " . $this->_tableName() . " set " . join(", ", $setClause) . " where " ; // build the where clause, which must be able to support // compound primary keys if (is_array($this->_primaryKeyName())) { $where = array(); foreach ($this->_primaryKeyName() as $field) { $where[] = "$field = :$field"; $bindings[$field] = $this->$field; } $sql .= join(" AND ", $where); } else { $bindings['id'] = $this->{ $this->primaryKeyName() }; $sql .= $this->primaryKeyName() . " = :id"; } Logger::debug("SQL IS $sql"); $sth = $this->_dbh()->prepare($sql); // Logger::debug('ATTR_SERVER_INFO: ' . $this->_dbh()->getAttribute(PDO::ATTR_SERVER_INFO)); // Logger::debug('ATTR_CONNECTION_STATUS: ' . $this->_dbh()->getAttribute(PDO::ATTR_CONNECTION_STATUS)); $sth->execute($bindings); } /** * Refresh a snapshot with current data from the DB * @return unknown_type */ public function refresh() { $s = $this->_dbh()->prepare( "select " . join (", ", $this->_fieldNames()) . " from {$this->_tableName()} " . " where {$this->_primaryKeyName()} = :id"); $s->setFetchMode(PDO::FETCH_CLASS, get_class($this)); $s->execute(array('id' => $this->{$this->_primaryKeyName()})); $item = $s->fetch(PDO::FETCH_CLASS); foreach ($this->_fieldNames() as $field) { $this->{$field} = $item->{$field}; } } /** * Build and return the set-clause array and the bind-variables array. * * @return two lists (set-clause [key = :key] and bind-variables [key = value]) * */ protected function simpleUpdateSetBindings() { $setClause = array(); $bindings = array(); foreach ($this->_fieldNames() as $field) { // don't update the PK field if ($field == $this->_primaryKeyName()) { continue; } if (isset($this->$field) && $this->$field) { $setClause[] = "$field = :$field"; $bindings[$field] = $this->$field; } else { $setClause[] = "$field = NULL"; } } return array($setClause, $bindings); } /** * Remove this item * * @return void * */ public function remove() { // remove records in to-many relationships this item belongs to foreach ($this->relations as $name => $r) { Logger::debug("Checking on all $name instances for {$this->_tableName()} id {$this->{$this->_primaryKeyName()}} ..."); if ($r->type() == DaoRelationship::HAS_MANY) { Logger::debug(" removing all $name instances"); foreach($this->__call($name, array()) as $instance) { $instance->remove(); } } } // remove the item itself $key = $this->_primaryKeyName(); if (is_array($key)) { $this->removeCompound(); } else { $this->removeSimple(); } } protected function removeSimple() { $s = $this->_dbh()->prepare("delete from " . $this->_tableName() . " where " . $this->_primaryKeyName() . " = :id"); $s->execute(array('id' => $this->{$this->_primaryKeyName()})); } protected function removeCompound() { $bindKeys = array(); $bindVals = array(); foreach ($this->_primaryKeyName() as $field) { $bindKeys[] = "$field = :$field"; $bindVals[$field] = $this->$field; } $s = $this->_dbh()->prepare("delete from " . $this->_tableName() . " where " . implode(" AND ", $bindKeys)); $s->execute($bindVals); } /** * Return true if this item has changed since it was selected from the DB. * * @return boolean true if the item has changed; false otherwise */ public function hasUpdates() { return $this->hasUpdates; } /** * Set whether this item has changed since it was selected from the DB. * * @param boolean $b whether the item has changes */ public function setHasUpdates($b) { $this->hasUpdates = ($b ? true : false); } /** * retrieve an object by its pk. * * @param string $classname: name of the class to map the db row to * @param string[] $fields: columns in the db to retrieve * @param string $table: table in the db to retrieve from * @param string $id: PK of row to retrieve * * @throws Exception if required parameters are not passed * @throws PDOException if there are problems preparing or executing the statement * @throws ObjectNotFoundException if the requested row cannot be found * * @return extends Object: instance of $class with given PK * */ protected static function find(PDO $dbh, $classname, $id) { if (! ($classname && $id)) { throw new Exception("Couldn't find a $classname by PK because invalid parameters were specified: missing classname ($classname) or id ($id)."); } $item = null; try { // temp instance so we can extract DB metadata $instance = new $classname($dbh); $s = $dbh->prepare( "select " . join (", ", $instance->fieldNames()) . " from {$instance->tableName()} " . " where {$instance->primaryKeyName()} = :id"); $s->setFetchMode(PDO::FETCH_CLASS, $classname); $s->execute(array($instance->primaryKeyName() => $id)); $item = $s->fetch(PDO::FETCH_CLASS); } catch (PDOException $e) { throw new PDOException($e); } if ($item) { return $item; } // requested a specific ID and couldn't find it. that smells like trouble. throw new ObjectNotFoundException("Couldn't find a $classname identified by $id."); } /** * retrieve all instances of a class. * * @param PDO $dbh: * @param string $classname: name of the class to map the db row to * @param string[] $fields: columns in the db to retrieve * @param string $table: table in the db to retrieve from * * @throws Exception if invalid parameters are passed * @throws PDOException if there are problems preparing or executing the statement * * @return extends Object[]: all instances of $class */ protected static function findAll(PDO $dbh, $classname, $sortBy = null) { if (! $classname) { throw new Exception("Couldn't find all ${classname}s because invalid parameters were specified."); } $list = array(); try { // temp instance so we can extract DB metadata $instance = new $classname($dbh); $sql = "select " . join (", ", $instance->fieldNames()) . " from {$instance->tableName()} "; if ($sortBy != null) { $sql .= " order by $sortBy"; } elseif ($instance->sortDefault() != null) { $sql .= " order by {$instance->sortDefault()}"; } $s = $dbh->prepare($sql); $s->setFetchMode(PDO::FETCH_CLASS, $classname); $s->execute(); // have to fetch one-by-one; PDOStatement->fetchAll just returns // standardclass objects, not instances of the requested class while ($instance = $s->fetch(PDO::FETCH_CLASS)) { $list[] = $instance; } } catch (PDOException $e) { throw new PDOException($e); } return $list; } /** * retrieve items of a certain class given their PKs. * * @param string $classname: name of the class to map the db rows to * @param string[] $fields: columns in the db to retrieve * @param string $table: table in the db to retrieve from * @param string[] $id: PKs of rows to retrieve * * @throws Exception if any invalid parameters are passed * @throws PDOException if there are problems preparing or executing the statement * @throws ObjectNotFoundException if and of the requested rows cannot be found * * @return extends Object[]: instances of $class with given PKs * */ protected static function findList(PDO $dbh, $classname, array $ids) { if (! ($classname && $ids)) { throw new Exception("Couldn't find all ${classname}s because invalid parameters were specified."); } $list = array(); try { // temp instance so we can extract DB metadata $instance = new $classname($dbh); $s = $dbh->prepare( "select " . join (", ", $instance->fieldNames()) . " from {$instance->tableName()}" . " where {$instance->primaryKeyName()} in (" . join(", ", $ids) . ")"); $s->setFetchMode(PDO::FETCH_CLASS, $classname); $s->execute(); // have to fetch one-by-one; PDOStatement->fetchAll just returns // standardclass objects, not instances of the requested class while ($instance = $s->fetch(PDO::FETCH_CLASS)) { $list[] = $instance; } } catch(PDOException $e) { throw new PDOException($e); } if (count($list) != count($ids)) { throw new ObjectNotFoundException(count($ids) . " PKs specified but only " . count($list) . " items could be found!"); } return $list; } /** * retrieve an individual object by its name. * * @param string $classname: name of the class to map the db row to * @param string[] $fields: columns in the db to retrieve * @param string $table: table in the db to retrieve from * @param string $name: unique value in the row to retrieve * @param string $nameField: name of the field to search in; defaults to NAME * * @throws Exception if required parameters are not passed * @throws PDOException if there are problems preparing or executing the statement * @throws ObjectNotFoundException if the requested row cannot be found * * @return extends Object: instance of $class with given PK * */ protected static function findByName(PDO $dbh, $classname, $name, $nameField) { if (! ($classname && $name)) { throw new Exception("Couldn't find a $classname by PK because invalid parameters were specified."); } if (! $dbh) { $dbh = self::dbhS(); } try { // temp instance so we can extract DB metadata $instance = new $classname($dbh); $s = $dbh->prepare( "select " . join (", ", $instance->fieldNames()) . " from {$instance->tableName()}" . " where $nameField = :name"); $s->setFetchMode(PDO::FETCH_CLASS, $classname); $s->execute(array('name' => $name)); $item = $s->fetch(PDO::FETCH_CLASS); } catch(PDOException $e) { throw new PDOException($e); } if ($item) { return $item; } // requested a specific name and couldn't find it. that smells like trouble. throw new ObjectNotFoundException("Couldn't find a $classname identified by $name."); } /** * retrieve objects matching one or more criteria. * * @param PDO $dbh * @param string $classname: name of the class to map the db row to * @param string[] $where: columns in the db to retrieve * @param string $glue: default: ' AND ' * @param string $sortBy: field to sort by * * @throws Exception if required parameters are not passed * @throws PDOException if there are problems preparing or executing the statement * @throws ObjectNotFoundException if the requested row cannot be found * * @return extends $classname[]: instances of $class matching the given key/value pairs * */ protected static function findWhere(PDO $dbh, $classname, array $where, $glue = ' AND ', $sortBy = null) { if (! ($classname && $where)) { throw new Exception("Couldn't find a $classname by PK because invalid parameters were specified."); } $list = array(); try { // temp instance so we can extract DB metadata $instance = new $classname($dbh); $bindKeys = array(); $bindVals = array(); foreach ($where as $key => $val) { if ($val == 'null') { $bindKeys[] = "$key is null"; } else { $bindKeys[] = "$key = :$key"; $bindVals[$key] = $val; } } $sql = "select " . join (", ", $instance->fieldNames()) . " from " . $instance->tableName() . " where " . join(" $glue ", $bindKeys); if ($sortBy != null) { $sql .= " order by $sortBy"; } elseif ($instance->sortDefault() != null) { $sql .= " order by {$instance->sortDefault()}"; } $s = $dbh->prepare($sql); $s->setFetchMode(PDO::FETCH_CLASS, $classname); $s->execute($bindVals); // have to fetch one-by-one; PDOStatement->fetchAll just returns // standardclass objects, not instances of the requested class while ($instance = $s->fetch(PDO::FETCH_CLASS)) { $list[] = $instance; } } catch(PDOException $e) { throw new PDOException($e); } return $list; } protected static function findByQualifier(PDO $dbh, $classname, array $where, $glue = ' AND ', $sortBy = null) { if (! ($classname && $where)) { throw new Exception("Couldn't find a $classname by PK because invalid parameters were specified."); } $list = array(); try { // temp instance so we can extract DB metadata $instance = new $classname($dbh); $bindKeys = array(); $bindVals = array(); foreach ($where as $w) { if ($w->value == 'null') { if ($w->qualifier == '<>') { $bindKeys[] = "{$w->field} is not null"; } else { $bindKeys[] = "{$w->field} is null"; } } else { $bindKeys[] = $w->field . ' ' . $w->qualifier . ' :' .$w->field; $bindVals[$w->field] = $w->value; } } $sql = "select " . join (", ", $instance->fieldNames()) . " from " . $instance->tableName() . " where " . join(" $glue ", $bindKeys); if ($sortBy != null) { $sql .= " order by $sortBy"; } elseif ($instance->sortDefault() != null) { $sql .= " order by {$instance->sortDefault()}"; } $s = $dbh->prepare($sql); $s->setFetchMode(PDO::FETCH_CLASS, $classname); $s->execute($bindVals); // have to fetch one-by-one; PDOStatement->fetchAll just returns // standardclass objects, not instances of the requested class while ($instance = $s->fetch(PDO::FETCH_CLASS)) { $list[] = $instance; } } catch(PDOException $e) { throw new PDOException($e); } return $list; } /** * retrieve objects matching one or more criteria. * * @param PDO $dbh * @param string $classname: name of the class to map the db row to * @param string[] $where: columns in the db to retrieve * @param string $glue: default: ' AND ' * @param string $sortBy: field to sort by * * @throws Exception if required parameters are not passed * @throws PDOException if there are problems preparing or executing the statement * @throws ObjectNotFoundException if the requested row cannot be found * * @return extends $classname[]: instances of $class matching the given key/value pairs * */ protected static function findWhereIn(PDO $dbh, $classname, $keyname, $keylist, $sortBy = null) { if (! ($classname && $keyname && $keylist)) { throw new Exception("Couldn't execute a where-in query for $classname because invalid parameters were specified. [$classname, $keyname]"); } $list = array(); try { // temp instance so we can extract DB metadata $instance = new $classname($dbh); $bindKeys = array(); $bindVals = array(); if (! is_array($keylist)) { throw new Exception(); } foreach ($keylist as $id) { $bindKeys[] = ":id_$id"; $bindVals["id_$id"] = $id; } $sql = "select " . join (", ", $instance->fieldNames()) . " from " . $instance->tableName() . " where $keyname in (" . join(", ", $bindKeys) . ")"; if ($sortBy != null) { $sql .= " order by $sortBy"; } elseif ($instance->sortDefault() != null) { $sql .= " order by {$instance->sortDefault()}"; } $s = $dbh->prepare($sql); $s->setFetchMode(PDO::FETCH_CLASS, $classname); $s->execute($bindVals); // have to fetch one-by-one; PDOStatement->fetchAll just returns // standardclass objects, not instances of the requested class while ($instance = $s->fetch(PDO::FETCH_CLASS)) { $list[] = $instance; } } catch(PDOException $e) { throw new PDOException($e); } return $list; } /** * retrieve objects matching one or more criteria. * * @param string $classname: name of the class to map the db row to * @param hash $where: key/value pairs of rows to retrieve * @param string $glue: where-clause join (default: ' AND ') * @param string $sortBy: field to sort by * * @throws Exception if required parameters are not passed * @throws PDOException if there are problems preparing or executing the statement * @throws ObjectNotFoundException if the requested row cannot be found * * @return extends $classname[]: instances of $class matching the given key/value pairs * */ protected static function findLike(PDO $dbh, $classname, array $where, $glue = ' AND ', $sortBy = null) { if (! ($classname && $where)) { throw new Exception("Couldn't find a $class by PK because invalid parameters were specified."); } $list = array(); try { // temp instance so we can extract DB metadata $instance = new $classname($dbh); $bindKeys = array(); $bindVals = array(); foreach ($where as $key => $val) { if ($val == 'null') { $bindKeys[] = "$key is null"; } else { $bindKeys[] = "$key like :$key"; $bindVals[$key] = $val . '%'; } } $sql = "select " . join (", ", $instance->fieldNames()) . " from " . $instance->tableName() . " where " . join(" $glue ", $bindKeys); if ($sortBy != null) { $sql .= " order by $sortBy"; } elseif ($instance->sortDefault() != null) { $sql .= " order by {$instance->sortDefault()}"; } Logger::debug('SQL WAS ' . $sql); $s = $dbh->prepare($sql); $s->setFetchMode(PDO::FETCH_CLASS, $classname); $s->execute($bindVals); // have to fetch one-by-one; PDOStatement->fetchAll just returns // standardclass objects, not instances of the requested class while ($instance = $s->fetch(PDO::FETCH_CLASS)) { $list[] = $instance; } } catch(PDOException $e) { throw new PDOException($e); } return $list; } /** * Return this object's primary key. * * @return mixed */ protected function id() { return $this->fields[$this->_primaryKeyName()]; } /** * Return a Logger instance, instantiating it if necessary * * @return Logger */ protected function logger() { if (! $this->logger) { // $this->logger = Logger::getDefaultLogger(); $this->logger = new Syslogger(get_class($this), LOG_LOCAL0); // $this->logger->setDebugLevel(Logger::LEVEL_DEBUG); } return $this->logger; } /** * Capture an object's print_r output as a string and return it. * * @param unknown_type $input * @return string the input object, exploded into a string */ public static function toString($input) { ob_start(); print_r($input); $string = ob_get_contents(); ob_end_clean(); return $string; } protected function requiredFields() { return array(); } public function validateForSave() { $errors = null; foreach ($this->requiredFields() as $field) { if (! $this->$field) { if (! $errors) { $errors[$field]; } } } if (! $errors) { return; } throw new Exception("VALIDATION FAILED"); } public function max($fieldName) { if (! in_array($fieldName, $this->_fieldNames())) { throw new Exception("The table {$this->_tableName()} does not contain a field $fieldName."); } $s = $this->_dbh()->prepare("select max($fieldName) as max from {$this->_tableName()}"); $s->execute(); $row = $s->fetch(); if ($row['max']) { return $row['max']; } return 0; } /** * Commit the active DB handle. * */ public function save() { $this->_dbh()->commit(); } /** * If the requested method is defined, call it. Otherwise, see if it * matches a to-one or to-many relationship and retrieve the relationship * members. Relationship members are cached; the cache is cleared on * $this->update. * * @param string $name name of method to call * @param array $argv method arguments * * @return mixed result of calling $this->$name(array $argv) */ public function __call($name, array $argv) { if (method_exists($this, $name)) { return call_user_func_array(array($this, $name), $argv); } if (! array_key_exists($name, $this->relations)) { error_log("Attempting to call $name on an instance of " .(get_class($this)) . " failed!"); throw new Exception("Cannot traverse the relationship $name; it does not exist!"); } // return item from the cache, if available if (array_key_exists($name, $this->cache)) { return $this->cache[$name]; } $r = $this->relations[$name]; // TO-MANY RELATIONSHIP if ($r->type() == DaoRelationship::HAS_MANY) { // FLATTENED TO-MANY RELATIONSHIP if ($r->through()) { $joinKey = $r->fk(); $keys = array(); $list = Dao::findWhere(Dao::dbhS(), $r->through(), array($r->throughFk() => $this->id())); foreach ($list as $join) { $keys[] = $join->$joinKey; } $this->cache[$name] = Dao::findList(Dao::dbhS(), $r->classname(), $keys); } // REGULAR TO-MANY else { $this->cache[$name] = Dao::findWhere(Dao::dbhS(), $r->classname(), array($r->fk() => $this->id())); } } // TO-ONE RELATIONSHIP elseif ($r->type() == DaoRelationship::HAS_ONE) { $this->cache[$name] = Dao::find(Dao::dbhS(), $r->classname(), $this->fields[$r->fk()]); } return $this->cache[$name]; } /** * Add a to-one relationship * * @param string $name Name of the relationship * @param string $classname Name of the destination class * @param string $fk Name of the foreign key on this class referencing the destination class */ protected function hasOne($name, $classname, $fk = null) { if (array_key_exists($name, $this->relations)) { throw new Exception("Cannot create a relationship named $name; one already exists!"); } if (! $fk) { if (in_array(strtolower($classname . "_id"), $this->_fieldNames())) { $fk = strtolower($classname) . "_id"; } else { throw new ValidationException("Could not create a relationship from " . __CLASS__ . " to $classname because no foreign key was specified, nor could one be determined automatically."); } } $this->relations[$name] = new DaoRelationship(strtolower($classname), $fk, DaoRelationship::HAS_ONE); } /** * Add a to-many relationship * * @param string $name Name of the relationship * @param string $classname Name of the destination class * @param string $fk Name of the foreign key on the destination referencing this class, or referencing the join class * @param string $through Name of a join table the relationship is flattened through * @param string $throughFK Name of the foreign key on the join table referencing this class */ protected function hasMany($name, $classname, $fk = null, $through = null, $throughFk = null) { if (array_key_exists($name, $this->relations)) { throw new Exception("Cannot create a relationship for $classname; one already exists!"); } if (! $fk) { $fk = strtolower(get_class($this)) . "_id"; } $this->relations[$name] = new DaoRelationship($classname, $fk, DaoRelationship::HAS_MANY, $through, $throughFk); } public function equals(Dao $item) { // representing the same class? if ($this->_tableName() != $item->_tableName()) { return false; } // primary keys match, multi-key? if (is_array($this->_primaryKeyName())) { foreach ($this->_primaryKeyName() as $key) { if ($this->{$key} != $item->{$key}) { return false; } } return true; } // primary keys match, single-key else { if ($this->{$this->_primaryKeyName()} == $item->{$this->_primaryKeyName()}) { return true; } return false; } } } class DaoRelationship { const HAS_ONE = 'has_one'; const HAS_MANY = 'has_many'; protected $classname = null; protected $fk = null; protected $through = null; protected $throughFk = null; public function __construct($classname, $fk, $type, $through = null, $throughFk = null) { $this->classname = $classname; $this->fk = $fk; $this->type = $type; if ($through && $throughFk) { $this->through = $through; $this->throughFk = $throughFk; } } public function classname() { return $this->classname; } public function fk() { return $this->fk; } public function type() { return $this->type; } public function through() { return $this->through; } public function throughFk() { return $this->throughFk; } } class DaoQualifier { const GT = ' > '; const GTE = ' >= '; const LT = ' < '; const LTE = ' <= '; const EQ = ' = '; const NE = ' <> '; const LIKE = ' LIKE '; public $field = null; public $qualifier = null; public $value = null; public function __construct($f, $q, $v) { $this->field = $f; $this->qualifier = $q; $this->value = $v; } }