Advanced Paginator with Zend_Db_Table

Make your pagination EASY! Whether using Zend Framework’s MVC, or just the library classes!

By now you might have read by now that half the folk that were using Zend_Paginator have been using it wrong.  They would get their rowset from the table, and then create a paginator and pass in the rowset. Sure, it works, but you have just fetched ALL the rows first, and Paginator displays only the records in question. That is a huge waste of resources!

What SHOULD be happening is that you pass a select() object to the paginator, to which it will figure out the offsets and limits.

Anyway, pretty much all my db classes were returning either rowsets or rows depending on the method. What i needed was for it to return the select. So I had an idea. Auto pagination!

Rather than your db classes extending Zend_Db_Table, we will extend our own abstract class which will  itself extend Zend_Db_Table.

Our class will have a couple of properties;  a flag for pagination on/off, the page number, and the num per page. It will also have a few  methods, as you can see below.

<?php

namespace AA\Repository;

use Zend_Db_Table_Abstract as Table;
use Zend_Db_Table_Select as Select;
use Zend_Paginator as Paginator;
use Zend_View_Helper_PaginationControl as PaginationControl;

abstract class AbstractRepository extends Table
{
 /** @var bool */
 private $paginator_flag = false;
 /** @var int */
 private $page = 1;
 /** @var int */
 private $num_per_page = 30;

 /**
 * @param bool $bool
 * @return $this
 */
 public function setPaginatorFlag($bool)
 {
 $this->paginator_flag = $bool;
 return $this;
 }

 /**
 * @param int $no
 * @return $this
 */
 public function setPage($no)
 {
 $this->page = $no;
 return $this;
 }

 /**
 * @param int $no
 * @return $this
 */
 public function setNumPerPage($no)
 {
 $this->num_per_page = $no;
 return $this;
 }

 /**
 * @param Select $select
 * @return Paginator
 */
 private function getPaginator(Select $select)
 {
 PaginationControl::setDefaultViewPartial('NavControls.phtml');
 $paginator = Paginator::factory($select);
 $paginator->setCurrentPageNumber($this->page);
 $paginator->setItemCountPerPage($this->num_per_page);
 return $paginator;
 }

 /**
 * @param Select $select
 * @return \Zend_Db_Table_Rowset_Abstract|Paginator
 */
 protected function getResult(Select $select)
 {
 if($this->paginator_flag == true)
 {
 return $this->getPaginator($select);
 }
 return $this->fetchAll($select);
 }
}

(If you aren’t using namespaces in your code, you can tweak it to the old style.)

The setPaginatorFlag($bool) turns paging on or off. setPage($no) and
setNumPerPage($no) are self explanitory. getPaginator(Select $select) returns a paginator from the select object.  The interesting method is getResult(Select $select). It checks wether we asked for a paginator. If so it calls getPaginator, if not it does a plain fetchAll.

Have a look at a simple DB Class which now uses the auto paginator:

namespace AA\Repository;


class Competitors extends AbstractRepository
{
 protected $_name = 'branch_competitors';
 protected $_rowClass = 'AA\Entity\Branch\Competitors';


 public function getCompetitors($bid)
 {
 return $this->fetchRow('bid = '.$bid);
 }

 public function getAllCompetitors()
 {
 $select = $this->select()
 ->order('bid DESC');
 return $this->getResult($select);
 }
}

The interesting parts here are the extends AbstractRepository, and return $this->getResult($select). We now use getResult so that it can decide whether to give you a rowset or a paginator.

In your views, you can now just echo it out to give you your paginator! (Of course, you will have your pagination view partial set! see manual for details)

However if you are using Zend Framework libraries without using the MVC component, we can still use Zend Paginator without concerning ourselves with Zend_View etc. I created a view helper class with a static render method, accepting the paginator and the current url:

namespace AA\View\Helper;

use Zend_Paginator;


class Paginator 
{
 public static function render(Zend_Paginator $paginator,$url)
 {
 $url = parse_url($url);
 parse_str($url['query'],$result);
 $class = null; //for now
 if ($paginator->getPages()->pageCount)
 {
 $html = '<div class="pagination"><ul>';
 if (isset($paginator->getPages()->previous))
 {
 $result['page'] = $paginator->getPages()->first;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="first" href="'.$url['path'].'?'.$query.'">
 first
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">first</a></li>';
 }
 if (isset($paginator->getPages()->previous))
 {
 $result['page'] = $paginator->getPages()->previous;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="last" href="'.$url['path'].'?'.$query.'">
 previous
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">previous</a></li>';
 }
 if (isset($paginator->getPages()->next))
 {
 $result['page'] = $paginator->getPages()->next;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="next" href="'.$url['path'].'?'.$query.'">
 next
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">next</a></li>';
 }
 if (isset($paginator->getPages()->next))
 {
 $result['page'] = $paginator->getPages()->last;
 $query = http_build_query($result);
 $html .= '<li>
 <a class="'.$class.'" id="last" href="'.$url['path'].'?'.$query.'">
 last
 </a>
 </li>';
 }
 else
 {
 $html .= '<li class="disabled"><a href="#">last</a></li>';
 }
 $html .= '</ul></div>';
 }
 else
 {
 $html = null;
 }
 return $html;
 }
}

