Using Auto_Modeler to write quicker code

Difficulty: Medium

Hello, I’m Jeremy, a developer for the Kohana framework.

I had a problem. All my models for database access had extremely similar code in them for inserting, updating and deleting rows. I knew about ORM, but felt it was too large and clunky for my needs. I didn’t need relationships or auto-joining tables or any of that junk, as I prefer to do that myself. So I developed this Auto_Modeler library class to drastically reduce my TLOC and to get some DRY going in my applications, and thought I’d share it and provide some examples on how you can reduce your codebase.

First off, here is the meat of the class:

class Auto_Modeler extends Model
{
	protected $data = array();
 
	public function __construct($id = NULL)
	{
		parent::__construct();
 
		if ($id != NULL)
		{
			// try and get a row with this ID
			$data = $this->db->getwhere($this->table_name, array('id' => $id))->result(FALSE);
 
			// try and assign the data
			if (count($data) == 1 AND $data = $data->current())
			{
				foreach ($data as $key => $value)
					$this->data[$key] = $value;
			}
		}
	}
 
	public function __destruct()
	{
		return TRUE;
	}
 
	public function __get($key)
	{
		if (isset($this->data[$key]))
			return $this->data[$key];
	}
 
	public function __set($key, $value)
	{
		if (empty($this->data[$key]) OR $this->data[$key] !== $value)
			$this->data[$key] = $value;
	}
 
	public static function factory($model = FALSE, $id = FALSE)
	{
		$model = empty($model) ? __CLASS__ : ucfirst($model).'_Model';
		return new $model($id);
	}
 
	public function set_fields($data)
	{
		foreach ($data as $key => $value)
			if (isset($this->data[$key]))
				$this->data[$key] = $value;
	}
 
	public function save()
	{
		if ($this->data['id']) // Do an update
			return count($this->db->update($this->table_name, $this->data, array('id' => $this->data['id'])));
		else // Do an insert
			return $this->db->insert($this->table_name, $this->data)->insert_id();
	}
 
	public function delete()
	{
		if ($this->data['id'])
		{
			$this->db->delete($this->table_name, array('id' => $this->data['id']));
			return $this->__destruct();
		}
	}
 
	public function fetch_all($orderby = 'id', $direction = 'ASC')
	{
		return $this->db->orderby($orderby, $direction)->get($this->table_name)->result(TRUE, __CLASS__);
	}
 
	public function select_list($key, $display)
	{
		$rows = array();
 
		foreach ($this->fetch_all() as $row)
			$rows[$row->$key] = $row->$display;
 
		return $rows;
	}
}

Some of you familiar with ORM will notice this is pretty similar to ORM. This class needs to go in your application/libraries directory. The first thing you need to do is make a model in your application/models directory. We will create a simple blog post model as an example:

class Post_Model extends Auto_Modeler
{
	// Database table name
	protected $table_name = 'posts';
 
	// Database fields and default values
	protected $data = array('id' => '',
	                        'title' => '',
	                        'content' => '',
	                        'date' => '',
	                        'active' => '');
}

That’s the whole model. You get to define your table name and field names in the model for easy reference (that’s not just a “nice” feature, you need to do this for it to function properly). Doing this also prevents bad columns from being set when you insert or update a row, due to the behavior in those Auto_Model methods.

Now that you have your model defined, you can start to use it. To do this, we will create a simple blog post controller and add an “add” and “edit” method to modify our data.

class Post_Controller extends Controller {
 
	public function index()
	{
		$post_model = new Post_Model();
		$this->template->title = 'Blog Posts';
 
		$this->template->content = new View('publication/index');
		$this->template->content->posts = $post_model->fetch_all();
	}
 
	public function add()
	{
		$this->template->title = 'Add Post';
 
		$form = new Forge(NULL, 'Add Post');
		$form->input('title')->rules('required');
		$form->input('content')->label(TRUE)->rules('required');
		$form->dateselect('date')->label(TRUE)->rules('required')->set_parts(array('month', 'day', 'year'));
		$form->checkbox('active')->label(TRUE)->value(TRUE);
		$form->submit('Add Post');
 
		if ($form->validate() AND $post = $form->as_array())
		{
			$this->upload($post);
			url::redirect('proof_sheet/index');
		}
		else
		{
			$this->template->content = $form;
		}
	}
 
