Book Image

Instant Zend Framework 2.0

By : A N M Mahabubul Hasan
Book Image

Instant Zend Framework 2.0

By: A N M Mahabubul Hasan

Overview of this book

Zend Framework 2 is a modern object-oriented PHP framework. It has lots of useful components, which are easy to use and user friendly, helping you get your job done faster. This book shows you how to use the framework components to build a practical MVC application. Topics covered in this application include validation, file upload, database CRUD (create, read, update, and delete) operations, and so on.Instant Zend Framework 2.0 has been specifically created to provide you with all the information that you need to get up with Zend Framework 2 applications. You will learn the basic of Zend Framework 2.0, and get started with building your first Zend Framework MVC application. The book promises to deliver all the information you need to get started with Zend Framework 2.0. Instant Zend Framework 2.0 starts with how to download Zend Framework 2.0 and the skeleton application, and then shows how to configure the skeleton application to use the Zend Framework 2.0. Then it takes you through step-by-step instructions of how to create and configure modules, controllers, views, models, helpers and so on. By following the instructions in the book, you will be able to build a small fully functional database-driven MVC application. You will also learn how to use different framework components like the event manager, Zend log, and how to configure them with the module. This will give you an idea of how to configure any component with the module. After finishing this book you will be able to build a small database-driven web application with Zend Framework 2.
Table of Contents (7 chapters)

Quick start – creating your first web application


In this section, we will be creating a small address book application to demonstrate some basic functionality of Zend Framework, including the database create, read, update, and delete (CRUD) operation. In this small application, the user will be able to add new contacts and edit and delete contacts.

Step 1 – creating a module

Let’s look again inside the address-book directory that we have created in the Step 2 – unzipping the skeleton application section. There is a config directory and inside that you will see an autoload directory and an application.config.php file. In this application.config.php file, we can tell the framework which modules to load and we can tell the paths where to look for those modules.

There is another directory called module where we will put all our custom modules. By default, there is an Application module in this directory; however, we are not going to use that. We will create our own custom module named Contact. To create a module, we need to do at least two things: first, create a directory called Contact. Second, inside that Contact directory, we need to add a Module.php file.

Let’s create a Contact directory inside the module directory and the Module.php file inside that Contact directory. Now the module directory should look like the following:

/module
  /Contact
    Module.php

Add the following lines of code to the Module.php file:

<?php
namespace Contact;

class Module{    

}

And with that, we have just created our very first module.

Now we need to tell Zend Framework to load this Contact module. To do that, we need to add the following lines of codes in the application.config.php file:

<?php
return array(    
    ‘modules’ => array(
        ‘Contact’,
    ),    
    ‘module_listener_options’ => array(        
        ‘module_paths’ => array(
            ‘./module’,            
        ),
    ),
);

Although we have now created and loaded our first module, it will not give us any output. If we browse to the URL http://localhost/address-book/public, it will give us an error because we have not created any controller and view yet.

Step 2 – creating a controller

If you look at the Application module that comes by default with the skeleton app, you will notice its directory structure is somewhat similar to the following code snippet:

/module
  /Application
        /config
              /module.config.php
        /src
              /Application
                       /Controller
                       /IndexController.php
        /view
        /Module.php

The config directory has a module.config.php file that holds all module-specific configurations such as router, service_manager, controllers, and view_manager. The src directory holds Controllers and Models, and the view directory holds all the view-related things; I will come back to this in the next step.

Let’s create our first controller home for our Contact module, and for this we need to create the directory structure as follows:

/Contact
  /config
      /module.config.php
  /src
      /Contact
             /Controller
                    /HomeController.php
             /Model
  /view
  /Module.php

Note that inside the src directory, we have the /Contact/Controller directory and inside that we have HomeController.php.

In ZF, the controller ends with the Controller suffix. So, as our controller name is Home, its class name should be HomeController and the filename should be the same as the class name. Our HomeController.php should look as follows:

<?php
namespace Contact\Controller;
use Zend\Mvc\Controller\AbstractActionController;

class HomeController extends AbstractActionController
{
    public function indexAction()
    {
                echo ‘Hello Zend Framework 2’;
        return $this->response;
    }
}

