Here comes the busiest conference time of the year - September. Apart from school starting, which probably merits its own posting (assuming I can ever get back into the swing of regular writing on this blog), there are any number of conferences to attend this month and next.

This month, I've got plane tickets to hit Columbus, Ohio for the Habari Party -- an event whose name I'm not fond of, but is good enough for the purposes of celebrating the third year of Habari development. It sure does not seem like it's been that long. While I'm there, I'll also be popping by the Columbus PHP Meetup chapter with skippy to pitch Habari in some fashion.

I've been trying to coordinate a few other plans. The weekend of Ohio Linux is pretty tight. Mom is going to be at the beach house that weekend, and I'm not sure if that's the weekend everyone will want to go down. Also, my coworkers are trying to coordinate some kind of canoeing event on that day in preparation for our race in October. So there is some contention for the date. If things go as they usually do, I'll end up sitting at home in front of the PC that weekend, paradox of choice and all.

October looks to be pretty busy, too. We're hopefully going to plan a Halloween party in lieu of this year's somewhat canceled birthday BBQ. BlogWorld is in October on the same day as CPOSC. CPOSC is cheaper and local, but of less overall interest to me, espcially since my proposal for presenting was turned down. Then again, going to Vegas alone again seems... questionable.

I suppose over the next five days I should think about what it would be useful to say to two groups of people about Habari. First, the group that doesn't know what it is but knows PHP, then second, a group that knows what it is but doesn't necessarily know PHP. Crazy.

This is a weird topic, since I'm not sure if it belongs here or at RedAlt, but I think until such time as I do it (since it's not talking about program planning), it's here for me to talk about. But if I do create the thing, I'll put it on RedAlt for other people to use as a resource. Ok, with that out of the way...

I have a problem remembering the codes for the obscure date characters that PHP's date() function uses. I know, it's something I should just know, but it's not. Yes, I have trouble remembering whether "M", "F", or "J" outputs the abbreviated form of the month name. I can never remember how to get the number of the day of the year. It's a real mental block and an impediment to my day-to-day life.

In contrast, I have absolutely no problem remembering how regular expressions work. I know that "\s" is a whitespace, and it's capital, "\S" is not a whitespace. I know that "{,4}" means up to four times. I even know what the "ism" flags mean on an expression. I surely don't need to know that a dot matches any character, and that "^" and "$" mark the beginning and ending of a string, depending on the flags provided, of course. Why does this matter at all?

I have very specific needs for a PHP cheat sheet. I need it only for the things that I can't remember, but need to look up often enough that a sheet of them on my desk would be more useful than going to a web site that had it all. I really want most of the space used for the stuff I need to know rather than the stuff I already know, so 3/4 of a page of basic regular expression stuff (That omits the look-ahead and look-behind assertions? Oh, come on!) is not good for me.

There are only a scattered few things that I need to keep track of. The order of parameters of some PHP functions might be nice, since they change unexpectedly from function to function. Like, whether sort() returns a value. Useful stuff.

Some trickier HTML and CSS might be useful, too. Just the obscure stuff, nothing mundane. Some of the crazier MySQL hints, like how to effectively turn a query with an IN() into a more efficient query that's JOINed.

For this reason, all of the cheat sheets I've found on the web so far are too cluttered. I don't really want a novel on my desk for reference when a page of specific info will do fine. Yes, I'm just picky.

But I'm envisioning a tool that would let you enable and disable sections, hide and show specific functions, and rearrange topical blocks on the page to produce something useful to whoever had configured it. Such a tool could be of value to the universe. Perhaps I will build this tool. Ironically, it will probably require the use of a cheat sheet.

On the Habari development mailing list, Daniel Smith writes:

I am interested in learning PHP (object)/MYSQL programming in general, and Habari-specific in particular. As a php newbie, could anyone please point me to any resources, latest quality books, tutorials or whatever, that could help me to learn how this all works?

An excellent question, and something that we experienced PHP folks would all do well to answer to help bring more people in and teach better coding practices.

For beginning PHP and MySQL, I really like the Visual Quickstart series of books. They have clear chapter goals and are rife with examples. Combine the books on PHP, MySQL, and PHP and MySQL with a willingness to frequently search the online documentation for both tools, and you can accomplish pretty much anything you set yourself to. Obviously, these are not for seasoned code veterans, but I think they're just right for people just starting who don't want to be babied but instead want to learn real code.

One thing that I've always found useful is picking a specific project goal, the simplicity of which would be determined by how green I am on the topic, and trying to code it myself. Sometimes working through other people's code can be daunting.

