📖 PHP Autoloading and Namespaces

Autoloading is a process recommended in the Autolaoading Standards Recommendation of the PHP Framework Interop Group. It provides enhanced loading of classes from file paths that are called but not explicitly included (required) in a PHP script. Namespaces are used to map classes to files and directories. When used in conjunction with the autoloader, PHP scripts can be reorganized to improve clarity and follow recommended universal development patterns by autoloading files from called classes.

Class not found error

If your script tries to create an instance of a class, it must have the class definition to do so. Normally, we would include the file that contains the class definition with a require statement that points to the file. These require statements begin to propagate throughout the application as it grows. If we miss one, the entire app will fail with a Fatal Error.

You can test this by removing the require "src/router.php"; statement in the index.php file and reload the browser. My app returned this error.

Fatal error: Uncaught Error: Class "Router" not found in C:\xampp\htdocs\profherd\php\adv-php\mvc-routing\index.php:9 Stack trace: #0 {main} thrown in C:\xampp\htdocs\profherd\php\adv-php\mvc-routing\index.php on line 9

One way to help simplify our code and avoid these types of errors is to add an autoloader. This autoloader will automatically attempt to load the file containing the class definition for any class called that does not have a file already associated. Using this approach, we can then eliminate the require statements that import the class files. To do this properly, we will follow the PSR-4 standards of the PHP Framework Interop Group.

Implement the spl_autoload_register() Function

To use the PHP autoloader, you need to add it to the script in your index.php file. The goal being to remove all require statements in the code base, we will begin with removing the require "src/router.php"; statement and replace it with code that calls the built-in PHP spl_autoload_register() function to attempt to load a file when a class is called that was not defined.

Remove the require "src/router.php"; statement from the index.php file. Replace it with the following call to the spl_autoload_register() function before we try to create a new Router object. In this initial implementation, we will simply dump the return value to the browser so we can see how it works. Add the code below to your index.php file and load the file in your browser.

<?php

$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

spl_autoload_register( function (string $class_name) {

    var_dump($class_name);

});

$router = new Router;

This should return something like string(6) "Router" which should match the name of the new object being created. This verifies the autoloader was executed when the page was loaded. We can use this to find the file needed to load the class.

<?php

$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

spl_autoload_register( function (string $class_name) {

    require "src/$class_name.php";

});

$router = new Router;

Replace the var_dump($class_name); statement with a require "src/$class_name.php"; statement. Notice I've added the parent directory and the .php suffix to the class name. This will return src/Router.php to the script. Your script might fail with an error saying it cannot find that file. It is likely that your system, like most servers, are case-sensitive and cannot find the (uppercase) Router.php file.

File Name case-sensitivity

We need to fix this by updating our file naming convention in our project. Rename the router.php file to Router.php noting the case sensitivity of the file names. Try refreshing your browser. You should now see your page content load properly.

Namespaces

Now that the autoloader is loading files when we call a class, let's remove the require "src/controllers/$controller.php"; statement lower in the index.php file. Refresh your browser. Now we get a new error in the browser.

Warning: require(src/products.php): Failed to open stream: No such file or directory

The case of the file is ok, but the path is wrong. In our call to the autoloader, we prefixed the class name with src/ since this was where the Router.php file was located. However, the products.php file file is in a different directory path. We need to fix this. The solution is to use namespaces.

Namespaces are used to map a class to its parent directory. You declare the namespace in the file before the class definition. The namespace should match the file structure of the file that contains the class. Namespaces and their related directories should use StudlyCaps names.

Let's add the Framework namespace to the Router.php file since the router is part of the application framework and shouldn't change with the application like the controllers and models will.

<?php

namespace Framework;

class Router
{
...router code here
}
Reorganizing the Code Base

First thing that needs to be done is to organize the code base into a logical structure where the namespaces can be defined. Since the Router.php file is part of the framework we are building it needs to go into a Framework directory. Create the Framework directory in the src directory and move the Router.php file into the Framework directory.

The controllers and models are part of the app. They can change with application changes, so we will create a new App directory for those sub-directories. The App directory will be sub-directories of the src directory along side the Framework directory.

We will also need to rename the controllers and models directories to Controllers and Models, so their names begin with uppercase letters. Then we need to move both of these directories into the App directory.

Finally, the files in these directories need to match the class names with case sensitivity. Change the controller filenames and model filenames to begin with capital letters.

Sample File Directory Structure

File Directory Structure

↧ NAMESPACE-AUTOLOAD (SITE ROOT)
↳ src
| ↳ App
| | ↳ Controllers
| | | ↳ Products.php
| | ↳ Models
| |  ↳ Product.php
| ↳ Framework
|  ↳ Router.php
↳ views
↳ .htaccess
↳ index.php

Adding Namespaces to Files

The namespaces need to be added to the controller and model files. Once they are updated, we can fix the autoloader.

Updating the Autoloader with Namespaces

