PHP OOP - Namespaces

Last revision:

Introduction

Namespaces are a way to make your class names and function names unique so they won't conflict with other libraries that may be using the same class and function names.

In this unit you'll learn how namespaces help avoid naming conflicts.

Packages

When you build any application, it's always a good idea to reuse code as much as you can, instead of reinventing the wheel and possibly introducing errors that could have been avoided.

Modern PHP applications contain dozens or hundreds of small code libraries called packages, often distributed via the Packagist repository. While some packages are only suitable for a single application, many packages are generic and can be reused across different applications.

Package examples

The psr/log package helps developers create uniform logging systems, and is used (depended upon) by more than 5000 other packages and applications, including Drupal.

The drupal/coder package is a library for automated Drupal code reviews and coding standard fixes. It is used by Drupal itself and by external tools that automate Drupal code updates and quality checks.

Symfony's symfony/console package lets you to create command-line commands that can be used for any purpose, including recurring tasks, deployment tasks, and import/export tasks.

Problem: naming conflicts.

Have you ever noticed how people in the United States often say they live in "Paris, Texas" or "Flint, Michigan"? 

They qualify the name of their city by adding the name of the state, to avoid confusion with cities by the same name in other US states.

You could say that "City Name, State name" is the fully qualified name of a city. There may be many cities named Paris, but there's only one Paris, Texas.

Naming conflicts in code.

Even if your application doesn't use many packages, it's still possible that some of them use the same class names or function names.

If not handled correctly, this can lead to naming conflicts, which cause serious trouble.

Consider the following scenario:

  • PHP has a built-in strtoupper() function which transforms a string into capital letters. 
  • We want to create our own strtoupper() function that transforms the first letter into uppercase and the other letters into lowercase. 
  • There is already another built-in string function for this named ucfirst(). 
  • So our goal is to make our own strtoupper() function and make it produce the same output as ucfirst() does. 

When we try to define a function named strtoupper(), an error message is produced because PHP already has a built-in function named strtoupper():

Example 1

// Transform string.
// Make first letter uppercase and the rest lowercase.
// This will produce an ERROR.

function strtoupper($str) {
  return ucfirst($str);
}

print strtoupper('hello world');

Output:

PHP Fatal error:  Cannot declare strtoupper().

Solution: namespaces

Namespaces solve this problem. They let you reuse function or class names that have already been declared elsewhere, but without causing a naming conflict.

Here's how it works: use the namespace keyword to declare a namespace of your choice above your code:

Example 2

Code
<?php

namespace MyTools;

function strtoupper($str) {
  return ucfirst($str);
}

print strtoupper('hello world');

The strtoupper() function now belongs to the MyTools namespace, which makes the function's fully qualified name \MyTools\strtoupper().

In a PHP code file, as soon as you have declared a namespace, all the functions, classes, interfaces, and traits you define will automatically belong to that namespace.

Any time you work in a namespace - any time you write code in file where a namespace was declared - the functions, classes, etc defined under that namespace will have priority when called.

This is demonstrated in the strtoupper() example: our version of strtoupper() is used instead of the default version of strtoupper().

But what if you have redeclared an existing function but want to use the original one? Use the fully qualified name:

The built-in version of strtoupper() is defined under the global namespace \, so its fully qualified name is \strtoupper()

Our version of strtoupper() is defined under the MyTools namespace, so its fully qualified name is \Mytools\strtoupper().


If this confuses you, here's a different way of looking at it:

If you do NOT declare a namespace in your code file, PHP assumes the global namespace \.

As a consequence, when you call any function, such as strtoupper(), PHP will combine the implicit \ namespace with the function name strtoupper(), and call \strtoupper().

On the other hand, if you do declare a namespace such as MyTools, redefine strtoupper(), and then call strtoupper(), PHP will combine your explicit namespace MyTools with the function name strtoupper() and call \MyTools\strtoupper().

If you now want to call the original strtoupper() function, you need to use the fully qualified name to tell PHP which one to use: \strtoupper() instead of strtoupper().

Comparing usage examples

Let's look at a few practical examples to illustrate what was just explained.

At the end of the examples you'll find an activity that asks you to explain the output of each of the examples.
 

Example 3: using the built-in \strtoupper() function

Code
<?php

print strtoupper('hello world');
print \strtoupper('hello world');

Example 4: Redeclaring strtoupper() outside a namespace (ERROR)

Code
<?php

// This will result in an ERROR.
function strtoupper($str) {
  return ucfirst($str);
}

print strtoupper('hello world');

Example 5: Redeclaring strtoupper() inside a namespace

Code
<?php

namespace MyTools;

function strtoupper($str) {
  return ucfirst($str);
}

print strtoupper('hello world');

