Permalink
Browse files

BackwardsCompatibilityBreak - Changed the array structure for child r…

…ecord in validation messages. Instead of all main record and child record messages being in a single array with keys in the form of `child_table[0]::column`, there is now a `child_table[0]` key that points to an array containing a `name` key and an `errors` key. The value for `name` is the name of the child record, e.g. `Child Record #1`. The value for `errors` is an array of validation messages with the keys being the column names and the values being the validation messages. Thus:

array(
    'first_name' => 'First Name: Please enter a value',
    'user_permissions[0]::resource' => 'Resource: Please enter a value'
)

would now be represented as:

array(
    'first_name' => 'First Name: Please enter a value',
    'user_permissions[0]' => array(
        'name' => 'User Permission #1',
        'errors' => array(
            'resource' => 'Resource: Please enter a value'
        )
    )
)

Methods registered to the `pre::validate()` and `post::validate()` hooks will need to handle this new array structure. Code that calls `->validate(TRUE)` on an fActiveRecord object will need to handle this new array structure. fORMValidation::addStringReplacement(), fORMValidation::addRegexReplacement() and fORMValidation::setMessageOrder() will now only affect the specified class specified and will not affect messages from child records.

Fixed a couple of bugs related to recent changes to the fRecordSet API.

Added the method fORMRelated::registerValidationNameMethod() to create custom related record names for child record validation.
  • Loading branch information...
wbond committed Oct 4, 2010
1 parent f69f8b7 commit 313acde2564008abc4f33b099904f48ddc3071f0
Showing with 153 additions and 61 deletions.
  1. +2 −3 classes/fActiveRecord.php
  2. +88 −41 classes/fORMRelated.php
  3. +24 −12 classes/fORMValidation.php
  4. +39 −5 classes/fValidationException.php