That said, looking through other people's code to see how they do things is essential to being literate with the common conventions for coding. A good example of something that the PHP coding world does that you don't usually learn in a book would relate to database access.

When looking through the books on PHP, you'll see a lot of reference to commands for accessing MySQL. No project I know worth anything uses PHP's mysql commands directly. Instead, they use an abstraction layer that handles many of the mundane details of database use without having to write the same three lines of database connection code repeatedly.

The lesson from the above is to see what projects like Habari do for database access. Habari is very object-oriented code. Practically everything in Habari takes place in classes and objects. If this is not your mental paradigm, then it may take some relearning to become comfortable in that milieu.

On the plus side, you will be significantly rewarded for your patience in learning Habari's style of coding with code that is cleaner and more flexible than that of many other projects. And if you're starting out with object-oriented thoughts, then you'll be a step ahead of others who learned procedural coding and have bullishly stuck to it.

Perhaps the best resource Habari has to offer for teaching new coders is the IRC channel, where Habari developers occasionally roost. There, you can ask PHP questions and get real answers from people who often know what they're doing. The plus side of using #habari and not #php is that while many of us are seasoned developers, we have a commitment (at least I do) to making new people feel comfortable in our community. As we say, "community first."

I'm glad you've decided to take the plunge in learning PHP, and hope that Habari can offer you tools toward your success.

On page 74 of Web Designer magazine #130 is an article on making tag clouds in PHP. I suppose you could do it their way, but a few little things left me puzzled about their implementation, and I thought I would give it a go myself.

I'm a big, big fan of associative arrays in PHP. Most people who know arrays know that they are variables that contain a set of elements indexed by numerically, but PHP can index arrays in two ways. An associative array allows you to use a string key to identify each element of the array, rather than a number. The strings have to be unique, but for the purposes of creating a tag cloud, this is perfect because we only want to list each element once.

Using an associative array for the tags makes a huge difference in the amount of work you need to do. Using some smarts in other areas can offer some improvement, too. Let's step through the process and document it a bit to see how it goes.

First, lets define some CSS classes that represent the frequency of use of each tag:
// These are the class names that we're going to use for frequency $frequencies = array( 'used_never', 'used_infrequently', 'used_frequently', 'used_continuously' );

You may prefer to use other class names than these, but you get the idea. 'used_never' is for tags that are hardly ever used, ranging up to 'used_continuously', which is, uh, used continuously. One thing to note here that's going to be different from the Web Designer article is that this code will let you add new frequencies, as long as you keep them in order. You'd have to adjust parts of the article's code to make this happen. Moving on, let's look at loading the data, which is where the biggest change is.

We want to get our data loaded into an associative array, rather than a strangely-indexed multi-dimensional array. Here's some PHP that would load the data directly:

// Here are some predefined tag frequencies $tags = array( 'foo' => 2, 'bar' => 3, 'baz' => 14, 'qux' => 10, 'Freddy Mercury' => 1, );

Let's figure out how to get the data into the array, which the Web Designer magazine completely overlooks.

If you're using PEAR's MDB2 classes, you can load this array super-easy:
// Get the tags using MDB2 $tags = $database->getAssoc('SELECT tag, count(tag) AS used_times FROM tags GROUP BY tag ORDER BY tag');

The getAssoc() function returns an associative array, using the first column as the key and the second column as the value. It's pretty slick, and we should probably add something like that to Habari's DatabaseConnection class, because it's pretty useful.

If you're using something like Habari's DatabaseConnection class, you need to do a little more work:
// Get the tag data using Habari's default DatabaseConnection $temp_tags = DB::get_results(' SELECT tag_text, count(id) AS used_times FROM habari__tags INNER JOIN habari__tag2post ON habari__tag2post.tag_id = habari__tags.id GROUP BY id ORDER BY tag_text '); // Convert the tag data into an associative array: foreach($temp_tags as $tag) { $tags[$tag->tag_text] = $tag->used_times; }

That's how to get the data out of Habari's database, if you're using the default table prefixes, but you can use the same general idea for any database that stores tags.

Note that the foreach() loop above can be used to convert any existing array of data into an associative array for our purposes, as long as the keys are unique. Ok, so you've got the data into the right format -- now things get much easier.

Steps 6 through 9 of the Web Designer article talk about looping through the array values to get the number of times the most-used tag was used, and the number of times the least-used tag was used. This is silly. Try this instead:

