Hi, this is your host Michał Rudnicki short "stronger", and Ye are visiting my jagged border of life and work. Have a nice read, and please feel free to drop me a line or twelve.
Since this is a tri-lingual blog you may find it useful to filter it out by: english | polish | php-ish

Unobtrusive CSS icons

27 Jul 2010

There can't be anything more straightforward than displaying icons beside links/buttons, can it? After all it's just a CSS class for generic icon with background-image inline style. Or maybe separate CSS class for each icon? After all, there's only so many of them. Or even partial view that takes image, caption, and link as parameters and spits out complete piece of HTML?

None of the above. They all suck due to either inherent ugliness, annoing restrictions, or lack of simplicity. All fail programmers' ultimate criterion – <blink>elegance</blink>. But there is one that excels where others fall short.

Enter convention

<style type="text/css">
.icon { padding: 0 0 0 20px; background: transparent left center no-repeat }
</style>

<a href="#" class="icon i-add">Add</a>
<a href="#" class="icon i-edit">Edit</a>
<a href="#" class="icon i-delete">Delete</a>

A convention whereby all elements with class icon i-something are transformed so that i-something part becomes background-image: url(/icons/something.png).
Nothing can be easier with jQuery.

$(".icon").each(function (i, e) {
	var icon = e.className.replace(/.*i-/, '');
	e.style.backgroundImage = 'url(/icons/' + icon + '.png)';
});

Note: make sure your web server has no alias set for path /icons/ or you won't see any icons.

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

11 comments | permalink | trackback | rss

Agile database modelling

10 Jan 2010

Things change. New laws are being passed, new business rules are kicking in, new requirements emerge and old ones get phased out. To keep the planets moving software must play catch up and good developer can be easily spotted: their code is ready for changes pretty much all the time and the change won't be a painful one.

NoSQL

The überpopular term NoSQL is not the thing I'm about to propose here. Despite its enthusiastic uptake the established position in the industry is yet to come. Secondly, because there are absolutely outstanding tools, books, common practices, and other knowledge sources on relational databases, SQL language, and Object-Relational Mapping. There are RDBMSes that range from zero to megabucks, there is workforce on the market that range from interns to six-digits-salary wizards. Everywhere you look the web is living and breathing in relational databases.

But I'm getting off the point. To outline this picture quickly, let's put two items into the equation:

Granted the two forces opposing, cracks on the surface are inevitable. Sure, you can bend your neck and keep updating schema with every change. Or you could employ one of this big, complicated ORM engines and force it on all memebers of your team. Or, you can oppose all change and get replaced with someone competent. Finally, you can be smart and use in-schema serialised LOBs to provide required dynamic features. To illustrate what I mean, may I present real life example. I use PDO for database access, DAO for persistence handling, and JSON LOBs for dynamic containers. First, example without dynamic features:

CREATE TABLE orders (
	id		SERIAL PRIMARY KEY,
	ref_num		VARCHAR(60),
	created_on	TIMESTAMP NOT NULL DEFAULT current_timestamp,
	customer_id	INT
);
class Order {

	protected $columns = array('id', 'ref_num', 'created_on', 'customer_id');
	protected $data = array();
	protected $dao;

	public function __construct() {
		$this->dao = new PDODataAccessObject('orders', 'id', $this->columns);
	}

	public static function getById($id) {
		$o = new Order();
		$o->data = $o->dao->getByPrimaryKey($id);
		return $o;
	}

	public function __get($k) {
		return array_key_exists($k, $this->data) ? $this->data[$k] : null;
	}

	public function __set($k, $v) {
		$this->data[$k] = $v;
	}

	public function save() {
		$this->dao->save($this->data);
	}

}

This is minimal implementation of working persistent object. As you can possibly imagine class PDODataAccessObject is a relational storage, which was skipped as not being relevant for this example. You can retrieve Order from database by primary key simply typing $order = Order::getById(10). You can read echo $order->created_on and write $order->ref_num = 12345 object properties, that will be stored in database upon $order->save(). DAO will figure out whether to insert new row into db or update existing – but that's not relevant either. Let's enable class Order for dynamic schema.

CREATE TABLE orders (
	id		SERIAL PRIMARY KEY,
	ref_num		VARCHAR(60),
	created_on	TIMESTAMP NOT NULL DEFAULT current_timestamp,
	customer_id	INT,
	json		TEXT
);
Column "json" is a JSON container for dynamic properties.
class Order {

	protected $columns = array('id', 'ref_num', 'created_on', 'customer_id', 'json');
	protected $data = array();
	protected $json;
	protected $dao;

	public function __construct() {
		$this->dao = new PDODataAccessObject('orders', 'id', $this->columns);
	}

	public static function getById($id) {
		$o = new Order();
		$o->data = $o->dao->getByPrimaryKey($id);
		return $o;
	}

