PHP - - By Phil Sturgeon

PHP vs Ruby – Let’s All Just Get Along

Quite often you see developers who have a lot of experience in one language try to play with another, then make a rather quick comparison between the two. This comparison is usually quite worthless, but the clickbait titles get them a lot of traffic.

Instead of doing that, I thought it would be interesting to have a slightly more fair comparison, from the perspective of someone who really enjoys writing both PHP and Ruby, and has done so for years. The aim here is not to find out which is “better”, but to point out a few key things I like about Ruby and its ecosystem.

Screenshot 2015-11-21 01.20.42

Conceptual differences

Different languages are often compared to find out which is better, when the differences that make them up are more ideological. Sometimes one thing seems better to one group of people, when that very same thing makes the language a nightmare for other developers.

With this in mind, before I get into the “bits of Ruby I like in comparison” I think explaining a few of these conceptual differences will be important.

Method, Variable, Property?

PHP offers different syntax to access properties, methods or variables. Ruby does not.

PHP

$this->turtle   # Instance Property
$this->bike()   # Method
$apple          # Variable

Ruby

@turtle         # Instance Property
turtle          # "Instance Property" using attr_reader: :turtle
bike            # Method
apple           # Variable

Pedants will point out here that attr_reader :turtle will define a method dynamically, which is used as a getter for @turtle, making turtle and bike the same thing. A PHP developer looking at usage of turtle with no method or variable name explicitly defined will be incredibly confused about where it’s coming from.

This should not be something that causes you problems too often, but it has caught me out now and then. Sometimes folks on your team might change things from an attr_reader to a full method, or vice-versa, and it might cause knock-on affects.

That said, it can allow for some really flexible APIs, and let you do some cheeky things that you’re thankful for in the heat of the moment.

Deleted a field, but have loads of code, and a JSON contract still expecting it to be there?

class Trip
  def canceled_at
    nil
  end
end

That’ll work nicely. Anything calling trip.canceled_at is going to get a nil for this field, which is fine. We’ll remove it properly later.

Type Hints vs. Duck Typing

In the PHP world, type hints are a weird and wonderful thing. Languages like Golang absolutely require you to define a type for arguments, and return types. PHP added optional type hinting in 5.0 for arguments. You could require arrays, specific class names, interfaces or abstracts, and more recently callables.

PHP 7.0 finally allows return type hinting, along with support for hinting int, string, float, etc. This was done with the scalar type hints RFC. This RFC was bitterly argued and debated, but it got in there, and it is still completely optional; good news for the varied user-base that is PHP.

Ruby has literally none of that.

Duck Typing is the way to go here, which some folks in the PHP world also maintain is a superior approach.

Instead of saying: “The argument needs to be an instance of a class which implements FooInterface” and knowing that FooInterface will have a bar(int $a, array $b) method, you essentially say: “The argument can be literally anything that responds to a bar method, and if it doesn’t we can do something else.”

Ruby

def read_data(source)
  return source.read if source.respond_to?(:read)
  return File.read(source.to_str) if source.respond_to?(:to_str)
  raise ArgumentError
end

filename = "foo.txt"
read_data(filename) #=> reads the contents of foo.txt by calling 
                    #   File.read()

input = File.open("foo.txt")
read_data(input) #=> reads the contents of foo.txt via 
                 #   the passed in file handle

This is really flexible, but to some this is a code smell. Especially in a language like PHP where a int(0) or int(1) are considered valid boolean items in weak mode, taking any value and just hoping it works right can be a scary move.

In the PHP world, we might just define two different methods/functions:

function read_from_filename(string $filename)
{
    $file = new SplFileObject($filename, "r");
    return read_from_object($file);
}

function read_from_object(SplFileObject $file)
{
  return $file->fread($file->getSize());
}

$filename = "foo.txt";
read_from_filename($filename);

$file = new SplFileObject($filename, "r");
read_from_object($file);

That said, if we wanted to do the exact same duck typing approach in PHP, we easily could:

function read_data($source)
{
    if (method_exists($source, 'read')) {
        return $source->read();
    } elseif (is_string($source)) {
        $file = new SplFileObject($source, "r"));
        return $file->fread($file->getSize());
    }
    throw new InvalidArgumentException;
}

$filename = "foo.txt";
read_data($filename); #=> reads the contents of foo.txt by calling 
                      #   SplFileObject->read();

$input = new SplFileObject("foo.txt", "r");
read_data($input); #=> reads the contents of foo.txt via 
                   #   the passed in file handle

You can do either in PHP. PHP doesn’t care.

