Array Validation in Laravel 4

Reading time ~5 minutes

Array Validation in Laravel 4

At work we are still using Laravel 4 for a few projects. From time to time we need to validate an array of form fields using the Laravel Validator, however before Laravel 5.2 this was not a trivial task. Let’s break down the problem a little bit.

Suppose that you want to validate a form that uses dynamic fields, added at run time by the user. Take the example of a product in an ecommerce website. We need to insert some information on a product, let’s say the name, the price and a list of barcodes (EAN, ISBN and so on). Each product can have zero or more bar codes, added dynamically to the form using JavaScript. We want to be able to validate all of them.

Laravel 4 doesn’t have Form requests therefore we need to create a validator using the Validator facade.

Creating the controller

We have a ProductController which takes the POST request and validates it before storing the product in the database. The POST request can has the following structure:

<?php
[
	'name' => 'My awesome product',
	'price' => 20.00,
	'codes' => [
		[
			'label' => 'EAN',
			'type' => 'ean-13',
			'value' => '2412345678901',
		],
		[	
			'label' => 'ISBN',
			'type' => 'isbn',
			'value' => '9788843025343'
		]
	]
]

Notice: Probably a product doesn’t need to have both an EAN code and an ISBN, but bear with me, it’s just an example.

In this request name is a string, price is a float and codes is an array which contains a label, the type of the code (chosen using a <select> input) and the value of the code.

To keep our controller light, we can use a different method (or class) to validate the request. Therefore our store method can be like the following:

<?php
public function store()
{
	// Remember Input facade, uh?
	$input = Input::except('_token');
	
	$validator = $this->makeValidator($input);
	
	if($validator->fails())
	{
		return Redirect::back()->withInput()->with('errors', $validator->getMessages());
	}
	
	// persist logic [...]
}

Adding custom rules

Now let’s take a look to the makeValidator function. It takes the input array, creates a validator for it and dynamically adds the rules to validate the codes. For example we want to add the required rule to each one of the code fields, but we want also to check that the value passes the validation for the barcode defined by type.

<?php
protected function makeValidator(array $input)
{
	$messages = [];

	// the codes input is not required, therefore we use the 
	// array_get helper to provide an empty array as fallback
	foreach(array_get($input, 'codes', []) as $index => $code)
	{
		// first, add the translation messages
		$messages['codes.' . $index . '.label.required'] = trans('validation.required', array('attribute' => trans('fields.label') . ' ' .$index));
		$messages['codes.' . $index . '.type.required'] = trans('validation.required', array('attribute' => trans('fields.type') . ' ' .$index));

		// add the messages for each one of our codes
		foreach(Config::get('ecommerce.codes') as $key => $product_code)
		{
			$messages['codes.' . $index . '.value.' . str_replace('-', '', $key)] = trans('validation.product_code.' . $key, array('attribute' => $product_code['label']));
		}
	}

	// make the validator on the other fields
	$validator = Validator::make($input, [
		'name' => 'required',
		'price' => 'required|numeric'
	], $messages);

	// merge the rules for each product code
	foreach(array_get($input, 'codes', []) as $index => $code)
	{
		$validator->mergeRules('codes.' . $key . '.label', ['required']);
		$validator->mergeRules('codes.' . $key . '.type',  ['required']);
		$validator->mergeRules('codes.' . $key . '.value', [$code['type']]);
	}

	return $validator;
}

As you can see there’s a lot going on here!

First we need to add the translations for each validation rule and for each code. This allows us to avoid ugly validation messages like “codes.0.label is required” and instead use something more readable.

Then we need to add the messages for each code type. From the configuration file we retrieve the array of our available codes:

<?php
'product_codes' => [
	'ean-8' => 'EAN-8',
	'ean-13' => 'EAN-13',
	'upc-a' => 'UPC-A',
	'isbn-13' => 'ISBN-13'
]

Note that we are removing the dash for each key in the array. This is requried because Laravel validation rules are defined via Camel Case functions:

<?php
// class that extends Illuminate\Validation\Validator

public function validateEan8($attribute, $barcode, $parameters)
{
	if ( ! preg_match("/^[0-9]{8}$/", $barcode))
	{
		return false;
	}

	// most barcodes can be validated using a single function
        return $this->validateBarCode($attribute, $barcode, $parameters);
}

Finally, after creating the validator, we merge the rules for the codes, adding the required rule and the type validation using directly the value passed in input. Laravel is smart enough to translate a rule like ean-8 into ean8.

Basic ProcessWire website workflow - Part Four

Part four of a basic workflow/tutorial for building simple websites with the ProcessWire CMF/CMS. Continue reading