Archive for the 'php' Category

JavaScript-style object literals in PHP

Sunday, March 20th, 2011

The object literal notation in JavaScript looks like:

var fido = {name: "Fido", barks: true};

or

var fido = {};
fido.name = "Fido";
fido.barks = true;

From assoc arrays to objects

In PHP you would call that an associative array.

$fido = array(
  'name' => "Fido",
  'barks' => true
);

And you can easily make it an object too:

$fido = (object)$fido;
echo gettype($fido); // "object"

Or if you want to start with a blank object and add stuff to it:

$fido = (object)array();

or

$fido = new StdClass();

and then

$fido->name = "Fido";
$fido->barks = true;

A little explanation maybe: objects in JavaScript are hashes, maps, whatever you decide to call them. Objects in PHP were an afterthought in the language and (at least initially) were not much more than "fancy arrays". Fancy associative arrays (hashes, maps, whatever you call them).

Objects in PHP need a class, but the new stdClass() lets you start quickly without the class {...} jazz. Same for casting an array (upgrading it in its fanciness) to an object with (object)array().

So far - so good. What about methods?

Methods anyone?

JavaScript doesn't care about properties versus methods. It's all members of an object (like elements of an assoc array). Only if a member happens to be a function, it's invokable.

fido.say = function () {
  if (this.barks) {
    return "Woof!";
  }
};
 
fido.say(); // "Woof!"

Turns out, since PHP 5.3 there are closures in PHP too. So you can do:

$fido->say = function() {
  if ($this->barks) {
    return "Woof";
  }
};

The difference is that $fido->say() won't work. Two reasons for that:

  1. say is not a method. It's a property. For PHP it matters. You can however assign the property say to a new variable $callme. This variable is now a closure object. As such you can invoke it:
    $callme = $fido->say;
    echo $callme();

    Note the $ in $callme().

  2. the above will also fail because $this is an weird context and doesn't point to the object $fido. But you can use $self and point it to the global object $fido.

So that's a little .... unpretty, but it works:

$fido = (object)array();
$fido->name = "Fido";
$fido->barks = true;
 
$fido->say = function() {
  $self =& $GLOBALS['fido'];
  if ($self->barks) {
    return "Woof";
  }
};
 
$callme = $fido->say;
echo $callme(); // "Woff!"

And a sprinkle of magic

We can make this prettier with the help of a little PHP magic. PHP has some magic methods going on and one of these is the __call() method. If you implement it in a class, then it will be invoked whenever someone tries to call a method that doesn't exist.

In our case $fido->say is not a method. So __call can intercept $fido->say() calls and invoke the $fido->say property as a closure object. Closures are callable and call_user_func() and call_user_func_array() work fine with them. So all in all we should make this work:

$fido = new JSObject();
$fido->name = "Fido";
$fido->barks = true;
 
$fido->say = function($self) {
  if ($self->barks) {
    return "Woof";
  }
};
 
echo $fido->say();

As you can see, very JavaScript-esque. Except that $this is $self and will always be the first argument passed to every method. The secret sauce to make this happen is the JSObject() class.

class JSObject {
  function __call($name, $args) {
    if (is_callable($this->$name)) {
      array_unshift($args, $this);
      return call_user_func_array($this->$name, $args);
    }
  }
}

Nice and easy. Namely:

  1. __call takes the name of the missing method and any arguments.
  2. It checks whether there's a callable property with the same name (a closure object property).
  3. It adds $this to the arguments list and calls the closure.

Yupee! Now you can haz moar class-less JS-like PHP objects :)