Step 3 – configuring a controller

We have created our first controller in the last step; however, to make it work, we need to configure it in config/module.config.php and Module.php.

Add the following code in the module.config.php file:

<?php
return array(
    ‘router’ => array(
        ‘routes’ => array(
            ‘home’ => array(
                ‘type’ => ‘Zend\Mvc\Router\Http\Literal’,
                ‘options’ => array(
                    ‘route’    => ‘/’,
                    ‘defaults’ => array(
                        ‘controller’ => ‘Contact\Controller\Home’,
                        ‘action’     => ‘index’,
                    ),
                ),
            ),            
            ‘contact’ => array(
                ‘type’    => ‘Literal’,
                ‘options’ => array(
                    ‘route’    => ‘/contact’,
                    ‘defaults’ => array(
                        ‘__NAMESPACE__’ => ‘Contact\Controller’,
                        ‘controller’    => ‘Home’,
                        ‘action’        => ‘index’,
                    ),
                ),

                ‘may_terminate’ => true,
                ‘child_routes’ => array(
                    ‘default’ => array(
                        ‘type’    => ‘Segment’,
                        ‘options’ => array(
                            ‘route’    => ‘/[:controller[/:action]]’,
                            ‘constraints’ => array(
                                ‘controller’ => 

                             ‘[a-zA-Z][a-zA-Z0-9_-]*’,
                                ‘action’     => ‘

                             ‘[a-zA-Z][a-zA-Z0-9_-]*’,
                            ),
                            ‘defaults’ => array(

                            ),
                        ),
                    ),
                ),
            ),
        ),
    ),
    ‘controllers’ => array(
        ‘invokables’ => array(
            ‘Contact\Controller\Home’ => 

            ‘Contact\Controller\HomeController’
        ),
    ),
);

Add the following code in the Module.php file:

<?php
namespace Contact;

use Zend\Mvc\ModuleRouteListener;
use Zend\Mvc\MvcEvent;

class Module
{
        public function onBootstrap(MvcEvent $e)
    {        
        $eventManager = $e->getApplication()->getEventManager();
        $moduleRouteListener = new ModuleRouteListener();
        $moduleRouteListener->attach($eventManager);
    }

    public function getConfig()
    {
        return include __DIR__ . ‘/config/module.config.php’;
    }

    public function getAutoloaderConfig()
    {
        return array(
            ‘Zend\Loader\StandardAutoloader’ => array(
                ‘namespaces’ => array(
                    __NAMESPACE__ => 
                               __DIR__ . ‘/src/’ . __NAMESPACE__,
                ),
            ),
        );
    }
}

Now, in the browser go to the address book app http://localhost/address-book/public. You should see a page saying Hello Zend Framework 2.

Step 4 – creating a view

So far we have created and configured a controller, and now we will place all our view files inside the view directory of the Contact module. A view file has a close relation with the controllers. By default, every controller and action expects that there should be a corresponding view file for them. So this means if our controller name is Home and action name is Index, there should be a .phtml file in the view directory as follows:

/view
  /contact
    /home
      /index.phtml

For the view file, ZF uses the .phtml extension. Another important thing is that for showing all our application errors, we need to put the .phtml files in the error directory. We will create an index.phtml file for all general purpose errors and a 404.phtml file for all page not found errors. So finally at this point, our view directory should be similar to the following code snippet:

/view
  /contact/
        /home
            /index.phtml
  /layout
        /layout.phtml
  /error
        /index.phtml
        /404.phtml

Let’s add some code in these .phtml files. Add the following code in the error/index.phtml file:

<h1>An error occurred</h1>
<h2><?php echo $this->message ?></h2>

Add the following code in the error/404.phtml file:

<h1>Page not found</h1>
<h2><?php echo $this->message ?></h2>

Add the following code in the home/index.phtml file:

This is a view file of <b>(<?=$controller?>)</b> / (<?=$action?>).

Here, the value of the $controller and $action variable will come from the controller. I will show you how, in a moment, but before that let’s talk about layouts.

