Example #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;
 }
Example #2
0
 /**
  * Deletes old files for this record that have been replaced by new ones
  * 
  * @internal
  * 
  * @param  fActiveRecord $object            The fActiveRecord instance
  * @param  array         &$values           The current values
  * @param  array         &$old_values       The old values
  * @param  array         &$related_records  Any records related to this record
  * @param  array         &$cache            The cache array for the record
  * @return void
  */
 public static function deleteOld($object, &$values, &$old_values, &$related_records, &$cache)
 {
     $class = get_class($object);
     // Remove the old files for the column
     foreach (self::$file_upload_columns[$class] as $column => $directory) {
         $current_file = $values[$column];
         foreach (fActiveRecord::retrieveOld($old_values, $column, array(), TRUE) as $file) {
             if ($file instanceof fFile && (!$current_file instanceof fFile || $current_file->getPath() != $file->getPath())) {
                 $file->delete();
             }
         }
     }
 }
 /**
  * Validates values against unique constraints
  *
  * @param  fSchema        $schema       The schema object for the object
  * @param  fActiveRecord  $object       The instance of the class to check
  * @param  array          &$values      The values to check
  * @param  array          &$old_values  The old values for the record
  * @return array  An aray of error messages for the unique constraints
  */
 private static function checkUniqueConstraints($schema, $object, &$values, &$old_values)
 {
     $class = get_class($object);
     $table = fORM::tablize($class);
     $db = fORMDatabase::retrieve($class, 'read');
     $key_info = $schema->getKeys($table);
     $pk_columns = $key_info['primary'];
     $unique_keys = $key_info['unique'];
     $messages = array();
     foreach ($unique_keys as $unique_columns) {
         settype($unique_columns, 'array');
         // NULL values are unique
         $found_not_null = FALSE;
         foreach ($unique_columns as $unique_column) {
             if ($values[$unique_column] !== NULL) {
                 $found_not_null = TRUE;
             }
         }
         if (!$found_not_null) {
             continue;
         }
         $params = array("SELECT %r FROM %r WHERE ", $key_info['primary'], $table);
         $column_info = $schema->getColumnInfo($table);
         $conditions = array();
         foreach ($unique_columns as $unique_column) {
             $value = $values[$unique_column];
             // This makes sure the query performs the way an insert will
             if ($value === NULL && $column_info[$unique_column]['not_null'] && $column_info[$unique_column]['default'] !== NULL) {
                 $value = $column_info[$unique_column]['default'];
             }
             if (self::isCaseInsensitive($class, $unique_column) && self::stringlike($value)) {
                 $condition = fORMDatabase::makeCondition($schema, $table, $unique_column, '=', $value);
                 $conditions[] = str_replace('%r', 'LOWER(%r)', $condition);
                 $params[] = $table . '.' . $unique_column;
                 $params[] = fUTF8::lower($value);
             } else {
                 $conditions[] = fORMDatabase::makeCondition($schema, $table, $unique_column, '=', $value);
                 $params[] = $table . '.' . $unique_column;
                 $params[] = $value;
             }
         }
         $params[0] .= join(' AND ', $conditions);
         if ($object->exists()) {
             foreach ($pk_columns as $pk_column) {
                 $value = fActiveRecord::retrieveOld($old_values, $pk_column, $values[$pk_column]);
                 $params[0] .= ' AND ' . fORMDatabase::makeCondition($schema, $table, $pk_column, '<>', $value);
                 $params[] = $table . '.' . $pk_column;
                 $params[] = $value;
             }
         }
         try {
             $result = call_user_func_array($db->translatedQuery, $params);
             $result->tossIfNoRows();
             // If an exception was not throw, we have existing values
             $column_names = array();
             foreach ($unique_columns as $unique_column) {
                 $column_names[] = fORM::getColumnName($class, $unique_column);
             }
             if (sizeof($column_names) == 1) {
                 $messages[join('', $unique_columns)] = self::compose('%sThe value specified must be unique, however it already exists', fValidationException::formatField(join('', $column_names)));
             } else {
                 $messages[join(',', $unique_columns)] = self::compose('%sThe values specified must be a unique combination, however the specified combination already exists', fValidationException::formatField(join(', ', $column_names)));
             }
         } catch (fNoRowsException $e) {
         }
     }
     return $messages;
 }
 /**
  * Makes sure the ordering value is sane, removes error messages about missing values
  *
  * @internal
  *
  * @param  fActiveRecord $object                The fActiveRecord instance
  * @param  array         &$values               The current values
  * @param  array         &$old_values           The old values
  * @param  array         &$related_records      Any records related to this record
  * @param  array         &$cache                The cache array for the record
  * @param  array         &$validation_messages  An array of ordered validation messages
  * @return void
  */
 public static function validate($object, &$values, &$old_values, &$related_records, &$cache, &$validation_messages)
 {
     $class = get_class($object);
     $table = fORM::tablize($class);
     $db = fORMDatabase::retrieve($class, 'read');
     $schema = fORMSchema::retrieve($class);
     foreach (self::$ordering_columns[$class] as $column => $other_columns) {
         $current_value = $values[$column];
         $old_value = fActiveRecord::retrieveOld($old_values, $column);
         $params = array("SELECT MAX(%r) FROM %r", $column, $table);
         if ($other_columns) {
             $params[0] .= " WHERE ";
             $params = self::addOtherFieldsWhereParams($schema, $params, $table, $other_columns, $values);
         }
         $current_max_value = (int) call_user_func_array($db->translatedQuery, $params)->fetchScalar();
         $new_max_value = $current_max_value;
         if ($new_set = self::isInNewSet($column, $other_columns, $values, $old_values)) {
             $new_max_value = $current_max_value + 1;
             $new_set_new_value = fActiveRecord::changed($values, $old_values, $column);
         }
         $column_name = fORM::getColumnName($class, $column);
         // Remove any previous validation warnings
         $filtered_messages = array();
         foreach ($validation_messages as $validation_column => $validation_message) {
             if (!preg_match('#(^|,)' . preg_quote($column, '#') . '(,|$)#D', $validation_column)) {
                 $filtered_messages[$validation_column] = $validation_message;
             }
         }
         $validation_messages = $filtered_messages;
         // If we have a completely empty value, we don't need to validate since a valid value will be generated
         if ($current_value === '' || $current_value === NULL) {
             continue;
         }
         if (!is_numeric($current_value) || strlen((int) $current_value) != strlen($current_value)) {
             $validation_messages[$column] = self::compose('%sPlease enter an integer', fValidationException::formatField($column_name));
         } elseif ($current_value < 1) {
             $validation_messages[$column] = self::compose('%sThe value can not be less than 1', fValidationException::formatField($column_name));
         }
     }
 }