	public function __get($k) {
		if (in_array($k, $this->data)) {
			return array_key_exists($k, $this->data) ? $this->data[$k] : null;
		}
		if (is_null($this->json)) {
			$this->json = (array)json_decode($this->data['json'], true);
		}
		return array_key_exists($k, $this->json) ? $this->json[$k] : null;
	}

	public function __set($k, $v) {
		if (in_array($k, $this->data)) {
			$this->data[$k] = $v;
			return;
		}
		if (is_null($this->json)) {
			$this->json = (array)json_decode($this->data['json'], true);
		}
		$this->json[$k] = $v;
	}

	public function save() {
		$this->data['json'] = json_encode($this->json);
		$this->dao->save($this->data);
	}

}
Parts in bold were added to previous implementation.

Note the additional column json in table schema. This is where your dynamic properties go. Now, you are free to store any property you want without making any change to schema! Like, $order->if_not_delivered_please_call = "085123456"

No DBA would ever design something like this!

Yep, true. It is precisely because what most DBAs tend to care about is their golden temple of normalised tables, non-reduntant schemas, and optimal column sizes. It's the mental wank, the uplifting feeling of doing "Proper Engineering" that hardly ever contributes to anything but self esteem. Such DBAs would fear of a craftsman who treats their golden temple as a mean to an end and not an end itself, a developer who rather than sticking to "middleware" operates across and between layers where needed.

Dynamic schema extension as presented above has severe limitation of not being easy to report upon. You cannot retrieve records by columns stored in serialised LOB. It's just not suitable for that purpose. But serialised LOBs have this nice feature that once necessity arises, their content can be easily migrated to dedicated column. I.e. let's say that putting employee_id in Order object is becoming a common thing. Now, manager wants to know how do employees perform. Reporting on dynamic column is not possible, but at least, since information was stored in JSON, writing a five-liner that would extract property employee_id and store it into newly created column is a no-brainer. Now, DBA's been saved from heart attack.

However not an one-size-fits-all type of tool, dynamic schema can be a life saver in situations when it is unknown what sort of fields are worth storing into db. It's also perfect for rapid prototyping, where you don't really want to remodel your table columns endlessly and just get the shit running asap. Agile schema modelling can buy you significant amount of time before stabilising your code. Then, once you have pretty good idea on how is this all expected to work, you can start pulling dynamic properties into strict db columns.

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

10 comments | permalink | trackback | rss

PHP+SOAP+WSSE

24 Jun 2009

Pick any two and your life will remain nice and easy. Pick all and you will want to hurt yourself with random object in reach. PHP native class for handling SOAP calls can't do WS-Security. Here's how one can work around this problem.

class WSSESoapClient extends SoapClient {                                                                                           
	protected $wsseUser;
	protected $wssePassword;

    public function setWSSECredentials($user, $password) {
        $this->wsseUser = $user;
        $this->wssePassword = $password;
    }

    public function __doRequest($request, $location, $action, $version) {
        if (!$this->wsseUser or !$this->wssePassword) {
            return parent::__doRequest($request, $location, $action, $version);
        }

        // get SOAP message into DOM
        $dom = new DOMDocument();
        $dom->loadXML($request);
        $xp = new DOMXPath($dom);
        $xp->registerNamespace('SOAP-ENV', 'http://schemas.xmlsoap.org/soap/envelope/');

        // search for SOAP header, create one if not found
        $header = $xp->query('/SOAP-ENV:Envelope/SOAP-ENV:Header')->item(0);
        if (!$header) {
            $header = $dom->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'SOAP-ENV:Header');
            $envelope = $xp->query('/SOAP-ENV:Envelope')->item(0);
            $envelope->insertBefore($header, $xp->query('/SOAP-ENV:Envelope/SOAP-ENV:Body')->item(0));
        }

        // add WSSE header
        $usernameToken = $dom->createElementNS('http://schemas.xmlsoap.org/ws/2002/07/secext', 'wsse:UsernameToken');
        $username = $dom->createElementNS('http://schemas.xmlsoap.org/ws/2002/07/secext', 'wsse:Username', $this->wsseUser);
        $password = $dom->createElementNS('http://schemas.xmlsoap.org/ws/2002/07/secext', 'wsse:Password', $this->wssePassword);
        $usernameToken->appendChild($username);
        $usernameToken->appendChild($password);
        $header->appendChild($usernameToken);

        // perform SOAP call
        $request = $dom->saveXML();
        return parent::__doRequest($request, $location, $action, $version);
    }

} // class WSSESoapClient
Use method setWSSECredentials() to enclose WS-Security header in SOAP message.

Code above is a modification and extension of reply to Q#953639 on StackOverflow.com

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

5 comments | permalink | trackback | rss

FOWA Dublin 2009

11 Mar 2009

FOWA Dublin 2009

One of the biggest web events came down to Ireland last Friday. Future of Web Apps is a conference for web developers, designers, and entrepreneurs. Also, great opportunity to get together, discuss, and make new connections. I quickly hooked up with Rachel and Aoife and also bumped into lot of people I know (how come so many 'em?).

Me and Rach

One of the most striking talks was the one given by Robin Christopherson of AbilityNet, a charity organisation for accessibility aware web development. Robin himself being blind gave us a sample of his everyday computer use. Walking in his shoes was almost a nightmare to me – web sites constantly interrupting speech synthesis, missing or misleading ALTs, obtrusive JavaScript, and ever-preventing CAPTCHAs. You wouldn't want to go through it. On the other hand there are well written web sites. And to my great surprise Google Maps was one of them. Instead of simply presenting map a list of instructions is presented, like 5 minutes walk north from train station, 200 yards down the street. Also, video text was invaluable when watching video scenes without dialogues – here some fight scene was presented and we were told to close our eyes. I join Robin in his plea for accessible web applications. Plus 10% potential customers extra, now think about it!

Emma Persky gave us a speech on Domain Specific Languages (or should I rather say s/Languages/Frameworks/) built on top of Ruby. Interesting speech, but by far not enough technical. Which is kinda funny as vast majority of designers complained they didn't understand a word from it ;-) Surely I will google for more from Emma.

During the break Microsoft showcased their Surface concept computer. Surface is strange. It is definitely an impressive technology and I have deep respect to engineers responsible. The thing is it's just too weird in a way I believe is common to all Microsoft products – operating it requires so much attention that one can't just focus on getting the job done. Also, due to input method limitation developers are not targeted here.

Microsoft Surface 1 2 3 4

After the break Simon Willison of Django gave us deeply shocking insight in state of web apps security. Let me sum it up for you: WE'RE SCREWED. We're so seriously fucked that almost beyond repair. And if you think he was exaggerating, let me rephrase it: THERE IS NO SUCH THING AS WEB APPS SECURITY. MOSTLY. Aside of well known XSS, SQL-I, and CSRF attacks, he outlined new CSRF-Login and Clickjacking attack vectors. To solve the situation a radical steps must be taken. This may include complete protocols rewrite or emerging of new language/framework/stack.

Corsonified FOWAD2009

Finally, outstanding speech by David Hinemeier Hansson of Rails and 37Signals. He's given us some strong opinions on building successful web apps and running sustainable business. It was a series of bumper-sticker slides, many of which were contrary to popular beliefs. (Would you turn down an offer by world-class potential customer, say IBM?). It was a good closing talk and I learned a lot from it. Thus, Folksr (coming soon!) is undergoing series of major changes. To be completely honest David, you were second. My girlfriend gave me the very same advices a month ago, plus she's got me shiny new graphic design. Oh, and she's pretty. So you loose, buddy ;-)

Aftwerwards, we headed to Longstone Pub to have a chat and few pints, and oh, Aoife was hungry :-D

Food is good

Earlier during the conference Ryan Carson announced that FOWA is looking for sponsors to cover drinks in Dandelion pub. He was more than successful and we ended up boozing on Google's. It was great craic, no doubt about it, when we later hooked up with Dave and his mates. As if you don't know asking a barmaid for phone number is soooooooo last century! Now they ask for Twitter username, LOL. Stefano deserved to be given special credit for absolutely unrivalled joyfulness that night – man, I've no clue whatcha smokin but it must be something good. As if that was not enough we moved to some rather posh-looking place full of unrealistic creatures (no one you could call ordinary guy/gal) dancing to house music with live percussion background. Few pints later we called it a night. A good one.

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

add coment | permalink | trackback | rss

Object oriented Web-UI in PHP

28 Feb 2009

Are you familiar with one of these fancy rich-client GUI toolkits? GTK+, Qt, SWT, you name it. They provide nice bridge between what user and programmer see. One would notice that HTML is itself UI platform with all this links, form controls, and JavaScript events. I can't disagree on that, but there's definitely more that could be done to push it more toward high level API rather than low level mark-up to make writing applications easier.

The idea I'm going to present is founded on the elegance and object orientation of SWT I've had chance to work with ages ago. The main goal is to create convenient API to avoid manual HTML/JavaScript coding.

Start holistic

Instead of specing out list of requirements, I'll just write piece of code I'd love to work with – my idealistic way of doing things. This is somewhat holistic approach that may be more appropriate for religion rather than software engineering, but down the line we will get to details.

$login = new Input();
$login->setValue(@$_POST['login']);
$passwd = new PasswordInput();
$button = new SubmitButton();

$form = new Form();
$form->template = 'login-form.tpl';
$form->target = ...some-url...;
$form->add($login)->add($passwd)->add($button);

print($form);
Very basic Web-UI API example

So far so good. We have fairly understandable object oriented API, now it is time to define events. In SWT each listener requires action adapter. Such adapter is most often defined as anonymous subclassed object. Since in web environment most actions is basically described by URL, we don't need action adapter. At least not just yet.

$forgot = new Button();
$forgot->text = 'Forgot my password';
$forgot->addListener(new ClickListener(...some-url...));
$form->add($forgot);
Listener demo

Now, our fancy OO API supports handling events. onclick in this case, but virtually any other is possible. Now what about reading form fields? After user had typed in her name and clicked Forgot button the application should read that name and pass it in URL. Obviously you can't do it on server side, or can you?

$forgot->addListener(
	new ClickListener(
		...some-url... . '?login=' . $login->valueReader()
	)
);
Value reader can be used to read form fields on client-side.

Now what the heck is valueReader()? Think of it as of proxy object for querying form fields on client side. In reality it's not an object (although it could be one implementing __toString() method) it's just a piece of inline JavaScript that gets DOM element by id and returns value property. Since ClickListener generates onclick string it plays nicely with value reader.

So yeah, get by id. But what is the id? I don't know. It should be automatically generated, shouldn't it? Yes, that would help, however I'd rather generate it automaticaly only when not passed to view constructor. This way you can specify id by hand if you need to. But this id thing is a minor problem.

Now consider signup form with more events, listeners, and actions madness. Do you know this nice feature that as you type your login name it checks on the fly if it's available? Surely, we c4n haz t4ht too!

$login = new Input();
$login->addListener(
	new StopTypingListener(
		...some-url... . '?login=' . $login->valueReader()
	)
);

StopTypingListener is (allegedly) listener that triggers action only when user has stopped typing for certain amount of time. In this example it fires event for checking whether username is free or taken. But wait a minute, you can't just reload whole page when user is half way through the form! Obviously, this needs to be Ajax call and here's where you actually do need action adapter.

$login->addListener(
	new StopTypingListener(
		new Action(
			...some-url... . '?login=' . $login->valueReader(),
			...callback-name...
		)
	)
);
Ajax action adapter example

Tada. Done Ajax way. Now, it's very likely you have your head full of question marks. How is this all implemented?

And now something for reductionists

It is important to state that in above examples I rely heavily on PHP's ability to cast objects to strings. Doing so with __toString() method frees your code from litter otherwise caused by manual casting. Let's just track which object returns what when cast to string.

Fear, Uncertainty, and Doubt

Developers, mostly those of little exposure to real life in its whole brutality, tend to raise alarm that having such heavywight API is, uhm, heavy. I have only one answer to that: Like I care! Iron is cheap, programmers' time is not (sorry guys, this reads in Polish). So go and get more memory for your box, or don't and waste more time tinkering with low lever/lightweight API you like more.

So where can I get this from?

Recent activity on Stackoverflow.com brought back my faith in Tigermouse framework I started years ago. Just play with it for a while, see examples, download, have fun, delete or take over further development of the framework as I have no time to do it any more. Oh, please use SVN version of it, as the last official release is terribly outdated.

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

3 comments | permalink | trackback | rss

Just before you lanuch a PHP application

04 Feb 2009

Applications are buggy. Fact. To take professor Dijkstra's advice I won't call them bugs. I call them errors. Me, you, and other folks do err and these errors do sit in the software quietly ticking to go off when the circumstances arise. Improving software build process so the software is less fscked up does not come for free. In fact it's quite expensive, hence the popularity of Beta sign. Public beta phase is nothing more than employing consumers to do some free error hunting for you. The problem is however, that your targeted audience may just not bother doing your job.

Below you will find few tips I found in my last project – Blastbeat.tv.

Forget about users spontaneously reporting problems

Users do use beta software, but they mostly expect it to be of final version quality. Don't even bother placing "Report problem" link as floating-point number only slightly less than 100 per cent of users will actually use it. And even if somehow 50% of them did it's still merely half of all problems that go under the radar – not too good.

What you should do instead is to develop automated reporting utility. With PHP it is super easy. First, wrap whole application with try…catch statement. Once otherwise unhandled exception is thrown it gets sent via email. This way you get error reported immediately with complete stack trace included. No other user would give you that many details in their report.
Don't forget to redirect user to a friendly page apologizing for technical difficulties with link to 1. previous page, 2. home page. I can't stress enough importance of such links (I should probably blog about it some other day).

Other cheap trick is to redirect PHP errors output to a file. This way you catch all fatal errors, warnings and notices that are not normally reported via exceptions. Put these lines in .htaccess:

php_value error_log log/errors.txt
RewriteEngine On
RewriteRule log/* index.php [F]
Be sure to protect your logs with HTTP 403 status code.

I assume you are quite sane and write your programs with error_reporting set to E_ALL or E_ALL | E_STRICT.

"We are sorry, we were unable to recreate your problem"

Just before you go live make sure you can quickly deploy exact copy of live system on any other machine to recreate problems that occur on production box only. This may seem to you as unnecessary effort, but soon you will find it time-saver whenever new developer joins the team. My additional advice is to keep development environment as heterogeneous as possible. This will surface potential problems sooner than in one-OS, one-DB, one-PHP environment. It is also a very good idea to maintain test installation with db synchronized over night. Having exact copy of your live system will help you pin down errors and save your customer from hearing from you the above quotation.

Care about user experience yourself

As I mentioned earlier users don't bother reporting problems. This means that they also won't bother reporting their not-so-great experience with the site. Experience is something more subtle and elusive, and is not as easy to express as working/not-working situation. You will have to care about it before users turn away.

The most technical aspect of user experience is smoothness of navigation. Page load time is something you can measure using external tools like YSlow or internally just by comparing request and response time. You should also log slow database queries and use indices where appropriate. For MySQL just edit /etc/my.cnf file and add:

log-slow-queries=/var/log/mysql-slow-queries.log
long_query_time=1

Read your reports

Now you can focus on reading reports coming to your mailbox. Some of them will be pretty obvious and easy to fix, some can be caused by session expiry, some other by outdated web browsers. The ones caused by web crawlers are particularly interesting as they point to an action that should be probably available only for logged user. Fix them one by one and you'll do great. I managed to take amount of emails from ca. 70 per day down to zero in one man-month. What's really interesting is that I was as same confident that software is relatively bug-free before launch as I am now. The difference is that now I can prove it really is.

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

11 comments | permalink | trackback | rss

Persistent Objects Metadata

26 Jan 2009

If I was to employ some handy analogy I would say that metadata is to persistent object what interfaces are to object oriented programming. More or less. Persistent Objects Metadata lets you spread common functionality (as described by interface) across different schemas (classes). There is a variety of scenarios where you would like to use metadata, hence I tend to treat this technique as design pattern. (It is different one than Metadata Mapping by Martin Fowler). Areas in which I successfully used this pattern are: searching across several incompatible table schemas, logging access to various objects, building permission system for arbitrary objects. So you can see that this technique brings a layer of common functionality on top of set of incompatible types.

Before we begin there are prerequisites, as there is huge chasm between object-oriented and relational world. And a bridge is needed to map objects to records and the way around. It is important to stress that Persistence Metadata builds relations between objects without using standard relational integrity checks, therefore you cannot rely on restricting/cascading any more. Here is the core interface for dealing with metadata-attached objects:

interface IDistinctive {

	public function getDistinctiveId();

	public static function instantiate($id);

}
Class must implement this essential interface to use Persistence Metadata technique

Example scenario

One practical example I can show is how to implement logging system that will work for incompatible objects. Imagine set of classes representing persistent objects like Video, Photo, and Song. Assume that db schema is utterly incompatible between each of the three. Also assume that two common operations are required: 1. log plays or views, 2. increase plays/views count. Additional db table is needed for storing metadata information only.

CREATE TABLE videos (
	video_id SERIAL PRIMARY KEY,
	short_desc TEXT,
	long_desc TEXT,
	file TEXT,
	views_count INT
);
CREATE TABLE photos (
	id SERIAL PRIMARY KEY,
	name TEXT,
	tags TEXT,
	album_id INT,
	views INT
);
CREATE TABLE songs (
	sid SERIAL PRIMARY KEY,
	title TEXT,
	plays_count INT
);
CREATE TABLE access_log (
	distinctive_id INT,
	class_name TEXT,
	accessed_on DATETIME DEFAULT current_date,
	ip_addr VARCHAR(15)
);
Three incompatible table schemas and metadata storage table (in bold).
Not-null constraints and integrity checks skipped for better clarity.

Examining the last table might give you an idea how metadata records are bound with objects. It is by storing class name and distinctive identifier in database (think of it as of a composite pointer to object), so you can store/retrieve persistent object and its metadata independently. It is done via interface previously defined. See example implementation for video class.

class Video extends SomeFancyORMEntity implements IDistinctive {

	public function getDistinctiveId() {
		return $this->video_id;
	}

	public static function instantiate($id) {
		return Video::findById($id);
	}

}

And now logger implementation. All required information is provided by IDistinctive interface and handy get_class function. See this pseudocode example:

class Logger {

	public function getLastAccessTime(IDistinctive $o, $ip) {
		$cn = get_class(o);
		$id = $o->getDistinctiveId();
			SELECT max(accessed_on) AS last_access FROM access_log
			WHERE class_name = $cn AND distinctive_id = $id AND ip_addr = $ip
		return last_access
	}

	public function log(IDistinctive $o, $ip) {
		$cn = get_class(o);
		$id = $o->getDistinctiveId();
			INSERT INTO access_log (class_name, distinctive_id, ip_addr)
			VALUES ($cn, $id, $ip)
	}

}
Adding metadata to incompatible objects is now possible thanks to IDistinctive interface.

By providing class names and unique identifiers via IDistinctive interface it is now possible to add metadata for persistent objects of different classes and db schemas. In fact metadata can be stored in different database or server. Or, if you like technology at its extreme, persistent objects could be kept in XML. Not bad, eh?

Having working logger with ability to check last access we can easily detect multiple hits from the same IP and decide whether to increase views/plays count or not. (Just to mention: it is not the best idea to rely solely on IP address to detect false hits).

More goodies

Static log seems to be an append-only data store, but having historical record of all views/plays you can easily report, say last 10 multimedia files. Just improve Logger class slightly by adding this method:

	public function getLastItems($n) {
		foreach
			SELECT class_name, distinctive_id FROM access_log
			ORDER BY accessed_on DESC
			LIMIT $n
		$items[] = class_name::instantiate(distinctive_id);
		return $items;
	}

Disadvantages

Alert reader will immediately spot two problems here. One (minor) is PHP inability to use variable in static call. The other, much more serious one, is sequential instantiation of persistent objects which is terribly slow for large datasets. There are ugly ways to workaround these problems. To squash vars in static calls eval expression can be employed. It looks really ugly but at least does the job:

eval('$o = ' . $className . "::instantiate($distinctiveId)");
return $o;
Workaround for making static calls on parametrised class.

With a little bit of iterative sorcery sequential instantiation can be brought down to number of different classes in result set returned. This would equate to turning this:

SELECT * FROM videos WHERE video_id = 3653;
SELECT * FROM videos WHERE video_id = 824;
SELECT * FROM videos WHERE video_id = 5004;
SELECT * FROM videos WHERE video_id = 19077;
SELECT * FROM songs WHERE sid = 1829;
SELECT * FROM songs WHERE sid = 5141;
SELECT * FROM photos WHERE id = 8923;
SELECT * FROM photos WHERE id = 844;

…into that:

SELECT * FROM videos WHERE video_id IN (3653, 824, 5004, 19077);
SELECT * FROM songs WHERE sid IN (1829, 5141);
SELECT * FROM photos WHERE id IN (8923, 844);

…which is much better. To achieve this your ORM library must support arbitrary conditions, which is more than likely it does. Splitting and grouping identifiers by class names is done easily:

$groups = array();
foreach ($results as $record) {
	$cn = $record['class_name'];
	$id = $record['distinctive_id'];
	array_key_exists($cn, $groups)
		? $groups[$cn][] = $id
		: $groups[$cn] = array($id);
}

…and the last thing you need to take care is to restore original order in which records were returned, which I spare myself implementing in this place ;-)

Final remarks

As you can observe Persistent Object Metadata is a quick way of delivering common functionality to variety of incompatible persistent objects. Service class (like logger from example above) implements that functionality relying on metadata storage combined with link (composite pointer) to persistent object. All of the applications I mentioned at the beginning (logging, searching, access control) are present in my current project at work and they show their power as system grows and amount AND versatility of data increases. Permission system is particularly interesting for it maps one persistent object to another (source_id, source_class, target_id, target_class, access_type) and same-class looping can represent hierarchical structure (e.g. user 456 reports to user 234). Metadata introduce overhead in performance, but with a little bit of coding skills and common sense it can be overcome.

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

add coment | permalink | trackback | rss

Annotations based URL mapping in PHP

19 Nov 2008

I have recently played a bit with different URL mapping implementations. Basically, there are two approaches that are the most common. One is to map controller and method names directly from URL using forwardslash delimiter. Another is to provide global configuration file that stores controller mappings. Usually, the first method is used when no map file is present.

Having some time for experiments I created simple URL mapper parsing method annotations as mappings source. I blogged about annotations in PHP good while ago (sorry guys, it's in Polish) and I found this declarative technique powerful, yet elegant as it is not code intrusive at all. What does it look like then? See the following example.

class NewsCtrl extends Ctrl {

	/**
	 * @URL news/?
	 */
	public function view($id) { ... }

}

Admittedly, this kind of declaration is much cleaner and easier to read than XML one. Also, you see mapped URLs all the time, not only when working on map file. The question mark here is just a placeholder for controller method parameter. Once URL to this method is requested mapper will return associated annotation. Reading doc-comment block is relatively slow in PHP, so simple file based caching is recommended.

Surely, the example above is simplified beyond real life requirements. Decent mapping engine should at least provide support for

Multiple URLs/variable args count can be handled just by simply adding more annotations. As a side effect default parameters get supported.

	/**
	 * @URL news/edit/?
	 * @URL news/add
	 */
	public function edit($id = null) { ... }

It is pretty common to use default null parameter to indicate that new entry will be created rather than existing one edited.

Reverse mapping is much more tricky. It requires reading cache file and is done in two steps. First, pattern matching is performed to find mapped URL template, i.e. http://mysite.com/news/edit/25 should match pattern news/edit/?. Secondly, arguments must be extracted from URL based on placeholders (question marks), i.e. 25 is the argument to be passed to controller, and not "news" nor "edit" strings.

URL mapper implementation is quite awful for both mapping directions. In forward mapping, annotation extraction code is a mess and lack of native annotations reflection API is to blame. In reverse mapping two step procedure is in large part strings processing, therefore it simply cannot be pretty ;-)

There are some common pitfalls, though. It is easy to build two overlapping/ambiguous URLs. Look at what we've already built in above examples:

	/**
	 * @URL news/?
	 */
	public function view($id) { ... }

	/**
	 * @URL news/edit/?
	 * @URL news/add
	 */
	public function edit($id = null) { ... }

Now, passing URL like http://mysite.com/news/add reverse mapper may return URL template news/? which is perfectly valid as "news" is exact match and "add" matches placeholder sign (?). As a result string parameter "add" will be passed to method view. This clash can be solved by searching for exact match in first place, however it's still not totally safe and will fail badly when you really intended to pass string "add" to method view. I have no answer to this issue at the moment. The only thing I can suggest is to be super cautious while creating mappings.

Another problem is that when using multiple parameters their order must be preserved.

	/** @URL move/?/? */
	public function move($from, $to) { ... }

The first parameter here will always be tied to $from variable, and you can't really do much about it. Unless you want to extend reverse mapper so it can parse some kind of ?1, ?2, etc. which is quite an overkill if you ask me.

I deliberately refrained from publishing my mapper implementation, as it is thightly coupled with dispatcher and will differ significantly for each framework. And as I use custom framework it would probably be useless for you anyway. Instead, you will get implementation of annotation parser.

function getAnnotatedURL($class, $method, $argsCount) {
	$r = new ReflectionMethod($class, $method);
	$doc = $r->getDocComment();
	foreach (explode("\n", $doc) as $ln) {
		$c = explode(' ', trim($ln, " /*\n\t"));
		if (count($c) != 2 or $c[0] != '@URL') {
			// process only @URL annotations with 1 parameter
			continue;
		}
		if (substr_count($c[1], '?') == $argsCount) {
			$url = $c[1];
			break;
		}
	}
	return $url ? $url : null;
}

Pass class and method name, and parameters count and you'll be good to go.

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

3 comments | permalink | trackback | rss

On-demand CSS/Javascript loading

18 Jun 2008

The web prefers fewer larger files than lots of small ones. Fact. Overall performance is determined by two factors: throughput and latency. In these days throughput seems to be less and less a problem. Optical fibrers, gigabit switches, multicore CPUs, vast RAM and RAIDed storage space make throughput problem passing away. Latency on the other hand is not decreasing. It is the short period of time after request is sent and nothing happens. It is a milisecond value, but when interdependent requests cascade, total latency grows to a significant value. There are ways to work around it. Neither of them is perfect, but some are clearly better than others.

Keep files big. The problem is, that although large files are effective to transfer over network, they are ineffective to work on them. Hell, having one, big, solid CSS file can be a developer's nightmare. I have worked with stylesheets crafted by 3 mindcracked Java developers with tiny knowledge on styles, cool guys on the other hand. Many sections were written numerous times, definitions were overlaping and tracking down inheritance and cascading issues was a tedious job. Beyond some point this is no longer a solution when project grows.

Use linker. Before uploading to the server, files can be merged into single one. Additionally, some text-level optimisation can be done, such as \r removal or comments stripping. The good side of using linker is basically, that it does its job. The downside is that it sucks. You have to re-run linker every single time you modify Javascript or stylesheet. Another drawback is that when Firebug says "error in line 825" there is no way to map it back to before-linking state.

