/** * Takes information from a method call and determines the subject, route and if subject was plural * * @param string $class The class the method was called on * @param string $subject An underscore_notation subject - either a singular or plural class name * @param string $route The route to the subject * @return array An array with the structure: array(0 => $subject, 1 => $route, 2 => $plural) */ private static function determineSubject($class, $subject, $route) { $schema = fORMSchema::retrieve($class); $table = fORM::tablize($class); $type = '*-to-many'; $plural = FALSE; // one-to-many relationships need to use plural forms $singular_form = fGrammar::singularize($subject, TRUE); if ($singular_form && fORM::isClassMappedToTable($singular_form)) { $subject = $singular_form; $plural = TRUE; } elseif (!fORM::isClassMappedToTable($subject) && in_array(fGrammar::underscorize($subject), $schema->getTables())) { $subject = fGrammar::singularize($subject); $plural = TRUE; } $related_table = fORM::tablize($subject); $one_to_one = fORMSchema::isOneToOne($schema, $table, $related_table, $route); if ($one_to_one) { $type = 'one-to-one'; } if ($one_to_one && $plural || !$plural && !$one_to_one) { throw new fProgrammerException('The table %1$s is not in a %2$srelationship with the table %3$s', $table, $type, $related_table); } $route = fORMSchema::getRouteName($schema, $table, $related_table, $route, $type); return array($subject, $route, $plural); }
/** * 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` and `link` verbs * for records in many-to-many relationships; `build`, `count` and * `populate` verbs for all related records in one-to-many relationships * and the `create` verb for all related records in *-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); 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); } $table = fORM::tablize($class); $records = $parameters[0]; $route = isset($parameters[1]) ? $parameters[1] : NULL; $plural = FALSE; // one-to-many relationships need to use plural forms if (in_array($subject, fORMSchema::retrieve()->getTables())) { if (fORMSchema::isOneToOne($table, $subject, $route)) { throw new fProgrammerException('The table %1$s is not in a %2$srelationship with the table %3$s', $table, '*-to-many ', $subject); } $subject = fGrammar::singularize($subject); $plural = TRUE; } $subject = fGrammar::camelize($subject, TRUE); // This handles one-to-many and many-to-many relationships if ($plural) { return fORMRelated::associateRecords($class, $this->related_records, $subject, $records, $route); } // This handles one-to-one relationships return fORMRelated::associateRecord($class, $this->related_records, $subject, $records, $route); case 'build': $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); 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': $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); 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': $subject = fGrammar::camelize($subject, TRUE); 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 'inject': if (sizeof($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); 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': $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); if (isset($parameters[0])) { return fORMRelated::linkRecords($class, $this->related_records, $subject, $parameters[0]); } return fORMRelated::linkRecords($class, $this->related_records, $subject); case 'list': $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); 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': $table = fORM::tablize($class); $route = isset($parameters[0]) ? $parameters[0] : NULL; // one-to-many relationships need to use plural forms if (in_array($subject, fORMSchema::retrieve()->getTables())) { if (fORMSchema::isOneToOne($table, $subject, $route)) { throw new fProgrammerException('The table %1$s is not in a%2$srelationship with the table %3$s', $table, ' one-to-many ', $subject); } $subject = fGrammar::singularize($subject); } $subject = fGrammar::camelize($subject, TRUE); return fORMRelated::populateRecords($class, $this->related_records, $subject, $route); case 'tally': if (sizeof($parameters) < 1) { throw new fProgrammerException('The method, %s(), requires at least one parameter', $method_name); } $subject = fGrammar::singularize($subject); $subject = fGrammar::camelize($subject, TRUE); 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); } }
/** * Validates one-to-* related records * * @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 * @param string $related_class The name of the class for this record set * @param string $route The route between the table and related table * @return array An array of validation messages */ private static function validateOneToStar($class, &$values, &$related_records, $related_class, $route) { $schema = fORMSchema::retrieve($class); $table = fORM::tablize($class); $related_table = fORM::tablize($related_class); $relationship = fORMSchema::getRoute($schema, $table, $related_table, $route); $first_pk_column = self::determineFirstPKColumn($class, $related_class, $route); $filter = self::determineRequestFilter($class, $related_class, $route); $pk_field = $filter . $first_pk_column; $input_keys = array_keys(fRequest::get($pk_field, 'array', array())); $related_record_name = self::getRelatedRecordName($class, $related_class, $route); $messages = array(); $one_to_one = fORMSchema::isOneToOne($schema, $table, $related_table, $route); if ($one_to_one) { $records = array(self::createRecord($class, $values, $related_records, $related_class, $route)); } else { $records = self::buildRecords($class, $values, $related_records, $related_class, $route); } foreach ($records as $i => $record) { fRequest::filter($filter, isset($input_keys[$i]) ? $input_keys[$i] : $i); $record_messages = $record->validate(TRUE); foreach ($record_messages as $column => $record_message) { // Ignore validation messages about the primary key since it will be added if ($column == $relationship['related_column']) { continue; } if ($one_to_one) { $token_field = fValidationException::formatField('__TOKEN__'); $extract_message_regex = '#' . str_replace('__TOKEN__', '(.*?)', preg_quote($token_field, '#')) . '(.*)$#D'; preg_match($extract_message_regex, $record_message, $matches); $column_name = self::compose('%1$s %2$s', $related_record_name, $matches[1]); $messages[$related_table . '::' . $column] = self::compose('%1$s%2$s', fValidationException::formatField($column_name), $matches[2]); } else { $main_key = $related_table . '[' . $i . ']'; if (!isset($messages[$main_key])) { if (isset(self::$validation_name_methods[$class][$related_class][$route])) { $name = $record->{self::$validation_name_methods[$class][$related_class][$route]}($i + 1); } else { $name = $related_record_name . ' #' . ($i + 1); } $messages[$main_key] = array('name' => $name, 'errors' => array()); } $messages[$main_key]['errors'][$column] = $record_message; } } fRequest::unfilter(); } return $messages; }
/** * Validates one-to-* related records * * @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 * @param string $related_class The name of the class for this record set * @param string $route The route between the table and related table * @return array An array of validation messages */ private static function validateOneToStar($class, &$values, &$related_records, $related_class, $route) { $table = fORM::tablize($class); $related_table = fORM::tablize($related_class); $first_pk_column = self::determineFirstPKColumn($class, $related_class, $route); $filter = self::determineRequestFilter($class, $related_class, $route); $pk_field = $filter . $first_pk_column; $input_keys = array_keys(fRequest::get($pk_field, 'array', array())); $related_record_name = self::getRelatedRecordName($class, $related_class, $route); $messages = array(); $one_to_one = fORMSchema::isOneToOne($table, $related_table, $route); if ($one_to_one) { $records = array(self::createRecord($class, $values, $related_records, $related_class, $route)); } else { $records = self::buildRecords($class, $values, $related_records, $related_class, $route); } // Ignore validation messages about the primary key since it will be added $primary_key_name = fValidationException::formatField(fORM::getColumnName($related_class, $route)); $primary_key_regex = '#^' . preg_quote($primary_key_name, '#') . '.*$#D'; fORMValidation::addRegexReplacement($related_class, $primary_key_regex, ''); foreach ($records as $i => $record) { fRequest::filter($filter, isset($input_keys[$i]) ? $input_keys[$i] : $i); $record_messages = $record->validate(TRUE); foreach ($record_messages as $record_message) { $token_field = fValidationException::formatField('__TOKEN__'); $extract_message_regex = '#' . str_replace('__TOKEN__', '(.*?)', preg_quote($token_field, '#')) . '(.*)$#D'; preg_match($extract_message_regex, $record_message, $matches); if ($one_to_one) { $column_name = self::compose('%1$s %2$s', $related_record_name, $matches[1]); } else { $column_name = self::compose('%1$s #%2$s %3$s', $related_record_name, $i + 1, $matches[1]); } $messages[] = self::compose('%1$s%2$s', fValidationException::formatField($column_name), $matches[2]); } fRequest::unfilter(); } fORMValidation::removeRegexReplacement($related_class, $primary_key_regex, ''); return $messages; }