I ran into a situation recently where I needed to perform some custom validation when saving Statamic entries in the control panel. Essentially, we have a site that has lots of articles on it, and it has become harder and harder for our editors to know if they have already used a specific article title in the past.

As you may know, Statamic is based (at the time of writing) on the Laravel PHP development platform. In this tutorial I hope to show you how to easily extend Statamic field validation with your own custom validators as needed by tying into the functionality for custom validators offered by Laravel.

The first step in this process was to create a new addon for Statamic with a custom service provider.

php please make:provider YourCustomServiceProviderName

This will create a new file in the statamic folder structure with a name something like this:

./site/addons/YourCustomServiceProviderName/YourCustomServiceProviderNameServiceProvider.php

To the ‘boot’ method of that service provider you will need to add your new validation extension. The Laravel codebase documents it in some detail, but the basic structure could be something like this:

To the namespace includes in the “header” area of the class: (maybe someone can correct me on the proper term for this area)

use Illuminate\Support\Facades\Validator;

Inside the boot method:

Validator::extend('foo', 'FooValidator@validate');

Replace ‘foo’ with a validator name that makes sense to you. Also, be aware that if you have your class namespaced (as you should) then you also need to include the namespace in this class name. For example, I used the following in my own validator extension

Validator::extend('uniqueTitle', UniqueTitleValidator::class . '@validate', 'This title has already been used by another article');

In my example above, I reference the class directly, outside of the ‘@validate’ string in order to have it fully resolve to the correct class name. I also found a little trick with Validator::extend which lets you pass through a fallback error message as the third parameter. I realize it’s not pretty, but it worked in my situation.

I then created another class inside the same addon folder called “UniqueTitleValidator” and put a public method in it called “validate”. In order to work, this method simply needs to return a boolean true/false value regarding whether the field in question is valid. You can do whatever testing you need, and then simply return true or false. In my case, it looked something like this:

public function validate($attribute, $value, $parameters, $validator) {
	$count = $this->usingValidator($validator)
	              ->countEntriesWith($value, 'title');

	return $count == 0;
}

My code above uses a few additional functions to essentially count the number of entries that have a title matching the given value. I wrapped those functions in another class since I use this sort of functionality a few different validators, but here’s the basic structure:

/** @var  \Illuminate\Validation\Validator */
protected $validator;

protected function usingValidator( $validator ) {
	$this->validator = $validator;
	return $this;
}

protected function countEntriesWith( $value, $location ) {
	$entries = $this->getEntries()->filter(function ($entry) use ($value, $location) {
		$testValue = $entry->get($location);
		return $testValue == $value;
	});

	$count = $entries->count();

	if($this->validator->getData()['new'] === false)
		$count = $count-1;

	return $count;
}

protected function getEntries()
{
	$collectionName = $this->validator->getData()['extra']['collection'];
	return \Statamic\API\Entry::whereCollection($collectionName);
}

The final step is to add the validator to the fieldset yaml file in statamic. In this specific site, we use it on several different types of content by adding it as a validator in the fieldset files. For example:

  title:
    type: title
    display: Title
    localizable: true
    validate: unique-title

Note that the validator name ‘unique-title’ is written with each word separated by a dash (aka lisp-case or kebab-case) while the validator name that is described by Validator::extend(‘uniqueTitle’ is camelCase.

The end result of this is that now when the editors on our team write an article that has the same name as another one in the same collection, it will kick up a validation warning when they try to save, saying that the title has already been used.

I trust this has been helpful to someone. Please feel free to leave comments or questions in the area below.

Filed in: PHP Programming

Feedback