(Note that $this->$name is not a typo and should not be $this->name because it's a dynamic property name.)

And one more thing

If we add a constructor to JSObject, it can accept any properties at creation time. So you can be even closer to JavaScript and allow both creating an "empty" object and adding to it later, or creating an object and adding properties simultaneously.

The slightly modified JSObject:

class JSObject {
  function __construct($members = array()) {
    foreach ($members as $name => $value) {
      $this->$name = $value;
    }
  }
  function __call($name, $args) {
    if (is_callable($this->$name)) {
      array_unshift($args, $this);
      return call_user_func_array($this->$name, $args);
    }
  }
}

And example use:

$fido = new JSObject(array(
  'name' => "Fido",
  'barks'=> true,
  'say'  => function($self) {
    if ($self->barks) {
      return "Woof";
    }
  }
));
 
echo $fido->say(); // "Woff"

This is pretty close to what you can have in JavaScript (adding $ and ' even though we can do without them), only changing a few things like -> to . and => to :

$fido = {
  'name' : "Fido",
  'barks': true,
  'say'  : function() {
    if (this.barks) {
      return "Woof";
    }
  }
};
$fido.say(); // Woof

JS and PHP look like twins now don't they.

JS for PHP devs at confoo.ca

This was extracted from a talk I gave at the confoo.ca conference a week or so ago. Below are the slides:

JavaScript for PHP developers
View more presentations from Stoyan Stefanov

 

Automating HTTPWatch with PHP #3

Monday, March 7th, 2011

The first part is here, the second is here. This third post is more about PHP and COM, rather than HTTPWatch or monitoring web performance, so feel free to skip if the title mislead you :) Keep reading if you want to use and improve/update my HTTPWatch class in the future.

The problem

After running a HTTPWatch-ed browser via a script I wanted to have an easy way to dump all the data collected. Since the PHP-HTTPWatch bridge is via COM interface, all the objects returned by HTTPWatch are Variants and not ready for introspection with the usual PHP functions, like get_object_vars() for example.

The solution

Interestingly, turns out there exist a function called com_print_typeinfo(). You give it a COM object (works with those variants HTTPWatch gives you) and it returns you source code for a PHP class defining this COM object. So the hack here is to evaluate this source code with eval() (oh, the horror!) and then inspect it with get_class_vars(). Luckily there's no naming collision between the PHP built-in classes and those defied by HTTPWatch.

// the input is a class name 
// and an object of that class
$class = "Entry";
$object = $http->watch->Log->Entries->Item(0);
 
// buffer output
ob_start();
// print out class definition
// derived from an object
com_print_typeinfo($obj);
$typeinfo = ob_get_contents();
ob_end_clean();
 
// evaluate the generated PHP source
eval($typeinfo);
 
// get the properties
$properties = get_class_vars($class);
 
// Horay!

In order for this to work, as you can see, we need to know the class names and we need access to an example object of each class. This is where I needed to study the HTTPWatch API and find a suitable example page that will generate enough objects to derive the API from.

The free HTTPWatch version

The free version has restrictions where you don't have access to all properties. I wanted my class to be able to do the best job possible with or without the presence of restrictions. That's why I first load google.com which is unrestricted and then an image on my blog, which is restricted.

From the first page I derive the complete API (that I'm interested in) and then I use the derived API to study the second URL request. Accessing each property in a try-catch blows up when a property is restricted, so I write it to a second array of API properties $paidproperties.

Source: the end

In the end when you run the script dumpapi.php. It uses my HTTPWatch class to derive the API for the HTPWatch class itself. How meta! The result of the run you write to an API file and then this file is included by the class. Nice and clean after a messy hack :)

Run:

$ php dumpapi.php > HTTPWatchAPI.php

(This is a one-off operation, no need to run it at all if you don't need to change anything in the class)

Then, before you instantiate the constructor, you go:

// point to the API dump
HTTPWatch::$apipath = "HTTPWatchAPI.php";
 
// the usual
$http = new HTTPWatch();

The default name and location for that API dump is the same directory and file name HTTPWatchAPI.php, so you skip that first line unless you have a valid reason to store the API in a different location.

 

Automating HTTPWatch with PHP #2

Monday, March 7th, 2011

In part 1 I demonstrated how you can use PHP to script and automate HTTPWatch. And how you can get data back, either reading the API docs or using a quick HAR hack to get a lot of data in one go.

Now I want to share a little class I wrote to make all that a little easier.

The code is here on GitHub.

Basic usage

Open IE, navigate, close:

$http = new HTTPWatch();
$http->go('http://phpied.com/');
$http->done();

To do the same in Firefox, just pass "ff" to the constructor:

$http = new HTTPWatch('ff');
$http->go('http://phpied.com/');
$http->done();

The constructor accepts a second param with options, like empty cache, hide browser (ie only), etc, largely underused for the time being.

Handle to the HTTPWatch plugin

After $http = new HTTPWatch(); a watch property will be added to $http. This is the HTTPWatch instance which gives you access to all its APIs, so you can do e.g.

$summary = $http->watch->Log->Entries->Summary;

Data out

My main motivation behind this class, other than simpler api, has been to provide the ability to just dump all the data that HTTPWatch collects in a quick print_r(). That has been a challenge with the COM PHP bridge, but I found a hack around it. In any event, most of the HTTPWatch API I've exported to a second PHP file - the HTTPWatchAPI.php script. (This is an auto-generated file, created by another script, but let's leave that out for now.)

So after you've navigated to a page you have two convenient methods to grab a bunch of data from HTTPWatch. The first is:

$http->getSummary();

This gives a summary stats for the http observation session. The second is

$http->getEntries();

It gives you details about every HTTPWatch log entry - be it cached or an actual HTTP request.

Here's an example of what getSummary() can give you. Here's how this file was generated:

$http = new HTTPWatch();
$http->go("http://google.com");
print_r($http->getSummary());
$http->done();

And here's some output print_r()-ed from getEntries(). Here's the code that produced it:

$http = new HTTPWatch();
$http->go("http://google.com");
print_r($http->getEntries());
$http->done();

If you look carefully at the dump, you may notice something like [Stream] => [BYTESTREAM]. Most of the times you don't need the raw HTTP streams (gzipped, chunked, etc), but you can get them if you want by setting:

$http->skipStreams = false;

Here's the same google.com example, this time including the raw streams. And the code:

$http = new HTTPWatch();
$http->go("http://google.com");
$http->skipStreams = false;
print_r($http->getEntries());
$http->done();

Free vs. paid

One pain with HTTPWatch is that the free version has restrictions. The summary for example doesn't include TimingSummaries and WarningSummaries properties. The entry log has almost nothing - no headers or content or streams. My class handles that by giving you as much as it can. If you're using the free version, it will return the limited data for the restricted URLs, but still the full data for those URLs that HTTPWatch's demo version allows - the top Alexa sites.

So here's a dump of visiting http://givepngachance.com with my free HTTPWatch edition.

The data has restricted information about givepngachance.com URL but full data related to the embeded youtube.com resource.

The code:

$http = new HTTPWatch();
$http->go("http://givepngachance.com");
print_r($http->getEntries());
$http->done();

Again with the video

If you've read part 1, you've probably seen the video, but here's the link again (try the HD version). This is a screencapture of loading FF and IE using my new class. The code that produced it is:

$ie = new HTTPWatch();
$ie->go('http://google.com/');
$sum = $ie->getSummary();
$ff = new HTTPWatch('ff');
$ff->go('http://google.com/');
$sumff = $ff->getSummary();
 
echo "\nRun 1 ";
echo $ie->watch->Log->BrowserName, ' ';
echo $ie->watch->Log->BrowserVersion;
echo "\nSent: ", $sum['BytesSent'], "; Received: ", $sum['BytesReceived'];
 
echo "\nRun 2 ";
echo $ff->watch->Log->BrowserName, ' ';
echo $ff->watch->Log->BrowserVersion;
echo "\nSent: ", $sumff['BytesSent'], "; Received: ", $sumff['BytesReceived'];
 
$ie->done();
$ff->done();

As you can see I'm accessing the HTTPWatch plugin object ($http->watch) directly to get the browser version and name. I didn't think this was worth wrapping in a more convenient API the way I did with getSummary() and getEntries().

The result of this is:

$ php examples.php

Run 1 Internet Explorer 6.0.2900.5512
Sent: 7102; Received: 89188
Run 2 Firefox 3.5.6
Sent: 6388; Received: 166473 

If you're wondering why FF gets twice the bytes, it's because google.com in IE6 is very basic - no search-as-you-type so much less JavaScript and one less sprite.

That's all folks

Enjoy, fork and keep an eye on what's up with the HTTP traffic. What goes through the tubes is too important not to be observed and monitored :)

 

Automating HTTPWatch with PHP

Saturday, March 5th, 2011

HTTPWatch is a nice tool to inspect HTTP traffic in easy and convenient way and it works in both IE and FF now. Drawback - windows-only and paid. But the free version is good enough for many tasks.

HTTPWatch can be automated and scripted which is pretty cool for a number of monitoring-like tasks. Their site and help section lists C# and Ruby+Watir examples. So I was curious - what about PHP (and no Watir).

In general with PHP you can open/close/navigate IE using COM (whatever that is) which is nice, but you can't do that with Firefox as it doesn't expose a COM interface. But HTTPWatch fills the gap. K, let's see an example.

Prerequisites

OS: Windows
Install: IE, Firefox, HTTPWatch, php (command-line is fine, no need for Apache, MySQL, etc)

Getting started

Create a file, say C:\http.php, open command prompt and go:

cd \
C:\>php http.php

Now all that's left is to put something worth executing in http.php :)

Instantiating HTTPWatch

$controller = new COM("HttpWatch.Controller");
if(!method_exists($controller, 'IE')) {
  throw new Exception('failed to enable HTTPWatch');
}

Opening a new Firefox window:

$plugin = $controller->Firefox->New();

BTW, it's the same for IE:

$plugin = $controller->IE->New();

Disabling any filters (filters defined in HTTPWatch that is)

$plugin->Log->EnableFilter(false);

Clear HTTPWatch's log (the list of requests), clear the browser cache and start recording traffic:

// clear log and cache
$plugin->Clear();
$plugin->ClearCache();
 
// start
$plugin->Record();

Navigate to a URL and wait for it to complete - that means wait a bit after onload even

// browse
$plugin->GotoUrl('http://google.com');
$controller->Wait($plugin, -1);

Stop monitoring traffic and quit the browser:

$plugin->Stop();
$plugin->CloseBrowser();

This is nice, we opened the browser, visited a URL and closed. Now we can even get some meaningful data out of the whole experience.

$plugin->Log->Entries is an object that has a list of all requests. It also has a property Summary. So we can see how many bytes we sent and how many received as a result of this visit to google.com

$sum = $plugin->Log->Entries->Summary;
echo "in: {$sum->BytesReceived}, out: {$sum->BytesSent}";

Note: oh, you need to get your data before closing the browser, otherwise the Log object gets destroyed it seems

So the result:

C:\>php http.php
in: 89185, out: 7102

Yeah!

This may look like nothing, but is pretty impressive in an of itself. At least I know I was happy the first time it worked. Because, you see, any monitoring that doesn't use a real browser is kinda smelly, isn't that right? Plus this is awesome for performance tests, research and experiments. You can create page A and page B and go out for a walk. Meanwhile your script can load the pages 200 times in the two browsers (at least, because you can have FF+IE[678]), with empty and full cache... and you come back for the results! Tired of all the walking, not of hitting REFRESH.

Below you can see (HD!) video of a script that opens IE and FF, loads Google and then gives you the bytes in/out in the two browsers. This example uses a PHP class I created and will talk about later, but you can still see the idea.

A better experience in IE

One thing I don't like is that HTTPWatch won't let you control the browser very well. Two features I'm looking for: being able to see HTTPWatch's log while running (for testing) and then being able to completely hide the window (for "production"). Luckily IE let's you do that and HTTPWatch let's you "attach" an already running IE instance.

So. We open IE with its own COM interface:

$browser = new COM("InternetExplorer.Application");
if(!method_exists($browser, 'Navigate')) {
  throw new Exception('didn\'t create IE obj');
}
$browser->Visible = true;

As you can see - not very different. But there's Visibile which can be false if you so like. This way you can still work on something while tests are running in the background without windows popping up all the time.

Also if you open HTTPWatch manually and close the browser, then the next time (in your scripted runs) HTTPWatch will stay open and you can check what's up.

So, connecting HTTPWatch with the IE instance means instantiating HTTPWatch as before and passing the IE object to Attach() method (was New() before).

// watch this!
$controller = new COM("HttpWatch.Controller");
if(!method_exists($controller, 'IE')) {
  throw new Exception('failed to enable HTTPWatch');
}
 
// enable plugin
$plugin = $controller->IE->Attach($browser);

The rest is all the same.

There's more

The most interesting part is getting data back from HTTPWatch. Dunno about you, but I love just dumping whatever structure I have with print_r() or var_dump() and then deciding what I want from it and how to to go about getting it.

That doesn't happen here because these COM objects are Variants and you can't just dump'em. You have to read the API docs. That sucks. So I did a hack (next post) and also read the APIs ("Stoyan: reading the APIs so you don't have to!") to enable just dumping the httpwatch's log.

Meawhile...

HAR

HTTPWatch can write you a HAR file with the log. Not everything is in there, but it's still a lot and it's easy. HAR is JSON so you json_decode() it and voila - a log!

$filename = tempnam('/tmp', 'watchmenowimgoindown');
$plugin->Log->ExportHAR($filename);
$json = file_get_contents($filename);
 
print_r(json_decode($json));

If you're curious as to what that prints - here it is.

Want to see a HAR (from another run)? Here it is.

So here you go - much data can be extracted and dumped for inspection from the HAR output. For the full httpwatch data, there's the API.

(to be continued...)

 

Reducing the payload: compression, minification, 204s

Friday, December 11th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 11 This post is part of the 2009 performance advent calendar experiment. Stay tuned for the next articles.

After removing all the extra HTTP requests you possibly can from your waterfall, it's time to make sure that those that are left are as small as they can be. Not only this makes your pages load faster, but it also helps you save on the bandwidth bill. Your weapons for fighting overweight component include: compression and minification of text-based files such as scripts and styles, recompression of some downloadable files, and zero-body components. (A follow-up post will talk about optimizing images.)

Gzipping plain text components

Hands down the easiest and at the same time quite effective optimization - turning on gzipping for all plain text components. It's almost a crime if you don't do it. Doesn't "cost" any development time, just a simple flip of a switch in Apache configuration. And the results could be surprisingly pleasant.

When Bill Scott joined Netflix, he noticed that gzip is not on. So they turned it on. And here's the result - the day they enabled it, the outbound traffic pretty much dropped in half (slides)

Netflix traffic after turning on gzipping

Gzip FAQ

How much improvement can you expect from gzip?
On average - 70% reduction of the file size!
Any drawbacks?
Well, there's a certain cost associated with the server compressing the response and the browser uncompressing it, but it's negligible compared to the benefits you get
Any browser quirks?
Sure, IE6, of course. But only in IE6 service pack 1 and fixed for after that. You can boldly ignore this edge case, but if you're extra paranoid you can disable gzip for this user agent
How to tell if it's on?
Run YSlow/PageSpeed and they'll will warn you if it's not on. If you don't have any of those tools just look at the HTTP headers with any other tool, e.g. Firebug, webpagetest.org. You should see the header:

Content-Encoding: gzip

provided, of course, that your browser claimed it supports compression by sending the header:

Accept-Encoding: gzip, deflate
What types of components should you gzip?
All text components:

  • javascripts
  • css
  • plain text
  • html, xml, including any other XML-based format such as SVG, also IE's .htc
  • JSON responses from web service calls
  • anything that's not a binary file...

You should also gzip @font-files like EOT, TTF, OTF, with the exception of WOFF. Average about 40% to be won there with font files.

How-to turn on gzipping

Ideally you need control over the Apache configuration. If not full control, at least most hosting providers will offer you ability to tweak configuration via .htaccess. If your host doesn't, well, change the host.

So just add this to .htaccess:

AddOutputFilterByType DEFLATE text/html text/css text/plain text/xml application/javascript application/json

If you're on Apache before version 2 or your unfriendly host don't allow any access to configuration, not all is lost. You can make PHP do the gzipping for you. It's not ideal but the gzip benefits are so pronounced that it's worth the try. This article describes a number of different options for gzipping when dealing with uncooperative hosts.

Rezipping

As Billy Hoffman discovered, there's potential for file size reduction with common downloadable files, which are actually zip files in disguise. Such files include:

  • Newer MS Office documents - DOCX, XLSX, PPTX
  • Open Office documents - ODT, ODP, ODS
  • JARs (Java Applets, anyone?)
  • XPI Firefox extensions
  • XAP - Silverlight applications

These ZIP files in disguise are usually not compressed with the maximum compression. If you allow such downloads from your website, consider recompressing them beforehand with maximum compression.

There could be anywhere from 1 to 30% size reduction to be won, definitely worth the try, especially since you can do it all on the command line, as part of the build process, etc. (re)Compress once, save bandwidth and offer faster downloads every time ;)

15% uncompressed traffic

Tony Gentilcore of Google reported his findings that a significant chunk of their traffic is still sent uncompressed. Digging into it he realized there's a number of anti-virus software and firewalls that will mingle with the browser's Accept-Encoding header changing into the likes of:

Accept-Encoding: xxxx, deflxxx
Accept-Enxoding: gzip, deflate

Since this is an invalid header, the server will decide that the browser doesn't support gzip and send uncompressed response. And why would the retarded anti-virus program do it? Because it doesn't want to deal with decompression in order to examine the content. Probably not to slow down the experience? In doing so it actually hurts the user to a greater extend.

So compression is important, but unfortunately it's not always present. That's why minification helps - not only because compressing minified responses is even smaller, but because sometimes there is no compression despite your best efforts.

Minification

Minification means striping extra code from your programs that is not essential for execution. The code in question is comments, whitespace, etc from styles and scripts, but also renaming variables with shorter names, and various other optimizations.

This is best done with a tool, of course, and luckily there a number of tools to help.

