MVC design
It's common enough at this point to jump in and start cranking out code
based on what the application is intended to do. A login page, a
database, registration — BAM! Simple enough. And it usually is. Until
you want to change how it looks, how it interacts with the database,
change validation rules, or change essentially anything about the
application at all. Then things start to get complicated.
CakePHP is designed to make writing the application as easy as
possible, while giving you something you can maintain long-term. The
tutorial will show you how to build Tor using the MVC design pattern.
CakePHP will show you how it can be easy. You'll see for yourself why
it makes sense.
The MVC design pattern breaks an application into three distinct
layers: Data management, UI and Logic, respectively. MVC was first
described in the book Design Patterns: Elements of Reusable
Object-Oriented Software, also known as the "Gang Of Four"
book. A full discussion of design patterns and MVC is outside the
scope of this tutorial, but it will be helpful to touch on what the
three pieces mean — specifically, in the CakePHP world.
Model
Users, products, prices, messages — when you boil it all down, it's
all just data. You make sure the data is what you want, it goes into a
database, then you have to get it back out. It will be useful for all
the data-handling aspects of Tor to live in one place. That where
model comes in.
Model is primarily concerned with handling data. Getting data from a
database, inserting data into a database, validating data — all of
this takes place within the model.
An individual model typically is used to access a specific table in the
database. Generally, a model will associate to a database table if the
table name is a plural form of the model name. For example, a Product
model will, by default, associate to a Products table. All user-defined models will extend the CakePHP
AppModel class.
The user model
Having created a users table for Tor, you will need a user model to
interact with the table. The user model will save your user data, get
it back out of the table for you, and verify that the user data is in
an acceptable format. For now, this model can be a stub. It should be
created as app/models/user.php and might look something like Listing
8.
Listing 8. The user model
<?php
class User extends AppModel
{
var $name = 'User';
}
?>
|
Notice the line var $name = 'User';. It is
accepted as best practice to specify the name of the model in
$name.
View
Being able to save, retrieve, and validate your data is pretty useless
without some means of displaying the data. By putting all display and
presentation code in one place, you can change the look and feel of
your application without having to work around application logic and
data-related code.
View is primarily concerned with formatting data for presentation to
the user. The view represents any and all UI work, including all
templates and HTML. CakePHP's view files are regular HTML files
embedded with PHP code.
Ultimately, a view is just a page template. Usually, the view will be
named after the action associated with it. For example, a
display() action would normally have a
display view.
Returning to the sample application, we have created a users table and
a user model to interact with this table. Now we need some views. The
user will need to register an account and log in to the application.
This calls for a register and a login view.
The register view
Before a user can use the Tor application, he will need to register an
account. This will require a register view, which will present the
registration form to the user. You should already know what kind of
user information Tor is looking for — specifically, you will need to
collect a username, password, e-mail address, and first and last
name.
The register view should be created as app/views/users/register.ctp and
might look something like that shown in Listing 9.
Listing 9. The register view
<form action="/users/register" method="post">
<p>Please fill out the form below to register an account.</p>
<label>Username:</label><input name="username" size="40" />
<label>Password:</label><input type="password" name="password" size="40"
/>
<label>Email Address:</label><input name="email" size="40"
maxlength="255" />
<label>First Name:</label><input name="first_name" size="40" />
<label>Last Name:</label><input name="last_name" size="40" />
<input type="submit" value="register" />
</form>
|
It is important to note that the field names are the same as the column
names in your database. This will come into play when we get to the
users controller. Later, when you learn a little more about Cake and
see how Cake Helpers work, you will rewrite this page completely, but
for now this page is regular HTML, just like you have probably always
written.
Controller
With data handling all contained within in the model, and the
presentation layer all contained within the view, the rest of the
application is going to live in the controller. This is where the
application 'does' things — logic, decision-making, workflow, etc. The model manages your data, the view shows it to you, the
controller does everything else.
The controller manages server requests. It accepts user input (URL
requests, form POSTs,
GET requests, etc.), applies logic where
necessary, invokes models where data handling is required, and sends
output through the appropriate view.
Generally, a controller will manage logic for a single model. A
controller contains any number of functions, referred to as actions.
An action typically applies logic and displays a view. All
user-defined controllers will need to extend the CakePHP
AppController class.
In Tor, you have created a users table, a user model to interact with
the table, and views to present registration and login forms to the
user. Those forms will submit to the users controller, invoking the
register and login actions, respectively. The users controller should be created as app/controllers/users_controller.php and should start
out looking something like Listing 10.
Listing 10. The users controller
<?php
class UsersController extends AppController
{
}
?>
|
The register action
You now have a controller. But it's not doing anything yet. Before it
will do anything for you, you'll have to give it some actions to
perform.
Now you are getting down to the nuts and bolts. The user has filled out
a form and submitted it to the application. We will cover data
validation later, but for now, we will just push the registration data
into the database. We do this by adding the register action to the
users controller.
Listing 11. The register action
<?php
class UsersController extends AppController
{
function register()
{
if (!empty($this->params['form']))
{
if ($this->User->save($this->params['form']))
{
$this->flash('Your registration information was accepted.',
'/users/register');
} else {
$this->flash('There was a problem with your registration',
'/users/register');
}
}
}
}
?>
|
The register action starts by looking at the form
($this->params['form']) to see if it
was submitted. If the form was not submitted, the action will not do
anything. Once you know the form was submitted with data, the
controller calls the save method on the user model, which is an
extension of AppModel. The save method will, by default, look for a
table that's a plural of the model itself. In this case, it is looking
for a users table to go with the user model. If the table is found,
the save method will turn the passed array
($this->params['form']) into an
INSERT statement, using the array keys as column names and the array
values as the INSERT values.
Once you know the form was submitted with data, the controller calls
the save method on the user model, which is
an extension of AppModel. The
save method will, by default, look for a
table that's a plural of the model itself. In this case, it is looking
for a users table to go with the user model. If the table is found,
the save method will turn the passed array
($this->params['form']) into an
INSERT statement, using the array keys as
column names and the array values as the
INSERT values. In this case, $this->params['form']
will look like Listing 12.
Listing 12. The $this->params['form'] array
Array
(
[username] => zaphod
[password] => secret
[email] => beeblebrox@heartofgold.hhg
[first_name] => zaphod
[last_name] => beeblebrox
)
|
The save method will build an INSERT
statement from the rest. It would look something like Listing 13.
Listing 13. INSERT statement
INSERT INTO
users
(username, password, email, first_name, last_name)
VALUES
('zaphod', 'secret', 'beeblebrox@heartofgold.hhg', 'zaphod', 'beeblebrox')
|
It should now be apparent why you used the database column names in the
register view for the register form field names. By doing so, you
simplified the process of saving your data significantly.
To continue, if the data inserts successfully, the register action
calls the Flash method. Flash presents a basic message to the user (in
this case, a success or failure message) with a link away from the
message (in this case, back to the register view, since nothing else
has been defined.
Now that you have a user model to interface with your users table, and
a register view to show a registration form, and a users controller
with a register action, you can actually see your application in
action.
Try it out
All the pieces have fallen into place. It's time to bring Tor to life.
Fire up your browser and jump in. Load the register view by going to http://localhost/users/register. You
should see something like Figure 4.
Figure 4. Loading the register
view
Fill out and submit the form. You should see your success message.
Figure 5. Success message
If you look in your database, you should see a record corresponding to
your form submission, complete with a plain-text password (a terribly
bad idea, which we will fix later). Try to fill out the form again, using the same information. You should
see the failure message.
Figure 6. Register failure
If you are using a database that supports the
UNIQUE field restraint, the registration
will fail. During the creation of the users table, the username and
e-mail fields were defined as UNIQUE,
meaning values for those fields cannot be duplicated. CakePHP
recognizes the error when the database throws it and displays an
error.
|