Quick Start Guide

The best way to experience and learn CakePHP is to sit down and build something.To start off we’ll build a simple Content Management application.

Content Management Tutorial

This tutorial will walk you through the creation of a simple CMS application. To start with, we’ll be installing CakePHP,creating our database, and building simple article management.

Here’s what you’ll need:

  • A database server. We’re going to be using MySQL server in this tutorial.You’ll need to know enough about SQL in order to create a database, and runSQL snippets from the tutorial. CakePHP will handle building all the queriesyour application needs. Since we’re using MySQL, also make sure that you havepdo_mysql enabled in PHP.
  • Basic PHP knowledge.Before starting you should make sure that you have got an up to date PHPversion:
  1. php -v

You should at least have got installed PHP 7.2.0 (CLI) or higher.Your webserver’s PHP version must also be of 7.2.0 or higher, andshould be the same version your command line interface (CLI) PHP is.

Getting CakePHP

The easiest way to install CakePHP is to use Composer. Composer is a simple wayof installing CakePHP from your terminal or command line prompt. First, you’llneed to download and install Composer if you haven’t done so already. If youhave cURL installed, it’s as easy as running the following:

  1. curl -s https://getcomposer.org/installer | php

Or, you can download composer.phar from theComposer website.

Then simply type the following line in your terminal from yourinstallation directory to install the CakePHP application skeletonin the cms directory of the current working directory:

  1. php composer.phar create-project --prefer-dist cakephp/app cms

If you downloaded and ran the Composer Windows Installer, then type the following line inyour terminal from your installation directory (ie.C:\wamp\www\dev\cakephp3):

  1. composer self-update && composer create-project --prefer-dist cakephp/app cms

The advantage to using Composer is that it will automatically complete someimportant set up tasks, such as setting the correct file permissions andcreating your config/app.php file for you.

There are other ways to install CakePHP. If you cannot or don’t want to useComposer, check out the Installation section.

Regardless of how you downloaded and installed CakePHP, once your set up iscompleted, your directory setup should look something like the following:

  1. /cms
  2. /bin
  3. /config
  4. /logs
  5. /plugins
  6. /resources
  7. /src
  8. /templates
  9. /tests
  10. /tmp
  11. /vendor
  12. /webroot
  13. .editorconfig
  14. .gitignore
  15. .htaccess
  16. .travis.yml
  17. composer.json
  18. index.php
  19. phpunit.xml.dist
  20. README.md

Now might be a good time to learn a bit about how CakePHP’s directory structureworks: check out the CakePHP Folder Structure section.

If you get lost during this tutorial, you can see the finished result on GitHub.

Checking our Installation

We can quickly check that our installation is correct, by checking the defaulthome page. Before you can do that, you’ll need to start the development server:

  1. cd /path/to/our/app
  2.  
  3. bin/cake server

Note

For Windows, the command needs to be bin\cake server (note the backslash).

This will start PHP’s built-in webserver on port 8765. Open uphttp://localhost:8765 in your web browser to see the welcome page. All thebullet points should be green chef hats other than CakePHP being able to connect toyour database. If not, you may need to install additional PHP extensions, or setdirectory permissions.

Next, we will build our Database and create our first model.

CMS Tutorial - Creating the Database