Minifying JavaScript

Some of the tools to minify JavaScript include:

How much size reduction can you expect from minification? To answer that I ran jQuery 1.3.2. through all the tools mentioned above (using hosted versions) and compared the sizes before/after and with/without gzipping the result of minification.

The table below lists the results. All the % figures are % of the original, so smaller is better. 29% means the file was reduced to 29% of its original version, or a saving of 71%

File original size size, gzipped % of original gzip, % of original
original 120619 35088 100.00% 29.09%
closure-advanced 49638 17583 41.15% 14.58%
closure 55320 18657 45.86% 15.47%
jsmin 73690 21198 61.09% 17.57%
packer 39246 18659 32.54% 15.47%
shrinksafe 69516 22105 57.63% 18.33%
yui 57256 19677 47.47% 16.31%

As you can see gzipping alone gives you about 70% savings, minification alone cuts script sizes with more than half and both combined (minifying then gzipping) can make your scripts 85% leaner. Verdict: do it. The concrete tool you use probably doesn't really matter all that much, pick anything you're comfortable with to run before deployment (or best, automatically during a build process)

Minifying CSS

In addition to the usual stripping of comments and whitespaces, more advanced CSS minification could include for example:

// before
#mhm {padding: 0px 0px 0px 0px;}
// after
#mhm{padding:0}

// before
#ha{background: #ff00ff;}
// after 
#ha{background:#f0f}
//...

A CSS minifier is much less powerful than a JS minifier, it cannot rename properties or reorganize them, because the order matters and for example text-decoration:underline cannot get any shorter than that.

There's not a lot of CSS minifiers, but here's a few I tested:

  • YUI compressor - yes, the same YUI compressor that does JavaScript minification. I've actually ported the CSS minification part of it to JavaScript (it's in Java otherwise) some time ago. There's even an online form you can paste into to test. The CSS minifier is regular expression based
  • Minify is a PHP based JS/CSS minification utility started by Ryan Grove. The CSS minifier part is also with regular expressions, I have the feeling it's also based on YUICompressor, at least initially
  • CSSTidy - a parser and an optimizer written in PHP, but also with C version for desktop executable. There's also a hosted version. It's probably the most advanced optimizer in the list, being a parser it has a deeper understanding of the structure of the styleshets
  • HTML_CSS from PEAR - not exactly an optimizer but more of a general purpose library for creating and updating stylesheets server-side in PHP. It can be used as a minifier, by simply reading, then printing the parsed structure, which strips spaces and comments as a side effect.

Trying to get an average figure of the potential benefits, I ran these tools on all stylesheets from csszengarden.com, collected simply like:

<?php
$urlt = "http://csszengarden.com/%s/%s.css";
for ($i = 1; $i < 214; $i++) {
  $id = str_pad($i, 3, "0", STR_PAD_LEFT);
  $url = sprintf($urlt, $id, $id);
  file_put_contents("$id.css", file_get_contents($url));
}
?>

3 files gave a 404, so I ran the tools above on the rest 210 files. CSSTidy ran twice - once with its safest settings (which even keep comments in) and then with the most aggressive. The "safe" way to use CSSTidy is like so:

<?php
// dependencies, instance
include 'class.csstidy.php';
$css = new csstidy();
 
// options
$css->set_cfg('preserve_css',true);
$css->load_template('high_compression');
 
// parse
$css->parse($source_css_code);
 
// result
$min = $css->print->plain();
?>

The aggressive minification is the same only without setting the preserve_css option.

Running Minify is simple:

<?php
// dependencies, instance
require 'CSS.php';
$minifier = new Minify_CSS();
 
// minify in one shot
$min = $minifier->minify($source_css_string_or_url);

As for PEAR::HTML_CSS, since it's not a minifier, you only need to parse the input and print the output.

<?php
require 'HTML/CSS.php';
 
$options = array(
    'xhtml' => false,
    'tab' => 0,
    'oneline' => true,
    'groupsfirst' => false,
    'allowduplicates' => true,
);
 
$css = new HTML_CSS($options);
$css->parseFile($input_filename);
$css->toFile($output_filename);
// ... or alternatively if you want the result as a string
// $minified = $css->toString();

So I ran those tools on the CSSZenGarden 200+ files and the full table of results is here, below are just the averages:

  Original YUI Minify CSSTidy-safe CSSTidy-small PEAR
raw 100% 68.18% 68.66% 84.44% 63.29% 74.60%
gzipped 30.36% 19.89% 20.74% 28.36% 19.44% 20.20%

Again, the numbers are percentage of the original, so smaller is better. As you can see, on average gzip alone gives you 70% size reduction. The minification is not so successful as with JavaScript. Here even the best tool cannot reach 40% reduction (for JS it was usually over 50%). But nevertheless, gzip+minification on average gives you a reduction of 80% or more. Verdict: do it!

An important note here is that in CSS we deal with a lot of hacks. Since the browsers have parsing issues (which is what hacks often exploit), what about a poor minifier? How safe are the minifiers? Well, that's a subject for a separate study, but I know I can at least trust the YUICompressor, after all it's used by hundreds of Yahoo! developers daily and probably thousands non-Yahoos around the world. PEAR's HTML_CSS library also looks pretty safe because it has a simple parser that seems to tolerate all kinds of hacks. CSSTidy also claims to tolerate a lot of hacks, but given that the last version is two years old (maybe new hacks have surfaced meanwhile) and the fact that it's the most intelligent optimizer (knows about values, colors and so on) it should be approached with care.

204

Let's wrap up this lengthy posting with an honorable mention of the 204 No Content response (blogged before). It's the world's smallest componet, the one that has no body and a Content-Length of 0.

Often people use 1x1 GIFs for logging and tracking purposes and other types of requests that don't need a response. If you do this, you can return a 204 status code and no response body, only headers. Look no further that Google search results with your HTTP sniffer ON to see examples of 204 responses.

The way to send a 204 response from PHP is simply:

header("HTTP/1.0 204 No Content");

A 204 response saves just a little bit but, hey, every little bit helps.

And remember the mantra: every extra bit is a disservice to the user :)

Thank you for reading!

Stay tuned for the next article continuing the topic of reducing the component sizes as much as possible.

 

Collecting web data with a faster, free server

Tuesday, December 8th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 8 This article is part of the 2009 performance advent calendar experiment. This is also the first ever guest post to this blog.
Please welcome the world-famous Christian Heilmann! And stay tuned for the next articles.

Christian HeilmannChris Heilmann is a self confessed data junkie and worked for over 12 years as a professional web developer. Having published several books on JavaScript, Accessibility and web development using web services he right now works as a developer evangelist for the Yahoo Developer Network. He blogs at http://wait-till-i.com/, has all his talks and videos at http://icant.co.uk/ and can be found on Twitter as @codepo8.

 

RSS is a wonderful format to get information from all kind of different sources. It is dead easy to provide, has a predictable (albeit limited) format and is very easy to use. The problem of course is that with the amount of different feeds used in one page its performance goes down.

The reason is the classic HTTP request issue - the more you negotiate, find and pull the slower your page renders. Therefore you need to try to shorten the time the calls happen.

Say you want to pull the following five RSS feeds and display them:

  • http://code.flickr.com/blog/feed/rss/
  • http://feeds.delicious.com/v2/rss/codepo8?count=15
  • http://www.stevesouders.com/blog/feed/rss
  • http://www.yqlblog.net/blog/feed/
  • http://www.quirksmode.org/blog/index.xml

The least effective way of doing that is pulling and displaying them one after the other:

Retrieving five RSS feeds using curl takes about 11 seconds.

$oldtime = microtime(true);
$url = 'http://code.flickr.com/blog/feed/rss/';
$content[] = get($url);
$url = 'http://feeds.delicious.com/v2/rss/codepo8?count=15';
$content[] = get($url);
$url = 'http://www.stevesouders.com/blog/feed/rss';
$content[] = get($url);
$url = 'http://www.yqlblog.net/blog/feed/';
$content[] = get($url);
$url = 'http://www.quirksmode.org/blog/index.xml';
$content[] = get($url);
display($content);
echo '<p>Time spent: <strong>' . (microtime(true)-$oldtime) .'</strong></p>';
function get($url){
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  $output = curl_exec($ch);
  curl_close($ch);
  return $output;
}
function display($data){
  foreach($data as $d){
    $obj = simplexml_load_string($d);
    echo '<div><h2><a href="'.$obj->channel->link.'">'.
          $obj->channel->title.'</a></h2>';
    echo '<ul>';
    foreach($obj->channel->item as $i){
      echo '<li><a href="'.$i->link.'">'.$i->title.'</a></li>';
    }
    echo '</ul></div>';
  }
}

Using Stoyan's Multi Curl function you already realise quite an increase in speed.

Retrieving five RSS feeds with multicurl takes about 2.8 seconds.

$data = array(
  'http://code.flickr.com/blog/feed/rss/',
  'http://feeds.delicious.com/v2/rss/codepo8?count=15',
  'http://www.stevesouders.com/blog/feed/rss',
  'http://www.yqlblog.net/blog/feed/',
  'http://www.quirksmode.org/blog/index.xml'
 
);
$r = multiRequest($data);
display($r);
 
function multiRequest($data, $options = array()) {
  // array of curl handles
  $curly = array();
  // data to be returned
  $result = array();
  // multi handle
  $mh = curl_multi_init();
  // loop through $data and create curl handles
  // then add them to the multi-handle
  foreach ($data as $id => $d) {
    $curly[$id] = curl_init();
    $url = (is_array($d) && !empty($d['url'])) ? $d['url'] : $d;
    curl_setopt($curly[$id], CURLOPT_URL,            $url);
    curl_setopt($curly[$id], CURLOPT_HEADER,         0);
    curl_setopt($curly[$id], CURLOPT_RETURNTRANSFER, 1);
    // post?
    if (is_array($d)) {
      if (!empty($d['post'])) {
        curl_setopt($curly[$id], CURLOPT_POST,       1);
        curl_setopt($curly[$id], CURLOPT_POSTFIELDS, $d['post']);
      }
    }
    // extra options?
    if (!empty($options)) {
      curl_setopt_array($curly[$id], $options);
    }
    curl_multi_add_handle($mh, $curly[$id]);
  }
  // execute the handles
  $running = null;
  do {
    curl_multi_exec($mh, $running);
  } while($running > 0);
  // get content and remove handles
  foreach($curly as $id => $c) {
    $result[$id] = curl_multi_getcontent($c);
    curl_multi_remove_handle($mh, $c);
  }
  // all done
  curl_multi_close($mh);
  return $result;
}
function display($data){
  foreach($data as $d){
    $obj = simplexml_load_string($d);
    echo '<div><h2><a href="'.$obj->channel->link.'">'.
          $obj->channel->title.'</a></h2>';
    echo '<ul>';
    foreach($obj->channel->item as $i){
      echo '<li><a href="'.$i->link.'">'.$i->title.'</a></li>';
    }
    echo '</ul></div>';
  }
}

