Zend Framework Quick Start
Create a Model and Database Table
First we will put our db connection information inside the application's ini file.
;; application/config/app.ini
;;
;; This file replaces the one presented originally in the section entitled
;; "Create a Configuration and Registry".
;;
;; This is a sample app.ini file. Your application will dictate the format and the
;; type of sections and data that can be found in this ini file. It will also dictate
;; how many ini files will be contained in your config/ directory. For the puropose
;; of our application, this one file makes the most sense.
;; we always have our "production" section first, because it will define ALL of the
;; keys that our application is expecting to see.
[production]
database.adapter = "PDO_SQLITE"
database.params.dbname = APPLICATION_PATH "/../data/db/guestbook.db"
[development : production]
database.params.dbname = APPLICATION_PATH "/../data/db/guestbook-dev.db"
[testing : production]
database.params.dbname = APPLICATION_PATH "/../data/db/guestbook-testing.db"
;; note: as you can see, we are able to use our APPLICATION_PATH constant in this ini file.
;; this is important so that we can make the most self-contained, and modular application we
;; can make.
Next, we will add a section to the application's bootstrap file that will take the configuration for the database, and generate an adapter from Zend_Db::factory(). This adapter will then be put inside the application registry. Also note that after the adapter is in the registry, we can then add $dbAdapter to the unset() at the bottom of the bootstrap file.
// application/bootstrap.php
//
// Add the following code prior to the comment marked "Step 5" in
// application/bootstrap.php.
//
// DATABASE ADAPTER - Setup the database adapter
// Zend_Db implements a factory interface that allows developers to pass in an
// adapter name and some parameters that will create an appropriate database
// adapter object. In this instance, we will be using the values found in the
// "database" section of the configuration obj.
$dbAdapter = Zend_Db::factory($configuration->database);
// DATABASE TABLE SETUP - Setup the Database Table Adapter
// Since our application will be utilizing the Zend_Db_Table component, we need
// to give it a default adapter that all table objects will be able to utilize
// when sending queries to the db.
Zend_Db_Table_Abstract::setDefaultAdapter($dbAdapter);
// REGISTRY - setup the application registry
// An application registry allows the application to store application
// necessary objects into a safe and consistent (non global) place for future
// retrieval. This allows the application to ensure that regardless of what
// happends in the global scope, the registry will contain the objects it
// needs.
$registry = Zend_Registry::getInstance();
$registry->configuration = $configuration;
$registry->dbAdapter = $dbAdapter;
// CLEANUP - remove items from global scope
// This will clear all our local boostrap variables from the global scope of
// this script (and any scripts that called bootstrap). This will enforce
// object retrieval through the Applications's Registry
unset($dbAdapter, $registry);
At this point we have a connection to a database; in our case, its a connection to a Sqlite database located inside our application/data/ directory. So, let's design a simple table that will hold our guestbook entries.
-- scripts/schema.sqlite.sql
--
-- You will need load your database schema with this SQL.
CREATE TABLE guestbook (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
email VARCHAR(32) NOT NULL DEFAULT 'noemail@test.com',
comment TEXT NULL,
created DATETIME NOT NULL
);
CREATE INDEX "id" ON "guestbook" ("id");
And, so that we can have some working data out of the box, lets create a few rows of information to make our application interesting.
-- scripts/data.sqlite.sql
--
-- You can begin populating the database with the following SQL statements.
INSERT INTO guestbook (email, comment, created) VALUES
('ralph.schindler@zend.com',
'Hello! Hope you enjoy this sample zf application!',
DATETIME('NOW'));
INSERT INTO guestbook (email, comment, created) VALUES
('foo@bar.com',
'Baz baz baz, baz baz Baz baz baz - baz baz baz.',
DATETIME('NOW'));
Now that we have both the schema and some data defined. Lets get a script together that we can now execute to build this database. Naturally, this is not needed in production, but this script will help developers build out the database requirements locally so they can have the fully working application.
<?php
/**
* scripts/load.sqlite.php
*
* Script for creating and loading database; execute this script with the PHP
* interpreter to prepare your SQLite database.
*/
// use bootstrap (contains prepared db adapter and prepared table
// component)
set_include_path(dirname(__FILE__) . '/../library' . PATH_SEPARATOR . get_include_path());
require_once 'Zend/Loader.php';
Zend_Loader::registerAutoload();
include_once dirname(__FILE__) . '/../application/bootstrap.php';
// if any parameter is passed after the script name (like 1 or --withdata)
// load the data file after the schema has loaded.
$withData = isset($_SERVER['argv'][1]);
// pull the adapter out of the application registry
$dbAdapter = Zend_Registry::getInstance()->dbAdapter;
// let the user know whats going on (we are actually creating a
// database here)
echo 'Writing Database Guestbook in (control-c to cancel): ' . PHP_EOL;
for ($x = 5; $x > 0; $x--) {
echo $x . "\r"; sleep(1);
}
// this block executes the actual statements that were loaded from
// the schema file.
try {
$schemaSql = file_get_contents('./schema.sqlite.sql');
// use the connection directly to load sql in batches
$dbAdapter->getConnection()->exec($schemaSql);
echo PHP_EOL;
echo 'Database Created';
echo PHP_EOL;
if ($withData) {
$dataSql = file_get_contents('./data.sqlite.sql');
// use the connection directly to load sql in batches
$dbAdapter->getConnection()->exec($dataSql);
echo 'Data Loaded.';
echo PHP_EOL;
}
} catch (Exception $e) {
echo 'AN ERROR HAS OCCURED:' . PHP_EOL;
echo $e->getMessage() . PHP_EOL;
return false;
}
// generally speaking, this script will be run from the command line
return true;
path/to/ZendFrameworkQuickstart/scripts$ php load.sqlite.php
Writing Database Guestbook in (control-c to cancel):
1
Database Created
Now, we have a fully working database and table for our guestbook application. Our next few steps are to build out our application code. This includes building a data source connector (in our case, we will use Zend_Db_Table), a model file, and the controller that will interact with this model to both display existing entries and process new entries.
So, to get started, lets create a Zend_Db_Table-based table class.
<?php
// application/models/DbTable/GuestBook.php
/**
* This is the DbTable class for the guestbook table.
*/
class Model_DbTable_GuestBook extends Zend_Db_Table_Abstract
{
/** Table name */
protected $_name = 'guestbook';
/**
* Insert new row
*
* Ensure that a timestamp is set for the created field.
*
* @param array $data
* @return int
*/
public function insert(array $data)
{
$data['created'] = date('Y-m-d H:i:s');
return parent::insert($data);
}
/**
* Override updating
*
* Do not allow updating of entries
*
* @param array $data
* @param mixed $where
* @return void
* @throws Exception
*/
public function update(array $data, $where)
{
throw new Exception('Cannot update guestbook entries');
}
}
All that is truly necessary when extending Zend_Db_Table is to provide a table name and optionally the primary key (if it is not "id"). Here, though, we add a little logic to enforce data integrity: we ensure that every entry receives a timestamp for the "created" field, and we disallow updates to existing entries.
Now that we have our application's connection to the database table, we need to create a model file. A model provides business logic surrounding the domain, which usually involves a data source. As such, it also abstracts and restricts access to the data source.
In our case, we want to be able to do the following:
- Fetch a list of all entries
- Fetch individual entries by identifier
- Save new entries
<?php
// application/model/GuestBook.php
/**
* This model class represents the business logic associated with a "guestbook"
* model. While its easy to say that models are generally derived from
* database tables, this is not always the case. Data sources for models are
* commonly web services, the filesystem, caching systems, and more. That
* said, for the purposes of this guestbook applicaiton, we have split the
* buisness logic from its datasource (the dbTable).
*
* This particular class follows the Table Module pattern. There are other
* patterns you might want to employ when modeling for your application, but
* for the purposes of this example application, this is the best choice.
* To understand different Modeling Paradigms:
*
* @see http://martinfowler.com/eaaCatalog/tableModule.html [Table Module]
* @see http://martinfowler.com/eaaCatalog/ [See Domain Logic Patterns and Data Source Arch. Patterns]
*/
class Model_GuestBook
{
/** Model_Table_Guestbook */
protected $_table;
/**
* Retrieve table object
*
* @return Model_Guestbook_Table
*/
public function getTable()
{
if (null === $this->_table) {
// since the dbTable is not a library item but an application item,
// we must require it to use it
require_once APPLICATION_PATH . '/models/DbTable/GuestBook.php';
$this->_table = new Model_DbTable_Guestbook;
}
return $this->_table;
}
/**
* Save a new entry
*
* @param array $data
* @return int|string
*/
public function save(array $data)
{
$table = $this->getTable();
$fields = $table->info(Zend_Db_Table_Abstract::COLS);
foreach ($data as $field => $value) {
if (!in_array($field, $fields)) {
unset($data[$field]);
}
}
return $table->insert($data);
}
/**
* Fetch all entries
*
* @return Zend_Db_Table_Rowset_Abstract
*/
public function fetchEntries()
{
// we are gonna return just an array of the data since
// we are abstracting the datasource from the application,
// at current, only our model will be aware of how to manipulate
// the data source (dbTable).
// This ALSO means that if you pass this model
return $this->getTable()->fetchAll('1')->toArray();
}
/**
* Fetch an individual entry
*
* @param int|string $id
* @return null|Zend_Db_Table_Row_Abstract
*/
public function fetchEntry($id)
{
$table = $this->getTable();
$select = $table->select()->where('id = ?', $id);
// see reasoning in fetchEntries() as to why we return only an array
return $table->fetchRow($select)->toArray();
}
}
Lastly, to connect these elements all together, lets create a guestbook controller that will both list the entries that are currently inside the database.
<?php
// application/controllers/GuestbookController.php
/**
* GuestbookController - in this example, we will build a simple
* guestbook style application. It is capable only of being "signed" and
* listing the previous entries.
*/
class GuestbookController extends Zend_Controller_Action
{
/**
* While overly simplistic, we will create a property for this controller
* to contain a reference to the model associated with this controller. In
* larger system, or in systems that might have referential models, we
* would need additional properties for each.
*/
protected $_model;
/**
* The index, or landing, action will be concerned with listing the entries
* that already exist.
*
* Assuming the default route and default router, this action is dispatched
* via the following urls:
* /guestbook/
* /guestbook/index
*
* @return void
*/
public function indexAction()
{
$model = $this->_getModel();
$this->view->entries = $model->fetchEntries();
}
/**
* The sign action is responsible for handling the "signing" of the
* guestbook.
*
* Assuming the default route and default router, this action is dispatched
* via the following url:
* /guestbook/sign
*
* @return void
*/
public function signAction()
{
$request = $this->getRequest();
$form = $this->_getGuestbookForm();
// check to see if this action has been POST'ed to
if ($this->getRequest()->isPost()) {
// now check to see if the form submitted exists, and
// if the values passed in are valid for this form
if ($form->isValid($request->getPost())) {
// since we now know the form validated, we can now
// start integrating that data sumitted via the form
// into our model
$model = $this->_getModel();
$model->save($form->getValues());
// now that we have saved our model, lets url redirect
// to a new location
// this is also considered a "redirect after post"
// @see http://en.wikipedia.org/wiki/Post/Redirect/Get
return $this->_helper->redirector('index');
}
}
// assign the form to the view
$this->view->form = $form;
}
/**
* _getModel() is a protected utility method for this controller. It is
* responsible for creating the model object and returning it to the
* calling action when needed. Depending on the depth and breadth of the
* application, this may or may not be the best way of handling the loading
* of models. This concept will be visited in later tutorials, but for now
* - in this application - this is the best technique.
*
* Also note that since this is a protected method without the word 'Action',
* it is impossible that the application can actually route a url to this
* method.
*
* @return Model_GuestBook
*/
protected function _getModel()
{
if (null === $this->_model) {
// autoload only handles "library" compoennts. Since this is an
// application model, we need to require it from its application
// path location.
require_once APPLICATION_PATH . '/models/GuestBook.php';
$this->_model = new Model_GuestBook();
}
return $this->_model;
}
/**
* This method is essentially doing the same thing for the Form that we did
* above in the protected model accessor. Same logic applies here.
*
* @return Form_GuestBook
*/
protected function _getGuestbookForm()
{
require_once APPLICATION_PATH . '/forms/GuestBook.php';
$form = new Form_GuestBook();
$form->setAction($this->_helper->url('sign'));
return $form;
}
}
And, of course, we need a view script to go along with that.
<? // application/views/scripts/guestbook/index.phtml ?>
<p><a href="<?= $this->url(
array(
'controller' => 'guestbook',
'action' => 'sign'
),
'default',
true) ?>">Sign Our Guestbook</a></p>
Guestbook Entries: <br />
<dl>
<!-- Loop through the entries that were provided to us by the controller -->
<? foreach ($this->entries as $entry): ?>
<dt><?= $this->escape($entry['email']) ?></dt>
<dd><?= $this->escape($entry['comment']) ?></dd>
<? endforeach ?>
</dl>
Quickstart Navigation
- ZF & MVC Introduction
- Set Up the Project Structure
- Download & Install ZF
- Create a Rewrite Rule
- Create a Bootstrap File
- Create an Action Controller & View
- Create an Error Controller & View
- Create a Layout
- Create a Configuration and Registry
- Create a Model and Database Table
- Create a Form
- Congratulations
ZF Reference Guide - now in PDF!
The Zend Framework Reference Guide is now available in PDF format from Zend's
high-speed content distribution network. Registration is required.
Documentation Archives
If you're looking for an older version of our reference guide, you'll find it in our download archives.