Now that we have CakePHP installed, let’s set up the database for our CMS application. If you haven’t already done so, createan empty database for use in this tutorial, with a name of your choice, e.g.cake_cms. You can execute the following SQL to create the necessarytables:

  1. USE cake_cms;
  2.  
  3. CREATE TABLE users (
  4. id INT AUTO_INCREMENT PRIMARY KEY,
  5. email VARCHAR(255) NOT NULL,
  6. password VARCHAR(255) NOT NULL,
  7. created DATETIME,
  8. modified DATETIME
  9. );
  10.  
  11. CREATE TABLE articles (
  12. id INT AUTO_INCREMENT PRIMARY KEY,
  13. user_id INT NOT NULL,
  14. title VARCHAR(255) NOT NULL,
  15. slug VARCHAR(191) NOT NULL,
  16. body TEXT,
  17. published BOOLEAN DEFAULT FALSE,
  18. created DATETIME,
  19. modified DATETIME,
  20. UNIQUE KEY (slug),
  21. FOREIGN KEY user_key (user_id) REFERENCES users(id)
  22. ) CHARSET=utf8mb4;
  23.  
  24. CREATE TABLE tags (
  25. id INT AUTO_INCREMENT PRIMARY KEY,
  26. title VARCHAR(191),
  27. created DATETIME,
  28. modified DATETIME,
  29. UNIQUE KEY (title)
  30. ) CHARSET=utf8mb4;
  31.  
  32. CREATE TABLE articles_tags (
  33. article_id INT NOT NULL,
  34. tag_id INT NOT NULL,
  35. PRIMARY KEY (article_id, tag_id),
  36. FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
  37. FOREIGN KEY article_key(article_id) REFERENCES articles(id)
  38. );
  39.  
  40. INSERT INTO users (email, password, created, modified)
  41. VALUES
  42. ('cakephp@example.com', 'secret', NOW(), NOW());
  43.  
  44. INSERT INTO articles (user_id, title, slug, body, published, created, modified)
  45. VALUES
  46. (1, 'First Post', 'first-post', 'This is the first post.', 1, now(), now());

You may have noticed that the articles_tags table used a composite primarykey. CakePHP supports composite primary keys almost everywhere allowing you tohave simpler schemas that don’t require additional id columns.

The table and column names we used were not arbitrary. By using CakePHP’snaming conventions, we can leverage CakePHP moreeffectively and avoid needing to configure the framework. While CakePHP isflexible enough to accommodate almost any database schema, adhering to theconventions will save you time as you can leverage the convention based defaultsCakePHP provides.

Database Configuration

Next, let’s tell CakePHP where our database is and how to connect to it. Replacethe values in the Datasources.default array in your config/app.php filewith those that apply to your setup. A sample completed configuration arraymight look something like the following:

  1. <?php
  2. return [
  3. // More configuration above.
  4. 'Datasources' => [
  5. 'default' => [
  6. 'className' => 'Cake\Database\Connection',
  7. 'driver' => 'Cake\Database\Driver\Mysql',
  8. 'persistent' => false,
  9. 'host' => 'localhost',
  10. 'username' => 'cakephp',
  11. 'password' => 'AngelF00dC4k3~',
  12. 'database' => 'cake_cms',
  13. 'encoding' => 'utf8mb4',
  14. 'timezone' => 'UTC',
  15. 'cacheMetadata' => true,
  16. ],
  17. ],
  18. // More configuration below.
  19. ];

Once you’ve saved your config/app.php file, you should see that ‘CakePHP isable to connect to the database’ section have a green chef hat.

Note

A copy of CakePHP’s default configuration file is found inconfig/app.default.php.

Creating our First Model

Models are the heart of a CakePHP applications. They enable us to read andmodify our data. They allow us to build relations between our data, validatedata, and apply application rules. Models build the foundations necessary tobuild our controller actions and templates.

CakePHP’s models are composed of Table and Entity objects. Tableobjects provide access to the collection of entities stored in a specific table.They are stored in src/Model/Table. The file we’ll be creating will be savedto src/Model/Table/ArticlesTable.php. The completed file should look likethis:

  1. <?php
  2. // src/Model/Table/ArticlesTable.php
  3. namespace App\Model\Table;
  4.  
  5. use Cake\ORM\Table;
  6.  
  7. class ArticlesTable extends Table
  8. {
  9. public function initialize(array $config): void
  10. {
  11. $this->addBehavior('Timestamp');
  12. }
  13. }

We’ve attached the Timestamp behavior which willautomatically populate the created and modified columns of our table.By naming our Table object ArticlesTable, CakePHP can use naming conventionsto know that our model uses the articles table. CakePHP also usesconventions to know that the id column is our table’s primary key.

Note

CakePHP will dynamically create a model object for you if itcannot find a corresponding file in src/Model/Table. This also meansthat if you accidentally name your file wrong (i.e. articlestable.php orArticleTable.php), CakePHP will not recognize any of your settings and willuse the generated model instead.