Layout is a special kind of view that wraps the action’s view; for example, in a normal website, you will see that the header and footer are common for every page. This doesn’t mean they have written the header and footer in every single page. These common parts are normally written in layout. We will place a very basic layout.phtml file in the layout directory.

Let’s add the following code in the layout.phtml file:

<?php echo $this->doctype(); ?>
<html lang=”en”>
    <head>
        <meta charset=”utf-8”>
        <title>Address Book</title>
        <base href=”<?=$this->basePath()?>/” />
    </head>
    <body>
        <h1>Address Book</h1>
         <hr />
        <div class=”container”>
            <?php echo $this->content; ?>            
             <hr />
            <footer>
                <p>Address Book &copy; 2013</p>
            </footer>
        </div>        
    </body>
</html>

Here in the code, the <base> tag is important because we have not created a virtual host, and our path is not directly under the web root directory. Also, the $this->content; function is important as it shows the action-specific view.

Now, let’s configure all our view files with view_manager in the module.config.php file with the following code:

<?php
return array(
    ‘router’ => array(...),
    ‘controllers’ => array(..),

           ‘view_manager’ => array(
        ‘display_not_found_reason’ => true,
        ‘display_exceptions’       => true,
        ‘doctype’                  => ‘HTML5’,
        ‘not_found_template’       => ‘error/404’,
        ‘exception_template’       => ‘error/index’,

        ‘template_map’ => array(
            ‘layout/layout’      => __DIR__ . ‘/../view/layout/layout. 
                                    phtml’,

            ‘contact/home/index’ => __DIR__ . ‘/../view/contact/home/
                                    index.phtml’,

            ‘error/404’          => __DIR__ . ‘/../view/error/404.
                                    phtml’,

            ‘error/index’     => __DIR__ . ‘/../view/error/index.
                                 phtml’,

        ),
        ‘template_path_stack’ => array(
            __DIR__ . ‘/../view’,
        ),
    ),
);

So view is now configured. To pass the value from the controller to a view file, we need to update the home controller’s indexAction method with the following code:

<?php
namespace Contact\Controller;

use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;

class HomeController extends AbstractActionController
{
    public function indexAction()
    {
         $data[‘action’] = __FUNCTION__;
         $data[‘controller’] = __CLASS__;
         return new ViewModel($data);
    }
}

Now let’s browse the app again. Go to http://localhost/address-book/public, and you should see the output similar to the following screenshot:

Step 5 – configuring a database

When working with a database, ZF is quite flexible; you can either use any third-party Object Relational Mapper (ORM) such as Doctrine or Propel, or you can use ZF’s own DB library. However, here I will not show any of this because it is beyond the scope of this book. Rather, I will show you a different and easy way with PHP’s native PDO library (http://php.net/manual/en/book.pdo.php) that most PHP developers are aware of.

Let’s say we create a database named address_book_db. To configure this with the Contact module, open the module.config.php file, and add the following lines of code in the sevice_manager file:

<?php
return array(
    ‘router’ => array(...),
    ‘controllers’ => array(..),
    ‘db’ => array(
        ‘dsn’ => ‘mysql:dbname=address_book_db;host=localhost’,
        ‘username’ => ‘your_db_username’,
        ‘password’ => ‘your_password’,
    ),
    ‘service_manager’ => array(
        ‘factories’ => array(
               ‘db_adapter’ => function($sm) {
                 $config = $sm->get(‘Config’)[‘db’];

                      return new PDO( $config[‘dsn’],
                                     $config[‘username’],
                                     $config[‘password’]);
                 }
        ),
    ),
    ‘view_manager’ => array(...),
);

Replace the db name if you have used a different name, and also your MySQL username and password. At this point, the database is configured. Now we can access this connection from any ServiceLocatorAware class in any module.

Step 6 – creating a model

Zend Framework does not prove a Zend\model component because model is just a simple class where we write all our business logic. That’s why it is up to us to choose how we want it to work.

For this project, we will create a very basic model that will do CRUD operation using the PDO library. So let’s create our first model inside the src/Contact/Model directory. We will name this model as Contact, so we need to create a Contact.php file inside the Model directory, and in this Contact.php file, we need to add the following code:

<?php
namespace Contact\Model;

class Contact{
    private $_db;
    
    public function __construct($db) {
        $this->_db = $db;        
    }
}

Note that in the code, the constructor has a $db argument. This is the PDO class instance. We will inject this PDO class instance from our Module class. To do this, we need to add the getServiceConfig method in the Module class as follows:

public function getServiceConfig() {
        return array(
            ‘factories’ => array(
                ‘Contact\Model\Contact’ => function($sm) {
                    $db_adapter = $sm->get(‘db_adapter’);
                    return new\Contact\Model\Contact($db_adapter);
                }
            ),
        );
    }

Now, at this point, our model is fully capable of doing any kind of database operation with PDO. One of the ways to access this model from any controller is as follows:

$contact = $this->getServiceLocator()->get(‘Contact\Model\Contact’);

Step 7 – project in the big picture

So far we covered several configurations: how to create controller, model, and view, and how to connect with the database. Now we will see the project in the big picture.

Before we begin, let’s create a table named contact using SQL as follows:

CREATE TABLE IF NOT EXISTS `contact` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) NOT NULL,
  `email` varchar(50) NOT NULL,
  `phone` varchar(10) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1;

