Zend Framework: How to use Zend_Paginator

Having a “search” functionality in your web application is very essential and for every search comes a “search results” page. It is not hard to create your own search results page with a pagination, but it does take a certain amount of time. But by using Zend_Paginator, you will be able to save time and make your code easier to understand and debug.

Here is a short example on how to create your own search results page with Zend_Paginator.

Before anything else, we start with our search form.

<form method="GET" action="<?=$this->baseUrl()?>/search">
    SEARCH:
    <input name="keyword" type="text"/>
    <input type="submit" value="Search" />
</form>

Next, we have our Controller:

<?php
class SearchController extends Zend_Controller_Action
{
    public function indexAction()
    {
        /**
         * Load model and perform search if $keyword is found in the URL
         */
        $content_model = new Default_Model_Content();
        if($this->_request->getParams('keyword')) {
            $results = $content_model->fetchSearchResults($this->_request->getParams('keyword'));
        }
 
        /**
         * Setup the paginator if $results is set.
         */
        if(isset($results)) {
            $paginator = Zend_Paginator::factory($results);
            $paginator->setItemCountPerPage(20);
            $paginator->setCurrentPageNumber($this->_getParam('page'));
            $this->view->paginator = $paginator;
            /**
             * We will be using $this->view->paginator to loop thru in our view ;-)
             */
 
            Zend_Paginator::setDefaultScrollingStyle('Sliding');
            Zend_View_Helper_PaginationControl::setDefaultViewPartial(
                'query/pagination.phtml' //Take note of this, we will be creating this file
            );
        }
    }

Note: In this tutorial we are using LIKE to compare strings in our MySQL database. The search is very basic and there is no ranking in our results.


The Model
Our model will have 2 files. See below:

<?php
/**
 * First model file.
 * application/models/Content.php
 */
class Default_Model_Content extends Zend_Db_Table
{
    protected $_table;
 
    public function getTable()
    {
        if(null === $this->_table) {
            $this->_table = new Default_Model_DbTable_Content();
        }
        return $this->_table;
    }
 
    public function fetchSearchResults($keyword)
    {
        $result = $this->getTable()->fetchSearchResults($keyword);
        return $result;
    }

And our second model file.

<?php
/**
 * Second model file.
 * application/models/DbTable/Content.php
 */
class Default_Model_DbTable_Orders extends Zend_Db_Table_Abstract
{
    /** Table name */
    protected $_name = 'content';
 
    public function init()
    {
        $this->_db->setFetchMode(Zend_Db::FETCH_OBJ);
    }
 
