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!


		
Advertisements

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!)

Upgrade your ZF1 classes to PSR-0 Autoload

As a Zend Framework 1 user I loved the simplicity of the class naming convention, being Folder_Subfolder_ClassName. However as you probably know, these class names get really quite long! The latest PHP as you already know uses namespaces and allows for shorter classnames that wont clash with each other. Now I have added an API to my website using the incredible Apigility (http://apigility.org) which was built in Zend Framework 2, I thought it would be nice to upgrade my existing classes to autoload PSR-0 style, so I can eventually migrate easily across.

First thing then, you need composer installed. If you’ve been following my blog, or using any other vendors packages, you’ll already have it in your project.

In ZF1, the library folder was where you would keep your different modules/packages/classes. I have a library called TTB. So in the TTB folder, create an src folder, and another TTB folder in there (this is a quirk of PSR-0, but trust me). In that folder, recreate your classes. Changes aren’t very difficult:

<?php
namespace TTB\Form;
use TTB\Form\Element;

class Contact extends \Zend_Form
{
    //etc
}

The line TTB_Form_Contact extends Zend_Form is shortened by way of the namespace line at the top to just become Contact, and the Zend_Form gets a backslash in front of it as it is in the global namespace. You also specify use  to import any other classes into the namespace. Now we can call Textbox instead of Element\Textbox or TTB\Form\Element\Textbox.

You probably know all this stuff anyway! The point is, to get it autoloading in your project!

So in your index file of your ZF1 project, require once vendor/autoload.php. And in your composer.json, add the following:

"autoload": {
     "psr-0": {
         "TTB" : "application/library/TTB/src/"
     }
 }

Finally, run composer dump-autoload in the terminal from your site root, and this will generate the classmap. You are now ready for PSR-0 compliance! Now you just need to spend all day refactoring! It’ll be worth it when you take your old project to a new framework!  😉

Setup a ZF1 DB Adapter in Zend Framework 2

If you checked my last post, you will have read about how to get all your old ZF1 classes autoloading in your new shiny ZF2 project. However, the DB settings from your application.ini won’t have been set, and so you’ll get a 500 response with  No Db Adapter. Not a problem. We just create the db adapter ourselves and tell Zend_Db that it is the default.

I’ve put this in the ZF2 index.php just before the last line, Zend\Mvc\Application::init($appConfig)->run();

//Set up ZF1 DB
$params = array(
 'host' => '127.0.0.1',
 'username' => 'xxx',
 'password' => 'xxx',
 'dbname' => 'xxx'
);
$adapter = Zend_Db::factory('Pdo_Mysql',$params);
Zend_Db_Table_Abstract::setDefaultAdapter($adapter);

Now your autoloading ZF1 DB classes wont freak out, and should connect no problem!

load ZF1 classes in Zend Framework 2 using composer

I’ve started using Apigility for building my API for my mobile app of my site! It’s incredible, you have to try it!

Anyway, I wanted to be able to load my existing code into ZF2 so I didn’t have to reinvent the wheel.

My API is running on a subdomain in an api sub folder within the main project. In the api folder, I made a folder called library. Then I symlinked my Zend and other folders from my ZF1 project into it (this may not have been necessary, but I didn’t want ../.. relative link type stuff in my composer.json)

:~/www/site/api/library$ ln -s ../../application/library/Zend Zend
:~/www/site/api/library$ ln -s ../../application/library/ZendX ZendX
:~/www/site/api/library$ ln -s ../../application/library/TTB TTB
:~/www/site/api/library$ ln -s ../../application/library/AA AA

Next you need to do is tell composer.json about your libraries.

 "autoload": {
 "psr-0": {
 "AA_": "library/",
 "Zend_": "library/",
 "ZendX_": "library/"
 }
 },
 "include-path": [
 "library"
 ]

Finally get composer generating autoload files. Type in:

~/www/site/api$ composer dump-autoload
Generating autoload files

And thats it! You should now be able to call things like:

$awesome = new Zend_Pdf();
$old_skool = new AA_Old_Skool_Class();

Yay!

Set Character Encoding as UTF-8 throughout your Zend Framework App

AKA more utf-8 vs latin pain and woe.

Ok. Some of you may have read my earlier post where I thought zend form was screwing up, when in actuality is the IDE. Well the fun didn’t stop there. So to spare you the pain and anguish etc, here’s what you do.

Make sure your database is UTF-8, and any fields have utf8_general_ci
In your application.ini - resources.db.params.charset = utf8
In your application.ini - resources.view.encoding = "UTF-8"
In your layout phtml head - <meta charset='utf-8'>

We should all be smiling happy now!

Consuming Services with Zend Framework

Just when Zend couldn’t get any better, it already is!!

Today we’re farting about with RSS feeds and Twitter feeds! It’s remarkably simple! Lets get cracking:

In your controller:

    //look for Ron Paul news and twitter (Americans please vote this guy!!!)
    $q = 'Ron%20Paul';
    $this->view->q = $q;

    //get twitter feeds
    $twitter = new Zend_Service_Twitter_Search();
    $this->view->tweets = $twitter->search($q,array('lang' => 'en', 'rpp' => 8, 'show_user' => true));

    // get Google News Atom feed
    $this->view->feeds = array();
    $gnewsFeed = "http://news.google.com/news?hl=en&q=$q&output=atom";
    $this->view->feeds = Zend_Feed_Reader::import($gnewsFeed);

Thats it!!! In your view:

<?php 
  $count = 0;  
  foreach ($this->feeds as $entry)
  { 
?> 
  <p class="post">
    <span class="text"><a href="<?php echo $entry->getLink(); ?>"> <?php echo $entry->getTitle(); ?></a></span>
    <span class="time"><?php echo $entry->getDateModified(); ?></span>
  </p>
<?php 
    $count++; 
  }
?>

And for the tweets:

<?php foreach ($this->tweets['results'] as $tweet) {?>
<p class="tweet"> <span class="image">
<img src="<?php echo $tweet['profile_image_url']; ?>" /> </span>
<span class="user">
<?php echo $tweet['from_user'] . ': '; ?>
</span>
<span class="text">
<?php echo $tweet['text']; ?> </span>
<span class="time">
<?php echo $tweet['created_at']; ?>
</span>
</p>
<?php } ?>

Ok, I mean come on, how much easier does this stuff get?!?!?!? Rock on, amigos!