In my previous articles, we looked at ways to avoid collisions between similarly named functions by putting them in a class file. In this one, we'll see how to use namespaces to avoid collisions between similarly named functions and classes.
Even if you don't use namespaces in your code, it's important to understand how they work if you need your code to interact with a namespaced third-party library like GuzzleHttp, or a namespaced content management system such as MODX 3, Magento, ProcessWire, or Concrete.
Overview
Suppose you have a section of code that has a save() function in its global scope (that is, not inside a function or class). At the top of your main code file, you "include" a file containing code that also has a save() function in its global scope. Imagine that the save() function in the included code saves a user variable, which is an array of use data to a file and the save() function in the main code saves the user object to the database.
Obviously, this is a recipe for disaster. When you call save(), the code of the wrong save() function may execute. It will either corrupt the file or database, or it will crash because its argument is of the wrong type.
A similar problem exists for classes. You might, for example, have two separate User classes in the case described above.
One common solution to this is to rename the functions and classes. You could add a descriptive prefix to the function and class names, but changing them all could be a big job. It could also create errors if you missed any of the necessary changes.
In a small project, prefixes could be an acceptable solution, but with a large project involving many coders, a better, more reliable method is necessary. Using namespaces provides an organized way to automatically add prefixes to all functions, class names, interfaces, and constants. Even in a small project, using one or more namespaces can help make the code more robust by preventing name collisions.
What's a Namespace?
The GeeksForGeeks website defines namespaces like this: "A namespace is a hierarchically labeled code block holding a regular PHP code."
If you're a beginner at PHP, I'm going to guess that the definition above is not much help to you.
The PHP Manual's definition is better, but still probably not satisfactory: "In the broadest definition namespaces are a way of encapsulating items."
To be fair, both sources elaborate on the definition and provide examples. The PHP Manual goes on to point out the two problems namespaces are meant to solve:
- Avoiding name collisions for functions, classes, and constants by providing a structured prefix for them
- Providing shorter aliases to save us from having to type those very long prefixes
We'll get to the aliases in the next article, but first let's look at the namespace itself, with a concrete example.
Suppose we're writing code for a veterinary office. Here's an abbreviated version of what our directory structure might look like:
/* PROJECT_BASE_PATH is a constant that will
be set to the full path of the VeterinaryOffice
directory; it must end with a slash */
PROJECT_BASE_PATH
src/
Controllers/
Controller.php
Model/
Pets/
Dog.php
Cat.php
The PROJECT_BASE_PATH would be a constant holding the full path to the project's root directory (VeterinaryOffice). All the directories below it would be relative to that path.
Conventions
Notice that all our class names start with a capital letter, and so do our Directories and class file names. This is a common practice. The file and directory names on Unix-style servers are case-sensitive. On Microsoft Windows and Apple's iOS, they are not case-sensitive. Using a standard scheme for capitalization helps prevent the case where code developed on a Windows or iOS machine runs fine there, but fails when moved to a Unix-style server.
There's another good reason for doing it this way. It looks ahead to using an autoloader to avoid having to "include" all the files (more on this in an upcoming article). Namespace names are not case-sensitive in PHP, but Composer, the most popular and convenient way to create an autoloader is picky about capitalization. By convention, class names begin with a capital letter. So do parts of a namespace making file and directory names match the namespace makes life easier for the coder.
Putting all the class files under a src/ directory (as MODX 4 does) is also a convention. It makes them easier to find. It also makes creating an autoloader simpler, since it only needs to look in one place for the classes.
A further benefit of using these conventions is that, as more people and packages use Composer, it makes your code look like other people's code, making it easier to follow. It also makes it possible to release your code as a Packagist package for others to use.
Pets\ directory, since the Cat and Dog class files could come directory below the Model/ directory. It's there because we would probably want to add other directories like Clients/ and Veterinarians/ below the Model/ directory, with their own classes.
The directory structure itself is namespaced in the abstract sense (although it doesn't necessarily use PHP namespaces). The Controller class, for example, is distinguished from other controllers that might exist inside or outside the project because any reference to its files must be prefixed with PROJECT_BASE_PATH . VeterinaryOffice/Controllers/. Other Controllers/ directories would have a different prefix, so when you load a Controller class, it will be the one you want.
Here are the namespaced Dog, Cat, and Controller classes:
Dog Class File
<?php
namespace VeterinaryOffice\Model\Pets;
class Dog {
function __construct() {
/* constructor code here */
}
function speak() {
return 'arf';
}
}
Cat Class
<?php
namespace VeterinaryOffice\Model\Pets;
class Cat {
function __construct() {
/* constructor code here */
}
function speak() {
return 'meow';
}
}
Controller Class File
<?php
namespace VeterinaryOffice\Controllers;
class Controller {
function __construct() {
/* constructor code here */
}
public static function sayHello() {
echo "\nController says Hello!";
}
/* Other controller methods here */
}
Notice the location of the namespace line in the files above. It must be the first line of the file (after the opening PHP tag), and should be followed by a blank line. I prefer the format above, but this is also acceptable:
<?php namespace VeterinaryProject\Controllers;
The backslash between the parts of the namespace is not a directory separator. It must be a backslash or PHP will throw a fatal parse error before the code even executes. A good code editor like PhpStorm will flag this as an error. In fact PhpStorm will tell you when anything related to namespaces is wrong, including incorrect new statements, as long as your code is part of a PhpStorm project. It will also autocomplete the namespaces for you most of the time.
Not only is the backslash not a directory separator, the directory structure is completely irrelevant to the use of namespaces. If we completely reorganized the directory structure, our namespaced code would still work. That said, when using namespaces, directories almost always have a structure that matches the namespaces because it makes the files much easier to find. A more important consideration is that if you're going to use an autoloader to load your classes for you, it will make life much easier if the directory structure matches the namespace structure.
By setting the namespace, we're telling PHP that when someone uses this class, it is under the VeterinaryOffice namespace so it won't be confused with other classes of the same name. The namespace given at the top of the file is essentially a prefix for the classes below it.
Of course this means that when we refer to the class with new, instanceof, or class_exists(), we have to specify the namespace it belongs to. Now that we have our namespace set up for the class files, let's look at how we can use them. Because we're not using an autoloader, we have to load the class files the old-fashioned way with include or require (we'll add a new, namespace-based autoloader in a later article). Even without the autoloader, though, we'll still have the collision-avoidance that comes with using namespaces.
Using Namespaced Classes
Here's some code that uses our new, namespaced classes.
<?php
namespace VeterinaryOffice;
/* Note: PROJECT_BASE_PATH needs to end in a slash (/) */
const PROJECT_BASE_PATH = 'full/path/to/VeterinaryOffice/';
$basePath = PROJECT_BASE_PATH . 'src/';
require $basePath . 'Model/Pets/' . 'Dog.php';
require $basePath . 'Model/Pets/' . 'Cat.php';
require $basePath . 'Controllers/ . 'Controller.php';
if (class_exists('VeterinaryOffice\Model\Pets\Dog'), false) {
$dog = new Model\Pets\Dog();
}
if ($dog && ($dog instanceof Model\Pets\Dog)) {
echo "\nDogs say: " . $dog->speak();
}
if (class_exists('VeterinaryOffice\Model\Pets\Cat'), false) {
$cat = new Model\Pets\Cat();
}
if ($cat && ($cat instanceof Model\Pets\Cat)) {
echo "\nCats say: " . $cat->speak();
}
if (class_exists('VeterinaryOffice\Controllers\Controller', false)) {
$controller = new Controllers\Controller();
}
if ($controller && ($controller instanceof Controllers\Controller)) {
$controller::sayHello();
}
Notice that, although the src/ directory is part of our "require" statements (as it must be for PHP to find the files), it is not in our namespaces anywhere else in the code. It's not part of the namespace. This is also a widely used convention, and the reason its name is in lowercase.
With the namespaced classes, we can no longer write new Dog(). PHP would consider that to be in the VeterinaryOffice namespace (because of the namespace line at the top) namespace and would refuse to instantiate it because there's no Dog class directly under VeterinaryOffice). In other words, there's no VeterinaryOffice\Dog class. The only Dog class available is VeterinaryOffice\Model\Pets\Dog.
Directory, DirectoryIterator, or Exception, you need to prefix the class name with a backslash. That tells PHP to look for the class in the global namespace, rather than in the namespace declared at the top of the file:
$di = new \DirectoryIterator($path);
If the backslash were omitted in the code above, PHP would look for a VeterinaryOffice\DirectoryIterator class, (again, because of the namespace line at the top, and would throw an error because it doesn't exist.
Another thing you might notice is that the namespace here is shorter than the ones in the class files. This is because we have classes in different locations and a file can only have one namespace line. Some people put all classes in a single classes directory to avoid this, but we've put them in subnamespaces. If we used namespace VeterinaryOffice\Model\Pets for the namespace, we could use our Dog and Cat classes as just Dog() and Cat(), but we would have no way use our Controller class. If we used VeterinaryOffice\Controllers for the namespace, we'd have the opposite problem. We couldn't use the Dog and Cat classes. In order to instantiate all classes, we have to go up the tree (or backwards in the namespace) until we find a part that's common to all classes. In this case it's
VeterinaryOffice (technically called the "namespace root"). All
our classes can then be specified by adding to the end of that namespace root.
When we use new or instanceof, we have to specify the part of the namespace that follows the namespace specified at the top of the file. Since the file's namespace is VeterinaryOffice, and the namespace is added as a prefix for namespace references below it, Model\Pets\Cat resolves to VeterinaryOffice\Model\Pets\Cat.
Notice that this not the case with class_exists(). That function requires a "fully qualified class name." In other words, the full namespace path, for example (for our Cat class):
if (class_exists('VeterinaryOffice\Model\Pets\Cat'), false) {
}
The fully qualified class name is required for class_exists() because, unlike the other namespaces used in the code, its argument is a string.
The sayHello() method of the Controller class is declared as static. that means we have to call it with the scope resolution operator:
$controller::sayHello();
Using $controller->sayHello() may work, but it is deprecated and will likely throw an error in future versions of PHP. PhpStorm will also warn you about this mistake.
Because the $controller variable was set to a namespaced class, we don't need to provide the namespace for this call — it's already contained in the $controller variable.
Since the sayHello() method is static, we can call it without instantiating the Controller class. So a file that just included the Controller class could call that method with a fully qualified class name and no other code, like this:
VeterinaryOffice\Controllers\Controller::sayHello();
The uses of instanceof and class_exists in the code above are error checks. They are not required for using namespaces, they're in our code above to demonstrate how to use them with namespaces.
I should also mention that the code that uses our namespaced classes doesn't have to specify a namespace at the top. It could also use a different namespace than VeterinaryOffice. The namespace line does protect any code under it from collisions, but it could be left out or changed. In either case, all the class names in the file would have to be fully qualified (like the class_exists() calls already are). We'd have to add the VeterinaryOffice\ prefix to all namespace paths in the code.
Constants
We haven't talked about constants, but they work almost exactly like namespaced classes. If we modify our Dog class to add a global constant outside the class:
const OUTSIDE = 'Outside Constant';
And add a class constant inside the Dog class (at the top):
const INSIDE = 'Inside Constant';
In another file that includes the Dog class file, we can do this if the other file contains namespace VeterinaryOffice; at the top:
echo "\nOutside: " . Model\Pets\OUTSIDE; /* displays 'Outside Constant' */
$dog = new Model\Pets\Dog();
echo "\n" . $dog::INSIDE; /* displays 'Inside Constant' */
If the file containing the code that uses our namespaced classes does not have the namespace VeterinaryOffice; line at the top or has a different namespace, we'd have to do this:
echo "\nOutside: " . VeterinaryOffice\Model\Pets\OUTSIDE; /* displays
'Outside Constant' */
$dog = new VeterinaryOffice\Model\Pets\Dog();
echo "\n" . $dog::INSIDE; /* displays 'Inside Constant' */
Functions
If the namespaced class file has functions outside the class, they are in the global scope, but they still need to be prefixed with the namespace when called.
If, in the Dog class file, we add this function outside the class:
function outsideFunction() {
echo "\noutsideFunction";
}
Assuming the file doing the calling has namespace VeterinaryOffice; at the top, and has included the Dog class file, we'd call that function like this:
Model\Pets\outsideFunction();
Without the namespace at the top of the calling file, we'd do this:
VeterinaryOffice\Model\Pets\outsideFunction();
Even though that function is in the global scope, we've protected it with the namespace, so it can't conflict with another global function with the same name. Note that though you can protect constants this way, global variables in namespaced files are not protected. They are not bound to the namespace and continue to be available globally with no namespace prefix. Using a namespace prefix on them will throw an error. Namespaces only apply to classes, functions, constants, and interfaces. This is another good reason not to use your own global constants in your code.
Streamlining things
You probably noticed that creating the code that uses the namespaced classes would require a lot of typing compared to the same code with un-namespaced classes.
There's a relatively easy way to avoid most of that using what are technically called imports. We'll see how to do that in my next article.
Bob Ray is the author of the MODX: The Official Guide and dozens of MODX Extras including QuickEmail, NewsPublisher, SiteCheck, GoRevo, Personalize, EZfaq, MyComponent and many more. His website is Bob’s Guides. It not only includes a plethora of MODX tutorials but there are some really great bread recipes there, as well.