Using PHP - Resque with Silex and the Symfony2 Classloader

Resque is a popular Redis-backed Ruby library for creating and processing background jobs, this is all well and good if you’re building applications in Ruby but fortunately for those, like me, who prefer PHP there is also a PHP port - PHP-Resque. This post will describe how to use php-resque in conjunction with the Silex micro framework to queue jobs, initially this was not immediately obvious to me as php-resque is not namespaced so wouldn’t work using the default autoloading configuration but fortunately the class naming seems to follow the PEAR convention where the path to the class is defined by the classname (i.e. Resque_Event would be found in Resque/Event.php). The method described here should work for any similar libraries.

One point to note, there is a Symfony2 bundle that could be easily integrated with Silex which aims to integrate php-resque but it’s a lot more complicated than I require and in most cases is just not necessary.

The key to using php-resque is to use the registerPrefix method of the symfony classloader (included with Silex) which uses PEAR naming conventions load libraries.

To configure php-resque first clone a copy of the library from github into the vendors folder (or wherever you like to store your external libraries):

git clone https://github.com/chrisboulton/php-resque.git vendors/php-resque

Now we have the library in place where ever you configure your application, in my case /src/bootstrap.php which is included in the main application file point the Synfony class loader to the place for classes beginning with the prefix Resque, all the relevant php-resque files are in the lib subdirectory:

$app['autoloader']->registerPrefix('Resque', __DIR__ . '/../vendor/php-resque/lib');
$app['autoloader']->register();

It does need to be before the $app['autoloader']->register();

The library can then be used in your application at will without requiring or manually including it:

Resque::setBackend('localhost:6379');

$args = array();

Resque::enqueue('default', 'My_Job', $args);

Once you get your head around the different options the synfony2 classloader (and the rest of the components) are wonderful.

HTTP Basic Auth in Silex

Silex is a great platform for building small web applications and APIs, recently I’ve been using it to build an API with only a couple of routes. As this API will only be used by a couple of users it made sense to use use HTTP basic auth (over SSL of course). HTTP auth could be left to apache/nginx etc. but that wouldn’t give me the control I’d like over the output and authentication so I implemented it in Silex, I hope someone finds this useful:

HTTP basic authentication is very simple and just passes a username and password in the headers, PHP has built in functionality to extract these values which can be used in the Silex before hook to ensure it happens before every request is fulfilled, my example is for an API which returns JSON but it would work equally well for a conventional website:

$app->before(function() use ($app)
{
    if (!isset($_SERVER['PHP_AUTH_USER']))
    {
        header('WWW-Authenticate: Basic realm='<website name>'');
        return $app->json(array('Message' => 'Not Authorised'), 401);
    }
    else
    {
        //once the user has provided some details, check them

        $users = array(
            'workflow' => 'password'
            );

        if($users[$_SERVER['PHP_AUTH_USER']] !== $_SERVER['PHP_AUTH_PW'])
        {
            //If the password for this user is not correct then resond as such

            return $app->json(array('Message' => 'Forbidden'), 403);
        }

        //If everything is fine then the application will carry on as normal

    }
});

Full details of implementing HTTP auth in PHP can be found in the PHP manual, this includes how to implement HTTP digest auth.

JSON Error / Exception Messages in Codeigniter 2.0+

The Problem

One of my recent projects required me to build a quick JSON only API to abstract interaction with multiple databases for multiple web applications, as I’d already got some of the logic in Codeigniter I just added Phil Sturgeon’s Codeigniter REST Library. However while this handles all method not found errors when URL routing gets as far as the controller all other errors still appear as HTML, when using curl and attempting to parse as JSON this isn’t helpful.

The Solution

My solution was to extend the core Exceptions class which normally deals with these errors. This is done by creating MY_Exceptions.php in application/core and using something similar to the code below:

<?php defined('BASEPATH') OR exit('No direct script access allowed');

/**
 * Extending the default errors to always give JSON errors
 *
 * @author Oliver Smith
 */

class MY_Exceptions extends CI_Exceptions
{
	function __construct()
	{
		parent::__construct();
	}