We will create our address book app’s home page similar to this mockup layout:

Note that in the layout, we have the Add new contact, Edit, and Delete links. When a user clicks on the Add new contact link, we will take the user to the add new contact page, and after successful submission of the form, we will save the information and redirect the user to this home page and update the grid with the new row.

Again, if the user clicks on the Edit link, we will open that row in the edit contact page, just like the add new contact page; after successful submission of the form, we will update the information and redirect the user to this home page and update the row with the updated contact information. Clicking on the Delete link will not have any visual page; the user will just click on the link and it will take him to the controller and delete the row and redirect him to the home page with that row deleted.

The add and edit contact page will be similar to the following screenshot:

Let’s start coding. First, open the Contact model that we created in the Step 6 – creating a model section. We will add a new getAllRows() method to it that will fetch all the rows from the contact table. Code for the getAllRows() method is as follows:

    public function getAllRows() {
        $sql = “select * from contact”;
        $stat = $this->_db->query($sql);
        return $stat->fetchAll();
    }

Here in this method, the $this->_db instance is the PDO object, which we have injected in the contact model’s constructor from the Module class.

Now open the HomeController class, and in the indexAction method, assign all the rows returned by the getAllRows() method to our view page. To do so, we need to update our indexAction method with the following code:

public function indexAction() {
  $contact = $this->getServiceLocator()
                        ->get(‘Contact\Model\Contact’);
  $data[‘rows’] = $contact->getAllRows();
  return new ViewModel($data);
}

Now, open the contact/home/index.phtml file, and add the following code to view all the rows:

<div><a href=”contact/home/new”>Add new Contact</a></div>
<hr />
<div>
    <table>
        <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Phone</th>
            <th>Action</th>
        </tr>
    <?php if(count($rows)>0):
        foreach($rows as $r):?>    
        <tr>
            <td><?=$r[‘name’]?></td>
            <td><?=$r[‘email’]?></td>
            <td><?=$r[‘phone’]?></td>
            <td><a href=”contact/home/edit?id=<?=$r[‘id’]?>”>Edit</a>
                / 
            <a href=”contact/home/delete?id=<?=$r[‘id’]?>”>Delete</a>
            </td>
        </tr>
    <?php endforeach;endif;?>
    </table>
</div>

At this point, there are no data in the contact table. If we manually insert some data and browse to http://localhost/address-book/public, we will see an output as shown in the following screenshot:

Now we will create the add new contact form, and a newAction method in homeController and a addRow method in the Contact model.

Add the following code in the newAction method of homeController:

  public function newAction(){
        if($_POST){
            $contact = $this->getServiceLocator()
                             ->get(‘Contact\Model\Contact’);
            $contact->addRow($_POST);
            return $this->redirect()->toRoute(‘home’);
        }
        
        return new ViewModel($_POST);
    }

Here, $this->redirect() is a controller plugin. There are several other controller plugins like this; for example, param, forward, and layout.

