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.