So on a plain ol’ PHP page (that’s able to autoload the classes, if not, require them or look into composer), here’s all you would need (my current url in this example is “/?id1=-competitors”):

$page = ($_GET['page']) ? filter_input(INPUT_GET, 'page', FILTER_SANITIZE_NUMBER_INT) : 1;
//set pagination on for this query
$db->setPaginatorFlag(true)
   ->setPage($page)
   ->setNumPerPage(30);
//get paginator/rowset
$competitors = $db->getAllCompetitors();
//show paginator
echo \AA\View\Helper\Paginator::render($competitors,'/?id1=competitors');
foreach($competitors as $competitor)
{
    //row stuff here
}

Awesome! Now you need never again have to muck around with paginators, whether using Zend Frameworks MVC or not! Have fun!


			

Installing Memcached on CentOS with PHP Memcache

So I pushed my changes to the production repository, only to discover that Memcache wasn’t installed on the server at work (a cPanel installation running over CentOS(6 i think)). Time to install it then!

in the terminal, get memcached installed. Nice n easy.

sudo yum install memcached

Then launch it!

memcached -d -u nobody -m 512 -p 11211 127.0.0.1

Then install memcache PHP extension. You do this in cPanels WHM. Search module installers in WHM

In Find a PHP Pecl, type memcache, and get it installed! You may or may not have to restart apache, but go to a phpinfo() and you should now see it running on your system!

See my previous post for how to use memcache in your PHP 🙂

Speed up slow pages with Memcache

Caching can really help speed matters on on a webpage. Especially when you have a big ass property search going on with umpteen joins etc. Sometimes it’s just easier to cache the results (for a suitably reasonable time). So lets do just that. If you haven’t got memcache installed, search my blog for my post manually compiling php modules successfully (justr search ‘successfully’). Once you’ve done that, it’s a piece of cake!

$cache_key = $area;

 $memcache = new Memcache;
 $memcache->connect('localhost', 11211) or die ("Could not connect");

 $cache_value = $memcache->get($cache_key);
 if(!$cache_value)
 {
 $db = new Application_Model_DbTable_Property();
 $props = $db->areaSearch($area);
 $cache_value = $props->count();
 $memcache->set($cache_key, $cache_value, false, (60*60*24)) or die ("Failed to save data at the server");
 }
 return $cache_value;

And thats it! We give each query we need a unique key, in the example it is whatever $area is (Scotland!) and try to retrieve it. If there  is nothing returned, we do our query and store the result. The 60*60*24 (thats in seconds by the way) is the time until the cache expires, upon which it will return nothing again and refresh the query and cache. It really is that easy! Have fun!

Tidy looking var_dump

I only just noticed that my work computer wasn’t outputting var_dumps nicely like my laptop did, and I knew XDebug took care of that, so I looked into it in more detail! Basically there are TWO settings. Make sure both are on:

 xdebug.overload_var_dump
html_errors

And voila! You can now read your var_dumps a lot more easily! However it looks disgusting and orange, so lets fix that too:

.xdebug-error {
 width: 97%;
 margin: 10px auto;
 border-collapse: collapse;
 border-right: 1px #5b5440 solid;
 border-left: 1px #5b5440 solid;
 border-bottom: 1px #5b5440 solid;
 }
.xdebug-error th,
 .xdebug-error td {
 padding: 4px 6px 3px 5px;
 border-left: none;
 border-right: none;
 }
.xdebug-error th:first-child {
 padding-top: 0;
 }
.xdebug-error th {
 background-color: #515151;
 color: #EEEEEE;
 }
.xdebug-error td {
 background-color: #EEEEEE;
 border-top: 1px #5b5440 solid;
 border-bottom: #DFBA69;
 }
.xdebug-error span {
 background-color: inherit !important;
 color: #FFFF00 !important;
 }

debug   Much nicer! 😀

Overriding DB Connected Resources in Apigility

Apigility rocks! It’s still in its early beta days (and hence a little buggy at times), but it’s WELL worth playing with now for building your API’s!

Anyway, you can create code connected and db connected services. Db connected REST services pretty much give you the auto CRUD functionality, but it would be nice to have some where, and order clauses!

This feature should be ready for version 1.2, but in the meantime, lets override it mannually!

You will notice in your Rest/ServiceName folder (in my particular case, Agents), that it has auto generated an AgentsCollection and an AgentsEntity PHP file.

We will create our own Resource Class which will extend the DBConnectedResource. So I created AgentsResource.php :

 

 <?php