However, there are still two things that are annoying:

  • You pull far more data than you really need
  • You do all the request from your server

Yahoo Pipes has been used for that kind of task for quite a while, but the issue was that it is a visual interface and therefore hard to maintain. The server was also not the best performing out there.

The good news is that there is a new(er) kid on the block in Yahoo Land called YQL running on a massively fast server farm and with one purpose: making it easier to use web services, mix them and get only the data back that you want.

YQL in itself is a web service and you post queries to it that access other web services in a SQL style syntax. Normally you'd get RSS feeds using the RSS table, like so:

select * from rss where url="http://feeds.delicious.com/v2/rss/codepo8?count=15"

See the single RSS in the YQL console (you need a Yahoo account to log in). You can also see the single RSS retrieval output.

The issue with this is that it only retrieves the items of the RSS feed and not the title, which we need for the headings. Therefore we need to use the XML table:

select * from xml where url="http://feeds.delicious.com/v2/rss/codepo8?count=15"

See the single XML in the YQL console (you need a Yahoo account to log in). You can also see the single XML retrieval output.

This gives us the same data the normal cURL calls give us. The cool thing about YQL is though that you can filter the data you get back to the bare minimum. In our case, this means replacing the * with the title and the link of the feed and of the items:

select channel.title,channel.link,channel.item.title,channel.item.link 
  from xml 
  where url="http://feeds.delicious.com/v2/rss/codepo8?count=15"

See the filtered RSS in the YQL console (you need a Yahoo account to log in). You can also see the filtered RSS retrieval output.

In order to use this with all of our RSS feeds, we can use the in() command:

select channel.title,channel.link,channel.item.title,channel.item.link 
    from xml where url in(
      'http://code.flickr.com/blog/feed/rss/',
      'http://feeds.delicious.com/v2/rss/codepo8?count=15',
      'http://www.stevesouders.com/blog/feed/rss',
      'http://www.yqlblog.net/blog/feed/',
      'http://www.quirksmode.org/blog/index.xml'
    )

Check the aggregation in the console or the aggregation output.

This leaves all the hard work to the Yahoo Server farm. YQL pulls all the RSS feeds, adds one after the other and then gives it back to us as XML. We could simply use the generated URL from the console, but it is much more versatile to assemble the query in PHP:

$data = array(
  'http://code.flickr.com/blog/feed/rss/',
  'http://feeds.delicious.com/v2/rss/codepo8?count=15',
  'http://www.stevesouders.com/blog/feed/rss',
  'http://www.yqlblog.net/blog/feed/',
  'http://www.quirksmode.org/blog/index.xml'
);
$url ='http://query.yahooapis.com/v1/public/yql?q=';
$query = "select channel.title,channel.link,channel.item.title,channel.item.link from xml where url in('".implode("','",$data)."')";
$url.=urlencode($query).'&format=xml';
$content = get($url);
display($content);
function get($url){
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  $output = curl_exec($ch);
  curl_close($ch);
  return $output;
}
function display($data){
  $data = simplexml_load_string($data);
  $sets = $data->results->rss;
  $all = sizeof($sets);
  for($i=0;$i<$all;$i++){
    $r = $sets[$i];
    $title = $r->channel->title.'';
    if($title != $oldtitle){
      echo '<div><h2><a href="'.($r->channel->link.'').'">'.
           ($r->channel->title.'').'</a></h2><ul>';
    }
      echo '<li><a href="'.($r->channel->item->link.'').'">'.
           ($r->channel->item->title.'').'</a></li>';
    if($title != $sets[$i+1]->channel->title.''){
      echo '</ul></div>';
    }
    $oldtitle = $r->channel->title.'';
  };
}

As you can see the loop to display the different RSS feeds a bit clunky and we could use an open YQL table to move the whole conversion to a server-side JavaScript. However, as it is the performance of this way of retrieving the RSS feeds beats all the others hands-down already:

Retrieving five RSS feeds using YQL takes about half a second

You can try it yourself, get the demo code from GitHub and run it on your own server to see the magic of YQL.

 

Data URIs, MHTML and IE7/Win7/Vista blues

Monday, December 7th, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 7 This is the seventh in the series of performance articles as part of my 2009 performance advent calendar experiment. Stay tuned for the next articles.

UPDATE: While this post is an interesting study, the problem it solves turns out to be much simpler. The details are here. In resume: you need a closing separator and it all works fine in IE7/Vista/Win7

Let's start this post as a dialog:

- Data URIs are a way to embed the base64-encoded content of images inside HTML and CSS. Inlining images in markup or stylesheets helps you save precious HTTP requests. It's an alternative to CSS sprites.
- BUT! IE6 and IE7 don't support data URIs
- Yes, for them you can inline the images using MHTML
- BUT! Looks like IE7 on Vista and IE7 on Win7 have problems with MHTML
- [Sigh...] For those browser/OS combos there's a solution too.

I'll quickly go over what are data URIs and how MHTML addresses IE6 and IE7. If you're familiar with these, feel free to skip down to the Vista blues part.

Data URIs

Here's how to use data URIs in your pages:

  1. take an image:
    horoscopes icon image
  2. Use PHP to read this image's binary content and encode it using base64 encoding:
    $ php -r "echo base64_encode(file_get_contents('horoscopes.png'));"
    iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9...
    ...more scary stuff goes here...
    dajNGGzlDAAAAABJRU5ErkJggg==
  3. To use this image in CSS, paste the encoded image content in the stylesheet following this syntax:
    .horoscopes {
        background-image: url("data:image/png;base64,iVBOR...rkJggg==");
    }

    Note: the trailing == is part of the image content it's not part of the syntax

  4. Alternatively if you want to use this image in the HTML, you can go like:
    <img src="data:image/png;base64,iVBOR...rkJggg==" />

And that's about it for data URIs, it's quite simple actually.

Data URIs in the wild

To see real-life, high-traffic sites using data URIs look no further than your favorite search engines.

Yahoo! Search using data URI in CSS for a button background:

yahoo search screenshot

Google Search using data URI in an IMG tag for a video thumb:

Google search screenshot

Base64-encoded filesizes

The base64 encoding adds about 33% to the filesize. But, then you gzip the result, the compression brings the size back to the original. Plus or minus. Interestingly enough, sometimes after base64 and gzip, the result is smaller than the original. But don't count on that.

Theoretically also when you base64 encode a bunch of images and inline them in the same CSS or HTML, you'll have a larger string to compress, so increasing the chance of repetitions and compressing better. (Todo: test this assumption)

In any event, the benefit of reducing HTTP requests will likely greatly outweigh any fluctuation in the file size.

MHTML

IE8 supports data URIs, but earlier IEs do not. For them you can use MHTML (Multipart HTML, blogged previously here)

Continuing with the previous example, in order to display the horoscopes image in IE < 8 you can use a stylesheet like this:

/*
Content-Type: multipart/related; boundary="_MY_BOUNDARY_SEPARATOR"
 
--_MY_BOUNDARY_SEPARATOR
Content-Location:horoscopes
Content-Transfer-Encoding:base64
 
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9....U5ErkJggg==
*/
 