	/**
	 * 404 Page Not Found Handler
	 *
	 * @param	string	the page
	 * @param 	bool	log error yes/no
	 * @return	string
	 */
	function show_404($page = '', $log_error = TRUE)
	{
		// By default we log this, but allow a dev to skip it

		if ($log_error)
		{
			log_message('error', '404 Page Not Found --> '.$page);
		}

		header('Cache-Control: no-cache, must-revalidate');
		header('Content-type: application/json');
		header('HTTP/1.1 404 Not Found');

		echo json_encode(
			array(
				'status' => FALSE,
				'error' => 'Unknown method',
			)
		);

		exit;
	}

	/**
	 * General Error Page
	 *
	 * This function takes an error message as input
	 * (either as a string or an array) and displays
	 * it using the specified template.
	 *
	 * @access	private
	 * @param	string	the heading
	 * @param	string	the message
	 * @param	string	the template name
	 * @param 	int		the status code
	 * @return	string
	 */
	function show_error($heading, $message, $template = 'error_general', $status_code = 500)
	{
		header('Cache-Control: no-cache, must-revalidate');
		header('Content-type: application/json');
		header('HTTP/1.1 500 Internal Server Error');

		echo json_encode(
			array(
				'status' => FALSE,
				'error' => 'Internal Server Error',
			)
		);

		exit;
	}

	/**
	 * Native PHP error handler
	 *
	 * @access	private
	 * @param	string	the error severity
	 * @param	string	the error string
	 * @param	string	the error filepath
	 * @param	string	the error line number
	 * @return	string
	 */
	function show_php_error($severity, $message, $filepath, $line)
	{
		header('Cache-Control: no-cache, must-revalidate');
		header('Content-type: application/json');
		header('HTTP/1.1 500 Internal Server Error');

		echo json_encode(
			array(
				'status' => FALSE,
				'error' => 'Internal Server Error',
			)
		);

		exit;
	}
}

?>

Limitations

This code will only output a basic JSON formatted message, it will not vary the http error codes based on conditions, nor will it take into account accepts headers or similar. Right now this class meets my needs but features such as other formats could easily be added.

Codeigniter Conference 2012 - Post Conference Thoughts

Having not even got back home yet after the 2012 Codeigniter Conference I thought I’d write up some of my highlights of a very enjoyable weekend:

Testing - John Crepizzi

Testing is something I’ve been working on recently using Simpletest, Jenkins and Codeigniter with moderate success, it was great to see that PHPUnit can be used quite simply, however as full PHPUnit support is planned to be integrated into the core of Codeigniter in v3.0 I think I’ll wait before transitioning away from Simpletest.

API Driven Development - Nick Jackson

Nick did a great job of reassuring me I’m doing things right. I’ve just started a project where I’m essentially building the API first as I know there are going to be multiple apps on top of it. I’ll definitely be refining my existing API based on some of the tips given regarding response format.

MongoDB - Alex Bilbie

MongoDB is something I’ve played with but not used in anger yet, I’m not sure I have a use case for it yet but this talk did demonstrate some very cool edge case functions for searching using latitude and longitude where MongoDB will do all the heavy lifting when trying to do box searches on a 3d spherical earth model.

Who needs Ruby when you've got CodeIgniter? - Jamie Rumbelow

Jamie Rumberlow’s talk was very practical and showed some very good ideas of how Codeigniter could be improved but demonstrated some really hacky ways to make it work the way he wanted (putting non POST data for validation into $_POST anyone?). Really I’d have preferred to see him talk about how the framework could be improved to fit in with the best practice. That’s the kind of thing that gives Codeigniter a bad reputation amongst many PHP developers.

Live coding - Phil Sturgeon

Phil demoed setting up a pyrocms site on pagodabox, while I didn’t find the actual demo very relevant to me, I do like is looking into others coding setups, this lead me to spend half of the next talk configuring my bash prompt and colour scheme to show mercurial or git branch names!

Other Talks

All of the talks were very engaging but some not as interesting to me personally, in particular Harro Verton was a great speaker on ORMs but it just further confirmed my views that I’m not a fan.

