Ejemplo n.º 1
0
 public function testCustomClassTableMapping()
 {
     $this->assertEquals('users', fORM::tablize('User'));
     $this->assertEquals('User', fORM::classize('users'));
     fORM::mapClassToTable('User', 'person');
     $this->assertEquals('person', fORM::tablize('User'));
     $this->assertEquals('User', fORM::classize('person'));
     $this->assertNotEquals('users', fORM::tablize('User'));
     $this->assertEquals('bicycles', fORM::tablize('Bicycle'));
     $this->assertEquals('Bicycle', fORM::classize('bicycles'));
     fORM::mapClassToTable('Bicycle', 'bike');
     $this->assertEquals('bike', fORM::tablize('Bicycle'));
     $this->assertEquals('Bicycle', fORM::classize('bike'));
     $this->assertNotEquals('bicycles', fORM::tablize('Bicycle'));
 }
Ejemplo n.º 2
0
 protected function populateRelated($recursive = false)
 {
     if ($recursive) {
         $one_to_many_relationships = $schema->getRelationships($table, 'one-to-many');
         foreach ($one_to_many_relationships as $relationship) {
             $route_name = fORMSchema::getRouteNameFromRelationship('one-to-many', $relationship);
             $related_class = fORM::classize($relationship['related_table']);
             $method = 'populate' . fGrammar::pluralize($related_class);
             $this->{$method}(TRUE, $route_name);
         }
         $one_to_one_relationships = $schema->getRelationships($table, 'one-to-one');
         foreach ($one_to_one_relationships as $relationship) {
             $route_name = fORMSchema::getRouteNameFromRelationship('one-to-one', $relationship);
             $related_class = fORM::classize($relationship['related_table']);
             $this->__call('populate' . $related_class, array(TRUE, $route_name));
         }
     }
     return $this;
 }
Ejemplo n.º 3
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;
 }
Ejemplo n.º 4
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;
 }
