It should be easy to use it in a form generator package or API package.
It should be possible to validate both individual values and value sets.
Messages should be customizable.
Validating a single value
$rules = new Rules([
new Required(),
(new Number())->min(10),
function ($value): Result {
$result = new Result();
if ($value !== 42) {
$result->addError('Value should be 42!');
}
return $result;
}
]);
$result = $rules->validate(41);
if ($result->isValid() === false) {
foreach ($result->getErrors() as $error) {
// ...
}
}
Validating a set of data
class MoneyTransfer implements \Yiisoft\Validator\DataSet
{
private $amount;
public function __construct($amount) {
$this->amount = $amount;
}
public function getValue(string $key){
if (!isset($this->$key)) {
throw new \InvalidArgumentException("There is no \"$key\" in MoneyTransfer.");
}
return $this->$key;
}
}
$moneyTransfer = new MoneyTransfer();
$validator = new Validator([
'amount' => [
(new Number())->integer(),
(new Number())->integer()->max(100),
function ($value): Result {
$result = new Result();
if ($value === 13) {
$result->addError('Value should not be 13!');
}
return $result;
}
],
]);
$results = $validator->validate($moneyTransfer);
foreach ($results as $attribute => $result) {
if ($result->isValid() === false) {
foreach ($result->getErrors() as $error) {
// ...
}
}
}
Creating your own validation rules
In order to create your own valdation rule you should extend Rule class:
namespace MyVendor\Rules;
use Yiisoft\Validator\Result;
use Yiisoft\Validator\Rule;
class Pi extends Rule
{
public function validateValue($value): Result
{
$result = new Result();
if ($value != M_PI) {
$result->addError('Value is not PI!');
}
return $result;
}
}
Are we sure that this syntax is needed and useful?
At first glance it is less readable respect previous array values syntax and, from my point of view, custom validation inside rules it is for me almost never useful, because the most time I need to compare an attribute (or attributes) with others, so I need entire model.
For example, one validation rule could be that a specific field is mandatory when a check is set, and I solve it with “afterValidate” method. This should be impossible to achieve with callback.
class CheckboxForm implements DataSet
{
private $checked;
private $mandatoryIfChecked;
public function __construct(array $data)
{
$this->checked = $data['checked'] ?? false;
$this->mandatoryIfChecked = $data['mandatoryIfChecked'] ?? null;
}
public function rules()
{
$rules = [];
if ($this->checked) {
$rules['mandatoryIfChecked'] = [new Required()];
}
return $rules;
}
public function getValue(string $key)
{
if (!isset($this->$key)) {
throw new InvalidArgumentException("$key does not exist in CheckboxForm.");
}
return $this->$key;
}
}
class FormController
{
public function actionSubmit()
{
$form = new CheckboxForm($_POST);
$validator = new Validator($form->rules());
$results = $validator->validate($form);
// ..
}
}
class MyModel extends Model {
public function afterValidate()
{
if(($this->checked)&&($this->mandatoryIfChecked == null))
{
$this->addError('mandatoryIfChecked', 'This field is mandatory when check is on');
}
}
}
The old syntax is clearer and shorter. Finally, afterValidation is inside the model, so it will be called everytime model is validated/saved, without adding extra code.
Yes but there’s one huge issue with the old syntax. You need a class extending from Model while often validation is required in different classes: requests, DTOs, Entities etc. that may be inherited from something we do not control.
We can subclass Model class to handle different scope where we are validating data, to keep things clean. This new syntax is maybe useful for IDE integration, but not from developer point of view.
Anyway, could we continue to use also old syntax or it will be discontinued? For all other simpler cases in which validation is required only in web environment.
I think it is ok to couple active record library with validators and to use it inside. Lots of developers love this functionality
There was a discussion at GitHub and Slack with the decision to necessarily leave ability to configure validation rules with arrays (I’ll seek for links if you want). I.e. new Number(['min' => 10]);
I don’t think so. Yii 1.1 and Yii 2.0 were actively used with Doctrine and in that case validation, forms, data providers and everything was not usable. Now we have multiple alternatives some of which, I believe, are better than Doctrine and potentially work well in AR-style as well. That’s why I want an ability to use Yii with any DB layer possible.
That could be added, yes. Syntax would still require new XRule([ ... ]).
I think that’s not needed. See my code post above. You can compose rules dynamically.
I think new syntax isn’t worse and is IDE friendly:
return [
'username' => [
new Required(),
(new HasLength())
->min(3)
->max(255),
new MatchRegularExpression($this->userModel::$usernameRegexp),
new Unique()
->against($this->userModel)
->message($this->app->t('ModuleUser', 'This username has already been taken.')),
],
'email' => [
new Required(),
new Email(),
(new Unique())
->against($this->userModel)
->message($this->app->t('ModuleUser', 'This email address has already been taken.')),
],
'password' => [
(new Required())
->skipOnEmpty($this->module->accountGeneratingPassword),
(new HasLength())
->min(6)
->max(72),
]
];
More than that, concrete rule syntax could be tuned. It’s not final yet.
If the change will give us more freedom and more functionality I agree, but must have the written documentation before removing the validators, to be clear in their syntax and form of employment.
Can we make it more IDE friendly? For example adding an validator\Element
return [
(new Element('phone'))->set(new Required())->set(/* ... */),
// For some one who loved this one
(new Element('email'))->setMulti(new Required(), new Email() /*, ... */),
];
So we can create a multi usable classes like
class Email extends validator\Element
{
public function __constructor(string $attribute = 'email')
{
parent::__construct($attribute);
}
public function defaultValidators(): array
{
return [new Required(), new Email()];
}
}
and just return it
return [
new myvalidator\Email(),
new myvalidator\Phone(),
];
The problem with array syntax is that it should not be AR responsibility to interpret this array and instantiate validators objects. And you can still create helper which will interpret this array and instantiate validators at user code level, without introducing such convention in framework itself.