General Thoughts

Generally the conference went well, the venue was pretty good (apart from the food) and the wifi nothing short of amazing. Oh and the best bit - a FREE t shirt!

I’m always in a bit of a love hate relationship with Codeigniter, it’s very quick and easy but its slightly old fashioned way of doing things and lack of modern features like auto-loading, namespacing and dependency injection make it annoy me and cause me to look at things like Symfony2 or Silex.

Still a great weekend away and thanks to all who were responsible for the planning and organisation. I’ll hopefully be back next time.

Streaming audio from Ubuntu Linux to a DLNA player (Blu Ray or PS3) using Rygel

This project started out of researching how to play sound from spotify or rhythmbox from my laptop running ubuntu 11.10 through my hifi. Initially I set out to see if an airport express would work using raop and pulseaudio but it seems that support for the new 802.11 version is flakey so I didn’t wish to invest £80 in a device that might not work. During my research I found that DLNA supported streaming, DLNA is a protocol commonly used for sharing media files with devices such as networked dvd players, internet tvs and consoles like the ps3 so I explored further.

DLNA is supported in Ubuntu (and other modern linux distros) by Rygel, part of the Gnome project. Rygel provides a DLNA server which also has the capability to capture a pulseaudio sink (an input or output stream) and stream it to a DLNA enabled device.

Below are the steps I took to enable me to stream audio from my computer to my Sony BDP-S370, they should be applicable to any similar device:

  1. Install required packages:
    sudo apt-get install rygel rygel-gst-launch wavpack
  2. Find the name of the pulseaudio sink which you wish to capture: To list the choices, use:
    pacmd list-sinks
    Then make a note of the name attribute (minus surrounding brackets), in my case it was:
    alsa_output.usb-C-Media_INC._USB_Sound_Device-00-Device.analog-stereo
    I found that adding .monitor to this was required for the next stage, this can be achieved in one command:
    pactl list | egrep -A2 '^(\*\*\* )?Source #' | grep 'Name: .*\.monitor$' | awk '{print $NF}' | tail -n1
  3. Edit /etc/rygel.conf: Replace (or comment out)
    [GstLaunch]
    enabled=true
    launch-items=audiotestsrc;videotestsrc;videotestoverlay
    audiotestsrc-title=Audiotestsrc
    audiotestsrc-mime=audio/x-wav
    audiotestsrc-launch=audiotestsrc ! wavenc
    videotestsrc-title=Videotestsrc
    videotestsrc-mime=video/mpeg
    videotestsrc-launch=videotestsrc ! ffenc_mpeg2video ! mpegtsmux
    videotestoverlay-title=Videotestsrc with timeoverlay 2
    videotestoverlay-mime=video/mpeg
    videotestoverlay-launch=videotestsrc ! timeoverlay ! ffenc_mpeg2video ! mpegtsmux
    with
    [GstLaunch]
    enabled=true
    launch-items=mypulseaudiosink
    mypulseaudiosink-title=Audio on @HOSTNAME@
    mypulseaudiosink-mime=audio/x-wav
    mypulseaudiosink-launch=pulsesrc device=alsa_output.usb-C-Media_INC._USB_Sound_Device-00-Device.analog-stereo.monitor ! wavpackenc
    replacing the device on the last line with the output from the previous stage.
  4. Start Rygel (type rygel in the terminal)
  5. Connect your player to the DLNA device which should have appeared (probably as GstLaunch) and you should hear any audio played on your computer through your DLNA device.
  6. If you wish (I don't) add rygel to run at startup.
This worked perfectly for my desktop but for my laptop I had to fiddle with which hardware output of the soundcard was being used under the standard gnome sound settings, changing the profile for the selected device to an option with no output (ie input only or disabled).

Alternatives

  • If you just wish to share audio and video files then something like mediatomb with be much more simple (although rygel also shares files).
  • There is meant to be a simpler way to link rygel and pulseaudio where everything works out of the box and rygel appears as a separate audio out but it's currently broken with the supplied pulseaudio/rygel combination in ubuntu.

Acknowledgements

I figured all this out with the help of these guys:

Thanks.