Example 6: Redeclaring inside a namespace but calling the original function

Code
<?php

namespace MyTools;

function strtoupper($str) {
  return ucfirst($str);
}

print \strtoupper('hello world');

Example 7a: Calling a namespaced function from outside its namespace

Code
<?php

namespace MyTools;

function strtoupper($str) {
  return ucfirst($str);
}

namespace SomePackage;

print \MyTools\strtoupper('hello world');

Example 7b: Calling a built-in function from the global namespace

Code
<?php

print strtoupper('hello world');

Example 7c: Calling a built-in function from the global namespace, explicitly using the global namespace

Code
<?php

print \strtoupper('hello world');

Activity

For examples 3 to 7c, explain in your own words which version of strtoupper() was used, and why.

Answer / solution

Example 3

The built-in strtoupper() function transforms the whole string into uppercase. Since we're not working in a namespace, the version of strtoupper() from the global namespace (\strtoupper()) is used.

Example 4

A fatal error has occurred because we tried to redeclare function strtoupper() which already exists in the global namespace (it's a built-in function).

Example 5

The "Hello world" output is exactly what we wanted to achieve. We declare a namespace, and redeclare strotupper() inside that namespace.

While still inside the same namespace we call strtoupper().

We don't explicitly mention a namespace when calling strtoupper() so PHP first checks if a strtoupper() has been declared in the current namespace (\MyTools), which is the case, so it uses that one and will not try to find one in the global namespace.

Example 6

We redeclared strtoupper() in our own namespace.

While still in our namespace we explicitly call the strtoupper() function that's part of the global namespace: \strtoupper().

Had we instead called strtoupper() (without the leading backslash), PHP would have used our redeclared version of strtoupper() (see explanation of example 5).

Example 7a

We redeclared strtoupper() in our MyTools namespace.

While working in a different namespace, we want to call that function, so we use its fully qualified name \Mytools\strtoupper(). Since we use the fully qualified name, there is no naming conflict and no doubt which version of strtoupper() will be used.

Example 7b

We are in the global namespace and we are calling strtoupper(), so PHP uses the version of strtoupper() that was declared in the current (global) namespace: \strtoupper().

Example 7c

This is a variation on 7b. We are explicitly calling \strtoupper(), the version declared in the global namespace. There is no possible confusion as we use its fully qualified name.

In this example we are calling \strtoupper() while working in the global namespace, but regardless of which namespace you're working in, when you use the fully qualified name of a function there is never any confusion as to which exact version you want.
 

Multi-level namespaces

Namespaces often have multiple levels to help organize the various code files, classes, etc.

For example, as of Drupal 8, each module's namespace is \Drupal\name_of_the_module, and depending on what the module actually does you will find namespaces like

  • \Drupal\name_of_the_module\Plugins
  • \Drupal\name_of_the_module\Plugins\Block
  • \Drupal\name_of_the_module\Plugins\form
  • \Drupal\name_of_the_module\Controller
     

\Drupal\name_of_the_module\Plugins\Block\MyCustomBlock would be the fully qualified name of a MyCustomBlock class inside the \Drupal\name_of_the_module\Plugins\Block namespace.

And and the fully qualified name of a create() method inside that class would be \Drupal\name_of_the_module\Plugins\Block\MyCustomBlock::create.

In other words, any time you want to refer to a specific method of a namespaced class in documentation like this course, you  can provide the fully qualified name of the class, followed by the scope resolution operator ::, followed by the method name. That way it's clear and unambiguous which exact method on which exact namespaced class you're talking about.

Note that namespaces look a bit like web addresses or folders on a hard drive. The actual code files that contain classes, functions, etc is often organised in folders that follow the same structure as the namespace, but this is NOT mandatory: it's technically possible to place all your code files in one single directory, and still use multi-level namespaces to organise them as if they were stored in different folders.

In general you're free to choose your own namespace names and levels for your own code, but if you write code in the context of a framework like Symfony or Drupal, you also have to follow certain rules about the name and structure of your namespaces, and the underlying folder structure. 

Refer to your framework's documentation for details. 
 

Example

Code
<?php
// We declare a first namespace;
namespace MyTools\Strings;

function strtoupper($str) {
  return ucfirst($str);
}

// We declare a second namespace.
namespace MyTools;

class User {

  private string $name;

  public function setName(string $name) {
    $this->name = strtoupper($name);
  }

  public function getName() {
    return $this->name;
  }

}

$user = new User();
$user->setName('maia');
print $user->getName();

In this example we declare two namespaces:

  • MyTools\Strings
  • MyTools

We defined strtoupper() inside the MyTools\Strings namespace, so that function's fully qualified name is \Mytools\Strings\strtoupper().

We created a User class inside the MyTools namespace, so the class' namespace is \MyTools\User, and it  contains a setName() method.

Inside \MyTools\User::setName() we call strtoupper().

Which version of strtoupper() do you think will be called? \strtoupper() or \MyTools\Strings\strtoupper()?

The answer is: \strtoupper().

Why? Because we're working in the MyTools namespace, and directly inside the MyTools namespace there exists no strtoupper() function: \MyTools\strtoupper() does not exist.  

However, \Mytools\Strings\strtoupper() does exist.

But that version of strtoupper() lives in the Mytools\Strings namespace, not in the MyTools namespace where we are currently working.

PHP tries to find a strtoupper() function in the MyTools namespace, doesn't find one, then tries in the global namespace, and there it successfully finds \strtoupper().

If instead of \strtoupper() we wanted to call \Mytools\Strings\strtoupper() from inside \MyTools\User::setName(), we can do two things:

Option 1

Code
<?php
namespace MyTools;

class User {
  private string $name;

  public function setName(string $name) {
    $this->name = \MyTools\Strings\strtoupper($name);
  }

}

We provide the fully qualified name \MyTools\Strings\strtoupper() to avoid any confusion.

Option 2

Code
<?php

namespace MyTools;

class User {
  
  private string $name;

  public function setName(string $name) {
    $this->name = Strings\strtoupper($name);
  }

}

We provide a partial, relative namespace: Strings\strtoupper().

When PHP tries to figure out the fully qualified name for Strings\strtoupper(), it first checks if there is a sub-namespace Strings inside the current namespace MyTools. 

If there is, it tries to find a strtoupper() function inside the MyTools\Strings namespace.

If there is no \MyTools\Strings\strtoupper(), PHP will check if there is a Strings namespace inside the global namespace: it checks if \Strings\strtoupper() exists.

If that can't be found either, PHP will return an error:  "undefined function Strings\strtoupper()"
 

Namespace resolution

As demonstrated in the previous section on multi-level namespaces, there is a consistent and predictable mechanism that PHP uses to figure out which version of a class or function you want to use.

If you don't provide a fully qualified name for the function you're calling, PHP will prefix the given function name with the name of the current namespace. If you're not in a specific namespace, you're in the global namespace.

→ Study example 8 carefully and don't skip the activity that follows.
 

Example 8

Code
<?php

namespace MyTools\Strings;

function strtoupper() {
  // ...
}

namespace MyTools;

// 8a
// → call \MyTools\strtoupper() if it exists
// → call \strtoupper() if it exists
print strtoupper('hello');

// 8b
// → call \strtoupper() if it exists
print \strtoupper('hello');

// 8c
// → call \MyTools\Strings\strtoupper() if it exists
print Strings\strtoupper('hello');

// 8d
// → call \Strings\strtoupper() if it exists
print \Strings\strtoupper('hello');

// 8e
// → call \MyTools\MyTools\Strings\strtoupper() if it exists
print MyTools\Strings\strtoupper('hello');
  
// 8f
// → call \MyTools\Strings\strtoupper() if it exists
print \MyTools\Strings\strtoupper('hello');

Activity

For examples 8a to 8f, which version of strtoupper() gets called?

If only one option is given, is it a valid option (will it work), or will an error ("undefined function") be emitted?

Answer / solution

8a

  • \MyTools\strtoupper() is undefined.
  • \strtoupper() exists and will be called

8b

  • \strtoupper() exists and will be called.

8c

  • \MyTools\Strings\strtoupper() exists and will be called.

8d

  • \Strings\strtoupper() is fully qualified, but undefined.
  • Since a fully qualified name is provided in the first place, PHP does not try to find anything in the global namespace. A "undefined function" error is produced.

8e

  • This is a tricky one. Look at the code carefully.
  • From the namespace MyTools we attempt to call MyTools\Strings\strtoupper(). 
  • This namespace does NOT start with a backslash so it is NOT a fully qualified name. As a result, PHP will try to prefix it with the MyTools namespace to see if \MyTools\MyTools\Strings\strtoupper() exists, but of course it does not.
  • Error: "undefined function".

8f

  • We try to call \MyTools\Strings\strtoupper(), which starts with a slash so it's a fully qualified name.
  • \MyTools\Strings\strtoupper() exists and will be called.
     

Summary

  • Namespaces provide a safe space for your methods and classes so their names don't conflict. 
  • Namespaces let you organise your functions and classes logically, even if they are all located in the same single physical directory.
  • A function or class' fully qualified name consists of a backslash, followed by its namespace, followed by its name: function create() in namespace MyTools → \MyTools\create().
  • If you provide a partial namespace (one that does not start with a backslash), PHP will prefix it with the name of the current namespace. 
  • If you're not working in a specific namespace, you are working in the global \ namespace.