Extending Craft CMS with Validation Rules and Behaviors
Tags: craftcms, craft3, php
Published: 2020-05-30 19:07:58

---
title: Extending Craft CMS with Validation Rules and Behaviors
published: true
date: 2020-04-24 02:55:00 UTC
tags: craftcms,craft-3,php
canonical_url: https://nystudio107.com/blog/extending-craft-cms-with-validation-rules-and-behaviors
---

# Extending Craft CMS with Validation Rules and Behaviors

### Craft CMS is a web appli­ca­tion that is amaz­ing­ly flex­i­ble & cus­tomiz­able using the built-in func­tion­al­i­ty that the plat­form offers. Use the platform!

Andrew Welch / [nystudio107](https://nystudio107.com)

![Craft cms rules behaviors yii2 module](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x675_crop_center-center_82_line/craft-cms-rules-behaviors-yii2-module.jpg)

[Craft CMS](https://craftcms.com/) is built on the rock-sol­id [Yii2 frame­work](https://www.yiiframework.com/), which is some­thing you nor­mal­ly don’t need to think about. It just works, as it should.

But there are times that you need or want to extend the plat­form into some­thing tru­ly cus­tom, which we looked at in the [Enhanc­ing a Craft CMS 3 Web­site with a Cus­tom Mod­ule](https://dev.to/gaijinity/enhancing-a-craft-cms-3-website-with-a-custom-module-7k7) article.

In this arti­cle, we’ll talk about two ways you can use the plat­form that you nor­mal­ly don’t even have to think about to your advantage.

<aside>
                                Use the Platform
                            </aside>

Some­thing we com­mon­ly hear in fron­tend devel­op­ment is to [​“use the plat­form”](https://timkadlec.com/remembers/2019-10-21-using-the-platform/), which I think is fan­tas­tic advice. Why re-invent an elab­o­rate cus­tom set­up when the plat­form already pro­vides you with a bat­tle-worn way to accom­plish your goals?

The same holds true with any kind of devel­op­ment. If you’ve writ­ing some­thing on top of a plat­form — what­ev­er that plat­form may be — I think it always makes sense to try to lever­age it as much as possible.

It’s there. It’s well thought-out. It’s test­ed. Use it!

When we’re using Craft CMS, we’re also using the Yii2 plat­form that it’s built on. Indeed, as we dis­cussed in the [So You Wan­na Make a Craft 3 Plu­g­in?](https://dev.to/gaijinity/so-you-wanna-make-a-craft-3-plugin-76m-temp-slug-4321262) arti­cle, to know Craft plu­g­in devel­op­ment, you will want to learn some part of Yii2.

So let’s do just that! The [Yii2 doc­u­men­ta­tion](https://www.yiiframework.com/doc/guide/2.0/en/structure-overview) is a great place to start.

## Mod­els & Rules

[Mod­els](https://www.yiiframework.com/doc/guide/2.0/en/structure-models) are a core build­ing block of Yii2, and so also Craft CMS. They are at the core of the [Mod­el-View-Con­troller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) (MVC) par­a­digm that many frame­works use.

<aside>
                                Mod­els are objects rep­re­sent­ing busi­ness data, rules and logic.
                            </aside>

Mod­els are used to rep­re­sent data and val­i­date data via a set of rules. For instance, Craft CMS has a [User ele­ment](https://docs.craftcms.com/api/v3/craft-elements-user.html) (which is also a mod­el) that encap­su­lates all of the data need­ed to rep­re­sent a User in Craft CMS.

It also has [val­i­da­tion rules](https://github.com/craftcms/cms/blob/develop/src/elements/User.php#L670) for the data:

```

/**
 * @inheritdoc
 */
protected function defineRules(): array
{
    $rules = parent::defineRules();
    $rules[] = [['lastLoginDate', 'lastInvalidLoginDate', 'lockoutDate', 'lastPasswordChangeDate', 'verificationCodeIssuedDate'], DateTimeValidator::class];
    $rules[] = [['invalidLoginCount', 'photoId'], 'number', 'integerOnly' => true];
    $rules[] = [['username', 'email', 'unverifiedEmail', 'firstName', 'lastName'], 'trim', 'skipOnEmpty' => true];
    $rules[] = [['email', 'unverifiedEmail'], 'email'];
    $rules[] = [['email', 'password', 'unverifiedEmail'], 'string', 'max' => 255];
    $rules[] = [['username', 'firstName', 'lastName', 'verificationCode'], 'string', 'max' => 100];
    $rules[] = [['username', 'email'], 'required'];
    $rules[] = [['username'], UsernameValidator::class];
    $rules[] = [['lastLoginAttemptIp'], 'string', 'max' => 45];

```

If this looks more like con­fig than code to you, then you’d be right! Mod­el val­i­da­tion rules are essen­tial­ly a list of rules that the data must pass in order to be con­sid­ered valid.

Yii2 has a base [Val­ida­tor class](https://www.yiiframework.com/doc/api/2.0/yii-validators-validator) to help you write val­ida­tors, and ships with a whole bunch of use­ful [Core Val­ida­tors](https://www.yiiframework.com/doc/guide/2.0/en/tutorial-core-validators) built-in that you can leverage.

And we can see here that Craft CMS is doing just that in its <kbd>craft\elements\User.php</kbd> class. Any val­i­da­tion rule is an array:

1. **Field**  — the mod­el field (aka attribute or object prop­er­ty) or array of mod­el fields to apply this val­i­da­tion rule to
2. **Val­ida­tor**  — the val­ida­tor to use, which can be a Val­ida­tor class, an alias to a val­ida­tor class, [PHP Callable](https://www.php.net/manual/en/language.types.callable.php), or even an anony­mous func­tion for [inline val­i­da­tion](https://www.yiiframework.com/doc/guide/2.0/en/input-validation#inline-validators)
3. **[params]** — depend­ing on the val­ida­tor, there may be addi­tion­al option­al para­me­ters you can define

So in the above User Ele­ment exam­ple, the <kbd>email</kbd> & <kbd>unverifiedEmail</kbd> fields are using the built-in [email core val­ida­tor](https://www.yiiframework.com/doc/guide/2.0/en/tutorial-core-validators#email) that Yii2 provides.

The <kbd>username</kbd> has sev­er­al val­i­da­tion rules list­ed, which are applied in order:

1. [**string**](https://www.yiiframework.com/doc/guide/2.0/en/tutorial-core-validators#string) — This val­ida­tor checks if the input val­ue is a valid string with cer­tain length (<kbd>100</kbd> in this case)
2. **[required](https://www.yiiframework.com/doc/guide/2.0/en/tutorial-core-validators#required)** — This val­ida­tor checks if the input val­ue is pro­vid­ed and not empty
3. **[User­nameVal­ida­tor](https://github.com/craftcms/cms/blob/develop/src/validators/UsernameValidator.php)** — This is a cus­tom val­ida­tor that P&T wrote to han­dle val­i­dat­ing the user­name field

The user­name field actu­al­ly gives us a fun lit­tle tan­gent we can go on, so let’s peek under the hood to see how sim­ple it can be to write a cus­tom validator.

Here’s what the class looks like:

```

<?php
/**
 * @link https://craftcms.com/
 * @copyright Copyright (c) Pixel & Tonic, Inc.
 * @license https://craftcms.github.io/license/
 */

namespace craft\validators;

use Craft;
use yii\validators\Validator;

/**
 * Class UsernameValidator.
 *
 * @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
 * @since 3.0.0
 */
class UsernameValidator extends Validator
{
    /**
     * @inheritdoc
     */
    public function validateValue($value)
    {
        // Don't allow whitespace in the username
        if (preg_match('/\s+/', $value)) {
            return [Craft::t('app', '{attribute} cannot contain spaces.'), []];
        }

        return null;
    }
}

```

At its sim­plest form, this is all a Val­ida­tor needs to imple­ment! Giv­en some passed in <kbd>$value</kbd>, return whether it pass­es val­i­da­tion or not.

In this case, it’s just check­ing if it pass­es a reg­u­lar expres­sion ([RegEx](https://regexr.com/)) test.

And indeed, we can even sim­pli­fy this fur­ther, and get rid of the cus­tom val­ida­tor alto­geth­er by using the [match core val­ida­tor](https://www.yiiframework.com/doc/guide/2.0/en/tutorial-core-validators#match):

```

    $rules[] = [['username'], 'match', '/\s+/', 'not' => true];

```

Then we’re real­ly be using the plat­form, and get­ting rid of cus­tom code.

<aside>
                                Being able to delete code is one of the most under­rat­ed joys of programming
                            </aside>

But let’s return from our tan­gent, and see how we can lever­age these rules to our own advan­tage. Let’s say we have spe­cif­ic require­ments for our <kbd>username</kbd> and <kbd>password</kbd> fields.

Well, we can eas­i­ly _extend_ the exist­ing mod­el val­i­da­tion rules for our User Ele­ment by lis­ten­ing for the <kbd>User</kbd> class trig­ger­ing the <kbd>EVENT_DEFINE_RULES</kbd> event: 

```

<?php
/**
 * Site module for Craft CMS 3.x
 *
 * Custom site module for the devMode.fm website
 *
 * @link https://nystudio107.com
 * @copyright Copyright (c) 2020 nystudio107
 */

namespace modules\sitemodule;

use modules\sitemodule\rules\UserRules;

use craft\elements\User;
use craft\events\DefineRulesEvent;

// ...

class SiteModule extends Module
{
    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();

        // Add in our custom rules for the User element validation
        Event::on(
            User::class,
            User::EVENT_DEFINE_RULES,
            static function(DefineRulesEvent $event) {
                foreach(UserRules::define() as $rule) {
                    $event->rules[] = $rule;
                }
            });
    // ...
    }
}

```

We’re call­ing our cus­tom class method <kbd>UserRules::define()</kbd> to return a list of rules we want to add, and then we’re adding them one by one to the <kbd>$event-&gt;rules</kbd>

Here’s what the <kbd>UserRules</kbd> class looks like:

```

<?php
/**
 * Site module for Craft CMS 3.x
 *
 * Custom site module for the devMode.fm website
 *
 * @link https://nystudio107.com
 * @copyright Copyright (c) 2020 nystudio107
 */

namespace modules\sitemodule\rules;

use Craft;

/**
 * @author nystudio107
 * @package SiteModule
 * @since 1.0.0
 */
class UserRules
{
    // Constants
    // =========================================================================

    const USERNAME_MIN_LENGTH = 5;
    const USERNAME_MAX_LENGTH = 15;
    const PASSWORD_MIN_LENGTH = 7;

    // Public Methods
    // =========================================================================

    /**
     * Return an array of Yii2 validator rules to be added to the User element
     * https://www.yiiframework.com/doc/guide/2.0/en/input-validation
     *
     * @return array
     */
    public static function define(): array
    {
        return [
            [
                'username',
                'string',
                'length' => [self::USERNAME_MIN_LENGTH, self::USERNAME_MAX_LENGTH],
                'tooLong' => Craft::t(
                    'site-module',
                    'Your username {max} characters or shorter.',
                    [
                        'min' => self::USERNAME_MIN_LENGTH,
                        'max' => self::USERNAME_MAX_LENGTH
                    ]
                ),
                'tooShort' => Craft::t(
                    'site-module',
                    'Your username must {min} characters or longer.',
                    [
                        'min' => self::USERNAME_MIN_LENGTH,
                        'max' => self::USERNAME_MAX_LENGTH
                    ]
                ),
            ],
            [
                'password',
                'string',
                'min' => self::PASSWORD_MIN_LENGTH,
                'tooShort' => Craft::t(
                    'site-module',
                    'Your password must be at least {min} characters.',
                    ['min' => self::PASSWORD_MIN_LENGTH]
                )
            ],
            [
                'password',
                'match',
                'pattern' => '/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])(?=.{7,})/',
                'message' => Craft::t(
                    'site-module',
                    'Your password must contain at least one of each of the following: A number, a lower-case character, an upper-case character, and a special character'
                )
            ],
        ];
    }
}

```

And then BOOM! Just like that we’ve extend­ed the User Ele­ment mod­el val­i­da­tion rules with our own cus­tom rules.

We’re even giv­ing it the cus­tom <kbd>message</kbd> to dis­play if the <kbd>password</kbd> field does­n’t match, as well as the mes­sage to dis­play if the <kbd>username</kbd> field is <kbd>tooLong</kbd> or <kbd>tooShort</kbd>.

Nice.

![Craft cms model validation rules frontend](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_1200x684_crop_center-center_100_line/craft-cms-model-validation-rules-frontend.png)

As you can see, we even get the dis­play of the val­i­da­tion errors ​“for free” on the fron­tend, with­out hav­ing to do any addi­tion­al work.

<aside>
                                The less code you write, the few­er chances you have to intro­duce bugs
                            </aside>

Bear in mind that while we’re show­ing the User Ele­ment as an exam­ple, we can do this for any mod­el that Craft uses.

For instance, if you want to make the address field in [Craft Com­merce](https://craftcms.com/commerce) required, this is your ticket!

## Mod­els & Behaviors

But what if we want to add some prop­er­ties or meth­ods to an exist­ing mod­el? Well, we can do that, too, via [Yii2 Behav­iors](https://www.yiiframework.com/doc/guide/2.0/en/concept-behaviors).

To extend our User Ele­ment with a cus­tom Behav­ior, we can lis­ten for the <kbd>User</kbd> class trig­ger­ing the <kbd>EVENT_DEFINE_BEHAVIORS</kbd> event:

```

<?php
/**
 * Site module for Craft CMS 3.x
 *
 * Custom site module for the devMode.fm website
 *
 * @link https://nystudio107.com
 * @copyright Copyright (c) 2020 nystudio107
 */

namespace modules\sitemodule;

use modules\sitemodule\behaviors\UserBehavior;

use craft\elements\User;
use craft\events\DefineBehaviorsEvent;

// ...

class SiteModule extends Module
{
    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();

        // Add in our custom behavior for the User element
        Event::on(
            User::class,
            User::EVENT_DEFINE_BEHAVIORS,
            static function(DefineBehaviorsEvent $event) {
                $event->behaviors['userBehavior'] = ['class' => UserBehavior::class];
            });
    // ...
    }
}

```

Here we just add our <kbd>userBehavior</kbd> by set­ting the <kbd>$event-&gt;behaviors['userBehavior']</kbd> to a cus­tom <kbd>UserBehavior</kbd> class we wrote that inher­its from the Yii2 [Behav­ior](https://www.yiiframework.com/doc/api/2.0/yii-base-behavior) class:

```

<?php
/**
 * Site module for Craft CMS 3.x
 *
 * Custom site module for the devMode.fm website
 *
 * @link https://nystudio107.com
 * @copyright Copyright (c) 2020 nystudio107
 */

namespace modules\sitemodule\behaviors;

use craft\elements\User;

use yii\base\Behavior;

/**
 * @author nystudio107
 * @package SiteModule
 * @since 1.0.0
 */
class UserBehavior extends Behavior
{
    // Public Properties
    // =========================================================================

    // Public Methods
    // =========================================================================

    /**
     * @inheritDoc
     */
    public function events()
    {
        return [
            User::EVENT_BEFORE_SAVE => 'beforeSave',
        ];
    }

    /**
     * Save last names in upper-case
     *
     * @param $event
     */
    public function beforeSave($event)
    {
        $this->owner->lastName = mb_strtoupper($this->owner->lastName);
    }

    /**
     * Return a friendly name with a smile
     *
     * @return string
     */
    public function getHappyName()
    {
        $name = $this->owner->getFriendlyName();

        return ':) ' . $name;
    }
}

```

We’re using the <kbd>events()</kbd> method to define the [Com­po­nent Events](https://www.yiiframework.com/doc/guide/2.0/en/concept-behaviors#handling-component-events) we want our behav­ior to lis­ten for.

In our case, we’re lis­ten­ing for the <kbd>EVENT_BEFORE_SAVE</kbd> event, and we’re call­ing a new method we added called <kbd>beforeSave</kbd>.

In the con­text of a behav­ior, <kbd>$this-&gt;owner</kbd> refers to the Mod­el object that our behav­ior is attached to; in our case, that’s a User Element.

So our <kbd>beforeSave()</kbd> method just upper-cas­es the <kbd>User::$lastName</kbd> prop­er­ty before sav­ing it. So every­one’s last name will be upper-case.

Then we’ve added a <kbd>getHappyName()</kbd> method that prepends a smi­ley face to the User Ele­men­t’s name, so in our Twig tem­plates we can now do:

```

{{ currentUser.getHappyName() }}

```

Pret­ty slick, we just pig­gy­backed on the exist­ing Craft User Ele­ment func­tion­al­i­ty with­out hav­ing to do a while lot of work.

In our Behav­ior, if we defined any addi­tion­al prop­er­ties, they’d be added to the User Ele­ment mod­el as well… which opens up a whole world of possibilities.

In addi­tion to writ­ing our own cus­tom behav­iors, we can also lever­age oth­er built-in Behav­iors that Yii2 offers, and add them to our own Mod­els. My per­son­al favorite is the [Attrib­ut­e­Type­cast­Be­hav­ior](https://www.yiiframework.com/doc/api/2.0/yii-behaviors-attributetypecastbehavior).

Check out Zoltan’s arti­cle [Extend­ing entries with Yii behav­iors in Craft 3](https://medium.com/@drifter/extending-entries-with-yii-behaviors-in-craft-3-c06e33bd1f0) for even more on behaviors.

I’d also like to note what Behav­iors are **not**. You can **not** over­ride an exist­ing method with a Behav­ior. You might want over­ride an exist­ing method and replace it with your own code dynam­i­cal­ly… but Behav­iors can­not do that.

Behav­iors can only _extend_, not _replace_.

## Wrap­ping Up

In addi­tion to ​“use the plat­form”, when­ev­er we’re adding code, I think we should add as lit­tle as possible.

![Code with precision like a surgeon](https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/blog/_992x558_crop_center-center_82_line/code-with-precision-like-a-surgeon.jpg)

Yes, there are oth­er ways to add the func­tion­al­i­ty we’ve shown in this arti­cle, but the meth­ods dis­cussed here are sim­ple, and require less code.

<aside>
                                Sim­ple is Good
                            </aside>

When adding code to an exist­ing project or frame­work, you typ­i­cal­ly want to go in like a sur­geon, chang­ing as lit­tle as pos­si­ble to achieve the desired effect.

Hap­py coding!

## Further Reading

_If you want to be notified about new articles, follow [nystudio107](https://twitter.com/nystudio107) on Twitter._

<small>Copyright ©2020 nystudio107. Designed by nystudio107</small>