[Solar-svn] Revision 2645

pmjones at solarphp.com pmjones at solarphp.com
Sun Jul 29 10:48:09 CDT 2007


Branch: Solar_Form:

Many BC breaks related to using the new Filter mechanism, where sanitizing and
validation are wrapped into the same class.

* [BRK] Element-specific messages are now keyed on 'invalid' (not 'feedback'),
  although overall form-level messages are still called feedback.

* [ADD] New config key 'filter' is a Solar_Filter dependency injection.

* [BRK] Methods addFilter() and addFilters() are now related to the new
  Solar_Filter class, and so handle both validation and sanitizing. Removed
  addValid() method entirely.

* [BRK] Methods addInvalid() and addInvalids() to invalidation messages to one
  or more elements.  This replaces addFeedback(), which has been removed.

* [ADD] Methods setValue() and setValues() to more reliably set the value on
  one or more elements manually.

* [CHG] Method validate() now uses the Solar_Filter filter-chain instead of
  a custom internal sanitize/validate routine.

* [BRK] Renamed method values() to getValues(), to be more consistent with
  coding standards.



Modified: branches/orm/Solar/Form.php
===================================================================
--- branches/orm/Solar/Form.php	2007-07-29 15:36:17 UTC (rev 2644)
+++ branches/orm/Solar/Form.php	2007-07-29 15:48:09 UTC (rev 2645)
@@ -9,7 +9,7 @@
  * 
  * @author Paul M. Jones <pmjones at solarphp.com>
  * 
- * @author Contributions from Matthew Weier O'Phinney <mweierophinney at gmail.com>
+ * @author Matthew Weier O'Phinney <mweierophinney at gmail.com>
  * 
  * @license http://opensource.org/licenses/bsd-license.php BSD
  * 
@@ -50,18 +50,19 @@
      * : (string) The overall "failure" message when validating form
      * input. Default is Solar locale key FAILURE_FORM.
      * 
+     * `filter`
+     * : (dependency) A Solar_Filter dependency injection; default is empty,
+     *   which creates a standard Solar_Filter object on the fly.
+     * 
      * @var array
      * 
      */
     protected $_Solar_Form = array(
         'request' => 'request',
-        'attribs' => array(
-            'action'  => null,
-            'method'  => 'post',
-            'enctype' => 'multipart/form-data',
-        ),
+        'filter'  => null,
         'success' => null,
         'failure' => null,
+        'attribs' => array(),
     );
     
     /**
@@ -70,11 +71,25 @@
      * 
      * @var bool Null if validation has not occurred yet, true if
      * valid, false if not valid.
+     * 
      */
     protected $_status = null;
     
     /**
      * 
+     * Default <form> tag attributes.
+     * 
+     * @var array
+     * 
+     */
+    protected $_default_attribs = array(
+        'action'  => null,
+        'method'  => 'post',
+        'enctype' => 'multipart/form-data',
+    );
+    
+    /**
+     * 
      * Attributes for the form tag itself.
      * 
      * The `$attribs` array holds HTML attributes for the
@@ -115,8 +130,8 @@
      * 
      * Note that the $feedback property pertains to the form as a
      * whole, not the individual elements.  This is as opposed to
-     * the 'feedback' key in each of the elements, which contains
-     * feedback specific to that element.
+     * the 'invalid' key in each of the elements, which contains
+     * invalidation messages specific to that element.
      * 
      * @var array
      * 
@@ -125,24 +140,15 @@
     
     /**
      * 
-     * The array of pre-filters for the form elements.
+     * The array of filters for the form elements.
      * 
      * @var array 
      * 
      */