.horoscopes{*background-image:url(mhtml:http://example.org/styles.css!horoscopes);}

A few notes on this example:

  • the image content is base64-encoded just like before
  • if you need more images, use your chosen separator prepended with --, in this case --_MY_BOUNDARY_SEPARATOR
  • Content-Location:horoscopes is how you give a name to the image in order to use it later
  • then later in your stylesheet you can reference the name in the stylesheet using http://url/styles.css!horoscopes
  • you need absolute URLs in the mhtml:http://.... part (that's retarded, hope I'm mistaken. Didn't work for me with relative URLs)
  • you can use a single CSS file to support both old IE as well as new browsers, but you'll have to repeat the image stream twice, probably a better approach is to use browser specific stylesheets

For more examples of MHTML (which is also used for multipart email messages) check this and for a brilliant example of using one MHTML to embed images in HTML (not CSS), check Hedger's test page here (appears 404 at the moment of writing). For a tool to automate the dataURI-zation/MHTML-ization, be sure to check Nicholas Zakas' CSSEmbed

The problem with IE7 on Vista (and Win7)

So far we have data URIs and MHTML fallback. Turns out we're not done yet, because IE7 on Vista and Windows 7 fails with the MHTML example. Two points:

  • IE8 is the default in Windows 7, but it comes with several IE7 modes (either by default or turned on by users when their favorite pages break). Compatibility view, ie7 mode, ie8 in ie7 mode, blah-blah, it's all pretty confusing, but all modes with the exception of IE8 in proper IE8 standards don't work with the MHTML
  • I tested Windows 7 evaluation copy, but I have the bad, bad feeling that Windows 7 normal copy will be as broken when it comes to MHTML. I believe it has something to do with security, if anyone finds an article on MSDN, or anywhere, please share.

The solution to the Vista problem is to split the CSS shown above (the one that uses MHTML) in two: one which contains the encoded images (the commented part) and a second one which only has the normal CSS selectors part.

In other words have something like:

  1. mhtml.txt:
    Content-Type: multipart/related; boundary="_MY_BOUNDARY_SEPARATOR"
    
    --_MY_BOUNDARY_SEPARATOR
    Content-Location:horoscopes
    Content-Transfer-Encoding:base64
    
    iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAMAAADXqc3KAAADAFBMVEX///8mPnru9....U5ErkJggg==
    

    No need to use comments. Could be .txt, .css or anything you like. In my tests the content-type didn't matter - text/html, text/css, text/plain all worked

  2. styles.css:
    .horoscopes{*background-image:url(mhtml:http://example.org/mhtml.txt!horoscopes);}

    The normal CSS simply references the new MHTML file appending !identifier

  3. in your HTML you don't need to reference the MHTML document, you just point your link tag to styles.css as usual and styles.css contains the reference to the MHTML doc

Time to celebrate? Almost.

Another ugly bug surfaces - this procedure outlined above works only once. Once the MHTML.txt is cached, it stops working. That means repeating visits to the page or other pages using the same mhtml. The effect also means hovering on the same page. Say you have two images - one normal and one mouse over, both in the same MHTML. The normal works and the hover breaks, mouseout breaks too. Insane, isn't it?

There's a solution though - you should make the browser request the MHTML document every time. This is kind of backwards when it comes to performance optimization, where we want to cache everything. It's pretty unfortunate. The best you can do is avoid sending the MHTML every time, but only send a "not-modified" header. In order for this to work, the browser has to send If-Modified-Since or If-None-Match.

So you can send your MHTML file with an ETag (yes, ETags can help sometimes ;) ) Then the browser will send an If-None-Match and you can happily reply "304 Not Modified".

Sounds complicated? It sure is. And, remember, all this is only for IE7 (or any IE7 mode) on Vista and Windows 7. All other IEs on all other platforms work with the easy MHTML and IE8 works with data URIs.

Everything is a trade-off (see last night's post) and I can understand how you may think "That is one big tradeoff". There's always the option of serving normal images to the affected browser/os and just don't implement this optimization for them.

But it's good to know there is a solution.

DataSprites class

Let me offer this DataSprites PHP class I coded that takes care of all the scenarios. It takes a bunch of image filenames as input and produces:

  • a CSS containing data URIs for normal browsers (example output)
  • an MTHML CSS fallback for IE6,7 (example output)
  • a CSS that refers to an additional MHTML for IE7 on Vista, Win7 (example output - css and mhtml). The MHTML in this case is sent out with an ETag (derived from the input image filenames) and consecutive requests with the same ETag return 304 Not Modified

You can see a test page here and view the source code here. The directory listing is also available if you want to grab the images for testing.

The perfect use case for such an approach is when you want to combine background images on the fly. When you would normally use CSS sprites, but you want to produce them dynamically at run time (maybe because you have too many combinations?).

I've tested this in Safari, Firefox, Opera, IE6, IE7 and IE8 (in all compat modes) on Vista and Windows 7 evaluation copy. If you find the test page is not working for you, please let me know.

In this DataSprites class, I'm checking the problem OS looking for the strings "Windows NT 6" and "Windows NT 7" in the user agent string. My Win7 evaluation copy had "Windows NT 6.1" in the UA, so I may be a little forwards-aggressive with the check, but somehow I thought Win7 proper should have "Windows NT 7" in the UA string. And also that Win7 will suffer from the same issue as Vista and Windows 7 evaluation.

UPDATE: Added a hover example.

UPDATE 2: Recorded a screencast to demo the IE7/Vista experience

Thanks!

Thanks for reading! Now you know all about data URIs, MHTML, and maybe a little too much about IE7/Vista's challenges :) Ready to use data URIs and save some HTTP requests?

 

Performance tools

Wednesday, December 2nd, 2009

2010 update:
Lo, the Web Performance Advent Calendar hath moved

Dec 2 This is the second in the series of performance articles as part of my 2009 performance advent calendar experiment. Stay tuned for the next articles.

While theoretically you can speed up your site by just blindly following advice from this blog and other sources, it is much better to understand what's going on on the page and what you're dealing with. That's where the tools come in. Some tools give you insight about the network activities going on between the server and the browser (packet sniffers), some help you benchmark code execution on the client (profilers), some even give recommendations specific to performance improvements. You should aim at mastering as many of the tools as possible, because there's no single one that is The Tool. And that's not a bad thing, it's normal, because performance optimization is a multi-discipline activity touching a lot of different aspects of the the development process.

YSlow

(Full disclaimer: I helped with YSlow development and I was the architect of YSlow 2.0 so this fact may or may not have something to do with why YSlow is the first in the list. Plus, this is an unordered list.)

YSlow is an extension to Firebug, that:

  • inspects the DOM
  • hooks into the Net panel to listen to network activity and discover components that are not part of the DOM

Then the tool looks at the page and the components and tries to figure out how closely they match with Yahoo!'s performance best practices. Then you're given a list of findings with some advice and links for more information on how to improve.

PageSpeed

page speed screenshot

PageSpeed inspects the page and the components and checks it against conformance with Google's performance best practices.

In addition to that, PageSpeed has some quite advanced features like the Activity Panel which shows more detailed information on the page's, well, activity - such as the browser paint events, javascript parsing, execution, DNS lookups and so on. PageSpeed also tells you how many (and which) JavaScript functions were never called before onload so you can take some hints to lazy-load some of the JavaScript payload (after analysing, of course that the code is not needed in other browsers or other page use cases). Same with CSS - PageSpeed gives you a list of unused selectors so you can check whether you have leftovers from previous versions of the page.

MSFast

MSFast (from MySpace) inspects the page and helps answer many questions left open by YSlow and PageSpeed, such as:

  • What's going on with IE?
  • What's the memory and CPU footprint of your code?
  • How does the page looks like (as in screenshots) while it's being loaded so you can see what the people with slower connections have to experience

PageTest

AOL's PageTest is an IE plugin but also a hosted service which is a great way to show your boss/client performance details without inconveniencing them with challenging download and installation activities. PageTest gives you a waterfall view of the page load and a checklist of things to improve, plus some screenshots of interesting moments during load and even a video - an excellent view of how the page looks like in slow speeds. The hosted service can show you the dial-up experience in 4 different places in the world.

DynaTrace's Ajax Edition

dynatrace screenshot

Dynatrace Ajax is a very detailed lower level tool that not only shows the waterfall of components downloads but also the rendering time, CPU consumption, JavaScript parsing and execution. The screenshot above is just the tip of the iceberg of the tool's plethora of views and insights. It's highly recommended. (free, registrationware)

Packet sniffers

A good packet sniffer is indispensable for inspecting the HTTP traffic and figuring out how the page loads and what the request/responses and their headers look like. Here's a list of recommended sniffers, each with something good on top of the others:

  • IBM PageDetailer - a mature tool, somewhat simple which makes it a good start, requires registration to download
  • Fiddler - very powerful, extensible
  • HTTPWatch - (paid, but with a free version) integrates into the browser (both IE and FF) as a panel - very convenient to use. Extensible.
  • Microsoft Visual Round Trip analyser (and an excellent writeup)- goes even lower into the packet level of the requests and draws a different view of the waterfall, one that visualizes the TCP packets and the TCP slow start. It also gives recommendations for performance improvements. Built on top of NetMon (Microsoft Network Monitor) to present the data in a more useful and friendly way.
  • Charles proxy - the only non-windows tool in the list is an excellent packet sniffer for Mac

Time for a little rant - a more detailed view into the HTTP chunks is something that I think is important (will blog about it as part of this series) and missing from the current tools. HTTPWatch is the only tool that at least tells you the number of chunks and Fiddler prompts you to de-chunk HTTP responses when inspecting the body, which gives you a hint that the response was chunked. I hope to see more in that area, hopefully soon.

Thanks for reading

That concludes day 2 of the performance advent calendar. Hope you'll have fun installing and playing with new toys!

Did I miss a tool that should've been in the list? Let me know.

 

Installing PHP and Apache on Mac OSX – that was (pretty) easy

Saturday, March 7th, 2009

This posts is one of those "note to self" kinda posts. I just finished installing PHP and Apache on my Mac OS 10.5.6 and though I should document the experience should I (or you) need to do it again.

It could already be there

The default OS install came with goodies like ruby and php already there. So I could use php on the command line already. But it wasn't "hooked" to Apache for proper web development.

Also turns out Mac comes with some version of Apache, looks like it's disabled by default, but if it isn't, disable it from System Preferences / Sharing / Web Sharing.

Now let's start fresh with PHP5 and Apache 2, ignoring the PHP that's already there.

Mac ports prerequisite

Mac ports makes installing many software packages a breeze on the Mac. If you don't have it already, do set up Mac ports first.

Ready? Set? Go

  1. Log into the mac ports prompt:
    $ sudo port
    Password:
    MacPorts 1.700
    Entering interactive mode... ("help" for help, "quit" to quit)
    [Users/stoyan] >
  2. inside the Mac ports prompt simply do:
    [Users/stoyan] > install php5
  3. This is it! Give it a bit of time to pull out all dependencies, including Apache2. The rest is just some configuration...
  4. Start Apache and make it start when you power on the computer next time:
    $ sudo launchctl load -w /Library/LaunchDaemons/org.macports.apache2.plist
  5. Test that Apache runs fine by pointing your browser to http://localhost/. You should see a page that says "It works!"
  6. Tell Apache that PHP exists:
    $ sudo /opt/local/apache2/bin/apxs -a -e -n "php5" libphp5.so
    [activating module `php5' in /opt/local/apache2/conf/httpd.conf]
    
  7. Create a php.ini file (PHP configuration) by copying the default .ini
    sudo cp /opt/local/etc/php.ini-dist /opt/local/etc/php.ini
  8. If you need Apache stuff, like config files, error/access logs, htdocs... look around /opt/local/apache2. The web root for example is /opt/local/apache2/htdocs. I found it kinda convoluted so decided to move the web root to my home directory. So next two steps are optional.
  9. I'll store all web apps and pages and scripts in a directory called /localhost in my home directory:
    mkdir ~/localhost
  10. Time to edit the Apache config to tell it about the new location of the web root. Open httpd.conf
    sudo vi /opt/local/apache2/conf/httpd.conf

    Search for "DocumentRoot" and replace the current value /opt/local... with /Users/[your username]/localhost.
    The result would be like:

    DocumentRoot "/Users/stoyan/localhost"

    Then do the same further down in the config where it says <Directory..., so it should read:

    #
    # This should be changed to whatever you set DocumentRoot to.
    #
    <Directory "/Users/stoyan/localhost">
    
  11. Apache also need to know that files that end with .php will be handled by the php module, so edit httpd.conf (the same one from the previous step). Search for "php" and you'll find:
    LoadModule php5_module        modules/libphp5.so

    Add one more line so it looks like:

    LoadModule php5_module        modules/libphp5.so
    AddHandler application/x-httpd-php .php
  12. restart Apache (see below) and you're all done

start/stop/restart Apache

Here's how you start/stop/restart Apache:

  • $ sudo /opt/local/etc/LaunchDaemons/org.macports.apache2/apache2.wrapper start
  • $ sudo /opt/local/etc/LaunchDaemons/org.macports.apache2/apache2.wrapper stop
  • $ sudo /opt/local/etc/LaunchDaemons/org.macports.apache2/apache2.wrapper restart

Or to stop and disable starting up every time you power on:

$ sudo launchctl unload -w /Library/LaunchDaemons/org.macports.apache2.plist

Verify that all is good

You can check that all is good by creating a PHP info script.

echo "<?php phpinfo(); ?>" > ~/localhost/test.php

Now point your browser to http://localhost/test.php. It should give you the php info page (a looong page of PHP-related information)

 

image diff

Saturday, November 15th, 2008

Was having fun today with idiff.php - a PHP shell script to tell you if two images are visually different by comparing them pixel by pixel. If there's a difference, the script creates a third image - black background with the different pixels in green. Only after writing the script I found that there's an imagemagick command compare that does the same :)

the script

The idea is that two GD images are created from the input files, also a third GD image with black background to store the diff. Loop through all the pixels and compare them one by one. If one is different, write a green pixel at the same location of the diff image.

<?php
/**
 * Shell script to tell if two images are identical.
 * If not, a third image is written - black background with the different pixels painted green
 * Code partially inspired by and borrowed from http://pear.php.net/Image_Text test cases
 */
 
// check if there's enough input
if (empty($argv[1]) || empty($argv[2])) {
    echo 'gimme at least two image filenames, please.', "\n";
    echo 'e.g. "php idiff.php img1.png img2.png"';
    echo "\n", 'third filename is the image diff, optional, default is "diffy.png"';
    exit(1);
}
 
// create images
$i1 = @imagecreatefromstring(file_get_contents($argv[1]));
$i2 = @imagecreatefromstring(file_get_contents($argv[2]));
 
// check if we were given garbage
if (!$i1) {
    echo $argv[1] . ' is not a valid image';
    exit(1);
}
if (!$i2) {
    echo $argv[2] . ' is not a valid image';
    exit(1);
}
 
// dimensions of the first image
$sx1 = imagesx($i1);
$sy1 = imagesy($i1);
 
// compare dimensions
if ($sx1 !== imagesx($i2) || $sy1 !== imagesy($i2)) {
    echo "The images are not even the same size";
    exit(1);
}
 
// create a diff image
$diffi = imagecreatetruecolor($sx1, $sy1);
$green = imagecolorallocate($diffi, 0, 255, 0);
imagefill($diffi, 0, 0, imagecolorallocate($diffi, 0, 0, 0));
 
// increment this counter when encountering a pixel diff
$different_pixels = 0;
 
// loop x and y
for ($x = 0; $x < $sx1; $x++) {
    for ($y = 0; $y < $sy1; $y++) {
 
        $rgb1 = imagecolorat($i1, $x, $y);
        $pix1 = imagecolorsforindex($i1, $rgb1);
 
        $rgb2 = imagecolorat($i2, $x, $y);
        $pix2 = imagecolorsforindex($i2, $rgb2);
 
        if ($pix1 !== $pix2) { // different pixel
            // increment and paint in the diff image
            $different_pixels++;
            imagesetpixel($diffi, $x, $y, $green);
        }
 
    }
}
 
 
if (!$different_pixels) {
    echo "Image is the same";
    exit(0);
} else {
    if (empty($argv[3])) {
        $argv[3] = 'diffy.png'; // default result filename
    }
    imagepng($diffi, $argv[3]);
    $total = $sx1 * $sy1;
    echo "$different_pixels/$total different pixels, or ", number_format(100 * $different_pixels / $total, 2), '%';
    exit(1);
}
?>

usage

Type in the command line something like:

php idiff.php img1.png img2.png result.png

This command will compare img1.png with img2.png. If they are not the same dimensions, will exit with error code. If their pixels exactly the same will exit with success code 0 and print a success message. If the images are not the same, will write the file result.png.

If you omit result.png, the generated file will be called diffy.png

example

img1.png:

img2.png

running the command:

>php idiff.php img1.png img2.png
70/24600 different pixels, or 0.28%

the result diffy.png:

imagemagick

yep, so after I did this I found that imagemagick has a command called compare that does the same, only with more features, like the -fuzz option for example that allows you to specify how different should the pixels be, in order to consider them important.

the command:

>compare img1.png img2.png diffy-im.png

the diffy-im.png result

As you can see the different pixels are red and the actual image is a very pale background. Nice.

 

Best open-source PHP CMS 2008

Sunday, October 26th, 2008

It's that time of the year again. Time to cast my vote as part of the jury in Packt's open-source awards, category "PHP CMS".

(for my last year's rant, check the internet archive copy)

How to judge a CMS?

How to judge a CMS? Tough one. How do you judge any piece of software anyway?

Features? Usually any system or product lists the features in way meant to wow you, I mean a list of features always looks nice, it's a sales tool. And the features don't mean that much, actually some say, you should underfeature the competition. Of course, you can't take that to the extreme and build software that does nothing. You need features, but the best CMS doesn't mean the one with the most features, neither the one with less. You just need the right mix and "right" may wildly vary depending on the task. Are you building an e-commerce site, a library of articles, a 5-page business card-like site, on online community? No way to judge a system without an idea of what you want it to do for you. And no way what you want to do is what everyone else wants to do.

Quality? This is much easier. Does the code validate, YSlow score, accessibility, etc. Easy, even automated, but again, the system can be written beautifully but doesn't do the right thing or work the right way.

Documentation? A must-have, usually a problem with any software. Can be too specific in areas that don't matter and completely lacking where you really need it. Or outdated. It's also nice to have some books, sign that the system is relatively popular, mature and at the end of it, it's cozier to share the couch with a book then with an online text. Also API documentation, at least it's easier to generate and keep updated.

Community? Very important, you'll inevitably have questions when you start working with the system. It would be nice to have a friendly place where you can ask/answer questions. But how do you judge a community? The only way is to get involved and spend time. No way to do this when you're judging 5 systems in a matter of days.

Verdict

It's virtually impossible to judge any piece of software until you start using it. It may have the right features, but they might be unfriendly or virtually unusable. It may have documentation, but not the one you need, etc, etc. So the only way is to spend time with the software and try to make it work for you.

And since I've been meaning to create my personal stoyanstefanov.com, I thought it would be a good idea to kill two birds with one stone - cast my vote and have a working site at the end, powered by my personal CMS favorite.

Content

So I sit down and listed the features I need, nothing overly complicated, and also decided on some rough structure. Here's my feature list:

  • Blog. In English and also in Bulgarian, nice test for i18n capabilities
  • Photos
  • Syndicated Twitter feed, probably the CMS will not have it out of the box, but syndicating content and consuming web services is so common these days that the software better be capable of doing it easily or let me write an extension easily
  • Slides, with the help of Slideshare's API, same as above
  • Books - a page for each book maybe, reviews, image, etc
  • Projects - list of stuff I'm involved in
  • Publications - articles I wrote for other sites and magazines
  • Conferences - list the conferences I've spoken to, encourage people to invite me, bio blurb, hi-res photo
  • About
  • Contact
  • Social - links to facebook profile, linked in, etc
  • Sites - other sites of mine
  • Syndicated content - again, syndicate friendfeed or a Yahoo pipe that contains pretty much all my online activity, such as other blogs and even tiny things like last.fm love/hate ratings
  • Subscribe - option to subscribe to RSS feeds or even monthly email
  • custom 404

All these features I would combine in 5 top-level menu items: blog, news, writing, speaking, info.

Checklist

So now that I know what I want to build, I can go ahead and (attempt to) do it with each CMS while looking out for some pre-defined checks.

Info

URL:
Maturity: (since when the CMS exists)
Frequency of new releases: (is the project active)

Tasks

The best way to get a feeling of the software is by performing some tasks, the same tasks for all CMS

  1. Download
  2. Install
  3. setup friendly urls
  4. setup sections and content
  5. customize presentation
  6. customize homepage
  7. create an extension
  8. customize 404

QA

Test both the front-end and the back-end admin sections.

  • works without js
  • works without css
  • validates html
  • validates css
  • jslint
  • yslow grade
  • smush.it

Misc

  • Is there a WYSIWYG, people like those (I avoid them)
  • available extensions
  • available themes
  • Documentation
  • API docs
  • Community
  • Books

Whew!

Lotsa stuff, wish me luck with aaall that :) And I would be interested to hear any comments on the subject. Thanks for reading!

 

php|works and pyWorks

Wednesday, September 10th, 2008

I'll be speaking at the php|works and pyWorks conferences in Atlanta, Georgia in November, they'll be held together and there is a central track that has topics of interest to both phpiers and pythonistas, this is where I come in.

The conference(s) schedule is here and this is me: "Image optimization for the web"

 

My online footprint lately

Wednesday, July 23rd, 2008

This is a sort of a catch-up post for listing what I've been up to lately.

  • YUI Blog just published my first article, I'm so proud. It's about loading JavaScript in non-blocking fashion, because JavaScripts, they, you know, like, block downloads. Luckily, there's an easy fix - DOM includes, which I've previously discussed, discussed and discussed.
  • SitePoint published an update to my older article that introduces AJAX, ok, Ajax, by creating a command-line-like interface with PHP on the server side. The updated article features improved code, jQuery example, YUI example, JSON discussion and example. Check it out, bookmark and recommend to your friends that keep asking you "What's this AJAX (they are new, don't know it's now spelled "Ajax") thing? Do you know of a good article?"
  • YDN (developer.yahoo.com) published a video presentation of me and my lovely teammate Nicole Sullivan where we talk about some new and cool front-end performance techniques. So if you wandered how I look and are eager to hear my fabulous Balkan peninsula accent, give it a shot. The talk is called "After YSlow 'A'" and is targeted at those of you who have reached performance nirvana, but are still hungry for more. We talk about preloading components, post-loading, javascript, images, using flush() in PHP to send first byte early on and other fun stuff.
  • Last, not least, I decided to try and find some time to update my JavaScript patterns site. Unfortunately I got sidetracked (yep, I'm easily distracted by shiny objects) and played with a not-so-javascript pattern. The post I published (includes a pretty lame screencast! and) demonstrates how you can use animated background position to indicate loading progress.

Whew, c'est tout pout ce moment, expect a lot more now that the JavaScript book is out of the way. Ah, yep, if you feel like it, join me on Facebook, I created a JS book page.

 

www vs no-www and cookies

Tuesday, May 13th, 2008

One of Yahoo's performance rules says: Use cookie-free domains for static components. This is good because the server has no use for cookie information when serving a JPEG or another static component, so all this cookie information creates network traffic for no reason.

One of the implications of following the rule is related to the whole www vs no-www question. Basically you should always use www if you're planning to use any other sub-domains and you want them cookie-free. This is because you have no way to set a cookie only to the top-level domain. So for example you cannot write a cookie only to phpied.com. If you load component from img.phpied.com, the cookie from phpied.com will be sent again. In Firefox this doesn't seem to be the case, but in IE it is.

Here are two test scripts that demonstrate the behavior:

Load both of these pages and then reload them to see what cookies are sent. They both try to set cookies in all possible ways:

  1. omitting the domain name
  2. .domain.com
  3. domain.com
  4. www.domain.com

Here's the source code of the files:

nowww.php [test]

<?php
setcookie('no0', 'no www, no domain');
setcookie('no1', 'no www, .phpied.com', 0, '/', '.phpied.com');
setcookie('no2', 'no www, phpied.com', 0, '/', 'phpied.com');
setcookie('no3', 'no www, www.phpied.com', 0, '/', 'www.phpied.com');
 
echo '<pre>';
print_r($_COOKIE);
?>

yeswww.php [test]

<?php
setcookie('yes0', 'yes www, no domain');
setcookie('yes1', 'yes www, .phpied.com',    0, '/', '.phpied.com');
setcookie('yes2', 'yes www, phpied.com',     0, '/', 'phpied.com');
setcookie('yes3', 'yes www, www.phpied.com', 0, '/', 'www.phpied.com');
 
echo '<pre>';
print_r($_COOKIE);
?>

Loading the two pages twice shows how in IE, no0, no1, and no2 are all visible when using www as well as when not using it. In Firefox it's almost the same, only that no0 is not visible when using www.

As a take-home:

  • use www
  • write cookies to the appropriate domain level (e.g. don't write to *.domain.com)
 

strftime() in JavaScript

Friday, April 25th, 2008

Philip "Bluesmoon" Tellis has posted a tiny (under 3K minified) JavaScript library to format dates, inspired by PHP's strftime()

Examples:

d.strftime('%Y/%m/%d')
» en: 2008/04/25
» fr: 2008/04/25
» de: 2008/04/25

d.strftime('%A, %d %B')
» en: Friday, 25 April
» fr: Vendredi, 25 Avril
» de: Freitag, 25 April

There's also a demo to fiddle with.

I've previously had fun with kinda like the opposite: translating human-readable times into JS Date objects. Also here and here you have strtotime() look-alikes that take human-readable dates and turn them into Date objects.

 

Simultaneuos HTTP requests in PHP with cURL

Tuesday, February 19th, 2008

The basic idea of a Web 2.0-style "mashup" is that you consume data from several services, often from different providers and combine them in interesting ways. This means you often need to do more than one HTTP request to a service or services. In PHP if you use something like file_get_contents() this means all the requests will be synchronous: a new one is fired only after the previous has completed. If you need to make three HTTP requests and each call takes a second, your app is delayed at least three seconds.

Solution

An improvement of course is to cache responses as much as possible, but at one point or another you still need to make those requests.

Using the curl_multi* family of cURL functions you can make those requests simultaneously. This way your app is as slow as the slowest request, as opposed to the sum of all requests. And that's something.

A function

Here's a little function I coded that will allow you do multi requests.

<?php
 
function multiRequest($data, $options = array()) {
 
  // array of curl handles
  $curly = array();
  // data to be returned
  $result = array();
 
  // multi handle
  $mh = curl_multi_init();
 
  // loop through $data and create curl handles
  // then add them to the multi-handle
  foreach ($data as $id => $d) {
 
    $curly[$id] = curl_init();
 
    $url = (is_array($d) && !empty($d['url'])) ? $d['url'] : $d;
    curl_setopt($curly[$id], CURLOPT_URL,            $url);
    curl_setopt($curly[$id], CURLOPT_HEADER,         0);
    curl_setopt($curly[$id], CURLOPT_RETURNTRANSFER, 1);
 
    // post?
    if (is_array($d)) {
      if (!empty($d['post'])) {
        curl_setopt($curly[$id], CURLOPT_POST,       1);
        curl_setopt($curly[$id], CURLOPT_POSTFIELDS, $d['post']);
      }
    }
 
    // extra options?
    if (!empty($options)) {
      curl_setopt_array($curly[$id], $options);
    }
 
    curl_multi_add_handle($mh, $curly[$id]);
  }
 
  // execute the handles
  $running = null;
  do {
    curl_multi_exec($mh, $running);
  } while($running > 0);
 
 
  // get content and remove handles
  foreach($curly as $id => $c) {
    $result[$id] = curl_multi_getcontent($c);
    curl_multi_remove_handle($mh, $c);
  }
 
  // all done
  curl_multi_close($mh);
 
  return $result;
}
 
?>

Consuming

The function accepts an array of URLs to hit and optionally an array of cURL options if you need to pass any. The first array can be a simple indexed array or URLs or it can be an array of arrays where the second has a key named "url". If you use the second way and you also have a key called "post", the function will do a post request.

The function returns an array of responses as strings. The keys in the result array match the keys in the input.

A GET example

Let's say you want to use some Yahoo search web services (consult YDN) to create a music artist band-o-pedia kind of mashup. Here's how you can search audio, video and images at the same time:

<?php
 
$data = array(
  'http://search.yahooapis.com/VideoSearchService/V1/videoSearch?appid=YahooDemo&query=Pearl+Jam&output=json',
  'http://search.yahooapis.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&query=Pearl+Jam&output=json',
  'http://search.yahooapis.com/AudioSearchService/V1/artistSearch?appid=YahooDemo&artist=Pearl+Jam&output=json'
);
$r = multiRequest($data);
 
echo '<pre>';
print_r($r);
 
?>

This will print something like:

Array
(
    [0] => {"ResultSet":{"totalResultsAvailable":"633","totalResultsReturned":...
    [1] => {"ResultSet":{"totalResultsAvailable":"105342","totalResultsReturned":...
    [2] => {"ResultSet":{"totalResultsAvailable":10,"totalResultsReturned":...
)

A POST example

There's an interesting Yahoo search service called term extraction which analyses content. It accepts POST requests. Here's how to consume this service with the function above, making two simultaneous requests:

<?php
$data = array(array(),array());
 
$data[0]['url']  = 'http://search.yahooapis.com/ContentAnalysisService/V1/termExtraction';
$data[0]['post'] = array();
$data[0]['post']['appid']   = 'YahooDemo';
$data[0]['post']['output']  = 'php';
$data[0]['post']['context'] = 'Now I lay me down to sleep,
                               I pray the Lord my soul to keep;
                               And if I die before I wake,
                               I pray the Lord my soul to take.';
 
 
$data[1]['url']  = 'http://search.yahooapis.com/ContentAnalysisService/V1/termExtraction';
$data[1]['post'] = array();
$data[1]['post']['appid']   = 'YahooDemo';
$data[1]['post']['output']  = 'php';
$data[1]['post']['context'] = 'Now I lay me down to sleep,
                               I pray the funk will make me freak;
                               If I should die before I waked,
                               Allow me Lord to rock out naked.';
 
$r = multiRequest($data);
 
print_r($r);
?>

And the result:

Array
(
    [0] => a:1:{s:9:"ResultSet";a:1:{s:6:"Result";s:5:"sleep";}}
    [1] => a:1:{s:9:"ResultSet";a:1:{s:6:"Result";a:3:{i:0;s:5:"freak";i:1;s:5:"sleep";i:2;s:4:"funk";}}}
)
 

Fancy formatting

Friday, December 21st, 2007

Writing readable code means proper indentation. Usually you'd tab (or use 2 or 4 or 3 spaces) after every curly bracket. Something like this:

if (true) {
    // indent
    if (false) {
        // another indent
        // and some more
    }
}

Same goes when you have a bigger hash/object sort of thing:

var memememe = {
    name: 'Stoyan',
    family_name: 'Stefanov',
    blog: 'http://www.phpied.com',
    kids_count: 2,
    books_count: 3,
    occupation: 'programmer'
}

Sometimes I find myself going a little fancy and aligning all the values in the name/value pairs:

var memememe = {
    name:        'Stoyan',
    family_name: 'Stefanov',
    blog:        'http://www.phpied.com',
    kids_count:  2,
    books_count: 3,
    occupation:  'programmer'
}

But recently, inspired by Firebug's Net panel way of presenting header information, I tried aligning the keys to the right in addition to aligning the values to the left. So I ended up with something like this:

var memememe = {
          name: 'Stoyan',
   family_name: 'Stefanov',
          blog: 'http://www.phpied.com',
    kids_count: 2,
   books_count: 3,
    occupation: 'programmer'
}

Fancy, eh? I liked the way it looks. But then I thought that when writing maintainable code, anything fancy suggests uncommon, uncommon suggests that other team members won't be using it, so it means breaking the rule #1 of writing maintainable code: be predictable. (this also happens to be rule #1 of other common activities, such as driving on the highway and designing usable web sites)

This type of formatting is also not easy to type in an editor, so it will require a little more effort. Those two drawbacks are enough, I believe, to dismiss this idea. But I can't help myself liking the way the code looks. Here's a piece of PHP, which looks even better than javascript, because even more characters are centered.

<?php
$memememe = array(
          'name' => 'Stoyan',
   'family_name' => 'Stefanov',
          'blog' => 'http://www.phpied.com',
    'kids_count' => 2,
   'books_count' => 3,
    'occupation' => 'programmer'
);
?>

Ain't that cool?

 

Image fun with PHP – part 2

Tuesday, November 13th, 2007

This post is a demo of what the imagefilter() PHP function can do for you.

The Original

Nathalie

imagefilter() called with different filter constants

img_filter_brightness_5.png
Filter: IMG_FILTER_BRIGHTNESS
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_BRIGHTNESS5);
imagepng($image'img_filter_brightness_5.png');
imagedestroy($image);
?>

img_filter_brightness_50.png
Filter: IMG_FILTER_BRIGHTNESS
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_BRIGHTNESS50);
imagepng($image'img_filter_brightness_50.png');
imagedestroy($image);
?>

img_filter_brightness_100.png
Filter: IMG_FILTER_BRIGHTNESS
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_BRIGHTNESS100);
imagepng($image'img_filter_brightness_100.png');
imagedestroy($image);
?>

img_filter_grayscale.png
Filter: IMG_FILTER_GRAYSCALE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagepng($image'img_filter_grayscale.png');
imagedestroy($image);
?>

img_filter_contrast_5.png
Filter: IMG_FILTER_CONTRAST
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_CONTRAST5);
imagepng($image'img_filter_contrast_5.png');
imagedestroy($image);
?>

img_filter_contrast_-40.png
Filter: IMG_FILTER_CONTRAST
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_CONTRAST, -40);
imagepng($image'img_filter_contrast_-40.png');
imagedestroy($image);
?>

img_filter_contrast_50.png
Filter: IMG_FILTER_CONTRAST
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_CONTRAST50);
imagepng($image'img_filter_contrast_50.png');
imagedestroy($image);
?>

img_filter_colorize_100_0_0.png
Filter: IMG_FILTER_COLORIZE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_COLORIZE10000);
imagepng($image'img_filter_colorize_100_0_0.png');
imagedestroy($image);
?>

img_filter_colorize_0_100_0.png
Filter: IMG_FILTER_COLORIZE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_COLORIZE01000);
imagepng($image'img_filter_colorize_0_100_0.png');
imagedestroy($image);
?>

img_filter_colorize_0_0_100.png
Filter: IMG_FILTER_COLORIZE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_COLORIZE00100);
imagepng($image'img_filter_colorize_0_0_100.png');
imagedestroy($image);
?>

img_filter_colorize_100_100_-100.png
Filter: IMG_FILTER_COLORIZE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_COLORIZE100100, -100);
imagepng($image'img_filter_colorize_100_100_-100.png');
imagedestroy($image);
?>

img_filter_colorize_50_-50_50.png
Filter: IMG_FILTER_COLORIZE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_COLORIZE50, -5050);
imagepng($image'img_filter_colorize_50_-50_50.png');
imagedestroy($image);
?>

img_filter_edgedetect.png
Filter: IMG_FILTER_EDGEDETECT
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_EDGEDETECT);
imagepng($image'img_filter_edgedetect.png');
imagedestroy($image);
?>

img_filter_emboss.png
Filter: IMG_FILTER_EMBOSS
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_EMBOSS);
imagepng($image'img_filter_emboss.png');
imagedestroy($image);
?>

img_filter_gaussian_blur.png
Filter: IMG_FILTER_GAUSSIAN_BLUR
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GAUSSIAN_BLUR);
imagepng($image'img_filter_gaussian_blur.png');
imagedestroy($image);
?>

img_filter_selective_blur.png
Filter: IMG_FILTER_SELECTIVE_BLUR
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_SELECTIVE_BLUR);
imagepng($image'img_filter_selective_blur.png');
imagedestroy($image);
?>

img_filter_mean_removal.png
Filter: IMG_FILTER_MEAN_REMOVAL
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_MEAN_REMOVAL);
imagepng($image'img_filter_mean_removal.png');
imagedestroy($image);
?>

img_filter_smooth_5.png
Filter: IMG_FILTER_SMOOTH
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_SMOOTH5);
imagepng($image'img_filter_smooth_5.png');
imagedestroy($image);
?>

img_filter_smooth_50.png
Filter: IMG_FILTER_SMOOTH
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_SMOOTH50);
imagepng($image'img_filter_smooth_50.png');
imagedestroy($image);
?>

img_filter_negate.png
Filter: IMG_FILTER_NEGATE
Code to reproduce:
<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_NEGATE);
imagepng($image'img_filter_negate.png');
imagedestroy($image);
?>

A lazy way to do sepia

In order to do sepia, first you do grayscale, then colorize. Here are some experiments:

sepia_100_50_0.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE100500);
imagepng($image'sepia_100_50_0.png');
imagedestroy($image);
?>

sepia_100_70_50.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE1007050);
imagepng($image'sepia_100_70_50.png');
imagedestroy($image);
?>

sepia_90_60_30.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE906030);
imagepng($image'sepia_90_60_30.png');
imagedestroy($image);
?>

sepia_60_60_0.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE60600);
imagepng($image'sepia_60_60_0.png');
imagedestroy($image);
?>

sepia_90_90_0.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE90900);
imagepng($image'sepia_90_90_0.png');
imagedestroy($image);
?>

sepia_45_45_0.png

<?php
$image
imagecreatefrompng('nathalie.png');
imagefilter($imageIMG_FILTER_GRAYSCALE);
imagefilter($imageIMG_FILTER_COLORIZE45450);
imagepng($image'sepia_45_45_0.png');
imagedestroy($image);
?>

You can read about the right way to do sepia in Wikipedia, but I'd say that the fakes above look pretty good too.
You can try with different values for R, G and B, but hear this - the thing about sepia is that it's brownish-yellow.
Brown is something that has more red, little blue and the green exactly in between the red and the blue. So brown examples would be (200, 100, 0) or (150, 100, 50).
Yellow on the other hand is equal red and green and no blue, like (255, 255, 0). So if you want more brownish sepia, use the first pattern when calling the colorize filter.

About part one

Part one of the image fun is here, it contains code that more or less does the same things, but pixel by pixel, which is very slow, but also works in PHP4.
At the time of writing part one, imagefilter() funtion was probaly only in cvs, not part of the official PHP. imagefilter() is PHP5-only.

 

Maui, PHP Quebec, etc

Friday, November 9th, 2007

Aloha, I'm back from a family vacation in Maui, HI, feeling rejuvenated after having fun on some nice beaches with crystal water, seen an ex-volcano, sunsets, etc.

An email from PHP Quebec was in my inbox saying I'll be speaking at the 2008 PHP Conference in Montreal. Isn't that great?! I'll have a chance to (have some poutine) meet my friends in Montreal, enjoy (the poutine!) the snow I'm sure I'll be missing this year in LA. If you're in Montreal (do try poutine!), the conference is in March and features quite an impressive lineup of PHP speakers.

I had about 200 entries to go over in my RSS reader. I don't read a lot of blogs (not up-to-date list here) but those that I do are really interesting. That explains why after 2-3 hours of reading, I have only 150 out of 200 posts left to read. Anyway, here's a jewel I came across today - theonion.com (via)

 

JS/PHP string concatenation mistype

Thursday, October 25th, 2007

Another one from the "this is not a syntax error" department.

The front-end developer is a strange beast who has to jiggle to and fro and code in several languages literally at the same time - javascript, html, css, php or some other server side language, some SQL dialect... No wonder that sometimes we make silly mistakes like:

var $myarray;
var array = array();
$myarray = [];
foreach(var i in myarray)

Last night I just did a silly mistake like this. In JavaScript I used the PHP way of concatenating strings. Something like:

var user = 'Stoyan'; 
alert('hello ' . user);

This is obviously wrong, but the thing is that it's not a syntax error as one might expect. It alerts "undefined". Why is that?

Well, 'hello' is a string object. You can call methods and properties on it, like:

>>> 'hello'.toUpperCase()
"HELLO"
>>> 'hello'.length
5

And spaces don't matter...

>>> 'hello'     .   length
5
>>> 'hello'  . toUpperCase()
"HELLO"

So 'hello' . user is an attempt to access the "user" property of the string object 'hello'. This property doesn't exist, hence the "undefined" result.

Doing the opposite (using JavaScript-type concatenation in PHP) is also not an error:

$user = 'Stoyan';
echo 'Hello ' + $user; // prints 0
 

5 domain names – 5 CMS’s

Tuesday, October 2nd, 2007

As bloged before, my publisher organizes a "Best CMS" award thingie and I'm one of the judges in the "Best PHP CMS" category. It's time now to sit on my behind and judge, as scary as it sounds. I have to pick three from the five finalists in the PHP category in any order, so that makes it easier. It could also mean I just pick two I don't like and call it a day. But I want to be more responsible than that and actually experience the pain and the pleasure of building a web site with each CMS. Installing, customizing, add/edit/publishing content, upgrading ... the whole thing.

So I picked 5 domains of mine that already have something published and decided to convert them to one of the CMS's. I picked which CMS will be used for each domain by ordering the domains and the CMS's alphabetically, I think this is as random as any other way of doing it. Here's the list in the format: domain-cms-description.

  • bebetata.com - CMS Made Simple - will have photos of my kids, content in Bulgarian, "bebetata" means "the babies"
  • csssprites.com - Drupal - currently tool for creating CSS sprites, will expand to have articles, tips, etc.
  • hiliteme.com - e107 - tool for highlighting code, will add articles, different methods to highlight, etc.
  • jspatterns.com - Joomla! - best/worst practices when it comes to coding javascript
  • ragtimebg.com - PHP Fusion - this will be a site for my mother-in-law's business, in Bulgarian. "About us", "contact", that kind of stuff. Also articles on how to take care of your tires. The current version is at ragtime.hit.bg (don't laugh, this site is ooold, featuring client-side inclusion of header and footer, using document.write, fun stuff)

As you can see, different sites with different requirements, so hopefully I'll think of interesting scenarios to try and achieve with each CMS. Any requirement I implement in one CMS, I'll try in all others, so that things are as fair as possible.

I posted initial musings on the "best CMS" problem some time ago in this posting over at opensourcecommunity.org, where the charming Amy Stephen is a frequent contributor.

Wish me luck to have this initiative more than just a nice "wannado".

 

file_put_contents() for PHP4

Monday, September 10th, 2007

Simplified, but still...

<?php
if (!function_exists('file_put_contents')) {
    function file_put_contents($filename, $data) {
        $f = @fopen($filename, 'w');
        if (!$f) {
            return false;
        } else {
            $bytes = fwrite($f, $data);
            fclose($f);
            return $bytes;
        }
    }
}
?>
 

LA Web devs meetup at Yahoo

Monday, July 23rd, 2007

So there is this group of local LA web developers that meet every month or so to meet and discuss what's up. More about/join the group here.

This month Yahoo will be hosting the meetup in the Santa Monica office (my workplace), it's actually tomorrow, so if you're in LA, don't miss the opportunity for beer, pizza and meeting fellow web devs. RSVP here.

On Yahoo's side, Jim Bumgardner, a.k.a. krazydad will be demoing the Facebook app he did that allows you to find music videos and discover artists similar to the ones you like. The app, Jim talking about it, Yahoo Developers Network posting.

Sounds like it would be fun, and also a chance for a local web dev to see what Yahoo's office looks like, meet some of the people that work here, and in a way to try-before-you-apply :D

 

CMS award nominations open

Monday, July 16th, 2007

Nominate your favorite open-source CMS before August 31st. (Yours truly is one of the judges in the PHP category.)

 

PHP/Javascript dev tools for TextPad

Monday, July 16th, 2007

Here are some convenient tools I've added to my TextPad editor, hope you'll like 'em.

TextPad tools

Stuff can easily be added to TextPad's Tools menu, like I did, shown on the screenshot.
textpad-tools.png

In order to do so, you go Configure -> Preferences. Then select Tools on the tree to the left, then Add. You can add a DOS command, an application or a help file (.hlp or .chm)

The first three tools on the picture above come out-of-the-box, the other 4 I've added myself. Let me show you what I did.

Tool #1 - PHP lint (a.k.a. syntax check)

So I'm editing a PHP file and I want to syntax check it from the editor. Good. PHP on the command line comes with the -l option (this is lowercase L) which does just that. For example if you run this from your command prompt, it will check the file test.php for syntax errors:
C:\php> php -l test.php

So for tool #1 I just did - Configure-Preferences-Tools-Add-DOS command, then typed:
php –l $File

In Textpad apparently $File refers to the current file being edited. So now I can edit a file, press CTRL+4 and syntax check the file. Neat.

Tool #2 - PHP help

This is exactly the same idea as the previous tool. I use PHP command line option --rf which gives you help information. For example try getting help with the str_replace() function:

C:\php>php --rf str_replace

The result is

Function [ <internal> public function str_replace ] {

  - Parameters [4] {
    Parameter #0 [ <required> $search ]
    Parameter #1 [ <required> $replace ]
    Parameter #2 [ <required> $subject ]
    Parameter #3 [ <optional> &$replace_count ]
  }
}

Adding this functionality to textpad is very similar to tool #1, only this time the command is:

php --rf $SelWord

$SelWord is the currently selected word in textpad (just placing the cursor somewhere in the word is enough)

Tool #3 - PHP Manual

Sometimes the help above is not enough and you want to hit the manual on php.net. Here's the next tool. You go:
Configure-Preferences-Tools-Add-Program and you find your firefox.exe, like
C:\Program Files\Mozilla Firefox\firefox.exe

Now, in order to edit a tool you created in TextPad, you need to expand the Tools node of the Preferences tree and click the tool you need, as shown on the screenshot:

In this screen, you need to type this in the Parameters field:

http://php.net/$SelWord

Tool #4 - JS Lint

JSLint is a tool for checking JavaScript code, it also can be run on the command line in Windows, using cscript. So if your jslint.js is in C:\, you can have tool #4 another DOS command:

cscript C:\jslint.js <$File

Hope you like it

Or maybe add these simple tools to your text editor of choice.

Last thing

One little thing I didn't mention - it's a bit of a challenge to figure out how to rename a tool once created. Basically on the list of tools (next to the Add button), just click, right click, double-click or simultaneously click left and right mouse buttons. One of these will work eventually :)