What this redirect plugin does in this code is that after submission of the form, the addRow() method saves the data to the database and then redirects the plugin to do the redirection of the client to the home route. Remember, we have defined this home route in the module.config.php file’s router section.

Let’s create a new.phtml file in the view/contact/home/ directory, and add the following code:

<hr />
<div>
    <form method=”post” action=”new”>
    Name <br />
    <input type=”text” name=”name” value=”<?=$name?>” /><br />
    Email <br />
    <input type=”text” name=”email” value=”<?=$email?>” /><br />
    Phone <br />
    <input type=”text” name=”phone” value=”<?=$phone?>” /><br />
    <br /><br />
    <input type=”submit” value=”Save” />
    </form>
</div>

Now, open the contact model and add the addRow method to it as follows:

public function addRow($data){
    $sql = “INSERT INTO
            contact (name,email,phone)
            VALUES (‘{$data[‘name’]}’, 
                                  ‘{$data[‘email’]}’, 
                                  ‘{$data[‘phone’]}’)”;

    return $this->_db->exec($sql);
}

Now open http://localhost/address-book/public/home/new. You should see the new contact form. Fill up the form and click on the Save button. It will save the data to the database and redirect you to the home page with the new row showing.

The edit contact code is similar to the new contact code except that we need a to add a few extra lines of code; for example, the edit URL needs to carry the ID of the contact that we want to edit as shown in the following line of code:

contact/home/edit?id=<?=$r[‘id’]

Then we need to fetch the contact row from the contact table and assign it to the edit contact form. So, we need a method such as getRow($id) that will return the row for $id.

Let’s create the getRow() method in the contact model.

public function getRow($id) {
    $sql = “select * from contact where id=?”;

    $stat = $this->_db->prepare($sql);
    $stat->execute(array($id));
    return $stat->fetch();
}

Also, we will need another method that will update the contact data. So we need to add an updateRow() method in this model as follows:

public function updateRow($data, $id) {
    $sql = “UPDATE contact SET
        name=’{$data[‘name’]}’,
        email=’{$data[‘email’]}’,
        phone=’{$data[‘phone’]}’
        WHERE id={$id}
        “;

    return $this->_db->exec($sql);
}

Now open the homeController file, and add a new editAction method to it as follows:

public function editAction(){
    $id = $this->params()->fromQuery(‘id’,0);

    $contact = $this->getServiceLocator()
                        ->get(‘Contact\Model\Contact’);

    if($_POST){            
        $contact->updateRow($_POST, $id);
        return $this->redirect()->toRoute(‘home’);
    }else{
        $row = $contact->getRow($id);
    }

    return new ViewModel($row);
}

Here, $this->params() is another controller plugin. So, what this edit action does is it takes the ID from the request URL and assigns it to the $id variable, and then fetches the row from the database based on that ID and assigns it to the edit view file. When the form is submitted, it saves the data to the contact table and redirects the user to the home route.

We need to add an edit.phtml file in the view/contact/home/ directory, and add the following code to it:

<hr />
<div>
    <form method=”post” action=”edit?id=<?=$id?>”>
    Name <br />
    <input type=”text” name=”name” value=”<?=$name?>” /><br />
    Email <br />
    <input type=”text” name=”email” value=”<?=$email?>” /><br />
    Phone <br />
    <input type=”text” name=”phone” value=”<?=$phone?>” /><br />
    <br /><br />
    <input type=”submit” value=”Save” />
    </form>
</div>

Note that it is the same as the new.phtml file; the only difference is the form method’s action URL.

The delete action is pretty straightforward. It will not have any view file, and it will just get the ID from the URL and delete the contact from the database using the contact model.

Code for the delete action is as follows:

public function deleteAction(){
    $id = $this->params()->fromQuery(‘id’,0);

    $contact = $this->getServiceLocator()
                              ->get(‘Contact\Model\Contact’);

    $contact->delRow($id);
    return $this->redirect()->toRoute(‘home’);
}

Code for the delRow method of the Contact model is as follows:

public function delRow($id){
    $sql = “delete from contact where id={$id}”;
    return $this->_db->exec($sql);}