We’ll also create an Entity class for our Articles. Entities represent a singlerecord in the database, and provide row level behavior for our data. Our entitywill be saved to src/Model/Entity/Article.php. The completed file shouldlook like this:

  1. <?php
  2. // src/Model/Entity/Article.php
  3. namespace App\Model\Entity;
  4.  
  5. use Cake\ORM\Entity;
  6.  
  7. class Article extends Entity
  8. {
  9. protected $_accessible = [
  10. '*' => true,
  11. 'id' => false,
  12. 'slug' => false,
  13. ];
  14. }

Our entity is quite slim right now, and we’ve only setup the _accessibleproperty which controls how properties can be modified byMass Assignment.

We can’t do much with our models right now, so next we’ll create our firstController and Template to allow us to interactwith our model.

CMS Tutorial - Creating the Articles Controller

With our model created, we need a controller for our articles. Controllers inCakePHP handle HTTP requests and execute business logic contained in modelmethods, to prepare the response. We’ll place this new controller in a filecalled ArticlesController.php inside the src/Controller directory.Here’s what the basic controller should look like:

  1. <?php
  2. // src/Controller/ArticlesController.php
  3.  
  4. namespace App\Controller;
  5.  
  6. class ArticlesController extends AppController
  7. {
  8. }

Now, let’s add an action to our controller. Actions are controller methods thathave routes connected to them. For example, when a user requestswww.example.com/articles/index (which is also the same aswww.example.com/articles), CakePHP will call the index method of yourArticlesController. This method should query the model layer, and preparea response by rendering a Template in the View. The code for that action wouldlook like this:

  1. <?php
  2. // src/Controller/ArticlesController.php
  3.  
  4. namespace App\Controller;
  5.  
  6. class ArticlesController extends AppController
  7. {
  8. public function index()
  9. {
  10. $this->loadComponent('Paginator');
  11. $articles = $this->Paginator->paginate($this->Articles->find());
  12. $this->set(compact('articles'));
  13. }
  14. }

By defining function index() in our ArticlesController, users can nowaccess the logic there by requesting www.example.com/articles/index.Similarly, if we were to define a function called foobar(), users would beable to access that at www.example.com/articles/foobar. You may be temptedto name your controllers and actions in a way that allows you to obtain specificURLs. Resist that temptation. Instead, follow the CakePHP Conventionscreating readable, meaningful action names. You can then useRouting to connect the URLs you want to the actions you’vecreated.

Our controller action is very simple. It fetches a paginated set of articlesfrom the database, using the Articles Model that is automatically loaded via namingconventions. It then uses set() to pass the articles into the Template (whichwe’ll create soon). CakePHP will automatically render the template after ourcontroller action completes.

Create the Article List Template

Now that we have our controller pulling data from the model, and preparing ourview context, let’s create a view template for our index action.

CakePHP view templates are presentation-flavored PHP code that is inserted insidethe application’s layout. While we’ll be creating HTML here, Views can alsogenerate JSON, CSV or even binary files like PDFs.

A layout is presentation code that is wrapped around a view. Layout filescontain common site elements like headers, footers and navigation elements. Yourapplication can have multiple layouts, and you can switch between them, but fornow, let’s just use the default layout.

CakePHP’s template files are stored in templates inside a foldernamed after the controller they correspond to. So we’ll have to createa folder named ‘Articles’ in this case. Add the following code to yourapplication:

  1. <!-- File: templates/Articles/index.php -->
  2.  
  3. <h1>Articles</h1>
  4. <table>
  5. <tr>
  6. <th>Title</th>
  7. <th>Created</th>
  8. </tr>
  9.  
  10. <!-- Here is where we iterate through our $articles query object, printing out article info -->
  11.  
  12. <?php foreach ($articles as $article): ?>
  13. <tr>
  14. <td>
  15. <?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
  16. </td>
  17. <td>
  18. <?= $article->created->format(DATE_RFC850) ?>
  19. </td>
  20. </tr>
  21. <?php endforeach; ?>
  22. </table>

In the last section we assigned the ‘articles’ variable to the view usingset(). Variables passed into the view are available in the view templates aslocal variables which we used in the above code.

