I’ve released version 1.5 of my Auto_Modeler Kohana module today. This is a pretty big release. It provides built in support for in-model data validation. Here’s a list of all the changes:
Auto_Modeler:
- Added support for in-model validation
- Added a $rules member array variable (an array of rule arrays)
- Added a $callbacks member array variable
- Added a ‘fetch_some’ method to search for specific database rows with a where() array
- Added comments =)
Auto_Modeler_ORM:
- Fixed some bugs with many to many relationship setting
- Added one to many support in find_related()
- Added one to many support in find_parent()
- Added one to many support in delete()
- Added has_attribute()
The big change is the built in validation. Here’s an example of how I use it.
First, my model:
class Asset_Model extends Auto_Modeler_ORM {
// Database table name
protected $table_name = 'assets';
// Database fields and default values
protected $data = array('id' => '',
'title' => '');
protected $has_many = array('files', 'countries');
protected $belongs_to = array('categories');
protected $rules = array('title' => array('required'),
'description' => array('required'));
// Overloading delete() to delete the physical file as well.
public function delete()
{
// Delete all the files
$files = Auto_Modeler_ORM::factory('file')->fetch_some(array('asset_id' => $this->data['id']));
echo Kohana::debug($files);
foreach ($files as $file)
{
$dest_dir = APPPATH.'views/assets/'.substr($file->filename, 0, 1).'/';
$uploadfile = str_replace(' ', '_', $dest_dir.$file->filename);
unlink($uploadfile);
}
parent::delete();
}
protected function check_country(Validation &$validation)
{
$validation->add_rules('country', 'required');
}
protected function check_category(Validation &$validation)
{
$validation->add_rules('category', 'required');
}
protected function check_file(Validation &$validation)
{
$validation->add_callbacks('file', array($this, 'validate_file_upload'));
}
public function validate_file_upload(Validation &$validation, $input)
{
if (isset($_FILES) AND $_FILES[$input]['error'])
$validation->add_error($input, 'no_file');
else if (count($this->db->getwhere('files', array('filename' => $_FILES[$input]['name']))))
$validation->add_error($input, 'duplicate_filename');
}
}
Now here’s the insert() method of my controller:
public function insert()
{
if ( ! ($this->auth->logged_in('admin') OR $this->auth->logged_in('publisher')))
Event::run('system.404');
$asset = new Asset_Model();
if ( ! $_POST)
{
$region = new Region_Model();
$this->template->body = new View('asset/insert');
$this->template->body->regions = $region->fetch_all();
$this->template->body->errors = '';
}
else
{
$post = $this->input->post();
$asset->title = $post['title'];
$asset->description = $post['description'];
try
{
// Save the asset
$asset->save(
array('country' => $this->input->post('country', array()), 'category' => $this->input->post('category', array())), // Additional data to validate with the form
array('check_country', 'check_category', 'check_file') // Model callbacks to run
);
// Then upload the file
$dest_dir = APPPATH.'views/assets/'.substr($_FILES['file']['name'], 0, 1).'/';
$uploadfile = str_replace(' ', '_', $dest_dir.$_FILES['file']['name']);
! is_dir($dest_dir) AND mkdir($dest_dir, 0777, TRUE);
move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile);
foreach ($post['country'] as $country)
$asset->countries = $country;
$asset->category_id = $post['category'];
$file = new File_Model();
$file->filename = str_replace(' ', '_', $_FILES['file']['name']);
$file->asset_id = $asset->id;
$file->timestamp = time();
$file->save();
url::redirect('');
}
catch (Kohana_User_Exception $e)
{
$region = new Region_Model();
$this->template->body = new View('asset/insert');
$this->template->body->regions = $region->fetch_all();
$this->template->body->errors = $e;
$this->template->body->set($post);
}
}
}
The important part of this block of code is in the try/catch block. The model does all the validation internally. So when it save()s, it will try and validate it’s internal data. If it fails, it will throw an exception that includes the error messages (this comes from views/form_errors.php).
The second new part of this code is the extra parameters in the save() method. This form has external requirements seperate from the assets table that are required (country and category). A file upload is also required.
Since this data has nothing inherintly to do with the asset model or database table, we pass them as callbacks. The model has callback methods to check the external data. So we pass the extra data in with the first parameter to save(), and the second parameter is the callbacks to run in addition to the built in validation.
If the save() succeeds, we continue our insert procedure, otherwise we skip to the catch{} block and display the form again with the invalida data and errors.