Monday 31 March 2008

PHP_CodeSniffer and JavaScript Lint

The new JavaScript tokenizer in PHP_CodeSniffer allows you to write your own custom JS sniffs, but JavaScript Lint already has a set of generic tests that can be applied to your code. Thanks to the JavaScript Lint command line tool, jsl, you can now include JavaScript Lint warnings and errors in your coding standards.

I've just committed the JavaScriptLint sniff into the Squiz standard under a new Debug category. The sniff requires you to have jsl installed on the same machine as PHP_CodeSniffer and you need to tell PHP_CodeSniffer where to find it.

$ phpcs --config-set jsl_path /path/to/jsl
$ phpcs --standard=squiz /path/to/file.js
JavaScript Lint checks for a number of common errors, including missing semi-colons and unused code. Like the Zend CodeAnalyzer sniff, not all errors and warnings need fixing, so all messages reported by jsl are warnings within PHP_CodeSniffer and can be hidden easily.

Thursday 20 March 2008

Obscure software bugs always come in pairs

Well, for me anyway, and in MySource Matrix in particular. I don't know what it is, but obscure bugs get reported to me and shortly after I have a fix, I will be fixing the bug on one or two other systems. Yet this bug may have been in the software for years and may be extremely hard to replicate. This has been happening more and more; I think it has been three or four times in the past 12 months, which is just strange.

The strangest case is a bug in a trigger condition that was caused by the way PHP converts strings to numbers. I was so surprised we had never previously encountered the problem that I wrote a blog post on the issue. The night I wrote that post I also committed a fix to MySource Matrix. The very next day I was asked to diagnose a strange problem on another client's system with triggers. The fix I committed the night before also fixed this client's problem.

The latest case reared its head again just today. About a month ago we diagnosed and fixed a strange problem that was caused by IP addresses changing and logging a user out of MySource Matrix during the multiple requests made on the frontend (HMTL, CSS, JS etc). The session is destroyed if you get logged out, but the code that loaded a serialized class didn't first include the class file. We fixed that bug. The next week I diagnosed the same bug on another client's system. Last week I received a support ticket for the same issue. And just today I diagnosed the same problem on a system undergoing implementation. The strange thing is that we have always had systems where users have their IP changing and yet we've never seen this error.

I don't get this so much with PHP_CodeSniffer, although I do tend to fix bugs in CVS only to have someone report it via the PEAR bug tracker. They are not as common or obscure though. Perhaps the size of the project directly relates to the incidence of these sort of bugs.

I think everyone hears that these sort of things happen, and not just with software bugs, but it is still amazing to see how often it occurs.

Monday 10 March 2008

Rating content and calculating average ratings in MySource Matrix

