25 Dec 2009 @ 1:44 PM 

I think we’ve all gotten sick of keeping track of arguments lists for certain functions and suddenly felt the desire to resort to named params in order to avoid messes such as functions that look like

  foo(array $argot, ingot $begot,ingot $cargot, cat $dargot,$ergot,$farthing,$garging,$haring,$baonr,$tarish  = 4 ,$smar = "lare",$lar = null,$var = smare, $snare= gare);

The simplicity of

  foo($params);

is clear, especially if many or all of our named params have default values and do not need to be passed in explicitly.

I have been playing with the concept of a NamedParam class. It is a class that allows us to clearly specify default values, specify required parameters, required parameters types and constraints on valid parameters values. It also provides functionality for restoring class or construction time default values — this feature may be useful if we want to quickly revert to our specified parameters when for instance our user has provided us with garbage values but we do not wish to halt execution.

/ /For our callers we keep things fairly simple
// Callers Do This:
$ClassInstance->AcceptsNamedParams( array('garfield' => 6, 'apple' => 'fruit'));

// Implementors have a little more work to do. Often we may be able to reduce the size of the second parameter by  keeping a common group of  default arrays handy at the class scope,
// or by setting many of these universal (to our named params class) values in our namedparams derived classes's $_classConditions member.

//Implementors Do This:
public function AcceptsNamedParams($namedparams)
{
   $params = new MyClassesNamedParamedInstance($namedparams, array(  'garfield' /*required field*/ ,
                                                                                                                  'apple' /*required field*/ ,
                                                                                                                  'tango' => array( 'required' /* required field */ ,
                                                                                                                                          'type' = array( 'int, /*or*/ , 'float', /*or*/ 'MyComplexNumberClass'),
                                                                                                                                          'lessthan' => 10, /* if the value of tango is not < 10 meetscriteria will fail */
                                                                                                                                          'greaterthan' => 3, /* if the value of tango is not > 3 meetscriteria will fail */
                                                                                                                                          'default' =>5); /* If the user has not set a value for tango it will be set to 5.

   if(!$params->MeetsCriteria)
   {
          echo "Please call this function with all required constraints met! ";
          var_dump($this->getUnmetCriteria());
   } else {
          echo $params->tango . " is the best number ever!";
   }
}

NamedParams Class

Source:

/**
 * Description of NamedParams
 *
 * @author kbrings
 */
class NamedParams {
    protected $_namedParams = array();

    protected $_constructConditions = array();
    protected $_constructConditionsCriteria = array();
    protected $_constructConditionsDefaults = array();

    protected $_classConditions = array();
    protected $_classConditionsCriteria = array();
    protected $_classConditionsDefaults = array();

    protected $_requiredFields = array();
    protected $_metCriteria = null;

    protected $_failedCriteria = array();

    /**
     *@example array( firstName => array( 'Required', 'Default' => 7, 'Type' => 'int' );
     * @param array $array
     */
    public function ProcessConditionArray(array $array = null) {

        $_criteria = array();
        $_values = array();
        foreach($array as $key => $criteria) {
            if(is_null($criteria)) {
                $_criteria[$key][] = array('required' , null);
            }

            if(is_numeric($key) && is_string($criteria)) {
                $_criteria[$criteria][] = array('required' , null);
            }

            if(is_array($criteria)) {
                foreach ($criteria as $criterion=>$params) {
                    $criterion = strtolower($criterion);
                    if($criterion == 'default') {
                        $_values[$key] = $params;
                    }
                    else {
                        if(is_numeric($criterion) && is_string($params)) {
                            $criterion = strtolower($params);
                            $params = null;
                        }
                        $_criteria[$key][] = array($criterion, $params);
                    }
                }
            }
        }
        return array($_criteria, $_values);
    }

    public function MeetsCriteria($refresh = false) {
        if($this->_metCritieria !== null && !$refresh) {
            return $this->_metCriteria;
        }

        $meetsCriteria = true;
        $this->_failedCriteria = array();

        $whiteList = array();
        $meetsCriteria &= $this->MeetsCriteriaGroup($this->_constructConditionsCriteria, $whiteList);
        $meetsCriteria &= $this->MeetsCriteriaGroup($this->_classConditionsCriteria, $whiteListl);
        $this->_metCriteria = $meetsCriteria;
        return (bool)$this->_metCriteria;
    }

    protected function MeetsCriteriaGroup($criteriaGroup) {
        $meetsCriteria  = true;
        foreach ($criteriaGroup as $attribute =>$criteria) {
            foreach($criteria as $index => $criterion) {
                $func = "cri_" . $criterion[0];

                if(method_exists($this, $func)) {
                    $meetsCriteria &= $pass = $this->$func($attribute,$criterion[1]);
                    if($pass == false) {
                        $this->_failedCriteria[] = array('attribute' => $attribute, 'msg'=>"Criteria Not Met", 'criteria'=>$criterion[0], 'conditions'=>$criterion[1], 'value'=>$this->$attribute);
                    }

                }
                else {
                    $this->_failedCriteria[] = array('attribute' => $attribute, 'msg'=>"Criteria Function Not Found", 'criteria'=>$criterion[0], 'conditions'=>$criterion[1], 'value'=>$this->$attribute);
                    return false;
                }
            }
        }
        return $meetsCriteria;
    }

    public function getUnmetCriteria() {
        return $this->_failedCriteria;
    }

    public function __construct($namedParams, $conditions) {
        $this->_namedParams = $namedParams;
        $this->_constructConditions = $conditions;
        list($this->_constructConditionsCriteria, $this->_constructConditionsDefaults) = $this->ProcessConditionArray($conditions);
        list($this->_classConditionsCriteria, $this->_classConditionsDefaults) = $this->ProcessConditionArray($this->_classConditions);
    }

    public function output() {
        return "------- named params -------\n" .
            print_r($this->_namedParams,true) .
            "------- construct defaults -------\n" .
            print_r($this->_constructConditionsDefaults, true) .
            "------- class defaults -------\n" .
            print_r($this->_classConditionsDefaults, true);
    }

    public function __get($attribute) {
        if(array_key_exists($attribute, $this->_namedParams)) {
            return $this->_namedParams[$attribute];
        }
        else if(array_key_exists($attribute, $this->_constructConditionsDefaults)) {
            return $this->_constructConditionsDefaults[$attribute];
        }
        else if(array_key_exists($attribute, $this->_classConditionsDefaults)) {
            return $this->_classConditionsDefaults[$attribute];
        }
    }

    public function __set($attribute, $value) {
        $this->_namedParams[$attribute] = $value;
    }

    public function __isset($attribute) {
        return isset($this->_namedParams[$attribute]) ||
            isset($this->_constructConditionsDefaults[$attribute]) ||
            isset($this->_classConditionsDefaults[$attribute]);
    }

    public function __unset($attribute) {
        if(array_key_exists($attribute, $this->_namedParams)) {
            unset($this->_namedParams[$attribute]);
        }
        else if(array_key_exists($attribute, $this->_constructConditionsDefaults)) {
            unset($this->_constructConditionsDefaults[$attribute]);
        }
        else if(array_key_exists($attribute, $this->_classConditionsDefaults)) {
            unset($this->_classConditionsDefaults[$attribute]);
        }
    }

    public function clearToDefault($attribute) {
         if(array_key_exists($attribute, $this->_namedParams)) {
            unset($this->_namedParams[$attribute]);
        }
    }

    public function clearToClassDefault($attribute) {
         if(array_key_exists($attribute, $this->_namedParams)) {
            unset($this->_namedParams[$attribute]);
        }

        if(array_key_exists($attribute, $this->_constructConditionsDefaults)) {
            unset($this->_constructConditionsDefaults[$attribute]);
        }
    }
    //==========================================================================
    // Criteria Tests
    // These functions are called indirectly by the MeetsCriteriaGroup function.
    //==========================================================================
    protected function cri_equals($attribute,$target) {
        return $this->$attribute == $target;
    }

    protected function cri_tequals($attribute,$target) {
        return $this->$attribute === $target;
    }

    protected function cri_notequals($attribute,$target) {
        return $this->$attribute != $target;
    }

    protected function cri_nottequals($attribute,$target) {
        return $this->$attribute !== $target;
    }

    protected function cri_greaterthan($attribute,$target) {
        return $this->$attribute > $target;
    }
    protected function cri_greaterequalthan($attribute,$target) {
        return $this->$attribute >= $target;
    }
    protected function cri_lessthan($attribute,$target) {
        return $this->$attribute < $target;
    }
    protected function cri_lessequalthan($attribute,$target) {
        return $this->$attribute <= $target;
    }

    protected function cri_required($attribute,$target) {
        return isset($this->$attribute);
    }

    protected function cri_type($attribute,$target) {
        if(!is_array($target)) {
            $target = array($target);
        }

        $typeMatched = false;
        foreach($target as $entry) {
            switch($entry) {
                case 'bool':
                case 'boolean':
                case 'string':
                case 'array':
                case 'numeric':
                case 'float':
                case 'double':
                case 'array':
                case 'object':
                case 'null':
                case 'int':
                    $f = "is_$entry";
                    $typeMatched |= $f($this->$attribute);
                    break;

                default:
                    $typeMatched |=  $this->$attribute instanceof $entry;
                    break;
            }
            if($typeMatched) return true;
        }
    }
}

Tests

require_once 'NamedParams.php';
require_once 'PHPUnit/Framework.php';

class TestNamedParams extends NamedParams{

    protected $_classConditions = array( 'test' => array('required', 'type'=>'int','default' => 7),
                                       'test2' => array('required', 'type'=>array('string','null'),'default' => "bannaple"),
                                      );

}

class NamedParamsTest extends PHPUnit_Framework_TestCase {
    public $functionConditions = array (
      'test' => array('default' => 8, 'LessThan' => 3),
      'reqParam',
      'reqParam2' => array('type' => array('float','double')),
      'defParam3' => array('default' => 'foo')
    );

    public function testSmoke()
    {
        $params = new TestNamedParams(array(), array());
    }

    public function testBasicFunctionality()
    {
        $namedParams = array( 'test'=>7, 'reqParam' => 'apple', 'reqParam2' => 3.14179);
        $params = new TestNamedParams($namedParams, $this->functionConditions);
        $params->MeetsCriteria(true);
        $this->AssertEquals(7,$params->test);
        $this->AssertFalse($params->MeetsCriteria(true));

        $t = $params->getUnmetCriteria();
        $this->AssertEquals($t[0]['attribute'], "test");
        $this->AssertEquals($t[0]['criteria'], "lessthan");
        $params->test = 2;

        $this->AssertTrue($params->MeetsCriteria(true));
        $this->AssertEquals(2,$params->test);
        $this->AssertEquals('apple',$params->reqParam);
        $this->AssertEquals(3.14179,$params->reqParam2);
        $this->AssertEquals('bannaple',$params->test2);
        $this->AssertEquals('foo',$params->defParam3);
    }

    /**
     *
     * @param  $namedParams
     * @param  $attribute
     * @param  $value
     * @param  $msg
     * @dataProvider attributeRetrievalTestCases
     */
    public function testAttributeRetrieval($namedParams, $attribute,$value,$msg)
    {
        $params = new TestNamedParams($namedParams, $this->functionConditions);
        $this->AssertEquals($params->$attribute,$value,$msg);
    }

    public function attributeRetrievalTestCases()
    {
        return array(

            array(
          array("test" => 77),
          "test",
           77,
           "passed class default override value test was not set correctly"
            ),

                        array(
          array("newp" => 77),
          "newp",
           77,
           "passed value newp was not set correctly"
            ),
                                    array(
          array("reqParam" => 77),
          "reqParam",
           77,
           "passed value reqParam was not set correctly"
        ),

                                                array(
          array("test" => null),
          "test",
           null,
           "overidden value test was not set to null correctly"
        ),

            );
    }

}

Test Output

unit tests output

    PHPUnit 3.3.16 by Sebastian Bergmann.

......

Time: 0 seconds

OK (6 tests, 14 assertions)
Posted By: Keith Noizu
Last Edit: 02 Dec 2011 @ 04:18 PM

EmailPermalink
Tags
Categories: Code, Php


 

Responses to this post » (One Total)

 
  1. Lestai says:

    Yhanks for your great website! ^^

Post a Comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">


 Last 50 Posts
 Back
Change Theme...
  • Users » 75
  • Posts/Pages » 14
  • Comments » 10
Change Theme...
  • VoidVoid « Default
  • LifeLife
  • EarthEarth
  • WindWind
  • WaterWater
  • FireFire
  • LightLight

About



    No Child Pages.

About



    No Child Pages.

Resume



    No Child Pages.