Example #5
0
 /**
  * Makes sure the ordering value is sane, removes error messages about missing values
  * 
  * @internal
  * 
  * @param  fActiveRecord $object                The fActiveRecord instance
  * @param  array         &$values               The current values
  * @param  array         &$old_values           The old values
  * @param  array         &$related_records      Any records related to this record
  * @param  array         &$cache                The cache array for the record
  * @param  array         &$validation_messages  An array of ordered validation messages
  * @return void
  */
 public static function validate($object, &$values, &$old_values, &$related_records, &$cache, &$validation_messages)
 {
     $class = get_class($object);
     $table = fORM::tablize($class);
     $column = self::$ordering_columns[$class]['column'];
     $other_columns = self::$ordering_columns[$class]['other_columns'];
     $current_value = $values[$column];
     $old_value = fActiveRecord::retrieveOld($old_values, $column);
     $sql = "SELECT max(" . $column . ") FROM " . $table;
     if ($other_columns) {
         $sql .= " WHERE " . self::createOtherFieldsWhereClause($table, $other_columns, $values);
     }
     $current_max_value = (int) fORMDatabase::retrieve()->translatedQuery($sql)->fetchScalar();
     $new_max_value = $current_max_value;
     if ($new_set = self::isInNewSet($column, $other_columns, $values, $old_values)) {
         $new_max_value = $current_max_value + 1;
         $new_set_new_value = fActiveRecord::changed($values, $old_values, $column);
     }
     $column_name = fORM::getColumnName($class, $column);
     // Remove any previous validation warnings
     $filtered_messages = array();
     foreach ($validation_messages as $validation_message) {
         if (!preg_match('#^' . str_replace('___', '(.*?)', preg_quote(fValidationException::formatField('___' . $column_name . '___'), '#')) . '#', $validation_message)) {
             $filtered_messages[] = $validation_message;
         }
     }
     $validation_messages = $filtered_messages;
     // If we have a completely empty value, we don't need to validate since a valid value will be generated
     if ($current_value === '' || $current_value === NULL) {
         return;
     }
     if (!is_numeric($current_value) || strlen((int) $current_value) != strlen($current_value)) {
         $validation_messages[] = self::compose('%sPlease enter an integer', fValidationException::formatField($column_name));
     } elseif ($current_value < 1) {
         $validation_messages[] = self::compose('%sThe value can not be less than 1', fValidationException::formatField($column_name));
     }
 }
