Intelligent image thumbnails

A recent project I’ve been working on required 4:3 thumbnails of all images no matter the original image’s orientation. This required me to do a little math and cropping before actually making the thumbnail. I use the package Image_Transform to figure out which orientation the image is and then crop it appropriately.


$o = Image_Transform::factory('IM');
if (PEAR::isError($o)) {
    return $o;
}


$result = $o->load($ds1);
if (PEAR::isError($result)) {
    return $result;
}


$w = $o->getImageWidth();
$h = $o->getImageHeight();

if ($h > $w) {
    $newWidth = $w;
    $newHeight = ($newWidth * .75);
    $newY = ($h * .15);
    $newX = 0;
} else {
    $newWidth = ($w / 2);
    $newHeight = ($newWidth * .75);
    $newY = ($h / 2) - ($newHeight / 2);
    $newX = ($x / 2) + ($newWidth / 2);
}

$o->crop($newWidth, $newHeight, $newX, $newY);
$o->save($ds2);

The first check checks to see if the image is in landscape or portrait (in portrait the height will be greater than the width). With portrait’s I use the width, multiple that by 0.75 to get my 4:3 ratio and finish by going 15% down from the top of the portrait (assuming that you’re focusing your portrait in the upper portion of the image). With landscape images I simply go outwards from the center of the image (again, assuming you’re focusing your landscape in the center of the image).

Writing phpt files for PEAR and profit

I’ve been programming PHP now for, oh, almost a decade and I’ve been a contributor to PEAR for about four years now. It took me this long to finally get to the point that I think writing unit tests, in some form, is a good idea. I jumped on the OOP bandwagon about 6 years ago and drank the MVC koolaid about 4 years ago. As of a few weeks ago, I’ve jumped on the phpt bandwagon. I’m not going to cover the basics, because they’re well covered in a few different places around the web, but I am going to cover some of the nuances I’ve learned in the last few weeks.First off, you can run phpt files using the PEAR command line utility. I’ve been creating a tests directory and putting all of my phpt files in there. I normally name them 000-class-method.phpt and then increment the sequence number at the front for each test by 5 so I can do a decent job of predicting how tests run. Once you have a few tests you can run your tests in two different manners from the command line:

  1. pear run-tests
  2. pear run-tests 000-class-method.phpt

When you run your tests you’ll get results of which tests passed, which failed, if any of them were skipped and the reason why they were skipped, and files for debugging. If 000-class-method.phpt fails, there are a few files that will be of interest to you:

  • 000-class-method.php is the actual PHP file ran, which is found in the --FILE-- portion of your phpt file.
  • 000-class-method.outis what was actually output by the PHP file ran from the --FILE-- portion of your phpt file.
  • 000-class-method.exp is what the test expected the output of 000-class-method.php to be, which is found in the --EXPECT-- portion of your phpt file.

You pretty much have everything you need to debug why a test failed. I normally diff the exp and out files and then debug the test (or my code, which is usually the case) using the php file.

Another great thing about phpt files is that you can use a cgi PHP binary to debug actual GET requests (phpt supports spoofing $_GET, $_POST and $_COOKIE using the cgi binary). This lets you create tests like the following:

--TEST--
Test a GET request
--GET--
foo=bar&baz=foo
--FILE--
<?php
echo $_GET['bar'] . "\n";
echo $_GET['baz'] . "\n";

?>
--EXPECT--
bar
foo

This test should pass without incident. The only catch is that you need to specify your cgi binary path using the --cgi argument when you run the tests (e.g. pear run-tests --cgi=/path/to/php-cgi).

Overall, I’m pretty happy with my newfound testing experiences. My only complaint is that phpt files don’t support spoofing of $_SESSION natively. A small complaint to be sure.

My first Digg code goes live

So if you’re interested in seeing what I’ve been doing for Digg lately, you can now go to the site, log in and send invites to your friends. You’ve actually always been able to do this, but now you’re able to keep track of how many friends you’ve invited, those friend invites will be announced to the world on the homepage and you’ll automagically be added to the invitee’s friends list.

Once you’ve invited a friend, the friend needs to register and digg at least three stories before they count as a referral. At that point a little note is posted to the frontpage announcing your invite and your friend referral count in your user profile goes up. I, of course, invite all of you to join.

I’ve also been working on other magical doings, but I can’t talk about those quite yet. I hope to talk more about that work after it goes live as well.

I'm employee #20 at Digg

It’s official. As of today I’m employee #20 at Digg. I sent in my resume in September fully expecting not to hear back, but over the course of the last few months it became apparent I was being considered for a position. I didn’t think much of it after not hearing from them for almost a month, but Brian Link sent me an email around the first of the year which turned out to be an official offer.

For those of you that don’t know, Digg is the 19th most visited site on the internet in the United States. It serves up more traffic than Wal-Mart, the New York Times or Best Buy. So, as you can imagine, I’m pretty excited about this opportunity. I’ll be arriving in San Francisco somewhere towards the beginning of February. If you’re in the area and would like to get pints please let me know.

Taking the job means that I won’t be doing any contract work and my open source projects will most likely sit idle for the time being. If you’re interested in maintaining Framework or any of my PEAR packages please let me know.

Adding authentication to PEAR channels

A client of mine is taking the proactive approach of packaging all of their software using PEAR and distributing it via a custom PEAR channel. I can’t recommend this enough for people that are distributing their PHP code to a number of clients/users.

