The CodeIgniter Decorator Library
I’ve been thinking a lot lately about how I can clean up my CodeIgniter code, specifically my controllers and views. CodeIgniter is dead simple to work with, but on larger, more complex projects I find that my controllers get a little long and my views get messy. Here are my main issues and how I’ve decided to address them.
Controllers
The main thing I don’t like doing in my controllers is all of the data retrieval and preparation to pass data to the view. Things like this:
$product = $this->product_model->find($product_id);
$company = $this->company_model->find($product->company_id);
And this:
$view_data = array(
'product' => $product,
'company' => $company
);
$this->load->view('products/view', '$view_data');
They just don’t feel right to have in the controller because they get in the way of the core function of a controller.
The controller receives user input and initiates a response by making calls on model objects. A controller accepts input from the user and instructs the model and a view port to perform actions based on that input.
Views
I’ve always disliked putting any sort of logic in my views. In fact, anything beyond looping through an object or array and echoing its values makes me feel dirty. Things like setting a default value if an attribute is empty:
<?php echo empty($product->price) ? $product->price : 'N/A'; ?>
Or updating the layout of a page based on the type of user that is viewing it:
<?php if ($user->company_id == $product->company_id): ?>
<div class="product-admin-links">
<?php echo anchor('products/edit/' . $product->id, 'Edit Product Info'); ?>
<?php echo anchor('products/inventory/' . $product->id, 'Edit Product Inventory'); ?>
</div>
<?php else: ?>
<?php echo anchor('cart/add/' . $product->id, 'Add to Cart'); ?>
<?php endif; ?>
Not only does this clutter up my views, but it also has to be repeated anywhere that I want to display information for a resource, in this case a product. I would much prefer to have this data prepared outside of the view and the controller, but it doesn’t really belong in the model either.
My Solution: Decorators
After seeing how a couple other frameworks handle this (FuelPHP and Rails) and thinking through what my ideal solution would be, I’ve started writing a decorator library and created a spark out of it.
The library allows you to put all of the data retrieval and preparation into a decorator, which is stored in application/decorators
. A decorator must extend the CI_Deocrator class which gives it access to the CodeIgniter super global ($this
) so you can load models and everything else like you are working in a controller or model. Going back to our earlier example, if you removed the data retrieval from the controller and preparation logic form your views, you would end up with something likes this:
File: application/controllers/products.php
public function view($product_id)
{
$this->load->view('products/view', $this->decorator->decorate('product', 'view', $product_id));
}
As opposed to this:
public function view($product_id)
{
$this->load->model('product_model', 'company_model');
$product = $this->product_model->find($product_id);
$company = $this->company_model->find($product->id);
$view_data = array(
'product' => $product,
'company' => $company
);
$this->load->view('products/view', $view_data);
}
Tip: By default, the
decorate()
method will look for a class/method that matches the controller class/method, otherwise you can specify them as parameters (i.e.decorate('products', 'view')
). It will also accept a third parameter as an array of parameters to pass to the decorator.
Going back at our views, we can now do this:
File: application/views/products/view.php
<?php echo $product->price; ?>
<?php echo $product_links; ?>
As opposed to this:
<?php echo $product->price ? $product->price : 'N/A'; ?>
<?php if ($user->company_id == $product->company_id): ?>
<div class="product-admin-links">
<?php echo anchor('products/edit/' . $product->id, 'Edit Product Info'); ?>
<?php echo anchor('products/inventory/' . $product->id, 'Edit Product Inventory'); ?>
</div>
<?php else: ?>
<?php echo anchor('cart/add/' . $product->id, 'Add to Cart'); ?>
<?php endif; ?>
And our decorator looks like this:
File: application/decorators/products_decorator.php
class Product_decorator extends CI_Decorator {
public function view($product_id)
{
$this->load->model('product_model', 'company_model');
$this->view_data->company = $this->company_model->find($product->id);
$product = $this->product_model->find($product_id);
if (empty($product->price)) $product->price = 'N/A'
$this->view_data->product = $product;
if ($this->session->userdata('company_id') == $product->company_id)
{
$product_links = '<div class="product-admin-links">';
$product_links .= anchor('products/edit/' . $product->id, 'Edit Product Info');
$product_links .= anchor('products/inventory/' . $product->id, 'Edit Product Inventory');
$product_links .= '</div>';
}
else
{
$product_links = anchor('cart/add/' . $product->id, 'Add to Cart');
}
$this->view_data->product_links = $product_links;
}
}
Tip: The class variable,
$view_data
, that I’m referencing here is a variable that is automatically returned by thedecorate()
method. Using this, you don’t have to worry about explicitly returning the data, but if you do return something,decorate()
will use that instead of the$view_data
class variable.
Help Me Out
It’s definitely not perfect, and I certainly make no claims of it being bug-free, but it’s a start and I hope to keep improving it based on the feedback I receive (Hint, hint!). I’ve been using this on a couple of my larger projects the last month or so and it has really helped me clean up my controllers and views. Hopefully others find it useful too.
Source: