Object-oriented code

Last modified: October 20, 2009 - 02:18

Drupal follows common PHP conventions for object-oriented code, and established industry best practices. As always, though, there are Drupal-specific considerations.

Naming conventions

  1. Classes and Interfaces should use UpperCamel naming.
  2. Methods and class properties should use lowerCamel naming.
  3. Classes should not use underscores in class names unless absolutely necessary to derive names inherited class names dynamically. That is quite rare, especially as Drupal does not mandate a class-file naming match.
  4. Interfaces should always have the suffix "Interface".
  5. Protected or private properties and methods should not use an underscore prefix. Although common in the PHP 4 era, it is discouraged in PHP 5-specific code.

Examples:

<?php
interface FelineInterface {

  public function
meow();

  public function
eatLasagna($amount);

}

class
GarfieldTheCat implements FelineInterface {

  protected
$lasagnaEaten = 0;

  public function
meow() {
    return
t('Meow!');
  }

  public function
eatLasagna($amount) {
   
$this->lasagnaEaten += $amount;
  }
}
?>

Use of interfaces

The use of a separate Interface definition from an implementing class is strongly encouraged. It encourages separation of concerns and allows more flexibility in extending code later. It also makes the documentation easier to read as the documentation is neatly centralized in the interface.

If there is even a remote possibility of a class being swapped out for another implementation at some point in the future, split the method definitions off into a formal Interface. A class that is intended to be extended must always provide an Interface that other classes can implement rather than forcing them to extend the base class.

Visibility

All methods and properties of classes must specify their visibility: public, protected, or private. The PHP 4-style "var" declaration must not be used.

The use of public properties is strongly discouraged, as it allows for unwanted side effects. It also exposes implementation specific details, which in turn makes swapping out a class for another implementation (one of the key reasons to use objects) much harder. Properties should be considered internal to a class.

The use of private methods or properties is strongly discouraged. Private properties and methods may not be accessed or overridden by child classes, which limits the ability of other developers to extend a class to suit their needs.

Type hinting

PHP supports optional type specification for function and method parameters for classes and arrays. Although called "type hinting" it does make a type required, as passing an object that does not conform to that type will result in a fatal error.

  • DO specify a type when conformity to a specific interface is an assumption made by the function or method. Specifying the required interface makes debugging easier as passing in a bad value will give a more useful error message.
  • DO NOT specify a type for a class. If specifying a type, always specify an Interface. That allows other developers to provide their own implementations if necessary without modifying existing code.

Example:

<?php
// Wrong:
function make_cat_speak(GarfieldTheCat $cat) {
  print
$cat->meow();
}

// Correct:
function make_cat_speak(FelineInterface $cat) {
  print
$cat->meow();
}
?>

Instantiation

Creating classes directly is discouraged. Instead, use a factory function that creates the appropriate object and returns it. This provides two benefits:

  1. It provides a layer of indirection, as the function may be written to return a different object (with the same interface) in different circumstances as appropriate.
  2. PHP does not allow class constructors to be chained, but does allow the return value from a function or method to be chained.

Chaining

PHP allows objects returned from functions and methods to be "chained", that is, a method on the returned object may be called immediately, like so:

<?php
// Unchained version
$result = db_query("SELECT title FROM {node} WHERE nid = :nid", array(':nid' => 42));
$title = $result->fetchField();

// Chained version
$title = db_query("SELECT title FROM {node} WHERE nid = :nid", array(':nid' => 42))->fetchField();
?>

@TODO: We should have some standard for when to make a class chainable and when not to.

 
 

Drupal is a registered trademark of Dries Buytaert.