Idee für einen einfachen, abstrakten Custom Validator mit PHP 5.3 Closures
Gerade eben hatte ich Spaß daran mit der neuen Möglichkeit Closures in PHP 5.3 zu verwenden herumzuspielen. Mir kam die Idee, das Ganze mit Zend_Validate und Zend_Form zu einem Custom Validator zu kombinieren.
Folgende Klasse kam dabei heraus:
namespace Application\Validators;
class ClosureValidator extends \Zend_Validate_Abstract
{
const GENERIC_ERROR_KEY = 'genericErrorKey';
protected $_messageTemplates = array();
/**
* @var \Closure
*/
private $predicate;
public function __construct(\Closure $predicate)
{
$this->predicate = $predicate;
}
public function isValid($value, $context = null)
{
$predicate = $this->predicate;
list($result, $errorMessage) = $predicate($value, $context);
if(!is_bool($result))
throw new \RuntimeException('First return value of predicate must be boolean value.');
if(!is_string($errorMessage))
throw new \RuntimeException('Second return value of predicate must be string value (error message).');
if($result) return true;
$this->_messageTemplates[self::GENERIC_ERROR_KEY] = $errorMessage;
$this->_error(self::GENERIC_ERROR_KEY);
return false;
}
}
Wie habe ich die Klasse verwendet?
Die Klasse kam bei mir zum Einsatz bei einem Zend_Form mit dem ein User sein Passwort aktualisieren kann.
Die Bedingung war, dass der User sein Passwort nur aktualisieren kann, wenn er sein aktuelles Passwort richtig eingibt. Der Einfachheit halber gehe ich jetzt mal davon aus, dass die Passwörter in der DB im Klartext gespeichert sind und nicht gehashed (natürlich sollte man NIE die original Passwörter speichern, sondern immer nur als Hash!).
So wird zunächst mal das Formular verwendet (bekommt also eine User Instanz im Konstruktor mit übergeben):
$user = new \Application\Models\User();
$user->setPassword('CURRENT_PASSWORD');
$form = new \Application\Forms\UserPasswordChangeForm($user);
Im Formular wird der ClosureValidator dann wie folgt hinzugefügt:
namespace Application\Forms;
class UserPasswordChangeForm extends \Zend_Form
{
/**
* @var \Application\Models\User
*/
private $user;
public function __construct(\Application\Models\User $user, $options = null)
{
$this->user = $user;
parent::__construct($options);
}
public function init()
{
$user = $this->user;
$this->setMethod('post');
$this->addElement(new \Zend_Form_Element_Password('currentPassword', array(
'label' => 'Current Password:',
'required' => true,
'filters' => array('StringTrim'),
// Validator mit Closure hier hinzufügen
'validators' => array(new \Application\Validators\ClosureValidator(function($value) use(&$user) {
return array(
$user->getPassword() == $value,
'Invalid Password'
);
})),
)));
// ... weitere Formularelemente weggelassen
$this->addElement(new \Zend_Form_Element_Submit('submit', array(
'ignore' => true,
'label' => 'Save',
)));
}
}
Der Validator bekommt eine Closure Funktion mitübergeben, welche ein Array mit zwei Elementen zurückgeben muss: zum einen das Validation Ergebnis (boolean true oder false) und zum anderen einen String mit einer Error-Message, die im Fehler-Fall verwendet werden soll.
Über die Implementierung kann man sicher reden und obs das ganze einfacher oder komplizierter macht ist sicher auch diskussionswürdig, aber als Idee finde ich es doch irgendwie ganz nett. Mal schauen, ob man das so brauchen kann.