Ejemplo n.º 5
0
 /**
  * Generates a clone of the current record, removing any auto incremented primary key value and allowing for replicating related records
  * 
  * This method will accept three different sets of parameters:
  * 
  *  - No parameters: this object will be cloned
  *  - A single `TRUE` value: this object plus all many-to-many associations and all child records (recursively) will be cloned
  *  - Any number of plural related record class names: the many-to-many associations or child records that correspond to the classes specified will be cloned
  * 
  * The class names specified can be a simple class name if there is only a
  * single route between the two corresponding database tables. If there is 
  * more than one route between the two tables, the class name should be
  * substituted with a string in the format `'RelatedClass{route}'`.
  * 
  * @param  string $related_class  The plural related class to replicate - see method description for details
  * @param  string ...
  * @return fActiveRecord  The cloned record
  */
 public function replicate($related_class = NULL)
 {
     fActiveRecord::$replicate_level++;
     $class = get_class($this);
     $hash = self::hash($this->values, $class);
     $table = fORM::tablize($class);
     // If the object has not been replicated yet, do it now
     if (!isset(fActiveRecord::$replicate_map[$class])) {
         fActiveRecord::$replicate_map[$class] = array();
     }
     if (!isset(fActiveRecord::$replicate_map[$class][$hash])) {
         fActiveRecord::$replicate_map[$class][$hash] = clone $this;
         // We need the primary key to get a hash, otherwise certain recursive relationships end up losing members
         $pk_columns = fORMSchema::retrieve()->getKeys($table, 'primary');
         if (sizeof($pk_columns) == 1 && fORMSchema::retrieve()->getColumnInfo($table, $pk_columns[0], 'auto_increment')) {
             fActiveRecord::$replicate_map[$class][$hash]->values[$pk_columns[0]] = $this->values[$pk_columns[0]];
         }
     }
     $clone = fActiveRecord::$replicate_map[$class][$hash];
     $parameters = func_get_args();
     $recursive = FALSE;
     $many_to_many_relationships = fORMSchema::retrieve()->getRelationships($table, 'many-to-many');
     $one_to_many_relationships = fORMSchema::retrieve()->getRelationships($table, 'one-to-many');
     // When just TRUE is passed we recursively replicate all related records
     if (sizeof($parameters) == 1 && $parameters[0] === TRUE) {
         $parameters = array();
         $recursive = TRUE;
         foreach ($many_to_many_relationships as $relationship) {
             $parameters[] = fGrammar::pluralize(fORM::classize($relationship['related_table'])) . '{' . $relationship['join_table'] . '}';
         }
         foreach ($one_to_many_relationships as $relationship) {
             $parameters[] = fGrammar::pluralize(fORM::classize($relationship['related_table'])) . '{' . $relationship['related_column'] . '}';
         }
     }
     $record_sets = array();
     foreach ($parameters as $parameter) {
         // Parse the Class{route} strings
         if (strpos($parameter, '{') !== FALSE) {
             $brace = strpos($parameter, '{');
             $related_class = fGrammar::singularize(substr($parameter, 0, $brace));
             $related_table = fORM::tablize($related_class);
             $route = substr($parameter, $brace + 1, -1);
         } else {
             $related_class = fGrammar::singularize($parameter);
             $related_table = fORM::tablize($related_class);
             $route = fORMSchema::getRouteName($table, $related_table);
         }
         // Determine the kind of relationship
         $many_to_many = FALSE;
         $one_to_many = FALSE;
         foreach ($many_to_many_relationships as $relationship) {
             if ($relationship['related_table'] == $related_table && $relationship['join_table'] == $route) {
                 $many_to_many = TRUE;
                 break;
             }
         }
         foreach ($one_to_many_relationships as $relationship) {
             if ($relationship['related_table'] == $related_table && $relationship['related_column'] == $route) {
                 $one_to_many = TRUE;
                 break;
             }
         }
         if (!$many_to_many && !$one_to_many) {
             throw new fProgrammerException('The related class specified, %1$s, does not appear to be in a many-to-many or one-to-many relationship with %$2s', $parameter, get_class($this));
         }
         // Get the related records
         $record_set = fORMRelated::buildRecords($class, $this->values, $this->related_records, $related_class, $route);
         // One-to-many records need to be replicated, possibly recursively
         if ($one_to_many) {
             if ($recursive) {
                 $records = $record_set->call('replicate', TRUE);
             } else {
                 $records = $record_set->call('replicate');
             }
             $record_set = fRecordSet::buildFromArray($related_class, $records);
             $record_set->call('set' . fGrammar::camelize($route, TRUE), NULL);
         }
         // Cause the related records to be associated with the new clone
         fORMRelated::associateRecords($class, $clone->related_records, $related_class, $record_set, $route);
     }
     fActiveRecord::$replicate_level--;
     if (!fActiveRecord::$replicate_level) {
         // This removes the primary keys we had added back in for proper duplicate detection
         foreach (fActiveRecord::$replicate_map as $class => $records) {
             $table = fORM::tablize($class);
             $pk_columns = fORMSchema::retrieve()->getKeys($table, 'primary');
             if (sizeof($pk_columns) != 1 || !fORMSchema::retrieve()->getColumnInfo($table, $pk_columns[0], 'auto_increment')) {
                 continue;
             }
             foreach ($records as $hash => $record) {
                 $record->values[$pk_columns[0]] = NULL;
             }
         }
         fActiveRecord::$replicate_map = array();
     }
     return $clone;
 }