-    protected $_filter = array();
+    protected $_filters = array();
     
     /**
      * 
-     * The array of validations for the form elements.
-     * 
-     * @var array
-     * 
-     */
-    protected $_valid = array();
-    
-    /**
-     * 
      * Array of submitted values.
      * 
      * Populated on the first call to [[_populate()]], which itself uses
@@ -175,13 +181,13 @@
      * : (string) The default or selected value(s) for the element.
      * 
      * `descr`
-     * : (string) A longer description of the element, for example a tooltip
+     * : (string) A longer description of the element, such as a tooltip
      *   or help text.
      * 
      * `status`
-     * : (bool) Whether or not the particular elements has
-     *   passed or failed validation (true or false), or null if there
-     *   has been no attempt at validation.
+     * : (bool) Whether or not the particular element has passed or failed
+     *   validation (true or false), or null if there has been no attempt at
+     *   validation.
      * 
      * `require`
      * : (bool) Whether or not the element is required.
@@ -198,47 +204,37 @@
      * : (array) Additional XHTML attributes for the element in the
      *   form (attribute => value).
      * 
-     * `feedback`
-     * : (array) An array of feedback messages for this element,
-     *   generally based on validation of previous user input.
+     * `invalid`
+     * : (array) An array of messages if the value is invalid.
      * 
      * @var array
      * 
      */
