Tag Archive for 'PHP'

What to do about ZodiacFacts and #zf on Twitter?

The Twitter hashtag #zf has been used by the Zend Framework for several years now. The Twitter user @ZodiacFacts began using it some time ago, polluting the #zf stream with all that medieval nonsense. Zend Framework Project Lead Matthew Weier O’Phinney tweeted a reply to @ZodiacFacts politely requesting that he/she refrain from using the hashtag and @ZodiacFacts seems to have politely complied.

So, what’s the problem?

As of this writing, @ZodiacFacts has over 170,000 followers - a lamentable indictment on the state of human rationality. It is no surprise that some of them continue to retweet into the #zf stream.

There are not many of them, only a few each day. So, it’s only a minor annoyance. Still, life is filled with enough of minor annoyances that it’s desirable to remove/minimize them when we can.

The solution, perhaps

A Twitter Bot could be built that polls Twitter for tweets containing both @ZodiacFacts and #zf and then sends a politely worded reply to the Twitter user requesting that he refrain from using the #zf hashtag.

I have actually built a class on which such a bot can be based. And I have registered the Twitter account @zfisforzend to function as the sender.

But since the #zf hashtag should rightly be regarded as a community resource, I don’t really feel that it is my place to deploy the solution on my own. The “community” or even Zend HQ should make the decision about whether to deploy, the phrasing of the message, etc. After all, both of these things affect the public face of the project. Hey, it might even inflame the legions of @ZodiacFact fans to spam the #zf stream, just out of pique.

So, I am posting my current code so that if one of the project leaders wishes to use it - or to develop their own, which would no doubt be better than mine - then they can do so with little effort. I’d be happy to pass off the ownership of the @zfisforzend Twitter account to whatever community member wants to use it for this purpose.


/**
 * Access Twitter API, do twitter search, then reply
 *
 * @author David Weinraub ( david@papayasoft.com )
 */
class PS_TwitterSearchAndReplyBot
{
    /**
     * @var Zend_Service_Twitter
     */
    protected $_twitter = null;

    /**
     * @var string
     */
    protected $_reply = null;

    /**
     * Path to the file containing the id of the tweet to which we have replied
     *
     * @var string
     */
    protected $_sincePath = null;

    /**
     * @var string
     */
    protected $_userAgent = 'PapayaSoft-SearchAndReplyBot/1.0';

    /**
     * Constructor
     *
     * @param string $username Twitter username for the acct sending replies
     * @param string $password Twitter password for the acct sending replies
     * @param string $query the query we use to find tweets.
     * @param string $reply the text of the reply we wish to send
     * @param string $sincePath path to the file where we save the last replied tweet
     */
    public function __construct($username, $password, $query, $reply, $sincePath)
    {
        if ('' == $username){
            throw new Exception('User is required');
        }

        if ('' == $password){
            throw new Exception('Password is required');
        }

        $this->_twitter = new Zend_Service_Twitter($username, $password);
        $this->_twitter->getHttpClient()->setHeaders('useragent', $this->_userAgent);

        if ('' == $query){
            throw new Exception('Query is required');
        }
        $this->_query = $query;

        if ('' == $reply){
            throw new Exception('Reply is required');
        }
        $this->_reply = $reply;

        $this->_sincePath = $sincePath;
    }

    /**
     * Get the matching tweets
     *
     * @return array the matching tweets
     */
    public function getTweets()
    {
        $service = new Zend_Service_Twitter_Search();
        $service->getHttpClient()->setHeaders('useragent', $this->_userAgent);
        if (null == $sinceId){
            $sinceId = (int) $this->readSince();
        }
        $params = array(
            'since_id' => $sinceId,
        );
        return $service->search($this->_query, $params);
    }

    /**
     * Send the reply to a matching tweet
     *
     * @param array $tweet
     * @return void
     */
    public function sendReply($tweet)
    {
        $target = $tweet['from_user'];
        $reply = '@' . $target . ' ' . $this->_reply;

        $result = $this->_twitter->statusUpdate($reply, $tweet['id']);

        if ($result->isSuccess()){
            $this->writeSince($tweet['id']);
            return true;
        } else {
            throw new Exception('Failure posting status reply to target');
        }
    }

    /**
     * Save the id of the last tweet to which we have replied
     *
     * @param int $sinceId
     * @return 
     */
    public function writeSince($sinceId)
    {
        @$fp = fopen($this->_sincePath, 'w');

        if (!$fp){
            throw new Exception('Unable to open since-file for writing');
        }
        fwrite($fp, $sinceId);
        fclose($fp);
        return $this;
    }

    /**
     * Get the twitter id of the last tweet to which we have replied
     *
     * @return int
     */
    public function readSince()
    {
        return file_get_contents($this->_sincePath);
    }

    /**
     * Entry point. Do it all.
     */
    protected function run()
    {
        $tweets = $this->getTweets();
        foreach ($tweets as $tweet){
            $this->sendReply($tweet['from_user'], $tweet['id']);
        }
    }
}

