If you're asking, "What's Yii?" check out my earlier tutorial: Introduction to the Yii Framework, which reviews the benefits of Yii and includes an overview of what's new in Yii 2.0, released in October 2014.
In this Programming With Yii2 series, I'm guiding readers in use of the newly upgraded Yii2 Framework for PHP. This tutorial is our second part, looking at Yii2's validators. Validators simplify the code needed to validate input, i.e. verify conformance or non-conformance of data input, typically from users via web forms. Specifically, we're going to explore some of the built-in specialty validations that are common to web development.
Here's a list of the built-in Yii validators and links to documentation that we're going to explore:
- CaptchaValidator: Validates a CAPTCHA form verification field.
- CompareValidator: Compares two values from the form or a constant, e.g. x must be less than 99.
-
EmailValidator: Ensures a value is a valid email address.
- ExistValidator: Ensures that a value exists in another table.
- FileValidator: Ensures existence of an uploaded file.
- ImageValidator: Validates image and image properties.
- RangeValidator: Ensures a value is within a list of values.
- RegularExpressionValidator: Performs validation against a condition defined by a regular expression.
-
UniqueValidator: Ensures value is unique within a table, such as an email address.
- UrlValidator: Ensures value is in URL format, e.g. http://yourdomain.com.
I'm going to guide you through examples of each of these validations using the Hello application codebase from past tutorials and a couple from our Building Your Startup Series which also uses Yii2. Use the GitHub links on this page to get the code.
Just a reminder, I do participate in the comment threads below. I'm especially interested if you have additional ideas or want to suggest topics for future tutorials. You can also reach me @reifman on Twitter or email me at Lookahead Consulting.
What Is a Validator?
If you're a web developer, you likely know that user input can't be trusted. For example, users can use SQL injection techniques to try to run queries that change or expose passwords. Someone once leveraged SQL injection against my open source PHPList installation and managed to discover one of my passwords (PHPList stored these in plain text). More commonly, you just want to ensure that the data users provide conforms to the types, forms and ranges of your application.
Building validators in PHP by hand takes time. The Yii Framework provides a ton of baseline validation features so there's no need to build them from scratch. But, if you need some custom extensions, that's straightforward as well.
Validations are yet another reason why I think it always makes sense to build applications on a web framework such as Yii rather than vanilla PHP.
In earlier episodes, we've also talked a lot about Yii's code generator, Gii. One of the benefits of Gii is that it will write the appropriate validation rules for your models based on the SQL type definitions in the schema. This is a big time saver.
You may want to go back to our last episode to read more on Yii2's basic type validations.
Now, let's begin looking at the next set of Yii2's built-in validators.
The Next Set of Validators
The Captcha Validator
Let's begin with CaptchaValidator which checks that there is a proper response to a CAPTCHA verification field. CAPTCHAs help ensure that a human is filling out the form, hopefully keeping automated scripts from submitting it.
Here's an example of the Yii Captcha in action:
In our Hello codebase, I've simplified our sample form to just include the Thought and Captcha fields for now. Here's a look at the model code's rule definitions:
class Sample extends \yii\db\ActiveRecord { public $captcha; /** * @inheritdoc */ public function rules() { return [ [['thought'], 'string', 'max' => 255], [['thought'], 'trim'], [['thought'], 'required'], [['captcha'], 'captcha'], ]; }
The captcha isn't part of our database schema—it's only used to verify the form. Therefore, I've added an attribute to the model for it, e.g. public $captcha;
.
Here's the view code for the form. We have to include the Captcha library at the top.
<?php use yii\helpers\Html; use yii\widgets\ActiveForm; use yii\captcha\Captcha; /* @var $this yii\web\View */ /* @var $model app\models\Sample */ /* @var $form yii\widgets\ActiveForm */ ?> <div class="sample-form"> <?php $form = ActiveForm::begin(); ?> <?= $form->errorSummary($model); ?> <?= $form->field($model, 'thought')->textInput(['maxlength' => 255]) ?> <?= $form->field($model, 'captcha')->widget(\yii\captcha\Captcha::classname(), [ // configure additional widget properties here ]) ?> <div class="form-group"> <?= Html::submitButton($model->isNewRecord ? 'Create' : 'Update', ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> </div> <?php ActiveForm::end(); ?> </div>
Here's what the Captcha validation looks like in action:
If you click the Captcha, Yii will generate a new image.
The Compare Validator
Now, let's move on to the CompareValidator. This validator compares two values from the form or a single form value to a constant, such as x must be less than 99.
For this example, I want to make sure that user input for rank is greater than zero but less than or equal to 100.
First, I'll add the input field back to our form for the rank attribute:
<?php $form = ActiveForm::begin(); ?> <?= $form->errorSummary($model); ?> <?= $form->field($model, 'thought')->textInput(['maxlength' => 255]) ?> <?= $form->field($model, 'rank')->textInput() ?> <?= $form->field($model, 'captcha')->widget(\yii\captcha\Captcha::classname(), [ // configure additional widget properties here ]) ?>
Then I'll add two compare validation rules to our model:
public function rules() { return [ [['thought'], 'string', 'max' => 255], [['thought'], 'trim'], [['thought'], 'required'], [['captcha'], 'captcha'], [['rank'], 'integer'], ['rank', 'compare', 'compareValue' => 0, 'operator' => '>'], ['rank', 'compare', 'compareValue' => 100, 'operator' => '<='], ]; }
You can see a full list of available comparison operators here.
Here's what our form looks like when the user submits an invalid attribute:
If we want to provide the specific constraint rules in one error message, Yii Validators allow you to customize the error shown to the user, like this:
Implementing this is quite simple with the addition of the message attribute:
public function rules() { return [ [['thought'], 'string', 'max' => 255], [['thought'], 'trim'], [['thought'], 'required'], [['captcha'], 'captcha'], [['rank'], 'integer'], ['rank', 'compare', 'compareValue' => 0, 'operator' => '>','message'=>Yii::t('app','Rank must be between 0 and 100 inclusive.')], ['rank', 'compare', 'compareValue' => 100, 'operator' => '<=','message'=>Yii::t('app','Rank must be between 0 and 100 inclusive.')], ]; }
Updating Our Schema to Test More Validations
For some of these next validation tests, I'm going to ask you to add some fields to the database.
In \migrations\m150219_235923_create_sample_table.php
, we'll add some new fields to test the next set of validators: email, URL, filename, etc.
$this->createTable('{{%sample}}', [ 'id' => Schema::TYPE_PK, 'thought' => Schema::TYPE_STRING.' NOT NULL DEFAULT ""', 'goodness' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'rank' => Schema::TYPE_INTEGER . ' NOT NULL', 'censorship' => Schema::TYPE_STRING . ' NOT NULL', 'occurred' => Schema::TYPE_DATE . ' NOT NULL', 'email' => Schema::TYPE_STRING . ' NOT NULL DEFAULT ""', 'url' => Schema::TYPE_STRING . ' NOT NULL DEFAULT ""', 'filename' => Schema::TYPE_STRING.' NOT NULL', 'avatar' => Schema::TYPE_STRING.' NOT NULL', ], $tableOptions);
Then run the migration down to drop the table and then up:
Admins-MBP:hello Jeff$ ./yii migrate/down 1 Yii Migration Tool (based on Yii v2.0.2) Total 1 migration to be reverted: m150219_235923_create_sample_table Revert the above migration? (yes|no) [no]:yes *** reverting m150219_235923_create_sample_table > drop table {{%sample}} ... done (time: 0.002s) *** reverted m150219_235923_create_sample_table (time: 0.005s) Migrated down successfully. Admins-MBP:hello Jeff$ ./yii migrate/up 1 Yii Migration Tool (based on Yii v2.0.2) Total 1 new migration to be applied: m150219_235923_create_sample_table Apply the above migration? (yes|no) [no]:yes *** applying m150219_235923_create_sample_table > create table {{%sample}} ... done (time: 0.007s) *** applied m150219_235923_create_sample_table (time: 0.010s) Migrated up successfully.
We're now ready to test the email and URL validators.
Email & URL Validators
The EmailValidator ensures a value is a valid email address and the UrlValidator ensures a value is in URL format, e.g. http://yourdomain.com.
It's quite simple to create rules for our new email and URL fields:
public function rules() { return [ [['thought'], 'string', 'max' => 255], [['email'], 'email'], [['url'], 'url'],
Here's the view code for the form. Note how I'm using custom labels to improve the form usability:
<div class="sample-form"> <?php $form = ActiveForm::begin(); ?> <?= $form->errorSummary($model); ?> <?= $form->field($model, 'thought')->textInput(['maxlength' => 255]) ?> <?= $form->field($model, 'email')->textInput()->label(Yii::t('app','Your email address')) ?> <?= $form->field($model, 'url')->textInput()->label(Yii::t('app','Your website')) ?>
Here are the validators in action:
These are obviously highly useful for web applications.
The Exist Validator
The ExistValidator is super useful in certain scenarios. It can ensure that a value exists in another table. And it can be used in a variety of ways—here are some examples given in the documentation:
// a1 needs to exist ['a1', 'exist'] // a1 needs to exist, but its value will use a2 to check for the existence ['a1', 'exist', 'targetAttribute' => 'a2'] // a1 and a2 need to exist together, and they both will receive error message [['a1', 'a2'], 'exist', 'targetAttribute' => ['a1', 'a2']] // a1 and a2 need to exist together, only a1 will receive error message ['a1', 'exist', 'targetAttribute' => ['a1', 'a2']] // a1 needs to exist by checking the existence of both a2 and a3 (using a1 value) ['a1', 'exist', 'targetAttribute' => ['a2', 'a1' => 'a3']]
The Yii documentation highlights that Exist can be used to "verify that a foreign key contains a value that can be found in the foreign table."
For our example, I'm going to create a rule that checks that the email address in the form already exists in our registered User table. To do this, we use the targetClass which tells Yii which Class (or Model table) to look up the user's email address in for validation.
Here's the rule definition—note the inclusion of our User model at the top:
<?php namespace app\models; use Yii; use yii\models\User; /** * This is the model class for table "sample". * * @property integer $id * @property string $thought * @property integer $goodness * @property integer $rank * @property string $censorship * @property string $occurred */ class Sample extends \yii\db\ActiveRecord { public $captcha; /** * @inheritdoc */ public function rules() { return [ [['thought'], 'string', 'max' => 255], [['email'], 'email'], [['email'], 'exist','targetClass'=>'\app\models\User','message'=>Yii::t('app','Sorry, that person hasn\'t registered yet')], [['url'], 'url'],
That instructs Yii to query the User table to ensure that the provided email address matches a previously registered user.
Here's what it looks like in action:
You can learn more about the Exist validation and its permutations here.
The File and Image Validators
Next, I'll show you examples of the FileValidator, which ensures existence, MIME-type and size of an uploaded file, and the ImageValidator, which validates the image and its properties.
To explore the File and Image validators, let's let's look at an example from the Building Your Startup With PHP series: User Settings, Profile Images and Contact Details. In that episode in the UserSettings model, we're allowing users to upload a file for their profile image.
The image attribute accepts the uploaded file:
public function rules() { return [ [['user_id', ], 'required'], [['user_id', ], 'unique'], [['image'], 'safe'], [['image'], 'file', 'extensions'=>'jpg, gif, png'], [['image'], 'file', 'maxSize'=>'100000'], ['image', 'image', 'extensions' => 'png, jpg, gif', 'minWidth' => 100, 'maxWidth' => 400, 'minHeight' => 100, 'maxHeight' => 400, ],
The FileValidators are ensuring that the image ends in a proper image extension and is less than 100,000 bytes.
The ImageValidator also verifies the extension type as well as the width and height ranges for the image.
Here's an example of errors generated by uploading an image whose dimensions are larger than 400 x 400 pixels:
That's my assistant up above who used to like to copyedit my tutorials.
The Range In Validator
There's also the RangeValidator which ensures a value is within a list of allowable entries.
For our example, let's add the field for censorship back into the form:
<div class="sample-form"> <?php $form = ActiveForm::begin(); ?> <?= $form->errorSummary($model); ?> <?= $form->field($model, 'thought')->textInput(['maxlength' => 255]) ?> <?= $form->field($model, 'email')->textInput()->label(Yii::t('app','Your email address')) ?> <?= $form->field($model, 'url')->textInput()->label(Yii::t('app','Your website')) ?> <?= $form->field($model, 'censorship')->textInput() ?> <?= $form->field($model, 'rank')->textInput() ?> <?= $form->field($model, 'captcha')->widget(\yii\captcha\Captcha::classname(), [ // configure additional widget properties here ]) ?>
Then, we'll add a RangeValidator to match the response to a yes or no string:
public function rules() { return [ [['thought'], 'string', 'max' => 255], ['thought', 'match', 'pattern' => '/^[a-z][A-Za-z,;\"\\s]+[!?.]$/i','message'=>Yii::t('app','Your thoughts should form a complete sentence of alphabetic characters.')], [['email'], 'email'], [['email'], 'exist','targetClass'=>'\app\models\User','message'=>Yii::t('app','Sorry, that person hasn\'t registered yet')], [['url'], 'url'], ['censorship', 'in', 'range' => ['yes','no','Yes','No'],'message'=>Yii::t('app','The censors demand a yes or no answer.')],
Here's an example of the RangeValidator in action:
The Regular Expression Match Validator
Next, let's look at the RegularExpressionValidator, which performs validation against a condition defined by a regular expression.
In our example, I'm using the following regex to match complete sentences with alphabetic characters. This means they must end with either (!, ? or .) and have no numeric characters.
public function rules() { return [ [['thought'], 'string', 'max' => 255], ['thought', 'match', 'pattern' => '/^[a-z][A-Za-z,;\"\\s]+[!?.]$/i','message'=>Yii::t('app','Your thoughts should form a complete sentence of alphabetic characters.')],
Here's an example of user input that fails the test because of the numbers and the lack of a trailing punctuation mark:
Here's a valid sentence:
You might also be interested in Eight Regular Expressions You Should Know (Tuts+) as a reference for common regex patterns.
The Unique Validator
Finally, let's review the UniqueValidator, which ensures that a value is unique within a table, such as an email address or a slug.
I reviewed SluggableBehavior earlier in this series, which offers its own built-in uniqueness support. However, let's look at a couple more examples from the Building Your Startup With PHP series.
In the codebase for Meeting Planner (from the more recent tutorial episodes) in the Place model (\frontend\models\Place.php
), we use the unique validator in several ways:
public function rules() { return [ [['name','slug'], 'required'], [['place_type', 'status', 'created_by', 'created_at', 'updated_at'], 'integer'], [['name', 'google_place_id', 'slug', 'website', 'full_address', 'vicinity'], 'string', 'max' => 255], [['website'], 'url'], [['slug'], 'unique'], [['searchbox'], 'unique','targetAttribute' => 'google_place_id'], [['name', 'full_address'], 'unique', 'targetAttribute' => ['name', 'full_address']], ]; }
First, we use the unique rule with the slug to augment SluggableBehavior, which is redundant; but you can see the validation format.
Second, we're checking that the results of the Google Places Autocomplete searchbox result in the hidden field for google_place_id
being unique in that it doesn't yet exist within the Places table yet. We're essentially preventing duplicate Google Place IDs.
The significant piece of this is that Yii2's unique validator allows us to enforce uniqueness on the visible field (searchbox
) while validating it on the secondary column returned via AJAX from Google (google_place_id
).
Third, we're ensuring that name
and full_address
are unique together. In other words, duplicate places names are okay. There can be a bazillion Starbucks. However, we do not want anyone entering the same Starbucks location twice.
Note: Starbucks coffee is not an effective stimulant for software developers. I encourage you to frequent independent coffeehouses.
Here's an example of this in action:
What's Next?
I hope that you agree how simple and useful Yii2 validators are for web development. I simply can't imagine ever returning to vanilla PHP development without the aid of a framework.
Watch for upcoming tutorials in my Programming With Yii2 series as I continue diving into different aspects of the framework. In the next episode, I'm going to review Yii2's advanced validation features such as:
- Conditional validation to perform a validation rule only if a specific event is true
- Custom validators to create essential validations beyond what Yii offers out of the box
-
Client side validation to make use of Yii's built-in ActiveForm JavaScript validation without requiring a page refresh
- AJAX validation for implementing server side AJAX validations to extend Yii's client-side JavaScript page validation capabilities.
- Validation events to override validation or perform specific functionality before and/or after validation
-
Defining scenarios to selectively apply rules for certain situations
- Ad Hoc validation to use validation rules independently of form submission
I welcome feature and topic requests. You can post them in the comments below, reach out to me @reifman on Twitter, or email me at Lookahead Consulting.
If you'd like to know when the next Yii2 tutorial arrives, you can also check my Tuts+ instructor page. It always includes links to my articles immediately after they are published.
Related Links
- Yii2 Guide to Validating User Input
- Yii2 Guide to Core Validators
- Yii2 Validators (Documentation)
- Yii2 Developer Exchange, my own Yii2 resource site
Comments