Pretending that Ruby is “better” because it uses duck typing would be misguided, but very common. You may prefer the approach, and PHP can do both, but basically, Ruby is lacking a feature that PHP has. Being able to do whichever you please does strike me as a bit of a win for PHP, when it is completely impossible to type hint in Ruby even if a developer wanted to.

That said, there are many PHP developers who are strongly against type hints, who wished there were none at all and were upset when more were added in PHP 7.0.

Interestingly, Python used to be totally lacking type hints just like Ruby. Then recently they added them. I’d be interested in knowing how many people flipped a table over that transition.

Fun Features

Once I’d accepted those differences, I was able to focus on the fun stuff. These are the features I started to notice myself using regularly, or least fairly often.

Nested Classes

Nested classes sound a little alien to PHP developers. Our classes live in namespaces, and a class and a namespace can share the same name. So, if we have a class that is only relevant to one class, we namespace it and that’s it.

So, we have a class called Box, which can throw a ExplodingBoxException:

namespace Acme\Foo;

class Box
{
    public function somethingBad()
    {
      throw new Box\ExplodingBoxException;
    }
}

That exception class declaration has to live somewhere. We could put it at the top of the class, but then we have two classes in one file and… well that feels a little funny to many. It also violates PSR-1, which states:

This means each class is in a file by itself, and is in a namespace of at least one level: a top-level vendor name.

So, it goes in its own file:

namespace Acme\Foo\Box;

class ExplodingBoxException {}

To load that exception we have to hit the autoloader and go to the filesystem again. Doing that isn’t free! PHP 5.6 lowers the overhead for subsequent requests if the opcode cache is enabled, but it is still extra work.

In Ruby, you can nest a class inside another class:

module Acme
  module Foo
    class Box
      class ExplodingBoxError < StandardError; end

      def something_bad!
        raise ExplodingBoxError
      end
    end
  end
end

This is available to the defining class, and available outside of the class too:

begin
  box = Acme::Foo::Box.new
  box.something_bad!
rescue Acme::Foo::Box::ExplodingBoxError
  # ...
end

That might look a little weird, but it’s pretty cool. A class is only relevant to one class? Group them!

Another example would be database migrations.

Migrations are available in many popular PHP frameworks, from CodeIgniter to Laravel. Anyone who has used them often will know, if you reference a model or any other class in your migrations, then later you change that class, your old migrations will break in weird and wonderful ways.

Ruby gets around this nicely with nested classes:

class PopulateEmployerWithUserAccountName < ActiveRecord::Migration
  class User < ActiveRecord::Base
    belongs_to :account
  end

  class Account < ActiveRecord::Base
    has_many :users
  end

  def up
    Account.find_each do |account|
      account.users.update_all(employer: account.name)
    end
  end

  def down
    # Update all users whose have account id to previous state
    # no employer set
    User.where.not(account_id: nil).update_all(employer: nil)
  end
end

The nested version of the User and Account ORM models will be used instead of the global declared classes, meaning that they are more like snapshots in time of what we need them to be. This is far more useful than calling code with moving goalposts, which could change at any point.

Again, this will sound mad to some people, until you’ve been bitten a few times.

PHP developers often end up copying complex logic to do this, or write the SQL by hand, which is a waste of time and effort compared to just copying the relevant bits of a model over.

Debugger

XDebug is a wonderful piece of work. Don’t get me wrong, using breakpoints is amazing, and revolutionizes the way PHP developers debug their applications, moving beyond the “var_dump() + refresh” debug workflow that is wildly common amongst junior developers.

That said, getting XDebug to work with your IDE of choice, finding the right addon if it’s missing, getting the php.ini tweaked for the right version of PHP to enable zend_extension=xdebug.so for your CLI and web version, getting the breakpoints sent out even if you’re using Vagrant, etc., can be a massive pain in the backside.

Ruby has a bit of a different approach. Much like debugging JavaScript in the browser, you can just bang the debugger keyword into your code and you’ve got a breakpoint. At the point your code executes that line – be it the $ rails server, a unit test, integration test, etc., you’ll have an instance of a REPL to interact with your code.

There are a few debuggers around. One popular debugger is called pry, but another is byebug. They’re both gems, installable via Bundler by adding this to your Gemfile:

group :development, :test do
  gem "byebug"
end

This is the equivalent of a dev Composer dependency, and once installed you can just call debugger if you’re using Rails. If not, you’ll need to require "byebug" first.

A handy Rails guide Debugging Rails Applications, shows how things look if you shove the keyword in your application:

