Tag Archive for 'web'

Custom frontends for Zend_Cache

As we all know, server-side caching is a useful technique for optimizing the performance of web apps. Whenever some expensive operation is called for – a db query, a remote web service call, etc – caching the results of the operation means that the next time you need them, you can get them relatively cheaply from the cache.

Zend_Cache: A basic usage example

Zend Framework contains a Zend_Cache component that supports a variety of frontends and backends. Typical usage for filesystem-based caching is something like the following:


// create the cache object
$frontend = array(
    'lifetime' => 86400, // seconds
    'automatic_serialization' => true,
);
$backend = array(
    'cache_dir' => '/path/to/your/cache',
);
$cache = Zend_Cache::factory('Core', 'File', $frontend, $backend);

// now use the cache object
$cacheId = 'someCacheId';
$data = $cache->load($cacheId);
if (false == $data){
    $data = someExpensiveOperation();
    $cache->save($data, $cacheId);
}

In practice, I tend to put the cache creation portion into a factory/container class of some kind, while the usage/consumption is often in a repository or service class. But for clarity, it is shown all in line, as if it were in a controller.

The first time you run through this code, the data will not have been saved in cache (a cache miss, as they say), so you will be forced to incur the cost of the someExpensiveOperation() call. But the next time you run through it – assuming the cache has not expired beyond its lifetime – then you will register a cache hit and bypass the expensive operation.

All cool.

Yeah, but…

But there is one thing that has always bugged me about this typical flow: generating the $cacheId. For example, if someExpensiveOperation() is a db call to fetch an article, then the $cacheId will probably employ the id or slug of the article:

// $slug is the slug of the article, something like 'my-cool-article'
$cacheId = 'article_byslug_' . str_replace('-', '_', $slug);  // Zend_Cache does not like cache id's with hyphens

Generating a cache id strikes me as part of the internal details of the “caching process”. As such, it seems to me like the knowledge of how to do that should be embedded inside the caching object itself. It’s his business to know how to load data from cache, save data to cache, and remove data from cache. Connecting an id to that data should be part of his job. So, why should I have to construct cache id’s for him?

Even further, why am I seeing cache id’s at all? Couldn’t the cache object simply offer me an interface with methods like:


public function loadArticlebySlug($slug);
public function saveArticleBySlug($article);
public function removeArticleBySlug($article);

Isn’t there some easy way to make this happen?

Custom cache frontends to the rescue!

It turns out that Zend_Cache::factory() actually does allow me to create custom frontends that implement whatever interface I want. I can define my interface:

interface Project_Cache_Frontend_ArticleInterface
{
    public function loadArticlebySlug($slug);
    public function saveArticleBySlug($article);
    public function removeArticleBySlug($article);
}

Then implement in a class extending Zend_Cache_Core:

class Project_Cache_Frontend_Article extends Zend_Cache_Core implements Project_Cache_Frontend_ArticleInterface
{
    public function loadArticlebySlug($slug)
    {
        return $this->load($this->getCacheIdBySlug($article->slug));
    }

    public function saveArticleBySlug($article)
    {
        return $this->save($article, $this->getCacheIdBySlug($article->slug));
    }

    public function removeArticleBySlug($article)
    {
        return $this->remove($this->getCacheIdBySlug($article->slug));
    }

    protected function getCacheIdBySlug($slug)
    {
        return 'article_byslug_' . str_replace('-', '_', $slug);
    }
}

Note the protected method getCacheIdBySlug($slug). All the knowledge of how to create cache id’s is wrapped up in the cache object. It just exposes a functional interface that describes what you want to do, not how you want it done.

Now, tell the Zend_Cache::factory() method to use my custom frontend:

// frontend and backend options as before
$frontend = array(
    'lifetime' => 86400, // seconds
    'automatic_serialization' => true,
);
$backend = array(
    'cache_dir' => '/path/to/your/cache',
);
$cache = Zend_Cache::factory('Project_Cache_Frontend_Article', 'File', $frontend, $backend, true, false, true);

