Creating Zend_Tool Providers

When I was at Symfony Live this past February, I assisted Stefan Koopmanschap in a full-day workshop on integrating Zend Framework in Symfony applications. During that workshop, Stefan demonstrated creating Symfony "tasks". These are classes that tie in to the Symfony command-line tooling — basically allowing you to tie in to the CLI tool in order to create cronjobs, migration scripts, etc.

Of course, Zend Framework has an analogue to Symfony tasks in the Zend_Tool component's "providers". In this post, I'll demonstrate how you can create a simple provider that will return the most recent entry from an RSS or Atom feed.

First things first

Caveat: this entire post assumes you are using a unix-like operating system, such as a Linux distribution or Mac OSX. Most of the instructions should work in Windows, but I have not tested on that platform.

First, a little setup. Zend_Tool needs some configuration. To get started, you need to run the following command (if you haven't already):

$ zf create config

This will create a configuration in $HOME/.zf.ini. If you pop that file open, you should see an entry for php.include_path. This is the include_path Zend_Tool will use, and should include your ZF installation; any providers you create should be on this path — or you should modify it to add a path to your providers.

Create the provider

Providers are incredibly simple. The easiest way to create one is to create a class extending Zend_Tool_Framework_Provider_Abstract, and then to simply start creating methods.

A few rules are good to know, however:

  • If you need to throw an exception, throw a Zend_Tool_Project_Exception. This integrates with the CLI tooling to provide nice, colorful error messages.

  • While you can echo directly from your methods, the suggested practice is to use the response object and append content to it. This will ensure that if we later write an XML-RPC, SOAP, or web frontend to Zend_Tool, you will not need to make any changes to your code. This is as easy as:

    $response = $this->_registry->getResponse();
    $response->appendContent($content);
    

In my provider, I'm wanting to grab the first entry of a given feed. Instead of needing to remember the feed URL, I'd like to use a mnemonic; this will be my sole argument to the provider. I'll have it default to my own feed. The code ends up looking like this:

class Phly_Tool_Feed extends Zend_Tool_Framework_Provider_Abstract
{
    protected $_feeds = array(
        'weierophinney' => 'http://weierophinney.net/matthew/feeds/index.rss1',
        'planetphp'     => 'http://www.planet-php.net/rdf/',
    );

    /**
     * Read the first item of a feed
     * 
     * @param  string $feed Named identifier for a feed
     * @return bool
     */
    public function read($feed = 'weierophinney')
    {
        if (!array_key_exists($feed, $this->_feeds)) {
            throw new Zend_Tool_Project_Exception(sprintf(
                'Unknown feed "%s"', 
                $feed
            ));
        }

        $feed = Zend_Feed_Reader::import($this->_feeds[$feed]);
        $title = $desc = $link = '';
        foreach ($feed as $entry) {
            $title = $entry->getTitle();
            $desc  = $entry->getDescription();
            $link  = $entry->getLink();
            break;
        }
        $content = sprintf("%s\n%s\n\n%s\n", $title, strip_tags($desc), $link);

        $response = $this->_registry->getResponse();
        $response->appendContent($content);
        return true;
    }
}

I'm leveraging Zend_Feed_Reader here, and simply creating some formatted text output.

Now that the provider is created, I need to put it in the file Phly/Tool/Feed.php, relative to a directory in the include_path configured by Zend_Tool.

Tying the provider to the tool

Now that we've got the provider written and somewhere Zend_Tool can potentially find it, we need to tell Zend_Tool about it. Open up the $HOME/.zf.ini file again, and add the following line:

basicloader.classes.1 = \"Phly_Tool_Feed\"

This tells Zend_Tool that there's an additional provider it should be aware of. Note in particular the .1 portion of the key; basicloader.classes is an array. One gotcha I discovered is that, unlike Zend_Config, you cannot use the [] notation. In other words, the following does not work:

basicloader.classes[] = "Phly_Tool_Feed"

You need to specify keys manually, and they need to be unique.

Getting help

Now, time to test out if it all works. If you've done the above steps, you can now execute the following:

$ zf \? feed

Note: I use zsh, and need to escape the question mark; you may not need to in other shells.

If all is well, you'll get the following:

Actions supported by provider "Feed"
  Feed
    zf read feed feed[=weierophinney]

If you're not seeing this, check to make sure that your provider is on an include_path as defined in your .zf.ini file; if you still have issues, ask on the fw-general mailing list or in the #zftalk IRC channel on Freenode.

Using the provider

Once your provider is working, fire it up:

$ zf read feed

or

$ zf read feed planetphp

You should get something that looks like this (the actual entry will vary):

State of Zend Framework 2.0

    
    The past few months have kept myself and my team quite busy, as we've turned
    our attentions from maintenance of the Zend Framework 1.X series to Zend
    Framework 2.0. I've been fielding questions regularly about ZF2 lately, and
    felt it was time to talk about the roadmap for ZF2, what we've done so far,
    and how the community can help.

 Continue reading "State of Zend Framework 2.0"
    

http://weierophinney.net/matthew/archives/241-State-of-Zend-Framework-2.0.html

Closing notes

One "gotcha" you may experience is that there is currently no support for specifying project-specific providers within applications created with Zend_Tool — a feature that would be quite useful for creating project-specific tasks.*

That said, Zend_Tool providers are an incredibly useful and easy way to write CLI tools based on Zend Framework. Hopefully this post will help demystify the component and its usage, and get you thinking about what tasks you would like to write.

* You can fake it by creating an alternate configuration file in your project, informing the environment of it, and calling the zf commandline tool — something that can be done in a single line:

$ ZF_CONFIG_FILE=./zf.ini; zf <action> <provider> ...