A new feature was released in version 3.16.6 of MySource Matrix that allows you to calculate an average rating for content based on the comments and ratings users have made. The average rating is calculated as comments are created (or approved, it's up to you) and then stored in a metadata field. This field can then be used to sort pages based on their average rating.

Adding this feature to an existing comment system is fairly easy, but I'll just run through the basics of building a comment system first.

I've built a basic site with two standard pages. Both pages have a paint layout applied that nests in an asset builder and an asset listing that are used to create new comments and list existing ones. The general idea here is that every page will have a form that users can use to create new comments and apply a rating. Under the form will be a listing that prints the current comments and the ratings users applied to them.


The home page with a couple of comments and ratings

Asset Builder

The asset builder is configured to create comment assets. In my case, I have made them Live by default, but you could choose to create them as Under Construction so you can moderate comments. On the Create Locations screen, I have configured an Additional Create Location to create the comment asset under the current asset. I've made the links TYPE_2 so they are not shown in menus.


The bottom of the Create Locations screen, showing the additional create location


The create form for comments has been customised (bottom of the asset builder's Details screen) so that the rating can be added when the comment is created. I've also renamed Title to Subject.


The contents of the Comment Create Form Layout asset

Asset Listing

The asset listing is configured to list comment assets. The root node is set as the site asset, but this will be replaced dynamically by the current asset. Direct links only is set to Yes and there is a dynamic parameter set to replace the root node with the current asset's ID.

I've customised the No Results bodycopy to print a message encouraging users to post a comment and I've printed the rating as both a percentage and as a star rating in the default type format.


The contents of the Default Format bodycopy

Paint Layout

I've nested the asset builder and the asset listing in the default type format. Be sure to nest the asset builder first otherwise new comments will not appear until the next page refresh. This is obviously not a problem if you are moderating comments as they will not appear until they are approved.


The contents of the Default Format bodycopy

Adding Comments

Once the paint layout is applied, users are presented with the following form:


The home page before any comments have been added


Once they have added a few comments, we can start to work out the average rating. The image on the left shows two comment assets under the home page. Both are TYPE_2 linked so they don't appear in menus. By linking comments directly under each page, we can easily list them using a dynamic root node. If you are moderating comments, you may find it easier to create an asset listing of all Under Construction comments under the site with a link to their Simple Edit interface rather than manually looking for comments under pages.

Calculating Average Ratings

Now that we have our standard commenting system created, we can implement the new average rating functionality. All we do is apply a metadata schema to the assets that are rated and ensure there is a Text field in the schema in which to store the average rating. Then we create a trigger that sets the average rating when a new comment is added.

The metadata schema I have created is shown below.


This metadata schema contains a single section and Text field

Make sure you set a default value (e.g., zero) so that unrated content gets a default rating.

The trigger I have created is shown below. Remember to enable triggers on the Trigger Manager Details screen.


The trigger to calculate average ratings


Once this trigger is enabled, I added a new comment to the page to force the trigger to run. I now have three comments with three different ratings; 20%, 40% and 80%. Once the trigger has fired, the value 46 has been entered into the Rating metadata field for my page. Note that the rating has been rounded down so that an asset with a rating of 99.6% is not rated at 100%.


The metadata screen of the home page


Now I can configure an asset listing to show the most popular pages in my site. This asset listing is configured to list standard pages under my site and is sorted by the value of the Average metadata field. The Default type format is configured to print the asset's name and the average rating and I've limited it to 10 assets per page.


The asset listing of the most popular pages in the site


The rating is just a percentage, but you could get tricky with JavaScript and use the values to prints stars and other graphical elements. Just remember to put a textual value in there somewhere to ensure it is accessible.

And that's it. You can use this average rating anywhere you normally use metadata, including sorting search results. And of course, this is MySource Matrix so you can configure all the frontend interfaces shown here in any way you want.

Using PHP_CodeSniffer in an SVN pre-commit hook

I've just commit a new script to PHP_CodeSniffer called phpcs-svn-pre-commit. It sits in the scripts dir with phpcs and phpcs.bat. This script was contributed by Jake Bates, who has also volunteered to maintain the Debian package, and will be available in the 1.1.0 release.

Using the script is pretty easy, but you'll need to modify it slightly.

Edit /path/to/PHP_CodeSniffer/scripts/phpcs-svn-pre-commit and replace @php_bin@ in the first line with the path to the PHP CLI. For example,

#!@php_bin@
becomes
#!/usr/bin/php

Then, ensure the path to svnlook is correct by modifying the line:
define('PHP_CODESNIFFER_SVNLOOK', '/usr/bin/svnlook');

Now, add the following line to your pre-commit file in the SVN hooks directory:
/.../phpcs-svn-pre-commit "$REPOS" -t "$TXN" >&2 || exit 1

This will cause the SVN commit to fail if PHP_CodeSniffer finds any errors. The error report will be displayed to the user so they can fix errors before attempting the commit again.

You can also use all the standard phpcs command line options to do things like set the standard to use, the tab width and the error report format:
/.../phpcs-svn-pre-commit --standard=Squiz --tab-width=4 ...

And some example output:
$ svn commit -m "Test" temp.php
Sending temp.php
Transmitting file data .svn: Commit failed (details follow):
svn: 'pre-commit' hook failed with error output:

FILE: temp.php
---------------------------------------------------------------
FOUND 1 ERROR(S) AND 0 WARNING(S) AFFECTING 1 LINE(S)
---------------------------------------------------------------
2 | ERROR | Missing file doc comment
---------------------------------------------------------------

Friday 7 March 2008

Creating an SVN repostiory on OS X

I've been playing around with an SVN pre-commit hook for PHP_CodeSniffer that was submitted by Jack Bates. Part of that process required me to create a SVN repository for testing. While it was fairly easy overall, there were a few error messages that I got stuck on.

Firstly, creating the repository was dead simple:
$ create --fs-type fsfs /Users/Greg/TestSvnRepo

But then I couldn't checkout my repository. I'd get errors like:
$ svn co file:///Users/Greg/TestSvnRepo
Unable to open an ra_local session to URL
...
Expected version '3' of repository; found version '5'

Common suggestions on mailing lists point to svn not being compiled with ra_local support, which was not the problem in my case:
$ svn --version
svn, version 1.1.4 (r13838) compiled Jul 23 2006, 14:53:55
...
The following repository access (RA) modules are available:
* ra_dav : Module for accessing a repository via WebDAV (DeltaV) protocol.
- handles 'http' schema
- handles 'https' schema
* ra_local : Module for accessing a repository on local disk.
- handles 'file' schema
* ra_svn : Module for accessing a repository using the svn network protocol.
- handles 'svn' schema

I did find one useful article though. It mentions that different versions of svn software can cause problems. I believe this is more common when moving repositories between versions, or when connecting to a remote server, but in my case the problem was my version of svnadmin:
$ svnadmin --version
svnadmin, version 1.4.4 (r25188) compiled Sep 23 2007, 22:32:34

So my svnadmin utility is version 1.4.4 but the svn utility itself is 1.1.4. Adding the pre-1.4 command line argument suggested in the article worked a treat:
$ svnadmin create --fs-type fsfs --pre-1.4-compatible /Users/Greg/TestSvnRepo
$ svn co file:///Users/Greg/TestSvnRepo