Making use of Forms and Fieldsets¶
So far all we did was read data from the database. In a real-life-application this won’t get us very far as very often
the least we need to do is to support full Create
, Read
, Update
and Delete
operations (CRUD). Most
often the process of getting data into our database is that a user enters the data into a web <form>
and the
application then uses the user input and saves it into our backend.
Core components¶
We want to be able to do exactly this and Zend Framework provides us with all the tools we need to achieve our goal. Before we jump into coding, we need to understand the two core components for this task first. So let’s take a look at what these components are and what they are used for.
Zend\Form\Fieldset¶
The first component that you have to know about is Zend\Form\Fieldset
. A Fieldset
is a component that contains a
reusable set of elements. You will use the Fieldset
to create the frontend-input for your backend-models. It is
considered good practice to have one Fieldset
for every Model
of your application.
The Fieldset
-component, however, is no Form
, meaning you will not be able to use a Fieldset
without attaching it
to the Form
-component. The advantage here is that you have one set of elements that you can re-use for as many
Forms
as you like without having to re-declare all the inputs for the Model
that’s represented by the Fieldset
.
Zend\Form\Form¶
The main component you’ll need and that most probably you’ve heard about already is Zend\Form\Form
. The
Form
-component is the main container for all elements of your web <form>
. You are able to add single
elements or a set of elements in the form of a Fieldset
, too.
Creating your first Fieldset¶
Explaining how the Zend\Form
component works is best done by giving you real code to work with. So let’s jump right
into it and create all the forms we need to finish our Blog
module. We start by creating a Fieldset
that contains
all the input elements that we need to work with our Blog
-data.
- You will need one hidden input for the
id
property, which is only needed for editting and deleting data. - You will need one text input for the
text
property - You will need one text input for the
title
property
Create the file /module/Blog/src/Blog/Form/PostFieldset.php
and add the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?php
// Filename: /module/Blog/src/Blog/Form/PostFieldset.php
namespace Blog\Form;
use Zend\Form\Fieldset;
class PostFieldset extends Fieldset
{
public function __construct()
{
$this->add(array(
'type' => 'hidden',
'name' => 'id'
));
$this->add(array(
'type' => 'text',
'name' => 'text',
'options' => array(
'label' => 'The Text'
)
));
$this->add(array(
'type' => 'text',
'name' => 'title',
'options' => array(
'label' => 'Blog Title'
)
));
}
}
|
As you can see this class is pretty handy. All we do is to have our class extend Zend\Form\Fieldset
and then we
write a __construct()
method and add all the elements we need to the fieldset. This Fieldset
can now be used by
as many forms as we want. So let’s go ahead and create our first Form
.
Creating the PostForm¶
Now that we have our PostFieldset
in place, we need to use it inside a Form
. We then need to add a Submit-Button
to the form so that the user will be able to submit the data and we’re done. So create the PostForm
within the
same directory under /module/Blog/src/Blog/Form/PostForm
and add the PostFieldset
to it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?php
// Filename: /module/Blog/src/Blog/Form/PostForm.php
namespace Blog\Form;
use Zend\Form\Form;
class PostForm extends Form
{
public function __construct()
{
$this->add(array(
'name' => 'post-fieldset',
'type' => 'Blog\Form\PostFieldset'
));
$this->add(array(
'type' => 'submit',
'name' => 'submit',
'attributes' => array(
'value' => 'Insert new Post'
)
));
}
}
|
And that’s our form. Nothing special here, we add our PostFieldset
to the Form, we add a submit button to the form
and nothing more. Let’s now make use of the Form.
Adding a new Post¶
Now that we have the PostForm
written we want to use it. But there are a couple more tasks that you need to do.
The tasks that are standing right in front of you are:
- create a new controller
WriteController
- add
PostService
as a dependency to theWriteController
- add
PostForm
as a dependency to theWriteController
- create a new route
blog/add
that routes to theWriteController
and itsaddAction()
- create a new view that displays the form
Creating the WriteController¶
As you can see from the task-list we need a new controller and this controller is supposed to have two dependencies.
One dependency being the PostService
that’s also being used within our ListController
and the other dependency
being the PostForm
which is new. Since the PostForm
is a dependency that the ListController
doesn’t
need to display blog-data, we will create a new controller to keep things properly separated. First, register a
controller-factory within the configuration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?php
// Filename: /module/Blog/config/module.config.php
return array(
'db' => array( /** DB Config */ ),
'service_manager' => array( /** ServiceManager Config */),
'view_manager' => array( /** ViewManager Config */ ),
'controllers' => array(
'factories' => array(
'Blog\Controller\List' => 'Blog\Factory\ListControllerFactory',
'Blog\Controller\Write' => 'Blog\Factory\WriteControllerFactory'
)
),
'router' => array( /** Router Config */ )
);
|
Next step would be to write the WriteControllerFactory
. Have the factory return the WriteController
and add the
required dependencies within the constructor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php
// Filename: /module/Blog/src/Blog/Factory/WriteControllerFactory.php
namespace Blog\Factory;
use Blog\Controller\WriteController;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class WriteControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $serviceLocator)
{
$realServiceLocator = $serviceLocator->getServiceLocator();
$postService = $realServiceLocator->get('Blog\Service\PostServiceInterface');
$postInsertForm = $realServiceLocator->get('FormElementManager')->get('Blog\Form\PostForm');
return new WriteController(
$postService,
$postInsertForm
);
}
}
|
In this code-example there are a couple of things to be aware of. First, the WriteController
doesn’t exist yet, but we
will create this in the next step so we’re just assuming that it will exist later on. Second, we access the
FormElementManager
to get access to our PostForm
. All forms should be accessed through the FormElementManager
.
Even though we haven’t registered the PostForm
in our config files yet the FormElementManager
automatically knows
about forms that act as invokables
. As long as you have no dependencies you don’t need to register them explicitly.
Next up is the creation of our controller. Be sure to type hint the dependencies by their interfaces and to add the
addAction()
!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php
// Filename: /module/Blog/src/Blog/Controller/WriteController.php
namespace Blog\Controller;
use Blog\Service\PostServiceInterface;
use Zend\Form\FormInterface;
use Zend\Mvc\Controller\AbstractActionController;
class WriteController extends AbstractActionController
{
protected $postService;
protected $postForm;
public function __construct(
PostServiceInterface $postService,
FormInterface $postForm
) {
$this->postService = $postService;
$this->postForm = $postForm;
}
public function addAction()
{
}
}
|
Right on to creating the new route:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <?php
// Filename: /module/Blog/config/module.config.php
return array(
'db' => array( /** Db Config */ ),
'service_manager' => array( /** ServiceManager Config */ ),
'view_manager' => array( /** ViewManager Config */ ),
'controllers' => array( /** Controller Config */ ),
'router' => array(
'routes' => array(
'blog' => array(
'type' => 'literal',
'options' => array(
'route' => '/blog',
'defaults' => array(
'controller' => 'Blog\Controller\List',
'action' => 'index',
)
),
'may_terminate' => true,
'child_routes' => array(
'detail' => array(
'type' => 'segment',
'options' => array(
'route' => '/:id',
'defaults' => array(
'action' => 'detail'
),
'constraints' => array(
'id' => '\d+'
)
)
),
'add' => array(
'type' => 'literal',
'options' => array(
'route' => '/add',
'defaults' => array(
'controller' => 'Blog\Controller\Write',
'action' => 'add'
)
)
)
)
)
)
)
);
|
And lastly let’s create a dummy template:
1 2 | <!-- Filename: /module/Blog/view/blog/write/add.phtml -->
<h1>WriteController::addAction()</h1>
|
Checking the current status
If you try to access the new route localhost:8080/blog/add
you’re supposed to see the following error message:
1 2 | Fatal error: Call to a member function insert() on a non-object in
{libraryPath}/Zend/Form/Fieldset.php on line {lineNumber}
|
If this is not the case, be sure to follow the tutorial correctly and carefully check all your files. Assuming you are getting this error, let’s find out what it means and fix it!
The above error message is very common and its solution isn’t that intuitive. It appears that there is an error within
the Zend/Form/Fieldset.php
but that’s not the case. The error message let’s you know that something didn’t go right
while you were creating your form. In fact, while creating both the PostForm
as well as the PostFieldset
we
have forgotten something very, very important.
Note
When overwriting a __construct()
method within the Zend\Form
-component, be sure to always call
parent::__construct()
!
Without this, forms and fieldsets will not be able to get initiated correctly. Let’s now fix
the problem by calling the parents constructor in both form and fieldset. To have more flexibility we will also
include the signature of the __construct()
function which accepts a couple of parameters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | <?php
// Filename: /module/Blog/src/Blog/Form/PostForm.php
namespace Blog\Form;
use Zend\Form\Form;
class PostForm extends Form
{
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
$this->add(array(
'name' => 'post-fieldset',
'type' => 'Blog\Form\PostFieldset'
));
$this->add(array(
'type' => 'submit',
'name' => 'submit',
'attributes' => array(
'value' => 'Insert new Post'
)
));
}
}
|
As you can see our PostForm
now accepts two parameters to give our form a name and to set a couple of options. Both
parameters will be passed along to the parent. If you look closely at how we add the PostFieldset
to the form you’ll
notice that we assign a name to the fieldset. Those options will be passed from the FormElementManager
when the
PostFieldset
is created. But for this to function we need to do the same step inside our fieldset, too:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | <?php
// Filename: /module/Blog/src/Blog/Form/PostFieldset.php
namespace Blog\Form;
use Zend\Form\Fieldset;
class PostFieldset extends Fieldset
{
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
$this->add(array(
'type' => 'hidden',
'name' => 'id'
));
$this->add(array(
'type' => 'text',
'name' => 'text',
'options' => array(
'label' => 'The Text'
)
));
$this->add(array(
'type' => 'text',
'name' => 'title',
'options' => array(
'label' => 'Blog Title'
)
));
}
}
|
Reloading your application now will yield you the desired result.
Displaying the form¶
Now that we have our PostForm
within our WriteController
it’s time to pass this form to the view and have
it rendered using the provided ViewHelpers
from the Zend\Form
component. First change your controller so that the
form is passed to the view.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <?php
// Filename: /module/Blog/src/Blog/Controller/WriteController.php
namespace Blog\Controller;
use Blog\Service\PostServiceInterface;
use Zend\Form\FormInterface;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class WriteController extends AbstractActionController
{
protected $postService;
protected $postForm;
public function __construct(
PostServiceInterface $postService,
FormInterface $postForm
) {
$this->postService = $postService;
$this->postForm = $postForm;
}
public function addAction()
{
return new ViewModel(array(
'form' => $this->postForm
));
}
}
|
And then we need to modify our view to have the form rendered.
1 2 3 4 5 6 7 8 9 10 11 12 | <!-- Filename: /module/Blog/view/blog/write/add.phtml -->
<h1>WriteController::addAction()</h1>
<?php
$form = $this->form;
$form->setAttribute('action', $this->url());
$form->prepare();
echo $this->form()->openTag($form);
echo $this->formCollection($form);
echo $this->form()->closeTag();
|
Firstly, we tell the form that it should send its data to the current URL and then we tell the form to prepare()
itself which triggers a couple of internal things.
Note
HTML-Forms can be sent using POST
and GET
. ZF2s default is POST
, therefore you don’t have to be
explicit in setting this option. If you want to change it to GET
though, all you have to do is set the
specific attribute prior to the prepare()
call.
$form->setAttribute('method', 'GET');
Next we’re using a couple of ViewHelpers
which take care of rendering the form for us. There are many different ways
to render a form within Zend Framework but using formCollection()
is probably the fastest one.
Refreshing the browser you will now see your form properly displayed. However, if we’re submitting the form all we see is our form being displayed again. And this is due to the simple fact that we didn’t add any logic to the controller yet.
Note
Keep in mind that this tutorial focuses solely on the OOP aspect of things. Rendering the form like this, without any stylesheets added doesn’t really reflect most designers’ idea of a beautiful form. You’ll find out more about the rendering of forms in the chapter of Zend\Form\View\Helper.
Controller Logic for basically all Forms¶
Writing a Controller that handles a form workflow is pretty simple and it’s basically identical for each and every form you have within your application.
- You want to check if the current request is a POST-Request, meaning if the form has been sent
- If the form has been sent, you want to:
- store the POST-Data within the Form
- check if the form passes validation
- If the form passes validation, you want to:
- pass the form data to your service to have it stored
- redirect the user to either the detail page of the entered data or to some overview page
- In all other cases, you want the form displayed, sometimes alongside given error messages
And all of this is really not that much code. Modify your WriteController
to the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?php
// Filename: /module/Blog/src/Blog/Controller/WriteController.php
namespace Blog\Controller;
use Blog\Service\PostServiceInterface;
use Zend\Form\FormInterface;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class WriteController extends AbstractActionController
{
protected $postService;
protected $postForm;
public function __construct(
PostServiceInterface $postService,
FormInterface $postForm
) {
$this->postService = $postService;
$this->postForm = $postForm;
}
public function addAction()
{
$request = $this->getRequest();
if ($request->isPost()) {
$this->postForm->setData($request->getPost());
if ($this->postForm->isValid()) {
try {
$this->postService->savePost($this->postForm->getData());
return $this->redirect()->toRoute('blog');
} catch (\Exception $e) {
// Some DB Error happened, log it and let the user know
}
}
}
return new ViewModel(array(
'form' => $this->postForm
));
}
}
|
This example code should be pretty straight forward. First we save the current request into a local variable. Then we
check if the current request is a POST-Request and if so, we store the requests POST-data into the form. If the form
turns out to be valid we try to save the form data through our service and then redirect the user to the route blog
.
If any error occurred at any point we simply display the form again.
Submitting the form right now will return into the following error
1 2 | Fatal error: Call to undefined method Blog\Service\PostService::savePost() in
/module/Blog/src/Blog/Controller/WriteController.php on line 33
|
Let’s fix this by extending our PostService
. Be sure to also change the signature of the PostServiceInterface
!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | <?php
// Filename: /module/Blog/src/Blog/Service/PostServiceInterface.php
namespace Blog\Service;
use Blog\Model\PostInterface;
interface PostServiceInterface
{
/**
* Should return a set of all blog posts that we can iterate over. Single entries of the array are supposed to be
* implementing \Blog\Model\PostInterface
*
* @return array|PostInterface[]
*/
public function findAllPosts();
/**
* Should return a single blog post
*
* @param int $id Identifier of the Post that should be returned
* @return PostInterface
*/
public function findPost($id);
/**
* Should save a given implementation of the PostInterface and return it. If it is an existing Post the Post
* should be updated, if it's a new Post it should be created.
*
* @param PostInterface $blog
* @return PostInterface
*/
public function savePost(PostInterface $blog);
}
|
As you can see the savePost()
function has been added and needs to be implemented within the PostService
now.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?php
// Filename: /module/Blog/src/Blog/Service/PostService.php
namespace Blog\Service;
use Blog\Mapper\PostMapperInterface;
class PostService implements PostServiceInterface
{
/**
* @var \Blog\Mapper\PostMapperInterface
*/
protected $postMapper;
/**
* @param PostMapperInterface $postMapper
*/
public function __construct(PostMapperInterface $postMapper)
{
$this->postMapper = $postMapper;
}
/**
* {@inheritDoc}
*/
public function findAllPosts()
{
return $this->postMapper->findAll();
}
/**
* {@inheritDoc}
*/
public function findPost($id)
{
return $this->postMapper->find($id);
}
/**
* {@inheritDoc}
*/
public function savePost(PostInterface $post)
{
return $this->postMapper->save($post);
}
}
|
And now that we’re making an assumption against our postMapper
we need to extend the PostMapperInterface
and its
implementation, too. Start by extending the interface:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
// Filename: /module/Blog/src/Blog/Mapper/PostMapperInterface.php
namespace Blog\Mapper;
use Blog\Model\PostInterface;
interface PostMapperInterface
{
/**
* @param int|string $id
* @return PostInterface
* @throws \InvalidArgumentException
*/
public function find($id);
/**
* @return array|PostInterface[]
*/
public function findAll();
/**
* @param PostInterface $postObject
*
* @param PostInterface $postObject
* @return PostInterface
* @throws \Exception
*/
public function save(PostInterface $postObject);
}
|
And now the implementation of the save function.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | <?php
// Filename: /module/Blog/src/Blog/Mapper/ZendDbSqlMapper.php
namespace Blog\Mapper;
use Blog\Model\PostInterface;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\Adapter\Driver\ResultInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\Sql\Insert;
use Zend\Db\Sql\Sql;
use Zend\Db\Sql\Update;
use Zend\Stdlib\Hydrator\HydratorInterface;
class ZendDbSqlMapper implements PostMapperInterface
{
/**
* @var \Zend\Db\Adapter\AdapterInterface
*/
protected $dbAdapter;
/**
* @var \Zend\Stdlib\Hydrator\HydratorInterface
*/
protected $hydrator;
/**
* @var \Blog\Model\PostInterface
*/
protected $postPrototype;
/**
* @param AdapterInterface $dbAdapter
* @param HydratorInterface $hydrator
* @param PostInterface $postPrototype
*/
public function __construct(
AdapterInterface $dbAdapter,
HydratorInterface $hydrator,
PostInterface $postPrototype
) {
$this->dbAdapter = $dbAdapter;
$this->hydrator = $hydrator;
$this->postPrototype = $postPrototype;
}
/**
* @param int|string $id
*
* @return PostInterface
* @throws \InvalidArgumentException
*/
public function find($id)
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('posts');
$select->where(array('id = ?' => $id));
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult() && $result->getAffectedRows()) {
return $this->hydrator->hydrate($result->current(), $this->postPrototype);
}
throw new \InvalidArgumentException("Blog with given ID:{$id} not found.");
}
/**
* @return array|PostInterface[]
*/
public function findAll()
{
$sql = new Sql($this->dbAdapter);
$select = $sql->select('posts');
$stmt = $sql->prepareStatementForSqlObject($select);
$result = $stmt->execute();
if ($result instanceof ResultInterface && $result->isQueryResult()) {
$resultSet = new HydratingResultSet($this->hydrator, $this->postPrototype);
return $resultSet->initialize($result);
}
return array();
}
/**
* @param PostInterface $postObject
*
* @return PostInterface
* @throws \Exception
*/
public function save(PostInterface $postObject)
{
$postData = $this->hydrator->extract($postObject);
unset($postData['id']); // Neither Insert nor Update needs the ID in the array
if ($postObject->getId()) {
// ID present, it's an Update
$action = new Update('posts');
$action->set($postData);
$action->where(array('id = ?' => $postObject->getId()));
} else {
// ID NOT present, it's an Insert
$action = new Insert('posts');
$action->values($postData);
}
$sql = new Sql($this->dbAdapter);
$stmt = $sql->prepareStatementForSqlObject($action);
$result = $stmt->execute();
if ($result instanceof ResultInterface) {
if ($newId = $result->getGeneratedValue()) {
// When a value has been generated, set it on the object
$postObject->setId($newId);
}
return $postObject;
}
throw new \Exception("Database error");
}
}
|
The save()
function handles two cases. The insert
and update
routine. Firstly we extract the Post
-Object
since we need array data to work with Insert
and Update
. Then we remove the id
from the array since this
field is not wanted. When we do an update of a row, we don’t update the id
property itself and therefore it isn’t
needed. On the insert routine we don’t need an id
either so we can simply strip it away.
After the id
field has been removed we check what action is supposed to be called. If the Post
-Object has an id
set we create a new Update
-Object and if not we create a new Insert
-Object. We set the data for both actions
accordingly and after that the data is passed over to the Sql
-Object for the actual query into the database.
At last we check if we receive a valid result and if there has been an id
generated. If it’s the case we call the
setId()
-function of our blog and return the object in the end.
Let’s submit our form again and see what we get.
1 2 3 4 | Catchable fatal error: Argument 1 passed to Blog\Service\PostService::savePost()
must implement interface Blog\Model\PostInterface, array given,
called in /module/Blog/src/Blog/Controller/InsertController.php on line 33
and defined in /module/Blog/src/Blog/Service/PostService.php on line 49
|
Forms, per default, give you data in an array format. But our PostService
expects the format to be an implementation
of the PostInterface
. This means we need to find a way to have this array data become object data. If you recall the
previous chapter, this is done through the use of hydrators.
Note
On the Update-Query you’ll notice that we have assigned a condition to only update the row matching a given id
$action->where(array('id = ?' => $postObject->getId()));
You’ll see here that the condition is: id equals ?. With the question-mark being the id of the post-object. In the same way you could assign a condition to update (or select) rows with all entries higher than a given id:
$action->where(array('id > ?' => $postObject->getId()));
This works for all conditions. =
, >
, <
, >=
and <=
Zend\Form and Zend\Stdlib\Hydrator working together¶
Before we go ahead and put the hydrator into the form, let’s first do a data-dump of the data coming from the form. That
way we can easily notice all changes that the hydrator does. Modify your WriteController
to the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <?php
// Filename: /module/Blog/src/Blog/Controller/WriteController.php
namespace Blog\Controller;
use Blog\Service\PostServiceInterface;
use Zend\Form\FormInterface;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class WriteController extends AbstractActionController
{
protected $postService;
protected $postForm;
public function __construct(
PostServiceInterface $postService,
FormInterface $postForm
) {
$this->postService = $postService;
$this->postForm = $postForm;
}
public function addAction()
{
$request = $this->getRequest();
if ($request->isPost()) {
$this->postForm->setData($request->getPost());
if ($this->postForm->isValid()) {
try {
\Zend\Debug\Debug::dump($this->postForm->getData());die();
$this->postService->savePost($this->postForm->getData());
return $this->redirect()->toRoute('blog');
} catch (\Exception $e) {
// Some DB Error happened, log it and let the user know
}
}
}
return new ViewModel(array(
'form' => $this->postForm
));
}
}
|
With this set up go ahead and submit the form once again. You should now see a data dump like the following:
1 2 3 4 5 6 7 8 | array(2) {
["submit"] => string(16) "Insert new Post"
["post-fieldset"] => array(3) {
["id"] => string(0) ""
["text"] => string(3) "foo"
["title"] => string(3) "bar"
}
}
|
Now telling your fieldset to hydrate its data into an Post
-object is very simple. All you need to do is to assign
the hydrator and the object prototype like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <?php
// Filename: /module/Blog/src/Blog/Form/PostFieldset.php
namespace Blog\Form;
use Blog\Model\Post;
use Zend\Form\Fieldset;
use Zend\Stdlib\Hydrator\ClassMethods;
class PostFieldset extends Fieldset
{
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
$this->setHydrator(new ClassMethods(false));
$this->setObject(new Post());
$this->add(array(
'type' => 'hidden',
'name' => 'id'
));
$this->add(array(
'type' => 'text',
'name' => 'text',
'options' => array(
'label' => 'The Text'
)
));
$this->add(array(
'type' => 'text',
'name' => 'title',
'options' => array(
'label' => 'Blog Title'
)
));
}
}
|
As you can see we’re doing two things. We tell the fieldset to be using the ClassMethods
hydrator and then we tell the
fieldset that the default object to be returned is our Blog
-Model. However, when you’re re-submitting the form now
you’ll notice that nothing has changed. We’re still only getting array data returned and no object.
This is due to the fact that the form itself doesn’t know that it has to return an object. When the form doesn’t know
that it’s supposed to return an object it uses the ArraySeriazable
hydrator recursively. To change this, all we need
to do is to make our PostFieldset
a so-called base_fieldset
.
A base_fieldset
basically tells the form “this form is all about me, don’t worry about other data, just worry about
me”. And when the form knows that this fieldset is the real deal, then the form will use the hydrator presented by the
fieldset and return the object that we desire. Modify your PostForm
and assign the PostFieldset
as
base_fieldset
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?php
// Filename: /module/Blog/src/Blog/Form/PostForm.php
namespace Blog\Form;
use Zend\Form\Form;
class PostForm extends Form
{
public function __construct($name = null, $options = array())
{
parent::__construct($name, $options);
$this->add(array(
'name' => 'post-fieldset',
'type' => 'Blog\Form\PostFieldset',
'options' => array(
'use_as_base_fieldset' => true
)
));
$this->add(array(
'type' => 'submit',
'name' => 'submit',
'attributes' => array(
'value' => 'Insert new Post'
)
));
}
}
|
Now submit your form again. You should see the following output:
1 2 3 4 5 | object(Blog\Model\Post)#294 (3) {
["id":protected] => string(0) ""
["title":protected] => string(3) "foo"
["text":protected] => string(3) "bar"
}
|
You can now revert back your WriteController
to its previous form to have the form-data passed through the
PostService
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <?php
// Filename: /module/Blog/src/Blog/Controller/WriteController.php
namespace Blog\Controller;
use Blog\Service\PostServiceInterface;
use Zend\Form\FormInterface;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class WriteController extends AbstractActionController
{
protected $postService;
protected $postForm;
public function __construct(
PostServiceInterface $postService,
FormInterface $postForm
) {
$this->postService = $postService;
$this->postForm = $postForm;
}
public function addAction()
{
$request = $this->getRequest();
if ($request->isPost()) {
$this->postForm->setData($request->getPost());
if ($this->postForm->isValid()) {
try {
$this->postService->savePost($this->postForm->getData());
return $this->redirect()->toRoute('blog');
} catch (\Exception $e) {
// Some DB Error happened, log it and let the user know
}
}
}
return new ViewModel(array(
'form' => $this->postForm
));
}
}
|
If you send the form now you’ll now be able to add as many new blogs as you want. Great!
Conclusion¶
In this chapter you’ve learned a great deal about the Zend\Form
component. You’ve learned that Zend\Stdlib\Hydrator
takes a big part within the Zend\Form
component and by making use of both components you’ve been able to create an
insert form for the blog module.
In the next chapter we will finalize the CRUD functionality by creating the update and delete routines for the blog module.