Ejemplo n.º 6
0
 /**
  * Recursivly builds records.
  *
  * @param array* $completed_fixtures
  *   Completed records is stored in this array
  * @param $fixture_data
  *   Build records of this fixture
  */
 private function buildRecords(&$completed_fixtures, $fixture_name, $traverse = TRUE)
 {
     if (array_key_exists($fixture_name, $completed_fixtures)) {
         return;
     }
     // Load data
     if (isset($this->fixture_data[$fixture_name]) === FALSE) {
         $this->loadFixture($fixture_name);
     }
     $class_name = fORM::classize($fixture_name);
     // If the class does not exists created it
     if (class_exists($class_name) === FALSE) {
         fORM::defineActiveRecordClass($class_name);
     }
     // Create the records
     $method_name = NULL;
     $record = NULL;
     $records = array();
     foreach ($this->fixture_data[$fixture_name] as $record_data) {
         $record = new $class_name();
         foreach ($record_data as $key => $value) {
             $method_name = 'set' . fGrammar::camelize($key, $upper = TRUE);
             $value = $this->applyHookCallbacks(self::PreSetBuildHook, $fixture_name, $key, $value);
             if ($this->isRelationshipKey($fixture_name, $key)) {
                 $related_table = $this->getRelatedTable($fixture_name, $key);
                 $required = $this->isRequiredKey($fixture_name, $key);
                 if ($traverse && array_key_exists($related_table, $completed_fixtures) === FALSE && $fixture_name !== $related_table) {
                     if (isset($value) && array_key_exists($related_table, $this->fixture_sources)) {
                         $this->buildRecords($completed_fixtures, $related_table);
                         array_unshift($this->tables_to_tear_down, $related_table);
                     }
                 }
             }
             $record->{$method_name}($value);
         }
         $record->store();
         $records[] = $record;
     }
     $completed_fixtures[$fixture_name] = fRecordSet::buildFromArray($class_name, $records);
 }
Ejemplo n.º 7
0
 /**
  * Escapes a value for a DB call based on database schema
  * 
  * @internal
  * 
  * @param  string $table                The table to store the value
  * @param  string $column               The column to store the value in, may also be shorthand column name like `table.column` or `table=>related_table.column` or concatenated column names like `table.column||table.other_column`
  * @param  mixed  $value                The value to escape
  * @param  string $comparison_operator  Optional: should be `'='`, `'!='`, `'!'`, `'<>'`, `'<'`, `'<='`, `'>'`, `'>='`, `'IN'`, `'NOT IN'`
  * @return string  The SQL-ready representation of the value
  */
 public static function escapeBySchema($table, $column, $value, $comparison_operator = NULL)
 {
     // handle concatenated column names
     if (preg_match('#\\|\\|#', $column)) {
         if (is_object($value) && is_callable(array($value, '__toString'))) {
             $value = $value->__toString();
         } elseif (is_object($value)) {
             $value = (string) $value;
         }
         $column_info = array('not_null' => FALSE, 'default' => NULL, 'type' => 'varchar');
     } else {
         // Handle shorthand column names like table.column and table=>related_table.column
         if (preg_match('#(\\w+)(?:\\{\\w+\\})?\\.(\\w+)$#D', $column, $match)) {
             $table = $match[1];
             $column = $match[2];
         }
         $column_info = fORMSchema::retrieve()->getColumnInfo($table, $column);
         // Some of the tables being escaped for are linking tables that might break with classize()
         if (is_object($value)) {
             $class = fORM::classize($table);
             $value = fORM::scalarize($class, $column, $value);
         }
     }
     if ($comparison_operator !== NULL) {
         $comparison_operator = strtr($comparison_operator, array('!' => '<>', '!=' => '<>'));
     }
     $valid_comparison_operators = array('=', '!=', '!', '<>', '<=', '<', '>=', '>', 'IN', 'NOT IN');
     if ($comparison_operator !== NULL && !in_array(strtoupper($comparison_operator), $valid_comparison_operators)) {
         throw new fProgrammerException('The comparison operator specified, %1$s, is invalid. Must be one of: %2$s.', $comparison_operator, join(', ', $valid_comparison_operators));
     }
     $co = is_null($comparison_operator) ? '' : ' ' . strtoupper($comparison_operator) . ' ';
     if ($column_info['not_null'] && $value === NULL && $column_info['default'] !== NULL) {
         $value = $column_info['default'];
     }
     if (is_null($value)) {
         $prepared_value = 'NULL';
     } else {
         $prepared_value = self::retrieve()->escape($column_info['type'], $value);
     }
     if ($prepared_value == 'NULL') {
         if ($co) {
             if (in_array(trim($co), array('=', 'IN'))) {
                 $co = ' IS ';
             } elseif (in_array(trim($co), array('<>', 'NOT IN'))) {
                 $co = ' IS NOT ';
             }
         }
     }
     return $co . $prepared_value;
 }