	public function edit($id = NULL)
	{
		$this->template->title = 'Edit Blog Post '.$id;
 
		$post = new Post_Model($id);
		$form = new Forge(NULL, 'Edit Post');
		$form->hidden('id')->value($post->id);
		$form->input('title')->value($post->title)->rules('required');
		$form->input('content')->label(TRUE)->rules('required')->value($post->content);
		$form->dateselect('date')->label(TRUE)->rules('required')->set_parts(array('month', 'day', 'year'))->value($post->date);
		$form->checkbox('active')->label(TRUE)->value(TRUE)->checked($post->current);
		$form->submit('Update Post');
 
		if ($form->validate() AND $post_data = $form->as_array())
		{
			$this->do_post($post_data);
			url::redirect('post/index');
		}
		else
		{
			$this->template->content = $form;
		}
	}
 
	private function do_post($post)
	{
		// Insert/update the new item magically
		$post= new Post_Model();
		$post->set_fields($post);
		$post->save();
	}

By using this method, I was able to reduce all my model code by at least 75%, and my controller code by about 10-15%.

That’s it! I hope this short tutorial can help you reduce your code and make your application development time shrink dramatically. If you need special model method to do things other than what’s in Auto_Modeler, just add it to your model. Please leave comments with any suggestions or questions =)


11 Responses to “Using Auto_Modeler to write quicker code”

  1. Trini Says:

    Hi was wondering if the database metadata function list_fields() could have been used to list the tables;


    public function __construct($id = NULL)
    {
    parent::__construct();

    $this->data = $this->db->list_fields($this->table);

    if ($id != NULL)
    {
    // try and get a row with this ID
    $data = $this->db->getwhere($this->table_name, array('id' => $id))->result(FALSE);

    // try and assign the data
    if (count($data) == 1 AND $data = $data->current())
    {
    foreach ($data as $key => $value)
    $this->data[$key] = $value;
    }
    }
    }

    Thanks for the Lib!!!!

  2. Trini Says:

    Just realized that it not just field names but values / Database fields and default values hmm…

  3. zombor Says:

    Yes, you can specify default values in your model as well =)

    The only things that don’t work are things like ‘date’ => time(), since php doesn’t allow you to do that. You can accomplish this in your constructor though. Useful for things like blog posts where you want the post to have the current time.

  4. Trini Says:

    Actually what I wanted to know was if there was an easier to set the table names other than manually like, using the metadata list fields function above does not work, is there any other way!!!

  5. Easylancer Says:

    I was just wondering why is it in the add function it says $this->upload($post); but where is upload in the controller, is that an error or if not can you explain what it does?

  6. zombor Says:

    Ah, that should be do_post(), sorry, a typo! In my controller I named it upload(), but I changed the name for the example.

  7. bennythemink Says:

    Hi zombor,

    one quick question, what if i just wished to update one field and not all the fields in my table?

    it looks as if your save function updates all the fields (i could be mistaken), so if i hadn’t assigned values to all the keys in the data array it would assign the columns in the database table with an empty string.

    am i mistaken?

  8. zombor Says:

    When you do new My_Model($id), it assigns all the rows the value from the database, so if you update that model, it will retain what was in the row to begin with.

    The default values are just there for creating new rows.

  9. Henri Says:

    There are advantages (not being restricted by table-naming conventions is one of them) to using your auto_modeler but the useful __call() function to map find_by_* calls is missing.
    I’ve added the method but limited it to find_by_* queries.

  10. mkjems Says:

    I believe function do_post() contains an error. Var $post is used for two different things. This is correct imho:

    private function do_post($POST_DATA)
    {
    // Insert/update the new item magically
    $post= new Post_Model();
    $post->set_fields($POST_DATA);
    $post->save();
    }

  11. Rony Says:

    Hi
    I have started learning Kohana and getting frustrated. Its not at all clear from the doc from where to start implementing an application.I have been reading doc and trying to build an application but nothing working according to exceptions. Step by step tutorial is required.It would be nicer if there is any complete sample application where everything is organized according to MVC pattern.So, a beginner can download and play with that to develop his/her concept.
    Please i need help madly.

    Thanks in advance

Leave a Comment