test-filtre de parametres avec la documentation et un trait

Mon but est de faire des filtres automatiques sur les paramètres des méthodes

L’idée est d’écrire ces filtres dans la documentation php et, de les appliquer a l’aide de la réflection.

Notre propre syntaxe :

/**
*@id(« /[4]/ »)                         // filtre les 4
*@_id(« /^[0-5]?[0-9]$/ »)       // uniquement de 0 a 59
*/
function testMethode($id){             echo $id;         }

@_NonVariable : test de validité
@Nomvariable : filtre non bloquant

La première chose a faire est de lire la documentation associée a la méthode :

private function _parseDoc(ReflectionMethod $methode){
        $ret=array(); 
        $doc=$methode->getDocComment();
        $nb = preg_match_all('/@\s*([_,a-z,A-Z]+)\s*|\((\S+)\)/s',$doc,$matchs);
        if ($nb>0){
            array_shift($matchs[2]);
            $i=0;
            foreach($matchs[1] as $k=>$v){
                if ($v) {
                    $value= (isset($matchs[2][$i]))?str_replace(array(/*"'",*/'"'),'',$matchs[2][$i]):false;
                    $ret[strtolower($v)]=trim($value);
                }
                $i++;
            }
        }
        return $ret;
    }

$method = new ReflectionMethod( get_called_class(), ‘testMethode’ );
$docs=$this->_parseDoc($method);

ici, nous récupérons  par « ReflectionMethod » notre méthode, ensuite par « _parseDoc »
, nous avons un tableau ($docs) de notre syntaxe avec en clé le nom du paramètre; il faut maintenant filtrer les paramètres de notre méthode par :
function _setParams(ReflectionMethod $method, array $docs, $args)

la méthode « _setParams » parcours le tableau $args (les arguments passés a la méthode) et teste si la valeur du paramètre correspond bien a la regex qui est dans $docs(notre documentation). Si la valeur du paramètre n’est pas valide une Exception est déclenchée.

le code source du trait :