@@ -15,7 +15,8 @@
* @package Flourish
* @link http://flourishlib.com/fActiveRecord
*
* @version 1.0.0b68
* @version 1.0.0b69
* @changes 1.0.0b69 Backwards Compatibility Break - changed ::validate() to return a nested array of validation messages when there are validation errors on child records [wb-imarc+wb, 2010-10-03]
* @changes 1.0.0b68 Added hooks to ::replicate() [wb, 2010-09-07]
* @changes 1.0.0b67 Updated code to work with the new fORM API [wb, 2010-08-06]
* @changes 1.0.0b66 Fixed a bug with ::store() and non-primary key auto-incrementing columns [wb, 2010-07-05]
@@ -2884,8 +2885,6 @@ public function validate($return_messages=FALSE, $remove_column_names=FALSE)
$validation_messages
);
$validation_messages = array_unique($validation_messages);
$validation_messages = fORMValidation::replaceMessages($class, $validation_messages);
$validation_messages = fORMValidation::reorderMessages($class, $validation_messages);
View
@@ -5,14 +5,16 @@
* The functionality of this class only works with single-field `FOREIGN KEY`
* constraints.
*
* @copyright Copyright (c) 2007-2010 Will Bond
* @copyright Copyright (c) 2007-2010 Will Bond, others
* @author Will Bond [wb] <will@flourishlib.com>
* @author Will Bond, iMarc LLC [wb-imarc] <will@imarc.net>
* @license http://flourishlib.com/license
*
* @package Flourish
* @link http://flourishlib.com/fORMRelated
*
* @version 1.0.0b35
* @changes 1.0.0b35 Backwards Compatibility Break - changed the validation messages array to use nesting for child records [wb-imarc+wb, 2010-10-03]
* @changes 1.0.0b35 Updated ::getPrimaryKeys() to always return primary keys in a consistent order when no order bys are specified [wb, 2010-07-26]
* @changes 1.0.0b34 Updated the class to work with fixes in fORMRelated [wb, 2010-07-22]
* @changes 1.0.0b33 Fixed the related table populate action to use the plural underscore_notation version of the related class name [wb, 2010-07-08]
@@ -52,28 +54,29 @@
class fORMRelated
{
// The following constants allow for nice looking callbacks to static methods
const associateRecords = 'fORMRelated::associateRecords';
const buildRecords = 'fORMRelated::buildRecords';
const countRecords = 'fORMRelated::countRecords';
const createRecord = 'fORMRelated::createRecord';
const determineRequestFilter = 'fORMRelated::determineRequestFilter';
const flagForAssociation = 'fORMRelated::flagForAssociation';
const getOrderBys = 'fORMRelated::getOrderBys';
const getRelatedRecordName = 'fORMRelated::getRelatedRecordName';
const hasRecords = 'fORMRelated::hasRecords';
const linkRecords = 'fORMRelated::linkRecords';
const overrideRelatedRecordName = 'fORMRelated::overrideRelatedRecordName';
const populateRecords = 'fORMRelated::populateRecords';
const reflect = 'fORMRelated::reflect';
const reset = 'fORMRelated::reset';
const setOrderBys = 'fORMRelated::setOrderBys';
const setCount = 'fORMRelated::setCount';
const setPrimaryKeys = 'fORMRelated::setPrimaryKeys';
const setRecordSet = 'fORMRelated::setRecordSet';
const store = 'fORMRelated::store';
const storeManyToMany = 'fORMRelated::storeManyToMany';
const storeOneToMany = 'fORMRelated::storeOneToMany';
const validate = 'fORMRelated::validate';
const associateRecords = 'fORMRelated::associateRecords';
const buildRecords = 'fORMRelated::buildRecords';
const countRecords = 'fORMRelated::countRecords';
const createRecord = 'fORMRelated::createRecord';
const determineRequestFilter = 'fORMRelated::determineRequestFilter';
const flagForAssociation = 'fORMRelated::flagForAssociation';
const getOrderBys = 'fORMRelated::getOrderBys';
const getRelatedRecordName = 'fORMRelated::getRelatedRecordName';
const hasRecords = 'fORMRelated::hasRecords';
const linkRecords = 'fORMRelated::linkRecords';
const overrideRelatedRecordName = 'fORMRelated::overrideRelatedRecordName';
const populateRecords = 'fORMRelated::populateRecords';
const reflect = 'fORMRelated::reflect';
const registerValidationNameMethod = 'fORMRelated::registerValidationNameMethod';
const reset = 'fORMRelated::reset';
const setOrderBys = 'fORMRelated::setOrderBys';
const setCount = 'fORMRelated::setCount';
const setPrimaryKeys = 'fORMRelated::setPrimaryKeys';
const setRecordSet = 'fORMRelated::setRecordSet';
const store = 'fORMRelated::store';
const storeManyToMany = 'fORMRelated::storeManyToMany';
const storeOneToMany = 'fORMRelated::storeOneToMany';
const validate = 'fORMRelated::validate';
/**
@@ -97,6 +100,13 @@ class fORMRelated
*/
static private $related_record_names = array();
/**
* Methods to use for getting the name of related records when performing validation
*
* @var array
*/
static private $validation_name_methods = array();
/**
* Creates associations for one-to-one relationships
@@ -383,7 +393,7 @@ static public function createRecord($class, $values, &$related_records, $related
if ($one_to_one) {
if (isset($related_records[$related_table][$route]['record_set'])) {
if ($related_records[$related_table][$route]['record_set']->count()) {
return $related_records[$related_table][$route]['record_set']->current();
return $related_records[$related_table][$route]['record_set'][0];
}
return new $related_class();
}
@@ -402,7 +412,7 @@ static public function createRecord($class, $values, &$related_records, $related
self::setRecordSet($class, $related_records, $related_class, $record_set, $route);
if ($record_set->count()) {
return $record_set->current();
return $record_set[0];
}
return new $related_class();
}
@@ -1178,6 +1188,33 @@ static public function reflect($class, &$signatures, $include_doc_comments)
}
/**
* Registers a method to use to get a name for a related record when doing validation
*
* @param string|fActiveRecord $class The class to register the method for
* @param string $related_class The related class to register the method for
* @param string $method The method to be called on the related class that will return the name
* @param string $route The route to the related class
*/
static public function registerValidationNameMethod($class, $related_class, $method, $route=NULL)
{
$class = fORM::getClass($class);
$table = fORM::tablize($class);
$related_table = fORM::tablize($related_class);
$schema = fORMSchema::retrieve($class);
$route = fORMSchema::getRouteName($schema, $table, $related_table, $route, 'one-to-many');
if (!isset(self::$validation_name_methods[$class])) {
self::$validation_name_methods[$class] = array();
}
if (!isset(self::$validation_name_methods[$class][$related_class])) {
self::$validation_name_methods[$class][$related_class] = array();
}
self::$validation_name_methods[$class][$related_class][$route] = $method;
}
/**
* Resets the configuration of the class
*
@@ -1601,31 +1638,41 @@ static private function validateOneToStar($class, &$values, &$related_records, $
continue;
}
$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) {
$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 {
$column_name = self::compose(
'%1$s #%2$s %3$s',
$related_record_name,
$i+1,
$matches[1]
);
$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;
}
$messages[$related_table . (!$one_to_one ? '[' . $i . ']' : '') . '::' . $column] = self::compose(
'%1$s%2$s',
fValidationException::formatField($column_name),
$matches[2]
);
}
fRequest::unfilter();
}
@@ -1684,7 +1731,7 @@ private function __construct() { }
/**
* Copyright (c) 2007-2010 Will Bond <will@flourishlib.com>
* Copyright (c) 2007-2010 Will Bond <will@flourishlib.com>, others
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
View
@@ -10,7 +10,8 @@
* @package Flourish
* @link http://flourishlib.com/fORMValidation
*
* @version 1.0.0b27
* @version 1.0.0b28
* @changes 1.0.0b28 Updated the class to work with the new nested array structure for validation messages [wb, 2010-10-03]
* @changes 1.0.0b27 Fixed ::hasValue() to properly detect zero-value floats, made ::hasValue() internal public [wb, 2010-07-26]
* @changes 1.0.0b26 Improved the error message for integers to say `whole number` instead of just `number` [wb, 2010-05-29]
* @changes 1.0.0b25 Added ::addRegexRule(), changed validation messages array to use column name keys [wb, 2010-05-26]
@@ -1289,7 +1290,8 @@ static public function reorderMessages($class, $messages)
foreach ($messages as $key => $message) {
foreach ($matches as $num => $match_string) {
if (fUTF8::ipos($message, $match_string) !== FALSE) {
$string = is_array($message) ? $message['name'] : $message;
if (fUTF8::ipos($string, $match_string) !== FALSE) {
$ordered_items[$num][$key] = $message;
continue 2;
}
@@ -1318,19 +1320,29 @@ static public function reorderMessages($class, $messages)
static public function replaceMessages($class, $messages)
{
if (isset(self::$string_replacements[$class])) {
$messages = str_replace(
self::$string_replacements[$class]['search'],
self::$string_replacements[$class]['replace'],
$messages
);
foreach ($messages as $key => $message) {
if (is_array($message)) {
continue;
}
$messages[$key] = str_replace(
self::$string_replacements[$class]['search'],
self::$string_replacements[$class]['replace'],
$message
);
}
}
if (isset(self::$regex_replacements[$class])) {
$messages = preg_replace(
self::$regex_replacements[$class]['search'],
self::$regex_replacements[$class]['replace'],
$messages
);
foreach ($messages as $key => $message) {
if (is_array($message)) {
continue;
}
$messages[$key] = preg_replace(
self::$regex_replacements[$class]['search'],
self::$regex_replacements[$class]['replace'],
$message
);
}
}
return array_filter($messages, array('fORMValidation', 'isNonBlankString'));
@@ -2,14 +2,16 @@
/**
* An exception caused by a data not matching a rule or set of rules
*
* @copyright Copyright (c) 2007-2010 Will Bond
* @copyright Copyright (c) 2007-2010 Will Bond, others
* @author Will Bond [wb] <will@flourishlib.com>
* @author Will Bond, iMarc LLC [wb-imarc] <will@imarc.net>
* @license http://flourishlib.com/license
*
* @package Flourish
* @link http://flourishlib.com/fValidationException
*
* @version 1.0.0b3
* @version 1.0.0b4
* @changes 1.0.0b4 Added support for nested error arrays [wb-imarc, 2010-10-03]
* @changes 1.0.0b3 Added ::removeFieldNames() [wb, 2010-05-26]
* @changes 1.0.0b2 Added a custom ::__construct() to handle arrays of messages [wb, 2009-09-17]
* @changes 1.0.0b The initial implementation [wb, 2007-06-14]
@@ -53,7 +55,12 @@ static public function removeFieldNames($messages)
$output = array();
foreach ($messages as $column => $message) {
$output[$column] = preg_replace($replace_regex, '', $message);
if (is_array($message)) {
$message['errors'] = self::removeFieldNames($message['errors']);
$output[$column] = $message;
} else {
$output[$column] = preg_replace($replace_regex, '', $message);
}
}
return $output;
@@ -132,11 +139,14 @@ public function __construct($message='')
$params = func_get_args();
if ((count($params) == 2 || count($params) == 3) && is_string($params[0]) && is_array($params[1])) {
$message = sprintf(
"<p>%1\$s</p>\n<ul>\n<li>%2\$s</li>\n</ul>",
self::compose($params[0]),
join("</li>\n<li>", $params[1])
join("</li>\n<li>", $this->formatErrorArray($params[1]))
);
$params = array_merge(
// This escapes % signs since fException is going to look for sprintf formatting codes
array(str_replace('%', '%%', $message)),
@@ -150,12 +160,36 @@ public function __construct($message='')
$params
);
}
/**
* Takes an error array that may or may not be nested and returns a HTML string representation
*
* @param array $errors An array of (possibly nested) child record errors
* @return array An array of string error messages
*/
private function formatErrorArray($errors)
{
$new_errors = array();
foreach ($errors as $error) {
if (!is_array($error)) {
$new_errors[] = $error;
} else {
$new_errors[] = sprintf(
"<span>%1\$s</span>\n<ul>\n<li>%2\$s</li>\n</ul>",
$error['name'],
join("</li>\n<li>", $this->formatErrorArray($error['errors']))
);
}
}
return $new_errors;
}
}
/**
* Copyright (c) 2007-2010 Will Bond <will@flourishlib.com>
* Copyright (c) 2007-2010 Will Bond <will@flourishlib.com>, others
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal

0 comments on commit 313acde

Please sign in to comment.