[1, 10] in /PathTo/project/app/controllers/articles_controller.rb
    3:
    4:   # GET /articles
    5:   # GET /articles.json
    6:   def index
    7:     byebug
=>  8:     @articles = Article.find_recent
    9:
   10:     respond_to do |format|
   11:       format.html # index.html.erb
   12:       format.json { render json: @articles }
 
(byebug)

The arrow shows the line the REPL instance is running in, and you can execute code from right there. At this point, @articles is not yet defined, but you can call Article.find_recent to see what is going on. If it errors, you can either type next to go onto the next line in the same context, or step to go into next instruction to be executed.

Handy stuff, especially when you’re trying to work out why the heck some code is not giving you the output you expect. Inspect every single value until you find the culprit, try and make it work in that context, then whatever ends up being the working code can be copied and pasted into the file.

Doing this in your tests is amazing.

Unless

A lot of people do not like unless. It is oft abused, like many features of many programming languages, and unless has been winding people up for years as this article from 2008 points out.

The unless control structure is the opposite of if. Instead of executing the code block when a condition evaluates to true, it will only evaluate when the condition evaluates to false.

unless foo?
  # bar that thing
end

# Or

def something
  return false unless foo?
  # bar that thing
end

It makes things a bit easier to work out, especially when there are multiple conditions, maybe an || and some parentheses. Instead of switching it with a bang like so: if ! (foo || bar) && baz you can simply do unless (foo || bar) && baz.

Now, this can get a bit much, and nobody at work would let you submit an unless with an else on it, but an unless by itself is a handy feature.

When people requested this feature for PHP in 2007, it was ignored for a while until PHP creator Rasmus Lerdorf said it would be a BC break to anyone with a unless() function, and it would be “not obvious to non-native English speaking people like myself.”

It is an odd word that essentially means not-if even though it logically should be equivalent to “more” as in the opposite of “more” would be “less” and sticking “un” in front of it suddenly completely changes the meaning entirely.

I disagreed with that, and still do. People reading unless are not going to think it is the “opposite of less” just based on the un. If that were the case, people would read the function uniqid() and think it was the opposite of iqid().

We suggested as much, and got told that we were “just being silly.” It has since been labeled wontfix.

Predicate Methods

There are a few cool conventions in the world of Ruby that PHP is forced to solve in other ways. One of these is predicate methods, which are methods with a boolean response type. Seeing as Ruby has no return type hints, this is a good suggestion of intent to developers using the method.

Ruby has many built in, such as object.nil?. This is basically $object === nil in PHP. An include? instead of include is also a lot more clear in that it is asking a question, not executing an action.

Defining your own is pretty cool:

class Rider
  def driver?
    !potential_driver.nil? && vehicle.present?
  end
end

Many PHP developers will do this by prefixing the method name with is and/or has, so instead you have isDriver() and maybe hasVehicle(), but sometimes you have to use other prefixes. A method I wrote in Ruby which was can_drive? would be canDrive() in PHP, and that is not clear it is a predicate method. I’d have to rename it isAbleToDrive() or some guff to make it clear with the is/has tradition rolling in the PHP world.

Even Shorter Array Syntax

In PHP, defining literal arrays is easy, and has been made much less verbose in PHP 5.4 with the addition of short array syntax:

// < 5.4
$a = array('one' => 1, 'two' => 2, 'three' => 'three');
// >= 5.4
$a = ['one' => 1, 'two' => 2, 'three' => 'three'];

Some would say that is still a little too verbose. In Ruby 1.9 they added a new option, to allow rockets to be replaced with semi-colons, which if done in PHP would cut this syntax down a little further:

$a = ['one': 1, 'two': 2, 'three': 'three'];

That is rather nice if you ask me, and when you get to nesting arrays it really saves a bit of typing when you’re doing it a few hundred times in a day.

Sean Coates suggested this with an RFC back in 2011, but it never made it in.

This may well not make it into PHP ever. PHP tries to be minimalist with its syntax, and is very often against adding new approaches to do old things, even if the new approach is quite a bit nicer. Basically, syntactic sugar is not a priority for PHP maintainers, whereas it seems to be almost a core goal of Ruby.

Object Literals

The same RFC linked above highlights a feature in Ruby that I would love to see put into PHP: object literals. In PHP, if you would like to define an StdClass with values, you have two approaches:

$esQuery = new stdClass;
$esQuery->query = new stdClass;
$esQuery->query->term = new stdClass;
$esQuery->query->term->name = 'beer';
$esQuery->size = 1;
 
