Les ModelValidator de CakePHP

⏲️ ~5 min de lecture

Publié le par Pierre
CakePHP

TL;DR #

L'utilisation de l'attribut Model::validate est so 2012 ... pour déclarer les règles de validation dans CakePHP, utilisez une classe spécifique qui étend de ModelValidator. Votre code sera bien plus maintenable !

Introduction #

Le 2 Juin 2012 est sortie la version 2.2.0 de CakePHP introduisant pour la première fois la notion de ModelValidator. 2 ans plus tard nous nous rendons compte lorsque que nous échangeons avec des confrères ou lorsque l'on reprend des projets existants, que trop peu utilisent cette nouveauté à bon escient.

Le but de cet article est de montrer en un exemple en quoi cela peut simplifier votre code en réduisant la taille de vos modèles tout en gagnant en réutilisabilité.

Pour des raisons historiques la classe Model de CakePHP contient beaucoup (et donc trop) de responsabilités. En effet, cette classe est notamment responsable de :

La démarche de séparation des responsabilités de cette classe a donc commencé avec CakePHP 2.2, quand la core team a décidé de déplacer le code en charge de la validation des données dans une classe séparée appelée ModelValidator. Pour information, le grand coup de balai nécessaire (mais cassant la rétro-compatibilités) a été passé comme il se doit pour la version 3.0 du framework ! Je vous invite à lire la documentation déjà très complète à ce sujet.

La validation historique dans CakePHP #

L'approche traditionnelle pour mettre en place de la validation de données sur un Modèle CakePHP est d'utiliser l'attribut Model::validate.

Voici un exemple :

validate = array(
			'title' => array(
				'notempty' => array(
					'rule' => array('notempty'),
					'last' => true,
					'message' => __('You have to define a title for this content')
				),
				'maxlength' => array(
					'rule' => array('maxlength', 255),
					'message' => __('The title cannot exceed 255 chars')
				),
			),
			'content' => array(
				'notempty' => array(
					'rule' => array('notempty'),
					'message' => __('You have to define a content')
				),
			),
			'active' => array(
				'boolean' => array('rule' => array('boolean')),
			),
		);
		parent::__construct($id, $table, $ds);
	}

	protected function _findPublished($state, $query, $results = array()) {
	    // ...
	}

	public function publish() {
	    // ...
	}
}

Dans cet exemple qui ne contient que quelques règles simples, on se rend compte que la déclaration des règles de validation prend une place importante dans le Modèle. Cela peut être bien plus important lorsque des règles de validation personnalisées entrent en jeu : les méthodes de validation étant alors mêlées au code métier du modèle.

Nous allons voir comment le même modèle peut être simplifié en déplaçant cette logique de validation.

I can haz Validator #

La méthode Model::validator() permet depuis CakePHP 2.2 d'injecter ou de récupérer une instance de la classe en charge de la validation des données. Par défaut c'est donc une instance de ModelValidator qui est créée (voir le code source) et se comporte exactement comme le modèle dans les versions précédentes.

Je vous invite à regarder le code pour en savoir plus sur le fonctionnement de cette classe. Pour cet article nous allons créer une classe permettant de valider un Content ... que nous nommerons créativement ContentValidator !

Je vous recommande donc de créer le fichier dans app/Model/Validator/ContentValidator.php, ainsi nous pouvons réécrire le modèle Content comme suit :

validator(new ContentValidator($this));
	}

	protected function _findPublished($state, $query, $results = array()) {
	    // ...
	}

	public function publish() {
	    // ...
	}
}

C'est bien plus concis, et notre nouveau validator aura de son côté un code similaire à ce qui était dans le modèle auraparavant.

Ceci dit, et pour illustrer rapidement quelques possibilités que nous avons fait émerger je vous met ici un exemple légèrement différent de l'original :

add('title', 'mandatory', $this->__mandatoryRule('The title'));
		$this->add('title', 'max_length', $this->__maxLengthRule('The title'));

		$this->add('content', 'mandatory', $this->__mandatoryRule('The content'));

		$this->add('slug', 'mandatory', $this->__mandatoryRule('The slug'));
		$this->add('slug', 'max_length', $this->__maxLengthRule('The slug'));
	}

	private function __mandatoryRule($what) {
		return array(
			'allowEmpty' => false,
			'rule' => 'notEmpty',
			'message' => sprintf('%s is mandatory', $what)
		);
	}

	private function __maxLengthRule($what, $max = 255) {
		return array(
			'allowEmpty' => true,
			'rule' => array('maxLength', $max),
			'message' => sprintf('%s cannot exceed %s chars', $what, $max)
		);
	}

}

Cela permet donc de regrouper la logique de validation en un seul et même endroit. C'est bien plus testable mais ce n'est que le début. Vous pouvez ensuite penser à réutiliser la logique avec un Trait par exemple (dans notre cas, les méthodes privées pourrait être déplacées dans un CommonValidationsTrait), ou hériter des règles de validation existantes.

En tout cas vos modèles auront moins de responsabilités et c'est mieux ! C'est déjà dans le coeur de CakePHP depuis 2 ans et cela sera amplifié dans la prochaine version majeure alors à vos claviers, prêts ... refactorez !

Cet article vous a plu ? Sachez que nous recrutons !

← Accueil