Now when we call a class we need to prepend it with the namespace. In the index.php file where we create the new Router object, we need to add the namespace to the class. This $router = new Router; becomes this $router = New Framework\Router;. You can check this using var_dump($class_name); in the spl_autoload_register() function call. The browser output will include something like this string(16) "Framework\Router". This is referred to as a fully qualified class name. It include the namespace (path) in the reference to the class.

Notice the class reference has a backslash in the name. Only Windows OS uses these. We need to convert these to forward slashes to make the application more robust. We can do this using the str_replace() function. Add this to the var_dump() in the spl_autoload_register() function to test the output.

var_dump("src/" . str_replace("\\", "/", $class_name) . ".php");

The backslash is a special character so it must be escaped in the first argument "\\". This will replace all backslashes in the reference with forward slashes. Refresh your browser and you should see something like this. string(24) "src/Framework/Router.php"

Once you have this working, you can remove the var_dump and replace it with require to import the necessary class files.

require "src/" . str_replace("\\", "/", $class_name) . ".php";
Why the Backslash (\)?

You may be asking yourself, "Why does the code base use backslashes (\) in namespaces instead of forward slashes (/)?" I could ask you, "How many licks does it take to get to the tootsie roll center of a tootsie pop?" A: The world may never know. AKA: It just do!

In all seriousness, I think the folks who develop the PHP library were trying to solve an ambiguity problem with using namespace separators. It is really techy-nerdy, but you can see the resolution discussion at the PHP Wiki.

Controller Namespace

Add the namespace to to the controller request lower in the page code. Note that the controllers are located in a different namespace than the router. Make sure you add the ucwords() function to convert the controller name to use StudlyCaps names.

$controller = "App\Controllers\\" . ucwords($params["controller"]);
Namespaces and Autolaoding Example in the index.php File
<?php

$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

spl_autoload_register( function (string $class_name) {

    require "src/" . str_replace("\\", "/", $class_name) . ".php";

});

// request fully qualified class
$router = new Framework\Router;

$router->add("/", ["controller" => "home", "action" => "index"]);
$router->add("/products", ["controller" => "products", "action" => "index"]);
$router->add("/products/show", ["controller" => "products", "action" => "show"]);

$params = $router->matchRoute($path);

if ($params === false) {

    exit("No matching route");

}

// request fully qualified class
$controller = "App\Controllers\\" . ucwords($params["controller"]);
$action = $params["action"];

$controller_object = new $controller;

$controller_object->$action();
Other Files with Namespaces

Now that the controllers request error is fixed, we are getting a new error when trying to load the related model class.

Warning: require(src/models/product.php): Failed to open stream: No such file or directory in C:\xampp\htdocs\profherd\php\adv-php\src\App\Controllers\Products.php on line 9

We need to fix this by adding the fully qualified class name to the Products.php controller Products class.

// remove this line of code
require "src/models/product.php";

// edit this class call to include the namespace
$model = new App\Models\Product;

When you run this code, it returns yet another error. This time it shows the new namespace embedded within the existing namespace.

Warning: require(src/App/Controllers/App/Models/Product.php): Failed to open stream: No such file or directory in C:\xampp\htdocs\profherd\php\adv-php\index.php on line 10

This is because the file expects all called classes to be in the same namespace. We can fix this by adding the root reference (\) slash at the beginning of the fully qualified class name (new \App\Models\Product), but there is a better way.

The use Statement

When using namespaces, any class called to create a new object assumes the class is in the same namespace as the current file. However, classes are often used that are located somewhere else in the code base. We need to tell the file to use the namespace of the class that isn't in the current namespace. The easiest way to do this is to use the use statement to import namespaces into files.

Examine the Products.php controller file for a moment. Notice the file is in the App\Controllers namespace but we call the Product class from the App\Models namespace. Since the code expects all classes to be relative to the current namespace, this will cause the autoloader to look for the class in App\Controllers. You could prepend the class call with the root reference to the namespace using \App\Models\Product but there is a clearer and easier way to tell the code where to look for the external classes. The use statement.

The use statement allows you to tell the code where to find these external classes with a clear statement at the top of the file after the namespace designation.

The use Statement Example in the Products.php Controller
<?php

// current namespace for Products.php controller
namespace App\Controllers;

// map to include the Product namespace
use App\Models\Product;

class Products
{
    public function index()
    {
        // call to external class mapped with use statement
        $model = new Product;
        $products = $model->getData();

        require "views/product-list.php";
    }

    public function show() 
    {
        require "views/product-show.php";
    }
}

Notice the use statement after the namespace statement that tells the code to find the Product class in the App\Models\Product namespace. Similar updates (use PDO;) will be required to import the PDO class into the Product.php file.

Namespace Guidelines

There are a few rules we can extrapolate from the implementation of namespaces in our code.

  • Namespaces act like file paths for finding class files and their classes.
  • Namespace files and their related directories should use StudlyCaps names.
  • Namespaces are best used with the autoloader.
  • External namespace references should be defined with the use statement.
NOTE: The information in this video was correct at the time of production. Some elements may have changed. Please refer to the course syllabus and assignments for current requirements.