Object-oriented code

Last updated on
21 September 2023

This documentation needs review. See "Help improve this page" in the sidebar.

Note: Changes to Drupal coding standards are proposed and discussed in issues in the Coding Standards project.

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

Class-related coding standards covered elsewhere:

Declaring Classes

Where to define and place your classes?

Best practices include having one class or interface or trait per file. That file should be named for the class, such that the file name for FooInterface would be FooInterface.php.

From Drupal 8, classes are autoloaded based on the PSR-4 namespacing convention.

In core, the PSR-4 'tree' starts under core/lib/.

In modules, including contrib, custom and those in core, the PSR-4 'tree' starts under modulename/src.

Defining a class in your module's .module file is only possible if the class does not have a superclass which might not be available when the .module file is loaded. It's best practice to move such classes into a PSR-4 source directory.

Indenting and Whitespace

Please refer to the Indenting section for basic indenting that is applicable in OO as well. Here are a few additional examples that are specific to OO.

Leave an empty line between start of class/interface definition and property/method definition:

class GarfieldTheCat implements FelineInterface {
  // Leave an empty line here.
  public function meow() {
...
...
...

Leave an empty line between end of property definition and start method definition:

...
...
...
  protected $lasagnaEaten = 0;
  // Leave an empty line here.
  public function meow() {
    return t('Meow!');
  }

Leave an empty line between end of method and end of class definition:

class GarfieldTheCat implements FelineInterface {
...
...
...
  public function eatLasagna($amount) {
    $this->lasagnaEaten += $amount;
  }
  // Leave an empty line here.
}

Naming conventions

  1. Classes and interfaces should use PascalCase naming.
  2. Methods and class properties should use camelCase naming. In Drupal 8, properties of configuration entities are exempt of these conventions. Those properties are allowed to use underscores.
  3. If an acronym is used in a class or method name, make it CamelCase too (SampleXmlClass, not SampleXMLClass). [Note: this standard was adopted in March 2013, reversing the previous standard.]
  4. 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.
  5. Names should not include "Drupal".
  6. Class names should not have "Class" in the name.
  7. Interfaces should always have the suffix "Interface".
  8. Test classes should always have the suffix "Test".
  9. Protected or private properties and methods should not use an underscore prefix.
  10. Classes and interfaces should have names that stand alone to tell what they do without having to refer to the namespace, read well, and are as short as possible without losing functionality information or leading to ambiguity. Notes:
    • If necessary for clarity or to prevent ambiguity, include the last component of the namespace in the name.
    • Exception for Drupal 8.x: due to the way database classes are loaded, do not include the database engine name (MySQL, etc.) in engine-specific database class names.
    • Exception for test classes: Test classes only need to be unambiguous within the context of the module they are testing.

Stand-alone name examples:

Namespace Good name Bad names
Drupal\Core\Database\Query\ QueryCondition Condition (ambiguous)
DatabaseQueryCondition (Database doesn't add to understanding)
Drupal\Core\FileTransfer\ LocalFileTransfer Local (ambiguous)
Drupal\Core\Cache\ CacheDatabaseDriver Database (ambiguous/misleading)
DatabaseDriver (ambiguous/misleading)
Drupal\entity\ Entity
EntityInterface
DrupalEntity (unnecessary words)
EntityClass (unnecessary words)
Drupal\comment\Tests\ ThreadingTest CommentThreadingTest (only needs to be unambiguous in comment context)
Threading (not ending in Test)

A complete example of class/interface/method names:

interface FelineInterface {
  // Leave an empty line here.
  public function meow();
  // Leave an empty line here.
  public function eatLasagna($amount);
  // Leave an empty line here.
}
  // Leave an empty line here.
class GarfieldTheCat implements FelineInterface {
  // Leave an empty line here.
  protected $lasagnaEaten = 0;
  // Leave an empty line here.
  public function meow() {
    return t('Meow!');
  }
  // Leave an empty line here.
  public function eatLasagna($amount) {
    $this->lasagnaEaten += $amount;
  }
  // Leave an empty line here.
}

Use of interfaces

The use of a separate interface definition from an implementing class is strongly encouraged because it allows more flexibility in extending code later. A separate interface definition also neatly centralizes documentation making it easier to read. All interfaces should be fully documented according to established documentation standards.

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.

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 use a class as the type in type hinting. If specifying a type, always specify an Interface. That allows other developers to provide their own implementations if necessary without modifying existing code.

Example:

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

// Wrong:
function make_cat_speak(GarfieldTheCat $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. This is known as a 'fluent interface.' Here is an example:

// 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();

As a general rule, a method should return $this, and thus be chainable, in any case where there is no other logical return value. Common examples are those methods that set some state or property on the object. It is better in those cases to return $this rather than TRUE/FALSE or NULL.

In the case where you have a fluent interface for a class, and the code spans more than one line, the method calls should be indented with 2 spaces:

$query = db_select('node')
  ->condition('type', 'article')
  ->condition('status', 1)
  ->execute();

Drupal 7 class/interface autoloading

Use an .inc file and use files[] in the .info file to extend a class or implement an interface.

If you include a file that extends a class or implements an interface, PHP generates a fatal error if the parent class or interface is not loaded. So, if a class is provided by a contributed module, or core in some cases, it is not safe to put your classes in a .module file. It's better to use an .inc file and use files[] in your .info file. For example, even if you have a dependency on a module, it's possible that both your module and the dependency are disabled when your .module file is included. Since the registry won't auto-load a class from a disabled module, this would cause an error. Also, when hook_boot() is run, module dependencies aren't loaded. So, if you add a class, then later implement hook_boot(), your module could be loaded without the dependency, and that will also generate a fatal error. Using an .inc file and using files[] in your .info file is needed to avoid those errors.

Help improve this page

Page status: Needs review

You can: