ActiveSupport for PHP - Ruby style

Quick introduction

One of the things I miss most from Rails is ActiveSupport, the module that modifies Ruby’s core classes (numbers, strings, more) with handy utility methods. They tie so well into the language, most Rails developers don’t realize they aren’t core methods.

Here’s a few examples:

>> 'hello_world'.camelize
=> 'HelloWorld'

>> 7.days.ago
=> Sun Aug 05 20:53:12 -0400 2007

>> 'purple people eater'.ends_with?('eater')
=> true

Aside: For a near-complete list of ActiveSupport goodies, check out ErrTheBlog’s teriffic Rails Rubyisms Advent article.

The PHP way

Big chunks of ActiveSupport (mostly string inflection) have been ported to each of the major PHP frameworks, and an ActiveSupport extension has already been submitted to PEAR.

In nearly all of these cases, the functionality has been ported, but the elegance of ActiveSupport is lost in translation. Here’s the camelize example again, this time using Symfony:

sfInflector::camelize('hello_world');

As with pretty much all PHP code, it’s interpreted right-to-left instead of left-to-right, like Ruby. I prefer the latter.

The Ruby way (in PHP)

So, armed with an afternoon and a full pot of coffee, I wrote my own Ruby-like ActiveSupport library for PHP5. Here’s what it looks like:

require_once('ActiveSupport.php');

_(14)->ordinalize(); # Outputs "14th" 

_(7.3)->megabytes(); # Returns number of bytes in 7.3 megabytes

_("an example sentence")->endsWith("sentence"); # Returns true

What the hell?

Okay, there’s some trickery going on here, so I’ll provide a quick explanation.

The underscore shortcut you see above is a quick way of instantiating one of four core ActiveSupport types: ActiveSupport_Integer, ActiveSupport_Numeric, ActiveSupport_String, or ActiveSupport_Array. The class you get depends on the type of the parameter you pass in. Each of these classes have been armed the same utility methods in their Rails counterpart.

What about chaining?

Ruby’s chained time/date expressions are my favourite, and they haven’t been forgotten. You can chain ActiveSupport function calls by ending them with an underscore:

_(2)->weeks_()->ago(); # Returns appropriate timestamp

_(14)->days_()->before(time()); # Same value as above, written differently

By default, all ActiveSupport function calls return primitive types (integer, float, etc) in order to play nice with traditional PHP. But when you append an underscore to the function name, you get the corresponding ActiveSupport type instead. So where _(2)->weeks() returns an integer, _(2)->weeks_() returns an object of class ActiveSupport_Numeric.

And there you have it

Want to give it a try? Download the (mostly complete) source code here (MIT License).

I’m not sure how practical this library actually is, but developing it was an interesting exercise. I was able to learn a lot about PHP’s limitations, as well as its flexibility.

As always, feedback is appreciated.

Commentary

the only comment really that I have is the use of the _() function namespace, as there are some other projects that use that, like wordpress for example. I find alot of people use that style of function naming for gettext type of stuff, since its so small. maybe it would be better to just provide a simple way to get back an instance of the ActiveSupport class, maybe even come up with a different name so that its not so long.

Since you are essentially creating a factory you could just do something more PHPy like $instance = ActiveSupport::factory(your arugment);

something like that who knows.

Comment by kenrick on August 14, 2007

Hey Kenrick,

Thanks for the comments. Yeah, I had toyed with a number of different entry points instead of _(). Another thought was to have users of the library register their own shortcut function.

Comment by Ben on August 14, 2007

Ben I’ve just had to make a similar move from 2 years of solid Rails development to a PHP project. It’s nice to see others thinking in Ruby even though they have to write PHP.

Comment by Flinn on August 14, 2007

Hey Flinn,

I’d been keeping up with your blog, actually. It’s funny, because once I learned I’d be developing in PHP, I treaded down a similar path of attempting to re-write ActiveRecord and other familiar Railsisms. More often than not, it doesn’t work out.

BTW – You should open up commenting on your blog, or put up a “Contact Me” page of some kind.

Comment by Ben on August 14, 2007

I agree that the gettext thing would be the biggest hurdle here. A lot of popular hosting companies - such as Dreamhost - have the gettext extension enabled by default, so any attempt to create a _() function will generate “Cannot redeclare” errors.

(I know this because, well, I’ve tried to do it before.)

The obvious answer would be to do some function_exists magic. I’ve been working on something like this for Scatterpress, and and ended up with a bunch of N(), S(), and O() functions for Numeric, String, and Object types. It’s a nifty exercise, but the syntactic juice isn’t quite worth the squeeze.

Our solution has been to embrace the inversion, and change 2.days.ago into since(2, “days”)

Comment by Matt on August 15, 2007

As someone who programs both it is nice to see other solutions that people are coming up with. Going back to PHP from Rails always leaves me desiring the simplicity. CakePHP had a good helper class and inflection, but it still isn’t as simple as writing ‘hello_world’.camelize. You have to deal with the PHP way, so it would be $html->camelize(‘hello_world’).

Needless to say, I like your train of thought – and reversing things to fit more to the Ruby way (in reverse).

Comment by Nate Klaiber on August 15, 2007

Hey Matt,

You know what? When I first started writing this library, I similarly used n(), s(), and a() methods instead of _(). I guess I liked the single type-agnostic entry point, similar to JQuery’s $() method.

BTW, I might have to steal your since() method – alternatively, how about days(2), or days(ago(2))?

Comment by Ben on August 15, 2007

Hey,

In a quick check, I found a little bug, so I rewrote the endsWith method. The new code is below:

public function endsWith($string) { $string = preg_quote($string, ”/”); return preg_match(”/$string$/”, $this->native); }

Hope this is useful.

Regards.

Comment by Fernando Bittencourt on August 15, 2007

Hey Fernando,

Thanks for the patch – I’m encouraged to know someone’s poking through the code.

Comment by Ben on August 16, 2007

Hey Ben, yeah I spent a long time porting ActiveRecord to PHP and came to that same realization. It just doesn’t work. Rails is what it is because of Ruby. I’ve been a week into PHP and I miss Ruby. Btw, I have comments off on my blog because of the endless spam. I’m sure I’ll get around to adding an annoying captcha to re-enable comments.

Comment by Flinn on August 21, 2007

Sorry to say, it’s still ugly PHP way.

Ruby is the language for Rails, it could not be developed in any other language!

Comment by Jamal Soueidan on August 29, 2007