You can either install the project from existing sources or
create it from scratch. If use the sources,
download the ZendBooks.zip archive,
extract it into the desired location
and modify to suit your installation:
public/.htaccess reset RewriteBase if necessary
public/index.php replace '/usr/local/share/ZendLibrary' if necessary,
To create the application from scratch, run MakeZFProject
and give it the Project Name of ZendBooks.
This is a full CRUD application with only minimal JavaScript
usage.
Database setup
This application assumes the common MySQL database
setup for testing:
host: localhost
dbname: test
username: guest
password: <empty>
The test database holds the
books table with the usual content.
Creating this is a matter of executing the following:
mysql -u root
mysql> create database if not exists test;
mysql> create user guest@localhost;
mysql> grant all on test.* to guest@localhost;
If the "create user" statement fails, it means the
guest user already exists;
just go on to the last statement.
Create/initialize the books table
The easiest way to create the books table is to
open a terminal shell,
navigate to the ZendBooks folder
and execute
the mysql command-line client running
the table-creation script:
mysql -u guest test < table.sql
The table is created from scratch and
populated with initial content.
The table.sql script of can be seen here:
( click to show )
Layout
We have modified the layout used in the
ZendHello application from the
Zend Framework Intro document.
We expect the navigational content to differ at different pages
in the application and so
the function nav is created
and made available at the view level. In Zend terms,
nav is a view helper function and
defined in the following class:
application/views/helpers/Nav.php
<?php
class Zend_View_Helper_Nav {
public function nav() {
return $this->view->render("nav.phtml");
}
public $view;
public function setView(Zend_View_Interface $view)
{
$this->view = $view;
}
}
Zend makes the connection from:
the nav function, assumed to be a viewhelper
to the path to the class (relative to application)
views/helpers/Nav.php
and within this file, the class name:
class Zend_View_Helper_Nav
and finally to the desired function:
public function nav() {
return $this->view->render("nav.phtml");
}
When Zend instantiates the Zend_View_Helper_Nav
class, the function setView
is automatically called with the current
$view object passed to it.
The job of setView is to make $view available to the
nav function.
The nav function renders the script nav.phtml, which is
assumed to be in views/scripts (relative to application)
application/views/scripts/nav.phtml
<? if (!isset($this->navMenu)) return ?>
<table class="nav"><tr>
<? foreach($this->navMenu as $entry): ?>
<td>
<a href="<?= $this->url($entry['url']) ?>"><?=$this->escape($entry['label'])?></a>
</td>
<? endforeach ?>
</tr></table>
The nav.phtml script generates a table of navigational links
defined by the array variable:
Thus, the way a controller specifies what navigational menu
to use is by setting:
$this->view->navMenu = ...
The nav.phtml script uses the built-in view function url
in the expression:
<?= $this->url($entry['url']) ?>
in order to convert an action_controller_array into actual URL.
Cart creation
This application features
a common notion of creating of a "shopping cart" of books.
In this simplistic situation, the actual cart is treated as an
array which maps book id numbers to quantityies.
The important idea is that this cart must belong to some
persistent storage. The simplest solution is to store the cart
in the current browser session so that
this information is not lost at least as long as the browser session is active.
Zend regards sessions in this way:
$cartSpace = new Zend_Session_Namespace('ZendBooks-Cart');
corresponds roughly to the usual Php notation:
$_SESSION['ZendBooks-Cart']
The difference is that $cartSpace is an object and we access and/or create
members in this object via:
$cartSpace->cart = /* this is the actual cart */
Out treatement of $cartSpace->cart is to regard this as a map from
id's to quantities, e.g.,
$cartSpace->cart[$id] = $qty
Again, behind
the scenes, this corresponds to
$_SESSION['ZendBooks-Cart']['cart']
Thus Zend's $cartSpace object provides a useful way of
distinguishing portions of the session space.
Model
One of the key points to illustrate is the ease in which
the "Books" class is constructed and used.
The Zend_Db_Table
class does all the work of constructing the ORM.
We are implicitly using the Php PDO API as specified
in the configuration file.
The controller generates behaviors for the following action
sequences:
index: list available books
|
|==> show: show a certain book
| |
| |==> addtocart, redirect to showcart
|
|==> modify: show a selected book for modification
| |
| |==> update: update its values, redirect to index
|
|==> del: delete a book, redirect to indexcreate: generate a form for entering new book information
|
| => add: add it, redirect to indexshowcart: show current cart contents
|
| => clearcart: clear it, redirect to showcart
IndexController code
application/controllers/IndexController.php
<?php
class IndexController extends Zend_Controller_Action {
private $listing =
array('label'=>'Listing', 'url' => array('action'=>'index'));
private $create =
array('label'=>'Create', 'url' => array('action'=>'create'));
private $showcart =
array('label'=>"Cart", 'url'=>array('action'=>'showcart'));
public function init() {
$this->baseUrl = $this->getRequest()->getBaseUrl();
$this->view->navMenu =
array($this->listing, $this->create, $this->showcart);
$this->view->headLink()->appendStylesheet($this->baseUrl . "/css/main.css");
include_once "Books.php";
$this->cartSpace = new Zend_Session_Namespace('ZendBooks-Cart');
}
public function indexAction() {
$this->view->pageTitle = "Listing";
$books = new Books();
$allbooks = $books->fetchAll( null, array("id asc") );
$this->view->allbooks = $allbooks;
$this->view->headScript()->appendFile($this->baseUrl . "/js/del.js");
}
public function showAction() {
$this->view->pageTitle = "Select Book";
$id = (int) $this->getRequest()->getParam('id');
$books = new Books();
$this->view->book = $books->fetchRow("id=$id");
}
public function showcartAction() {
$this->view->pageTitle = "Current Cart";
$this->view->cart = $this->cartSpace->cart;
}
public function addcartAction() {
$id = (int) $this->getRequest()->getParam('id');
++$this->cartSpace->cart[$id];
$this->_helper->redirector('showcart');
}
public function clearcartAction() {
unset($this->cartSpace->cart);
$this->_helper->redirector('showcart');
}
public function modifyAction() {
$this->view->pageTitle = "Modify";
$id = (int) $this->getRequest()->getParam('id');
$books = new Books();
$this->view->book = $books->fetchRow("id=$id");
}
public function updateAction() {
try {
$req = $this->getRequest();
$books = new Books();
$id = (int) $req->getParam('id');
$book = $books->fetchRow("id=$id");
$book->title = trim($req->getParam('title'));
$book->type = $req->getParam('type');
$book->qty = trim($req->getParam('qty'));
$this->check($book);
$book->save();
$this->_helper->redirector('index');
}
catch(Exception $x) {
$this->view->info = $x->getMessage();
$this->view->book = $book;
$this->view->pageTitle = "Display/Update: try again";
$this->render("show");
}
}
private function check($book) {
if ($book->title == "") {
throw new Exception("empty title");
}
if (!preg_match("/^\d+$/", $book->qty) ) {
throw new Exception("incorrect quantity format");
}
}
public function createAction() {
$this->view->pageTitle = "Create";
}
public function addAction() {
try {
$req = $this->getRequest();
$books = new Books();
$book = $books->createRow();
$book->title = trim($req->getParam('title'));
$book->type = $req->getParam('type');
$book->qty = trim($req->getParam('qty'));
$this->check($book);
$book->save();
$this->_helper->redirector('index');
}
catch(Exception $x) {
$this->view->book = $book;
$this->view->info = $x->getMessage();
$this->_forward("create");
}
}
public function delAction() {
$id = (int) $this->getRequest()->getParam('id');
$books = new Books();
$books->delete("id=$id");
$this->_helper->redirector('index');
}
}
We could just as well use the standard $_GET and $_POST
arrays, however this has the advantages of being neutral to the query
method and also of better matching the Zend programming style
Forward, render, redirect
Forwarding means to invoke another script transparently to
the browser request. We do this in one situation, in the
add action in case of error:
$this->_forward("create");
What takes place is that create calls add,
but add simply
re-calls create while "add" remains as the visible URL.
Closely related to forwarding is rendering. We do this in
the update action in case of failure:
$this->render("show");
In this case it means to go right to the show.phtml script
without going through the entire show action. This is what
we want because we want the changes the user is trying
to make (which are in error) to be in effect.
Redirection means that the browser calls another action
by sending a new request. This is done in add, update and
delete on success:
$this->_helper->redirector('index');
sending the browser back to the book display list with the
changes in effect.
Forwarding and rendering are meant for "internal" use only
within the application, whereas redirection can redirect
the browser to a whole new application on a different site.
Zend has a simpler "_redirect" function
used like the "_forward" function above, but it is intended
for a more general usage
Style and JavaScript file inclusion
A new feature of this application is that we bring in
a single JavaScript file by this call in the indexAction: