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 © 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);}