Blog

Making PHP development bearable for Ruby developers using Coffeescript, Sass, and Haml

Preface: I don't want to bash PHP too much here, it is what it is, but I think it is clear that the Ruby community has put much more work into developing great tools to use while programming over the years, so any Ruby developer getting into PHP development should try and take some of those nice tools with her!

This week I had to start doing some PHP development, for the first time in about a year. Being a Ruby developer, I dreaded this moment, mostly because I am not so well-versed in PHP to avoid those little annoyances and inconsistencies that the language has accumulated over the years (or rather decades).

The reason I am doing PHP is that I need a Wordpress plugin for ProductWidgets. I wrote a Wordpress plugin before and I actually quite like the Wordpress API for developers.

When working with Ruby, I normally write Haml instead of HTML, Coffeescript instead of Javascript, and SCSS instead of CSS, so the first thing I did was to set up a workflow involving Guard that let me use those meta-languages in PHP land as well, and compile them to their more verbose counterparts automatically. Nothing too crazy here:

# Gemfile
source 'https://rubygems.org/'

gem 'guard',                '1.8.3'
gem 'guard-coffeescript',   '1.3.4'
gem 'guard-haml',           '0.5'
gem 'guard-sass',           '1.3.2'

# Guardfile
guard 'coffeescript', input: 'coffee', output: 'trunk/js'
guard 'sass', input: 'sass', output: 'trunk/css'
guard 'haml', input: 'haml', output: 'trunk/views'

Wordpress plugins are stored in Subversion, so the code lives in a trunk folder. I simply put my Coffeescript files in coffee, Sass in sass, and Haml in haml, start Guard, and anytime I change on of the files, they are compiled into the correct folder in trunk. Great success!

So I happily started coding, but quickly got annoyed by PHPisms such as:

<?php if (!empty($api_key)) { ?>
%a{ href: "?page=<?php echo $this->plugin_slug ?>", class: "nav-tab" }
<?php } ?>

See how I have to use the same indentation for the Haml code inside the PHP if statement? Haml doesn't understand that the first PHP tag is actually an opening tag the last one the closing tag and that the code in between can (and should) be indented.

How cool would it be if I could use Ruby blocks instead, I thought?

Same goes for the "?page=<?php echo $this->plugin_slug ?>" part: why not use proper Ruby string interpolation here?

Well, long story short, I coded up some helpers that let me write the same block of code like this:

= php_if '!empty($api_key)' do
  %a{ href: "?page=#{php_echo '$this->plugin_slug'}", class: "nav-tab" }

It's all Ruby now, you see?

And this is how it's done:

  1. Add haml_options: { escape_attrs: false } to the guard 'haml' line in your Guardfile. Otherwise the PHP opening and closing tags you pass as attributes in Haml will be escaped.
  2. Add the following to your Guardfile:
module ::Haml::Helpers
  def php(text)
    "<?php #{text} ?>"
  end

  def php_echo(text)
    php %(echo #{text})
  end

  def php_if(condition, more_conditionals = false, &block)
    php_condition condition, 'if', more_conditionals, &block
  end

  def php_else_if(condition, more_conditionals = false, &block)
    php_condition condition, '} else if', more_conditionals, &block
  end

  def php_else(condition, &block)
    php_condition condition, '} else', &block
  end

  private

  def php_condition(condition, conditional, more_conditionals = false, &block)
    %(
      #{php "#{conditional} (#{condition}) {"}
        #{capture_haml(&block)}
      #{php '}' unless more_conditionals}
    )
  end
end

How to use these helpers

Plain

php 'awesome();'

becomes

<?php awesome(); ?>

Echo

php_echo 'brilliant();'

becomes

<?php echo brilliant(); ?>

Simple if

= php_if 'pelle() == "fant"' do
  %span
    Pellefant!

becomes

<?php if (pelle() == "fant") { ?>
  <span>
    Pellefant!
  </span>
<?php } ?>

If, else if, else

Add true to php_if and php_else_if if more php_else_ifs or php_elses follow.

= php_if 'pelle() == "fant"', true do
  %span
    Pellefant!
= php_else_if 'pelle() == "foo"', true do
  %span
    Pellefoo??
= php_else do
  %span
    Oh boy...

becomes

<?php if (pelle() == "fant") { ?>
  <span>
    Pellefant!
  </span>
<?php } else if (pelle() == "foo") { ?>
  <span>
    Pellefoo??
  </span>
<?php } else { ?>
  <span>
    Oh boy...
  </span>
<?php } ?>

Bonus points

To be able to write multiple PHP statements like this:

:php
  settings_fields($this->plugin_slug);
  do_settings_sections($this->plugin_slug);
  submit_button('Submit');

which becomes

<?php
  settings_fields($this->plugin_slug);
  do_settings_sections($this->plugin_slug);
  submit_button('Submit');
?>

add the haml-contrib gem to your Gemfile and the following to your Guardfile:

require 'haml/filters'
require 'haml/filters/php'

Discuss this post on Hacker News

Ideas? Constructive criticism? Think I'm stupid? Let me know in the comments!