Trait paramFilters {

    private $debug=false;

    private function affDebug($label,$array='',$end=''){
        if ($this->debug) echo  "<br />\t".$label.': '.print_r($array,true).$end;
    }

    /**
     *test (et filtre) les arguments passées a la fonction appelante
     *@return array arguments apres filtre
     **/
    protected function testArgs (){
        // recup du nom et des parametres de la fonction appelante
        $arguments = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT,2)[1];

            $this->affDebug('<pre><hr>'.__class__.'::'.$arguments['function']);
        $method = new ReflectionMethod( __class__, $arguments['function'] );
        $docs=$this->_parseDoc($method);
            $this->affDebug('getParameters',$method->getParameters());
        return $params=$this->_setParams($method,$docs,$arguments['args']);

    }

    /**
     * lire la documentation de la méthode
     **/
    private function _parseDoc(ReflectionMethod $methode){
        $ret=array(); 
        $doc=$methode->getDocComment();
        $nb = preg_match_all('/@\s*([_,a-z,A-Z]+)\s*|\((\S+)\)/s',$doc,$matchs);
        if ($nb>0){
            array_shift($matchs[2]);
            $i=0;
            foreach($matchs[1] as $k=>$v){
                if ($v) {
                    $value= (isset($matchs[2][$i]))?str_replace(array(/*"'",*/'"'),'',$matchs[2][$i]):false;
                    $ret[strtolower($v)]=trim($value);
                }
                $i++;
            }
        }
        $this->affDebug('parseDoc',$ret);
        return $ret;
    }

    /**
     * faire des test ou filtres sur les arguments de la fonction
     * @param array $docs tableau de la documentation
     * @param array $args les arguments a tester passés a la méthode
     * @return array InvalidArgumentException si non valide
     *
     **/
    private function _setParams(ReflectionMethod $method, array $docs, $args){
        $this->affDebug('arguments',$args);
        $params=array();
        $i=0;
        $max=count($args);
	foreach($method->getParameters() as $pos=>$param){
            if (isset($args[$i])){
                $name=$param->getName();
                $params[$pos]= $args[$i++];

                $params[$pos]=$this->_filtrer($docs,$name,$params[$pos]);
                $params[$pos]=$this->_valider($docs,$name,$params[$pos]);
            }
	}
        $this->affDebug('','','</pre>');
        return $params;
    }

        private function _valider($docs,$name,$value){  // tests validité @_name
            if (isset($docs['_'.$name])){
                switch ($docs['_'.$name]) {
                    case 'int': if(!($value=(int)$value))
                        throw new InvalidArgumentException($name.' pas de type entier');
                        else break;
                    case 'numeric': if(!is_numeric($value))
                        throw new InvalidArgumentException($name.' pas de type numérique');
                        else break;
                    case '!null': if (($value==0)||($value==''))
                        throw new InvalidArgumentException($name.' vide');
                        else break;
                    case 'datesql': if (preg_match("/^(19|20)\d\d[-.](0[1-9]|1[012])[-.](0[1-9]|[12][0-9]|3[01])$/",$value)!=1)
                        throw new InvalidArgumentException($name.' pas de type date sql');
                        else break;
                    case 'date': if (preg_match("/^(0[1-9]|[12][0-9]|3[01])\/(0[1-9]|1[012])\/(19|20)\d\d$/",$value)!=1)
                        throw new InvalidArgumentException($name.' pas de type date');
                        else break;
                }
                if (substr($docs['_'.$name],0,1)=='/'){
                    if (preg_match($docs['_'.$name],$value)!=1){
                        $s= ($this->debug) ? '%s (%s) non conforme au masque %s' : '%s non conforme au masque';
                        throw new InvalidArgumentException( sprintf($s,$name,$value,$docs['_'.$name]));
                    }
                }
            }
            return $value;
        }

        private function _filtrer($docs,$name,$value){  // filtres @name
            if (isset($docs[$name])){
                switch ($docs[$name]) {
                    case 'int': return preg_replace('/\D+/', '', $value); 
                }
                if (substr($docs[$name],0,1)=='/'){
                    return preg_replace($docs[$name], '', $value);
                }                
            }
            return $value;
         }

}

Maintenant il ne nous reste plus qu’a créer une classe pour tester notre trait et son utilisation:

class essai {
    use paramFilters;
    /**
     *@id("/[4]/")                  // filtre les 4 
     *@_id("/^[0-5]?[0-9]$/")       // uniquement de 0 a 59
     *@_date("datesql")
     *@chaine ("/idiot/")
     **/
    function test( $id, $date, $chaine='' ){
        //$this->debug=true;
        $ret=$this->testArgs();
        //var_dump( $ret );
        return $ret;
    }
}

Le trait a donc une seule méthode a utiliser: ->testArgs() sans paramètres.

Maintenant faisons des test :

$essai= new essai();

    try{
        $ret=$essai->test(50,'1955-12-25');
        } catch (Exception $e) {
        echo '<h4>'.$e->getMessage().'</h4>';
    }

    try{
        $essai->test(44,'1955-12-25');
    } catch (Exception $e) {
        echo '<h4>'.$e->getMessage().'</h4>'; // et oui filtre appliqué avant le test
    }

    try{
        $ret=$essai->test(50,'1955-31-25');
    } catch (Exception $e) {
        echo '<h4>'.$e->getMessage().'</h4>';
    }

    try{
        $chaine = $essai->test(50,'1955-11-25','il est idiot ce filtre!')[2];
        echo '$chaine après ce tres bon filtre: <i>'.$chaine. '</i>';
    } catch (Exception $e) {
        echo '<h4>'.$e->getMessage().'</h4>';
    }

J’obtiens ceci, on distingue nettement trois tableaux:
la documentation,
par reflection les nom des paramètres,
les valeurs des paramètres.

Share Button

Vous devriez aimer...