    public function fetchSearchResults($keyword)
    {
        $sql = 'SELECT * FROM'.$this->_name.
                'WHERE '.
                $this->_db->quoteIdentifier('the_content').' '.
                'LIKE'.
                $this->_db->quote($keyword.'%');
        return $this->_db->fetchAll($sql);
    }



The View
Now that we have our controller and model ready, it is time to setup our view files.

First, create a file called index.phtml in application/views/scripts/search/:

<?php if($this->paginator): ?>
    <table>
        <tr>
            <th>Title</th>
            <th>Content</th>
        </tr>
        <?php foreach($this->paginator AS $key => $row): ?>
            <tr>
                <td><?=$row->title?></td>
                <td><?=$row->the_content?></td>
            </tr>
        <?php endforeach; ?>
    </table>
    <?php echo $this->paginator; ?>
<?php else: ?>
No results.
<?php endif; ?>



The Pagination View File
This is the last step. Now all you have to do is to copy-paste this section of code into a file named: pagination.phtml
Create that file in: application/views/scripts/search/pagination.phtml

<!--
See http://developer.yahoo.com/ypatterns/pattern.php?pattern=searchpagination
-->
 
<?php if ($this->pageCount): ?>
<div class="paginationControl">
<!-- Previous page link -->
<?php if (isset($this->previous)): ?>
  <a href="<?php echo $this->url(array('page' => $this->previous)); ?>">
    &lt; Previous
  </a> |
<?php else: ?>
  <span class="disabled">&lt; Previous</span> |
<?php endif; ?>
 
<!-- Numbered page links -->
<?php foreach ($this->pagesInRange as $page): ?>
  <?php if ($page != $this->current): ?>
    <a href="<?php echo $this->url(array('page' => $page)); ?>">
        <?php echo $page; ?>
    </a> |
  <?php else: ?>
    <span id="current_page"><?php echo $page; ?></span> |
  <?php endif; ?>
<?php endforeach; ?>
 
<!-- Next page link -->
<?php if (isset($this->next)): ?>
  <a href="<?php echo $this->url(array('page' => $this->next)); ?>">
    Next &gt;
  </a>
<?php else: ?>
  <span class="disabled">Next &gt;</span>
<?php endif; ?>
</div>
<?php endif; ?>

Test your search form!

More Reading
Zend_Paginator from the Zend.com manual.

On using a CSS Framework - BlueprintCSS

A quick background check on BlueprintCSS:

I have heard of Blueprint CSS a couple of months ago. But I have never used it until a few weeks ago because I kept thinking:

“Who needs a CSS Framework? We already have frameworks for Javascript and PHP. And now we have one for CSS? Oh nos.”

The only thing I regret is not using it using it earlier. Read that line again: The only thing I regret is not using it earlier. I cannot stress enough how much time and effort I have saved when I used BlueprintCSS. It just works and it is so damn easy to understand. BlueprintCSS uses a grid-style layout, just like old HTML tables do.

A Quick Overview After a Small Project Using Blueprint

With BlueprintCSS, everything started with a div that is 950px wide. The 950px div can be made up of up to 24 columns. I forgot the width of each column and you can probably find that here but the logic is that you can specify up to 24 columns in your layout. For example:

<div class="container">
    <div class="span-24">
        Header - 24 columns and the width is 950px
    </div>
    <div class="span-4">
        Left sidebar - 4 columns
    </div>
    <div class="span-20">
        Content pane - 20 columns
    </div>
</div>

Note that the Left Sidebar (span-4) and the Content Pane (span-20) adds up to 24? That is the basic concept of Blueprint. You can have as many columns as you want as long as it totals to 24. You can also do other useful stuff:

Grid.css can do a lot more than this, however. You can prepend and append empty columns, pull or push images across columns, add borders between columns, and use multiple containers to create almost any layout.

Using this simple concept, you can basically get any design/layout you have made in Photoshop and quickly make a cross-browser compatible CSS in less than 30 minutes.

All that by simply including a bunch of CSS files.

<link rel="stylesheet" href="css/blueprint/screen.css" type="text/css" media="screen, projection">
<link rel="stylesheet" href="css/blueprint/print.css" type="text/css" media="print">    
<!--[if lt IE 8]><link rel="stylesheet" href="css/blueprint/ie.css" type="text/css" media="screen, projection"><![endif]-->

A quick and comprehensive tutorial on BlueprintCSS can be found here.

Zend Framework: SQL Injection Prevention from DPC Slides

I would never make it to any PHP Conference, so I would have to be satisfied by the replays and slides. I got these from the DPC slides found here (pdf).

function query($sql, $bind = array())

- uses prepared statement internally
- SQL Injection still possible if $sql is dynamically created

function fetchAll($sql, $bind = array(), $fetchMode = null)

- all “fetch” methods use prepared statements internally
- SQL Injection still possible if $sql is dynamically created

<?php
$sql = "SELECT id FROM users WHERE lastname=? AND age=?";
$parans = array('Smith','18');
$result = $db->fetchAll($sql, $params);
?>

More stufff…

function insert($table, array $bind)

- internally uses prepared statements
- SQL-Injection not possible

function update($table, array $bind, $where = '')

- uses partially prepared statements
- SQL-Injection still possible if $where is dynamically created

function delete($table, $where = '')

- SQL-Injection still possible if $where is dynamically created

Zend_Db - Escaping

function quote($value, $type = null)

- applies the correct escaping - one function not many
- ATTENTION: also puts strings in quotes
Note: If the type of your field in your database is an Integer, then I would suggest that you use a second parameter — see below.

$value = '1234';
$sql = 'SELECT * FROM atable WHERE intColumn = '. $this->_db->quote($value, 'INTEGER');


function quoteIdentifier($ident, $auto=false)

- applies escaping for identifiers
- a function not available to traditional PHP applications
- ATTENTION: also puts strings in quotes
- Used for table names, columns, and other identifiers in SQL statements

If you use PHP variables to name tables, columns, or other identifiers in your SQL statements, you might need to quote these strings too.

Example:

class Default_Model_DbTable_Ordertype extends Zend_Db_Table_Abstract
{
    /** Table name */
    protected $_name = 'order_type';
 
    public function init()
    {
        $this->_db->setFetchMode(Zend_Db::FETCH_OBJ);
    }
 
    public function fetchAll($country_id)
    {
        $sql = 'SELECT *
                FROM
                '.$this->_name.'
                WHERE order_type_status = "on"
                AND country_id = ?';
 
        return $this->_db->fetchAll($sql,array($country_id));
    }
 