// OR

$esQuery = (object) array(
   "query" => (object) array(
       "term" => (object) array(
           "name" => "beer"
       )
   ),
   "size" => 1
);

I know this is how it’s been done in PHP forever, but it could be so much easier.

The proposed syntax in the RFC matches Ruby almost exactly:

PHP

$esQuery = {
   "query" : {
       "term" : {
           "name" : "beer"
       }
   },
   "size" : 1
};

Ruby

esQuery = {
   "query" : {
       "term" : {
           "name" : "beer"
       }
   },
   "size" : 1
}

I would really really like this to be done, but again, it has been attempted in the past and met with little interest.

Rescue a Method

Instead of try/catch in PHP, Ruby has begin/rescue. The way they work is essentially identical, with PHP 5.6 even getting finally, to match Ruby’s ensure.

PHP and Ruby can both recover from an exception anywhere, following their respective try or begin keywords, but Ruby can do something really clever: you can skip the begin method, and rescue directly from a function/method body:

Ruby

def create(params)
  do_something_complicated(params)
  true
rescue SomeException
  false
end

If things go wrong, an error can be handled somehow instead of bubbling up and forcing the callee to handle it. Not always what you want, but it’s handy to have the option available, and without needing to indent the whole thing to wrap in a begin.

In PHP this does not work, but the feature could look a little something like this if implemented:

function create($params) {
  do_something_complicated($params);
  return true;
} catch (SomeException $e) {
  return false;
}

While it might not be wildly important, there are a lot of nice little touches like this that make Ruby feel like it wants to help you.

Exception Retries

A few months ago I spotted a really handy feature that I never noticed before, the retry keyword:

begin
  SomeModel.find_or_create_by(user: user)
rescue ActiveRecord::RecordNotUnique
  retry
end

In this example, a race condition flares up because the find_or_create_by is not atomic (the ORM does a SELECT and then INSERT) which, if you’re really unlucky, can lead to a record being created by another process after the SELECT but before the INSERT.

As this can only happen once you can simply instruct the begin...rescue to try it again, and it will be found with the SELECT. In other examples, you’d probably want to put some logic in place to only retry once or twice, but let’s just ignore that for a second.

Focus on how annoying it would be to take one piece of the code and try it again. In Ruby you can do this:

def upload_image
  begin
    obj = s3.bucket('bucket-name').object('key')
    obj.upload_file('/path/to/source/file')
  rescue AWS::S3::UploadException
    retry
  end
end

PHP would require you to create a new function/method just for the contents of the begin block:

function upload_image($path) {
  $attempt = function() use ($path) {
    $obj = $this->s3->bucket('bucket-name')->object('key');
    $obj->upload_file($path);
  };
  
  try {
    $attempt();
  } catch (AWS\S3\UploadException $e)
    $attempt();
  }
}

Again, yes, you’d want to put some boilerplate into both to stop infinite loops, but the Ruby example is certainly much cleaner at retrying the code.

A little birdie tells me this feature is actively being developed as an RFC which will hopefully be announced soon. It could theoretically be a PHP 7.1 feature if the process goes well.

Final Thoughts

Dabbling with Ruby in the past, I was mostly just writing Ruby like PHP. Working with a team of amazing and extremely experienced Ruby developers for the past year has taught me a lot of Rubyisms and practices that are a little different, and I don’t hate it.

This article points out things I would miss about Ruby if I went back to working with PHP, but that wouldn’t stop me from doing it. What the PHP haters regularly ignore is the progress being made by the PHP core contributors, and whilst they might not have feature X or Y that I like from Ruby, they have done some amazing things for PHP 7 and PHP 7.1 looks full of interesting surprises.

PHP has made strong plays towards consistency, with uniform variable syntax, a context-sensitive lexer, and an abstract syntax tree. All of this makes PHP as a whole more consistent, regardless of the inconsistencies in the standard library.

While the standard library isn’t going to be fixed any time soon, if PHP could add a few handy features and sprinklings of syntactic sugar like the ones above, then it would be very nice indeed.

Other languages can blaze off in all directions working on new theories, experimenting, making cool stuff that gets the HackerNews types happy, then the stuff that makes sense for PHP to take is eventually grabbed. I like this approach. After all, PHP is a pillaging pirate.

Play with as many languages as you have time and interest in playing with. Ruby is a good start, Golang is a lot of fun and Elixir is quirky but a good challenge, but don’t feel the need to jump ship from one to the other. You can write in a few, and it keeps your brain nice and active.

Sponsors