Top 5 features you need to know about
In the previous sections we have covered how to create a basic module with controller, model, and view and also how to inject the database connection to the model and the CRUD. Here in this section, we will dive deep into Zend Framework with some of its very common functionalities.
Input filter and validation
In the Quick start – creating your first web application section, we have created a newAction
action and an editAction
action. Both of these actions have one thing in common: they were dealing with the client’s data. The client can insert anything which means they can insert malicious data that could lead to malicious attacks or invalidly formatted data; for example, we have a field for e-mail and e-mail has a specific format.
Zend Framework has a rich set of filters and validator classes (Zend\Filter
, Zend\Validator
, Zend\InputFilter
). Here we will see how to filter and validate the client’s data using Zend\InputFilter
.
Let’s update our newAction
method in the homeController
file with the following code:
public function newAction() { $invalids = array(); $filter = array( ‘name’ => array( ‘name’ => ‘name’, ‘required’ => true, ‘filters’ => array( array(‘name’ => ‘StripTags’), array(‘name’ => ‘StringTrim’) ), ‘validators’ => array( array( ‘name’ => ‘not_empty’, ), array( ‘name’ => ‘string_length’, ‘options’ => array( ‘min’ => 3 ), ), ), ), ‘email’ => array( ‘name’ => ‘name’, ‘required’ => true, ‘filters’ => array( array(‘name’ => ‘StripTags’), array(‘name’ => ‘StringTrim’) ), ‘validators’ => array( array( ‘name’ => ‘not_empty’, ), array( ‘name’ => ‘email_address’, ), ), ), ‘phone’ => array( ‘name’ => ‘name’, ‘required’ => true, ‘filters’ => array( array(‘name’ => ‘StripTags’), array(‘name’ => ‘StringTrim’) ), ‘validators’ => array( array( ‘name’ => ‘not_empty’, ), ), ), ); if ($_POST) { $factory = new \Zend\InputFilter\Factory(); $input = $factory->createInputFilter($filter); $input->setData($_POST); if($input->isValid()){ $contact = $this->getServiceLocator() ->get(‘Contact\Model\Contact’); $contact->addRow($_POST); return $this->redirect()->toRoute(‘home’); }else{ $invalids = $input->getInvalidInput(); } $data = $input->getValues(); } return new ViewModel(array(‘row’=>$data, ‘invalids’=>$invalids)); }
In the $filter
array, we have set filter and validator rules for each field; for example, for the name field, we have set two filters, StripTags
and StringTrim
, and two validators, not_empty
and string_length
.
As the name suggests, the StripTags
filter removes any unwanted HTML tags, and StringTrim
removes any unwanted space or newline character from the beginning and end of the given string.
Now we will activate our input filter using the Zend\InputFilter\Factory
class and pass our input data to validate with the setData
method.
If the inputs are valid, we will save the data in the database, or we will show error messages on the form view page. We will get all invalid inputs with the getInvalidInput()
method, and we will assign back our data and invalid inputs to the view page with the following code:
new ViewModel(array(‘row’=>$data, ‘invalids’=>$invalids));
We have three fields in our form: name
, email
, and phone
. If one of them is invalid or missing, the other field’s value needs to stay there otherwise it will be annoying for the user to fill out the whole form every time one or more inputs are invalid.
Let’s update our form view file (new.phtml
) to show the error message as follows:
<hr /> <div> <div> <?php foreach($invalids as $err): $msgs = $err->getMessages(); foreach($msgs as $m){ echo ‘<div>’.$m.’</div>’; } endforeach;?> </div> <form method=”post” action=”new”> Name <br /> <input type=”text” name=”name” value=”<?=$row[‘name’]?>” /> <br /> Email <br /> <input type=”text” name=”email” value=”<?=$row[‘email’]?>” /> <br /> Phone <br /> <input type=”text” name=”phone” value=”<?=$row[‘phone’]?>” /> <br /> <br /><br /> <input type=”submit” value=”Save” /> </form> </div>
Now if we browse to this page and submit the form with invalid input(s), we should see errors as follows:
Value is required and can’t be empty
The input is less than 3 characters long
Value is required and can’t be empty
The input is not a valid email address. Use the basic format local-part@hostname
Value is required and can’t be empty
We can use CSS to make these error messages more stylish.
Besides StripTags
and StringTrim
, Zend Framework has a few more standard filter classes such as NumberFormat
, PregReplace
, StringToLower
, Digits
, Alpha
, and Alnum
. You will find the full list of standard filter classes in the manual under the Zend\Filter
package.
Just like filter classes, validator has many standard classes in Zend Framework such as CreditCard
, Date
, Between
, Digits
, Identical
, ISBN
, InArray
, Alnum
, and Alpha
. You will find the full list of the standard validator classes in the manual under the Zend\Validator
package.
You might be wondering why the EmailAddress
class is written as email_address
, or the NotEmpty
class as not_empty
in the validators’ array. These are just class mappings, and you will find the full list of these names in the Zend\Validator\ValidatorPluginManager.php
file’s $invokableClasses
array. Note that the array key doesn’t have any underscore (_
) because mapping will ignore any underscore character; for example, not_empty
and notempty
will mean the same.
View helper
In the last code example in the view page, we have printed the error messages using the following code:
<?php foreach($invalids as $err): $msgs = $err->getMessages(); foreach($msgs as $m){ echo ‘<div>’.$m.’</div>’; } endforeach;?>
So, it will look really bad if we have many forms and we need to print the error messages with a repetitive code like this every time. Zend Framework has a good solution for this: we can create a View Helper for this repetitive code.
Basically, a helper is a class that implements at least Zend\View\Helper\HelperInterface
and has a setView
and getView
method. Another way to create a custom view helper is by extending Zend\View\Helper\AbstractHelper
. This AbstractHelper
method implements HelperInterface
giving you a head start in your development.
Let’s create our very first custom view helper. We will call it ErrorMsg
, and it will simply take an array and return a string. We will create our ErrorMsg.php
file inside the src/Contact/View/Helper
directory. So, our updated directory structure will now look as follows:
/Contact /config /src /Contact /Controller /Model /View /Helper /view /contact /error /layout /Module.php
Code for our ErrorMsg
helper class will be as follows:
<?php namespace Contact\View\Helper; class ErrorMsg extends \Zend\View\Helper\AbstractHelper{ public function __invoke($value){ $msg = ‘’; if(count($value)>0){ foreach($value as $err){ $msgs = $err->getMessages(); foreach($msgs as $m){ $msg .= ‘<div>’.$m.’</div>’; } } } return $msg; } }
Now we have our helper
class but to use this class on the view file, we need to register it. One way to do this is by adding a getViewHelperConfig
method in the Module
class as follows:
public function getViewHelperConfig() { return array( ‘invokables’ => array( ‘error_msg’ => ‘Contact\View\Helper\ErrorMsg’ ), ); }
Our helper is now fully ready to be used in the view file. Let’s replace the previous error message showing code with the following line of code:
<?=$this->errorMsg($invalids);?>
This will show the error messages same as the previous code but is more clean. Let’s see our final code for the new.phtml
view file.
<hr /> <div> <div><?=$this->errorMsg($invalids)?></div> <form method=”post” action=”new”> Name <br /> <input type=”text” name=”name” value=”<?=$row[‘name’]?>” /><br /> Email <br /> <input type=”text” name=”email” value=”<?=$row[‘email’]?>” /> <br /> Phone <br /> <input type=”text” name=”phone” value=”<?=$row[‘phone’]?>” /> <br /> <br /><br /> <input type=”submit” value=”Save” /> </form> </div>
Besides the custom view helper, Zend Framework has several built-in view helpers. Some of them are BasePath
, Cycle
, Doctype
, HeadLink
, HeadMeta
, HeadScript
, HeadStyle
, HeadTitle
, InlineScript
, JSON
, Partial
, and Placeholder
.
If you open the view/layout/layout.phtml
file, you will see that we have already used Doctype
and BasePath
view helper in it as <?php echo $this->doctype();?>
and <?=
$this->basePath()?>
.
Zend Log
We often need to log in to our apps for various reasons, such as debugging or error printing. We will see how we can use Zend\Log
in this small app that we are building, but before we begin, let’s see a few more points about Zend\Log
as follows:
Most applications use the
Zend\Log\Logger
class’s object the most. You can have as many logger objects as you like.A logger object must contain at least one writer and can optionally contain one or more filters.
A writer is responsible for saving the data to storage.
A filter blocks the log data from being saved. A filter is applied to an individual writer. Filters can be chained.
A formatter can format the log data before it is written by a writer. Each writer has exactly one formatter.
Let’s get started!
We will be introducing our logger in the Module
class’s getServiceConfig
method. We will add another Logger
element in the factories
array as follows:
public function getServiceConfig() { return array( ‘factories’ => array( ... ‘Logger’ => function($sm) { $logger = new \Zend\Log\Logger; $writer = new \Zend\Log\Writer\Stream(‘data.log’); $logger->addWriter($writer); return $logger; } ), ); }
Here in the code, our log filename is data.log
, and it will be created in the application’s root directory; you can also give the filename with the full path if you want. Our logger is ready to do any kind of logging, so let’s do a test.
Add the following lines of code in the HomeController’s
indexAction
method.
$log = $this->getServiceLocator()->get(‘Logger’); $log->info(‘Hello Zend Log’);
Now run the indexAction
action by going to http://localhost/address-book/public
. If everything went well, you should see a data.log
file in the application’s root path and in that file a line of text saying ‘Hello Zend Log’
.
Zend EventManager
Zend Framework 2 introduces a new EventManager
component. It allows a class to publish events that other objects can listen to and then act when the event occurs. This component is designed for the following cases:
Implementing simple subject/observer patterns
Implementing aspect-oriented designs
Implementing event-driven architectures
The biggest advantage of using EventManager
in our application is that we can decouple classes that really shouldn’t be coupled together, which makes the code easier to write and maintain. In fact, this is so useful that Zend Framework 2’s MVC system makes heavy use of EventManager
.
Let’s do some event handing in our small app. Suppose we call a function when a new contact is inserted, edited, or deleted. This means we will create an insert event, an edit event, and a delete event.
We will make our Contact
model as an EventManagerAware
class. To do this, we need to implement the EventManagerAwareInterface
interface.
Let’s open and update the Contact
module with the following code:
<?php namespace Contact\Model; use Zend\EventManager\EventManagerInterface; use Zend\EventManager\EventManager; use Zend\EventManager\EventManagerAwareInterface; class Contact implements EventManagerAwareInterface { private $_db; protected $events; public function setEventManager(EventManagerInterface $events) { $events->setIdentifiers(array( __CLASS__, get_called_class(), )); $this->events = $events; return $this; } public function getEventManager() { if (null === $this->events) { $this->setEventManager(new EventManager()); } return $this->events; } public function __construct($db) { $this->_db = $db; } public function getAllRows() { $sql = “select * from contact”; $stat = $this->_db->query($sql); return $stat->fetchAll(); } public function addRow($data){ $this->getEventManager()->trigger(‘event.insert’, $this); $sql = “INSERT INTO contact (name,email,phone) VALUES (‘{$data[‘name’]}’,’{$data[‘email’]}’,’{$data[‘pho ne’]}’)”; return $this->_db->exec($sql); } public function getRow($id) { $sql = “select * from contact where id=?”; $stat = $this->_db->prepare($sql); $stat->execute(array($id)); return $stat->fetch(); } public function updateRow($data, $id) { $this->getEventManager()->trigger(‘event.edit’, $this); $sql = “UPDATE contact SET name=’{$data[‘name’]}’, email=’{$data[‘email’]}’, phone=’{$data[‘phone’]}’ WHERE id={$id} “; return $this->_db->exec($sql); } public function delRow($id){ $this->getEventManager()->trigger(‘event.delete’, $this); $sql = “delete from contact where id={$id}”; return $this->_db->exec($sql); } }
Take a detailed look at the code. Note that we have added two new methods, setEventManager
and getEventManager
, and the Contact
class is implementing the EventManagerAwareInterface
method, which also extends EventsCapableInterface
. Here in the Contact
module, setEventManager
is a part of EventManagerAwareInterface
and getEventManager
is part of the EventsCapableInterface
interface.
Then, look at the addRow
method. Also, in the updateRow
and delRow
methods, we have triggered the events event.insert
, event.edit
, and event.delete
. Whenever these methods are executed, they will trigger those events; if we call addRow
, it will trigger the event.insert
event, and if we call the delRow
method, it will trigger the event.delete
event. Now we need to attach a listening method to each of these events to do some actions. An event has no point without its listener.
We will add our event listeners in our Module
class’s getServiceConfig
method where we will first instantiate the contact model. Let’s update the previous code with the following one:
public function getServiceConfig() { return array( ‘factories’ => array( ‘Contact\Model\Contact’ => function($sm) { $db_adapter = $sm->get(‘db_adapter’); $contact = new \Contact\Model\Contact($db_adapter); $log = $sm->get(‘Logger’); $eventManager = $contact->getEventManager(); $eventManager->attach(‘event.insert’, function ($e) use ($log) { $event = $e->getName(); $log->info(“{$event} event triggered”); }); $eventManager->attach(‘event.edit’, function ($e) use ($log) { $event = $e->getName(); $log->info(“{$event} event triggered”); }); $eventManager->attach(‘event.delete’, function ($e) use ($log) { $event = $e->getName(); $log->info(“{$event} event triggered”); }); return $contact; }, ‘Logger’ => function($sm) { $logger = new \Zend\Log\Logger; $writer = new \Zend\Log\Writer\Stream(‘data.log’); $logger->addWriter($writer); return $logger; } ), ); }
Now add a new contact, edit a contact, and delete a contact, and if everything went well, we will see the events in the log it prints as follows:
event.insert event triggered. event.edit event triggered. event.delete event triggered.
Uploading a file
File upload capability is very common in web applications. Zend Framework has a component (Zend\File\Transfer
) to handle a file upload. Here I will show how to utilize this component for a file upload.
First, let’s create another action method in our home controller. We will be naming it as fileUploadAction
, and we will need to add a file-upload.phtml
file in the /view/contact/home
directory. In the file-upload.phtml
file, we will add code for an HTML form with enctype=multipart/form-data
to handle the file upload. Let’s create the code as follows:
<hr /> <div> <div><?php print_r($msg)?></div> <form method=”post” action=”file-upload” enctype=”multipart/form-data”> File <br /> <input type=”file” name=”doc” /> <br /><br /> <input type=”submit” value=”Upload” /> </form> </div>
Here, print_r($msg)
will print all kinds of messages and errors it will encounter when trying to upload the file. Our form’s action is to upload the file using the home controller’s fileUploadAction
method.
Now lets see the code for the fileUploadAction
method:
public function fileUploadAction() { if($this->getRequest()->isPost()){ $adapter = new \Zend\File\Transfer\Adapter\Http(); $adapter->setDestination(‘public/uploads’); $files = $this->getRequest()->getFiles(); if ($adapter->receive($files[‘doc’][‘name’])) { return new ViewModel( array(‘msg’=>$files[‘doc’][‘name’].’ uploaded!’) ); } } }
This is a very minimalistic code for a file upload. In this method, Zend\File\Transfer\Adapter\Http
is the file transfer adapter class for the HTTP protocol. We have set our destination directory or the upload directory with the setDestination()
method. We have set our destination path as public/uploads
. So, we need to create an uploads directory inside the public directory of our application.
Here in the code, the getFiles()
method returns the same value as the PHP’s $_FILES
variable.
Let’s go a little deeper in the file upload. In the previous version of the code, we didn’t validate what kind of file we should be uploading or what should be the size of the file. To do this, Zend Framework has various kinds of file validator class in the Zend\Validator
component; for example, Crc32
, Extension
, Hash
, ImageSize
, IsImage
, IsCompressed
, Size
, and WordCount
.
Suppose we want to allow only PDF-type files of size 10 KB to 10 MB to be uploaded. For this, we need to update our previous fileUploadAction
method with this code:
public function fileUploadAction() { if($this->getRequest()->isPost()){ $size = new \Zend\Validator\File\Size( array(‘min’ => ‘10kB’, ‘max’ => ‘10MB’) ); $ext = new \Zend\Validator\File\Extension(‘pdf’); $files = $this->getRequest()->getFiles(); $adapter = new \Zend\File\Transfer\Adapter\Http(); $adapter->setValidators(array($size,$ext)); $adapter->setDestination(‘public/uploads’); if($adapter->isValid()){ if ($adapter->receive($files[‘doc’][‘name’])) { return new ViewModel( array(‘msg’=>$files[‘doc’][‘name’].’ uploaded!’) ); } }else{ return new ViewModel( array(‘msg’=>$adapter->getMessages()) ); } } }
Here in the code, we have added two validators with the setValidators
method; one validator is to check the PDF extension and another one is to check whether the size of the file is between 10 KB to 10 MB.
If the file is valid, it will upload the file to the destination directory.
Like the validators, Zend Framework also provides some file filter classes; for example, Encrypt
, Lowercase
, Uppercase
, and Rename
. We can add these filter classes with the setFilters
method on the adapter class instance just as we did for the setValidators
method.