Integrate ATK UI PHP Form with Valitron

Romans Malinovskis
3 min readMar 8, 2018

--

If your PHP application needs a out-of-the-box slick looking UI, ATK UI is a free and open-source framework to skip all that meddling with CSS and HTML and get straight to the point.

One of the most “used” components in ATK is “Form”, which is a full-featured AJAX HTML generator:

$f = $app->add('Form');
$f->setModel(
$app->user->ref('App'),
['name', 'short_name']
);

While there is support for marking required fields, ATK UI does not have an advanced value validation rules. As per documentation, the suggested way to imlpement it is through a 3rd party validator. For my current project, I needed to pick a powerful validator, so I decided to try Valitron due to it’s concise syntax and flexible nature.

Integration Process

ATK UI does not deal with data itself, instead it relies on ATK Data, which is responsible for collecting entered data from the form and stuffing it into your database of choice. To allow my “users” create and switch “apps” inside my project, I have declared “App” model class:

class App extends \atk4\data\Model {
public $table='app;
function init() {
parent::init();
$this->hasOne('user_id', new User()); $this->addField('name');
$this->addField('short_name', [
'required'=>true,
'ui'=>[
'hint'=>'This name may only contain alphanumeric characters and it should be valid namespace identifier, e.g. "MyApp"'
]
]);
}
function validate($intent = null)
{
if ($this['short_name'] != 'abc') {
return ['short_name' => 'should be abc'];
}
}
}

ATK executes method validate() and displays error to the user correctly. Next, I need to invoke Valitron from model validation.

Global Model Validation

Quite often in my app, I would create an intermediate class for a model — MyModel which gives me flexibility to define various global rules, conditions and properties. It is also useful for enabling Valitron rules globally:

class MyModel extends \atk4\data\Model {
}
class App extends MyModel {
...
}

Next, I need to move validate() method into my global class and write the integration. Here is my final version of the validate() method:

    function validate($intent = null) {
$v = new \Valitron\Validator($this->get());
foreach($this->elements as $key=>$field) {
if (!$field instanceof \MyModelField) {
continue;
}
$field->validate($v);
}
if ($v->validate()===true) {
return parent::validate($intent);
}
$errors = parent::validate($intent); foreach($v->errors() as $key=>$e){
if (!isset($errors[$key])) {
$errors[$key] = array_pop($e);
}
}
return $errors;
}

ATK Data uses Rich Field objects, that are declared once and retained across multiple operations. Field objects contain many useful values such as data type, caption, list of possible values, persistence rules, etc. I need to add a new property called validate for storing validation rules and method validate() for applying those rules on the Validator. It’s easy to specify a custom Field class by adding this method into MyModel:

    function init()
{
parent::init();
$this->_default_seed_addField = ['\MyModelField'];
}

Then all that remains is to create MyModelField class:

class ModelField extends \atk4\data\Field_SQL
{
/**
* Describes arbitrary validation rules
*/
public $validate = [];
function validate($v) {
if ($this->validate) {
if (is_array($this->validate)) {
foreach($this->validate as $k=>$v) {
$v->rule($k, $this->short_name, $v);
}
} else {
$v->rule($this->validate, $this->short_name);
}
}
}
}

This approach supports two handy ways to define rules:

$user->addField('email', ['validate'=>'email']);// or $user->addField('age', ['validate'=>['min'=>14]]);

Certainly it’s possible to further extend this, but this integration provided a simple and elegant solution for my project and I hope you could find it helpful too.

Thank you for reading!

--

--