OOP-NOOB Series – That Which Cannot Be Named

This article is part of the OOP-NOOB Series.

What Are We Talking About Here?

Real namespaces were only introduced in PHP 5.3, so it is quite normal to see what is called “poor man’s namespacing” in codebases that were started before PHP 5.3, or that try to still support PHP versions older than that.

How Is It Being Used?

When no real namespace is being used, all the functions that are defined within a PHP script execution lifecycle share a common global namespace. Each name within that global namespace can only be defined once. When a name is already taken, PHP will throw a fatal error when defining it again. This is referred to as a namespace collision (or naming collision).

// ---[ File: as_silly_example_conflicting_syntax.php ]---

// Constant risking a naming collision.
const KEY = 'answer';

// Global variable risking a naming collision.
$data = [
    KEY => 42,
];

// Function declaration risking a naming collision.
function get_data( $key ) {
    global $data;
    return $data[ $key ];
}

// ---[ File: use_as_silly_example_conflicting_syntax.php ]---

// Output will (probably not) be "answer is 42".
echo KEY . ' is ' . get_data( KEY );

When PHP did not yet have any namespaces, people (those who cared) generally used prefixes in front of everything that was to be defined within the global namespace (functions & global variables/constants). So, instead of risking that anyone else might also try to define the function get_data(), they prefixed it with a vendor/author and/or project prefix, like as_silly_example_get_data(). This greatly reduces the potential for collisions. However, it can quickly make the code unwieldy, as identifiers tend to get really long, while still not conveying any additional meaning.

// ---[ File: as_silly_example_prefix_syntax.php ]---

// Prefixed constant.
const AS_SILLY_EXAMPLE_KEY = 'answer';

// Prefixed global variable.
$as_silly_example_data = [
	AS_SILLY_EXAMPLE_KEY => 42,
];

// Prefixed function declaration.
function as_silly_example_get_data( $key ) {
	global $as_silly_example_data;
	return $as_silly_example_data[ $key ];
}

// ---[ File: use_as_silly_example_prefix_syntax.php ]---

// Output will (more reliably) be "answer is 42".
echo AS_SILLY_EXAMPLE_KEY
     . ' is '
     . as_silly_example_get_data( AS_SILLY_EXAMPLE_KEY );

This is where the poor man’s namespace comes into play! It wraps the entire code into one single class, which serves as a pseudo-namespace. Within that class, you can name your identifiers whatever you want, as long as the class name itself is safe from collisions.

// ---[ File: as_silly_example_class_syntax.php ]---

class AS_Silly_Example
{

    // Class-internal constant.
    const KEY = 'answer';

    // Class-internal variable.
    public $data = [
        self::KEY => 42
    ];

    // Class-internal function (= method) declaration.
    public function get_data($key)
    {
        return $this->data[$key];
    }

}

// ---[ File: use_as_silly_example_class_syntax.php ]---

// Output will (still) be "answer is 42".
$example = new AS_Silly_Example();

echo AS_Silly_Example::KEY
    . ' is '
    . $example->get_data( AS_Silly_Example::KEY );

So, with this version of the code, you can see that everything that is being referenced within the class is nice and short.

What’s The Problem?

Now, when you extend the code above, every new function goes into your class (which makes it a method, btw). Your class will grow and grow, and you are relieved to know that you will never have to worry about a naming collision. All swell, right?

The main issue is that you might think that you’re using object-oriented programming (OOP) principles. After all, you have a class construct, and you’re calling methods instead of functions… This is missing the point of what OOP is, though. OOP is not about syntax. Calling something a class does not make it OOP-y.

Wrapping a class around procedural code is not OOP. Still procedural, with different syntax. Click To Tweet

I will probably write a more elaborate article about what the main difference is between procedural programming and object-oriented programming at some point. There are lots of concepts that go hand in hand to make up the actual value that OOP provides. One of the more important ones is that it lets you split complexity in such a way that you can deal with only one part of the complexity without needing to bother about the rest. When you have a huge file with procedural code, wrapping that code in a class { } does not help you in any way to better deal with the inherent complexity of that code.

OOP lets you divide the overall complexity into smaller parts that you can deal with one at a time. Click To Tweet

You can, of course, split up procedural code as well. However, whenever you want to make a change to one part of it, you need to consider the effect on the entire rest of your code.

What OOP brings to the table (and we’re talking about a concept here, not a specific syntax) is contracts. If you divide your code, a contract between these two separate parts sets up the requirements they need to fulfill. If that contract is properly set up, both parties can independently make changes without considering the other, for as long as they adhere to the terms of the contract.

These contracts are one of the key concepts, and key benefits, that an OOP approach can provide.

Better Practice

What we have above in our last example is:

  • procedural code
  • …where we need to deal with the entire complexity at once
  • …where we add additional unnecessary complexity by (ab)using class syntax

If you really need to stay compatible with PHP 5.2 (and I don’t recommend you do), then you can either go with the prefixed code or with the poor man’s namespacing technique, knowing that this is not OOP!

If you use any PHP version greater or equal to 5.3, please, go with a real namespace instead! If you’re not comfortable writing proper OOP code, avoid the class syntax, it doesn’t do much by itself.

With our above example, this will produce the following code:

/ ---[ File: as_silly_example_namespace.php ]---

namespace AS_Silly_Example;

// Namespaced constant.
const KEY = 'answer';

// Variables cannot be namespaced, so this is global and needs to
// be prefixed.
$as_silly_example_data = [
	KEY => 42,
];

// Namespaced function declaration.
function get_data( $key ) {
	global $as_silly_example_data;
	return $as_silly_example_data[ $key ];
}

// ---[ File: use_as_silly_example_namespace.php ]---

// Import namespaced elements into current namespace.
// Note: Importing constants and functions only works for PHP 5.6+.
use AS_Silly_Example\KEY as the_key;
use AS_Silly_Example\get_data;

// Output will (still) be "answer is 42".
echo the_key . ' is ' . get_data( the_key );

There’s a small issue with procedural code using namespaces in that you can’t namespace a global variable. As you should strive to avoid global state anyway, this should not be a big deal.

However, what we gained though is that there’s a clear distinction in the consuming code between locating the different dependencies (very explicit, each dependency needs to be carefully considered), and actually using them once they’ve been imported (very short and practical).

So, using namespaces will lead to source code where each file begins by enumerating every single of its external dependencies (external to the own namespace). This is huge, as controlling the dependencies is one of the biggest headaches in software development.

Code with namespaces enumerates every single dependency, greatly helping to control them. Click To Tweet

Conclusion

While I doubt that this article will make you switch to using namespaces for all of your future code, I certainly hope that it helps to spread the notion that Object-Oriented Programming is not about syntax.

Leave a Comment