You might have noticed the use of an object called $this->Html. This is aninstance of the CakePHP HtmlHelper. CakePHP comeswith a set of view helpers that make tasks like creating links, forms, andpagination buttons easy. You can learn more about Helpers in theirchapter, but what’s important to note here is that the link() method willgenerate an HTML link with the given link text (the first parameter) and URL(the second parameter).

When specifying URLs in CakePHP, it is recommended that you use arrays ornamed routes. These syntaxes allow you toleverage the reverse routing features CakePHP offers.

At this point, you should be able to point your browser tohttp://localhost:8765/articles/index. You should see your list view,correctly formatted with the title and table listing of the articles.

Create the View Action

If you were to click one of the ‘view’ links in our Articles list page, you’dsee an error page saying that action hasn’t been implemented. Lets fix that now:

  1. // Add to existing src/Controller/ArticlesController.php file
  2.  
  3. public function view($slug = null)
  4. {
  5. $article = $this->Articles->findBySlug($slug)->firstOrFail();
  6. $this->set(compact('article'));
  7. }

While this is a simple action, we’ve used some powerful CakePHP features. Westart our action off by using findBySlug() which isa Dynamic Finder. This method allows us to create a basic query thatfinds articles by a given slug. We then use firstOrFail() to either fetchthe first record, or throw a NotFoundException.

Our action takes a $slug parameter, but where does that parameter come from?If a user requests /articles/view/first-post, then the value ‘first-post’ ispassed as $slug by CakePHP’s routing and dispatching layers. If wereload our browser with our new action saved, we’d see another CakePHP errorpage telling us we’re missing a view template; let’s fix that.

Create the View Template

Let’s create the view for our new ‘view’ action and place it intemplates/Articles/view.php

  1. <!-- File: templates/Articles/view.php -->
  2.  
  3. <h1><?= h($article->title) ?></h1>
  4. <p><?= h($article->body) ?></p>
  5. <p><small>Created: <?= $article->created->format(DATE_RFC850) ?></small></p>
  6. <p><?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?></p>

You can verify that this is working by trying the links at /articles/index ormanually requesting an article by accessing URLs like/articles/view/first-post.

Adding Articles

With the basic read views created, we need to make it possible for new articlesto be created. Start by creating an add() action in theArticlesController. Our controller should now look like:

  1. // src/Controller/ArticlesController.php
  2.  
  3. namespace App\Controller;
  4.  
  5. use App\Controller\AppController;
  6.  
  7. class ArticlesController extends AppController
  8. {
  9. public function initialize(): void
  10. {
  11. parent::initialize();
  12.  
  13. $this->loadComponent('Paginator');
  14. $this->loadComponent('Flash'); // Include the FlashComponent
  15. }
  16.  
  17. public function index()
  18. {
  19. $articles = $this->Paginator->paginate($this->Articles->find());
  20. $this->set(compact('articles'));
  21. }
  22.  
  23. public function view($slug)
  24. {
  25. $article = $this->Articles->findBySlug($slug)->firstOrFail();
  26. $this->set(compact('article'));
  27. }
  28.  
  29. public function add()
  30. {
  31. $article = $this->Articles->newEmptyEntity();
  32. if ($this->request->is('post')) {
  33. $article = $this->Articles->patchEntity($article, $this->request->getData());
  34.  
  35. // Hardcoding the user_id is temporary, and will be removed later
  36. // when we build authentication out.
  37. $article->user_id = 1;
  38.  
  39. if ($this->Articles->save($article)) {
  40. $this->Flash->success(__('Your article has been saved.'));
  41. return $this->redirect(['action' => 'index']);
  42. }
  43. $this->Flash->error(__('Unable to add your article.'));
  44. }
  45. $this->set('article', $article);
  46. }
  47. }

Note

You need to include the Flash component inany controller where you will use it. Often it makes sense to include it inyour 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 achance to show the user validation errors or other warnings.

Every CakePHP request includes a request object which is accessible using$this->request. The request object contains information regarding therequest that was just received. We use theCake\Http\ServerRequest::is() method to check that the requestis a HTTP POST request.

Our POST data is available in $this->request->getData(). You can use thepr() or debug() functions to print it out if you want tosee what it looks like. To save our data, we first ‘marshal’ the POST data intoan Article Entity. The Entity is then persisted using the ArticlesTable wecreated earlier.