    public function fetchAllQuoted($country_id)
    {
        /**
         * This would also work.
         * But I prefer the one above.
         * It is shorter and easier to read.
         * Both will have the same results.
         */
        $country_id = $this->_db->quote($country_id);
        $sql = 'SELECT *
                FROM
                '.$this->_name.'
                WHERE order_type_status = "on"
                AND country_id = '.$country_id;
 
        /**
         * No second parameter.
         */
        return $this->_db->fetchAll($sql);
    }
}

Arrogance is Limiting Framework Adoption by Cal Evans

I rarely post articles like these. But this one will hit a spot on some developers. I personally think that arrogance is a dangerous trait for a programmer/developer. I would not want to work with someone who thinks so highly of themselves. If I was to hire a guy for a team; between an inexperienced developer and a guy who claims he has 999 years of experience and thinks that he is Galactus of PHP, then I would pick the first one.

Here is the article from Cal Evans.

Developers are notoriously self-confident in their ability to write code that is better, faster, cleaner and better-smelling than everybody else’s. In today’s environment, however, the focus is on producing immediately useful code—and, given the richness of today’s frameworks, those who eschew them in favour of home-grown solutions are forever running the risk of reinventing the wheel for no good reason. We have enough wheels—start building some cars.

“…Frameworks! I don’t need no stickin’ PHP Frameworks!!!” – anonymous twitterer

It is a good read.

Zend Framework: Navigation and Breadcrumbs with an XML File in ZF 1.8

This is related to the Making the Built-in Breadcrumb Helper Work I posted earlier. Thanks to Jonathan Lebensold’s screencast, I am able to create my navigation and breadcrumbs in a better way. Using an XML file makes more sense than using an array. It is easier to maintain and edit.

What the Navigation will look like:

//layout.phtml
echo $this->navigation()->menu();

And what the breadcrumbs will look like:

//layout.phtml
echo $this->navigation()->breadcrumbs()->setLinkLast(false)->setMinDepth(0)->render();

To do this, you first create an XML file. It makes more sense to create your XML file inside the configs directory. So create an XML file in:

application/configs/navigation.xml

The data in the XML file should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<configdata>
    <nav>
        <label>Home</label>
        <controller>dashboard</controller>
        <action>index</action>
        <pages>
            <query>
                <label>Query</label>
                <controller>query</controller>
                <action>index</action>
                <pages>
                    <mydrafts>
                        <label>My Drafts</label>
                        <controller>query</controller>
                        <action>index</action>
                        <params>
                            <queryname>mydrafts</queryname>
                        </params>
                    </mydrafts>
                    <myorders>
                        <label>My Orders</label>
                        <controller>query</controller>
                        <action>index</action>
                        <params>
                            <queryname>myorders</queryname>
                        </params>
                    </myorders>
                    <allopenorders>
                        <label>All Open Orders</label>
                        <controller>query</controller>
                        <action>index</action>
                        <params>
                            <queryname>allopenorders</queryname>
                        </params>
                    </allopenorders>
                </pages>
            </query>
            <reports>
                <label>Reports</label>
                <controller>report</controller>
                <action>index</action>
                <pages>
                    <country>
                        <label>Country</label>
                        <controller>report</controller>
                        <action>country</action>
                    </country>
                    <countryrevenue>
                        <label>Country Revenue</label>
                        <controller>report</controller>
                        <action>countryrevenue</action>
                    </countryrevenue>
                </pages>
            </reports>
        </pages>
    </nav>
</configdata>

The next part involves editing the Bootstrap.php file.

//add this in your bootstrap file
//application/Bootstrap.php
protected function _initNavigation()
{
    $this->bootstrap('layout');
    $layout = $this->getResource('layout');
    $view = $layout->getView();
    $config = new Zend_Config_Xml(APPLICATION_PATH.'/configs/navigation.xml');
 
    $navigation = new Zend_Navigation($config);
    $view->navigation($navigation);
}

What we are doing here is that we are simply loading the XML file (as an array) into the $view object. We are doing this so that we can access the Zend_Navigation object in our layout/views.

Final Steps
Lastly, you will need to open up your layout file and add these lines:

<html>
    <head>
    </head>
    <body>
        <div id="menu">
            <?php echo $this->navigation()->menu(); ?>
        </div>
        <div id="breadcrumbs">
            You are in: <?php echo $this->navigation()->breadcrumbs()->setLinkLast(false)->setMinDepth(0)->render(); ?>
        </div>
        <div id="content">
            Content of your page here.
        </div>
    </body>
<html>

More reading:
If you have time, check out Jonathan Lebensold’s screencast found in ZendCasts. It is an excellent tutorial and it goes all the way to beautifying your navigation using tabs and your breadcrumbs.

And lastly, the Zend_Navigation documentation can be found here.


© Copyright 2007 eKini: Web Developer Blog . Thanks for visiting!