Usage would be something along the following:


$bot = new PS_TwitterSearchAndReplyBot(
    'zfisforzend',
    'thepassword',
    '#zf AND @ZodiacFacts',
    'The ZF hashtag has been used by http://bit.ly/dlRih0 for 2+ years. Please do not use it for ZodiacFacts. Thanks! ;-)',
    dirname(__FILE__) .'/data/since.dat'
);
$bot->run();

Then set up a cron job to run this script with some polling frequency.

Note that the message we send should not contain either of the terms “#zf” or “Zend” directly. After all, those are search terms we all use in our Twitter clients. The last thing we want is for all these bot-generated replies to further pollute the targeted hashtag stream.

As you can see, it is general enough to serve as the base for other Twitter Reply bots. I confess that the code is only partially tested, but I think it conveys the idea.

So, whaddya think?

Debugging PHP - Netbeans Code Templates for Zend_Debug and FirePHP

I recently changed my day-to-day PHP IDE to Netbeans and it’s been a pleasure. [Don't ask me what I was using before, too embarrassed to say.]

idea light bulb

In particular, one of Netbeans’ nice features is Code Templates, little snippets of text that you can insert into your document simply by typing a short prefix and then hitting the Tab key. They are actually smarter than just static snippets in that they can contain variables that are dynamically filled/changed as you edit. Hugely useful. I use them constantly for a variety of tasks, but my most common use of code templates is for debugging.

One useful debug tool is FirePHP in conjunction with Firebug, especially for applications using AJAX.

Several weeks ago, Jeremy Kendall (@JeremyKendall) helpfully pointed me to Zend_Debug::dump() which behaves like an enhanced var_dump(). I was actually gratified to see that it resembles one of my own hacks that I have been using for years, though Zend_Debug::dump() is, not surprisingly, more polished.

Following on that, Markus (@derstahlstift) posted a Netbeans code template that uses Zend_Debug::dump().

I admit that they are both cool, but I prefer to identify precisely where I am doing the dump, using PHP’s magic constants __FILE__ and __LINE__. This allows me to easily chase down the dump statements and remove them once the debugging is finished.

So, here are my own versions, both in the form of Netbeans Code Templates.

For FirePHP::send():

FB::send("${VAR default="variable"} = " . $$${VAR} . ":: ${func default="func"} :: " . __FILE__ . "(" . __LINE__ . ")");
${cursor}

For Zend_Debug::dump():

Zend_Debug::dump($$${VARIABLE variableFromNextAssignmentName default="variable"}, "${VARIABLE variableFromNextAssignmentName}", true);
echo "

" . __FILE__ . "(" . __LINE__ . ") :: ${function default="function"} :: ${message default="message"}

";
${selection}${cursor}

Whaddya think? Do you have any tricks/shortcuts that you use for debugging? Let me know.

Cheers!

Zend Framework - Here I Come

Time to elevate my game.

After years of developing and tuning my own PHP-based web development framework which I have used on countless projects, I have decided to move into one of the more established PHP-based web frameworks.

My own framework is a loose collection of classes and methodologies that have worked pretty well so far. I can usually figure out how to do anything that has come up. Although it is a huge step up from so much of the the script-kiddie crap that is still fairly common - among even some successful commercial houses here in Phuket - I must confess that it never felt quite tight enough for me. It never lent itself to the kind of controlled development process I would ultimately like to be doing: source control, automated unit-testing, automated builds, and automated deployments.

There is no shortage of discussion out there about web frameworks: which ones are essentially CMS’s or mere libraries rather than full frameworks; which ones are tightly/loosely coupled; which ones are sufficiently true to established design patterns, to object-oriented principles, to MVC principles, etc; which ones are performance dogs, etc. I’ve read through much of it.

Upshot: I’m sticking with PHP, going with Zend Framework.

I’ve got nothing against the other PHP frameworks. CodeIgniter seems very approachable and has a decent community. CakePHP seems to have a lot of advocates and supportive community. Kohana, too, seems nice. Of course, there are many others.

And if I were really going to go wild, I would leap in Ruby on Rails about which I have never heard a single bad thing. But a leap into a new framework is enough without having to bite off the syntax of another language to boot.

Perhaps some other time I’ll get into all the reasons and do a comparative analysis, though it is just as likely that it will never happen since there are already tons of such resources out there and doing my own comparative analysis at a level that would be satisfactory to me would require becoming expert at all the others.

First step is to use a few of the Zend Framework classes here and there in a few projects, availing myself of the “loosely-coupled, use-at-will” nature of the library. Mostly to familiarize myself with some of the design patterns and to learn what supportive functionality is available outside the context of the full framework stack.

Eventually, I will commit to using the full MVC stack on a green-field project, either a brand new site or a redesign of an existing site. I already have one in mind. Both excited and terrified at the same time.

Go, go, go, …