After saving our new article we use FlashComponent’s success() method to seta message into the session. The success method is provided using PHP’smagic method features. Flashmessages will be displayed on the next page after redirecting. In our layout we have<?= $this->Flash->render() ?> which displays flash messages and clears thecorresponding session variable. Finally, after saving is complete, we useCake\Controller\Controller::redirect to send the user back to thearticles list. The param ['action' => 'index'] translates to URL/articles i.e the index action of the ArticlesController. You can referto Cake\Routing\Router::url() function on the API to see the formats in which you can specify a URLfor various CakePHP functions.

Create Add Template

Here’s our add view template:

  1. <!-- File: templates/Articles/add.php -->
  2.  
  3. <h1>Add Article</h1>
  4. <?php
  5. echo $this->Form->create($article);
  6. // Hard code the user for now.
  7. echo $this->Form->control('user_id', ['type' => 'hidden', 'value' => 1]);
  8. echo $this->Form->control('title');
  9. echo $this->Form->control('body', ['rows' => '3']);
  10. echo $this->Form->button(__('Save Article'));
  11. echo $this->Form->end();
  12. ?>

We use the FormHelper to generate the opening tag for an HTMLform. Here’s the HTML that $this->Form->create() generates:

  1. <form method="post" action="/articles/add">

Because we called create() without a URL option, FormHelper assumes wewant the form to submit back to the current action.

The $this->Form->control() method is used to create form elementsof the same name. The first parameter tells CakePHP which fieldthey correspond to, and the second parameter allows you to specifya wide array of options - in this case, the number of rows for thetextarea. There’s a bit of introspection and conventions used here. Thecontrol() will output different form elements based on the modelfield specified, and use inflection to generate the label text. You cancustomize the label, the input or any other aspect of the form controls usingoptions. The $this->Form->end() call closes the form.

Now let’s go back and update our templates/Articles/index.phpview to include a new “Add Article” link. Before the <table>, addthe following line:

  1. <?= $this->Html->link('Add Article', ['action' => 'add']) ?>

Adding Simple Slug Generation

If we were to save an Article right now, saving would fail as we are notcreating a slug attribute, and the column is NOT NULL. Slug values aretypically a URL-safe version of an article’s title. We can use thebeforeSave() callback of the ORM to populate our slug:

  1. // in src/Model/Table/ArticlesTable.php
  2. namespace App\Model\Table;
  3.  
  4. use Cake\ORM\Table;
  5. // the Text class
  6. use Cake\Utility\Text;
  7.  
  8. // Add the following method.
  9.  
  10. public function beforeSave($event, $entity, $options)
  11. {
  12. if ($entity->isNew() && !$entity->slug) {
  13. $sluggedTitle = Text::slug($entity->title);
  14. // trim slug to maximum length defined in schema
  15. $entity->slug = substr($sluggedTitle, 0, 191);
  16. }
  17. }

This code is simple, and doesn’t take into account duplicate slugs. But we’llfix that later on.

Add Edit Action

Our application can now save articles, but we can’t edit them. Lets rectify thatnow. Add the following action to your ArticlesController:

  1. // in src/Controller/ArticlesController.php
  2.  
  3. // Add the following method.
  4.  
  5. public function edit($slug)
  6. {
  7. $article = $this->Articles->findBySlug($slug)->firstOrFail();
  8. if ($this->request->is(['post', 'put'])) {
  9. $this->Articles->patchEntity($article, $this->request->getData());
  10. if ($this->Articles->save($article)) {
  11. $this->Flash->success(__('Your article has been updated.'));
  12. return $this->redirect(['action' => 'index']);
  13. }
  14. $this->Flash->error(__('Unable to update your article.'));
  15. }
  16.  
  17. $this->set('article', $article);
  18. }

This action first ensures that the user has tried to access an existing record.If they haven’t passed in an $slug parameter, or the article does not exist,a NotFoundException will be thrown, and the CakePHP ErrorHandler will renderthe appropriate error page.

Next the action checks whether the request is either a POST or a PUT request. Ifit is, then we use the POST/PUT data to update our article entity by using thepatchEntity() method. Finally, we call save() set the appropriate flashmessage and either redirect or display validation errors.