// Get the max and min used frequencies. $used_max = max( $tags ); $used_min = min( $tags ); // Get the range of frequencies $range = $used_max - $used_min;

We don't need to know which tags were most and least used, just the numbers. That makes is simpler to use the above than writing out a whole loop. The only reason I can see that you'd assume you had to use your own loop is because your editor's intellisense doesn't mention that you can use an array as an argument for min()/max(). Too bad.

In the step above we've also gotten the range of frequencies used. These will be useful later.

Steps 10 through 18 in the original article are utter insanity. They create a couple of nested loops that assign classnames to a temporary array, indexed on the key of the multi-dimensional array... It's even hard to describe! Let's try a more sane approach.

We're going to go through all of our tags, figure out which size range they fit into, and change the value in the array from a number into the actual class name we want that tag to use on output. How do we do this? A callback function!

There are a few really useful functions in PHP that require callbacks. A callback is simply a function that you provide as an argument to another function. That other function calls your callback as required to produce its own output. In this case, we're going to use array_map().

array_map() is a PHP function that passes each member of an array through a callback function. The callback has one input argument (actually, this is not entirely true, but for our purposes it's good enough) which is a single array element, and the output returned is the value replaced into the array.

Our callback function needs to know a few things, so we're going to have to use a couple of global variables. I don't like doing this. The best way around using globals in this case is to use a class. If you were developing in Habari, this would be a cinch, since nearly everything is a class. But for this example, it's globals.

Our callback function:

/** * Returns a class name relating to the frequency of a used tag * @param integer $used_times Number of times a tag was used * @return string The associated class name **/ function count_to_class( $used_times ) { // Make the variables we created outside of this function // available inside this function global $used_min, $range, $frequencies; // Get the number of frequency classes, zero-indexed $frequency_count = count( $frequencies ) - 1; // Get the index of the frequency array that holds our classname $frequency_index = round( ($used_times - $used_min) / $range * $frequency_count ); // Return the frequency class name return $frequencies[ $frequency_index ]; }

The $frequency_index is calculated using a formula that looks complicated, but is actually pretty simple. It first figures out what percent of the overall range the tag's frequency is. Then it finds the index that corresponds to that frequency. I used round() to get a nice distribution between the class names, but if you used floor() instead, then you'd only get your highest frequency when it's used in every case.

Ok, so the callback function is defined. Let's use it:

// Process the tag array using the callback $tags = array_map( 'count_to_class', $tags );

Too simple? Maybe. Note that we have to pass the callback function name as a string. PHP is weird.

At this point we have an associative array with keys that are our tags, and values that are the classnames for their output. We can output all of that very easily, like this:

// Output the tags as list items echo '

    '; foreach( $tags as $tag => $class ) { echo "
  • {$tag}
  • "; } echo '
';

The foreach() loop construct allows us easy access to the associative keys in the array, using the double-arrow. The key is on the left, the value is on the right. We output them in the unordered list, and presto! Tag cloud.

Apply a little CSS, and you're home free:

#tagcloud { list-style:none; } #tagcloud li { display: inline; padding: 0.2em; } #tagcloud li.used_never { font-size: 0.8em; } #tagcloud li.used_infrequently { font-size: 1.0em; } #tagcloud li.used_frequently { font-size: 1.5em; } #tagcloud li.used_continuously { font-size: 2.0em; font-weight: bold; }

Web Designer magazine styles in font-size percentages. The other Owen says to try percent. I've given you ems, in case you're into that.

Enjoy.

I wanted to get some thumbnail creation code in PHP to use for a little project I was working on, and so I traipsed over to Google and asked for "php thumbnail". I took a peek at the first search result, snagged the code, inserted it in my test app, and went merrily on my way. And that will be the last time I trust Google to find me code.

There are many problems with the code that I got. It's not malicious, but it does many easy things wrong. And the one thing that it's supposed to do well - the thing for which Google found it - it doesn't do correctly.

I can't entirely blame the code author, because this particular bit of code is very commonly written incorrectly. But what's a guy to do when you just want a simple PHP function (as opposed to a whole class) that generates thumbnails? Well, it's about time someone did something about that. Here we go.

Here's what most people do wrong. You can't simply compare whether the width of the original image is greater than the height of the original image. You need to compare the aspect ratio (the ratio of width to height) of the source size to the destination (thumbnail) size. Why?

Assume the destination size is a square, 100 pixels by 100 pixels. The aspect ratio of the destination size is 1 (100/100). If the source image is wider than it is tall, then you need to maximize the width of the output. If the source image is taller than it is wide, then you need to maximize the height of the output. In either of these cases, you're fitting the larger of the two sides into the 100×100 pixel space. That the comparison of original width to original height works out is pure coincidence. To see where that evaluation fails, consider a different set of dimensions.

Assume the destination size is a rectangle, 100 pixels wide by 50 pixels high. If you have an image that is 50×50, it should obviously not be resized. According to the algorithm I was using, you would end up resizing the image to 100×50, which is obviously not right.

Worse yet, if your source image was 75×50, it should still not be resized because it is already at the maximum output height. You end up with a very oddly resized image using the old, incorrect algorithm.

If instead of comparing the size of the source image dimensions to itself, you compare the ratio of those dimensions to the ratio of the dimensions of the output image, then you know which way the source image will better fit into the destination size. You can then use that information to anchor your dimensions for the thumbnail output.

When you're done playing mental math with how to use those dimensions, there are a few other things that we should clean up in the PHP, too.

First, you don't explode() a file extension off the end of the file. If you already have the image file, use the built-in getimagesize() image function to determine the dimensions of the file and its file type all in one pass. When you have to determine filetype based on extension, use PHP's pathinfo() function. It returns other information that might be useful later.

For the love of all that is good, will people please start using switch/case? There is a point when you gain enough experience in coding to know that you're going to be making comparisons to a bunch of things and that you'll have to handle each differently. Even if you only have to deal with one thing to start off (like a jpeg, for example) you still get that feeling that maybe this shouldn't be an if(), but a switch(). Given the option, use the switch(). There's not much worse than a bunch of ifelse(){} statements stacked thoughtlessly onto one another.

Let's also try returning some values on success or failure instead of die()ing. Sure, we might not use the return value. There might be nothing we can do if the thumbnail creation fails, but at least we can try to let someone know if they care. I often care.

PHP GD now supports GIF input and output, so there is no need to omit it. There are also a few tricky functions in PHP that let you apply matrix transformations to your images, so you can do things like sharpen the thumbnail before it's saved. I stole borrowed the code for sharpening directly from the online PHP manual.

Granted, this function to create thumbnails isn't perfect, but in my opinion, it's a big improvement over what came before, and it's still small enough to include as a clip of commonly used code.

Here is the function, with a decent amount of inline and phpdoc comments:

/** * Creates a resized image from a source image, saves resized image to file * @param string $src_filename Filename of the image to resize * @param string $dst_filename Filename of the resized image to output * @param integer $max_width Maximum width of resized image * @param integer $max_height Maximum height of resized image * @return boolean true on success, false on failure */ function createthumb( $src_filename, $dst_filename, $max_width, $max_height ) { // Get information about the image list($src_width, $src_height, $type, $attr) = getimagesize( $src_filename ); // Load the image based on filetype switch( $type ) { case IMAGETYPE_JPEG: $src_img = imagecreatefromjpeg( $src_filename ); break; case IMAGETYPE_PNG: $src_img = imagecreatefrompng( $src_filename ); break; case IMAGETYPE_GIF: $src_img = imagecreatefromgif( $src_filename ); break; default: return false; } // Did the image fail to load? if ( !$src_img ) { return false; } // Calculate the output size based on the original's aspect ratio if ( $src_width / $src_height > $max_width / $max_height ) { $thumb_w = $max_width; $thumb_h = $src_height * $max_width / $src_width; } else { $thumb_w = $src_width * $max_height / $src_height; $thumb_h = $max_height; } // Create the output image and copy to source to it $dst_img = ImageCreateTrueColor( $thumb_w, $thumb_h ); imagecopyresampled( $dst_img, $src_img, 0, 0, 0, 0, $thumb_w, $thumb_h, $src_width, $src_height ); /* Sharpen before save? $sharpenMatrix = array( array(-1, -1, -1), array(-1, 16, -1), array(-1, -1, -1) ); $divisor = 8; $offset = 0; imageconvolution( $dst_img, $sharpenMatrix, $divisor, $offset ); //*/ // Get information about the output image $path_info = pathinfo($dst_filename); // Setup default return info $return = true; // Load the image based on filetype switch ( strtolower( $path_info['extension'] ) ) { case 'jpg': case 'jpeg': imagejpeg( $dst_img, $dst_filename ); break; case 'png': imagepng( $dst_img, $dst_filename ); break; case 'gif': imagegif( $dst_img, $dst_filename ); break; default: $return = $false; } // Clean up memory imagedestroy( $dst_img ); imagedestroy( $src_img ); return $return; }