You are on page 1of 91

kefactor|og Legacy

Appl|cat|oos Us|og
Chr|s Hartjes
CakePHP
"Hartjes' book takes an in-depth look at pitfalls common to many legacy
web applications, particularly those written in PHP."
- CakePHP lead developer Nate Abele
Leato mete abeot the beek at £asyPHPWebs|tes.cem|
Refactoring Legacy Applications using CakePHP
by Chris Hartjes
Preface
My thanks go out to various members oI the CakePHP community Ior providing me with help and advice while
I was writing this book. I apologize to those who I omit here but there are three I wish to single out:
* Nate Abele Ior always making Iun oI me, but always answering my questions too
* Garrett Woodworth Ior patiently explaining the inner workings oI CakePHP to me
* Joel Parras Ior his technical editing work
I also wish to thank my wiIe Claire Ior putting up with the seemingly-endless hours I spend on my computer
working on various projects. It is only because oI her support oI my eIIorts that I am able to do things like this.
What this book is NOT is an introduction to programming with CakePHP. There are already books
that do a good job oI giving you the basics oI using CakePHP. II this book is going to help you at all,
you really need to have the Iollowing qualities:
Understand PHP beyond <?php echo "ue11o Wor1d" ?>
Understand CakePHP beyond just using CakePHP`s built-in scaIIolding
Actually understand the application you`re trying to reIactor
Without those three qualities, I can almost predict that your reIactor will be a Iailure.
This book was produced with the help oI the Iollowing tools:
vim (http://www.vim.org)
reStructured Text (http://docutils.sourceIorge.net/rst.html)
rst2pdI (http://code.google.com/p/rst2pdI)
PHP (http://www.php.net)
Pages (http://www.apple.com/iwork/pages/)
CakePHP (http://www.cakephp.org)
All code examples were meant to be run under PHP 5, with development happening using PHP 5.2.8.
All CakePHP examples were written and tested using the 'bleeding-edge¨ version oI CakePHP 1.2
Irom the SVN repository at https://svn.cakephp.org/repo/branches/1.2.x.x. It was at revision 8052
at the time oI publishing. While every eIIort has been made to ensure that the code samples work as
advertised and are bug Iree, I oIIer no warranty in regards to their usage in your own code. Use the
samples at your own risk.
CakePHP is a registered trademark oI the Cake SoItware Foundation (http://cakeIoundation.org).
To contact the author, visit the web site Ior this book at http://www.littlehart.net/book or visit the
author`s blog at http://www.littlehart.net/atthekeyboard. Feedback and questions are always welcome
and while I read every email and comments that comes in, time constraints might prevent me Irom
responding.
All content, unless otherwise noted, is (c)2009 Chris Hartjes. In lieu oI adding Digital Rights Man-
agement to this document I ask that people respect copyright laws and not distribute copies oI this
book without my express written permission.
Table of Contents
Introduction - Frameworks Get No Respect 1
Legacy Applications 1
Chapter 1 - Understanding Models 3
Turning Tables Into Models 4
Model Associations 5
No Auto-incrementing Primary Key 7
Updating Denormalized Data 8
Removing Cached Data Upon Record Deletion 9
Making Your Models Behave: Behaviors 10
Request Routing 10
Chapter 2 - Creating Controllers 12
Determining Controller Names 12
Linking models to controllers 15
Components for your controllers 15
Chapter 3 - Layouts 16
Setting Character Sets 16
Setting Page Titles 17
1avascript Usage 18
Actual Content 18
CSS 18
META content 19
Chapter 4 - Separating Business and Presentation Logic 20
Helpers for your views 20
Chapter 5 -- Untwhirling The Spaghetti 22
Fat Models, Skinny Controllers 22
Hopelessly Twisted 22
Easy Drop-Down Lists 28
Model Association Tweaking 29
Multiple Select Drop-Downs 30
Chapter 6 -- Refactoring Multiple Record Editing 37
Simpliüed Views 43
Filtering and Updating Records 46
Only Updating The Data You Need To 48
Chapter 7 -- Easy Batch Record Manipulation 49
Using N.üeld Notation In Your Forms 52
Chapter 8 -- Easy Batch Record Addition 55
Easy Arrays In Forms 57
Saving and Validating Data 58
Chapter 9 -- Displaying Data 64
Simple Data Collection For Display
Grouping Display Output By Criteria 65
Business Logic Where It Belongs 70
Chapter 10 -- Playing With Date Ranges 72
Real World Date Forms 77
Date Form Automagic 79
Chapter 11 -- Wrapping It All Up 82
1 ReIactoring Legacy Applications Using CakePHP
Introduction - Frameworks Get No Respect
Web Irameworks just don't get much love in the PHP world these days. It seems that in any given
week I can fnd the Iollowing inIormation on the internet:
1. An announcement about the launch oI a new PHP web application Iramework
2. A set oI benchmarks using the ever-popular "Hello World" method showing that Framework X is awe-
some and every other Iramework out there sucks
While it's admirable that so much eIIort is being put into the creation, maintenance and use oI Irame-
works, I think a lot oI people are missing the point oI what a Iramework is supposed to do.
In my opinion, the job oI a Iramework should be to give your application a structure that is enIorced
via a set oI rules established by the Iramework itselI, and at the same time allow you to rapidly build
something.
I think most Irameworks do an awesome job in regards to the frst part, but oIten Iail to provide the
tools necessary to make the second part happen.
In this book, I'm going to Iocus on the ability oI a Iramework to provide your legacy application with
the structure it needs to make it more viable going Iorward.
Legacy Applications
Any developer who has ever gone back to an application that they wrote in the past has worked with
legacy code. Although it`s used by people as a derogatory label applied to code that you don`t like,
legacy code is simply code that you wrote yesterday that you need to fx today. When put into that
context, it`s not so scary any more. Any developer worth his skills is asked on a daily basis to go back
into some 'old and cruIty code¨ and fx it, or even rewrite it to meet a variety oI new goals or situa-
tions.
Sometimes it is because a new version oI the language has come out and is ready Ior production use,
oIten allowing the developer to take advantage oI new Ieatures or just increased speed. But I would
say it is rare that old code is rewritten or reIactored Ior this reason.
Sometimes it`s also because the developer is scratching an itch and wants to rewrite something using
a new language or Iramework because they`ve gotten caught up in the buzz and hype surrounding that
language. Every developer is guilty oI that at one point or another, but that is usually the worst reason
to reIactor something.
Finally, a reIactor can also occur when a developer discovers new techniques or tools that will allow
their application to perIorm better. These include things like rewriting a PHP library as an extension
in C, or rewriting a Ruby daemon as a Java application (this has happened where I`ve worked, go
fgure).
ReIactoring usually happens because the code in question has started to become a maintenance
nightmare. Code so brittle that you Iear making changes to libraries because a change in one place
could impact 100 diIIerent places. Code so tangled up that it`s impossible to create automated tests to
2 ReIactoring Legacy Applications Using CakePHP
veriIy that things are actually working. In short, the type oI code that has grown organically without
any structure. Hey, sometimes this sort oI thing Just Happens. But we have a tool that help us give our
legacy application the structure it needs going Iorward. That tool is a web application Iramework.
I`ve grown to appreciate just how fexible CakePHP really is once you start looking below the sur-
Iace. It comes with all sorts oI tools that are indispensable Ior when you are reIactoring an applica-
tion that has gone the 'spaghetti PHP¨ way oI mixing up business logic, request handling and display
logic together. Because that is what this is really about: giving your legacy application a structure
that will allow it to grow while being easier to maintain.
This book is not just someone talking about how awesome CakePHP is. There are enough books and
blogs out there that do that already. Instead, you will be Iollowing along as I take what can truly be
called a legacy app and rewriting it to use CakePHP, showing you much that the Iramework has to
oIIer.
I`ve been playing in simulation baseball leagues Ior nearly 20 years, and I`ve written web applications
to help run those leagues. One oI the tools I`ve created is called WebReg, short Ior Web Registrar. It`s
used to keep track oI rosters and trades in the league. It`s the one piece oI code I have that has been
online the longest, since 2005 at it`s current location and at least a year beIore that on the original
server. It`s used every week throughout the season.
It is, however, a mess. It`s pure spaghetti PHP - raw SQL calls, iI-then-else request responses and
HTML all mixed up together in one. An application that is ripe Ior a rewrite with CakePHP so I can
start to implement some oI the Ieatures that the current registrar has requested.
I`m hoping that by sharing my experiences with a real-world example oI PHP application that needed
the reIactoring and structure that a Iramework gives you, you will begin to truly understand and (more
importantly) appreciate what CakePHP can do Ior you.
Also, this book is not an exhaustive example oI how to use CakePHP in the 'real world¨. It is an
example oI porting over a Iairly simple application, but the principles I use in porting this over are
applicable in almost any other situation.
3 ReIactoring Legacy Applications Using CakePHP
Chapter 1 - Understanding Models
WebReg is very much a database-driven application with a very simple web-based Iront end, allowing
our registrar to manipulate rosters Ior teams in the simulation baseball league. It's natural that I would
start the process oI rewriting our legacy application by looking at how we will represent the tables
in the database using CakePHP models. This application uses PostgreSQL as the back-end, so some
things might be a little diIIerent iI you are used to dealing with MySQL. Lucky Ior us, we don't have
to worry about that because CakePHP abstracts all those details away.
Since this application shares a database and tables with another application written in CakePHP 1.2,
we are lucky enough to already have our tables, Ior the most part, Iollowing the convention that
CakePHP uses:
· auto-incremented primary keys, in this case oI the 'integer' data type
· table names that are plural, as the CakePHP convention is to give the Model object representing
the table the singular name
WebReg has three tables that we need to worry about:
Our 'Iranchises' table, which keeps track oI teams in the league. This table does not have an auto-
incremented integer Ior the primary key, mainly because the number oI Iranchises in the league will
not be exceeding 24 any time soon.
4 ReIactoring Legacy Applications Using CakePHP
Next, we have a table that gives us one oI the frst "code smells" Ior this application: a table called
'teams' that contained inIormation about players. By 'code smells¨ I mean something that when you
look at it in the context oI the application, it doesn`t seem right. Sometimes these types oI things are
easily fxable. Sometimes they are not. In this case, CakePHP itselI gives us a graceIul way to get
around it.
This table is supposed to be tracking players, not teams. I`m trying to remember why I called it
teams` in the frst place. Since I`m drawing a blank, I`ll have to assume that it was Ior a good reason
at the time. This table should`ve been called players`, but we can`t go back and fx it now.
In this table we are tracking the name oI the player in the dice-and-cards game that the league uses,
along with what Iranchise the player belongs to in the league. The 'comments' feld contains inIo
about when the player was draIted or traded Ior, basically a mini-transaction summary. We determine
whether a player is active or inactive through the 'status' feld, and we diIIerentiate between whether
someone is a batter, pitcher or draIt pick (teams can trade their picks) using the 'item¸type' feld.
Finally we have our 'transaction¸log' table, where keep track oI all transactions on a per-team basis.
Notice the primary key is called 'trans¸id', not 'id' as you would expect Irom the CakePHP coding
standards.
Turning Tables Into Models
The next step is Ior us to defne CakePHP models that will allow us to get data Irom these tables. The
frst thing that we should tackle is getting rid oI that "code smell" I mentioned beIore. Instead oI using
Team as the model name, let's instead call it what it should be called, Player.
c1ass Þ1ayer extends ^ppMode1 {
pub1ìc $use1ab1e = 'teams',
pub1ìc $be1ongs1o = array{
'Iranchìse' => array{
'c1assuame' => 'Iranchìse',
'1oreìgnkey' => 'ìb1_team'
)
),
)
5 ReIactoring Legacy Applications Using CakePHP
What is actually going on in this Model? It's actually quite simple.
pub1ìc $use1ab1e = 'teams',
Since we are not Iollowing the CakePHP convention oI having the name oI the model being the singular
version oI the table name, we have to defne what table we are actually using.
pub1ìc $be1ongs1o = array{
'Iranchìse' => array{
'c1assuame' => 'Iranchìse',
'1oreìgnkey' => 'ìb1_team'
)
),
Model Associations
Next, we are defning a relationship between two models. CakePHP supports 4 types oI
relationships:
Relationship Association Type
one to one hasOne
one to many hasMany
many to one belongsTo
many to many hasAndBelongsToMany
The use oI these associations allows you to use CakePHP's associated data mapping, where a request
to fnd data in one model will return all associated data. So, in this case we are establishing that a
Player belongs to a Franchise. The other inIormation in the association defnition is important as well.
I tend to only defne the parameters that I need, but you can Ieel Iree to set other parameters to their
deIaults iI so desired. Later in this chapter I talk more about those parameters, but Ior now we only
need these ones.
· We give the association itselI a name, which will show up in the associated array that gets returned
· We tell the model what class to use Ior the association
· We tell the model what the name oI the Ioreign key to the other table is
It's important to note here that you do NOT have to have integer-only Ioreign keys. As long as it's a
valid column in your table, CakePHP can use it in the queries it generates just fne.
6 ReIactoring Legacy Applications Using CakePHP
c1ass Iranchìse extends ^ppMode1 {
pub1ìc $prìmarykey = 'nìckname',
pub1ìc $hasMany = array{
'Þ1ayer' => array{
'c1assuame' => 'Þ1ayer',
'1oreìgnkey' => 'ìb1_team'
),
'1ransactìonlog' => array{'c1assuame' => '1ransactìonlog',
'1oreìgnkey' => 'ìb1_team'
)
),
)
The code in the Franchise model is very similar, except you can see how we've added in another asso-
ciation. A Iranchise has many entries in the transaction log, so it makes sense to defne the association
between them.
c1ass 1ransactìonlog extends ^ppMode1 {
pub1ìc $use1ab1e = 'transactìon_1og',
pub1ìc $prìmarykey = 'trans_ìd',
pub1ìc $be1ongs1o = array{
'Iranchìse' => array{
'c1assuame' => 'Iranchìse',
'1oreìgnkey' => 'ìb1_team',
)
),
)
Previously I mentioned "code smells", the little signs that perhaps you've made an architectural mis-
take in your code or your database schema. In this case we have a primary key that does not Iollow
the CakePHP naming convention oI just being 'id'. That's easy enough to fx.
7 ReIactoring Legacy Applications Using CakePHP
This simply tells CakePHP to use the column 'trans¸id' as the primary key Ior any queries.
pub1ìc $be1ongs1o = array{
'Iranchìse' => array{
'c1assuame' => 'Iranchìse',
'1oreìgnkey' => 'ìb1_team'
)
),
Much like our hasMany association, this just creates the reverse association Ior these tables, where
transaction log entries belong to a specifc team.
Now that we have our three models defned, let's explore some common "what iI?" scenarios you
might encounter when porting your legacy application over.
No Auto-incrementing Primary Key
It's entirely conceivable that you could have a database structure where you have a primary key that
has not been defned. This isn't always possible, but we have a way in CakePHP to handle this issue.
CakePHP has several callback methods Ior it's models that allow you to add in some logic just beIore
or just aIter you've done one oI the more standard Iunctions. In this case, the solution Ior code to add
logic to generate a primary key would be beIoreSave() .
,, Samp1e be1oreSave{) 1ogìc
c1ass Ioo extends ^ppMode1{) {
...
,, prìmary key ìs a tìmestamp when savìng a new record
pub1ìc be1oreSave{) {
ì1 {empty{$thìs->dataj'ìd'¸)) {
$thìs->dataj'ìd'¸ = tìme{),
)
return true,
)
)
,, Lxamp1e o1 how to ca11 ìt, assumìng you have data ìn $data
$thìs->Ioo->set{$data),
ì1 {$thìs->Ioo->save{)) {
8 ReIactoring Legacy Applications Using CakePHP
$thìs->Sessìon->setI1ash{'Created new record'),
)
Let's take a look at what's going on here:
pub1ìc be1oreSave{) {
ì1 {empty{$thìs->dataj'ìd'¸)) {
$thìs->dataj'ìd'¸ = tìme{),
)
return true,
)
The beIoreSave() method will be accessed any time you execute the save() met in your model, so it
is the ideal place to put any logic that you need executed beIore you save the record. So, the logic in
there says "iI we don't already have a value in our primary key column in our data set, assign it the
desired value".
Then, when the model goes to save() the record, it now has a value Ior the primary key and CakePHP
can do it's internal magic where it fgures out iI it's updating an existing record or creating a new one.
Also, be sure to have your beIoreSave() method return true or else your save attempt will Iail.
Updating Denormalized Data
At one time I worked Ior a company that was working on an adult dating web site, which is really
nothing more than a search engine with a very nice Iront end (ugh, what a bad pun). At one point,
to try and make various searches work more eIfciently, it was decided to denormalize our data Ior
searching purposes.
We ended up with multiple tables that contained data Irom our main user database, but not always the
same data and it wasn't updated with the same Irequency.
How would you implement something like this in CakePHP? One method would be to create an aIter-
Save() method in the User model that then updates all the search tables.
,, ^ssume that the data we ¸ust saved are ìn $thìs->data
c1ass user extends ^ppMode1 {
...
pub1ìc a1terSave{$created) {
,*
* 0n1y pass through the ìd, username, gender, age, and cìty fe1ds
* to the quìcksearch tab1e
9 ReIactoring Legacy Applications Using CakePHP
*,
$quìcksearch_data = array{
'username' => $thìs->dataj'user'¸j'username'¸,
'gender' => $thìs->dataj'user'¸jgender¸,
'age' => $thìs->dataj'user'¸j'age'¸,
'cìty' => $thìs->dataj'user'¸j'cìty'¸
),
$thìs->quìckSearch->save{$quìcksearch_data),
)
)
The aIterSave() method accepts a single parameter, a boolean feld that tells you whether a new
record has been created or not. Obviously, this is not being used in our example. What we're doing
here is quite simple, really. We're grabbing a subset oI the data that we saved in our User model and
then passing that set to our QuickSearch model Ior it to be updated. In this example, 'username' is the
primary key Ior our quick search table. Unlike the beIoreSave() method, we do not have to return any
value.
Removing Cached Data Upon Record Deletion
While caching is a great idea Ior just about application any application where you are expecting to
have more than one concurrent user, managing the cache is key to proper use. Whenever you delete
data Irom one oI your database tables, iI you've cached that inIormation anywhere you will want to
remove it Irom the cache. In CakePHP an ideal place Ior the logic to handle this would be in the be-
IoreDelete() and aIterDelete() callbacks.
In this example, let's assume that we have written our own caching library, called crudcache, that we
have placed in your APP/vendors directory. It supports CRUD (Create, Read, Update, Delete) meth-
ods Ior talking to our cache.
c1ass user extends ^ppMode1 {
...
,, ^ssume that we have set $thìs->user_ìd prìor to de1etìng
pub1ìc method a1ter0e1ete{) {
^pp::ìmport{'vendor', 'crudcache'),
$crudcache = C1ass8egìstry::ìnìt{array{'c1ass' => 'Crudcache')),
$cache_key = mdS{'quìcksearch_' . $thìs->user_ìd),
ì1 {!$crudcache->de1ete{$cache_key)) {
10 ReIactoring Legacy Applications Using CakePHP
$crudcache->1og{
'unab1e to remove quìcksearch ìn1ormatìon 1or user'
. $thìs->user_ìd),
)
)
)
,, Lxamp1e o1 usìng thìs, assumìng we have the ìd o1 the record
$thìs->user->user_ìd = $user_ìd,
$thìs->user->de1ete{$user_ìd),
Your aIterDelete() callback does not need to return anything, so any inIormation about what's hap-
pened needs to be communicated through a diIIerent channel. In this simple example we use a log-
ging Iacility built into the CrudCache library itselI.
II you really wanted to do things the Cake-approved way you could create your own cache engine,
which is an exercise I leave to more motivated users as current documentation on that is sparse. II
you do go that route, your Model::delete and Model::del methods will call Model::¸clearCache Ior
you. Go with whatever you Ieel comIortable doing.
Making Your Models Behave: Behaviors
One oI the interesting Ieatures oI CakePHP is that you can create code to improve the Iunctionality oI
your models. These bits oI code are called behaviors, and they can give you very powerIul Iunctional-
ity. For WebReg, we will be dealing with behaviors that are already part oI the core. One oI these is
the Containable behavior that allows you to dynamically flter what data gets returned via Model::fnd
calls.
Other behaviors that I have come across include things like taking an image that you have just created
a record Ior, and automatically create thumbnails oI diIIerent sizes, and adding in geocoding Iunction-
ality to your application.
I think that any time you have some business logic that needs to do something at the same time you
are saving inIormation in your database, a behavior is the way to go. This project does not require any
custom behaviors, but it's defnitely something to investigate iI you have some Iunctionality that does
not appear to properly ft anywhere.
Request Routing
CakePHP has very powerIul routing capabilities that are also surprisingly easy to use. OIten you will
be Iaced with URL`s in a legacy application that you cannot simply rename. For example, let`s take
a look at a URL Irom a project I worked on where I was porting over a web service and we had to
preserve the URL`s.
One sample URL was /en/nba/games/YYYYMMDDXZ.xml. To remain fexible, I decided to imple-
11 ReIactoring Legacy Applications Using CakePHP
ment a games` controller, where we would pass in the league (nba` in this case) and then the rest oI
the inIormation. So, how would we do this?
8outer::connect{',en,:1eague,games,:event_key', array{'contro11er' => 'games', 'actìon' =>
'ìndex'), array{'pass' => array{'1eague', 'event_key'))),
So what is going on here? Okay, we can put 'placeholders¨ or 'tokens¨ (call them whatever you
want) into a route. In this case I want to pass the league and the event key to my action in my con-
troller Ior Iurther processing. It`s really that simple and it reads like plain English, iI you ask me.
One mistake that is sometimes made is the desire oI some developers to create their controllers and
actions to give you pretty URL`s in advance. Sometimes this is easy, sometimes is not. Given how
fexible CakePHP`s routing system is, I think it`s better to Iocus on Iunctionality frst and worry about
the URL`s second. You might even fnd that you have one controller powering multiple seemingly-
unrelated URL`s.

12 ReIactoring Legacy Applications Using CakePHP
Chapter 2 - Creating Controllers
Determining Controller Names
Now that we've defned our models, the next step is to defne the controllers that we will need. In
CakePHP, the convention is that your controller maps to a noun (in most cases, a model) and your
controller methods are verbs that signiIy an action you're trying to do. However, in many legacy
applications this type oI easy mapping is not possible, so you oIten have to struggle to match the
conventions.
Once I did a blog post about CakePHP myths, Iocusing on trying to dispel some oI the more common
"urban legends" as it were about the Iramework. One oI the bigger ones was the idea that you can
only have on Model associated with one Controller. This is, oI course, Ialse. Once you realize that,
you understand that you are actually Iree to name your controllers and their associated actions what-
ever you want.
OIten you are not lucky enough that your existing URL's will map easily to the noun/verb pattern.
That leaves you with taking a look at the existing URL's and doing a little educated guessing. Let's try
that here.

URL controller/action Functionality
/ /page/home Main Menu
/roster¸management.php /rosters Roster management options
/make¸a¸trade.php /rosters/trade Trade a player
/modiIy¸roster.php /rosters/edit Edit existing roster
/Iree¸agents.php /Iree¸agents Free agent management options
/Iree¸agents.php?task÷sign /Iree¸agents/sign Sign a Iree agent
/Iree¸agents.php?task÷add /player/add Add a new player
/Iree¸agents.php?task÷view /Iree¸agents/view View existing Iree agents
So Iar we've done a good job oI mapping the old URL's to potential controller / action pairs. But per-
haps we can do better?
Free agents are simply a type oI player, so maybe we could rewrite those actions dealing with Iree
agents to Iall under the players controller. Now, we have the rosters controller to deal with actions
involving rosters and the players controller dealing with players. Imagine that.
Also, I think it makes more sense to put all the actions dealing with trades under their own controller,
as the make¸a¸trade.php script is it's own "Iront controller" (as you'll see later).
Let's not Iorget that we have some internal actions to account Ior. When making a trade we have a
screen where we not only pick what teams are involved in a trade, but then you pick the players. So
we need to tweak things. Here's the fnal mapping aIter digging around.
13 ReIactoring Legacy Applications Using CakePHP
URL controller/action Functionality
/ /page/home Main Menu
/roster¸management.php /rosters Roster Management
/make¸a¸trade.php /trade Make a trade, pick teams
/make¸a¸trade.php (aIter post) /trade/choose¸players Make a trade, pick players
/modiIy¸roster.php /rosters/choose ModiIy rosters, pick team
/modiIy¸roster.php (aIter post) /rosters/edit ModiIy rosters, edit players
/Iree¸agents.php /Iree¸agents Manage Iree agents
/Iree¸agents.php?task÷sign /Iree¸agents/sign ModiIy Iree agents
/draIt¸player.php /players/draIt DraIt Iree agents
/Iree¸agents.php?task÷add /players/add Manage Iree agents, add player
/Iree¸agents.php?task÷view /Iree¸agents/edit View Iree agents, edit
/view¸transactions.php /transactions View transactions, pick dates
/view¸transactions.php (aIter post) /transactions/view View transactions, show
/view¸rosters.php /rosters/view View rosters
/view¸Iree¸agents.php /Iree¸agents/view View Iree agents
There, doesn't that look better now? We now have three controllers that better map to the actual ac-
tions being done, with a manageable number oI methods in each controller. I made the non-intuitive
decision to make the home page Ior the application a static page. The page will never change so there
really is no reason to make it dynamic.
So let's set about creating the skeletons Ior our new controllers.
c1ass 8ostersContro11er extends ^ppContro11er {
pub1ìc 1unctìon ìndex{) {
)
pub1ìc 1unctìon choose{) {
)
pub1ìc 1unctìon edìt{) {
)
pub1ìc 1unctìon vìew{) {
)
)
14 ReIactoring Legacy Applications Using CakePHP
c1ass Þ1ayersContro11er extends ^ppContro11er {
pub1ìc 1unctìon add{) {
)
pub1ìc 1unctìon dra1t{) {
)
)
c1ass 1radeContro11er extends ^ppContro11er {
pub1ìc 1unctìon ìndex{) {
)
pub1ìc 1unctìon choose_p1ayers{) {
)
)
c1ass Iree^gentsContro11er extends ^ppContro11er {
pub1ìc 1unctìon ìndex{) {
)
pub1ìc 1unctìon sìgn{) {
)
pub1ìc 1unctìon edìt{) {
)
pub1ìc 1unctìon vìew{) {
)
)
c1ass 1ransactìonsContro11er extends ^ppContro11er {
pub1ìc 1unctìon ìndex{) {
)
pub1ìc 1unctìon vìew{) {
)
)
15 ReIactoring Legacy Applications Using CakePHP
Linking models to controllers
Since we are only dealing with three models, and a Iairly small data set (no more than about 1300
entries in total) we are not taking much oI a memory hit to ask CakePHP to hold those three mod-
els in memory Ior us. In some instances, you are bound to have 10 to 12 models, so oIten it's better
to simply leave the $uses array empty and then instantiate instances oI the models you need using
App::import():
c1ass Þ1ayersContro11er extends ^ppContro11er {
pub1ìc 1unctìon dra1t{) {
$p1ayer = C1ass8egìstry::ìnìt{'Þ1ayer'),
...
)
)
II you wish to Iollow the path oI least-use-oI-resources, I recommend using the ClassRegistry method
but you really need to experiment with your application to see iI it is worth it Ior you.
II you look at the two controllers you will see that we are passing some variables into some oI the
action methods. This is the usual CakePHP convention when editing or viewing data in your standard
CRUD application, so there's no need to reinvent the wheel. I've said it beIore, and I will say it again:
sticking to the CakePHP conventions as much as possible reduces the amount oI work you have to do.
Isn't that what we're really trying to accomplish here?
Components for your controllers
Much like you have behaviors to add and extend Iunctionality Ior your models, components allow
you to add and extend Iunctionality in your controller. The most common component I have used is
the Auth component, which allows you to add authorization code to your application. In Iact, I've
written quite a Iew tutorials dealing with the Auth component. For WebReg, I'm not so sure I need to
use it. Right now the application is simply protected by Apache http-auth. For most applications, the
collection oI core components is more than enough.
16 ReIactoring Legacy Applications Using CakePHP
Chapter 3 - Layouts
CakePHP supports the concept oI having layouts, meaning a skeleton Ior your HTML output and then
it will grab the output generated by the views and put it in the proper place. CakePHP comes with
a pretty decent deIault layout Ior HTML, so we'll take that and simply modiIy it Ior our needs. We
would be saving this fle in APP/views/layouts/deIault.ctp
<!00C1YÞL htm1 Þu8l1C "-,,W2C,,010 xu1Ml 1.0 1ransìtìona1,,Lu" "http:,,www.w2.org,18,xht-
m11,010,xhtm11-transìtìona1.dtd">
<htm1 xm1ns="http:,,www.w2.org,1999,xhtm1">
<head>
<?= $htm1->charset{) ?>
<tìt1e><?= $tìt1e_1or_1ayout ?><,tìt1e>
<?= $scrìpts_1or_1ayout ?>
<,head>
<body>
<dìv ìd="contaìner">
<dìv ìd="content">
<?= $content_1or_1ayout ?>
<,dìv>
<,dìv>
<,body>
<,htm1>
Being lazy I've started using PHP short tags in my applications, hence the use oI ·?÷ ... ?~ in the tem-
plates. Less typing, just as clear. Believe me, when you work on converting over 100¹ views on a site
you will appreciate the keystrokes saved.
Setting Character Sets
So what exactly is going on here? First oII, we are setting the character set Ior the layout, which
CakePHP deIaults to UTF-8. You can set that to other values by passing it as a parameter:
<?= $htm1->charset{'1S0-88S9-1') ?>
I can't imagine too oIten that you'd be setting that value dynamically in your controllers, so go into
your layout fles and just set it, and Iorget it.
17 ReIactoring Legacy Applications Using CakePHP
Setting Page Titles
$title¸Ior¸layout is a placeholder Ior what you would like to place in your ·title~ tag. You can set the
value you want to show up here in a couple oI diIIerent ways.
You can give a particular controller / action pair a name when you create a route. For WebReg, we
have set this value to 'home' in APP/confg/routes.php . See how the display() method in the standard
Pages controller does it?
pub1ìc 1unctìon dìsp1ay{) {
$path = 1unc_get_args{),
$count = count{$path),
ì1 {!$count) {
$thìs->redìrect{','),
)
$page = $subpage = $tìt1e = nu11,
ì1 {!empty{$pathj0¸)) {
$page = $pathj0¸,
)
ì1 {!empty{$pathj1¸)) {
$subpage = $pathj1¸,
)
ì1 {!empty{$pathj$count - 1¸)) {
$tìt1e = 1nfector::humanìze{$pathj$count - 1¸),
)
,, Ior the home page we have to set the tìt1e
ì1 {$pathj$count - 1¸ == 'home') {
$tìt1e = 'Web8eg uome',
)
$thìs->set{compact{'page', 'subpage', 'tìt1e')),
$thìs->render{¸oìn{',', $path)),
)
18 ReIactoring Legacy Applications Using CakePHP
The most common place to set this is in the controllers on a per-action basis.
c1ass 8ostersContro11er extends ^ppContro11er {
...
pub1ìc 1unctìon ìndex{) {
$thìs->page1ìt1e = '8osters Maìn Þage',
)
)
1avascript Usage
Now, I imagine most applications will be using some sort oI Javascript. In the "Web 2.0" environ-
ment it's pretty hard to avoid using Javascript Ior AJAX and other Iunctionality (rounded corners
immediately springs to mind) so CakePHP provides you with an easy way to insert calls to load your
Javascript into your HTML output up in the ·head~...·/head~ block. When you use the Javascript
helper, set the "inline" parameter to be FALSE to tell the helper you don't want the Javascript link to
be inline.
,, thìs code ìs ìnsìde a vìew
<?= $¸avascrìpt->1ìnk{'1oo', 1a1se) ?>
<!-- you shou1d see the 1o11owìng ìn the header
<1ìnk re1="¸avascrìpt" type="text,¸avascrìpt" hre1=",¸s,1oo.¸s"
-->
Actual Content
Finally, we have the $content¸Ior¸layout variable, which is where CakePHP will place the HTML
output that your controller / action pairs generate. Not too many options Ior you here, but without that
$content¸Ior¸layout your layouts will, well, not display any content.
Now that we've looked at the layout we'll be using Ior WebReg, there are some other things we would
probably want to use when reIactoring:
CSS
CakePHP makes it super-easy to add CSS fles to your layouts.
,, 1n your 1ayout
<?= $htm1->css{'maìn') ?>
<!-- shou1d output
<1ìnk re1="sty1esheet" type="text,css" hre1=",css,maìn.css" ,>
-->
19 ReIactoring Legacy Applications Using CakePHP
You can also include multiple CSS fles just as easily
,, 1n your 1ayout
<?= $htm1->css{array{'1oo', 'bar', 'baz')) ?>
<!-- shou1d output
<1ìnk re1="sty1esheet" type="text,css" hre1=",css,1oo.css" ,>
<1ìnk re1="sty1esheet" type="text,css" hre1=",css,bar.css" ,>
<1ìnk re1="sty1esheet" type="text,css" hre1=",css,baz.css" ,>
-->
META content
Keywords, reIreshes, links to RSS Ieeds, those are some oI the things you might also need to add in
when reIactoring. CakePHP has $html-~meta to help you out.
,, 1n your 1ayout
<?= $htm1->meta{'1avìcon.ìco', ',1avìcon.ìco', array{'type' => 'ìcon')) ?>
<?= $htm1->meta{'Þosts', ',posts,ìndex.rss', array{'type' => 'rss')) ?>
<?= $htm1->meta{'keywords', 'Web8eg, 18l') ?>
So, now that we've established our layout it's time to move onto the most diIfcult part oI this, separat-
ing the business logic Irom the presentation logic in our legacy application.
20 ReIactoring Legacy Applications Using CakePHP
Chapter 4 - Separating Business and Presentation Logic
The #1 reason Ior wanting to move your legacy application over to a Iramework like CakePHP is be-
cause the existing application is a twisted mess oI PHP code mixed in with HTML and CSS. WebReg
is no exception. Like most older applications, the business logic is mixed together with the presenta-
tion logic. CakePHP enIorces this separation quite well, and provides you with lots oI tools to make it
easier Ior you to accomplish this.
Helpers for your views
Much like Behaviors Ior Models and Components Ior Controllers, CakePHP provides Helpers as
code that provides Iunctionality to your views. CakePHP by deIault provides two helpers, the HTML
helper and the Form helper. To add other helpers, it's as simple as defning the $helpers variable in the
controller itselI. The list includes Session, Ajax, Number, Paginator, Rss, Text, Time and Cache. I`m
probably leaving out a Iew, but you get my point. Helpers will save you lots oI time and provide you
with a way to encapsulate code that you will Irequently use in your views.
One thing to remember is that iI you defne the $helpers array, you must speciIy all the helpers you
wish to use. I bet that trips people up all the time.
Here's the existing page:
<h2 a1ìgn=center>Web8eg -- 8oster Management<,h2>
<dìv a1ìgn=center>
<a hre1=make_a_trade.php>Make ^ 1rade<,a><br>
<a hre1=modì1y_roster.php>Modì1y ^n Lxìstìng 8oster<,a><br>
<a hre1=1ree_agents.php>Manage Iree ^gents<,a><br>
<br>
<a hre1=commìt.php><b>C0MM11 18^uS^C110uS<,a><br>
<br>
1mport 8oster Iì1e<br>
1mport Iree ^gent Iì1e<br>
<hr>
<a hre1="ìndex.php">8eturn 1o Web8eg uome<,a>
<,dìv>
Here's how we would clean this up to use the various CakePHP Iunctions.
21 ReIactoring Legacy Applications Using CakePHP
<h2 a1ìgn=center>Web8eg -- 8oster Management<,h2>
<dìv a1ìgn=center>
<?= $htm1->1ìnk{'Make ^ 1rade', ',trade') ?><br ,>
<?= $htm1->1ìnk{'Modì1y ^n Lxìstìng 8oster', ',rosters') ?><br ,>
<?= $htm1->1ìnk{'Manage Iree ^gents', ',p1ayers') ?><br ,>
<hr>
<?= $htm1->1ìnk{'8eturn 1o Web8eg uome', ',') ?><br ,>
<,dìv>
I imagine the frst thing you notice is that I've removed some links Irom that page. The "Import Roster
File" and "Import Free Agent File" Iunctionality was never really built, and now that everything is
database-driven there is no reason to need that Iunctionality again. I also removed the 'COMMIT
TRANSACTIONS' link, as it was a very early attempt at trying to make it so that our registrar could
roll back things iI he messed them up. It never worked properly, but iI I really wanted to use it I could
implement these things at the Model level.
The $html-~link(...) links are selI-explanatory, but iI I wanted to be totally Iormal with it, I should do
links like this:
<?= $htm1->1ìnk{'Make ^ 1rade', array{'contro11er' => 'trade')) ?>
Use whatever method you Ieel most comIortable with. II you were to twist my arm, I`d tell you to use
the array-based method oI passing in the controller and/or action you wish to link to. There, that was
easy, wasn't it? Let's move onto a more diIfcult one.
22 ReIactoring Legacy Applications Using CakePHP
Chapter 5 -- Untwhirling The Spaghetti
Fat Models, Skinny Controllers
One oI the main reasons to reIactor this legacy PHP application into CakePHP was that the struc-
ture was hopelessly interdependent. To make changes to one part oI the system risked wrecking
something else inside the same fle. But iI I was to do it right, and make this tool more viable going
Iorward then applying a Iramework like CakePHP to this application was a needed step.
One oI the design philosophies when it comes to MVC-style Irameworks is that I Iollow is 'Fat
models, skinny controllers¨. The idea is that you put as much oI the business logic in your models as
you can, reducing your controllers to something that does no more than move code Irom one model to
another and then passing it on to the view.
Now sometimes this isn`t possible, and that`s okay. There is no perIect way to do it. OIten is is not
clear where some particular Iunctionality should go, and there is a danger in over-abstracting things.
So keep that in mind when looking at the code examples.
Hopelessly Twisted
Here is the code Ior the page that deals with making a trade.
<htm1>
<head>
<tìt1e>Web8eg -- Make ^ 1rade<,tìt1e>
<,head>
<body>
<h2 a1ìgn="Center">Web8eg -- Make ^ 1rade<,h2>
<?php
,, make_a_trade.php
,, 1nter1ace to make trades between two teams
requìre_once '08.php',
requìre_once 'db_confg.php',
$task="",
$db =& 08::connect{0Su),
ì1 {ìsset{$_Þ0S1j"task"¸)) $task=$_Þ0S1j"task"¸,
ì1 {$task=="show_rosters") {
,, Make sure they dìdn't pìck the same team 1or both partìes
$team1=$_Þ0S1j"team1"¸,
23 ReIactoring Legacy Applications Using CakePHP
$team2=$_Þ0S1j"team2"¸,
ì1 {$team1==$team2) {
?>
<dìv a1ìgn=center>
<1ont co1or=red>You must pìck two dì11erent teams!<,1ont>
<,dìv>
<?php
$task="",
) e1se {
,, 0kay, 1et's show the rosters so we can do a trade
$sq1="
SLlLC1 tìg_name
I80M teams
WuL8L ìb1_team='$team1'
080L8 8Y tìg_name",
$resu1t=$db->query{$sq1),
whì1e {$resu1t->1etch1nto{$row)) {
$team1_1ìstj¸=trìm{$rowj0¸),
)
$sq1="
SLlLC1 tìg_name
I80M teams
WuL8L ìb1_team='$team2'
080L8 8Y tìg_name",
$resu1t=$db->query{$sq1),
whì1e {$resu1t->1etch1nto{$row)) {
$team2_1ìstj¸=trìm{$rowj0¸),
)
$t1_sìze=count{$team1_1ìst),
24 ReIactoring Legacy Applications Using CakePHP
$t2_sìze=count{$team2_1ìst),
ì1 {$t1_sìze>$t2_sìze) {
$drop-down_sìze=$t1_sìze,
) e1se {
$drop-down_sìze=$t2_sìze,
)
$team1_drop-down="<se1ect mu1tìp1e name=team1_tradej¸ sìze=$drop-down_sìze><br>",
1oreach {$team1_1ìst as $p1ayer) {
$team1_drop-down.="<optìon va1ue='$p1ayer'>$p1ayer<,optìon><br>",
)
$team1_drop-down.="<,se1ect>",
$team2_drop-down="<se1ect mu1tìp1e name=team2_tradej¸ sìze=$drop-down_sìze><br>",
1oreach {$team2_1ìst as $p1ayer) {
$team2_drop-down.="<optìon va1ue='$p1ayer'>$p1ayer<,optìon><br>",
)
$team2_drop-down.="<,se1ect>",
,, let's dìsp1ay the 1orm to do the trade
?>
<dìv a1ìgn=center>
<1orm actìon=<?php prìnt $_SL8vL8j"ÞuÞ_SLlI"¸,?> method=Þ0S1>
<ìnput type="hìdden" name="task" va1ue="do_trade">
<ìnput type="hìdden" name="team1" va1ue="<?php prìnt $team1,?>">
<ìnput type="hìdden" name="team2" va1ue="<?php prìnt $team2,?>">
<tab1e>
<tr>
<td a1ìgn=center><b><?php prìnt $team1,?><,td>
<td a1ìgn=center><b><?php prìnt $team2,?><,td>
<,tr>
<tr>
25 ReIactoring Legacy Applications Using CakePHP
<td><?php prìnt $team1_drop-down,?><,td>
<td><?php prìnt $team2_drop-down,?><,td>
<,tr>
<tr>
<td a1ìgn=center co1span=2><ìnput type="submìt" va1ue="Make 1rade"><,td>
<,tr>
<,tab1e>
<,1orm>
<,dìv>
<?php
)
)
ì1 {$task=="do_trade") {
ì1 {ìsset{$_Þ0S1j'team1_trade'¸)) $team1_trade=$_Þ0S1j"team1_trade"¸,
ì1 {ìsset{$_Þ0S1j'team2_trade'¸)) $team2_trade=$_Þ0S1j"team2_trade"¸,
ì1 {ìsset{$_Þ0S1j'team1'¸)) $team1=$_Þ0S1j"team1"¸,
ì1 {ìsset{$_Þ0S1j'team2'¸)) $team2=$_Þ0S1j"team2"¸,
ì1 {ìsset{$team1_trade)) {
1oreach {$team1_trade as $p1ayer) {
$team1_trade_p1ayersj¸=$p1ayer,
$trade_date = date{"m,y"),
$comments = "1rade {$team1) {$trade_date)",
$sq1="uÞ0^1L teams SL1 ìb1_team='{$team2)',
comments = '{$comments)',
status = 2 WuL8L tìg_name='{$p1ayer)'",
$db->query{$sq1),
)
)
ì1 {ìsset{$team2_trade)) {
26 ReIactoring Legacy Applications Using CakePHP
1oreach {$team2_trade as $p1ayer) {
$team2_trade_p1ayersj¸=$p1ayer,
$trade_date = date{"m,y"),
$comments = "1rade {$team2) {$trade_date)",
$sq1="uÞ0^1L teams SL1 ìb1_team='{$team1)',
comments = '{$comments)',
status = 2 WuL8L tìg_name='{$p1ayer)'",
$db->query{$sq1),
)
)
$team1_trade_report = "",
$team2_trade_report = "",
ì1 {ìsset{$team1_trade_p1ayers)) $team1_trade_report=ìmp1ode{", ",$team1_trade_p1ay-
ers),
ì1 {ìsset{$team2_trade_p1ayers)) $team2_trade_report=ìmp1ode{", ",$team2_trade_p1ay-
ers),
$team1_transactìon="1rades {$team1_trade_report) to {$team2) 1or {$team2_trade_re-
port)",
$team2_transactìon="1rades {$team2_trade_report) to {$team1) 1or {$team1_trade_re-
port)",
requìre_once 'transactìon_1og.php',
transactìon_1og{$team1,$team1_transactìon),
transactìon_1og{$team2,$team2_transactìon),
prìnt "
<dìv a1ìgn=center>
<b>$team1<,b> trades $team1_trade_report to <b>$team2<,b> 1or $team2_trade_
report<br>
<,dìv>",
)
ì1 {$task=="") {
?>
27 ReIactoring Legacy Applications Using CakePHP
<dìv a1ìgn=center>Þ1ease se1ect two teams 1or the trade<,dìv>
<?php
$sq1="SLlLC1 01S11uC1{ìb1_team) I80M teams",
$resu1t=$db->query{$sq1),
ì1 {$resu1t!=I^lSL) {
whì1e {$resu1t->1etch1nto{$row)) {
$ìb1_teamj¸=$rowj0¸,
)
)
$team_optìon="",
1oreach {$ìb1_team as $team) {
$team_optìon.="<optìon va1ue='$team'>$team<,optìon>\n",
)
?>
<dìv a1ìgn=center>
<1orm actìon=<?php prìnt $_SL8vL8j"ÞuÞ_SLlI"¸,?> method="Þ0S1">
<ìnput name="task" type="hìdden" va1ue="show_rosters">
<se1ect name="team1">
<?php prìnt $team_optìon,?>
<,se1ect>
<se1ect name="team2">
<?php prìnt $team_optìon,?>
<,se1ect>
<br>
<ìnput type="submìt" va1ue="use 1hese 1eams">
<,1orm>
<,dìv>
<?php
)
28 ReIactoring Legacy Applications Using CakePHP
?>
<hr>
<dìv a1ìgn=center>8eturn to <a hre1=roster_management.php>8oster Management<,a><,dìv>
<,body>
<,htm1>
So our task here is to fgure what needs to go into the models and what needs to go into the controller.
First up, we're going to take a look at how to make the frst screen people will see when they try to
make a trade.
Easy Drop-Down Lists
When approaching reIactoring I like to break down the Iunctionality that I need. For the "make a
trade" starting page, I need to do the Iollowing:
· get a list oI teams
· create a Iorm where you pick two teams
· aIter picking two diIIerent teams, send them to another page where they can choose players
c1ass 1radeContro11er extends ^ppContro11er {
...
pub1ìc 1unctìon ìndex{) {
$thìs->page1ìt1e = 'Web8eg -- Make ^ 1rade',
$teams = $thìs->Iranchìse->fnd{'1ìst', array{
'fe1ds' => 'Iranchìse.nìckname',
'order' => 'Iranchìse.nìckname'
)
),
$thìs->set{'teams', $teams),
)
)
Those Iamiliar with CakePHP will recognize the code as being pretty standard, but there is one little
twist. The fnd('list') command is a way to generate an array that can be passed into a ·select~ Iorm
element. Now, by deIault fnd('list') tries to create a hash oI primary key and the 'name' feld in your
model, iI you have one. But the existing page is using the nicknames oI the team, a 3-character string.
So we instead need the hash to be made up oI primary key and nickname. Easy to do by simply pass-
29 ReIactoring Legacy Applications Using CakePHP
ing the feld you want to the fnd('list') command.
Next, we create the view:
<h2 a1ìgn="center">Web8eg -- Make ^ 1rade<,h2>
<dìv a1ìgn="center">Þ1ease se1ect two teams 1or the trade<,dìv>v
<dìv a1ìgn="center">
<dìv a1ìgn="center"><1ont co1or="red"><?php $sessìon->fash{) ?><,1ont><,dìv>
<?= $1orm->create{array{'actìon' => ',trade,choose_p1ayers')) ?>
<?= $1orm->se1ect{'Iranchìse.team1', $teams, nu11, nu11, 1a1se) ?>
<?= $1orm->se1ect{'Iranchìse.team2', $teams, nu11, nu11, 1a1se) ?>
<br ,>
<?= $1orm->submìt{'use 1hese 1eams') ?>
<?= $1orm->end{) ?>
<,dìv>
<hr>
<dìv a1ìgn="center">8eturn to <a hre1=",rosters">8oster Management<,a><,dìv>
Now that we have the list, the next thing we need to do is process the data coming in and then either
spit out an error message that you cannot choose the same teams or show a view that contains the
players Irom both teams.
Model Association Tweaking
AIter experimenting with some queries using the CakePHP testing console (well, I did write it) I dis-
covered that I had one oI my primary keys set up wrong. The relationship between Player and Fran-
chise is via Franchise.nick¸name and Player.ibl¸team, so I decided to use Franchise.ibl¸team as the
primary key Ior my Franchise model.
c1ass Iranchìse extends ^ppMode1 {
pub1ìc $name = 'Iranchìse',
pub1ìc $prìmarykey = 'nìckname',
pubìc $hasMany = array{
'Þ1ayer' => array{
'c1assuame' => 'Þ1ayer',
'1oreìgnkey' => 'ìb1_team'
)
30 ReIactoring Legacy Applications Using CakePHP
),
)
Now my queries will come up correctly.
Multiple Select Drop-Downs
The choose¸players method needs to do the Iollowing:
· display the multiple-select Iorm felds that let you pick players
· process the incoming Iorm POST
· update the Player data to make the trade
· update the TransactionLog inIo with the results oI the trade
c1ass 1radeContro11er extends ^ppContro11er {
...
pub1ìc 1unctìon choose_p1ayers{) {
ì1 {!$thìs->data) {
$thìs->redìrect{',trade'),
)
ì1 {!empty{$thìs->dataj'Þ1ayer'¸)) {
$thìs->Þ1ayer->set{$thìs->data),
$p1ayers = $thìs->Þ1ayer->chooseÞ1ayers{),
ì1 {!$thìs->Þ1ayer->save^11{)) {
$thìs->Sessìon->setI1ash{'unab1e to comp1ete trade'),
) e1se {
$thìs->Sessìon->setI1ash{'Comp1eted trade'),
$thìs->1ransactìonlog->set{$thìs->data),
$thìs->1ransactìonlog->saveLntrìes{$p1ayers),
)
)
31 ReIactoring Legacy Applications Using CakePHP
$thìs->page1ìt1e = 'Web8eg -- Make ^ 1rade',
,, Make sure that the same two teams weren't se1ected
ì1 {$thìs->dataj'Iranchìse'¸j'team1'¸ == $thìs->dataj'Iranchìse'¸j'team2'¸) {
$thìs->Sessìon->setI1ash{'You must pìck two dì11erent teams'),
$thìs->redìrect{',trade,ìndex'),
)
$team1 = $thìs->dataj'Iranchìse'¸j'team1'¸,
$team2 = $thìs->dataj'Iranchìse'¸j'team2'¸,
$order = 'Þ1ayer.tìg_name',
$condìtìons = array{'Þ1ayer.ìb1_team' => $team1),
$roster1 = $thìs->Þ1ayer->fnd{'1ìst', array{'fe1ds' => array{'Þ1ayer.ìd',
'Þ1ayer.tìg_name'), 'condìtìons' => $condìtìons, 'order' => 'Þ1ayer.tìg_name')),
$condìtìons = array{'Þ1ayer.ìb1_team' => $team2),
$roster2 = $thìs->Þ1ayer->fnd{'1ìst', array{'fe1ds' => array{'Þ1ayer.ìd',
'Þ1ayer.tìg_name'), 'condìtìons' => $condìtìons, 'order' => 'Þ1ayer.tìg_name')),
$thìs->set{compact{'roster1', 'roster2', 'team1', 'team2')),
)
Woah. Is that a serious reduction in the amount oI code or what? In keeping with my 'Iat model,
skinny controller¨ practices I moved a lot oI Iunctionality out into two methods in the Player and
TransactionLog models respectively. Just remember to use your Model::set methods to pass the data
posted to your action into your model. Otherwise you`ll be wasting time building your own param-
eters to pass into a method in your model. Less code is good code.
It would also be good at this time to mention how simple it is to tell CakePHP you want to use trans-
actions iI you are using an ACID compliant database. Postgres, which is the database we are using
Ior this project, is capable oI doing transactional saves with rollback when things go wrong.
To gain the use oI transactions you can set and pass the atomic` variable to your model eg.
Model::saveAll($this-~data, array(validate` ÷~ frst`, atomic` ÷~ true)). That way, it validates your
data frst and then tries to save things only iI the validation was okay.
32 ReIactoring Legacy Applications Using CakePHP
UnIortunately we don`t have the type oI logic that would beneft Irom using transactions, so we can
just leave things they way they are.
c1ass Þ1ayer extends ^ppMode1 {
...
pub1ìc 1unctìon chooseÞ1ayers{) {
$data = array{),
$team1_p1ayers = array{),
$team2_p1ayers = array{),
1oreach {$thìs->dataj'Þ1ayer'¸j'roster1'¸ as $p1ayer_ìd) {
$dataj¸ = array{
'ìd' => $p1ayer_ìd,
'ìb1_team' => $thìs->dataj'Iranchìse'¸j'team2'¸,
'comments' => "1rade {$thìs->dataj'Iranchìse'¸j'team1'¸) " .
date{"m,y")
),
$team1_p1ayersj¸ = $p1ayer_ìd,
)
1oreach {$thìs->dataj'Þ1ayer'¸j'roster2'¸ as $p1ayer_ìd) {
$dataj¸ = array{
'ìd' => $p1ayer_ìd,
'ìb1_team' => $thìs->dataj'Iranchìse'¸j'team1'¸,
'comments' => "1rade {$thìs->dataj'Iranchìse'¸j'team2'¸) " .
date{"m,y")
),
$team2_p1ayersj¸ = $p1ayer_ìd,
)
$thìs->data = $data,
33 ReIactoring Legacy Applications Using CakePHP
return array{
'team1_p1ayers' => $team1_p1ayers,
'team2_p1ayers' => $team2_p1ayers
),
)
)
The tendency Ior some CakePHP developers is to save data in loops. The smart thing to do is to create
an array that contains all the data you want to save, and then call your model's saveAll() method and
CakePHP will do all the heavy SQL liIting Ior you. Again, let CakePHP do the work Ior you instead
oI doing it yourselI.
Second, discovering the little tricks you can use with fnd('list'). You can get fnd('list') to generate
pretty much whatever output you want, so since I knew I needed a hash that was "nickname" ÷~
"nickname" and only players Irom the specifc team I want. I passed in what felds I needed Ior the list
and the condition that said I only want a list oI players Irom a specifc team.
It is possible to skip having to set the felds you want Model::fnd(list`) to return iI you don`t plan on
returning more than one type oI list Ior a particular model. You can set $displayField to whatever you
want the deIault value to be Ior lists generated on that model. In Iact, there are many other values you
can set as deIault Ior your model such as primary key Ior your lists and the deIault sort order. Again,
the online documentation is the best place to check those things out.
c1ass 1ransactìonlog extends ^ppMode1 {
...
pub1ìc 1unctìon saveLntrìes{$p1ayers) {
$thìs->Þ1ayer = C1ass8egìstry::ìnìt{array{'c1ass' => 'Þ1ayer')),

$trade1_p1ayers = Set::extract{',Þ1ayer,tìg_name', $thìs->Þ1ayer->fnd{'a11',
array{'condìtìons' => array{'Þ1ayer.ìd' => $p1ayersj'team1_p1ayers'¸)))),
$trade2_p1ayers = Set::extract{',Þ1ayer,tìg_name', $thìs->Þ1ayer->fnd{'a11',
array{'condìtìons' => array{'Þ1ayer.ìd' => $p1ayersj'team2_p1ayers'¸)))),

$team1_transactìon = "1rades " . ìmp1ode{', ', $trade1_p1ayers) . " to {$thìs-
>dataj'Iranchìse'¸j'team2'¸) 1or " . ìmp1ode{', ', $trade2_p1ayers),
34 ReIactoring Legacy Applications Using CakePHP
$team2_transactìon = "1rades " . ìmp1ode{', ', $trade2_p1ayers) . " to {$thìs-
>dataj'Iranchìse'¸j'team1'¸) 1or " . ìmp1ode{', ', $trade1_p1ayers),
$data = array{
0 => array{
'ìb1_team' => $thìs->dataj'Iranchìse'¸j'team1'¸,
'1og_entry' => $team1_transactìon,
'transactìon_date' => 'u0W{)'
),
1 => array{
'ìb1_team' => $thìs->dataj'Iranchìse'¸j'team2'¸,
'1og_entry' => $team2_transactìon,
'transactìon_date' => 'u0W{)'
)
),
return $thìs->save^11{$data),
)
...
)
The syntax Ior Set::extract(...) makes so much better sense now that it uses XPath 2.0 syntax instead
oI the old '¦n}.Model.feld' syntax. OI course, I'm slightly biased because I've dealt with XPath syntax
quite a bit in my day job, which involves shoveling around a large amount oI XML. So, I needed an
easy way to get a comma-separated list oI players to enter in the transaction log. Set::extract did the
trick.
Here is the view that goes along with it:
<dìv a1ìgn="center">
<?= $1orm->create{'Þ1ayer', array{'ur1' => ',trade,choose_p1ayers')) ?>
<?= $1orm->hìdden{'Iranchìse.team1', array{'va1ue' => $team1)) ?>
<?= $1orm->hìdden{'Iranchìse.team2', array{'va1ue' => $team2)) ?>
<tab1e>
35 ReIactoring Legacy Applications Using CakePHP
<tr>
<td a1ìgn="center"><?= $team1 ?><,td>
<td a1ìgn="center"><?= $team2 ?><,td>
<,tr>
<tr>
<td co1span=2><?php $sessìon->fash{) ?><,td>
<,tr>
<tr>
<td><?= $1orm->se1ect{'Þ1ayer.roster1', $roster1, nu11, array{'mu1tìp1e' => true, 'sìze'
=> count{$roster1)), 1a1se) ?><,td>
<td><?= $1orm->se1ect{'Þ1ayer.roster2', $roster2, nu11, array{'mu1tìp1e' => true,'sìze' =>
count{$roster2)), 1a1se) ?><,td>
<,tr>
<,tab1e>
<?= $1orm->submìt{'Make 1rade') ?>
<?= $1orm->end{) ?>
<,dìv>
You might be wondering why I am not going with $Iorm-~input() Ior all the felds, instead preIer-
ring to use the actual feld-specifc methods. There`s a simple reason, really. When you use $Iorm-
~input() it oIten makes assumptions on how you want the data displayed. I mean, it is doing a lot oI
magic but in the end it is making decisions on how to display the data, decisions that might not match
what you want to do.
Secondly, $Iorm-~input() also produces markup to go with the Iorm feld. Here`s a sample, straight
out oI the Cookbook:
<?php echo $1orm->ìnput{'fe1d', array{'type' => 'f1e')), ?>
Output:
<dìv c1ass="ìnput">
<1abe1 1or="userIìe1d">Iìe1d<,1abe1>
<ìnput type="f1e" name="datajuser¸jfe1d¸" va1ue="" ìd="userIìe1d" ,>
<,dìv>
36 ReIactoring Legacy Applications Using CakePHP
Sometimes you might be okay with that. For this application I just need the Iorm input code and
nothing else. Experiment and play with $Iorm-~input to determine what is right Ior your application.
37 ReIactoring Legacy Applications Using CakePHP
Chapter 6 -- Refactoring Multiple Record Editing
We can move onto the code that lets you edit an existing roster. The existing code is this 335 line
monster that would simply be padding out the size oI this book, so instead I will break it down into
smaller chunks to show you what's going on.
Our index page Ior roster management, is very simple.
<h2 a1ìgn=center>Web8eg -- 8oster Management<,h2>
<dìv a1ìgn=center>
<a hre1=",trade">Make ^ 1rade<,a><br ,>
<a hre1=",rosters,choose">Modì1y ^n Lxìstìng 8oster<,a><br ,>
<a hre1=",1ree_agents">Manage Iree ^gents<,a>
<,dìv>
<hr>
<dìv a1ìgn="center"><a hre1=",">8eturn to Web8eg uome<,a><,dìv>
We only need an empty action method.
c1ass 8ostersContro11er extends ^ppContro11er {
pub1ìc 1unctìon ìndex{) {
)
Here's the snippet oI the code dealing with letting you pick what roster you want to edit.
<htm1>
<head>
<tìt1e>Web8eg -- Modì1y 8osters<,tìt1e>
<,head>
<body>
<h2 a1ìgn=center>Web8eg -- Modì1y 8osters<,h2>
<?php
,, modì1y_roster.php
,, used to modì1y exìstìng rosters
,, Can be used to do the 1o11owìng:
,, 1. update p1ayer names and teams at the end o1 the season
38 ReIactoring Legacy Applications Using CakePHP
,, 2. change status o1 p1ayers {^ctìve, 1nactìve, uC)
requìre_once '08.php',
requìre_once 'db_confg.php',
ìnc1ude_once{'transactìon_1og.php'),
$get_team=0,
$db =& 08::connect{0Su),
ì1 {ìsset{$_Þ0S1j"get_team"¸)) $get_team=$_Þ0S1j"get_team"¸,
,, uow, ì1 we don't have a team se1ected, ¸ust get a drop-down 1ìst
,, o1 teams to work wìth
ì1 {$get_team==0)
{
$sq1="SLlLC1 01S11uC1{ìb1_team) I80M teams 080L8 8Y ìb1_team",
$resu1t=$db->query{$sq1),
ì1 {$resu1t!=I^lSL)
{
whì1e {$resu1t->1etch1nto{$row))
{
$ìb1_teamj¸=$rowj0¸,
)
)
?>
<dìv a1ìgn=center>
<1orm actìon=<?php prìnt $_SL8vL8j"ÞuÞ_SLlI"¸,?> method="post">
<ìnput type=hìdden name=get_team va1ue=1>
<tab1e>
<tr><td><ìnput type=submìt va1ue="Choose ^ 1eam"><,td>
<td><se1ect name="ìb1_team">
<?php
1oreach {$ìb1_team as $team)
39 ReIactoring Legacy Applications Using CakePHP
{
?>
<optìon va1ue="<?php prìnt $team,?>"><?php prìnt $team,?><,optìon>
<?php
)
?>
<,se1ect><,td><,tr>
<,tab1e>
<,1orm>
<,dìv>
First thing I did was create the view that I would need Ior it:
<h2 a1ìgn=center>Web8eg -- Modì1y 8osters<,h2>
<dìv a1ìgn=center>
<?= $1orm->create{'Iranchìse', array{'ur1' => ',rosters,edìt')) ?>
<tab1e>
<tr>
<td><?= $1orm->submìt{'Choose ^ 1eam') ?><,td>
<td><?= $1orm->se1ect{'Iranchìse.nìckname', $1ranchìses) ?><,td>
<,tr>
<,tab1e>
<?= $1orm->end{) ?>
<,dìv>
<hr>
<dìv a1ìgn="center"><a hre1=",rosters">8eturn to 8oster Management<,a><,dìv>
The controller method is very simple as well:
c1ass 8ostersContro11er extends ^ppContro11er {
pub1ìc $he1pers = array{'utm1', 'Javascrìpt', 'Iorm'),
...
40 ReIactoring Legacy Applications Using CakePHP
pub1ìc 1unctìon choose{) {
$thìs->page1ìt1e = 'Web8eg -- Modì1y 8osters',
C1ass8egìstry::ìnìt{array{'c1ass' => 'Iranchìse')),
$1ranchìses = $thìs->Iranchìse->fnd{'1ìst', array{'fe1ds' => 'Iranchìse.nìckname',
'order' => 'Iranchìse.nìckname')),
$thìs->set{'1ranchìses', $1ranchìses),
)
The landing page Ior our Iorm will be /rosters/edit, where we will then want to display a Iorm con-
taining every batter, pitcher and draIt pick Ior that team. Also, I should add in something that redi-
rects them to /rosters/ iI they haven't picked a team or try to access the page directly without posting
something.
Just looking at this next block oI code makes me cringe.
<dìv a1ìgn=center>
Modì1yìng 8oster 1or <b><?php prìnt $ìb1_team,?><,b><br><br>
<?php
ì1 {sìzeo1{$update_1ìst)!=0)
{
1oreach {$update_1ìst as $update_tìg_name) prìnt "$update_tìg_name<br>",
)
?>
<1orm actìon=<?php prìnt $_SL8vL8j"ÞuÞ_SLlI"¸,?> method=post>
<ìnput type=hìdden name=get_team va1ue=1>
<ìnput type=hìdden name=ìb1_team va1ue="<?php prìnt $ìb1_team,?>">
<ìnput type=hìdden name=modì1y va1ue=1>
<tab1e>
<tr>
<td><b>116 uame<,b><,td>
<td><b>1ype<,b><,td>
<td><b>Comments<,b><,td>
<td><b>Status<,b><,td>
41 ReIactoring Legacy Applications Using CakePHP
<td><b>0e1ete<,b><,td>
<td><b>8e1ease<,b><,td>
<,tr>
<?php
ì1 {$resu1t!=I^lSL)
{
whì1e {$resu1t->1etch1nto{$row,08_IL1CuM00L_^SS0C))
{
$ìd=$rowj'ìd'¸,
$tìg_name=trìm{$rowj'tìg_name'¸),
$type=$rowj'ìtem_type'¸,
$comments=trìm{$rowj'comments'¸),
$status=$rowj'status'¸,
$type_se1ected=array{),
$status_se1ected=array{),
1or {$x=0,$x<=2,$x++) {
$type_se1ectedj$x¸="",
$status_se1ectedj$x+1¸="",
)
$type_se1ectedj$type¸="se1ected",
$status_se1ectedj$status¸="se1ected",
?>
<ìnput type=hìdden name=shadow_tìg_namej<?php prìnt $ìd,?>¸ va1ue="<?php
prìnt $tìg_name,?>">
<ìnput type=hìdden name=shadow_typej<?php prìnt $ìd,?>¸ va1ue=<?php prìnt
$type,?>>
<ìnput type=hìdden name=shadow_commentsj<?php prìnt $ìd,?>¸ va1ue="<?php
prìnt $comments,?>">
<ìnput type=hìdden name=shadow_statusj<?php prìnt $ìd,?>¸ va1ue=<?php
prìnt $status,?>>
<ìnput type=hìdden name=ìdj¸ va1ue=<?php prìnt $ìd,?>>
42 ReIactoring Legacy Applications Using CakePHP
<tr>
<td><ìnput name=tìg_namej<?php prìnt $ìd,?>¸ va1ue="<?php prìnt $tìg_
name,?>" sìze=20><,td>
<td><se1ect name=typej<?php prìnt $ìd,?>¸>
<optìon va1ue=0 <?php prìnt $type_se1ectedj0¸,?>>Þìck<,optìon>
<optìon va1ue=1 <?php prìnt $type_se1ectedj1¸,?>>Þìtcher<,optìon>
<optìon va1ue=2 <?php prìnt $type_se1ectedj2¸,?>>8atter<,optìon>
<,se1ect><,td>
<td><ìnput name=commentsj<?php prìnt $ìd,?>¸ va1ue="<?php prìnt $com-
ments,?>" sìze=40><,td>
<td><se1ect name=statusj<?php prìnt $ìd,?>¸>
<optìon va1ue=1 <?php prìnt $status_se1ectedj1¸,?>>^ctìve<,optìon>
<optìon va1ue=2 <?php prìnt $status_se1ectedj2¸,?>>1nactìve<,optìon>
<optìon va1ue=2 <?php prìnt $status_se1ectedj2¸,?>>uncarded<,optìon>
<,se1ect>
<,td>
<,td>
<td><ìnput name=de1etej¸ type="checkbox" va1ue="<?php prìnt $ìd,?>"><,td>
<td><ìnput name=re1easej¸ type="checkbox" va1ue=<?php prìnt $ìd,?>><,td>
<,tr>
<?php
)
)
?>
<tr>
<td><ìnput name=new_tìg_name va1ue="" sìze=20><,td>
<td><se1ect name=new_type>
<optìon va1ue=0 <?php prìnt $type_se1ectedj0¸,?>>Þìck<,optìon>
<optìon va1ue=1 <?php prìnt $type_se1ectedj1¸,?>>Þìtcher<,optìon>
<optìon va1ue=2 <?php prìnt $type_se1ectedj2¸,?>>8atter<,optìon>
43 ReIactoring Legacy Applications Using CakePHP
<,se1ect><,td>
<td><ìnput name=new_comments va1ue="" sìze=40><,td>
<td><se1ect name=new_status>
<optìon va1ue=1>^ctìve<,optìon>
<optìon va1ue=2>1nactìve<,optìon>
<optìon va1ue=2>uncarded<,optìon>
<,se1ect>
<,td>
<,tr>
<tr><td co1span=4><ìnput type=submìt va1ue="Modì1y 8oster"><,td><,tr>
<,tab1e>
<,1orm>
<,dìv>
Simpliüed Views
This is an area where CakePHP can really shine, greatly reducing the amount oI code you need to
write yourselI. First, we can get rid oI all that nonsense I did with shadow felds in the Iorm because
CakePHP will handle that Ior us at the model level. Here's the much-improved view.
<h2 a1ìgn=center>Web8eg -- Modì1y 8osters<,h2>
<dìv a1ìgn=center>
Modì1yìng 8oster 1or <?= $nìckname ?><,b><br ,><br ,>
<?php $sessìon->fash{) ?>
<?= $1orm->create{'Þ1ayer', array{'ur1' => ',rosters,edìt')) ?>
<?= $1orm->hìdden{'Iranchìse.nìckname', array{'va1ue' => $nìckname)) ?>
<tab1e>
<tr>
<td><b>116 uame<,b><,td>
<td><b>1ype<,b><,td>
<td><b>Comments<,b><,td>
<td><b>Status<,b><,td>
<td><b>0e1ete<,b><,td>
44 ReIactoring Legacy Applications Using CakePHP
<td><b>8e1ease<,b><,td>
<,tr>
<?php $next_ìd = count{$p1ayers) ?>
<?php 1oreach {$p1ayers as $p1ayer) : ?>
<?php $ìdx = $p1ayerj'Þ1ayer'¸j'ìd'¸ ?>
<tr>
<?= $1orm->hìdden{"{$ìdx).ìd", array{'va1ue' => $ìdx)) ?>
<td><?= $1orm->text{"{$ìdx).tìg_name", array{'va1ue' => $p1ayerj'Þ1ayer'¸j'tìg_name'¸,
'sìze' => 20)) ?><,td>
<td><?= $1orm->se1ect{"{$ìdx).ìtem_type", array{0 => 'Þìck', 1 => 'Þìtcher', 2 => '8at-
ter'), $p1ayerj'Þ1ayer'¸j'ìtem_type'¸, nu11, 1a1se) ?><,td>
<td><?= $1orm->text{"{$ìdx).comments", array{'va1ue' => $p1ayerj'Þ1ayer'¸j'comments'¸,
$p1ayerj'Þ1ayer'¸j'comments'¸, 'sìze' => 40)) ?><,td>
<td><?= $1orm->se1ect{"{$ìdx).status", array{1 => '^ctìve', 2 => '1nactìve', 2 => 'uncard-
ed'), $p1ayerj'Þ1ayer'¸j'status'¸, nu11, 1a1se) ?><,td>
<td><?= $1orm->checkbox{"Þ1ayer^ctìon.{$ìdx).de1ete") ?><,td>
<td><?= $1orm->checkbox{"Þ1ayer^ctìon.{$ìdx).re1ease") ?><,td>
<,tr>
<?php end1oreach, ?>
<?= $1orm->hìdden{"{$next_ìd).ìb1_team", array{'va1ue' => $nìckname)) ?>
<tr>
<td><?= $1orm->text{"{$next_ìd).tìg_name", array{'sìze' => 20)) ?><,td>
<td><?= $1orm->se1ect{"{$next_ìd).ìtem_type", array{0 => 'Þìck', 1 => 'Þìtcher', 2 =>
'8atter')) ?><,td>
<td><?= $1orm->text{"{$next_ìd).comments", array{'sìze' => 40)) ?><,td>
<td><?= $1orm->se1ect{"{$next_ìd).status", array{1 => '^ctìve', 2 => '1nactìve', 2 => 'un-
carded')) ?><,td>
<,tr>
<tr>
<td co1span="4"><?= $1orm->submìt{'Modì1y 8oster') ?><,td>
<,tr>
<,tab1e>
45 ReIactoring Legacy Applications Using CakePHP
<?= $1orm->end{) ?>
<,dìv>
<hr>
<dìv a1ìgn="center"><a hre1=",rosters">8eturn to 8oster Management<,a><,dìv>
II you're used to doing things like ·input type÷"text" name÷"Ioo||"~ then the CakePHP syntax takes
a little getting used to. CakePHP will take a syntax oI ·number~.·model~.·feld~ (i.e. 0.Player.
item¸type) and then handle all the magic behind the scenes. But the import thing is to understand the
syntax so that you can group your Iorm post results the way you need them. In this case we are try-
ing to group all results associated with one player together, so that's why we're looking at things like
¦$next¸id}.tig¸name to group each set oI felds Ior a Player together. By doing it this way, I can get a
data result set like this:
jÞ1ayer¸ => ^rray
{
j4470¸ => ^rray
{
jìd¸ => 4470
jtìg_name¸ => 8^l Cherry
jìtem_type¸ => 1
jcomments¸ => Iree ^gent 09,08 juC09¸
jstatus¸ => 2
)
j2847¸ => ^rray
{
jìd¸ => 2847
jtìg_name¸ => 8^l Markakìs
jìtem_type¸ => 2
jcomments¸ => 1st 8ound 07 {#12)
jstatus¸ => 1
)
j200S¸ => ^rray
{
46 ReIactoring Legacy Applications Using CakePHP
jìd¸ => 200S
jtìg_name¸ => 8^l Sherrì11
jìtem_type¸ => 1
jcomments¸ => Iree ^gent 8,0S
jstatus¸ => 1
)
Filtering and Updating Records
With a result set like the one above, you can just pass that data into your save() method oI your
model, no manipulation required. It's all about reducing the amount oI code you write. The code used
to be so jumbled, and I'm quite pleased with how the CakePHP version looks.
c1ass 8ostersContro11er extends ^ppContro11er {
...
pub1ìc 1unctìon edìt{) {
$thìs->page1ìt1e = 'Web8eg -- Modì1y 8osters',

ì1 {empty{$thìs->data)) {
$thìs->Sessìon->setI1ash{'Þ1ease se1ect a team'),
$thìs->redìrect{',rosters'),
)

ì1 {!empty{$thìs->dataj'Þ1ayer'¸)) {
1oreach {$thìs->dataj'Þ1ayer^ctìon'¸ as $ìd => $p1ayer) {
ì1 {$p1ayerj're1ease'¸ == 1) {
$thìs->dataj'Þ1ayer'¸j$ìd¸j'ìb1_team'¸ = 'I^',
)

ì1 {$p1ayerj'de1ete'¸ == 1) {
unset{$thìs->dataj'Þ1ayer'¸j$ìd¸),
$thìs->Þ1ayer->de1ete{$ìd),
)
47 ReIactoring Legacy Applications Using CakePHP
)

$p1ayer_data = $thìs->dataj'Þ1ayer'¸,
,, 8emove any records that have empty p1ayer names
1oreach {$p1ayer_data as $key => $data) {
ì1 {empty{$dataj'tìg_name'¸ == '')) {
unset{$p1ayer_dataj$key¸),
)
)

ì1 {$thìs->Þ1ayer->save^11{$p1ayer_data)) {
$thìs->Sessìon->setI1ash{'Modìfed roster'),
) e1se {
$thìs->Sessìon->setI1ash{'unab1e to modì1y roster'),
)
)

$condìtìons = array{'Iranchìse.nìckname' => $thìs->dataj'Iranchìse'¸j'nìckname'¸),
$order = 'Þ1ayer.tìg_name',
$thìs->Þ1ayer->contaìn{'Iranchìse'),
$p1ayers = $thìs->Þ1ayer->fnd{'a11', compact{'condìtìons', 'order')),
$thìs->set{'p1ayers', $p1ayers),
$thìs->set{'nìckname', $thìs->dataj'Iranchìse'¸j'nìckname'¸),
)
)
One oI the things I like to see in any Iramework is that the code you write reads almost like English.
Looking at this code, it seems very straightIorward. Well, at least to me.
48 ReIactoring Legacy Applications Using CakePHP
Only Updating The Data You Need To
Now, iI you look at the old code you'll notice how I used shadow felds in the Iorm so that later on I
could just update the records in the database that needed to be updated. To be honest, I see that as a lot
oI busy work and I'm perIectly okay to do all those database calls to update the roster. At most we're
talking 40 to 50 updates.
But iI you were dealing with an application that does a lot database updates as it is, then I would
defnitely recommend doing data comparisons oI the Iorm data coming in. Whether or not you do it
via hidden felds in the Iorm or by reading those particular records Irom the database depends on your
situation. One thing that is Ior sure, I would be putting that Iunctionality in it's own method in the
model.
Assuming that we've created "shadow felds" in the Iorm, here's some sample code to do that.
c1ass Þ1ayer extends ^ppMode1 {
...
,**
* ^ssume that you've a1ready popu1ated $thìs->data
* usìng $thìs->Þ1ayer->set{$thìs->data) ìn your contro11er
*,
pub1ìc 1unctìon modì1y8oster{) {
1oreach {$thìs->dataj'Þ1ayer'¸ as $key => $data) {
ì1 {!empty{$thìs->dataj'ShadowÞ1ayer'¸j$key¸)) {
ì1 {$data == $thìs->dataj'ShadowÞ1ayer'¸j$key¸) {
unset{$thìs->dataj'Þ1ayer'¸j$key¸),
)
)
)
$thìs->save^11{$thìs->data),
)
49 ReIactoring Legacy Applications Using CakePHP
Chapter 7 -- Easy Batch Record Manipulation
We`ve got all the existing roster methods taken care oI, next up is the Iunctionality dealing with ma-
nipulating the Iree agents themselves.
<h2 a1ìgn="center">Web8eg -- Manage Iree ^gents<,h2>
<p>
<dìv a1ìgn="center">
<a hre1=",1ree_agents,sìgn">Sìgn Iree ^gents<,a><br ,>
<a hre1=",p1ayers,dra1t">0ra1t ^ Þ1ayer<,a><br ,>
<a hre1=",p1ayers,add">^dd Þ1ayers to Iree ^gent Þoo1<,a><br ,>
<a hre1=",1ree_agents,vìew">vìew , Ldìt Iree ^gents<,a><br ,>
<,dìv>
<,p>
<hr>
<dìv a1ìgn="center">8eturn 1o <a hre1=",rosters">8oster Management<,a><,dìv>
The 'Sign Free Agents¨ page is supposed to display all the existing Iree agents and allow you to
add them to teams in batches, hopeIully reducing the amount oI work the league registrar has to do.
Here`s what the old code looked like.
ì1 {$task=="sìgn")
{
,, Þresent a 1ìst o1 a11 1ree agents
,, and then a11ow them to be assìgned to a team
$sq1="SLlLC1 01S11uC1{ìb1_team) I80M teams WuL8L ìb1_team!='I^' 080L8 8Y ìb1_team",
$resu1t=$db->query{$sq1),
$ìb1_team_drop-down="<se1ect name=ìb1_teamj¸>",
$ìb1_team_drop-down.="<optìon va1ue='I^' se1ected >Iree ^gent<,optìon>",
whì1e {$resu1t->1etch1nto{$row))
{
$ìb1_team=$rowj0¸,
$ìb1_team_drop-down.="<optìon va1ue='".$ìb1_team."'>".$ìb1_team."<,optìon>",
50 ReIactoring Legacy Applications Using CakePHP
)
$ìb1_team_drop-down.="<,se1ect>",
,, uow, get a 1ìst o1 a11 the p1ayers who are 1ree agents
$sq1="SLlLC1 ìd,tìg_name I80M teams WuL8L ìb1_team='I^' 080L8 8Y tìg_name",
$resu1t=$db->query{$sq1),
?>
<dìv a1ìgn=center>
<1orm actìon="<?php prìnt $_SL8vL8j"ÞuÞ_SLlI"¸,?>" method="post">
<ìnput type="hìdden" name="task" va1ue="do_sìgnìng">
<tab1e>
<?php
whì1e {$resu1t->1etch1nto{$row,08_IL1CuM00L_^SS0C))
{
$tìg_name=trìm{$rowj'tìg_name'¸),
$ìd = $rowj'ìd'¸,
?>
<tr>
<td><?php prìnt $tìg_name,?><,td><ìnput type="hìdden" name="tìg_namej<?php prìnt
$ìd,?>¸" va1ue="<?php prìnt $tìg_name,?>">
<td><?php prìnt $ìb1_team_drop-down,?><,td><ìnput type="hìdden" name="ìdj¸"
va1ue=<?php prìnt $ìd,?>>
<,tr>
<?php
)
?>
<tr><td co1span=2 a1ìgn=center><ìnput type="submìt" va1ue="Sìgn Iree ^gents"><,td><,tr>
<,tab1e>
51 ReIactoring Legacy Applications Using CakePHP
<,1orm>
<?php
)
The code that processed the Iorm post:
ì1 {$task=="do_sìgnìng")
{
,, assìgn 1ree agents to theìr new teams
$ìd=$_Þ0S1j"ìd"¸,
$ìb1_team=$_Þ0S1j"ìb1_team"¸,
$tìg_name=$_Þ0S1j"tìg_name"¸,
?>
<dìv a1ìgn="center">
<?php
1oreach {$ìd as $key=>$sìgn_ìd)
{
$sìgn_team=$ìb1_teamj$key¸,
ì1 {$sìgn_team!='I^')
{
$comments = "Iree ^gent ".date{"m,y"),
$sq1="uÞ0^1L teams SL1 ìb1_team='{$sìgn_team)', comments = '{$comments)', sta-
tus = 2 WuL8L ìd={$sìgn_ìd)",
$db->query{$sq1),
$1og_entry="Sìgns ".$tìg_namej$sìgn_ìd¸,
transactìon_1og{$sìgn_team,$1og_entry),
prìnt "<b>".$tìg_namej$sìgn_ìd¸."<,b> sìgns wìth {$sìgn_team)<br>",
)
52 ReIactoring Legacy Applications Using CakePHP
)
?>
<,dìv>
<?php
)
Using N.üeld Notation In Your Forms
Again, CakePHP to the rescue in simpliIying things. Let`s examine the view that presents us with our
list oI Iree agents, complete with a Iorm.
<h2 a1ìgn="center">Web8eg -- Manage Iree ^gents<,h2>
<dìv a1ìgn="center">
<?= $1orm->create{'Þ1ayer', array{'ur1' => ',1ree_agents,sìgn', 'method' => 'post')) ?>
<tab1e>
<tr>
<td co1span=2><?php $sessìon->fash{) ?><,td>
<,tr>
<?php 1oreach {$1ree_agents as $ìdx => $p1ayer) : ?>
<?= $1orm->hìdden{"{$ìdx).ìd", array{'va1ue' => $p1ayerj'Þ1ayer'¸j'ìd'¸)) ?>
<?= $1orm->hìdden{"{$ìdx).tìg_name", array{'va1ue' => $p1ayerj'Þ1ayer'¸j'tìg_
name'¸)) ?>
<tr>
<td><?= $p1ayerj'Þ1ayer'¸j'tìg_name'¸ ?><,td>
<td><?= $1orm->se1ect{"{$ìdx).ìb1_team", $1ranchìses, $p1ayerj'Þ1ayer'¸j'ìb1_
team'¸, nu11, 1a1se) ?><,td>
<,tr>
<?php end1oreach, ?>
<tr>
<td co1span=2><?= $1orm->submìt{'Sìgn Iree ^gents') ?><,td>
<,tr>
<,tab1e>
<?= $1orm->end{) ?>
53 ReIactoring Legacy Applications Using CakePHP
<hr>
8eturn to <a hre1=",rosters">8oster Management<,a>
<,dìv>
You can see that I`m using N.feld notation so that we have multiple entries in our array when we post
the data over, which in turns makes it easier to leverage the automagic oI CakePHP`s model methods.
This becomes very clear when you see the controller method.
pub1ìc 1unctìon sìgn{) {
ì1 {$thìs->data) {
ì1 {!empty{$thìs->dataj'Þ1ayer'¸)) {
$msg = '',
1oreach {$thìs->dataj'Þ1ayer'¸ as $ìdx => $p1ayer_data) {
ì1 {$p1ayer_dataj'ìb1_team'¸ == 'I^') {
unset{$thìs->dataj'Þ1ayer'¸j$ìdx¸),
) e1se {
$thìs->dataj'Þ1ayer'¸j$ìdx¸j'comments'¸ = "Iree ^gent " .
date{"m,y"),
$transactìon_dataj¸ = array{'ìb1_team' => $p1ayer_dataj'ìb1_team'¸,
'1og_entry' => "Sìgns {$p1ayer_dataj'tìg_name'¸)", 'transactìon_date' => date{'Y-m-d')),
$msg .= "<b>{$p1ayer_dataj'tìg_name'¸) sìgns wìth {$p1ayer_
dataj'ìb1_team'¸)<br ,>",
)
)
$thìs->1ransactìonlog->contaìn{),
ì1 {!{$thìs->Þ1ayer->save^11{$thìs->dataj'Þ1ayer'¸))) {
$msg = "unab1e to assìgn 1ree agents to theìr teams",
) e1se {
$thìs->1ransactìonlog->save^11{$transactìon_data),
)
54 ReIactoring Legacy Applications Using CakePHP
$thìs->Sessìon->setI1ash{$msg),
)
)
$order = 'Þ1ayer.tìg_name',
$condìtìons = array{'Þ1ayer.ìb1_team' => 'I^'),
$1ree_agents = $thìs->Þ1ayer->fnd{'a11', compact{'condìtìons', 'order')),
$1ranchìses = array_merge{array{'I^' => 'Iree ^gent'), $thìs->Iranchìse-
>fnd{'1ìst', array{'fe1ds' => 'Iranchìse.nìckname','order' => 'Iranchìse.nìckname'))),
$thìs->set{'1ree_agents', $1ree_agents),
$thìs->set{'1ranchìses', $1ranchìses),
)
Remember earlier when I showed a way to eliminate unneeded data Irom our results array as a Iunc-
tion inside one oI our models? Here I`ve shown you an easy way to do the same sort oI things inside
the controller. Given how simple the Iunctionality really was I fgured it wasn`t a big violation oI my
'Iat models, skinny controllers¨ convention to leave it in there.
As you can see, I`m removing results Irom $this-~data iI the user didn`t change the team Ior a Iree
agent. AIter a Iew quick checks and the creation oI the entries Ior the transaction log, we pass the
entire contents to the saveAll() method oI our models and we`re done. You`ll also noticed that I`ve
done a two-step save here, because I didn`t want transaction log entries showing up iI the main save
oI player inIormation didn`t work.
Really, the goal was to minimize the amount oI data I would be passing to the saveAll() method,
because it really is a waste to update records that don`t really need to be updated unless perIormance
doesn`t really matter. Give that I`m expecting one concurrent user at any time, I`d say slapping a
profler onto this application and looking Ior bottlenecks isn`t a priority.
You can also see a little trick where I Iorce the 'Iree agent¨ team to the Iront oI the list Ior my select
drop-down in the Iorm using array¸merge and fnd(list`). I fnd myselI doing that quite a bit when
creating Iorms with drop-downs, as the deIault order isn`t always alphabetical.
55 ReIactoring Legacy Applications Using CakePHP
Chapter 8 -- Easy Batch Record Addition
With the batch-assigning oI Iree agents to teams take care oI, we now can look at the Iunctionality
Ior adding players to the Iree agent pool, which is something we usually do as preparation Ior the
league`s annual draIt. The goal oI this page is to:
display an empty Iorm Ior adding players
process the data posted Irom the Iorm
add those players to the pool
ì1 {$task=="add")
{
,, Þresent a 1orm where you can add up to 20 p1ayers at a tìme to the 1ree agent poo1
?>
<dìv a1ìgn=center>
<p>^dd Þ1ayers to Iree ^gent Þoo1<,p>
<1orm actìon="<?php prìnt $_SL8vL8j"ÞuÞ_SLlI"¸,?>" method="post">
<ìnput name=task type=hìdden va1ue="do_add">
<tab1e>
<tr><td><b>116 uame<,b><,td><,tr>
<?php
1or {$x=1,$x<=20,$x++)
{
?>
<tr>
<td><ìnput name=1a_tìg_namej¸ sìze=20><,td>
<td><se1ect name=1a_typej¸>
<optìon va1ue=1>Þìtcher<,optìon>\n
<optìon va1ue=2>8atter<,optìon>\n
<,se1ect><,td>
<,tr>
<?php
56 ReIactoring Legacy Applications Using CakePHP
)
?>
<tr><td><ìnput type="submìt" va1ue="^dd Þ1ayers"><,td><,tr>
<,1orm>
<,tab1e>
<?php
)
Here`s the code that accepts the Iorm post and adds the players:
ì1 {$task=="do_add")
{
,, process the p1ayers that have been added to the 1ree agent poo1
ì1 {ìsset{$_Þ0S1j'1a_tìg_name'¸)) $1a_tìg_name = $_Þ0S1j'1a_tìg_name'¸,
ì1 {ìsset{$_Þ0S1j'1a_type'¸)) $1a_type = $_Þ0S1j'1a_type'¸,
ì1 {count{$1a_tìg_name)<1)
{
?>
<dìv a1ìgn=center><1ont co1or=red>You must f11 ìn at 1east one name<,dìv>
<?php
$task="",
)
e1se
{
prìnt "<dìv a1ìgn=center>",
1oreach {$1a_tìg_name as $key=>$p1ayer)
{
ì1 {$p1ayer!="")
{
57 ReIactoring Legacy Applications Using CakePHP
$sq1="1uSL81 1u10 teams {tìg_name,ìb1_team,ìtem_type,status) v^luLS
{'{$p1ayer)', 'I^', {$1a_typej$key¸) , 1)",
prìnt "{$sq1)<br>",
$db->query{$sq1),
prìnt "^dded <b>$p1ayer<,b> to the 1ree agent poo1<br>",
)
)
)
}
Easy Arrays In Forms
Again, very standard spaghetti PHP stuII. The CakePHP view Ior this is very simple:
<dìv a1ìgn="center">
<h2>Web8eg -- Manage Iree ^gents<,h2>
<p><?php $sessìon->fash{) ?><,p>
<p>^dd Þ1ayers to Iree ^gent Þoo1<,p>
<tab1e>
<tr>
<td co1span=2 a1ìgn="1e1t"><b>116 uame<,b><,td>
<,tr>
<?= $1orm->create{'Þ1ayer', array{'ur1' => ',p1ayers,add', 'method' => 'post')) ?>
<?php 1or {$x =1, $x <= 20, $x++) : ?>
<tr>
<td><?= $1orm->text{"{$x).Þ1ayer.tìg_name") ?><,td>
<td><?= $1orm->se1ect{"{$x).Þ1ayer.ìtem_type", $ìtem_types, nu11, nu11, 1a1se)
?><,td>
<,tr>
<?php end1or, ?>
<tr>
<td co1span=2 a1ìgn="1e1t"><?= $1orm->submìt{'^dd Þ1ayers') ?><,td>
<,tr>
58 ReIactoring Legacy Applications Using CakePHP
<?= $1orm->end{) ?>
<,tab1e>
<hr>
8eturn 1o <a hre1=",rosters">8oster Management<,a>
<,dìv>
Again, note the use oI the N.Model.feld notation to group the contents oI $this-~data nicely Ior use
by saveAll().
Saving and Validating Data
Although this application really has very little in the way oI data entry, I thought it would be good to
show how I would add in validation to this application. There was no data validation in the original
application, so this is sort oI violating one oI the rules oI reIactoring which says 'thou shalt not add
more Ieatures¨.
Like so many other things in CakePHP, adding in validation to your Iorms is easy. In this case, we
want to make sure that the feld in the Player model is valid whenever we save a record.
The rules are as Iollows:
* Either 2 or 3 uppercase letters (KC or FLA Ior example)
* one space and then a capitalized word with no spaces
So what does that type oI rule look like?
c1ass Þ1ayer extends ^ppMode1 {
....
pub1ìc $va1ìdate = array{
'tìg_name' => array{
'ru1e' => array{'custom', ',^j^-2¸{2,2)\sj^-2¸\w+$,'),
'message' => '1nva1ìd p1ayer name'
)
),
)
UnIortunately you will have to brush up on your regular expression skills in order to Iully appreciate
that custom rule. In order to do this we had to tell CakePHP that we wanted to use a custom valida-
tion rule. Out oI the box, CakePHP has an awesome collection oI validation rules, 25 at last count.
When you add in the Iact that you can do custom validations rules that are either regular expressions
or Iull-blown Iunctions that are accessed as a callback, there is absolutely no reason to have Iorm
validation in your application.
The use oI callbacks in validation is very powerIul, so don`t be aIraid oI complicated validation rules
in your existing application. II you coded it beIore, you can code it again.
59 ReIactoring Legacy Applications Using CakePHP
Finally, the controller that ties it all together.
c1ass Þ1ayersContro11er extends ^ppContro11er {
...
pub1ìc 1unctìon add{) {
$thìs->page1ìt1e = 'Web8eg -- Manage Iree ^gents',
ì1 {$thìs->data) {
1oreach {$thìs->data as $ìdx => $data) {
ì1 {$dataj'Þ1ayer'¸j'tìg_name'¸ == '') {
unset{$thìs->dataj$ìdx¸),
) e1se {
$thìs->dataj$ìdx¸j'Þ1ayer'¸j'ìb1_team'¸ = 'I^',
)
)
,, va1ìdate each p1ayer that we have
$va1ìd_p1ayers = true,
$msg = nu11,
$new_data = array{),
1oreach {$thìs->data as $ìdx => $data) {
$thìs->Þ1ayer->set{$data),

ì1 {$thìs->Þ1ayer->va1ìdates{)) {
$msg .= "^dded {$dataj'Þ1ayer'¸j'tìg_name'¸) to the 1ree agent
poo1<br ,>",
$thìs->Þ1ayer->save{),
60 ReIactoring Legacy Applications Using CakePHP
) e1se {
$va1ìd_p1ayers = 1a1se,
$new_dataj¸ = $data,
)
)
ì1 {$va1ìd_p1ayers == 1a1se) {
$msg = 'Þ1ease make sure that you have entered p1ayer names prop-
er1y',
$thìs->data = $new_data,
)
$thìs->Sessìon->setI1ash{$msg),
)
$ìtem_types = array{
1 => 'Þìtcher',
2 => '8atter'
),
$thìs->set{'ìtem_types', $ìtem_types),
)
)
The only bit oI logic above that I Ieel needs explaining is the loop where I`m perIorming validation
on every player passed in.
1oreach {$thìs->data as $ìdx => $data) {
$thìs->Þ1ayer->set{$data),

ì1 {$thìs->Þ1ayer->va1ìdates{)) {
61 ReIactoring Legacy Applications Using CakePHP
$msg .= "^dded {$dataj'Þ1ayer'¸j'tìg_name'¸) to the 1ree agent poo1<br ,>",
$thìs->Þ1ayer->save{),
unset{$thìs->dataj$ìdx¸),
) e1se {
$va1ìd_p1ayers = 1a1se,
$new_dataj¸ = $data,
)
)
Using a common CakePHP practice Ior when you wish to validate data beIore doing something to it, I
pass the data inIo my model using a Model::set command. Then, I ask the model to validate the data
I passed into it. Model::validates() returns TRUE or FALSE and will also populate Model::invalid¸
felds iI you wish to do something with the report.
Since I hadn`t implemented any validation beIore, I decided to take the easy way out and simply do
the Iollowing:
* save any records that met our validation criteria
* add any records that Iailed to an array and reset $this-~data to contain only the Iailed records so
they can be corrected
So the result is that anything that is okay gets saved, and everything that wasn`t okay gets put back
into the Iorm and I inIorm the user that they need to correct them. Maybe I could add a message tell-
ing them what Iormat it SHOULD be in, but the people using this application are already aware oI the
requirements oI the player name.
Another little trick being used is that I loop through the result set making sure that I`ve set the ibl¸
team Ior each record to FA`, which is the internal code Ior being a Iree agent. I could probably also
add a validation rule that says that ibl¸team cannot be null.
I think it`s important to understand that I`m taking shortcuts here because this is a system that is used
by people who are intimately Iamiliar with data entry requirements. You should not be so trusting
with any site that is used by people not so intimately Iamiliar with how to enter data. Validating
incoming data is just something you need to do all the time, and it was only the lack oI an easy-to-use
validation system that I did not do it originally.
62 ReIactoring Legacy Applications Using CakePHP
Chapter 9 -- Displaying Data

We have all the pages where we enter data into Iorms and update records based on that inIo fnished.
Next up are two examples oI how to grab data out oI your models and display it. First, let`s look at
the code that displays the list oI Iree agents. Here`s the original code:
<?php
,, vìew_1ree_agents.php
,, 6enerates a 1ree agent report
?>
<htm1>
<head>
<tìt1e>Web8eg -- vìew Iree ^gents<,tìt1e>
<,head>
<body>
<h2 a1ìgn="center">Web8eg -- vìew Iree ^gents<,h2>
<?php
requìre_once '08.php',
requìre_once 'db_confg.php',
$db =& 08::connect{0Su),
,, Iìrst, dìsp1ay a11 the pìtchers
prìnt "Þ11CuL8S<br><br>",
$sq1="SLlLC1 tìg_name I80M teams WuL8L ìb1_team='I^' ^u0 ìtem_type=1 080L8 8Y tìg_name",
$resu1t=$db->query{$sq1),
63 ReIactoring Legacy Applications Using CakePHP
whì1e {$resu1t->1etch1nto{$row,08_IL1CuM00L_^SS0C))
{
$tìg_name=$rowj'tìg_name'¸,
prìnt "$tìg_name<br>",
)
,, uow, the batters
prìnt "<br>8^11L8S<br>",
$sq1="SLlLC1 tìg_name I80M teams WuL8L ìb1_team='I^' ^u0 ìtem_type=2 080L8 8Y tìg_name",
$resu1t=$db->query{$sq1),
whì1e {$resu1t->1etch1nto{$row,08_IL1CuM00L_^SS0C))
{
$tìg_name = $rowj'tìg_name'¸,
prìnt "$tìg_name<br>",
)
?>
<hr>
<dìv a1ìgn=center><a hre1=ìndex.php>8eturn to Webreg uome Þage<,a><,dìv>
<,body>
<,htm1>
How to do it in CakePHP? Very simple. The view.
<h2 a1ìgn="center">Web8eg -- vìew Iree ^gents<,h2>
<p>
Þ11CuL8S
<br ,>
64 ReIactoring Legacy Applications Using CakePHP
<br ,>
<?php 1oreach {$1ree_agentsj'pìtchers'¸ as $pìtcher) : ?>
<?= $pìtcherj'Þ1ayer'¸j'tìg_name'¸ ?><br ,>
<?php end1oreach, ?>
<br ,>
8^11L8S
<br ,><br ,>
<?php 1oreach {$1ree_agentsj'batters'¸ as $batter) : ?>
<?= $batterj'Þ1ayer'¸j'tìg_name'¸ ?><br ,>
<?php end1oreach, ?>
<hr>
<dìv a1ìgn="center"><a hre1=",">8eturn to Web8eg uome Þage<,a><,dìv>
Simple Data Collection For Display
The action couldn`t be easier.
c1ass Iree^gentsContro11er extends ^ppContro11er {
...
pub1ìc 1unctìon vìew{) {
$thìs->Þ1ayer->contaìn{),
$order = 'Þ1ayer.tìg_name',
$condìtìons = array{
'Þ1ayer.ìb1_team' => 'I^',
'Þ1ayer.ìtem_type' => 1
),
$1ree_agentsj'pìtchers'¸ = $thìs->Þ1ayer->fnd{'a11', compact{'order', 'condì-
tìons')),
$condìtìonsj'Þ1ayer.ìtem_type'¸ = 2,
$1ree_agentsj'batters'¸ = $thìs->Þ1ayer->fnd{'a11', compact{'order', 'condì-
tìons')),
$thìs->set{'1ree_agents', $1ree_agents),
65 ReIactoring Legacy Applications Using CakePHP
)
)
As in other actions, notice the use oI contain() to limit the data set being returned to just records Irom
the Player model.
Grouping Display Output By Criteria
Elsewhere in the application, we also have to display a list oI players grouped by roster. This Iunc-
tionality isn`t really needed any more aIter I moved it to the CakePHP application that runs the web-
site Ior the league. However, it is a great example oI grouping results together by specifc criteria.
Here`s the old code:
<?php
,, vìew_rosters.php
requìre '08.php',
requìre 'db_confg.php',
,, 6enerates a team-by-team roster report
?>
<htm1>
<head>
<tìt1e>Web8eg -- vìew 8osters<,tìt1e>
<,head>
<body>
<h2 a1ìgn="center">Web8eg -- vìew 8osters<,h2>
<?php
1unctìon dìsp1ay_rosters{$team_1ìst)
{
$db = & 08::connect{0Su),
,, goes through array dìsp1ayìng the rosters 1or the teams on the 1ìst
66 ReIactoring Legacy Applications Using CakePHP
1oreach {$team_1ìst as $team)
{
prìnt "$team<br>===<br><br>",
,, 0ìsp1ay the pìtchers frst
prìnt "Þ11CuL8S<br>",
$sq1="SLlLC1 tìg_name,comments,status I80M teams WuL8L ìb1_team='$team' ^u0 ìtem_
type=1 080L8 8Y tìg_name",
$resu1t=$db->query{$sq1),
$counter=0,
whì1e {$resu1t->1etch1nto{$row)) {
$counter++,
$p1ayer_name=$rowj0¸,
$comments=$rowj1¸,
$status=$rowj2¸,
$status_fag="&nbsp,",
$carded_status="&nbsp",
ì1 {$status==1) $status_fag="*",
echo " {$counter). {$status_fag) {$p1ayer_name) -- {$comments) {$carded_
status)<br>",
)
,, uow, show the hìtters
prìnt "<br>8^11L8S<br>",
$sq1="SLlLC1 tìg_name,comments,status I80M teams WuL8L ìb1_team='$team' ^u0 ìtem_
type=2 080L8 8Y tìg_name",
$resu1t=$db->query{$sq1),
67 ReIactoring Legacy Applications Using CakePHP
whì1e {$resu1t->1etch1nto{$row,08_IL1CuM00L_^SS0C)) {
$counter++,
$p1ayer_name=$rowj'tìg_name'¸,
$comments=$rowj'comments'¸,
$status=$rowj'status'¸,
$status_fag="&nbsp,",
ì1 {$status==1) $status_fag="*",
prìnt " $counter. $status_fag $p1ayer_name -- $comments<br>",
)
,, Þrìnt out b1ank spots ì1 there are 1ess than 2S spots on the roster
ì1 {$counter<2S)
{
whì1e {$counter<2S)
{
$counter++,
prìnt " $counter.<br>",
)
)
,, Iìna11y we need to prìnt out theìr dra1t pìcks
prìnt "<br ,>08^I1 Þ1CkS<br ,>",
$sq1 = "SLlLC1 tìg_name I80M teams WuL8L ìb1_team='{$team)' ^u0 ìtem_type=0",
$resu1t = $db->query{$sq1),
$pìcks = ^rray{),
68 ReIactoring Legacy Applications Using CakePHP
whì1e {$resu1t->1etch1nto{$row,08_IL1CuM00L_^SS0C)) {
$pìcksj¸ = trìm{$rowj'tìg_name'¸),
)
prìnt ìmp1ode{", ", $pìcks),
prìnt "<br><br>",
)
)
,, create an array wìth the rosters seperated by con1erence
$ac_teams=^rray{'C0u','181','u^6','8uI','MCM','WMS','Þu1','80W','Þ^0','S1l','Þ08','l^W'),
sort{$ac_teams),
$nc_teams=^rray{'S0q','CSÞ','M1u','C^J','SCS','8u2','018','C0l','SÞ0','SL^','M^0','6^S'),
sort{$nc_teams),
prìnt "^merìcan Con1erence<br><br>",
dìsp1ay_rosters{$ac_teams),
prìnt "<br><br>uatìona1 Con1erence<br><br>",
dìsp1ay_rosters{$nc_teams),
?>
<hr>
<dìv a1ìgn="center">8eturn to <a hre1=ìndex.php>maìn page<,a><,dìv>
<,body>
<,htm1>
That`s quite a bit oI code. CakePHP to the rescue!
<h2>Web8eg -- vìew 8osters<,h2>
<?php 1oreach {$1eague_rosters as $con1erence => $rosters) : ?>
<?= $con1erence ?>
<br ,><br ,>
<?php 1oreach {$rosters as $team => $contents) : ?>
69 ReIactoring Legacy Applications Using CakePHP
<br ,><?= $team ?><br ,>
===
<br ,><br ,>
<?php $counter = 1 ?>
Þ11CuL8S<br ,>
<?php 1oreach {$contentsj'pìtchers'¸ as $pìtcher) : ?>
<?= $counter ?>. <?= {$pìtcherj'Þ1ayer'¸j'status'¸ == 1) ? '*' : nu11 ?> <?=
$pìtcherj'Þ1ayer'¸j'tìg_name'¸ ?> <?= $pìtcherj'Þ1ayer'¸j'comments'¸ ?><br ,>
<?php $counter++ ?>
<?php end1oreach, ?>
<br ,>
8^11L8S<br ,>
<?php 1oreach {$contentsj'batters'¸ as $batter) : ?>
<?= $counter ?>. <?= {$batterj'Þ1ayer'¸j'status'¸ == 1) ? '*' : nu11 ?> <?=
$batterj'Þ1ayer'¸j'tìg_name'¸ ?> <?= $batterj'Þ1ayer'¸j'comments'¸ ?><br ,>
<?php $counter++ ?>
<?php end1oreach, ?>
<br ,>
08^I1 Þ1CkS<br ,>
<?php $dra1t_pìcks = Set::extract{',Þ1ayer,tìg_name', $contentsj'dra1tpìcks'¸) ?>
<?php natsort{$dra1t_pìcks) ?>
<?= ìmp1ode{', ', $dra1t_pìcks) ?>
<br ,>
<?php end1oreach, ?>
<?php end1oreach, ?>
<hr>
<dìv a1ìgn="center">8eturn to <a hre1=",">maìn page<,a><,dìv>
Doesn`t that look better? Now fnally the action that pulls it all together.
70 ReIactoring Legacy Applications Using CakePHP
c1ass 8ostersContro11er extends ^ppContro11er {

...
pub1ìc 1unctìon vìew{) {
$thìs->page1ìt1e = 'Web8eg -- vìew 8osters',
$teams = array{'C0u','181','u^6','8uI','MCM','WMS','Þu1','80W','Þ^0','S1l','Þ08','
l^W'),
$1eague_rostersj'^merìcan Con1erence'¸ = $thìs->Þ1ayer->group8y8oster{$teams),
$teams = array{'S0q','CSÞ','M1u','C^J','SCS','8u2','018','C0l','SÞ0','SL^','M^0','
6^S'),
$1eague_rostersj'uatìona1 Con1erence'¸ = $thìs->Þ1ayer->group8y8oster{$teams),
$thìs->set{'1eague_rosters', $1eague_rosters),
)
)
Business Logic Where It Belongs
Those oI you with sharp eyes have noticed that I am calling a method inside Player called groupB-
yRoster(). That does all the 'heavy liIting¨ as it were. Here`s what that method looks like.
c1ass Þ1ayer extends ^ppMode1 {
...
pub1ìc 1unctìon group8y8oster{$teams) {
sort{$teams),
$rosters = array{),
1oreach {$teams as $team) {
$order = 'Þ1ayer.tìg_name',
$ìtem_types = array{
71 ReIactoring Legacy Applications Using CakePHP
'pìtchers' => 1,
'batters' => 2,
'dra1tpìcks' => 0
),
1oreach {$ìtem_types as $fe1d => $ìtem_type) {
$thìs->contaìn{),
$condìtìons = array{
'Þ1ayer.ìtem_type' => $ìtem_type,
'Þ1ayer.ìb1_team' => $team
),
$rostersj$team¸j$fe1d¸ = $thìs->fnd{'a11', compact{'condìtìons', 'order')),
)
)
return $rosters,
)
...
)
This method accepts an array oI teams and then builds a hash out oI the result set Irom the Player
model. Again, much tighter code than what we used to have, which is what we`re really aIter here.
72 ReIactoring Legacy Applications Using CakePHP
Chapter 10 -- Playing With Date Ranges
The fnal bit oI Iunctionality that we have to deal with Ior WebReg is the displaying oI league transac-
tions based on a date range.
<?php
,, vìew_transactìons.php
,, 6enerates a team-by-team transactìon report based on a date range
?>
<htm1>
<head>
<tìt1e>Web8eg -- vìew 1ransactìons<,tìt1e>
<,head>
<body>
<h2 a1ìgn="center">Web8eg -- vìew 1ransactìons<,h2>
<?php
requìre_once '08.php',
requìre_once 'db_confg.php',
$task="",
ì1 {ìsset{$_Þ0S1j"task"¸)) $task=$_Þ0S1j"task"¸,
ì1 {$task=="show_report")
{
$1rom_date=$_Þ0S1j"1rom_year"¸."-".$_Þ0S1j"1rom_month"¸."-".$_Þ0S1j"1rom_day"¸,
$to_date=$_Þ0S1j"to_year"¸."-".$_Þ0S1j"to_month"¸."-".$_Þ0S1j"to_day"¸,
73 ReIactoring Legacy Applications Using CakePHP
ì1 {$1rom_date>$to_date)
{
?>
<dìv a1ìgn="center">
<1ont co1or="red">1he "1rom" date cannot be newer than the "to" date<,1ont>
<,dìv>
<?php
$task="",
)
e1se
{
,, Co11ect a11 the transactìons that took p1ace ìn that date range
$sq1="SLlLC1 ìb1_team,1og_entry I80M transactìon_1og WuL8L {transactìon_
date>='$1rom_date' ^u0 transactìon_date<='$to_date') 080L8 8Y ìb1_team",
$db =& 08::connect{0Su),
$resu1t=$db->query{$sq1),
ì1 {$resu1t!=I^lSL)
{
whì1e {$resu1t->1etch1nto{$row,08_IL1CuM00L_^SS0C))
{
$ìb1_team=$rowj'ìb1_team'¸,
$1og_entry=$rowj'1og_entry'¸,
$transactìonj$ìb1_team¸j¸=$1og_entry,
)
)
ì1 {count{$transactìon)>0)
74 ReIactoring Legacy Applications Using CakePHP
{
?>
1ransactìons 1or <b><?php prìnt $1rom_date,?><,b> to <b><?php prìnt $to_
date,?><,b><br><br>
<tab1e>
<?php
1oreach {$transactìon as $ìb1_team=>$key)
{
$prìnted_team_name=I^lSL,
1oreach {$key as $1og_entry)
{
?>
<tr>
<?php
ì1 {$prìnted_team_name==I^lSL)
{
?>
<tr><td><?php prìnt $ìb1_team,?><,td><td>-- <?php prìnt $1og_en-
try,?><,td><,tr>
<?php
$prìnted_team_name=18uL,
)
e1se
{
?>
<tr><td><,td><td>-- <?php prìnt $1og_entry,?><,td><,tr>
<?php
)
)
75 ReIactoring Legacy Applications Using CakePHP
)
?>
<,tab1e>
<?php
)
)
)
ì1 {$task=="")
{
,, 0ìsp1ay range o1 years
$year_drop-down = "",
1or {$x=200S,$x<=date{'Y'),$x++)
{
ì1 {$x == date{'Y')) {
$year_drop-down .= "<optìon va1ue=$x se1ected>" . $x . "<,optìon>",
) e1se {
$year_drop-down .= "<optìon va1ue=$x>".$x."<,optìon>",
)
)
,, 0ìsp1ay range 1or months
$month_drop-down="<optìon va1ue='01'>January<,optìon>",
$month_drop-down.="<optìon va1ue='02'>Iebruary<,optìon>",
$month_drop-down.="<optìon va1ue='02'>March<,optìon>",
$month_drop-down.="<optìon va1ue='04'>^prì1<,optìon>",
76 ReIactoring Legacy Applications Using CakePHP
$month_drop-down.="<optìon va1ue='0S'>May<,optìon>",
$month_drop-down.="<optìon va1ue='06'>June<,optìon>",
$month_drop-down.="<optìon va1ue='07'>Ju1y<,optìon>",
$month_drop-down.="<optìon va1ue='08'>^ugust<,optìon>",
$month_drop-down.="<optìon va1ue='09'>September<,optìon>",
$month_drop-down.="<optìon va1ue='10'>0ctober<,optìon>",
$month_drop-down.="<optìon va1ue='11'>uovember<,optìon>",
$month_drop-down.="<optìon va1ue='12'>0ecember<,optìon>",
,, 0ìsp1ay range 1or days
$day_drop-down = "",
1or{$x=1,$x<=21,$x++)
{
ì1 {$x<10) $x="0".$x,
$day_drop-down.="<optìon va1ue='".$x."'>".$x."<,optìon>",
)
,, uow, show the 1orm so they can pìck a date range.
?>
<dìv a1ìgn=center>
<1orm actìon=<?php prìnt $_SL8vL8j"ÞuÞ_SLlI"¸,?> method=post>
<ìnput name="task" type="hìdden" va1ue="show_report">
Þ1ease se1ect a date range 1or the report
<tab1e>
<tr>
<td><b>Irom<,b><,td>
<td><se1ect name="1rom_year"><?php prìnt $year_drop-down,?><,se1ect><,td>
<td><se1ect name="1rom_month"><?php prìnt $month_drop-down,?><,se1ect><,td>
77 ReIactoring Legacy Applications Using CakePHP
<td><se1ect name="1rom_day"><?php prìnt $day_drop-down,?><,se1ect><,td>
<,tr>
<tr>
<td><b>1o<,b><,td>
<td><se1ect name="to_year"><?php prìnt $year_drop-down,?><,se1ect><,td>
<td><se1ect name="to_month"><?php prìnt $month_drop-down,?><,se1ect><,td>
<td><se1ect name="to_day"><?php prìnt $day_drop-down,?><,se1ect><,td>
<,tr>
<tr>
<td co1span=4 a1ìgn="center"><ìnput type="submìt" va1ue="8un 8eport"><,td>
<,tr>
<,tab1e>
<,1orm>
<?php
)
?>
<hr>
<dìv a1ìgn="center"><a hre1="ìndex.php">8eturn 1o Web8eg uome Þage<,a><,dìv>
<,body>
<,htm1>
Real World Date Forms
Man, is that ever a lot oI code to do something very simple. Let`s fx this up the CakePHP way. First
step was to create a controller / action pair to display the Iorm that allows you to pick the date range.
<dìv a1ìgn="center">
<h2>Web8eg -- vìew 1ransactìons<,h2>
<p>Þ1ease se1ect a date range 1or the report<,p>
<?= $1orm->create{'1ransactìonlog', array{'ur1' => ',transactìons,vìew')) ?>
<tab1e>
<tr>
78 ReIactoring Legacy Applications Using CakePHP
<td><b>Irom<,b><,td>
<td>
<?= $1orm->year{'1ransactìonlog.start_date', 200S, date{'Y'), date{'Y'),
nu11, 1a1se) ?>
<?= $1orm->month{'1ransactìonlog.start_date', date{'M'), nu11, 1a1se) ?>
<?= $1orm->day{'1ransactìonlog.start_date', date{'d'), nu11, 1a1se) ?>
<,td>
<,tr>
<tr>
<td><b>1o<,b><,td>
<td>
<?= $1orm->year{'1ransactìonlog.end_date', 200S, date{'Y'), date{'Y'),
nu11, 1a1se) ?>
<?= $1orm->month{'1ransactìonlog.end_date', date{'M'), nu11, 1a1se) ?>
<?= $1orm->day{'1ransactìonlog.end_date', date{'d'), nu11, 1a1se) ?>
<,td>
<,tr>
<,tab1e>
<?= $1orm->submìt{'8un 8eport') ?>
<?= $1orm->end{) ?>
<,dìv>
The view Ior actually displaying those transactions:
<h2 a1ìgn="center">Web8eg -- vìew 1ransactìons<,h2>
<p>1ransactìons 1or <b><?= $start_date ?><,b> to <b><?= $end_date ?><,b><,p>
<br ,>
<tab1e>
<?php $current_team = '' ?>
<?php 1oreach {$transactìons as $team => $ìtems) : ?>
<?php 1oreach {$ìtems as $ìtem) : ?>
<tr>
79 ReIactoring Legacy Applications Using CakePHP
<?php ì1 {$current_team != $team) : ?>
<td><?= $team ?><,td>
<?php $current_team = $team ?>
<?php e1se : ?>
<td>&nbsp,<,td>
<?php endì1, ?>
<td>-- <?= $ìtem ?><,td>
<?php end1oreach, ?>
<,tr>
<?php end1oreach, ?>
<,tab1e>
<hr>
<dìv a1ìgn="center"><a hre1=",">8eturn 1o Web8eg uome Þage<,a><,dìv>
The controller does the work in pulling the transactions together, just as you would expect. Can you
believe the controller is just 24 lines?
Date Form Automagic
c1ass 1ransactìonsContro11er extends ^ppContro11er {
pub1ìc $name = '1ransactìons',
pub1ìc $uses = array{'1ransactìonlog'),
pub1ìc $he1pers = array{'utm1', 'Javascrìpt', 'Iorm'),
pub1ìc 1unctìon ìndex{) {
$thìs->page1ìt1e = 'Web8eg -- vìew 1ransactìons',
)
pub1ìc 1unctìon vìew{) {
ì1 {!$thìs->data) {
$thìs->redìrect{',transactìons'),
80 ReIactoring Legacy Applications Using CakePHP
)
$thìs->page1ìt1e = 'Web8eg -- vìew 1ransactìons',
$transactìons = $thìs->1ransactìonlog->get8y0ate{$thìs->data),
$start_date = ìmp1ode{'-', $thìs->dataj'1ransactìonlog'¸j'start_date'¸),
$end_date = ìmp1ode{'-', $thìs->dataj'1ransactìonlog'¸j'end_date'¸),
$thìs->set{'start_date', $start_date),
$thìs->set{'end_date', $end_date),
$thìs->set{'transactìons', $transactìons),
)
)
Remember in the original page how I had to parse out the date by hand? CakePHP is smart enough to
handle all that date stuII Ior you.
You just knew there had to be a catch. In order to get the results in the Iormat needed Ior the view, I
created a method in the transactions model
c1ass 1ransactìonlog extends ^ppMode1 {

...

pub1ìc 1unctìon get8y0ate{$data) {
$thìs->contaìn{),
$start_date = $dataj'1ransactìonlog'¸j'start_date'¸j'year'¸ . ' ' .
$dataj'1ransactìonlog'¸j'start_date'¸j'month'¸ . ' ' . $dataj'1ransactìonlog'¸j'start_
date'¸j'day'¸,
$end_date = $dataj'1ransactìonlog'¸j'end_date'¸j'year'¸ . ' ' .
$dataj'1ransactìonlog'¸j'end_date'¸j'month'¸ . ' ' . $dataj'1ransactìonlog'¸j'end_date'¸
j'day'¸,
$fe1ds = "ìb1_team, 1og_entry, transactìon_date",
$condìtìons = array{
81 ReIactoring Legacy Applications Using CakePHP
'1ransactìonlog.transactìon_date >=' => $start_date,
'1ransactìonlog.transactìon_date <=' => $end_date
),
$order = '1ransactìonlog.transactìon_date',
$resu1ts = $thìs->fnd{'a11', compact{'fe1ds', 'condìtìons', 'order', 'group')),
$transactìons = array{),
1oreach {$resu1ts as $resu1t) {
$transactìonsj$resu1tj'1ransactìonlog'¸j'ìb1_team'¸¸j¸ =
$resu1tj'1ransactìonlog'¸j'1og_entry'¸,
)
ksort{$transactìons),
return $transactìons,
)
)
For those not Iamiliar with PHP`s sorting Iunctions, I am using ksort(...) to (not surprisingly) sort the
transactions in alphabetical by the key Ior the transactions array, which is the ibl¸team name feld.
Since the existing code spit out results in ibl¸team order, it`s better to stick that logic in the model
where it really belongs.
82 ReIactoring Legacy Applications Using CakePHP
Chapter 11 -- Wrapping It All Up
Wow! We`ve certainly covered a lot oI material in a short period oI time. Let`s think about what
we`ve actually covered:
* Why Irameworks get no respect, despite the benefts they can give
* Why 'Iat models, skinny controllers¨ is a great paradigm in CakePHP
* How to separate your business logic Irom your display logic
* Turning database tables into CakePHP models
* How to understand and create the associations between your CakePHP models
* User-submitted data handling in your controllers
* Real-world examples oI using the Iorm helper
The main thing I hope I`ve been able to show you is that it isn`t as diIfcult at all to take an older,
legacy application written in PHP and convert it over to using CakePHP. So many applications re-
ally need the structure that a Iramework can provide. While you could create your own (aIter all,
CakePHP started out the same way) I really encourage you to take a very honest look at your require-
ments and see iI there is an existing Iramework that can do the job and allow you to customize what
you need. I understand that everyone thinks their problems are unique, but there is no need to rein-
vent the wheel as the common saying goes.
II you have any questions or comments about the book, Ieel Iree to send me Ieedback via my blog
¸TheKeyboard, which can be Iound at http://www.littlehart.net/atthekeyboard. II you wish to
contact me via email, send it to me at chartjes¸littlehart.net. I do read every email I get but volume
sometimes does not permit me to respond to each and every one.
By this point I`m confdent that you have gotten a solid understanding oI how to move your older
legacy code into a CakePHP environment. Some oI it will obviously not be as easy to reIactor as
these examples, but iI you really take the time to try and understand how to solve the problem you are
Iacing in terms oI CakePHP`s conventions, I`m sure you`ll fnd a way.
83 ReIactoring Legacy Applications Using CakePHP