ZF-7366: Zend_Paginator does not use cached Zend_Db_Select correctly

Description

Caching is not working correctly when using Zend_Paginator to paginate Zend_Db_Select results. The values are being cached (I can see the files in my cache location), but they are not being used.

The problem is that the cache IDs are being generated incorrectly.


class Dashboard_Feed extends Zend_Db_Table_Abstract
{
    protected $_name = 'FEED';
    protected $_primary = 'FEED_ID';
}

$feed = new Dashboard_Feed( Zend_Registry::get('db') );
$select = $feed->select()->order('FEED_TITLE' );

$paginator = Zend_Paginator::factory( $select );
$paginator->setCache( Zend_Registry::get('cache') );
$paginator->setCurrentPageNumber( $this->_getParam('page', 1) );
$paginator->setItemCountPerPage(10);

cache.frontend.type = "Core"
cache.frontend.options.automatic_serialization = true
cache.frontend.options.cache_id_prefix = "dashboard_"

cache.backend.type = "File"
cache.backend.options.cache_dir = APPLICATION_PATH "/../data/cache/"

Db adapter is Oracle

Upon first execution, zend_cache---dashboard_Zend_Paginator_1_81a9b1f25e24763494338baa43444b99 is creating in my cache directory - so far so good.

I make a change to one of the items in the DB and refresh. I should not see the change since the results are cached; however, I do see it. Zend_Paginator went back to the database to get the info.

I looked into the Zend_Paginator.php to attempt to get to the bottom of the issue. The problem can be observed in getItemsByPage(...). See the comments I inserted.


public function getItemsByPage($pageNumber)
{
    $pageNumber = $this->normalizePageNumber($pageNumber);

    if ($this->_cacheEnabled()) {
        // here, _getCacheId(...)  returns "Zend_Paginator_1_a479583744ccc3a9c4d653c390e7ff7f"
        $data = self::$_cache->load($this->_getCacheId($pageNumber));
        if ($data !== false) {
            return $data;
        }
    }

    $offset = ($pageNumber - 1) * $this->_itemCountPerPage;

    $items = $this->_adapter->getItems($offset, $this->_itemCountPerPage);

    $filter = $this->getFilter();

    if ($filter !== null) {
        $items = $filter->filter($items);
    }

    if (!$items instanceof Traversable) {
        $items = new ArrayIterator($items);
    }

    if ($this->_cacheEnabled()) {
        // here, _getCacheId(...)  returns "Zend_Paginator_1_81a9b1f25e24763494338baa43444b99"
        self::$_cache->save($items, $this->_getCacheId($pageNumber), array($this->_getCacheInternalId()));
    }

    return $items;
}

As you can see, when the items are cached, the cache ID corresponds to what was created in my cache directory (obviously), but when Paginator tries to read the cache, it's looking for the wrong ID!

The source of the problem is that _getCacheInternalId() serializes the adapter to identify the cache entry.


protected function _getCacheInternalId()
{
    return md5(serialize($this->_adapter).$this->_itemCountPerPage);
}

Of course, the adapter's state is going to be different after results are retrieved vs before they are retrieved, so this mechanism cannot work. I saved the serialized value of the adapter from the first and second calls to _getInternalCacheId() to a file and did a diff... there are several differences.

The problem is greatly exacerbated if one is using Zend_Db_Profiler. I was using the profiler to determine if the cache was working correctly. Instead, Zend_Paginator was creating a brand new cache entry with a different ID each time every time I reloaded the page. Why? Well, the profiler contains performance information on the queries that have been run thus far. This information will be different from request to request 99.99% of the time. Different content = different serialization = different cacheId.

I would appreciate it if anybody can comment on this bug and perhaps queue up a resolution for the next ZF patch/release.

I am devising a workaround to use in my project and will post it here for the benefit of others who have encountered this problem.

Comments

Duplicates #ZF-6989 , even if it gives more informations (will be linked into #ZF-6989)

Please, get the patch attached to ZF-6989 and make a use case. We are actually looking for help to test that in order to provide a patch the better it could be.