Use automated on-demand loading. This is my favourite. The idea is to keep CSS and Js in small files and transfer them in single request. To download all required files in single http connection, a proper request is needed. This is relatively easy part and can be done straight away:

<link rel="stylesheet" type="text/css" href="css.php?f=style1.css,style2.css,style3.css"/>
<script type="text/javascript" src="js.php?f=script1.js,script2.js,script3.js"></script>

You can probably imagine the implementation for css.php and js.php, just remember to add some important headers:
Content-type: text/javascript (or text/css)
Cache-control: public, max-age=3600
Pragma: public

Also, sanitize inclusion path and/or verify MIME type for requested files - you definitely do not want to publish your db.ini settings.

Now, to the harder part. First, how do I know which CSS/Js is required and second, do I have to manually include them every time I need them? The short answers are: 1. you don't and 2. no, which in greater length translates into: 1. it's not you who knows what file to include, it's the view object and 2. once view object is instantiated it automatically appends required files to <link> or <script> line.
As I am well known to be a OOP junkie, the solution uses nice wrapper for Smarty template engine and some PHP magic methods starting with double underscore sign.

class View {

	protected $properties = array();
	public $templateFile;

	public function __set($p, $v) {
		$this->properties[$p] = $v;
	}

	public function __get($p) {
		return $this->properties[$p];
	}