-    protected $_default = array(
-        'name'     => null,
-        'type'     => null,
-        'label'    => null,
-        'descr'    => null,
-        'value'    => null,
-        'status'   => null,
-        'require'  => false,
-        'disable'  => false,
-        'options'  => array(),
-        'attribs'  => array(),
-        'feedback' => array(),
+    protected $_default_element = array(
+        'name'    => null,
+        'type'    => null,
+        'label'   => null,
+        'descr'   => null,
+        'value'   => null,
+        'status'  => null,
+        'require' => false,
+        'disable' => false,
+        'options' => array(),
+        'attribs' => array(),
+        'invalid' => array(),
     );
     
     /**
      * 
-     * A Solar_Filter object for internal filtering needs.
+     * A Solar_Filter object for filtering form values.
      * 
      * @var Solar_Filter
      * 
      */
-    protected $_obj_filter;
+    protected $_filter;
     
     /**
      * 
-     * A Solar_Valid object for internal validation needs.
-     * 
-     * @var Solar_Valid
-     * 
-     */
-    protected $_obj_valid;
-    
-    /**
-     * 
      * Request environment object.
      * 
      * @var Solar_Request
@@ -268,21 +264,15 @@
             $this->_config['request']
         );
         
-        // make sure we have an action. normally we try not to change $_config
-        // from what the user set, but in this case, we need $_config values
-        // so that reset() works properly.
-        if (empty($this->_config['attribs']['action'])) {
-            $this->_config['attribs']['action'] = $this->_request->server('REQUEST_URI');
-        }
+        // set the default action attribute
+        $action = $this->_request->server('REQUEST_URI');
+        $this->_default_attribs['action'] = $action;
         
-        // retain setups, create validator/filter objects
-        $this->attribs = array_merge(
-            $this->_Solar_Form['attribs'],
+        // now merge attribute configs
+        $this->_config['attribs'] = array_merge(
+            $this->_default_attribs,
             $this->_config['attribs']
         );
-        
-        $this->_obj_filter = Solar::factory('Solar_Filter');
-        $this->_obj_valid = Solar::factory('Solar_Valid');
     }
     
     // -----------------------------------------------------------------
@@ -299,7 +289,7 @@
      * $info['name'].
      * 
      * @param array $info Element information using the same keys as
-     * in Solar_Form::$_default.
+     * in Solar_Form::$_default_element.
      * 
      * @param string $array Rename the element as a key in this array.
      * 
@@ -312,48 +302,22 @@
         $name = $this->_prepareName($name, $array);
         
         // prepare the element info
-        $info = array_merge($this->_default, $info);
+        $info = array_merge($this->_default_element, $info);
         
         // forcibly cast each of the keys into the elements array
         $this->elements[$name] = array (
-            'name'     =>          $name,
-            'type'     => (string) $info['type'],
-            'label'    => (string) $info['label'],
-            'value'    =>          $info['value'], // mixed
-            'descr'    => (string) $info['descr'],
-            'require'  => (bool)   $info['require'],
-            'disable'  => (bool)   $info['disable'],
-            'options'  => (array)  $info['options'],
-            'attribs'  => (array)  $info['attribs'],
-            'feedback' => (array)  $info['feedback'],
+            'name'    => (string) $name,
+            'type'    => (string) $info['type'],
+            'label'   => (string) $info['label'],
+            'value'   =>          $info['value'], // mixed
+            'descr'   => (string) $info['descr'],
+            'require' => (bool)   $info['require'],
+            'disable' => (bool)   $info['disable'],
+            'options' => (array)  $info['options'],
+            'attribs' => (array)  $info['attribs'],
+            'invalid' => (array)  $info['invalid'],
+            'filters' => (array)  $info['filters'],
         );
-        
-        // add filters
-        if (array_key_exists('filter', $info)) {
-            foreach ( (array) $info['filter'] as $args) {
-                $this->_filter[$name][] = $args;
-            }
-        }
-        
-        // add validations
-        if (array_key_exists('valid', $info)) {
-        
-            foreach ( (array) $info['valid'] as $args) {
-            
-                // make sure $args is an array
-                settype($args, 'array');
-                
-                // shift the name onto the top of the args
-                array_unshift($args, $name);
-                
-                // add the validation to the element
-                call_user_func_array(
-                    array($this, 'addValid'),
-                    $args
-                );
-                
-            }
-        }
     }
     
     /**
@@ -361,7 +325,7 @@
      * Sets multiple elements in the form.  Appends if they do not exist.
      * 
      * @param array $list Element information as array(name => info), where
-     * each info value is an array like Solar_Form::$_default.
+     * each info value is an array like Solar_Form::$_default_element.
      * 
      * @param string $array Rename each element as a key in this array.
      * 
@@ -406,95 +370,107 @@
     
     /**
      * 
-     * Adds a Solar_Filter method callback for an element.
+     * Adds one filter to an element.
      * 
-     * All pre-filters are applied via 
-     * Solar_Filter::multiple() and should conform to the 
-     * specifications for that method.
+     * @param string $name The element name.
      * 
-     * All parameters after $method are treated as added parameters
-     * for the Solar_Filter method call.
+     * @param array|string $spec The filter specification; either a
+     * Solar_Filter method name (string), or an array where the first element
+     * is a method name and remaining elements are parameters to that method.
      * 
+     * @return void
+     * 
+     */
+    public function addFilter($name, $spec, $array) 
+    {
+        // make sure the element exists
+        $name = $this->_prepareName($name, $array);
+        if (empty($this->elements[$name])) {
+            throw $this->_exception('ERR_NO_SUCH_ELEMENT', array(
+                'name' => $name,
+            ));
+        }
+        
+        // add the filter
+        $this->elements[$name]['filters'][] = (array) $spec;
+    }
+    
+    /**
+     * 
+     * Adds many filters to one element.
+     * 
      * @param string $name The element name.
      * 
-     * @param string $method Solar_Filter method or PHP function to use
-     * for filtering.
+     * @param array|string $spec The filter specification; either a
+     * Solar_Filter method name (string), or an array where the first element
+     * is a method name and remaining elements are parameters to that method.
      * 
+     * @param string $array Rename each element as a key in this array.
+     * 
      * @return void
      * 
      */
-    public function addFilter($name, $method) 
+    public function addFilters($name, $list, $array)
     {
-        // Get the arguments, drop the element name
-        $args = func_get_args();
-        array_shift($args);
-
-        $this->_filter[$name][] = $args;
+        foreach ((array) $list as $spec) {
+            $this->addFilter($name, $spec, $array);
+        }
     }
     
     /**
      * 
-     * Adds a Solar_Valid method callback as a validation for an element.
+     * Adds one or more invalid message to an element, sets the element status
+     * to false (invalid), and sets the form status to false (invalid).
      * 
      * @param string $name The element name.
      * 
-     * @param string $method The Solar_Valid callback method.
+     * @param string|array $spec The invalidation message(s).
      * 
-     * @param string $message The feedback message to use if validation fails.
+     * @param string $array Rename each element as a key in this array.
      * 
      * @return void
      * 
      */
-    public function addValid($name, $method, $message = null)
+    public function addInvalid($name, $spec, $array = null)
     {
-        // get the arguments, drop the element name
-        $args = func_get_args();
-        $name = array_shift($args);
+        // make sure the element exists
+        $name = $this->_prepareName($name, $array);
+        if (empty($this->elements[$name])) {
+            throw $this->_exception('ERR_NO_SUCH_ELEMENT', array(
+                'name' => $name,
+            ));
+        }
         
-        // add a default validation message (args[0] is the method,
-        // args[1] is the message)
-        if (empty($args[1]) || trim($args[1]) == '') {
-            
-            // see if we have an method-specific validation message
-            $key = 'VALID_' . strtoupper($method);
-            $args[1] = $this->locale($key);
-            
-            // if the message is the same as the key,
-            // there was no method-specific validation
-            // message.  revert to the generic default.
-            if ($key == $args[1]) {
-                $args[1] = $this->locale('ERR_INVALID');
-            }
+        // add the messages
+        foreach ((array) $spec as $text) {
+            $this->elements[$name]['invalid'][] = $text;
         }
         
-        // add to the validation array
-        $this->_valid[$name][] = $args;
+        // mark the status of the element, and of the form
+        $this->elements[$name]['status'] = false;
+        $this->_status = false;
     }
     
     /**
      * 
-     * Adds multiple feedback messages to elements.
+     * Adds invalidation messages to multiple elements, sets their status to
+     * false (invalid) and sets the form status to false (invalid).
      * 
-     * @param array $list An associative array where the key is an element
-     * name and the value is a string or sequential array of feedback messages.
+     * @param array $list An array where the key is the element name, and the
+     * value is a string or array of invalidation messages for that element.
      * 
      * @param string $array Rename each element as a key in this array.
      * 
      * @return void
      * 
      */
-    public function addFeedback($list, $array = null)
+    public function addInvalids($list, $array = null)
     {
-        foreach ($list as $name => $feedback) {
-            $name = $this->_prepareName($name, $array);
-            settype($feedback, 'array');
-            foreach ($feedback as $text) {
-                $this->elements[$name]['feedback'][] = $text;
-            }
+        foreach ((array) $list as $name => $spec) {
+            $this->addInvalid($name, $spec, $array);
         }
     }
     
-    
     // -----------------------------------------------------------------
     // 
     // Value-management methods
@@ -503,8 +479,68 @@
     
     /**
      * 
-     * Populates form elements with specified values.
+     * Manually set the value of one element.
      * 
+     * Note that this is subtly different from [[populate()]].  This method
+     * takes full name of the element, whereas populate() takes a "natural"
+     * hierarchical array like $_POST.
+     * 
+     * @param string $name The element name.
+     * 
+     * @param mixes $value Set the element to this value.
+     * 
+     * @param string $array Rename the element as a key in this array.
+     * 
+     * @return void
+     * 
+     * @throws Solar_Form_Exception_NoSuchElement when the named element does
+     * not exist in the form.
+     * 
+     */
+    public function setValue($name, $value, $array = null)
+    {
+        // make sure the element exists
+        $name = $this->_prepareName($name, $array);
+        if (empty($this->elements[$name])) {
+            throw $this->_exception('ERR_NO_SUCH_ELEMENT', array(
+                'name' => $name,
+            ));
+        }
+        
+        // add the value
+        $this->elements[$name]['value'] = $value;
+    }
+    
+    /**
+     * 
+     * Manually set the value of several elements.
+     * 
+     * Note that this is subtly different from [[populate()]].  This method
+     * takes a flat array where the full name of the element is the key, 
+     * whereas populate() takes a "natural" hierarchical array like $_POST.
+     * 
+     * @param array $data An associative array where the key is the element
+     * name and the value is the element value to set.
+     * 
+     * @param string $array Rename each element as a key in this array.
+     * 
+     * @return void
+     * 
+     * @throws Solar_Form_Exception_NoSuchElement when the named element does
+     * not exist in the form.
+     * 
+     */
+    public function setValues($data, $array = null)
+    {
+        foreach ((array) $data as $name => $value) {
+            $this->setValue($name, $value, $array);
+        }
+    }
+    
+    /**
+     * 
+     * Automatically populates form elements with specified values.
+     * 
      * @param array $submit The source data array for populating form
      * values as array(name => value); if null, will populate from POST
      * or GET vars as determined from the Solar_Form::$attribs['method']
@@ -540,12 +576,10 @@
     
     /**
      * 
-     * Performs filtering and validation on each form element.
+     * Applies the filter chain to the form element values; in particular,
+     * checks validation and updates the 'invalid' keys for each element that
+     * fails.
      * 
-     * Updates the feedback keys for each element that fails validation.
-     * Values are either pulled from the submitted form or from the array
-     * passed in $submit.
-     * 
      * This method cycles through each element in the form, where it ...
      * 
      * 1. Applies the filters to populated user input for the element,
@@ -566,74 +600,46 @@
      * please see those pages for more information on how to add filters and
      * validation to an element.
      * 
-     * @param array $submit The source data array for populating form
-     * values as array(name => info); if null, will populate from POST
-     * or GET vars as determined from the 'method' attribute.
-     * 
      * @return bool True if all elements are valid, false if not.
      * 
      */
-    public function validate($submit = null)
+    public function validate()
     {
-        // Populate the form values.
-        if (empty($this->_submitted)) {
-            $this->populate($submit);
-        }
+        // reset the filter chain so we can rebuild it
+        $this->_filter->resetChain();
 
-        // Loop through each element to filter
-        foreach ($this->_filter as $name => $filters) {
-            $value = $this->elements[$name]['value'];
-            $this->elements[$name]['value'] = $this->_obj_filter->multiple(
-                $value, $filters
-            );
-        }
-
-        $validated = true;
-        
-        // loop through each element to be validated
-        foreach ($this->_valid as $name => $list) {
+        // build the filter chain and data values. note that the foreach()
+        // loop uses an info **reference**, not a copy.
+        $data = array();
+        foreach ($this->elements as $name => &$info) {
+            // keep a **reference** to the data (not a copy)
+            $data[$name] = &$info['value'];
             
-            // loop through each validation for the element
-            foreach ($list as $args) {
-                
-                // the name of the Solar_Valid method
-                $method = array_shift($args);
-                
-                // the text of the error message
-                $feedback = array_shift($args);
-                
-                // config is now the remaining arguments,
-                // put the value on top of it.
-                array_unshift($args, $this->elements[$name]['value']);
-                
-                // call the appropriate Solar_Valid method.
-                $result = call_user_func_array(
-                    array($this->_obj_valid, $method),
-                    $args
-                );
-                
-                // was it valid?
-                if (! $result) {
-                    // no, add the feedback message
-                    $validated = false;
-                    $this->elements[$name]['feedback'][] = $feedback;
-                    $this->elements[$name]['status'] = false;
-                } else {
-                    $this->elements[$name]['status'] = true;
-                }
-                
-            } // inner loop of validations
+            // set the filters and require-flag, reference not needed
+            $this->_filter->addChainFilters($info['filters']);
+            $this->_filter->setChainRequire($info['require']);
             
-        } // outer loop of elements
+        }
         
-        if ($validated) {
+        // apply the filter chain to the data, which will modify the 
+        // element data in place because of the references
+        $this->_status = $this->_filter->applyChain($data);
+        
+        // set the main feedback message
+        if ($this->_status) {
             $this->feedback = array($this->_config['success']);
         } else {
             $this->feedback = array($this->_config['failure']);
         }
         
-        $this->_status = $validated;
-        return $validated;
+        // retain any invalidation messages
+        $invalid = $this->_filter->getChainInvalid();
+        foreach ((array) $invalid as $key => $val) {
+            $this->addInvalid($key, $val);
+        }
+        
+        // done!
+        return $this->_status;
     }
     
     /**
@@ -647,7 +653,7 @@
      * @return array An associative array of element values.
      * 
      */
-    public function values($key = null)
+    public function getValues($key = null)
     {
         $values = array();
         foreach ($this->elements as $name => $elem) {




More information about the Solar-svn mailing list