You are on page 1of 91

!

"#$% '(#)%*+,
-./ 012/3453-/' .6527118
9+*(#% : ; <=+ 5%%>? '()@A
!"# %&&'( )*+,-
Copyrght 2012 |ame Rumbeow
A rghts reserved. No part of ths book may be reproduced wthout the pror
wrtten permsson of the pubsher, except for persona use and the case of
bref quotatons embedded n artces or revews.
Every effort has been made to ensure the accuracy of the nformaton
presented. However, the nformaton contaned n ths book s sod wthout
warranty, ether express or mped. Nether the authors, Efend Pubshng
nor ts deaers or dstrbutors w be hed abe for any damages caused or
aeged to be caused drecty or ndrecty by ths book.
Efend Pubshng has endeavored to provde trademark nformaton about
a companes and products mentoned n ths book, however we cannot
guarantee that ths nformaton s 100% accurate.
CodeIgnter s a regstered trademark of EsLab, Inc. CodeIgnter ogo
copyrght 2007 - 2012 EsLab, Inc., used wth permsson.
Frst Pubshed: 2012
Ths Edton Pubshed: 2012
Pubshed by Efendi Publishing
ISBN: 978-0-9571791-1-0
For Mum, Dad and the Alexes. l couldn't ask for better people in my life.
./+0& 12 3#45&45(
Acknowedgments.............................................................................
An Introducton to The CodeIgnter Handbook .................................
An Introducton to Voume One........................................................
Who shoud read ths book? .............................................................v
Part 1 - Modes
Fat Mode, Sknny Controer........................................... 3
Standard Mode Conventons.......................................... 4
Observers ..................................................................... 16
Scopng......................................................................... 20
Vadaton ..................................................................... 26
MY_Mode...................................................................... 32
Part 2 - Vews
Presenters .................................................................... 34
Partas.......................................................................... 42
Fragment Cachng ........................................................ 46
Part 3 - Controers
Autooadng Vews ........................................................ 54
Layouts......................................................................... 59
Autooadng Modes ...................................................... 65
Fters............................................................................ 68
Part 4 - REST
HTTP: The Forgotten Protoco ....................................... 73
CodeIgnter-frendy sem-RESTfu Routng................... 77
Summary ....................................................................................... 81
6784#90&':;&45(
A huge thank you to my fantastc edtors, Hope Doty and Dean Howe.
Wthout your treess efforts the book woud probaby be fu to the brm wth
overy-pretentous, bady speed ocutons and horrendousy broken code
sampes. Many thanks go to the ove of my fe-the ever-gorgeous and
wonderfuy taented Laura Parrott-for her fantastc ustratons that provde
a much needed vsua break between the reams of otherwse pateaung
textua boredom. Thanks to Chare + Matt over at Prnt GDS for beng so
understandng and hepng me destroy the panet, one dead tree at a tme.
Cheers to the EsLab team for CodeIgnter and aowng us to use ther
ogo. Thanks to the CodeIgnter Crew: Ph Sturgeon, Eot Haughn, Tom
Myer, |eremy Gmbe, Erskne, Coy, Fve Smpe Steps, A Book Apart, Smon
and Angea Campbe and the EE ot.
Thanks to me Mum and Dad for keepng me suffcenty sober and
adequatey drunk, respectvey. Lots of ove to my Granny who passed away
before ths book coud be pubshed. Love to my frends: Aex, Aex, Izzy,
Cauda and everyone ese, and Chrs, Bea, Pat, the Lambs + Rot, Andy
and |o n Bath for ookng after me.
I woud aso ke to thank you, dear reader, for provdng me wth enough
dsposabe ncome to buy an Aston Martn. Naturay, Im |okng. Nobody
became rch from wrtng a tech book. |ust ask Tom Myer.
But a guy can dream, rght?

64 <45=#'*75>#4 .# ."& 3#'&<:4>5&= ?/4'+##8


Ive been programmng wth CodeIgnter for sx years. When I frst
downoaded the source code and dug nto what woud soon become the
heart and sou of my professona career, I had an epphany. Suddeny, I had
found a framework that made sense from the word go. It took me a of
ffteen seconds to downoad the source and extract t nto my 5ites
drectory. From that pont onward, my fe as a deveoper had dramatcay
changed.
What reay made CodeIgnter speca was the sense of exctement that t
nvoked. If youve ever had the msfortune of meetng me, you know that
the exctement s st there. Im so passonate about CodeIgnter; ts vsbe
on my face and n my body anguage. |ust as t s vsbe n the faces of
every other deveoper Ive ever met who has experenced the same
epphany.
I assume youre readng The CodeIgnter Handbook because youre
passonate about CodeIgnter. Youre excted about the future of the
framework. But more mportant, youre excted about the present. About
the sheer promse that CodeIgnter can brng to your appcatons. Despte
a the hype around other frameworks and anguages, CodeIgnter st
remans the greatest PHP framework for any deveoper deveopng rea-
word appcatons. CodeIgnter provdes pragmatsm wthout over-
abstracton, speed wthout smpfcaton, power wthout buk. Its the
perfect mx of programmng happness and scaabty. Its ecectc.

64 <45=#'*75>#4 .# @#0*;& 14&


Ruby on Ras-and ts programmng anguage Ruby-have taken the web
deveopment word by storm. In |ust eght years theyve managed to
accumuate a huge communty, powerng massve stes a across the web
ncudng Twtter, Shopfy and Basecamp. They are both wdey prased for
promotng eegant syntax, rapd deveopment speed and the controversa
Conventon Over Confguraton pattern.
Whats the secret to ther success? Why cant us CodeIgnter deveopers
have a sce of the Ras pe?
In ths short, concse, succnct book I demonstrate that, not ony can we
can eat the pe, but ts tasty too. We take a ook at the basc concepts
that make Ras deveopers happy and how you can mpement them n
CodeIgnter n a CodeIgnter-frendy way. We expore the concept of
Conventon Over Confguraton and what smpe steps can be taken to make
wrtng reabe appcatons n CodeIgnter really, really easy. We ook at
tdyng up your code by studyng desgn prncpes ke Dont Repeat
Yoursef (DRY), and usng RESTfu stye controers to provde a consstent
URL pattern across the appcaton.
It mght be opnonated, but that doesnt mean the technques we earn
cant be fexbe. I sncerey hope that you fnd the concepts and deas n
ths book make programmng n CodeIgnter fun. Reay, reay fun.

!"# ("#*0' =&/' 5">( +##8-


If youre a novce to CodeIgnter, ths book s for you. If you are an
experenced CodeIgnter deveoper, ths book s for you. As ong as you
understand the core concepts of Mode-Vew-Controer and can code up a
smpe app, youre bound to get somethng vauabe from ths text. I
assume that you understand the basc dfferences between brares,
hepers, modes and vews and how to use them wthn CodeIgnter.
This book was written with Codelgniter version 2.1.0. Any code
exampes here are tested to work wth ths CodeIgnter verson, not any
pror or future versons. Wth the rapd pace of deveopment, I can make no
guarantee that code w work on a future verson. If youd ke to foow
aong, pease use 2.1.0.
v
.n whch we take a ook at the M n MVC, earn whats best to go n the mode and how
powerfu the mode ayer can, and shoud, be. We examne an effectve way to mprove your
modes across the board, as we as ook at some Ras-nspred patterns to make codng your
modes more effcent. We aso dscuss some commony made errors n the CodeIgnter
word and ook at how best to fx them and mpement better soutons.
The Mode-Vew-Controer (MVC) desgn pattern teaches us a seres of rues
for budng more robusty structured appcatons. The most mportant of
these s that modes store a the code reated to processng data. Ths tes
our modes that they are n contro of changng data or state. CodeIgnter's
mpementaton of MVC s rather oose, nsofar as t aows deveopers to
bypass the mode ayer and drecty nteract wth the database n the
2
controer ayer. Its mpementaton (and the CodeIgnter standard brary)
aso promote some bad practces, such as pacng vadaton ogc n the
controer. In reaty, the 'M' bt n MVC means that any code that stores data
to the database, vadates data, sends out emas, connects to APIs, or
cacuates statstcs needs to be put n the mode ayer.
Ths whoe dea revoves around two concepts from the Ras word: fat
mode, sknny controer and conventon over confguraton.
A/5 B#'&0C D8>44, 3#45=#00&=
Ths dea tes us that we need to wrte our appcatons around the ogc n
our modes. Here comes the frst sweepng statement of, I'm sure, many to
come: any code that you can't justify putting anywhere else should go in the
model.
Our modes shoud be fat. They shoud be the |ucy chcken of the tradtona
Brtsh Sunday unch that s our appcaton; stuffed to the brm wth the
appcaton-specfc stuffng, tasty, meaty and fat. Our controer s the
gravy. It's ghtweght, t's thn, t's succnct. Don't get me wrong, t's totay
necessary; the gravy bnds the whoe mea together and provdes common
ground for the meat and the trmmngs to communcate on the pate.
However, t shoudn't be the part of the mea you focus on. It's not the
centerpece.
Carryng on ths anaogy, the vew s the trmmngs. The veg, condments
and the pgs n bankets; the thngs that f up the pate, ook wonderfu, and
make us droo at the sght of t.
I' stop now, for I'm gettng hungry, but I hope the prncpe s cear. The
mode shoud be what drves the appcaton, not the controer. It shoud be
the thng that makes everythng run. We depend on the controer to brng t
together, and we depend on the vew to present that data to the user n an
3
ntutve and frendy way, but t's the mode that reay does the work. It's
because of the chcken that we st down to eat at a.
It's aso mportant to remember that the data mght not be comng from a
database. It mght be comng from an API, or a fe on the server, or the
user's sesson. It mght even be gong nto a dfferent database than t
comes from. Decoupng ths means that the same nterface s used across
the appcaton to nteract wth the data, whatever the data source. And ths
decouped nature becomes mmensey usefu as your appcaton grows.
D5/4'/=' B#'&0 3#4E&45>#4(
Arguaby the most drvng desgn decson behnd Ruby on Ras s
Conventon over Confguraton-the dea that estabshng a set of
conventons across a pro|ects s better than requrng ots of confguraton
and changes. It means that deveopers need to make ess decsons;
deveopers ony specfy unconventona aspects of the appcaton.
In practce, when ookng at modes, we make a bunch of assumptons about
our database-backed modes & ther correspondng database tabes. Ths
means we can wrte ess code, our appcaton s consstent across the
board, and we can abstract a bunch of functonaty away.
Wthout further ado, here s jamie Rumbelow's 5pectacular List of Model
Conventions.
Each database-backed mode maps to a snge database tabe
The mode name s a snguar, the tabe name s pura
Each tabe w contan an id coumn
Each tabe w contan both a created_at and an updated_at
coumn
These are a reasonabe assumptons to make, and by makng them, we
can mprove the quaty and concseness of code n our mode ayer. Let's go
4
through them, one by one, and take a ook at some exampes of how to
mpement them.
F/7" '/5/+/(&G+/78&' ;#'&0 ;/H( 5# / (>4:0& '/5/+/(& 5/+0&
As wth any rue, there are exceptons (whch we' ook at n a moment). For
the ma|orty of cases, however, each database-backed mode w prmary
nteract wth one database tabe.
Chances are we have a bunch of methods that nteract wth our database
tabe n varous ways. They're aso probaby gong to ncude our standard
roster of Create, Read, Update and Destroy (CRUD) methods. Because of
ths, we're gong to be specfyng the database tabe across the cass.
public function get($where)
{
return $this->db->where($where)
->get('users')
->row();
}
public function get_all($where)
{
return $this->db->where($where)
->get('users')
->result();
}
public function insert($user)
{
return $this->db->insert('users', $user);
}
5
Smpy put, we don't have to specfy ths across the cass. Instead, we can
use an nstance varabe to specfy the tabe once. That then makes t easy
to change the tabe, f we so wsh, wthout havng to re-code the whoe
mode.
public function update($where, $user)
{
return $this->db->where($where)
->update('users', $user);
}
public function delete($where)
{
return $this->db->where($where)
->delete('users');
}
class User_model extends CI_Model
{
protected $_table = 'users';
// ...
$this->db->get($this->_table);
$this->db->insert($this->_table, $user);
$this->db->update($this->_table, $user);
$this->db->delete($this->_table);
6
I'm cang t $this->_table so we don't confct wth CodeIgnter's tabe
brary.
As we as specfyng the tabe across most-f not a-of our modes, chances
are we're gong to have ths aforementoned standard roster of methods n
them too. In eu of repcatng these, we can extend CodeIgnter's CI_Model
and create a MY_Model cass. Ths ensures we're abdng by another
mportant Ras prncpe, Don't Repeat Yourself (DRY).
A of our modes can extend ths MY_Model. By extendng MY_Model we' get
a bunch of these basc CRUD functons baked n for 'free'. Create a new fe
n your application}core drectory caed MY_Model.php.
class MY_Model extends CI_Model
{
public function get($where)
{
return $this->db->where($where)
->get($this->_table)
->row();
}
public function get_all($where)
{
return $this->db->where($where)
->get($this->_table)
->result();
}
public function insert($data)
{
return $this->db->insert($this->_table, $data);
7
CodeIgnter w oad ths for us. We can then extend our modes from
MY_Model, rememberng to specfy a tabe:
}
public function update($where, $data)
{
return $this->db->where($where)
->update($this->_table, $data);
}
public function delete($where)
{
return $this->db->where($where)
->delete($this->_table);
}
}
class User_model extends MY_Model
{
protected $_table = 'users';
}
class Post_model extends MY_Model
{
protected $_table = 'posts';
}
class Category_model extends MY_Model
{
8
.and now we can access the three database tabes n a consstent, eegant
fashon, wthout havng to dupcate code.
."& ;#'&0 4/;& >( / (>4:*0/=C 5"& 5/+0& 4/;& >( H0*=/0
Dd you spot a pattern across those modes we |ust wrote?
Each mode had a snguar name (appended wth _model), and each tabe
was the pura of that snguar name. What a great conventon to have! It's
smpe and t means that we can auto-guess the tabe name based on
purazng the snguar mode's name.
Let's wrte some code to guess that. We can put t n our MY_Model's cass
constructor, whch w ensure that t gets run whenever we oad the mode.
Make sure we ca CI_Model's constructor too-we need to access
CodeIgnter!
We can now oad CodeIgnter's nfector heper, whch contans a bunch of
usefu functons for deang wth Engsh anguage strngs.
protected $_table = 'categories';
}
public function __construct()
{
parent::__construct();
9
Fnay, we can use get_class() to fetch the cass name, rememberng to
get rd of the _mode, and puraze t. We shoud ony try to guess the tabe
name f we have not set t aready. That way, f we have to specfy an
unconventona name, we can.
The plural() functon s pretty sophstcated. It' successfuy puraze
most words. If t's struggng to puraze a word, we can easy overrde t by
specfyng t drecty n our mode.
Now, we can get rd of the expct tabe defnton from our modes:
Decous.
$this->load->helper('inflector');
if ( ! $this->_table)
{
$this->_table =
strtolower(plural(str_replace('_model', '',
get_class($this))));
}
}
class User_model extends MY_Model { }
class Post_model extends MY_Model { }
class Category_model extends MY_Model { }
10
F/7" 5/+0& 9>00 7#45/>4 /4 >' 7#0*;4
Another assumpton we can safey make s that we' be many usng the id
coumn to unquey refer to an ndvdua row n our database tabe. Wth
ths assumpton n mnd, we can sghty rework the prevous CRUD methods
we wrote n the ast secton. Let's change our get() method a tte:
Ths aows us to pass through mutpe WHERE condtons f we choose, as
we as |ust passng through our ID coumn.
We' dupcate ths method and ca t get_all(), changng the ast ne to
return the result() rather than the row():
public function get()
{
$args = func_get_args();
if (count($args) > 1 || is_array($args[0]))
{
$this->db->where($args);
}
else
{
$this->db->where('id', $args[0]);
}
return $this->db->get($this->_table)->row();
}
return $this->db->get($this->_table)->result();
11
We can aso ad|ust the insert() method to return our new ID:
The ca to $this->db->insert_id() w mean we' get the newy nserted
ID as a return vaue from the insert() method.
Fnay, et's ad|ust our update() and delete() methods n smar ways:
public function insert($data)
{
$success = $this->db->insert($this->_table, $data);
if ($success)
{
return $this->db->insert_id();
}
else
{
return FALSE;
}
}
public function update()
{
$args = func_get_args();
if (is_array($args[0]))
{
$this->db->where($args);
}
else
12
.and |ust ke that, the ID s now set as the defaut means of unque row
dentfcaton. Let's take a ook at a bt of controer code that w nteract
wth our modes:
{
$this->db->where('id', $args[0]);
}
return $this->db->update($this->_table, $args[1]);
}
public function delete()
{
$args = func_get_args();
if (count($args) > 1 || is_array($args[0]))
{
$this->db->where($args);
}
else
{
$this->db->where('id', $args[0]);
}
return $this->db->delete($this->_table);
}
public function test()
{
$id = $this->user->insert(array( 'username' =>
'jamierumbelow' ));
13
It's a short, concse syntax based on common conventons. It goes to show
that by usng basc technques and foowng basc prncpes, you can
serousy mprove the quaty of your code.
F/7" 5/+0& 9>00 7#45/>4 +#5" / 7=&/5&'I/5 /4' /4 *H'/5&'I/5
7#0*;4
Ths s an nterestng one. Whe you mght not aways need to know when a
row n your database tabe was created or updated, t can st be usefu
nformaton. If somethng goes wrong nternay, t can be reay usefu to
know when a certan resource was created or changed. Addtonay, t can
be hepfu to know these dates and tmes when you're exportng data nto
other systems, tryng to mantan a cache or fetchng data from remote
paces to popuate your database.
Whe these factors may not be the case the vast ma|orty of the tme, when
you need them, you' be gad you put them n. Pus, wth the other
conventons n pace, keepng and mantanng ths nformaton s smpe.
Let's add a coupe of nes to our insert() and update() methods:
$user = $this->user->get($id);
$this->user->update($user->id, array( 'username' =>
'jamierumbelow' ));
$this->user->delete($user->id);
}
public function insert($data)
{
$data['created_at'] = $data['updated_at'] = date('Y-m-d
14
date('Y-m-d H:i:s'), as I'm sure you' know, s the standard DATETIME
format for MySOL. Ad|ust accordngy for your database server.
H:i:s');
$success = $this->db->insert($this->_table, $data);
if ($success)
{
return $this->db->insert_id();
}
else
{
return FALSE;
}
}
public function update()
{
$args = func_get_args();
$args[1]['updated_at'] = date('Y-m-d H:i:s');
if (is_array($args[0]))
{
$this->db->where($args);
}
else
{
$this->db->where('id', $args[0]);
}
15
And now, every tme you nsert somethng nto your database va your
mode, or update a row, the created_at and updated_at coumns w be
updated appropratey.
1+(&=E&=(
There are tons of occasons where you need to ater the data gong n and
out of your modes. These are usuay thngs ke assgnng the current
user's ID to a row, addng tmestamps, and hashng passwords. One way of
achevng an MVC mpementaton of ths woud be to overoad the base
methods or add custom methods to the mode. Whe ths would work, t's
not partcuary nce. It's not consstent, and you'd end up voatng the core
DRY prncpe.
A better way of achevng ths s to use a technque caed observing.
Observers are caback methods that st n your mode. They are caed at
certan ponts (or wth certan state changes). You' probaby be famar
wth the observer pattern aready; t's used across the programmng word
n a varety of dfferent crcumstances.
There are severa ponts (state changes; moments) that we may need to be
notfed about. They ncude before and after:
A row has been created
A row has been updated
A row has been retreved
A row has been deeted
Vadaton
return $this->db->update($this->_table, $args[1]);
}
16
I'm ony gong to focus on the frst. There are many moments n a program's
fow where you mght wsh to notfy methods of state changes. However,
the pattern s ncredby smar across the board, so f you'd ke to add the
pattern n anywhere ese, t' be easy.
|ust ke our vadaton rues, we're gong to defne the observers at the top
of our mode:
We can now modfy our MY_Model's insert() method so that our
hash_password() method s caed before a row s nserted nto the
database:
.and et's add the before_create array to our MY_Model so we don't get any
errors f the user doesn't defne any methods:
class User_model extends MY_Model
{
public $before_create = array( 'hash_password' );
}
foreach ($this->before_create as $method)
{
$data = call_user_func_array(array($this, $method),
array($data));
}
$success = $this->db->insert($this->_table, $data);
public $before_create = array();
17
Fantastc. Let's do the same wth after_create():
But, dscernng readers w have notced that we are now dupcatng code,
and ths voates our DRY phosophy. Let's abstract the observer
mechansm nto an observe() functon.
public $after_create = array();
// ...
$success = $this->db->insert($this->_table, $data);
if ($success)
{
foreach ($this->after_create as $method)
{
call_user_func_array(array($this, $method),
array($data));
}
// ...
public function observe($event, $data)
{
if (isset($this->$event) && is_array($this->$event))
{
foreach ($this->$event as $method)
{
$data = call_user_func_array(array($this,
$method), array($data));
18
We can then use ths functon, whch gves us a decent observaton
mechansm, anywhere we want, rather than dupcatng code:
Let's now mpement our hash_password() functon:
Whe ths s a smpe mechansm of hashng a user password, t
demonstrates how smpe t can be to defne observers and cean up your
}
}
return $data;
}
$data = $this->observe('before_create', $data);
$success = $this->db->insert($this->_table, $data);
if ($success)
{
$this->observe('after_create', $data);
public function hash_password($user)
{
$user['password'] = sha1($user['password']);
return $user;
}
19
codebase. It's mportant to remember that every observer caback you
defne needs to return the $data variable that's passed through.
Wth ths abstracted observer pattern n pace, we can add any observers
we ke n our modes, even n our custom methods. Observers are yet
another way of smpfyng and enhancng exstng and new code.
D7#H>4:
Mode scopng aows you to easy (and beautfuy) manpuate your
queres, partcuary makng database fnds eegant and convenent. They
open up a whoe new word of "terate" programmng. The essence of
mode scopng s that by channg named methods together, you can change
the parameters of a query, whe retanng readabty, keepng ths ogc n
the mode, and ensurng you don't repeat yoursef. In a smar way that
CodeIgnter's ActveRecord aows you to chan methods together to bud
queres, you can use mode scopng to ncrementay add onto your query.
The trck to wrtng scopes s return $this. By returnng $this, you're
returnng the current nstance of the mode cass. Ths aows PHP to chan
the methods onto one another. Take, for exampe, a reguar mode functon:
public function get_all_confirmed()
{
return $this->db->where('confirmed', 1)
->order_by('date')
->get($this->_table)
->result();
}
20
Ths mght not appear too messy, but what happens f we're tryng to get
many confrmed rows by a country:
It's gettng a tte messer. We aso want to get many confrmed rows by
frst name, so nstead of defnng another get_all_confirmed_by_blah()
method, we consodate these ookups:
.and then what happens f we decde we need to get one? Do we dupcate
the method? Or do we add some ogc and add a thrd parameter to return
one or many?
public function get_all_confirmed_by_country($country)
{
return $this->db->where('confirmed', 1)
->where('country', $country)
->order_by('date')
->get($this->_table)
->result();
}
public function get_all_confirmed_by($key, $value)
{
return $this->db->where('confirmed', 1)
->where($key, $value)
->order_by('date')
->get($this->_table)
->result();
}
21
You can see how qucky ths whoe ookup process can get convouted. Let's
rethnk ths. What f, nstead, we had one cean method that added the
confrmed query, one method that got a rows, one method that got a rows
by and one method that we got a snge row by?
That's ookng much, much better aready. If we then combne t wth our
prevousy constructed MY_Model we have an even neater mode:
public function confirmed()
{
$this->db->where('confirmed', TRUE);
return $this;
}
public function by($key, $value)
{
$this->db->where($key, $value);
return $this;
}
public function get_all()
{
return $this->db->get($this->_table)
->result();
}
public function get()
{
return $this->db->get($this->_table)
->row();
}
22
How do we nteract wth our mode?
Ths s aready an amost fawess system. Wth |ust a tny bt of code, we've
managed to consderaby cean up our mode and have gven ourseves a
reusabe nterface to add certan parameters to our query. We can add or
remove as many scopes as we ke, and they can be as comprehensve or as
smpe as we ke. It doesn't even have to be mted to database ookups.
We're sharng an nstance of the cass, so we can add thngs to an nstance
varabe whch we can then prnt, or we can perform other data-reated
processes a scoped out by our method scopes. There's |ust one probem.
What f we want to make a separate query INSIDE our chan mechansm?
Let's say we need to pu n some data from a separate tabe or database.
We mght be abe to acheve ths wth a JOIN, but t mght aready be a
compex query where throwng another JOIN n the fray woud ony
compcate matters. Take, for exampe, ths scope:
public function confirmed()
{
$this->db->where('confirmed', TRUE);
return $this;
}
$users = $this->user->confirmed()->get_all();
$users_uk = $this->user->confirmed()->by('country', 'United
Kingdom')->get_all();
public function favorited()
{
23
This will only work if it's called first in the chain. You can easy get confcts
n SOL queres when you're channg and makng separate queres nsde the
scopes. You' be ookng for coumns that don't exst, and executng the
prevousy caed scopes. The ca to $this->db->get() resets any other
Actve Record parameters, so, for exampe, f we caed the scope thus:
We'd be searchng for a rows n the favorites tabe where confirmed = 1.
The favorites tabe mght not have a confirmed coumn, and then,
nstanty, we've got an errorng query. We don't want to have to worry
about puttng these thngs n the correct order. Thankfuy, the souton s
reasonaby smpe.
$fav =
$this->db->select('user_id')->get('favorites')->result();
$ids = array();
foreach ($fav as $row)
{
$ids[] = $row->user_id;
}
$this->db->where_in('id', $ids);
return $this;
}
$this->user->confirmed()
->favorited()
->get_all();
24
The trck s to isolate the query. Prevousy, I'd take the current vaues from
CodeIgnter's ActveRecord cass, cache them, cear them, make the query,
and then reset the varabes to ther orgna state. However, these varabes
start wth an underscore. Whe they're st pubc-nternay they're defned
wth var-I have a funny feeng that sooner or ater they' be defned as
protected or private, whch w break ths method.
My souton s to re-ntaze the database cass and return that, aowng
methods to run queres that start wth a bank sate. A ths takes s a ca to
the core DB() functon:
public function favorited()
{
$db = DB('default');
$fav =
$db->select('user_id')->get('favorites')->result();
$ids = array();
foreach ($fav as $row)
{
$ids[] = $row->user_id;
}
$db->close();
$this->db->where_in('id', $ids);
return $this;
}
25
Now we can use the favorited() scope wthout fear!
As you can see, scopes are a powerfu and fexbe way of creatng
semantc, cean, and concse code.
@/0>'/5>#4
One of the bggest mstakes neary a CodeIgnter deveopers make s to
pace the vadaton n the controer ayer. CodeIgnter's but-n brary
promotes ths bad practce, but, as we earned earer, vadaton s
processng data, so t needs to be in the model layer.
That sad, CodeIgnter's form vadaton brary s pretty great. It provdes a
bunch of vadaton methods that make vadatng your POST data ncredby
easy.
But what f you're not vadatng form data? What f you're wrtng an API,
and you're vadatng the nput data from the user's request? What f you're
brngng n data through some form of mport mechansm: perhaps a CSV
fe, a CMS, or another data-drven system?
These are a cases where you' be wantng to vadate the data, but you
cannot do t through CodeIgnter's defaut vadaton mechansm.
$this->user->confirmed()
->sorted()
->favorited()
->get_all();
26
Contnung the dea of fat mode, sknny controer, et's begn by defnng
the vadaton rues n our mode drecty. We ony need to do ths once, so
we can defne t at the top of the cass:
As you can see, we are usng the same syntax of array as f we were passng
ths through to the $this->form_validation->set_rules() method. We
can then defne a functon to actuay process the vadaton.
Snce CodeIgnter's form vadaton functon drecty accesses the $_POST
array, and we want to vadate any arbtrary array of data, we have to do
somethng fary drty n order to enabe ths functonaty. We have to
drecty ater the $_POST array before passng the rues through to the form
vadaton brary, thus foong the brary nto thnkng that the data has
come from the user's $_POST data.
We' create a validate() functon, whch w check to see that we have
vadaton rues, oad the form vadaton brary, popuate the $_POST data,
and run the vadatons.
class User_model extends MY_Model
{
public $validate = array(
array( 'field' => 'username', 'label' => 'Username',
'rules' => 'required|max_length[20]|alpha_dash' ),
array( 'field' => 'password', 'label' => 'Password',
'rules' => 'required|min_length[8]' ),
array( 'field' => 'email', 'label' => 'Email',
'rules' => 'valid_email' )
);
}
27
Now, n our controer, nstead of settng the rues and processng the
vadaton, we can smpy ca validate() to ensure that the data s vad
before we nsert t nto the database. If t's not vad, we can access the
vadaton errors |ust ke norma, through the validation_errors() heper
method. The assocated controer code coud ook somethng ke ths:
public function validate($data)
{
if (!empty($this->validate))
{
foreach ($data as $key => $value)
{
$_POST[$key] = $value;
}
$this->load->library('form_validation');
$this->form_validation->set_rules($this->validate);
return $this->form_validation->run();
}
else
{
return TRUE;
}
}
public function create()
{
$user = $this->input->post('user');
if ($this->user->validate($user))
28
Ths s cean, and certany cearer than t coud be, but we can st mprove
on ths. Remember the MY_Model that we created earer? We can move the
validate() method nto there, whch w provde consstent vadaton
across the board.
We can aso modfy our insert() and update() methods to support data
vadaton.
{
$user_id = $this->user->insert($user);
redirect('/users/show/' . $user_id);
}
else
{
$this->session->set_flashdata('error',
validation_errors());
redirect('/users/add');
}
}
public function insert($data, $skip_validation = FALSE)
{
$data['created_at'] = $data['updated_at'] = date('Y-m-d
H:i:s');
if (!$skip_validation && !$this->validate($data))
{
$success = FALSE;
}
else
{
29
We're addng a second parameter, the $skip_validation varabe, whch
w aow us to skp the vadaton f we so choose. Let's do somethng
smar wth update().
$success = $this->db->insert($this->_table, $data);
}
return ($success) ? $this->db->insert_id() : FALSE;
}
public function update()
{
$args = func_get_args();
$args[1]['updated_at'] = date('Y-m-d H:i:s');
$validate = (isset($args[2])) ? !$args[2] : TRUE;
if ($validate && $this->validate($args[1]))
{
if (is_array($args[0]))
{
$this->db->where($args);
}
else
{
$this->db->where('id', $args[0]);
}
return $this->db->update($this->_table, $args[1]);
}
else
30
Agan, we add an optona thrd parameter whch aows us to skp the
vadaton. Otherwse, we vadate the data before we process t.
Wth these changes mpemented, the prevous controer code can now
become ths:
That's a tte neater and more concse. More mportant, t goes to show how
easy we can ensure the adherence of the MVC pattern wthn CodeIgnter.
{
return FALSE;
}
}
public function create()
{
$user = $this->input->post('user');
if ($user_id = $this->user->insert($user))
{
redirect('/users/show/' . $user_id);
}
else
{
$this->session->set_flashdata('error',
validation_errors());
redirect('/users/add');
}
}
31
BJIB#'&0
Over ths chapter we've but up a pretty smart and effcent MY_Model.
Whe t's ncredby mportant to understand the method behnd the magc,
t's aso mportant to try to mnmse your work and ensure that you make
the most out of the resources made avaabe by the communty.
Most of the deas, concepts and code n ths chapter have been fted and
adapted from the code n my freey-avaabe, open-source MY_Model. It's
under actve deveopment, has a far amount of communty contrbuton and
s used n ots of popuar appcatons, ncudng the fantastc PyroCMS.
You can head over to the GtHub repostory
|1|
and downoad my MY_Mode
for free. Copy t nto your application}core drectory and away you go!
1.https://gthub.com/|amerumbeow/codegnter-base-mode
32
.n whch we tdy up our presentaton ogc and dscuss concepts present n the Ras word
that makes t easer to wrte ceaner and better-formatted vews. We ook at separatng the
presentatona ogc out nto an abstracted cass, aowng us to DRY up our vews and keep
them cean. We examne partas, ettng us repeat sectons of content across the
appcaton, and take a ook at cachng fragments of vews for stes wth heaver oads.
K=&(&45&=(
Over the course of an appcaton's deveopment, t can be very common for
vews to become messy. Vews can easy become obfuscated and mxed
up, whch makes t much easer to voate DRY and MVC. After a, there
shoudn't be any busness ogc n the vew.
34
Presenters are a rather new technque that I dscovered n the Ras word
that can hep by addng another ayer of abstracton, by provdng a cass
representaton of the state of vew. I fnd that presenters can be a reay
seek way of hdng presentatona ogc.
Let's ook at a smpe vew that dspays some nformaton about a user's
bank account:
<div id="account">
<h1><?= $this->bank->get($account->bank_id)->name ?> -
<?= $account->title ?></h1>
<p class="information">
<strong>Name:</strong> <?php if ($account->name):
?><?= $account->name ?><?php else: ?>N/A<?php endif; ?><br />
<strong>Number:</strong> <?php if
($account->number): ?><?= $account->number ?><?php else:
?>N/A<?php endif; ?><br />
<strong>Sort Code:</strong> <?php if
($account->sort_code): ?><?= substr($account->sort_code, 0,
2) . "-" . substr($account->sort_code, 2, 2) . "-" .
substr($account->sort_code, 4, 2) ?><?php else: ?>N/A<?php
endif; ?>
</p>
<p class="balances">
<strong>Total Balance:</strong> <?php if
($account->total_balance): ?><?= "&pound;" .
number_format($account->total_balance) ?><?php else:
?>N/A<?php endif; ?>
<strong>Available Balance:</strong> <?php if
($account->available_balance): ?><?= "&pound;" .
35
Ths s a rather typca vew; t's dspayng bts of content from an ob|ect,
checkng for a vaue's exstence, and pung n bts from other database
tabes. It's fne, but t's a a bt messy, and we're dupcatng a far amount
of code. Ideay, we want our vew to ook somethng ke ths:
number_format($account->available_balance) ?><?php else:
?>N/A<?php endif; ?>
</p>
<p class="statements">
<?php if ($this->statements->count_by('account_id',
$account->id)): ?>
<?= anchor('/statements/' . $account->id, 'View
Statements') ?>
<?php else: ?>
Statements Not Currently Available
<?php endif; ?>
</p>
</div>
<div id="account">
<h1><?= $account->title() ?></h1>
<p class="information">
<strong>Name:</strong> <?= $account->name() ?><br />
<strong>Number:</strong> <?= $account->number()
?><br />
<strong>Sort Code:</strong> <?=
$account->sort_code() ?>
</p>
36
Ths cears up our vew consderaby and removes a bunch of the
dupcaton. It aso strps out as much ogc as possbe from the vews, and
can make for some very succnct code. Ths means that we can output ths
nformaton agan n other paces, f we so choose. (DRY, DRY, DRY!)
We're gong to create an application}presenters drectory, nsde of whch
s the presenter.php fe. Ths presenter base fe w defne a smpe cass
that our other presenters w nhert from.
<p class="balances">
<strong>Total Balance:</strong> <?=
$account->total_balance() ?>
<strong>Available Balance:</strong> <?=
$account->available_balance() ?>
</p>
<p class="statements"><?= $account->statements_link()
?></p>
</div>
class Presenter
{
public function __construct($object)
{
$name = strtolower(str_replace("_presenter", "",
get_class($this)));
$this->$name = $object;
}
}
37
The constructor here s gong to automatcay guess the name of the ob|ect
and set t ocay as an nstance varabe (so, effectvey, we're presenting
whatever ob|ect we pass through). We aso want to be abe to access the
CodeIgnter superob|ect, so et's defne the PHP magc method __get().
__get() s caed whenever the program tres to access a varabe on an
ob|ect that hasn't been defned. We're passng t through to
get_instance() so any cas to CodeIgnter n our presenter-for exampe, to
$this->load or $this->db-w be passed straght through.
Fnay, we need to oad the Presenter cass nto our appcaton. Ths s a
smpe require_once, but where shoud we put t? We, you can put t n
any fe that s oaded gobay: index.php, config}config.php and config}
autoload.php are three great exampes. Personay, I put my goba cas to
require_once n autoload.php, because to me t makes the most sense
there.
Next up, et's create an account_presenter.php fe. Ths fe w contan
the presenter cass for our account ob|ect. We can then extract the
public function __get($attr)
{
if (isset(get_instance()->$attr))
{
return get_instance()->$attr;
}
}
require_once APPPATH . 'presenters/presenter.php';
38
cuttered ogc from our vew and pace them n the cass. Let's start by
extractng the tte:
We're essentay |ust portng the ogc that was prevousy n the vew nto a
title() method. We can now go ahead and tdy up the nformaton secton:
class Account_Presenter extends Presenter
{
public function title()
{
return
$this->bank->get($this->account->bank_id)->name . "-" .
$account->title;
}
}
public function name()
{
return $this->account->name ?: "N/A";
}
public function number()
{
return $this->account->number ?: "N/A";
}
public function sort_code()
{
if ($sc = $this->account->sort_code)
{
39
Smary, we can tdy up the baances secton:
And fnay, the statements nk:
return substr($sc, 0, 2) . "-" . substr($sc, 2, 2) .
"-" . substr($sc, 4, 2);
}
else
{
return "N/A";
}
}
public function total_balance()
{
return ($this->account->total_balance) ? "&pound;" .
number_format($this->account->total_balance) : "N/A";
}
public function available_balance()
{
return ($this->account->available_balance) ? "&pound;" .
number_format($this->account->available_balance) : "N/A";
}
public function statements_link()
{
if ($this->statements->count_by('account_id',
$this->account->id))
40
Fantastc. Now here's the cever bt. Instead of passng through the account
ob|ect drecty from our mode, we frst wrap t n our presenter. In ths
respect, the presenter represents the pubc-facng output of the database
row.
We won't need to oad our presenters on every controer, so t makes more
sense that we oad the presenter at the top of each controer that needs t.
.and wth that we're done!
{
return anchor('/statements/' . $this->account->id,
'View Statements');
}
else
{
return "Statements Not Currently Available";
}
}
public function show($account_id)
{
$this->data['account'] = new
Account_Presenter($this->account->get($account_id));
$this->load->view('account/show', $this->data);
}
require_once APPPATH . 'presenters/account_presenter.php';
41
By addng another ayer of abstracton we've managed to convert our
prevousy messy and cuttered vews nto succnct, short, aesthetcay
peasng vews. Pus, t's nce and DRY.
K/=5>/0(
There w often be occasons when you're repeatng code ots of tmes
across mutpe vews. When budng a CRUD system, for exampe, you may
be repcatng the add and edt form code, or you mght be outputtng a
tabe or an output of a database row. In the sprt of DRY, we can extract
ths repcated code and move t nto what's caed a partial.
Budng a oose parta system nto your appcaton makes removng
dupcaton of code and enforcng DRY even easer. Conventonay, Ras
partas begn wth an underscore. Ths seems ke a good conventon to
contnue (aowng us to easy dstngush between vews nked to controer
actons and our reusabe partas).
We' start off by creatng a partial_helper.php fe n our application}
helpers drectory. We coud go the whoe hog and bud a parta renderng
brary. However, we ony need smpe renderng functonaty, so a heper
w suffce.
In our heper we' defne a snge, sotary functon, caed partial(). We'
start off by gettng partial() to smpy render out the parta.
function partial($name, $data)
{
return get_instance()->load->view($name, $data, TRUE);
}
42
Nothng usefu here yet, but not to worry. Lke I mentoned above, a ot of
the tme we' be oopng through a resut set from our modes and
outputtng a row. It'd be nce to have ths baked nto our parta system so
we can automatcay output mutpe partas based on an array.
We want the abty to enabe ths seectvey, so we' add a thrd
parameter:
Ths means we can oop through a resut set from Actve Record easy, ke
ths:
function partial($name, $data, $loop = FALSE)
{
$output = "";
if ($loop && is_array($data))
{
foreach ($data as $row)
{
$output .= get_instance()->load->view($name,
array( 'row' => $row ), TRUE);
}
}
else
{
$output = get_instance()->load->view($name, $data,
TRUE);
}
return $output;
}
43
And then access each row n our parta wth the $row varabe, ke so:
It'd be nce f ths method knew what controer we were n, so t coud
namespace our partas for us (we' dscuss more about ths conventon n
Part 3). We can do ths by parsng the parta name and addng our
controer name. We' aso add the underscore, for good measure:
Now, what f we want to oad a parta from outsde our controer's
drectory? Let's check for the exstence of a forward sash n our parta
name:
$result = $this->db->get('projects')->result();
<table>
<?= partial('projects/_row', $result, TRUE) ?>
</table>
<tr>
<td><?= $row->title ?></td>
<td><?= $row->duration ?></td>
<td><?= $row->cost ?></td>
</tr>
$name = get_instance()->router->directory .
get_instance()->router->class . '/_' . $name;
if (strpos('/', $name) === FALSE)
{
44
.but, snce we're tryng to be cever, and we want to be as conventona as
possbe, et's do our best to automatcay add n the underscore, even f we
specfy a drectory:
As you can see, we're aso checkng that we're not doubng up on
underscores, because we want our functon to work whatever the nput.
Now that we have a the magc n there, our parta method w respond
correcty to the foowng test cases:
$name = get_instance()->router->directory .
get_instance()->router->class . '/_' . $name;
}
else
{
$parts = explode('/', $name);
$last = count($parts) - 1;
$parts[$last] = (strpos('_', $parts[$last]) === 0) ?
$parts[$last] : '_' . $parts[$last];
$name = implode('/', $parts);
}
partial('row'); // projects/_row.php
partial('people/card') // people/_card
partial('users/_dropdown') // users/_dropdown
45
A=/:;&45 3/7">4:
Cachng s ke fossng. Everybody says you shoud do t, but uness you're
dentay bedeved, chances are you forget most mornngs. However, when
your gums beed every tme you brush, you're gong to start notcng and
reazng that maybe you shoud have taken the advce of your peasant, yet
du apponted doctor of dentstry, Dr. Leventhorpe.
Cachng s ony really necessary once you start seeng performance
probems that affect your webste. Once you notce these ssues, you really
notce them. It s equay mportant not to over-optmze, but a bt of seect
cachng on some partcuary ntensve pages can make a the dfference to
the speed of your ste.
CodeIgnter contans reasonaby decent fesystem-based page cachng,
aong wth a pretty powerfu mut-backend cachng brary. Both methods
work we, but they do have ther downsdes.
CodeIgnter's page cachng caches the entre page, and whe ths ads
performance, t can often be nconvenent. If you're showng ogn/ogout
nks on your pages, they need to respond to the user's sesson (rather than
|ust dspay whatever s n the cache). Wth CodeIgnter's page cachng, ths
s very trcky to do.
A good souton to ths knd of probem s Fragment cachng. Fragment
cachng aows you to cache seect parts of pages rather than the entre
thng, meanng that processor-ntensve eements can be cached wthout
osng the fexbty of a dynamc page.
lt's important to remember that fragment caching is only really
effective when heavy processing is happening !"#!$% the fragment.
That is to say, contrary to MVC, if you're fragment caching, you'll
want to call model methods directly inside the fragment}view,
rather than in the controller.
46
We're gong to bud a smpe fragment cachng brary that w pggyback
on CodeIgnter's but-n cachng drver. We' use the Memcached
|2|
drver
n whch store our cache data. Memcached s mut-patform, easy to nsta
and has a great PHP extenson that s smpe to enabe va PECL.
From ths pont onward, I'm gong to assume that you've nstaed
Memcached and enabed the PHP extenson. The process s very smpe, and
we documented onne.
Wth Memcached up and runnng, create a new confguraton fe,
application}config}memcached.php, and confgure your memcached
server connecton detas:
Create a Fragment.php fe n application}libraries. We' start off by
defnng our cass and grabbng an nstance of the CodeIgnter superob|ect.
$config['memcached'] = array(
'hostname' => 'localhost',
'name' => 'memcache',
'port' => 11211
);
class Fragment
{
public function __construct()
{
$this->ci =& get_instance();
}
}
2.http://memcached.org
47
Let's oad CodeIgnter's cachng drver:
Due to the nherent drver system, we can then go straght ahead and
access the Memcached drver.
Our fragment cachng s gong to work one of two ways. For peope usng
PHP 5.3 or earer, t's gong to ook ke ths:
We can make ths code even pretter n PHP 5.3, thanks to anonymous
functons:
Frsty, et's ntaze a coupe of varabes n our cass:
$this->load->driver('cache');
<?php if ($this->fragment->start_cache()): ?>
<h1><?=
$this->model->some_processor_or_database_heavy_function()
?></h1>
<?php endif; $this->fragment->end_cache(); ?>
<?= $this->fragment->cache(function(){ ?>
<h1><?=
$this->model->some_processor_or_database_heavy_function()
?></h1>
<?php }); ?>
48
Let's get to work on our start_cache() method. Next, we want to work out
a reproducbe cache key, under whch to store the fragment. We can do ths
by grabbng the current URI and combnng t wth the current ne number.
We can grab the URI from CodeIgnter, and the current ne number through
a ca to debug_backtrace(). We' aso aow the user to set an expraton
tme, whch w defaut to 3600 seconds (one hour).
Now that we've fgured out what key we're usng, et's try to get t from the
cache:
protected $key = '';
protected $fragment = '';
protected $fresh = FALSE;
protected $expire = 3600;
public function start_cache($expire = 3600)
{
$backtrace = debug_backtrace();
$this->key = sha1($this->ci->uri->uri_string .
$backtrace[0]['line']);
$this->expire = $expire;
if ($fragment =
$this->ci->cache->memcached->get('fragments.' . $this->key))
{
$this->fragment = $fragment;
49
If t's n the cache, we can set t temporary as a varabe and return FALSE.
By returnng FALSE, we prevent the tempate code n between our if
statement from beng run. We' output n end_cache() momentary.
If t's not n the cache, we want to mark t as fresh, begn output bufferng,
and aow the tempate code to run. We can then cache the data afterward.
Now we can defne our end_cache() method, whch w capture and cache
the output f t's fresh, or ese output the cached data.
return FALSE;
}
else
{
$this->fresh = TRUE;
ob_start();
return TRUE;
}
public function end_cache()
{
if ($this->fresh)
{
$output = ob_get_contents();
ob_end_clean();
50
That was smpe! We check to see f the cache s fresh. If t s, we capture
the output from the fragment and save t to the cache. Otherwse, we grab
the cache. We then reset our varabes and output the fragment.
Usng PHP 5.3, we can make ths even sweeter, by wrappng the fragment n
a snge ca to a cache() functon:
$this->ci->memcached->save($this->key, $output,
$this->expire);
}
else
{
ob_end_clean();
$output = $this->fragment;
}
$this->fresh = FALSE;
$this->key = '';
$this->fragment = '';
echo $output;
}
public function cache($fragment, $expire = 3600)
{
if ($this->start_cache($expire))
{
$fragment();
}
51
And wth under 50 nes of code, we've got a fuy functona, smart and easy
to use fragment cachng brary. Fragment cachng s most effectve wth
process- or database-heavy actons happenng inside the fragment; t can
be a reay smart way of cachng partcuar fragments of your vews.
$this->end_cache();
}
52
.n whch we examne the controer ayer and thoroughy mprove the way that the controer
nks up the vews and modes n our appcaton. We dscuss enforcng conventons to
autooad our vews and modes, we bud a confgurabe yet conventona ayout system wth
whch to oad our vews, and we dscuss usng fters to run code pre- and post-acton.
6*5#0#/'>4: @>&9(
Let me brng up a perfecty understandabe, non-shockng and non-
dsruptve conventon for organzng vews.
Views should be housed in a directory named after the controller, and
should themselves be named after the controller action.
54
Ths makes nothng but perfect sense. If we're at a URL, say, /users/list,
t makes absoute sense that the vew to be oaded woud be caed views}
users}list.php. In fact, you may have a namng pattern ke ths n pace
aready.
Wth ths conventon n mnd, we can dramatcay reduce the need for cas
to $this->load->view(), by automatcay oadng our vews after our
controer method s run. We can DRY up our controers mmensey and
create reay nce, succnct, cean controer code.
In order to acheve ths, we're gong to utze an often overooked feature of
CodeIgnter: _remap(). _remap() s a method that, f t exsts n a controer,
w be caed by CodeIgnter instead of the controer acton. Ths aows
deveopers to hook straght nto the ca process and execute functonaty
before and after each acton.
Let's get crackng straght away by creatng a MY_Controller.php fe
nsde application}core. We' use the MY_Controller to aow every
controer n our appcaton to have magcay autooadng vews.
Begn wth the usua stuff:
class MY_Controller extends CI_Controller
{
public function __construct()
{
parent::__construct();
}
}
55
We' defne our _remap() functon. It w take two parameters, $method, the
acton name, and $parameters, the parameters from the routed URL
segments ntended to be passed nto the controer acton.
We're usng call_user_func_array() to ca the method, passng through
the parameters. Let's frst make a santy check to ensure that the method
exsts. If t doesn't, we want to respond wth a 404.
If we're successfuy cang the method, we want to bud up the vew name
from the controer and the acton:
public function _remap($method, $parameters)
{
call_user_func_array(array($this, $method), $parameters);
}
if (method_exists($this, $method))
{
call_user_func_array(array($this, $method), $parameters);
}
else
{
show_404();
}
$view = strtolower(get_class($this)) . '/' . $method;
56
And how do we pass data through? Instead of defnng a oca $data
varabe, we can defne an nstance-eve $this->data varabe. Ths comes
n handy not |ust here, but t aso aows us to pass through data to each
vew at a goba eve.
Now that t's set up, we can oad the vew:
Ths s gong fantastcay. We aready have a great conventon set up. But
what f we fee ke beng unconventona? What f we decde not to oad a
vew after a, optng nstead to spt out some |SON, or perhaps some raw
text from the controer?
Let's add an nstance varabe caed $this->view. We' check f $this-
>view s set to FALSE. If t s, we won't show a vew. If t sn't, we' go rght
ahead and output the vew.
.and our condtona:
public $data = array();
$this->load->view($view, $this->data);
public $view = TRUE;
if ($this->view !== FALSE)
{
57
So far so good! Fnay, to make ths even more confgurabe, there may be
moments when we want to specfy the vew to oad precsey. We may want
to oad a shared vew, or a vew out of our conventona namng pattern.
Let's add to our condtona. We' check f $this->view s a strng-f t s, we
know we want to oad t.
And there we have t! It's a smpe technque, but usng ths conventon and
autooadng your vews can nstanty cean up your controer actons. Take,
for exampe, the foowng method:
$this->load->view($view, $this->data);
}
$view = (is_string($this->view) && !empty($this->view)) ?
$this->view : $view;
if ($this->view !== FALSE)
{
$this->load->view($view, $this->data);
}
public function index()
{
$data['projects'] = $this->project->get_all();
$data['title'] = 'All Projects';
$this->load->view('projects/index', $data);
}
58
Usng our autooadng code, we can qucky smpfy and strp off 50% of the
method body:
It's mportant to remember that, athough ths dfference mght not seem
bg, each tte bt adds up, and very qucky the controer becomes a
sprawng mess. A good rue of thumb to foow s:
lf the code doesn't need to be there, it probably shouldn't be.
L/,#*5(
One of the bggest ssues wth the prevousy proposed autooadng souton
s oadng the vew nto a ayout. Whe there are a few good ayout brares
avaabe for CodeIgnter, they're not commony used, and personay I
prefer to contro the oadng of ayouts mysef.
Take ths commony seen bock of code:
It seems to be fne, but when you cosey examne the way t works, t starts
to show ts probems. Ignorng the fact that you' be dupcatng a ot of
public function index()
{
$this->data['projects'] = $this->project->get_all();
$this->data['title'] = 'All Projects';
}
$this->load->view('shared/_header', $data);
$this->load->view('users/all', $data);
$this->load->view('shared/_footer', $data);
59
code, t aso doesn't gve you a huge amount of fexbty. A ayout system
ke ths presupposes a fat, un-dynamc ayout. The moment changng
sdebars and fexbe mastheads come nto the equaton, everythng starts
to get even more compcated.
A better souton woud be to have a snge ayout for the appcaton and
oad the vew nto ths as a varabe. It's then at the ayout's dscreton
where t decdes to output the vew. It coud ook ke ths:
Ths s a good start, and s how Ras tackes the probem. For consstency's
sake, and because t's a more descrptve term, I'm gong to rename the
$view varabe to $yield. We' aso move the ayout oadng nto our
prevousy created MY_Controller:
We' pass through our $this->data to the ayout n case we want to set a
tte, breadcrumbs, or somethng ese that needs to st outsde of our vew.
$view = $this->load->view('users/all', $data, TRUE);
$this->load->view('layouts/application', array( 'view' =>
$view ));
if ($this->view !== FALSE)
{
$this->data['yield'] = $this->load->view($view,
$this->data, TRUE);
}
$this->load->view('layouts/application', $this->data);
60
So far, ths s a reay great souton. Vews are automatcay beng oadng
nto a ayout, n whch we can then specfy exacty where we want to spt
out the vew:
The frst probem I can spot here s f we want to oad our vew nto a
separate ayout rather than our goba appcaton ayout. There are severa
occasons when you' need to do ths. Logn forms, mcro-stes and admn
panes a coud have dstnct ooks from the rest of the appcaton.
We can sove ths probem by estabshng the conventon that a controller
coud have ts own ayout. Ths way, f the controer has a ayout, we oad
that, otherwse we go for the generc appcaton ayout.
Lke before, we can mpement ths easy:
<header>
<h1>My Application</h1>
</header>
<div id="wrapper">
<?= $yield ?>
</div>
<footer>
<p>Copyright 2012</p>
</footer>
if (file_exists(APPPATH . 'views/layouts/' .
strtolower(get_class($this)) . '.php'))
{
61
Much ke our vews, t'd be nce f we coud choose to not dspay a ayout at
a. We mght want to output a snppet of HTML n an A|AX ca, for nstance,
and for that we probaby don't want the ayout. Let's add an nstance
varabe and copy what we dd wth vews:
And, ke before, check f t exsts:
It woud aso be usefu f we coud specfy the ayout. We can do somethng
ncredby smar ke we dd wth vews:
$layout = 'layouts/' . strtolower(get_class($this));
}
else
{
$layout = 'layouts/application';
}
$this->load->view($layout, $this->data);
public $layout = TRUE;
if ($this->layout)
{
$this->load->view($layout, $this->data);
}
else
{
echo $this->data['yield'];
}
62
Wth a of these changes, our _remap() functon shoud ook ke ths:
if (is_string($this->layout) && !empty($this->layout))
{
$layout = $this->layout;
}
elseif (file_exists(APPPATH . 'views/layouts/' .
strtolower(get_class($this)) . '.php'))
{
$layout = 'layouts/' . strtolower(get_class($this));
}
else
{
$layout = 'layouts/application';
}
public function _remap($method, $parameters)
{
if (method_exists($this, $method))
{
call_user_func_array(array($this, $method),
$parameters);
}
else
{
show_404();
}
$view = (is_string($this->view) && !empty($this->view))
? $this->view : $view;
63
if ($this->view !== FALSE)
{
$this->data['yield'] = $this->load->view($view,
$this->data, TRUE);
if (is_string($this->layout) &&
!empty($this->layout))
{
$layout = $this->layout;
}
elseif (file_exists(APPPATH . 'views/layouts/' .
strtolower(get_class($this)) . '.php'))
{
$layout = 'layouts/' .
strtolower(get_class($this));
}
else
{
$layout = 'layouts/application';
}
if ($this->layout)
{
$this->load->view($layout, $this->data);
}
else
{
echo $this->data['yield'];
}
}
}
64
And that s reay a t takes to have a totay confgurabe and customzabe,
conventon-powered automagca vew oader.
6*5#0#/'>4: B#'&0(
Carryng on wth the autooadng theme, a smpe technque you can use to
cear up your controer method s to provde a sma mode-autooadng
nterface. Usuay, f you oad a mode n your controer, chances are you
want to use t across mutpe actons.
Loadng a bunch of modes n your constructor can ook pretty messy. It'd be
much ncer f we coud take the conventons we estabshed n Part 1 and
expand on them to aow us to automatcay oad modes based on those
conventons.
I've aways named my modes aong these nes:
singular_resource_model.php. For a users tabe, t'd be
user_model.php. For a mode that handes fes, t'd be file_model.php.
Addtonay, I access my modes ke ths:
Fnay, I usually work to the assumpton that my controer name w be a
pura word, mappng to a snguar mode. We can use ths assumpton to try
to automatcay oad the mode reated to our controer.
Open up our MY_Controller.php ke before. Ths tme, we' work nsde
the constructor. We' fetch the mode name by snguarzng the controer
name:
$this->user->get_all();
$this->file->upload();
65
We' check that the mode exsts, and f t does, oad t:
Ths s great so far, but t doesn't reay sove our probem. Chances are we'
need to oad other modes too. How can we smpfy ths process?
My souton s to add a $this->models array to our controer whch w
automatcay add the _model and oad each mode nsde t. That way the
st of modes s confned to a snge pace, wth as tte repetton as
possbe:
Insde our controer we can oop through ths array:
public function __construct()
{
parent::__construct();
$this->load->helper('inflector');
$model = strotolower(singular(get_class($this)));
if (file_exists(APPPATH . 'models/' . $model . '_model.php'))
{
$this->load->model($model . '_model', $model);
}
public $models = array();
66
We can even refactor the prevous autooadng code to use ths array:
Then, n our controer tsef, we can specfy any of the modes we want to
oad wthn ths array:
Comparng ths to the code prevousy requred n the constructor, you see:
Suddeny, the dfference becomes obvous. Agan, t may ony be a subte
dfference, but when workng on arger apps wth hgher numbers of fes
and arger bocks of code, the sma changes can make a the dfference.
foreach ($this->models as $model)
{
$this->load->model($model . '_model', $model);
}
if (file_exists(APPPATH . 'models/' . $model . '_model.php'))
{
$this->models[] = $model;
}
public $models = array( 'user', 'project', 'benchmark' );
$this->load->model('user_model', 'user');
$this->load->model('project_model', 'project');
$this->load->model('benchmark_model', 'benchmark');
67
The more concse and succnct your code s, (usuay) the more
mantanabe.
A>05&=(
There w be occasons when you need to execute code on a controer-by-
controer bass, |ust before or after the controer acton has been run. Much
ke the mode observers we saw n Part 1, or the autooadng of vews we've
been usng here n Part 3, we may need to execute snppets of code before
and after actons.
Ths comes n especay usefu for thngs ke authentcaton. You can easy
get a method to be caed pre-acton across your controer methods. You're
probaby thnkng "|ust put t n the constructor," but what happens f you
want to ca the method on a actons but one? Suddeny, the concept of
fters pays a magnfcent roe.
In order to mantan our aready sprawng _remap() functon, we're gong to
pace our ca to call_user_func_array() n between two new methods
whch we' create, _run_before_filters() and _run_after_filters().
Our methods are gong to take our acton and parameters, and pass them
through to the fters we defne at the top of the cass. We want to be abe to
specfy the fters n one of two ways. Frst, as a fat array:
$this->_run_before_filters($method, $parameters);
call_user_func_array(array($this, $method), $parameters);
$this->_run_after_filters($method, $parameters);
68
Second, as an assocatve array, wth the abty to specfy whch actons
shoud be ncuded or excuded:
Fnay, we want to be abe to mx and match the two:
Wth ths n mnd, we' set up our base arrays (so that we don't have to do
superfuous checkng), and then defne our _run_before_filters()
method.
public $before_filters = array( 'authenticate_user',
'fetch_account' );
public $before_filters = array( 'authenticate_user' =>
array( 'only' => 'secure' ),
'fetch_account' => array(
'except' => 'select_account' ) );
public $before_filters = array( 'authenticate_user',
'fetch_account' => array(
'except' => 'select_account' ) );
public $before_filters = array();
public $after_filters = array();
protected function _run_before_filters($action, $parameters)
{
foreach ($this->before_filters as $filter)
{
69
Ths fufs requrement number one. We can defne a fat st of methods
that w be caed and pass the acton and parameters. Requrement
number two and three are a tte more trcky. We have to check to see f
the key ($details) s a strng. If t s, we know t's a fat ca. If t sn't, we
know that $filter s the method name and that we need to check
$details for the specfc array.
If $details s an array, we need to check to see f the acton s ether n the
only st or not n the except st. Fnay, f we can't fgure out what
$details s, we' gnore ths fter.
$this->$filter($action, $parameters);
}
}
foreach ($this->before_filters as $filter => $details)
{
if (is_string($details))
{
$this->$details($action, $parameters);
}
elseif (is_array($details))
{
if (in_array($action, @$details['only']) ||
!in_array($action, @$details['except']))
{
$this->$filter($action, $parameters);
}
}
}
70
Let's extract ths nto a separate method, _run_filters(). That way, we
don't have to dupcate any code n order to get ths to work for "after"
fters too.
Then we' make the change to our _remap() method:
And there we have t! A fuy functona, confgurabe way of DRYng up your
controer code and provdng pre- and post- acton methods.
protected _run_filters($what, $action, $parameters)
{
$what = $what . '_filters';
foreach ($this->$what as $filter => $details) {
$this->_run_filters('before', $method, $parameters);
call_user_func_array(array($this, $method), $parameters);
$this->_run_filters('after', $method, $parameters);
71
.n whch we ook at Representatona State Transfer (REST) and earn why ts a brant way
of desgnng your routes, and how to best mpement t n CodeIgnter. We take a good, n-
depth ook at the core of HTTP and the concept of resources on whch ts foundatons e. Then
we deveop a seres of repcabe routng patterns that make t easy to have RESTfu routng
wthn CodeIgnter appcatons.
?..KM ."& A#=:#55&4 K=#5#7#0
The vast, vast ma|orty of deveopers are ony aware (or ony care about)
two fundamenta HTTP methods; GET and POST. Consderng the fact that
most modern web browsers ony support these two, t sn't surprsng, but
there are a whoe range of other request methods that a modern
appcatons shoud be mpementng.
HTTP tsef s but upon a fundamenta concept: that of a resource. The
acronyms URL and URI themseves stand for Unform Resource Locator and
Unform Resource Identfer respectvey, and the HTTP spec s desgned
wth the ntenton of nteracton wth a resource.
73
So, to put ths a n ayman's terms, what are we defnng as a resource? A
resource is simply a thing. A user, a book, a post, a comment, a pro|ect, a
task, a og or an nvoce. And when we begn to thnk about budng our
appcaton around the concept of resources, we can reay start to see the
benefts.
Examnng the dfferent HTTP methods avaabe and how they work wth
resources can be a bg hep n understandng the core concepts of REST. Not
ony do we have our fathfu frends GET and POST, we're aso |oned by PUT,
DELETE, HEAD, OPTIONS, TRACE and CONNECT. The atter four aren't hepfu for
us rght now, so et's gnore them. I'm more nterested n PUT and DELETE.
KN.
The HTTP spec descrbes PUT as a method for "storng the encosed entty
under the supped Request-URI." It then goes on to state that "If the
Request-URI refers to an aready exstng resource, the encosed entty
SHOULD be consdered as a modfed verson of the one resdng on the
orgn server."
We can nfer from ths that PUT s a method for updatng exstng resources
on the server (or, ess frequenty, creatng a resource at a pre-determned
URI). In terms of REST, ths means that the PUT method shoud be used for
updatng our resource: changng the user's password, renamng a book tte,
or updatng a counter.
Accordng to the REST spec, the PUT and DELETE methods are idempotent,
meanng that they are totay repcabe. Mutpe dentca requests shoud
have the same effect as a snge request.
74
OFLF.F
Ths one s rather sef expanatory. From the HTTP spec, we get "The
DELETE method requests that the orgn server deete the resource
dentfed by the Request-URI." So, the DELETE method s for deetng your
resources. Smpe!
!"/5 /+#*5 PF. /4' K1D.-
We'd forgotten about our trusty frends, GET and POST. We, the HTTP spec
says: "The GET method means retreve whatever nformaton (n the form of
an entty) s dentfed by the Request-URI."
The name tsef, GET, tes us a that we need to know here. Retrevng
content, retrevng resources. Ths coud be a st of resources or an
ndvdua one. The spec aso aows us to process the nput n a way that
enabes us to retreve a more reevant st of these resources. Ths refers to
searchng, mts and fters.
As for POST, the HTTP spec says, "The POST method s used to request that
the orgn server accept the entty encosed n the request as a new
subordnate of the resource dentfed by the Request-URI n the Request-
Lne. POST s desgned to aow a unform method to cover the foowng
functons:
Annotaton of exstng resources;
Postng a message to a buetn board, newsgroup, mang st, or
smar group of artces;
Provdng a bock of data, such as the resut of submttng a form, to
a data-handng process;
Extendng a database through an append operaton."
Ths s where thngs start to get burry. In terms of the HTTP spec, POST s a
genera-purpose functon for submttng a resource to the server. Where
REST s concerned, POST s used to create new resources.
75
!"/5 '#&( 5"/5 ;&/4-
(That boy needs therapy?) Usng our knowedge of these HTTP methods, we
can start to bud a pattern of consstent, conventona, resource-drven
routes. Ths routng pattern w gve us a unform set of rues that we can
bud our appcaton around.
."& D5/4'/=' )#*5&(
The standard set of RESTfu routes are broken nto two categores: coecton
URIs, and member URIs. Coecton URIs are URIs that appy to the entre
coecton of resources. Member URIs are URIs that appy to a specfc
eement n that coecton. Submttng a certan HTTP request to ether one
of these URIs produces dfferent effects.
Let's assume our coecton s caed posts.
3#00&75>#4 )#*5&(
&''( *%+,-$ . /01 0%#23+
GET /posts Lst the coecton
POST /posts Create a new member n the coecton
PUT /posts Repace the entre coecton
DELETE /posts Deete the entre coecton
B&;+&= )#*5&(
&''( *%+,-$ . /01 0%#23+
GET /posts/:d Get detas about a specfc member
POST /posts/:d Create a new coecton based on that member
PUT /posts/:d Update the member
76
DELETE /posts/:d Deete the member
."& <;H#=5/45 14&(
I've sted the entre set of methods above, but n reaty, there are ony a
few that we need to focus on. There w be very few tmes when you' need
to repace or deete the entre coecton, and even fewer where you' need
to create a new coecton based on a member. Wth that n mnd, we ony
need to mpement a seect few methods:
GET /posts
POST /posts
GET /posts/:id
PUT /posts/:id
DELETE /posts/:id
3#'&<:4>5&=G2=>&4'0, D&;>G)FD.2*0 )#*5>4:
Whe t's mportant for the adherence of REST that we obey the HTTP
methods, understandng the process and then smpfyng t s certany a
step n the rght drecton. As most browsers don't support PUT or DELETE,
those methods can ony reay be used on APIs (or n the browser wth a bt
of work).
Whe there are ways to use PUT and DELETE, t makes much more sense to
adapt the pattern and mpement t n a CodeIgnter-frendy way. Wth a few
trcks, we can easy utze GET and POST effectvey and adhere to a
RESTfu pattern appropratey.
Addng sem-RESTfu routes s smpe. Open up config}routes.php. We'
add our GET /posts and POST /posts:
77
Snce our routes fe s |ust PHP, we can check to see what the request
method s, and thus mt our routng to a certan method. I'm prefxng the
controer methods wth the request method so that we can use reserved
PHP keywords ke new.
We can repcate that code for our pattern:
Ths w gve us the foowng URL-to-controer acton mappng:
$route['posts'] = ($_SERVER['REQUEST_METHOD'] == 'POST') ?
'posts/post_create' : 'posts/get_index';
$route['posts/new'] = 'posts/get_new';
$route['posts/(:any)'] = ($_SERVER['REQUEST_METHOD'] ==
'POST') ? 'posts/post_update/$1' : 'posts/get_show/$1';
$route['posts/(:any)/edit'] = 'posts/get_edit/$1';
if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
$route['posts/(:any)/confirm_delete'] = 'posts/
post_confirm_delete/$1';
$route['posts/(:any)/delete'] = 'posts/post_delete/$1';
}
GET /products -> get_index
GET /products/new -> get_new
GET /products/1 -> get_show(1)
GET /products/1/edit -> get_edit(1)
POST /products -> post_create
78
Ths doesn't obey the tradtona CodeIgnter expectaton of controller/
method/variable, and t w requre you to defne routes for each resource
you use. Ths change notwthstandng, t enabes us to defne a seres of
neary RESTfu routes and w cean up your routng consderaby.
Some modern browsers (at the tme of wrtng IE10 and the most recent
buds of Webkt and Frefox) are startng to support PUT and DELETE as
request methods n forms and A|AX2 as the popuarty of RESTfu nterfaces
has rsen. If you'd ke to use truy RESTfu routes you' need to be abe to
parse the data n PHP. You could do ths manuay wth PHP's php://input,
whch woud work, or, aternatvey, you coud use Ph Sturgeon's fantastc
REST brary
|3|
. Ph's brary works ke our MY_Controller: you nhert your
controers from REST_Controller and access parameters through $this-
>get(), $this->post(), $this->put() and $this->delete() methods.
Ph's brary s smpe to use, we tested, and very popuar. The
documentaton s somewhat ackng, but n order to counter ths Ph has
wrtten a rather comprehensve tutora on the tutora ste NetTuts
|4|
that
shoud hep you get started.
The other beneft to usng Ph's brary s that t supports a bunde of
features for workng on APIs, ncudng API keys, request oggng, HTTP
authentcaton and mutpe output formats. It's a rather sophstcated bt of
code, and I've been usng t snce ts eary ncarnatons to bud competey
POST /products/1 -> post_update(1)
POST /products/1/confirm_delete -> post_confirm_delete(1)
POST /products/1/delete -> post_delete(1)
3.https://gthub.com/phsturgeon/codegnter-restserver
4.http://net.tutspus.com/tutoras/php/workng-wth-restfu-servces-
n-codegnter-2/
79
RESTfu APIs wth CodeIgnter. We' dscuss Ph's brary and the whats and
whys of API desgn n Voume Two of The CodeIgnter Handbook.
80
D*;;/=,
In ths book we've deved nto the depths of CodeIgnter n order to mprove
our process and make codng robust web appcatons that much more fun.
We've ooked at the concept of Conventon over Confguraton, appyng t to
the fu MVC stack. We've set up a base mode cass wth whch we can
reduce the code across our modes, and we've but a fexbe tempatng
system to sodfy our controers.
We've ooked at ceanng up our vews wth presenters and partas and
we've taken a thorough ook at REST, wth whch we can have a set of
common routes for our resource-based appcatons. A n a, we've
managed to dramatcay reduce the amount of dupcaton across our
codebase by foowng the mantra of Don't Repeat Yoursef (DRY), and set
up a seres of conventons that you can now appy to any pro|ect, on any
framework, n any anguage.
I hope you've en|oyed readng Voume One n The CodeIgnter Handbook.
Part 4 was ntended to whet your appette for REST-Voume Two w focus
on creatng we-desgned and exceedngy peasant APIs usng CodeIgnter.
We' ook at the dfferences between RPC and REST, we' desgn our entre
data mode over the concept of resources and we' ook at some of the
esser-known features of the HTTP protoco.
If you spot any code errors, we'd sncerey apprecate t f you coud submt
t to our errata at http://codegnterhandbook.com/errata/. If you have any
questons or suggestons how to mprove ths book, pease don't hestate to
contact me at |ame@|amerumbeow.net or on Twtter, at @|amerumbeow.
It'd aso be great to hear about the code you've deveoped wth the
technques you've earned n ths book. I'm ookng forward to seeng what
you a come up wth!
81

You might also like