Documentation

User Guide

We can now code up the functionality to add new albums. There are two bits to
this part:

Display a form for user to provide details

Process the form submission and store to database

We use Zend\Form to do this. The Zend\Form component manages the form
and for validation, we add a Zend\InputFilter to our Album entity. We
start by creating a new class Album\Form\AlbumForm that extends from
Zend\Form\Form to define our form.
Create a file called AlbumForm.php in module/Album/src/Album/Form:

<?phpnamespaceAlbum\Form;useZend\Form\Form;classAlbumFormextendsForm{publicfunction__construct($name=null){// we want to ignore the name passedparent::__construct('album');$this->setAttribute('method','post');$this->add(array('name'=>'id','attributes'=>array('type'=>'hidden',),));$this->add(array('name'=>'artist','attributes'=>array('type'=>'text',),'options'=>array('label'=>'Artist',),));$this->add(array('name'=>'title','attributes'=>array('type'=>'text',),'options'=>array('label'=>'Title',),));$this->add(array('name'=>'submit','attributes'=>array('type'=>'submit','value'=>'Go','id'=>'submitbutton',),));}}

Within the constructor of AlbumForm, we set the name when we call the parent’s
constructor and then set the method and then create four form elements for the
id, artist, title, and submit button. For each item we set various attributes
and options, including the label to be displayed.

We also need to set up validation for this form. In Zend Framework 2 this is
done using an input filter which can either be standalone or within any class
that implements InputFilterAwareInterface, such as a model entity. We are
going to add the input filter to our Album.php file in module/Album/src/Album/Model:

The InputFilterAwareInterface defines two methods: setInputFilter() and
getInputFilter(). We only need to implement getInputFilter() so we
simply throw an exception in setInputFilter().

Within getInputFilter(), we instantiate an InputFilter and then add the
inputs that we require. We add one input for each property that we wish to
filter or validate. For the id field we add an Int filter as we only
need integers. For the text elements, we add two filters, StripTags and
StringTrim to remove unwanted HTML and unnecessary white space. We also set
them to be required and add a StringLength validator to ensure that the
user doesn’t enter more characters than we can store into the database.

We now need to get the form to display and then process it on submission. This
is done within the AlbumController’s addAction():

// module/Album/src/Album/Controller/AlbumController.php://...useZend\Mvc\Controller\AbstractActionController;useZend\View\Model\ViewModel;useAlbum\Model\Album;// <-- Add this importuseAlbum\Form\AlbumForm;// <-- Add this import//...// Add content to this method:publicfunctionaddAction(){$form=newAlbumForm();$form->get('submit')->setValue('Add');$request=$this->getRequest();if($request->isPost()){$album=newAlbum();$form->setInputFilter($album->getInputFilter());$form->setData($request->getPost());if($form->isValid()){$album->exchangeArray($form->getData());$this->getAlbumTable()->saveAlbum($album);// Redirect to list of albumsreturn$this->redirect()->toRoute('album');}}returnarray('form'=>$form);}//...

After adding the AlbumForm to the use list, we implement addAction().
Let’s look at the addAction() code in a little more detail:

$form=newAlbumForm();$form->get('submit')->setValue('Add');

We instantiate AlbumForm and set the label on the submit button to “Add”. We
do this here as we’ll want to re-use the form when editing an album and will use
a different label.

If the Request object’s isPost() method is true, then the form has been
submitted and so we set the form’s input filter from an album instance. We then
set the posted data to the form and check to see if it is valid using the
isValid() member function of the form.

If the form is valid, then we grab the data from the form and store to the
model using saveAlbum().

// Redirect to list of albumsreturn$this->redirect()->toRoute('album');

After we have saved the new album row, we redirect back to the list of albums
using the Redirect controller plugin.

returnarray('form'=>$form);

Finally, we return the variables that we want assigned to the view. In this
case, just the form object. Note that Zend Framework 2 also allows you to simply
return an array containing the variables to be assigned to the view and it will
create a ViewModel behind the scenes for you. This saves a little typing.

Again, we display a title as before and then we render the form. Zend Framework
provides some view helpers to make this a little easier. The form() view
helper has an openTag() and closeTag() method which we use to open and
close the form. Then for each element with a label, we can use formRow(),
but for the two elements that are standalone, we use formHidden() and
formSubmit().

Alternatively, the process of rendering the form can be simplified by using the
bundled formCollection view helper. For example, in the view script above replace
all the form-rendering echo statements with:

echo$this->formCollection($form);

This will iterate over the form structure, calling the appropriate label, element
and error view helpers for each element, but you still have to wrap formCollection($form) with the open and close form tags.
This helps reduce the complexity of your view script in situations where the default
HTML rendering of the form is acceptable.

You should now be able to use the “Add new album” link on the home page of the
application to add a new album record.

Editing an album is almost identical to adding one, so the code is very similar.
This time we use editAction() in the AlbumController:

// module/Album/src/Album/Controller/AlbumController.php://...// Add content to this method:publicfunctioneditAction(){$id=(int)$this->params()->fromRoute('id',0);if(!$id){return$this->redirect()->toRoute('album',array('action'=>'add'));}$album=$this->getAlbumTable()->getAlbum($id);$form=newAlbumForm();$form->bind($album);$form->get('submit')->setAttribute('value','Edit');$request=$this->getRequest();if($request->isPost()){$form->setInputFilter($album->getInputFilter());$form->setData($request->getPost());if($form->isValid()){$this->getAlbumTable()->saveAlbum($form->getData());// Redirect to list of albumsreturn$this->redirect()->toRoute('album');}}returnarray('id'=>$id,'form'=>$form,);}//...

This code should look comfortably familiar. Let’s look at the differences from
adding an album. Firstly, we look for the id that is in the matched route
and use it to load the album to be edited:

params is a controller plugin that provides a convenient way to retrieve
parameters from the matched route. We use it to retrieve the id from the
route we created in the modules’ module.config.php. If the id is zero,
then we redirect to the add action, otherwise, we continue by getting the album
entity from the database.

The form’s bind() method attaches the model to the form. This is used in two
ways:

When displaying the form, the initial values for each element are extracted
from the model.

After successful validation in isValid(), the data from the form is put back
into the model.

These operations are done using a hydrator object. There are a number of
hydrators, but the default one is Zend\Stdlib\Hydrator\ArraySerializable
which expects to find two methods in the model: getArrayCopy() and
exchangeArray(). We have already written exchangeArray() in our
Album entity, so just need to write getArrayCopy():

As a result of using bind() with its hydrator, we do not need to populate the
form’s data back into the $album as that’s already been done, so we can just
call the mappers’ saveAlbum() to store the changes back to the database.

The view template, edit.phtml, looks very similar to the one for adding an
album:

We need to change the test for edit ‘AlbumControllerTest’ in module/Album/test/AlbumTest/Controller :

<?php...publicfunctiontestEditActionCanBeAccessed(){$this->routeMatch->setParam('action','edit');$this->routeMatch->setParam('id','1');//Add this Row$result=$this->controller->dispatch($this->request);$response=$this->controller->getResponse();$this->assertEquals(200,$response->getStatusCode());}

If we do not send any id parameter the Controller will redirect us to the album route which returns the HTTP Status Code 302

We will also add another test to check if the redirection works.
Add the following also to AlbumControllerTest.php

To round out our application, we need to add deletion. We have a Delete link
next to each album on our list page and the naïve approach would be to do a
delete when it’s clicked. This would be wrong. Remembering our HTTP spec, we
recall that you shouldn’t do an irreversible action using GET and should use
POST instead.

We shall show a confirmation form when the user clicks delete and if they then
click “yes”, we will do the deletion. As the form is trivial, we’ll code it
directly into our view (Zend\Form is, after all, optional!).

Let’s start with the action code in AlbumController::deleteAction():

// module/Album/src/Album/Controller/AlbumController.php://...// Add content to the following method:publicfunctiondeleteAction(){$id=(int)$this->params()->fromRoute('id',0);if(!$id){return$this->redirect()->toRoute('album');}$request=$this->getRequest();if($request->isPost()){$del=$request->getPost('del','No');if($del=='Yes'){$id=(int)$request->getPost('id');$this->getAlbumTable()->deleteAlbum($id);}// Redirect to list of albumsreturn$this->redirect()->toRoute('album');}returnarray('id'=>$id,'album'=>$this->getAlbumTable()->getAlbum($id));}//...

As before, we get the id from the matched route,and check the request
object’s isPost() to determine whether to show the confirmation page or to
delete the album. We use the table object to delete the row using the
deleteAlbum() method and then redirect back the list of albums. If the
request is not a POST, then we retrieve the correct database record and assign
to the view, along with the id.