Note that we are specifying the complete name of our implementing cache class, as well as three boolean values.

The first boolean value – true in this case – tells the factory that we are using a custom frontend.

The second boolean value – false in this case – tells the factory that we are not using a custom backend.

The third boolean value – true in this case – tells the factory to use autoloading to instantiate the frontend and backend objects.

Fully constructed by the factory, cache usage is cleaner:


$data = $cache->loadArticleBySlug($slug);
if (false === $data){
    $data = someExpensiveOperation();
}

No cache id’s, no mixing of concerns.

Maybe it’s a whole lot of work just to tuck away some cache id generation. But I confess that it feels better to me.

Unit testing for placeholder-based view-helpers in Zend Framework

Just got bitten by an interesting little issue.

I have a Zend Framework view-helper that I use to add a CSS class to an HTML <body> tag. Usage in a view-script is:

$this->bodyTag()->addClass('someclass');

Then in a layout, I’d output it using:

<?= $this->bodyTag() ?>

The class itself is very simple:


/**
 * A body tag
 *
 * @author David Weinraub <david@papayasoft.com>
 */
class PapayaSoft_Zend_View_Helper_BodyTag extends Zend_View_Helper_Placeholder_Container_Standalone
{
    public function bodyTag()
    {
        return $this;
    }

    public function toString()
    {
        $tag = array();
        $tag[] = '<body';

        $storage = $this->getContainer();

        if (count($storage) > 0){
            $classes = array();
            foreach ($storage as $class){
                $classes[] = $class;
            }
            $tag[] = sprintf(' class="%s"', implode(' ', $classes));
        }
        $tag[] = '>';
        return implode('', $tag);
    }

    public function addClass($class)
    {
        $this->getContainer()->append($class);
        return $this;
    }
}

I’ve begun moving this kind of stuff out into my own library, which I will manage in its own project under source control. I can then import it into multiple projects, just as I would with the libs for Zend or Doctrine or HTML Purifier.

Consequently, it’s now time – actually long past-due – to really get into the discipline of unit-testing. A simple class like this seems a decent place to start.

Unit-tests for the BodyTag class looked something like this:


/**
 * Test BodyTag class
 *
 * @group Zend_View_Helper
 * @author David Weinraub <david@papayasoft.com>
 */
class PapayaSoft_Zend_View_Helper_BodyTagTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var PapayaSoft_Zend_View_Helper_BodyTag
     */
    private $_bodyTag;

    public function setUp()
    {
        $this->_bodyTag = new PapayaSoft_Zend_View_Helper_BodyTag();
    }

    public function tearDown()
    {
        unset($this->_bodyTag);
    }

    public function testRenderOnNoAddedClasses()
    {
        $this->assertEquals('<body>', (string) $this->_bodyTag);
    }

    public function testRenderOnSingleAddedClass()
    {
        $this->_bodyTag->addClass('myclass');
        $this->assertEquals('<body class="myclass">', (string) $this->_bodyTag);
    }

    public function testRenderOnMultipleAddedClasses()
    {
        $this->_bodyTag = new PapayaSoft_Zend_View_Helper_BodyTag();
        $this->_bodyTag->addClass('someclass')->addClass('anotherclass');
        $this->assertEquals('<body class="someclass anotherclass"<', (string) $this->_bodyTag);
    }
}

Pretty straightforward. Or so I thought.

The last test failed, reporting the following:

BodyTagTest::testRenderOnMultipleAddedClasses()
Failed asserting that two strings are equal.
--- Expected
+++ Actual
@@ @@
-<body class="someclass anotherclass">
+<body class="myclass someclass anotherclass">
It seemed as if result of running the penultimate method testRenderOnSingleAddedClass() was still in effect when testRenderOnMultipleAddedClasses() started. When the testRenderOnSingleAddedClass() method is removed, the testRenderOnMultipleAddedClasses() passes.

What’s going on here? The whole point of tearDown() and setUp() is to reset the test environment to a clean state, in this case by unsetting and re-instantiating the $_bodyTag member variable.