Create Edit Template

The edit template should look like this:

<!-- File: templates/Articles/edit.php -->

<h1>Edit Article</h1>
<?php
    echo $this->Form->create($article);
    echo $this->Form->control('user_id', ['type' => 'hidden']);
    echo $this->Form->control('title');
    echo $this->Form->control('body', ['rows' => '3']);
    echo $this->Form->button(__('Save Article'));
    echo $this->Form->end();
?>

This template outputs the edit form (with the values populated), alongwith any necessary validation error messages.

You can now update your index view with links to edit specificarticles:

<!-- File: templates/Articles/index.php  (edit links added) -->

<h1>Articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
    <tr>
        <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>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
        <td>
            <?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
        </td>
    </tr>
<?php endforeach; ?>

</table>

Update Validation Rules for Articles

Up until this point our Articles had no input validation done. Lets fix that byusing a validator:

// src/Model/Table/ArticlesTable.php

// add this use statement right below the namespace declaration to import
// the Validator class
use Cake\Validation\Validator;

// Add the following method.
public function validationDefault(Validator $validator): Validator
{
    $validator
        ->allowEmptyString('title', false)
        ->minLength('title', 10)
        ->maxLength('title', 255)

        ->allowEmptyString('body', false)
        ->minLength('body', 10);

    return $validator;
}

The validationDefault() method tells CakePHP how to validate your data whenthe save() method is called. Here, we’ve specified that both the title, andbody fields must not be empty, and have certain length constraints.

CakePHP’s validation engine is powerful and flexible. It provides a suite offrequently used rules for tasks like email addresses, IP addresses etc. and theflexibility for adding your own validation rules. For more information on thatsetup, check the Validation documentation.

Now that your validation rules are in place, use the app to try to addan article with an empty title or body to see how it works. Since we’ve used theCake\View\Helper\FormHelper::control() method of the FormHelper tocreate our form elements, our validation error messages will be shownautomatically.

Add Delete Action

Next, let’s make a way for users to delete articles. Start with adelete() action in the ArticlesController:

// src/Controller/ArticlesController.php

public function delete($slug)
{
    $this->request->allowMethod(['post', 'delete']);

    $article = $this->Articles->findBySlug($slug)->firstOrFail();
    if ($this->Articles->delete($article)) {
        $this->Flash->success(__('The {0} article has been deleted.', $article->title));
        return $this->redirect(['action' => 'index']);
    }
}

This logic deletes the article specified by $slug, and uses$this->Flash->success() to show the user a confirmationmessage after redirecting them to /articles. If the user attempts todelete an article using a GET request, allowMethod() will throw an exception.Uncaught exceptions are captured by CakePHP’s exception handler, and a niceerror page is displayed. There are many built-inExceptions that can be used to indicate the variousHTTP errors your application might need to generate.

Warning

Allowing content to be deleted using GET requests is very dangerous, as webcrawlers could accidentally delete all your content. That is why we usedallowMethod() in our controller.

Because we’re only executing logic and redirecting to another action, thisaction has no template. You might want to update your index template with linksthat allow users to delete articles:

<!-- File: templates/Articles/index.php  (delete links added) -->

<h1>Articles</h1>
<p><?= $this->Html->link("Add Article", ['action' => 'add']) ?></p>
<table>
    <tr>
        <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>
            <?= $this->Html->link($article->title, ['action' => 'view', $article->slug]) ?>
        </td>
        <td>
            <?= $article->created->format(DATE_RFC850) ?>
        </td>
        <td>
            <?= $this->Html->link('Edit', ['action' => 'edit', $article->slug]) ?>
            <?= $this->Form->postLink(
                'Delete',
                ['action' => 'delete', $article->slug],
                ['confirm' => 'Are you sure?'])
            ?>
        </td>
    </tr>
<?php endforeach; ?>

</table>

Using View\Helper\FormHelper::postLink() will create a linkthat uses JavaScript to do a POST request deleting our article.

Note

This view code also uses the FormHelper to prompt the user with aJavaScript confirmation dialog before they attempt to delete anarticle.

With a basic articles management setup, we’ll create the basic actionsfor our Tags and Users tables.