Models are the bread and butter of CakePHP applications. By creating a CakePHP model that will interact with our database, we’ll have the foundation in place needed to do our view, add, edit, and delete operations later.
CakePHP’s model class files are split between Table
and Entity
objects.
Table
objects provide access to the collection of entities stored in a
specific table and go in src/Model/Table. The file we’ll be creating will
be saved to src/Model/Table/ArticlesTable.php. The completed file should
look like this:
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
class ArticlesTable extends Table
{
public function initialize(array $config): void
{
$this->addBehavior('Timestamp');
}
}
Naming conventions are very important in CakePHP. By naming our Table object
ArticlesTable
, CakePHP can automatically infer that this Table object will
be used in the ArticlesController
, and will be tied to a database table called
articles
.
Note
CakePHP will dynamically create a model object for you if it cannot find a corresponding file in src/Model/Table. This also means that if you accidentally name your file wrong (i.e. articlestable.php or ArticleTable.php), CakePHP will not recognize any of your settings and will use the generated model instead.
For more on models, such as callbacks, and validation, check out the Database Access & ORM chapter of the Manual.
Note
If you completed Part 1 of the Blog Tutorial and created the articles
table in
our Blog database you can leverage CakePHP’s bake console and its code
generation capabilities to create the ArticlesTable
model:
bin/cake bake model Articles
For more on bake and its code generation features please visit Code Generation with Bake.
Next, we’ll create a controller for our articles. The controller is where all interaction with articles will happen. In a nutshell, it’s the place where you play with the business logic contained in the models and get work related to articles done. We’ll place this new controller in a file called ArticlesController.php inside the src/Controller directory. Here’s what the basic controller should look like:
// src/Controller/ArticlesController.php
namespace App\Controller;
class ArticlesController extends AppController
{
}
Now, let’s add an action to our controller. Actions often represent a single function or interface in an application. For example, when users request www.example.com/articles/index (which is also the same as www.example.com/articles/), they might expect to see a listing of articles. The code for that action would look like this:
// src/Controller/ArticlesController.php
namespace App\Controller;
class ArticlesController extends AppController
{
public function index()
{
$articles = $this->Articles->find()->all();
$this->set(compact('articles'));
}
}
By defining function index()
in our ArticlesController
, users can now
access the logic there by requesting www.example.com/articles/index. Similarly,
if we were to define a function called foobar()
, users would be able to
access that at www.example.com/articles/foobar.
Warning
You may be tempted to name your controllers and actions a certain way to obtain a certain URL. Resist that temptation. Follow CakePHP Conventions (capitalization, plural names, etc.) and create readable, understandable action names. You can map URLs to your code using Routing covered later on.
The single instruction in the action uses set()
to pass resultset
from the controller to the view (which we’ll create next). The find()
method
of the ArticlesTable
object returns an instance of Cake\\ORM\\Query
and
calling its all()
method returns as instance of Cake\\Collection\\CollectionInterface
which is set as a view variable called ‘articles’.
Note
If you completed Part 1 of the Blog Tutorial and created the articles
table in
your Blog database you can leverage CakePHP’s bake console and its code
generation capabilities to create the ArticlesController class:
bin/cake bake controller Articles
For more on bake and its code generation features please visit Code Generation with Bake.
To learn more about CakePHP’s controllers, check out the Controllers chapter.
Now that we have our data flowing from our model, and our application logic is defined by our controller, let’s create a view for the index action we created above.
CakePHP views are just presentation-flavored fragments that fit inside an application’s layout. For most applications, they’re HTML mixed with PHP, but they may end up as XML, CSV, or even binary data.
A layout is presentation code that is wrapped around a view. Multiple layouts can be defined, and you can switch between them, but for now, let’s just use the default.
Remember in the last section how we assigned the ‘articles’ variable
to the view using the set()
method? That would hand down the query
object collection to the view to be invoked with a foreach
iteration.
CakePHP’s template files are stored in templates inside a folder named after the controller they correspond to (we’ll have to create a folder named ‘Articles’ in this case). To format this article data in a nice table, our view code might look something like this:
<!-- File: templates/Articles/index.php -->
<h1>Blog articles</h1>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Created</th>
</tr>
<!-- Here is where we iterate through our $articles query object, printing out article info -->
<?php foreach ($articles as $article): ?>
<tr>
<td><?= $article->id ?></td>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
Hopefully this should look somewhat simple.
You might have noticed the use of an object called $this->Html
. This is an
instance of the CakePHP Cake\View\Helper\HtmlHelper
class.
CakePHP comes with a set of view helpers that make things like linking, form
output a snap. You can learn more about how to use them in
Helpers, but what’s important to note here is that the link()
method will generate an HTML link with the given title (the first parameter) and
URL (the second parameter).
When specifying URLs in CakePHP, it is recommended that you use the
array format. This is explained in more detail in the section on
Routes. Using the array format for URLs allows you to take
advantage of CakePHP’s reverse routing capabilities. You can also
specify URLs relative to the base of the application in the form of
/controller/action/param1/param2
or use named routes.
At this point, you should be able to point your browser to http://www.example.com/articles/index. You should see your view, correctly formatted with the title and table listing of the articles.
If you happened to have clicked on one of the links we created in
this view (that link a article’s title to a URL /articles/view/some\_id
),
you were probably informed by CakePHP that the action hasn’t yet
been defined. If you were not so informed, either something has
gone wrong, or you actually did define it already, in which case
you are very sneaky. Otherwise, we’ll create it in the
ArticlesController
now:
// src/Controller/ArticlesController.php
namespace App\Controller;
class ArticlesController extends AppController
{
public function index()
{
$this->set('articles', $this->Articles->find()->all());
}
public function view($id = null)
{
$article = $this->Articles->get($id);
$this->set(compact('article'));
}
}
The set()
call should look familiar. Notice we’re using
get()
rather than find()
because we only really want
a single article’s information.
Notice that our view action takes a parameter: the ID of the article
we’d like to see. This parameter is handed to the action through
the requested URL. If a user requests /articles/view/3
, then the value
‘3’ is passed as $id
.
We also do a bit of error checking to ensure a user is actually accessing
a record. By using the get()
function in the Articles table, we make sure
the user has accessed a record that exists. In case the requested article is not
present in the database, or the id is false the get()
function will throw
a NotFoundException
.
Now let’s create the view for our new ‘view’ action and place it in templates/Articles/view.php
<!-- File: templates/Articles/view.php -->
<h1><?= h($article->title) ?></h1>
<p><?= h($article->body) ?></p>
<p><small>Created: <?= $article->created->format(DATE_RFC850) ?></small></p>
Verify that this is working by trying the links at /articles/index
or
manually requesting an article by accessing /articles/view/{id}
, replacing
{id}
by an article ‘id’.
Reading from the database and showing us the articles is a great start, but let’s allow for the adding of new articles.
First, start by creating an add()
action in the
ArticlesController
:
// src/Controller/ArticlesController.php
namespace App\Controller;
use App\Controller\AppController;
class ArticlesController extends AppController
{
public function initialize(): void
{
parent::initialize();
$this->loadComponent('Flash'); // Include the FlashComponent
}
public function index()
{
$this->set('articles', $this->Articles->find()->all());
}
public function view($id)
{
$article = $this->Articles->get($id);
$this->set(compact('article'));
}
public function add()
{
$article = $this->Articles->newEmptyEntity();
if ($this->request->is('post')) {
$article = $this->Articles->patchEntity($article, $this->request->getData());
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to add your article.'));
}
$this->set('article', $article);
}
}
Note
You need to include the Flash component in any controller
where you will use it. If necessary, include it in your AppController
.
Here’s what the add()
action does: if the HTTP method of the
request was POST, try to save the data using the Articles model. If for some
reason it doesn’t save, just render the view. This gives us a
chance to show the user validation errors or other warnings.
Every CakePHP request includes a ServerRequest
object which is accessible using
$this->request
. The request object contains useful information regarding the
request that was just received, and can be used to control the flow of your
application. In this case, we use the Cake\Http\ServerRequest::is()
method to check that the request is a HTTP POST request.
When a user uses a form to POST data to your application, that
information is available in $this->request->getData()
. You can use the
pr()
or debug()
functions to print it out if you want to
see what it looks like.
We use FlashComponent’s success()
and error()
methods to set a message
to a session variable. These methods are provided using PHP’s magic method
features.
Flash messages will be displayed on the page after redirection. In the layout we
have <?= $this->Flash->render() ?>
which displays the message and clears the
corresponding session variable. The controller’s
Cake\Controller\Controller::redirect
function redirects to another
URL. The param ['action' => 'index']
translates to URL /articles i.e the
index action of the ArticlesController
. You can refer to
Cake\Routing\Router::url()
function on the API to see the formats in which you can specify a URL for
various CakePHP functions.
Calling the save()
method will check for validation errors and
abort the save if any occur. We’ll discuss how those errors are
handled in the following sections.
CakePHP goes a long way toward taking the monotony out of form input validation. Everyone hates coding up endless forms and their validation routines. CakePHP makes it easier and faster.
To take advantage of the validation features, you’ll need to use CakePHP’s
Form helper in your views. The
Cake\View\Helper\FormHelper
is available by default to all views
at $this->Form
.
Here’s our add view:
<!-- File: templates/Articles/add.php -->
<h1>Add Article</h1>
<?php
echo $this->Form->create($article);
echo $this->Form->control('title');
echo $this->Form->control('body', ['rows' => '3']);
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();
?>
We use the FormHelper to generate the opening tag for an HTML
form. Here’s the HTML that $this->Form->create()
generates:
<form method="post" action="/articles/add">
If create()
is called with no parameters supplied, it assumes
you are building a form that submits via POST to the current controller’s
add()
action (or edit()
action when id
is included in
the form data).
The $this->Form->control()
method is used to create form elements
of the same name. The first parameter tells CakePHP which field
they correspond to, and the second parameter allows you to specify
a wide array of options - in this case, the number of rows for the
textarea. There’s a bit of introspection and automagic here:
control()
will output different form elements based on the model
field specified.
The $this->Form->end()
call ends the form. Outputting hidden inputs if
CSRF/Form Tampering prevention is enabled.
Now let’s go back and update our templates/Articles/index.php
view to include a new “Add Article” link. Before the <table>
, add
the following line:
<?= $this->Html->link('Add Article', ['action' => 'add']) ?>
You may be wondering: how do I tell CakePHP about my validation requirements? Validation rules are defined in the model. Let’s look back at our Articles model and make a few adjustments:
// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class ArticlesTable extends Table
{
public function initialize(array $config): void
{
$this->addBehavior('Timestamp');
}
public function validationDefault(Validator $validator): Validator
{
$validator
->notEmptyString('title')
->requirePresence('title', 'create')
->notEmptyString('body')
->requirePresence('body', 'create');
return $validator;
}
}
The validationDefault()
method tells CakePHP how to validate your data when
the save()
method is called. Here, we’ve specified that both the body and
title fields must not be empty, and are required for both create and update
operations. CakePHP’s validation engine is strong, with a number of pre-built
rules (credit card numbers, email addresses, etc.) and flexibility for adding
your own validation rules. For more information on that
setup, check the Validation documentation.
Now that your validation rules are in place, use the app to try to add
an article with an empty title or body to see how it works. Since we’ve used the
Cake\View\Helper\FormHelper::control()
method of the FormHelper to
create our form elements, our validation error messages will be shown
automatically.
Post editing: here we go. You’re a CakePHP pro by now, so you
should have picked up a pattern. Make the action, then the view.
Here’s what the edit()
action of the ArticlesController
would look
like:
// src/Controller/ArticlesController.php
public function edit($id = null)
{
$article = $this->Articles->get($id);
if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->getData());
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been updated.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to update your article.'));
}
$this->set('article', $article);
}
This action first ensures that the user has tried to access an existing record.
If they haven’t passed in an $id
parameter, or the article does not
exist, we throw a NotFoundException
for the CakePHP ErrorHandler to take
care of.
Next the action checks whether the request is either a POST or a PUT request. If
it is, then we use the POST data to update our article entity by using the
patchEntity()
method. Finally we use the table object to save the entity
back or kick back and show the user validation errors.
The edit view might look something like this:
<!-- File: templates/Articles/edit.php -->
<h1>Edit Article</h1>
<?php
echo $this->Form->create($article);
echo $this->Form->control('title');
echo $this->Form->control('body', ['rows' => '3']);
echo $this->Form->button(__('Save Article'));
echo $this->Form->end();
?>
This view outputs the edit form (with the values populated), along with any necessary validation error messages.
CakePHP will determine whether a save()
generates an insert or an
update statement based on the state of the entity.
You can now update your index view with links to edit specific articles:
<!-- File: templates/Articles/index.php (edit links added) -->
<h1>Blog articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Created</th>
<th>Action</th>
</tr>
<!-- Here's where we iterate through our $articles query object, printing out article info -->
<?php foreach ($articles as $article): ?>
<tr>
<td><?= $article->id ?></td>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
<td>
<?= $this->Html->link('Edit', ['action' => 'edit', $article->id]) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
Next, let’s make a way for users to delete articles. Start with a
delete()
action in the ArticlesController
:
// src/Controller/ArticlesController.php
public function delete($id)
{
$this->request->allowMethod(['post', 'delete']);
$article = $this->Articles->get($id);
if ($this->Articles->delete($article)) {
$this->Flash->success(__('The article with id: {0} has been deleted.', h($id)));
return $this->redirect(['action' => 'index']);
}
}
This logic deletes the article specified by $id
, and uses
$this->Flash->success()
to show the user a confirmation
message after redirecting them on to /articles
. If the user attempts to
do a delete using a GET request, the allowMethod()
will throw an Exception.
Uncaught exceptions are captured by CakePHP’s exception handler, and a nice
error page is displayed. There are many built-in
Exceptions that can be used to indicate the various
HTTP errors your application might need to generate.
Because we’re just executing some logic and redirecting, this action has no view. You might want to update your index view with links that allow users to delete articles, however:
<!-- File: templates/Articles/index.php (delete links added) -->
<h1>Blog articles</h1>
<p><?= $this->Html->link('Add Article', ['action' => 'add']) ?></p>
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Created</th>
<th>Actions</th>
</tr>
<!-- Here's where we loop through our $articles query object, printing out article info -->
<?php foreach ($articles as $article): ?>
<tr>
<td><?= $article->id ?></td>
<td>
<?= $this->Html->link($article->title, ['action' => 'view', $article->id]) ?>
</td>
<td>
<?= $article->created->format(DATE_RFC850) ?>
</td>
<td>
<?= $this->Form->postLink(
'Delete',
['action' => 'delete', $article->id],
['confirm' => 'Are you sure?'])
?>
<?= $this->Html->link('Edit', ['action' => 'edit', $article->id]) ?>
</td>
</tr>
<?php endforeach; ?>
</table>
Using postLink()
will create a link
that uses JavaScript to do a POST request deleting our article.
Warning
Allowing content to be deleted using GET requests is dangerous, as web crawlers could accidentally delete all your content.
Note
This view code also uses the FormHelper
to prompt the user with a
JavaScript confirmation dialog before they attempt to delete an
article.
For some, CakePHP’s default routing works well enough. Developers who are sensitive to user-friendliness and general search engine compatibility will appreciate the way that CakePHP’s URLs map to specific actions. So we’ll just make a quick change to routes in this tutorial.
For more information on advanced routing techniques, see Connecting Routes.
By default, CakePHP responds to a request for the root of your site
(for example, http://www.example.com) using its PagesController
, rendering
a view called “home”. Instead, we’ll replace this with our
ArticlesController by creating a routing rule.
CakePHP’s routing is found in config/routes.php. You’ll want to comment out or remove the line that defines the default root route. It looks like this:
$builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
This line connects the URL ‘/’ with the default CakePHP home page. We want it to connect with our own controller, so replace that line with this one:
$builder->connect('/', ['controller' => 'Articles', 'action' => 'index']);
This should connect users requesting ‘/’ to the index()
action of
our ArticlesController
.
Note
CakePHP also makes use of ‘reverse routing’. If, with the above
route defined, you pass
['controller' => 'Articles', 'action' => 'index']
to a
function expecting an array, the resulting URL used will be ‘/’.
It’s therefore a good idea to always use arrays for URLs as this
means your routes define where a URL goes, and also ensures that
links point to the same place.
Keep in mind that this tutorial was very basic. CakePHP has many more features to offer, and is flexible in ways we didn’t wish to cover here for simplicity’s sake. Use the rest of this manual as a guide for building more feature-rich applications.
Now that you’ve created a basic CakePHP application, you can either continue to Blog Tutorial - Part 3, or start your own project. You can also peruse the Using CakePHP or API to learn more about CakePHP.
If you need help, there are many ways to get the help you need - please see the Where to Get Help page. Welcome to CakePHP!
These are common tasks people learning CakePHP usually want to study next:
Layouts: Customizing your website layout
Elements: Including and reusing view snippets
Code Generation with Bake: Generating basic CRUD code
Blog Tutorial - Authentication: User authentication and authorization tutorial