Quick Tips for Writing Object Oriented Code in PHP

  • 9 minute read

Recently I began working on a D8 module, but this isn't a story about a D8 module. The work I did provided me an opportunity to get back to my pre-Drupal object oriented (OO) roots. Writing OO code in PHP presented some curve balls I wasn’t prepared for. Here are some of the issues I encountered:

PSR-4 Autoloading: How to set up your files to be loaded

First things first, how do you include OO code in your project? In D7 you had to add the files to a .info file for a module or do module_load_include. In D8 all you have to do is follow PSR-4 namespacing. If you follow the PSR-4 folder and namespace structure your classes will be auto-detected. No more need to add them to a .info file! If you are writing code for D8 then it’s done. Great. In D7 you can use the XAutoload module to get PSR-4 autoloading in D7 today!

Namespacing In PHP: Loading your files

Namespacing in PHP can be confusing and misleading. In Java or .NET, in a file you first import other namespaces you intend to use. In the example below we use the “using” keyword. Then you declare the namespace wrapper for the code that is being implemented.

using System;
using Microsoft.VisualBasic.Devices;
namespace SampleNamespace {
 class SampleClass
  {
  }
}

 


PHP is VERY different. It’s actually the opposite. First you declare the namespace then inside the namespace you have your “includes.” In PHP including the use statements outside of the namespace would contaminate the global-scope.

namespace SampleNamespace
use GuzzleHttp;
use GuzzleHttp\Subscriber;
class SampleClass 
{
}


Now this is where things get tricky. If there is a class called Client inside GuzzleHttp then you would expect that you could use it by writing the following.

namespace SampleNamespace
use GuzzleHttp;
class SampleClass {
 function sampleFunction(){
   $myClient = new Client();
 }
}

And you would be wrong. The way that PHP interprets classes are RELATIVE to the current file’s namespace. So it actually sees “$myClient = new Client();” as “ $myClient = new SampleNamespace\Client();” which does not exist so the declaration fails. To work around this you can reference the actual class in the use statement. If you have multiple classes you must have an include for each one. It’s more verbose than what you might expect coming from .Net or Java e.g.:

namespace SampleNamespace
use GuzzleHttp\Client;
use GuzzleHttp\Subscriber\Mock;
class SampleClass {
 function sampleFunction(){
   $myClient = new Client();
   $mock = new Mock();
 }
}

 

Dynamic Typing: A variable can be anything!

PHP is a dynamically typed language. It provides great flexibility and velocity when coding, especially procedural code. However, this can be a nightmare when you are writing OO code. It means that you cannot make assumptions about the type being passed into an object. If you make assumptions and those assumptions are invalid your code can behave unpredictably. What are you to do?

Type Hint ALL THE THINGS:

You might be surprised to know that PHP allows you to apply and enforce function param types. PHP 5 introduced the concept of type hinting. With type hinting you can set type on objects. e.g
function sampleFunction(MySampleClass $a){

If a type hint is violated, an InvalidArgumentException is thrown. There is a catch to type-hinting in PHP, it doesn’t work for scalar types e.g (string, int, bool). There is also no type hinting on return types. You’ll have to wait for PHP 7 for both. In order to work around the scalar limitation in PHP5 you’ll need to write your own functions.

Setting up your code for an IDE

One of the advantages to writing Object Oriented code is that it works really nicely with an IDE like PHPStorm. If you have written type hinted code PHPStorm will pick up on it and help you with auto completion as you work. For the things that aren’t explicitly hinted you can use PHPDoc comments. e.g

/**
 * Gets a specific setting by its name/ID.
 *
 * @param string $id
 * The name/ID of the setting to retrieve.
 *
 * @return ZoneSettingBase
 * The setting object given the ID passed in.
 */
public function getSettingById($id) {
 return $this->settings[$id]; 
}

 

PHPDoc comments are actually mandatory as part of drupal-coding standards. Their omission causes coder’s code sniffer to fail.

You can also type-hint variables:

/* @var GuzzleHttp\Client $client/*

private $client;

 

While these hints are comments and not syntax they make the developer experience a lot more pleasant.

No Enums :(

PHP still doesn’t have a formal enumeration type so you will have to get creative and roll your own.

I often create CONST arrays and throw an exception if a function param is not in that array. It’s a poor-man’s enum. There is currently a proposal to add enums to PHP7. We’ll see if it makes the cut!

Associative Arrays

Associative arrays reflect the dynamic typed heritage of PHP. They are incredibly flexible and a quick and easy way to move data from one point of your app to another that being said the lack of structure requires a developer to know everything about the underlying implementation of the array. Also without a debugger they have no way to determine what is actually in an array. The dynamic nature of these arrays makes them undocumentable. That makes coding with someone else’s array hard. The idea of OO is that you have structured data and layers of abstraction so that a dev doesn’t need to know the low-level implementation details. When going OO you should try to convert arrays into structured, documentable classes that hide the underlying implementation. If you need to accept an array as input parse it and break it out into objects as soon as possible. Developers will praise you for it!

No Function Overloading

PHP does not natively support function overloading. Since PHP is dynamic it’s possible to come up with some Frankenstein solutions to get around this. However, Frankenstein code often confuses other developers interacting with your code and is to be avoided. For better or worse you need to accept this constraint.

Dusting off the design cobwebs. How to assign responsibility to classes.

The lack of overloading can actually be beneficial especially in the context of class constructors. Like normal functions you cannot have multiple constructors in PHP (You can technically write a static class method to work around this constraint). This sounds like a pain. However, it forces you to articulate the single responsibility of a class. Often overloaded class constructors can be a sign that a class is taking on too many responsibilities. For example if you have a constructor that take params and another for parsing an array you might ask why should my class know about another representation. Maybe it's a break in tiered architecture and violating a separation of responsibilities. In this case something that might be perceived as a limitation is actually supportive and liberating.

Go Forth and Write OO Code

As we transition into D8 writing solid PHP OO code is more important than ever. D8 is built around OO classes. Even in Drupal 7 we can start to strive towards an OO world. Obviously in Drupal 7 most problems don’t lend themselves to an OO approach. However, even having that option gives you new tools to solve problems in Drupal! The resulting code has a clarity and aesthetic that most procedural code just can’t match.

Finding those opportunities to apply an OO solution keeps you sharp and ready to hit the ground running on Drupal 8.