예제 #1
0
 /**
  * Stores a record in the database, whether existing or new
  * 
  * This method will start database and filesystem transactions if they have
  * not already been started.
  * 
  * @throws fValidationException  When ::validate() throws an exception
  * 
  * @param  boolean $force_cascade  When storing related records, this will force deleting child records even if they have their own children in a relationship with an RESTRICT or NO ACTION for the ON DELETE clause
  * @return fActiveRecord  The record object, to allow for method chaining
  */
 public function store($force_cascade = FALSE)
 {
     $class = get_class($this);
     if (fORM::getActiveRecordMethod($class, 'store')) {
         return $this->__call('store', array());
     }
     fORM::callHookCallbacks($this, 'pre::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
     $db = fORMDatabase::retrieve($class, 'write');
     $schema = fORMSchema::retrieve($class);
     try {
         $table = fORM::tablize($class);
         // New auto-incrementing records require lots of special stuff, so we'll detect them here
         $new_autoincrementing_record = FALSE;
         if (!$this->exists()) {
             $pk_columns = $schema->getKeys($table, 'primary');
             $pk_column = $pk_columns[0];
             $pk_auto_incrementing = $schema->getColumnInfo($table, $pk_column, 'auto_increment');
             if (sizeof($pk_columns) == 1 && $pk_auto_incrementing && !$this->values[$pk_column]) {
                 $new_autoincrementing_record = TRUE;
             }
         }
         $inside_db_transaction = $db->isInsideTransaction();
         if (!$inside_db_transaction) {
             $db->translatedQuery('BEGIN');
         }
         fORM::callHookCallbacks($this, 'post-begin::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
         $this->validate();
         fORM::callHookCallbacks($this, 'post-validate::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
         // Storing main table
         if (!$this->exists()) {
             $params = $this->constructInsertParams();
         } else {
             $params = $this->constructUpdateParams();
         }
         $result = call_user_func_array($db->translatedQuery, $params);
         // If there is an auto-incrementing primary key, grab the value from the database
         if ($new_autoincrementing_record) {
             $this->set($pk_column, $result->getAutoIncrementedValue());
         }
         // Fix cascade updated columns for in-memory objects to prevent issues when saving
         $one_to_one_relationships = $schema->getRelationships($table, 'one-to-one');
         $one_to_many_relationships = $schema->getRelationships($table, 'one-to-many');
         $relationships = array_merge($one_to_one_relationships, $one_to_many_relationships);
         foreach ($relationships as $relationship) {
             $type = in_array($relationship, $one_to_one_relationships) ? 'one-to-one' : 'one-to-many';
             $route = fORMSchema::getRouteNameFromRelationship($type, $relationship);
             $related_table = $relationship['related_table'];
             $related_class = fORM::classize($related_table);
             $related_class = fORM::getRelatedClass($class, $related_class);
             if ($relationship['on_update'] != 'cascade') {
                 continue;
             }
             $column = $relationship['column'];
             if (!fActiveRecord::changed($this->values, $this->old_values, $column)) {
                 continue;
             }
             if (!isset($this->related_records[$related_table][$route]['record_set'])) {
                 continue;
             }
             $record_set = $this->related_records[$related_table][$route]['record_set'];
             $related_column = $relationship['related_column'];
             $old_value = fActiveRecord::retrieveOld($this->old_values, $column);
             $value = $this->values[$column];
             if ($old_value === NULL) {
                 continue;
             }
             foreach ($record_set as $record) {
                 if (isset($record->old_values[$related_column])) {
                     foreach (array_keys($record->old_values[$related_column]) as $key) {
                         if ($record->old_values[$related_column][$key] === $old_value) {
                             $record->old_values[$related_column][$key] = $value;
                         }
                     }
                 }
                 if ($record->values[$related_column] === $old_value) {
                     $record->values[$related_column] = $value;
                 }
             }
         }
         // Storing *-to-many and one-to-one relationships
         fORMRelated::store($class, $this->values, $this->related_records, $force_cascade);
         fORM::callHookCallbacks($this, 'pre-commit::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
         if (!$inside_db_transaction) {
             $db->translatedQuery('COMMIT');
         }
         fORM::callHookCallbacks($this, 'post-commit::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
     } catch (fException $e) {
         if (!$inside_db_transaction) {
             $db->translatedQuery('ROLLBACK');
         }
         fORM::callHookCallbacks($this, 'post-rollback::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
         if ($new_autoincrementing_record && self::hasOld($this->old_values, $pk_column)) {
             $this->values[$pk_column] = self::retrieveOld($this->old_values, $pk_column);
             unset($this->old_values[$pk_column]);
         }
         throw $e;
     }
     fORM::callHookCallbacks($this, 'post::store()', $this->values, $this->old_values, $this->related_records, $this->cache);
     $was_new = !$this->exists();
     // If we got here we succefully stored, so update old values to make exists() work
     foreach ($this->values as $column => $value) {
         $this->old_values[$column] = array($value);
     }
     // If the object was just inserted into the database, save it to the identity map
     if ($was_new) {
         $hash = self::hash($this->values, $class);
         if (!isset(self::$identity_map[$class])) {
             self::$identity_map[$class] = array();
         }
         self::$identity_map[$class][$hash] = $this;
     }
     return $this;
 }
예제 #2
0
 /**
  * Validates any many-to-many associations or any one-to-many records that have been flagged for association
  *
  * @internal
  *
  * @param  string $class             The class to validate the related records for
  * @param  array  &$values           The values for the object
  * @param  array  &$related_records  The related records for the object
  * @return void
  */
 public static function validate($class, &$values, &$related_records)
 {
     $table = fORM::tablize($class);
     $schema = fORMSchema::retrieve($class);
     $validation_messages = array();
     // Find the record sets to validate
     foreach ($related_records as $related_table => $routes) {
         foreach ($routes as $route => $related_info) {
             if (!$related_info['count'] || !$related_info['associate']) {
                 continue;
             }
             $related_class = fORM::classize($related_table);
             $related_class = fORM::getRelatedClass($class, $related_class);
             $relationship = fORMSchema::getRoute($schema, $table, $related_table, $route);
             if (isset($relationship['join_table'])) {
                 $related_messages = self::validateManyToMany($class, $related_class, $route, $related_info);
             } else {
                 $related_messages = self::validateOneToStar($class, $values, $related_records, $related_class, $route);
             }
             $validation_messages = array_merge($validation_messages, $related_messages);
         }
     }
     return $validation_messages;
 }
예제 #3
0
 /**
  * Allows for preloading various data related to the record set in single database queries, as opposed to one query per record
  * 
  * This method will handle methods in the format `verbRelatedRecords()` for
  * the verbs `build`, `prebuild`, `precount` and `precreate`.
  * 
  * `build` calls `create{RelatedClass}()` on each record in the set and
  * returns the result as a new record set. The relationship route can be
  * passed as an optional parameter.
  * 
  * `prebuild` builds *-to-many record sets for all records in the record
  * set. `precount` will count records in *-to-many record sets for every
  * record in the record set. `precreate` will create a *-to-one record
  * for every record in the record set.
  *  
  * @param  string $method_name  The name of the method called
  * @param  string $parameters   The parameters passed
  * @return void
  */
 public function __call($method_name, $parameters)
 {
     if ($callback = fORM::getRecordSetMethod($method_name)) {
         return call_user_func_array($callback, array($this, $this->class, &$this->records, $method_name, $parameters));
     }
     list($action, $subject) = fORM::parseMethod($method_name);
     $route = $parameters ? $parameters[0] : NULL;
     // This check prevents fGrammar exceptions being thrown when an unknown method is called
     if (in_array($action, array('build', 'prebuild', 'precount', 'precreate'))) {
         $related_class = fGrammar::singularize($subject);
         $related_class_sans_namespace = $related_class;
         if (!is_array($this->class)) {
             $related_class = fORM::getRelatedClass($this->class, $related_class);
         }
     }
     switch ($action) {
         case 'build':
             if ($route) {
                 $this->precreate($related_class, $route);
                 return $this->buildFromCall('create' . $related_class_sans_namespace, $route);
             }
             $this->precreate($related_class);
             return $this->buildFromCall('create' . $related_class_sans_namespace);
         case 'prebuild':
             return $this->prebuild($related_class, $route);
         case 'precount':
             return $this->precount($related_class, $route);
         case 'precreate':
             return $this->precreate($related_class, $route);
     }
     throw new fProgrammerException('Unknown method, %s(), called', $method_name);
 }
 /**
  * Add a one-to-many rule that requires at least one related record is associated with the current record
  *
  * @param  mixed  $class          The class name or instance of the class to add the rule for
  * @param  string $related_class  The name of the related class
  * @param  string $route          The route to the related class
  * @return void
  */
 public static function addOneToManyRule($class, $related_class, $route = NULL)
 {
     $class = fORM::getClass($class);
     $related_class = fORM::getRelatedClass($class, $related_class);
     if (!isset(self::$related_one_or_more_rules[$class])) {
         self::$related_one_or_more_rules[$class] = array();
     }
     if (!isset(self::$related_one_or_more_rules[$class][$related_class])) {
         self::$related_one_or_more_rules[$class][$related_class] = array();
     }
     $route = fORMSchema::getRouteName(fORMSchema::retrieve($class), fORM::tablize($class), fORM::tablize($related_class), $route, 'one-to-many');
     self::$related_one_or_more_rules[$class][$related_class][$route] = TRUE;
 }
 /**
  * Handles all method calls for columns, related records and hook callbacks
  * 
  * Dynamically handles `get`, `set`, `prepare`, `encode` and `inspect`
  * methods for each column in this record. Method names are in the form
  * `verbColumName()`.
  * 
  * This method also handles `associate`, `build`, `count`, `has`, and `link`
  * verbs for records in many-to-many relationships; `build`, `count`, `has`
  * and `populate` verbs for all related records in one-to-many relationships
  * and `create`, `has` and `populate` verbs for all related records in
  * one-to-one relationships, and the `create` verb for all related records
  * in many-to-one relationships.
  * 
  * Method callbacks registered through fORM::registerActiveRecordMethod()
  * will be delegated via this method.
  * 
  * @param  string $method_name  The name of the method called
  * @param  array  $parameters   The parameters passed
  * @return mixed  The value returned by the method called
  */
 public function __call($method_name, $parameters)
 {
     $class = get_class($this);
     if (!isset(self::$callback_cache[$class][$method_name])) {
         if (!isset(self::$callback_cache[$class])) {
             self::$callback_cache[$class] = array();
         }
         $callback = fORM::getActiveRecordMethod($class, $method_name);
         self::$callback_cache[$class][$method_name] = $callback ? $callback : FALSE;
     }
     if ($callback = self::$callback_cache[$class][$method_name]) {
         return call_user_func_array($callback, array($this, &$this->values, &$this->old_values, &$this->related_records, &$this->cache, $method_name, $parameters));
     }
     if (!isset(self::$method_name_cache[$method_name])) {
         list($action, $subject) = fORM::parseMethod($method_name);
         if (in_array($action, array('get', 'encode', 'prepare', 'inspect', 'set'))) {
             $subject = fGrammar::underscorize($subject);
         } else {
             if (in_array($action, array('build', 'count', 'inject', 'link', 'list', 'tally'))) {
                 $subject = fGrammar::singularize($subject);
             }
             $subject = fORM::getRelatedClass($class, $subject);
         }
         self::$method_name_cache[$method_name] = array('action' => $action, 'subject' => $subject);
     } else {
         $action = self::$method_name_cache[$method_name]['action'];
         $subject = self::$method_name_cache[$method_name]['subject'];
     }
     switch ($action) {
         // Value methods
         case 'get':
             return $this->get($subject);
         case 'encode':
             if (isset($parameters[0])) {
                 return $this->encode($subject, $parameters[0]);
             }
             return $this->encode($subject);
         case 'prepare':
             if (isset($parameters[0])) {
                 return $this->prepare($subject, $parameters[0]);
             }
             return $this->prepare($subject);
         case 'inspect':
             if (isset($parameters[0])) {
                 return $this->inspect($subject, $parameters[0]);
             }
             return $this->inspect($subject);
         case 'set':
             if (sizeof($parameters) < 1) {
                 throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name);
             }
             return $this->set($subject, $parameters[0]);
             // Related data methods
         // Related data methods
         case 'associate':
             if (sizeof($parameters) < 1) {
                 throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name);
             }
             $records = $parameters[0];
             $route = isset($parameters[1]) ? $parameters[1] : NULL;
             list($subject, $route, $plural) = self::determineSubject($class, $subject, $route);
             if ($plural) {
                 fORMRelated::associateRecords($class, $this->related_records, $subject, $records, $route);
             } else {
                 fORMRelated::associateRecord($class, $this->related_records, $subject, $records, $route);
             }
             return $this;
         case 'build':
             if (isset($parameters[0])) {
                 return fORMRelated::buildRecords($class, $this->values, $this->related_records, $subject, $parameters[0]);
             }
             return fORMRelated::buildRecords($class, $this->values, $this->related_records, $subject);
         case 'count':
             if (isset($parameters[0])) {
                 return fORMRelated::countRecords($class, $this->values, $this->related_records, $subject, $parameters[0]);
             }
             return fORMRelated::countRecords($class, $this->values, $this->related_records, $subject);
         case 'create':
             if (isset($parameters[0])) {
                 return fORMRelated::createRecord($class, $this->values, $this->related_records, $subject, $parameters[0]);
             }
             return fORMRelated::createRecord($class, $this->values, $this->related_records, $subject);
         case 'has':
             $route = isset($parameters[0]) ? $parameters[0] : NULL;
             list($subject, $route, ) = self::determineSubject($class, $subject, $route);
             return fORMRelated::hasRecords($class, $this->values, $this->related_records, $subject, $route);
         case 'inject':
             if (sizeof($parameters) < 1) {
                 throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name);
             }
             if (isset($parameters[1])) {
                 return fORMRelated::setRecordSet($class, $this->related_records, $subject, $parameters[0], $parameters[1]);
             }
             return fORMRelated::setRecordSet($class, $this->related_records, $subject, $parameters[0]);
         case 'link':
             if (isset($parameters[0])) {
                 fORMRelated::linkRecords($class, $this->related_records, $subject, $parameters[0]);
             } else {
                 fORMRelated::linkRecords($class, $this->related_records, $subject);
             }
             return $this;
         case 'list':
             if (isset($parameters[0])) {
                 return fORMRelated::getPrimaryKeys($class, $this->values, $this->related_records, $subject, $parameters[0]);
             }
             return fORMRelated::getPrimaryKeys($class, $this->values, $this->related_records, $subject);
         case 'populate':
             $route = isset($parameters[0]) ? $parameters[0] : NULL;
             list($subject, $route, ) = self::determineSubject($class, $subject, $route);
             fORMRelated::populateRecords($class, $this->related_records, $subject, $route);
             return $this;
         case 'tally':
             if (sizeof($parameters) < 1) {
                 throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name);
             }
             if (isset($parameters[1])) {
                 return fORMRelated::setCount($class, $this->related_records, $subject, $parameters[0], $parameters[1]);
             }
             return fORMRelated::setCount($class, $this->related_records, $subject, $parameters[0]);
             // Error handler
         // Error handler
         default:
             throw new fProgrammerException('Unknown method, %s(), called', $method_name);
     }
 }
예제 #6
0
 /**
  * Sets the ordering to use when returning an fRecordSet of related objects
  *
  * @param  mixed  $class           The class name or instance of the class this ordering rule applies to
  * @param  string $related_class   The related class we are getting info from
  * @param  array  $order_bys       An array of the order bys for this table.column combination - see fRecordSet::build() for format
  * @param  string $route           The route to the related table, this should be a column name in the current table or a join table name
  * @return void
  */
 public static function setOrderBys($class, $related_class, $order_bys, $route = NULL)
 {
     fActiveRecord::validateClass($related_class);
     fActiveRecord::forceConfigure($related_class);
     $class = fORM::getClass($class);
     $table = fORM::tablize($class);
     $related_class = fORM::getRelatedClass($class, $related_class);
     $related_table = fORM::tablize($related_class);
     $schema = fORMSchema::retrieve($class);
     $route = fORMSchema::getRouteName($schema, $table, $related_table, $route, '*-to-many');
     if (!isset(self::$order_bys[$table])) {
         self::$order_bys[$table] = array();
     }
     if (!isset(self::$order_bys[$table][$related_table])) {
         self::$order_bys[$table][$related_table] = array();
     }
     self::$order_bys[$table][$related_table][$route] = $order_bys;
 }