The problem is that, by default, PEAR channels are consumable by anyone with an internet connection. I sent an email to Greg asking him if there was a way to restrict this and how to go about doing it. As it turns out it’s not only available, but detailed in the free excerpt from his new book The PEAR Installer Manifesto.

There are a number of ways to restrict access. The more complicated approach involves coding a script that handles the authentication and then restricts packages on a per client basis. This is a great way to say client X can install packages A, C and F, while client Y can only install packages B and D.

The route I ended up taking involved simply setting up HTTP-Auth using and .htaccess and .htpasswd file. Once you have that set up and working you can log in with the following commands.

$ pear -d "pear.mychannel.com" login

Follow the instructions by entering your username and password and you should see a confirmation that you’re logged in. After that you’re allowed to download and install.

Framework 0.1.4

This is a fairly major upgrade for the little framework that could. I haven’t stopped developing Framework. Quite the contrary, I’ve been working on it extensively as I’m starting to build sites utilizing it. Other than the extensive changes, fixes, etc. anybody wishing to try this out will be pleasantly surprised that there is now an example document root in the examples.

  • Added Framework_Exception
  • Added Framework_Template for unified templating in modules
  • Added __sleep() and __wakeup() to Framework_Object and Framework_Object_DB
  • Added $this->template->plugins_dir = array('plugins',$path.'/'.'plugins') to Framework_Presenter_Smarty
  • Added Framework_Auth_ACL to handle Access Control Lists based on module/event pairings
  • Added Framework_Request
  • Added Framework_User::__isset()
  • Added Framework_Site_Common::stop() which is ran from Framework::stop() when processing has completed
  • Added config.xml for site configuration data
  • Added Framework_Presenter_JSON which utilizes php-json
  • Fixed a bug when creating custom user classes in Framework_Uset::singleton()
  • Fixed how Framework_User::__construct() detected the userField from Framework_Session
  • Fixed misspelled function call in Framework_User
  • Fixed a bug where Framework_Object was attempting to create a log file before Framework_Site_Common had been created
  • Fixed mispelled return code in Framework::start()
  • Fixed a bug where a module’s event was running before the session/user had been authenticated
  • Changed Framework_Presenter_REST to include XML_Serializer options
  • Changed all Exception‘s to Framework_Exception
  • Changed Framework_Object::__construct() to use Framework_Site_Common::$logFile to create instance of PEAR Log in Framework::$log
  • Removed a few references to deprecated constants in Framework_Presenter_Module
  • Deprecated Framework_User::$userTable, Framework_User::$userField, Framework_User::$defaultUser, Framework_User::$userClass (see config.xml)

Download Framework 0.1.1

ext/mysqli

  1. PHP5 comes with a new MySQL extension called mysqli. While the i stands for improved, interface and incompatible (some say incomplete – HA!).
  2. Supports MySQL versions starting with MySQL 4.1.
  3. The new function was basically a way to start over and clean things up to work with the new features in 4.1+.
  4. Includes SSL connections, stronger password algorithm, prepared statements prevent SQL injection, no default connection parameters. Overall, their goal was to make it safer.
  5. Can make use of new and more efficient MySQL binary protocol, prepared statements give massive performance improvements on large data sets, faster overall code, support for gzip compressed connections. Additionally, the MySQL server can be embedded into PHP (wtf?).
  6. You can use either OOP or procedural interfaces, prepared statements make certain operations easier and there’s less that can go wrong (which seems a bit ambiguous).
  7. Some redundant functions have been dropped, some new functions that support new features and persistent connections are no longer support (about damn time).
  8. The OOP interface is “marginally” slower than the procedural interface, but tiny compared to the cost of actually getting the data. In other words, use whichever you like.
  9. In PHP5, the OOP interface supports Exceptions (ie. ConnectException, etc.).
  10. Added autocommit(), commit() and rollback() functions to the OOP interface.
  11. Now supports multiple queries with the multi_query() function. This looks absurdly awkward. Not sure if I can even think of a reason to use this. This functionality was added specifically for stored procedures which can return multiple result sets.

Nevermind, Ian just noticed he’s reading the notes verbatim from the manual. That being said, this looks like an extremely interesting enhancement over the old MySQL client library. We’ll probably look into switching things over when we get back and start forming a larger MySQL strategy.

My first WordPress plugin

So I finally broke down today and started working on my first WordPress plugin. I liked how recent links worked, but I wanted an aggregated list of both links I found interesting and my Flickr photos. I was going to hack the recent links plugin that I had used, but decided against it in the end. In the end I decided that it made sense to store my interesting links on del.icio.us and my photos on Flickr and then aggregate them into a single list, which is what you see now below this post.

This way my photos stay on Flickr and my links stay on del.icio.us, but they show up inline on my blog’s frontpage. I’ve got a few bugs and kinks to work out still, but it’s definitely ready for beta testing. If you are interested in playing with it then give me a shout and I’ll package it up for you to test out.

  • Integrates with Flickr and stores photo and tag information into a MySQL table.
  • Integrates with del.icio.us and stores link and tag information into a MySQL table.
  • Uses PEAR’s DB and HTML_Request packages and PHP5’s SimpleXML extension to seemlessly fetch and cache new content every hour or every time an admin visits the website.