namespace Allagents\V1\Rest\Agents;
use ZF\Apigility\DbConnectedResource;
use Zend\Paginator\Adapter\DbTableGateway as TableGatewayPaginator;

class AgentsResource extends DbConnectedResource
{
 public function fetchAll($data = array())
 {
 // WHERE CLAUSE
 $where = '';
 foreach($data->where as $clause)
 {
 $where .= $clause.' AND ';
 }
 //chop off the last AND
 $where = substr($where,0,-4);

 // ORDER CLAUSE
 $order = '';
 foreach($data->order as $clause)
 {
 $order .= $clause.' , ';
 }
 //chop off the last comma
 $order = substr($order,0,-2);

 // GROUP CLAUSE
 $group = '';
 foreach($data->group as $clause)
 {
 $group .= $clause.' , ';
 }
 //chop off the last comma
 $group = substr($group,0,-2);

 // HAVING CLAUSE
 $having = '';
 foreach($data->having as $clause)
 {
 $having .= $clause.' AND ';
 }
 //chop off the last AND
 $having = substr($having,0,-4);
 $adapter = new TableGatewayPaginator($this->table,$where, $order, $group, $having);
 return new $this->collectionClass($adapter);
 }
}

Ok, now we have to tell ZF2 to use our resource class instead of the default. As always, ZF2 modules are mega customisable, and usually involves tweaking your module.config. Apigility is no different, so jump into your module folder and open the module.config.php. We are adding one line to our DBConnectedResource. Find the section ‘zf-apigility’ => ‘db-connected’ and you will see your service’s entry. We add an array key like so; ‘resource_class’ => ‘Allagents\\V1\\Rest\\Agents\\AgentsResource’,

'zf-apigility' => array(
 'db-connected' => array(
 'Allagents\\V1\\Rest\\Agents\\AgentsResource' => array(
 'adapter_name' => 'DBAdapter',
 'table_name' => 'agents',
 'hydrator_name' => 'Zend\\Stdlib\\Hydrator\\ArraySerializable',
 'controller_service_name' => 'Allagents\\V1\\Rest\\Agents\\Controller',
 'entity_identifier_name' => 'aid',
 'resource_class' => 'Allagents\\V1\\Rest\\Agents\\AgentsResource',
 ),
 ),
 ),

Lastly, we need to whitelist the query parameters that we use, otherwise they will not appear in the HAL links!

Again, in the module.config.php, in the section ‘zf-rest’, we add an array defining the whitelisted parameters:

'zf-rest' => array(
 'Allagents\\V1\\Rest\\Agents\\Controller' => array(
 'listener' => 'Allagents\\V1\\Rest\\Agents\\AgentsResource',
 'route_name' => 'allagents.rest.agents',
 'route_identifier_name' => 'agents_id',
 'collection_name' => 'agents',
 'entity_http_methods' => array(
 0 => 'GET',
 1 => 'PATCH',
 2 => 'PUT',
 3 => 'DELETE',
 ),
 'collection_http_methods' => array(
 0 => 'GET',
 1 => 'POST',
 ),
 'collection_query_whitelist' => array(
 0 => 'where',
 1 => 'order',
 2 => 'having',
 3 => 'group',
 ),
),


To test it is working, call your URL (in my case /agents) and you should see the normal expected results. Now test with a query sting. I made a little script to build the query up:

<?php

$query = http_build_query(array(
 'where' => array(
 'name LIKE "A%"',
 ),
 'order' => array(
 'name asc',
 ),
 'group' => array(

 ),
 'having' => array(

 ),
));

echo $query.'<br />';

Which output:
where%5B0%5D=name+LIKE+%22A%25%22&order%5B0%5D=name+asc
So I called: /agents?where%5B0%5D=name+LIKE+%22A%25%22&order%5B0%5D=name+asc
You should have your filtered results, with HAL links in the JSON, like so!

{
“_links”: {
“self”: {
“href”: “http://api.allagents.del/agents?where%5B0%5D=name%20LIKE%20%22A%25%22&order%5B0%5D=name%20asc&page=1&#8221;
},
“first”: {
“href”: “http://api.allagents.del/agents?where%5B0%5D=name%20LIKE%20%22A%25%22&order%5B0%5D=name%20asc&#8221;
},
“last”: {
“href”: “http://api.allagents.del/agents?where%5B0%5D=name%20LIKE%20%22A%25%22&order%5B0%5D=name%20asc&page=44&#8221;
},
“next”: {
“href”: “http://api.allagents.del/agents?where%5B0%5D=name%20LIKE%20%22A%25%22&order%5B0%5D=name%20asc&page=2&#8221;
}
},
“_embedded”: {
“agents”: [
{
“aid”: “2842”,
“etc”:”etc!”

Have fun! Maybe I could even get this into the actual Apigility code by sending them a Pull Request! (Probably not though lol, I’m sure they’ll figure a better way of dealing with the query params than passing RAW SQL portions haha!)