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; }
/** * 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) { fORM::callHookCallbacks($this, 'pre::replicate()', $this->values, $this->old_values, $this->related_records, $this->cache, fActiveRecord::$replicate_level); fActiveRecord::$replicate_level++; $class = get_class($this); $hash = self::hash($this->values, $class); $schema = fORMSchema::retrieve($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 = $schema->getKeys($table, 'primary'); if (sizeof($pk_columns) == 1 && $schema->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 = $schema->getRelationships($table, 'many-to-many'); $one_to_many_relationships = $schema->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_class = fORM::getRelatedClass($class, $related_class); $related_table = fORM::tablize($related_class); $route = substr($parameter, $brace + 1, -1); } else { $related_class = fGrammar::singularize($parameter); $related_class = fORM::getRelatedClass($class, $related_class); $related_table = fORM::tablize($related_class); $route = fORMSchema::getRouteName($schema, $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 = $schema->getKeys($table, 'primary'); if (sizeof($pk_columns) != 1 || !$schema->getColumnInfo($table, $pk_columns[0], 'auto_increment')) { continue; } foreach ($records as $hash => $record) { $record->values[$pk_columns[0]] = NULL; } } fActiveRecord::$replicate_map = array(); } fORM::callHookCallbacks($this, 'post::replicate()', $this->values, $this->old_values, $this->related_records, $this->cache, fActiveRecord::$replicate_level); fORM::callHookCallbacks($clone, 'cloned::replicate()', $clone->values, $clone->old_values, $clone->related_records, $clone->cache, fActiveRecord::$replicate_level); return $clone; }
/** * Adds information about methods provided by this class to fActiveRecord * * @internal * * @param string $class The class to reflect the related record methods for * @param array &$signatures The associative array of `{method_name} => {signature}` * @param boolean $include_doc_comments If the doc block comments for each method should be included * @return void */ public static function reflect($class, &$signatures, $include_doc_comments) { $table = fORM::tablize($class); $schema = fORMSchema::retrieve($class); $one_to_one_relationships = $schema->getRelationships($table, 'one-to-one'); $one_to_many_relationships = $schema->getRelationships($table, 'one-to-many'); $many_to_one_relationships = $schema->getRelationships($table, 'many-to-one'); $many_to_many_relationships = $schema->getRelationships($table, 'many-to-many'); $to_one_relationships = array_merge($one_to_one_relationships, $many_to_one_relationships); $to_many_relationships = array_merge($one_to_many_relationships, $many_to_many_relationships); $to_one_created = array(); foreach ($to_one_relationships as $relationship) { $related_class = fORM::classize($relationship['related_table']); $related_class = fORM::getRelatedClass($class, $related_class); if (isset($to_one_created[$related_class])) { continue; } $routes = fORMSchema::getRoutes($schema, $table, $relationship['related_table'], '*-to-one'); $route_names = array(); foreach ($routes as $route) { $route_names[] = fORMSchema::getRouteNameFromRelationship('*-to-one', $route); } $signature = ''; if ($include_doc_comments) { $signature .= "/**\n"; $signature .= " * Creates the related " . $related_class . "\n"; $signature .= " * \n"; if (sizeof($route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $route_names) . "'.\n"; } $signature .= " * @return " . $related_class . " The related object\n"; $signature .= " */\n"; } $create_method = 'create' . $related_class; $signature .= 'public function ' . $create_method . '('; if (sizeof($route_names) > 1) { $signature .= '$route'; } $signature .= ')'; $signatures[$create_method] = $signature; $to_one_created[$related_class] = TRUE; } $one_to_one_created = array(); foreach ($one_to_one_relationships as $relationship) { $related_class = fORM::classize($relationship['related_table']); $related_class = fORM::getRelatedClass($class, $related_class); if (isset($one_to_one_created[$related_class])) { continue; } $routes = fORMSchema::getRoutes($schema, $table, $relationship['related_table'], 'one-to-one'); $route_names = array(); foreach ($routes as $route) { $route_names[] = fORMSchema::getRouteNameFromRelationship('one-to-one', $route); } $signature = ''; if ($include_doc_comments) { $signature .= "/**\n"; $signature .= " * Populates the related " . $related_class . "\n"; $signature .= " * \n"; if (sizeof($route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $route_names) . "'.\n"; } $signature .= " * @return fActiveRecord The record object, to allow for method chaining\n"; $signature .= " */\n"; } $populate_method = 'populate' . $related_class; $signature .= 'public function ' . $populate_method . '('; if (sizeof($route_names) > 1) { $signature .= '$route'; } $signature .= ')'; $signatures[$populate_method] = $signature; $signature = ''; if ($include_doc_comments) { $signature .= "/**\n"; $signature .= " * Associates the related " . $related_class . " to this record\n"; $signature .= " * \n"; $signature .= " * @param fActiveRecord|array|string|integer \$record The record, or the primary key of the record, to associate\n"; if (sizeof($route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $route_names) . "'.\n"; } $signature .= " * @return fActiveRecord The record object, to allow for method chaining\n"; $signature .= " */\n"; } $associate_method = 'associate' . $related_class; $signature .= 'public function ' . $associate_method . '($record'; if (sizeof($route_names) > 1) { $signature .= ', $route'; } $signature .= ')'; $signatures[$associate_method] = $signature; $signature = ''; if ($include_doc_comments) { $signature .= "/**\n"; $signature .= " * Indicates if a related " . $related_class . " exists\n"; $signature .= " * \n"; if (sizeof($route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $route_names) . "'.\n"; } $signature .= " * @return boolean If a related record exists\n"; $signature .= " */\n"; } $has_method = 'has' . $related_class; $signature .= 'public function ' . $has_method . '($record'; if (sizeof($route_names) > 1) { $signature .= ', $route'; } $signature .= ')'; $signatures[$has_method] = $signature; $one_to_one_created[$related_class] = TRUE; } $to_many_created = array(); foreach ($to_many_relationships as $relationship) { $related_class = fORM::classize($relationship['related_table']); $related_class = fORM::getRelatedClass($class, $related_class); if (isset($to_many_created[$related_class])) { continue; } $routes = fORMSchema::getRoutes($schema, $table, $relationship['related_table'], '*-to-many'); $route_names = array(); $many_to_many_route_names = array(); $one_to_many_route_names = array(); foreach ($routes as $route) { if (isset($route['join_table'])) { $route_name = fORMSchema::getRouteNameFromRelationship('many-to-many', $route); $route_names[] = $route_name; $many_to_many_route_names[] = $route_name; } else { $route_name = fORMSchema::getRouteNameFromRelationship('one-to-many', $route); $route_names[] = $route_name; $one_to_many_route_names[] = $route_name; } } if ($one_to_many_route_names) { $signature = ''; if ($include_doc_comments) { $related_table = fORM::tablize($related_class); $signature .= "/**\n"; $signature .= " * Calls the ::populate() method for multiple child " . $related_class . " records. Uses request value arrays in the form " . $related_table . "::{column_name}[].\n"; $signature .= " * \n"; if (sizeof($one_to_many_route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $one_to_many_route_names) . "'.\n"; } $signature .= " * @return fActiveRecord The record object, to allow for method chaining\n"; $signature .= " */\n"; } $populate_related_method = 'populate' . fGrammar::pluralize($related_class); $signature .= 'public function ' . $populate_related_method . '('; if (sizeof($one_to_many_route_names) > 1) { $signature .= '$route'; } $signature .= ')'; $signatures[$populate_related_method] = $signature; } if ($many_to_many_route_names) { $signature = ''; if ($include_doc_comments) { $related_table = fORM::tablize($related_class); $signature .= "/**\n"; $signature .= " * Creates entries in the appropriate joining table to create associations with the specified " . $related_class . " records. Uses request value array(s) in the form " . $related_table . "::{primary_key_column_name(s)}[].\n"; $signature .= " * \n"; if (sizeof($many_to_many_route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $many_to_many_route_names) . "'.\n"; } $signature .= " * @return fActiveRecord The record object, to allow for method chaining\n"; $signature .= " */\n"; } $link_related_method = 'link' . fGrammar::pluralize($related_class); $signature .= 'public function ' . $link_related_method . '('; if (sizeof($many_to_many_route_names) > 1) { $signature .= '$route'; } $signature .= ')'; $signatures[$link_related_method] = $signature; $signature = ''; if ($include_doc_comments) { $related_table = fORM::tablize($related_class); $signature .= "/**\n"; $signature .= " * Creates entries in the appropriate joining table to create associations with the specified " . $related_class . " records\n"; $signature .= " * \n"; $signature .= " * @param fRecordSet|array \$records_to_associate The records to associate - should be an fRecords, an array of records or an array of primary keys\n"; if (sizeof($many_to_many_route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $many_to_many_route_names) . "'.\n"; } $signature .= " * @return fActiveRecord The record object, to allow for method chaining\n"; $signature .= " */\n"; } $associate_related_method = 'associate' . fGrammar::pluralize($related_class); $signature .= 'public function ' . $associate_related_method . '($records_to_associate'; if (sizeof($many_to_many_route_names) > 1) { $signature .= ', $route'; } $signature .= ')'; $signatures[$associate_related_method] = $signature; } $signature = ''; if ($include_doc_comments) { $signature .= "/**\n"; $signature .= " * Builds an fRecordSet of the related " . $related_class . " objects\n"; $signature .= " * \n"; if (sizeof($route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $route_names) . "'.\n"; } $signature .= " * @return fRecordSet A record set of the related " . $related_class . " objects\n"; $signature .= " */\n"; } $build_method = 'build' . fGrammar::pluralize($related_class); $signature .= 'public function ' . $build_method . '('; if (sizeof($route_names) > 1) { $signature .= '$route'; } $signature .= ')'; $signatures[$build_method] = $signature; $signature = ''; if ($include_doc_comments) { $signature .= "/**\n"; $signature .= " * Indicates if related " . $related_class . " objects exist\n"; $signature .= " * \n"; if (sizeof($route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $route_names) . "'.\n"; } $signature .= " * @return boolean If related " . $related_class . " objects exist\n"; $signature .= " */\n"; } $has_method = 'has' . fGrammar::pluralize($related_class); $signature .= 'public function ' . $has_method . '('; if (sizeof($route_names) > 1) { $signature .= '$route'; } $signature .= ')'; $signatures[$has_method] = $signature; $signature = ''; if ($include_doc_comments) { $signature .= "/**\n"; $signature .= " * Returns an array of the primary keys for the related " . $related_class . " objects\n"; $signature .= " * \n"; if (sizeof($route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $route_names) . "'.\n"; } $signature .= " * @return array The primary keys of the related " . $related_class . " objects\n"; $signature .= " */\n"; } $list_method = 'list' . fGrammar::pluralize($related_class); $signature .= 'public function ' . $list_method . '('; if (sizeof($route_names) > 1) { $signature .= '$route'; } $signature .= ')'; $signatures[$list_method] = $signature; $signature = ''; if ($include_doc_comments) { $signature .= "/**\n"; $signature .= " * Counts the number of related " . $related_class . " objects\n"; $signature .= " * \n"; if (sizeof($route_names) > 1) { $signature .= " * @param string \$route The route to the related class. Must be one of: '" . join("', '", $route_names) . "'.\n"; } $signature .= " * @return integer The number related " . $related_class . " objects\n"; $signature .= " */\n"; } $count_method = 'count' . fGrammar::pluralize($related_class); $signature .= 'public function ' . $count_method . '('; if (sizeof($route_names) > 1) { $signature .= '$route'; } $signature .= ')'; $signatures[$count_method] = $signature; $to_many_created[$related_class] = TRUE; } }
/** * Throws an fEmptySetException if the record set is empty * * @throws fEmptySetException When there are no record in the set * * @param string $message The message to use for the exception if there are no records in this set * @return fRecordSet The record set object, to allow for method chaining */ public function tossIfEmpty($message = NULL) { if ($this->records) { return $this; } if ($message === NULL) { if (is_array($this->class)) { $names = array_map(array('fORM', 'getRecordName'), $this->class); $names = array_map(array('fGrammar', 'pluralize'), $names); $name = join(', ', $names); } else { $name = fGrammar::pluralize(fORM::getRecordName($this->class)); } $message = self::compose('No %s could be found', $name); } throw new fEmptySetException($message); }
/** * Validates against a *-to-many one or more rule * * @param fActiveRecord $object The object being checked * @param array &$values The values for the object * @param array &$related_records The related records for the object * @param string $related_class The name of the related class * @param string $route The name of the route from the class to the related class * @return string An error message for the rule */ private static function checkRelatedOneOrMoreRule($object, &$values, &$related_records, $related_class, $route) { $related_table = fORM::tablize($related_class); $class = get_class($object); $exists = $object->exists(); $records_are_set = isset($related_records[$related_table][$route]); $has_records = $records_are_set && $related_records[$related_table][$route]['count']; if ($exists && (!$records_are_set || $has_records)) { return; } if (!$exists && $has_records) { return; } return self::compose('%sPlease select at least one', fValidationException::formatField(fGrammar::pluralize(fORMRelated::getRelatedRecordName($class, $related_class, $route)))); }
/** * Takes a class name (or class) and turns it into a table name - Uses custom mapping if set * * @param string $class The class name * @return string The table name */ public static function tablize($class) { if (!isset(self::$class_table_map[$class])) { self::$class_table_map[$class] = fGrammar::underscorize(fGrammar::pluralize(preg_replace('#^.*\\\\#', '', $class))); } return self::$class_table_map[$class]; }
/** * @dataProvider singularizePluralizeProvider */ public function testPluralizer($output, $input) { $this->assertEquals($output, fGrammar::pluralize($input)); }
/** * Takes a class name (or class) and turns it into a table name - Uses custom mapping if set * * @param mixed $class he class name or instance of the class * @return string The table name */ public static function tablize($class) { if (!isset(self::$class_table_map[$class])) { self::$class_table_map[$class] = fGrammar::underscorize(fGrammar::pluralize($class)); } return self::$class_table_map[$class]; }