	protected function getSmarty() {
		$t = new Smarty(); // some further Smarty configuration is needed here
		foreach ($this->properties as $p => $v) {
			$t->assign($p, $v);
		}
		return $t;
	}

	public function __toString() {
		$t = $this->getSmarty();
		return $t->fetch($this->templateFile);
	}

}

What you see above is a very basic view implementation. Whatever property you set in this object, it will be reflected as template variable. When casting object to string __toString method will be called providing relevant string representation for that object (use PHP 5.2+ to avoid casting problems with . operator). See the example myTemplate.tpl and its use.

<div>
	Hello {$subject}! What a {$adjective} day we have!
</div>
$v = new View();
$v->templateFile = 'myTemplate.tpl';
$v->subject = 'Random User';
$v->adjective = 'beautiful';
echo $v;

Ok, this is rather obvious that we'll get Hello Random User! What a beautiful day we have! as a result. But the best is about to come. Views implemented this way can be easily nested. You can have index.tpl template that will become a backbone of your site. There is also initial support for CSS and Javascript autoinclusion in single request:

<html>
<head>
	<title>{$title}</title>
	<link rel="stylesheet" type="text/css" href="css.php?f={$css}"/>
	<script type="text/javascript" src="js.php?f={$js}"></script>
</head>
<body>
{$content}
</body>
</html>
$page = new View();
$page->templateFile = 'index.tpl';
$page->title = 'My lame page';

$v = new View();
$v->templateFile = 'myTemplate.tpl';
$v->subject = 'Random User';
$v->adjective = 'beautiful';

$page->content = $v;
echo $page;

This snippet is probably rendered in a browser the same way as the previous one, but at least we have proper HTML document (well, actually DOCTYPE is missing here ;-). For CSS/Js autoinclusion we need to add some index page specific logic. It is convenient to derive Index class from View.

class Index extends View {

	public $templateFile = 'index.tpl';
	public static $css = array();
	public static $js = array();

	public static function registerStyle($f) {
		in_array($f, self::$css) or self::$css[] = $f;
	}

	public static function registerScript($f) {
		in_array($f, self::$js) or self::$js[] = $f;
	}

	protected function getSmarty() {
		$t = parent::getSmarty();
		$t->assign('css', implode(',', self::$css));
		$t->assign('js', implode(',', self::$js));
		return $t;
	}

}

...and a specialised view that will require CSS and Javascript inclusion in constructor:

class MyStyledView extends View {

	public $templateFile = 'myTemplate.tpl';

	public function __construct() {
		Index::registerStyle('myStyle1.css');
		Index::registerStyle('myStyle2.css');
		Index::registerScript('myScript.js');
	}

}

Let's also rewrite the second example to make use of our new classes, Index and MyStyledView:

$page = new Index();
$page->title = 'My styled page';

$v = new MyStyledView();
$v->subject = 'Random User';
$v->adjective = 'beautiful';

$page->content = $v;
echo $page;

The result in pure HTML should be like (notice the lines in bold):

<html>
<head>
	<title>My styled page</title>
	<link rel="stylesheet" type="text/css" href="css.php?f=myStyle1.css,myStyle2.css"/>
	<script type="text/javascript" src="js.php?f=myScript.js"></script>
</head>
<body>
<div>
	Hello Random User! What a beautiful day we have!
</div>
</body>
</html>

Look at what you gain and what you loose with the on-demand approach presented.

Finally, there is also a simple solution for "error in line 825" problem I mentioned before. To map back result file line number to source file and its respective line number, just add comments before each file is merged and put global and local line number before every line in that file. Such comments are activated only by additional GET parameter and normally are not sent to client.

On the down side there is a little overhead when loading script or style URL for the first time - partially duplicated content is not cached yet. The other problem is that this technique does not work with Ajax, so you will have to write different loader for Ajax calls. However still, it is a good solution if you like to keep CSS and Javascript separated into logical blocks.

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

4 comments | permalink | trackback | rss

Gdzie jest apostrof na klawiaturze telefonu?

28 May 2008

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

add coment | permalink | trackback | rss

Nowy zasób ludzki w projekcie

21 Apr 2008

I jak tu dbać o atmosferę nieskrępowanej inwencji, gdy już na samym początku trzeba osobnikowi złamać osobowość?

Digg del.icio.us StumbleUpon Wykop Reddit Folksr

add coment | permalink | trackback | rss