Example #6
0
 /**
  * Add the appropriate SQL and params for a `WHERE` clause condition for primary keys of the table specified
  * 
  * @internal
  * 
  * @param  fSchema $schema        The schema for the database the query will be run on
  * @param  array   $params        The currently constructed params for fDatabase::query() - the first param should be a SQL statement
  * @param  string  $table         The table to build the where clause for
  * @param  string  $table_alias   The alias for the table
  * @param  array   &$values       The values array for the fActiveRecord object
  * @param  array   &$old_values   The old values array for the fActiveRecord object
  * @return array  The params to pass to fDatabase::query(), including the new primary key where condition
  */
 public static function addPrimaryKeyWhereParams($schema, $params, $table, $table_alias, &$values, &$old_values)
 {
     $pk_columns = $schema->getKeys($table, 'primary');
     $column_info = $schema->getColumnInfo($table);
     $conditions = array();
     foreach ($pk_columns as $pk_column) {
         $value = fActiveRecord::retrieveOld($old_values, $pk_column, $values[$pk_column]);
         // This makes sure the query performs the way an insert will
         if ($value === NULL && $column_info[$pk_column]['not_null'] && $column_info[$pk_column]['default'] !== NULL) {
             $value = $column_info[$pk_column]['default'];
         }
         $params[] = $table_alias . '.' . $pk_column;
         $params[] = $value;
         $conditions[] = self::makeCondition($schema, $table, $pk_column, '=', $value);
     }
     $params[0] .= join(' AND ', $conditions);
     return $params;
 }
Example #7
0
 /**
  * Validates values against unique constraints
  *
  * @param  fActiveRecord  $object       The instance of the class to check
  * @param  array          &$values      The values to check
  * @param  array          &$old_values  The old values for the record
  * @return string  An error message for the unique constraints
  */
 private static function checkUniqueConstraints($object, &$values, &$old_values)
 {
     $class = get_class($object);
     $table = fORM::tablize($class);
     $key_info = fORMSchema::retrieve()->getKeys($table);
     $primary_keys = $key_info['primary'];
     $unique_keys = $key_info['unique'];
     foreach ($unique_keys as $unique_columns) {
         settype($unique_columns, 'array');
         // NULL values are unique
         $found_not_null = FALSE;
         foreach ($unique_columns as $unique_column) {
             if ($values[$unique_column] !== NULL) {
                 $found_not_null = TRUE;
             }
         }
         if (!$found_not_null) {
             continue;
         }
         $sql = "SELECT " . join(', ', $key_info['primary']) . " FROM " . $table . " WHERE ";
         $first = TRUE;
         foreach ($unique_columns as $unique_column) {
             if ($first) {
                 $first = FALSE;
             } else {
                 $sql .= " AND ";
             }
             $value = $values[$unique_column];
             if (self::isCaseInsensitive($class, $unique_column) && self::stringlike($value)) {
                 $sql .= 'LOWER(' . $unique_column . ')' . fORMDatabase::escapeBySchema($table, $unique_column, fUTF8::lower($value), '=');
             } else {
                 $sql .= $unique_column . fORMDatabase::escapeBySchema($table, $unique_column, $value, '=');
             }
         }
         if ($object->exists()) {
             foreach ($primary_keys as $primary_key) {
                 $value = fActiveRecord::retrieveOld($old_values, $primary_key, $values[$primary_key]);
                 $sql .= ' AND ' . $primary_key . fORMDatabase::escapeBySchema($table, $primary_key, $value, '<>');
             }
         }
         try {
             $result = fORMDatabase::retrieve()->translatedQuery($sql);
             $result->tossIfNoRows();
             // If an exception was not throw, we have existing values
             $column_names = array();
             foreach ($unique_columns as $unique_column) {
                 $column_names[] = fORM::getColumnName($class, $unique_column);
             }
             if (sizeof($column_names) == 1) {
                 return self::compose('%sThe value specified must be unique, however it already exists', fValidationException::formatField(join('', $column_names)));
             } else {
                 return self::compose('%sThe values specified must be a unique combination, however the specified combination already exists', fValidationException::formatField(join(', ', $column_names)));
             }
         } catch (fNoRowsException $e) {
         }
     }
 }