Well, the whole key here turns out to be state of the system. It’s not sufficient to simply reset the object in the member variable if it has made other changes to the state of the system. Sort of like selling the cow that crapped in your living room, but forgetting to clean up the mess he made. And that’s actually what appears to be happening.

The framework’s own unit test for Zend_View_Helper_HeadTitle, another Zend_View_Helper_Placeholder_Container_Standalone-extended class, offers some hint of this. The setUp() method is as follows:

    /**
     * Sets up the fixture, for example, open a network connection.
     * This method is called before a test is executed.
     *
     * @return void
     */
    public function setUp()
    {
        $regKey = Zend_View_Helper_Placeholder_Registry::REGISTRY_KEY;
        if (Zend_Registry::isRegistered($regKey)) {
            $registry = Zend_Registry::getInstance();
            unset($registry[$regKey]);
        }
        $this->basePath = dirname(__FILE__) . '/_files/modules';
        $this->helper = new Zend_View_Helper_HeadTitle();
    }

So there is some Zend_Registry stuff in there that needs to be cleared, as well. Adding the registry-clearing code from the ZF unit-test into my own solved the problem.

Live and learn.

CRISP-y goodness: tracking Phuket internet speeds

I am pleased to report that we have launched the new version of CRISP – Customers Reporting Internet Speeds in Phuket.

For some years, well-known computer columnist and baker Woody Leonhard (see AskWoody.com for his opinions on Windows patches or his columns in the Windows Secrets Newsletter, or his various Windows for Dummies books, not to mention his bakery business and his Phuket Sandwich Shoppes) has led a group of Phuket internet users in running speed tests on their internet connections and storing this data in an app developed and hosted by Henry Habermacher. The data was freely available as a CSV download to anyone who wanted to perform their own analysis on it.

Woody’s prime motivations were to provide Phuket internet users with real usage data on which to base their ISP choices and to clearly demonstrate the significant discrepancy between speeds advertised by ISP’s in Phuket and those that are actually delivered.

As of this writing, the db has over 15,000 speed reports dating back to 2008.

Henry was unable to host it going forward, so we needed to move it to a new home. But rather than simply migrate, we decided to do a rewrite in PHP and I got the call. Using a design by Seth Bareiss, my re-implementation is built on Zend Framework and the Doctrine 1 ORM. Relative to the previous version, it features some enhanced searching and user management, including registration and profile management.

If you are a Phuket-based internet user, come on over, register, and start contributing to the tracking effort.

http://www.khunwoody.com/phuket-internet-speed/

Facebook connecting the dots?

An NY Times Op-Ed piece by Eduardo Porter about web privacy finishes with the following:

But with more and more information about people’s credit cards, browsing histories and identities sloshing around online, I wonder whether this will do. A few months ago, I nervously created my first Facebook page with the minimum necessary information to view pictures posted by old friends.

I returned to the page a few days later to discover that somehow it had found out both the name of my college and my graduation class, displaying them under my name. I have not returned since. In the back of my mind, I fear a 28-year-old hacker and a couple of Russians have gathered two more facts about me that I would rather they didn’t have. And it’s way too late to take my life offline.

Really? Facebook is connecting the dots this way?

Hard to make a true determination of what he means. His Facebook profile is obviously accessible to friends only. It could be that Facebook connected the dots and presented them to him for confirmation. That would certainly be less insidious than making the connections and imposing them upon his account. For example, he could easily wish to keep his Facebook profile clear of various episodes of his past life, like college, previous employers, etc.

But it surely shows that every little bit of ourselves that we leak out into the cyberspace represents another potential hook that a data miner could use to link vast data repositories.

One has to wonder if there is really any privacy left at all. We get by on obscurity, the (blind!) hope that no one will look too closely. But I wonder how most of us, with modest connections to to electronic world – an email address, some credit cards, a few online purchases, perhaps even a Facebook account – would fare if someone with access to those data mines turned their attention on them. In fact, it’s probably the case that the data miners do this themselves via automated searches on their data stores.

As Mr. Porter said, way too late to pull our lives offline.