Sub Namespaces

Cultivating Miniature Worlds through Sub-Namespace

If you can define namespaces, then it’s no different to define sub-namespaces. We’re also going to look at classes with the same name from different sub-namespaces and how we’re going to handle those naming conflicts that still might arise.

Recap: Review where we left off last time. The only difference is that we changed the namespace from CoolCars to Vehicles.

https://blog.devgenius.io/php-p69-defining-namespaces-5a8e0a4bfdc0

https://github.com/dinocajic/php-youtube-tutorials/tree/master

If we start with our Car class, we can see that it belongs to the Vehicles namespace.

<?php
namespace Vehicles;

require_once('Vehicle.php');
require_once('Engine.php');
require_once('Transmission.php');

class Car extends Vehicle
{
    //...
}

What we want is for this class to be part of it’s own sub-namespace, like the Cars sub-namespace. How would we achieve that? By adding \Cars after the Vehicles namespace.

<?php
namespace Vehicles\Cars;

require_once('Vehicle.php');
require_once('Engine.php');
require_once('Transmission.php');

class Car extends Vehicle
{
    ...
}

And guess what? Everything is broken. Why? We’re technically one subdirectory deeper than everything else. While all of the other code lives in Vehicles, the Car class lives in Vehicles\Cars.

<?php

/**
 * Vehicles/
 *   Cars/
 *     Car (class)
 *   Driver
 *   Engine
 *   Transmission
 *   ...
 */

Now that we know the directory structure, how do we fix the code so that if functions properly? We’ll just use the absolute path to each class that’s referenced in the code. For example, since we’re extending the Vehicle class, we’ll need to prefix it with its Vehicles namespace.

<?php
namespace Vehicles\Cars;

require_once('Vehicle.php');
require_once('Engine.php');
require_once('Transmission.php');

class Car extends \Vehicles\Vehicle 
{
    ...
}

We’ll need to make the same modification to the remainder of the code. Since we’ve implemented the Engine and Transmission traits, they’ll need to be modified as well.

<?php
namespace Vehicles\Cars;

require_once('Vehicle.php');
require_once('Engine.php');
require_once('Transmission.php');

class Car extends \Vehicles\Vehicle
{
    use \Vehicles\Transmission, \Vehicles\Engine {
        \Vehicles\Transmission::check_oil_level insteadof \Vehicles\Engine;
        \Vehicles\Engine::check_oil_level as check_engine_oil_level;
    }
    
    ...
}

If we didn’t do the backslash, for example Vehicles\Vehicle, we would get a new error: Undefined namespace ‘Vehicles’ in \Vehicles\Car\Vehicles\Vehicle. The backslash states to start at the global namespace. If we don’t include it, it appends to the current namespace.

Are we done? Nope. Still broken. We have to start digging deeper and see where the Car class has been extended. That would be in the Lamborghini and Ferrari classes.

<?php
namespace Vehicles;

require_once("Car.php");

class Lamborghini extends \Vehicles\Cars\Car {

    ...
}

I know you get it by now, but let’s take it up a level. Our Car class used the Engine and Transmission traits. Those are car parts and should probably live in a CarParts sub-namespace under Vehicle\Cars.

<?php
namespace Vehicles\Cars\CarParts;

trait Engine {

    public function check_oil_level() {
        echo "Engine oil level good";
    }
}
<?php
namespace Vehicles\Cars\CarParts;

trait Transmission {

    public function check_oil_level() {
        echo "Transmission oil level good";
    }
}

Did we break something? Of course we did. We need to modify all code that’s using those traits, i.e. Car.

<?php
namespace Vehicles\Cars;

require_once('Vehicle.php');
require_once('Engine.php');
require_once('Transmission.php');

class Car extends \Vehicles\Vehicle
{
    use \Vehicles\Cars\CarParts\Transmission, \Vehicles\Cars\CarParts\Engine {
        \Vehicles\Cars\CarParts\Transmission::check_oil_level insteadof \Vehicles\Cars\CarParts\Engine;
        \Vehicles\Cars\CarParts\Engine::check_oil_level as check_engine_oil_level;
    }
    
    ...
}

We’re just letting PHP know how to find those specific classes within our newly defined virtual directory system.

One additional class that we haven’t modified so far is the Driver class. It injects the Car class through its constructor. Instead of making the modification directly in the constructor, remember that we can use our use keyword before the class declaration in order to avoid ambiguity within the code. We can also define our Driver to be within the Vehicles\Cars\Drivers sub-namespace.

<?php
namespace Vehicles\Cars\Drivers;

use \Vehicles\Cars\Car;

class Driver {

    private Car $car;

    public function __construct(Car $car)
    {
        $this->car = $car;
    }

    public function drive() {
        echo $this->car->turnOn();
        echo $this->car->drive();
        echo $this->car->turnOff();
    }
}

Can we modify anything else? Of course we can. The Lamborghini and Ferrari classes are a type of Car: an exotic car. Let’s make that explicit with our sub-namespaces.

<?php
namespace Vehicles\Cars\ExoticCars;

require_once("Car.php");

class Lamborghini extends \Vehicles\Cars\Car 
{
    //...
}
<?php
namespace Vehicles\Cars\ExoticCars;

require_once("Car.php");

class Ferrari extends \Vehicles\Cars\Car {

}

It’s time for some testing. We can call the get_year_make_and_model() method from our Lamborghini class and see if we get what we intended to get.

<?php
/**
 * ROOT/
 *   Vehicles/
 *     Vehicle (Class)
 *     Cars/
 *       Car (Class)
 *       CarParts/
 *         Engine (Class)
 *         Transmission (Class)
 *       ExoticCars/
 *         Lamborghini (Class)
 *         Ferrari (Class)
 *       Drivers/
 *         Driver (Class)
 */

use Vehicles\Cars\ExoticCars\Lamborghini;

// Defining Sub-namespaces
require_once("Lamborghini.php");

$lamborghini = new Lamborghini(1999, "Lamborghini", "Murcielago", "1234");
echo $lamborghini->get_year_make_and_model();

We get 1999 Lamborghini Murcielago, which is what we expected.

It’s really up to you how you want to classify namespaces/sub-namespaces.

One last thing to tackle is naming conflicts, which is where namespaces/sub-namespaces really shine. If we wanted to create another Lamborghini class, we could. This could be our Knock-Off Lamborghini; we want to name it the exact same name, instead of LamborghiniKnockoff.

How can we do that? We’ll just throw it into its own namespace. All Exotic Knock-Offs will be going into the Vehicles/Cars/ExoticCars/KnockOffs namespace.

?php
namespace Vehicles\Cars\ExoticCars\KnockOffs;

require_once("Car.php");

class Lamborghini extends \Vehicles\Cars\Car {

    private string $secret_code;

    public function __construct( $year, $make, $model, $secret_code ) {
        parent::__construct($year, $make, $model);
        $this->secret_code = $secret_code;

        echo "Knockoff";
    }

    /**
     * @return string
     */
    public function getSecretCode(): string
    {
        return $this->secret_code;
    }

    /**
     * @param string $secret_code
     */
    public function setSecretCode(string $secret_code): void
    {
        $this->secret_code = $secret_code;
    }
}

Let’s test it. I will conveniently use the use keyword…and we’ve broken the code. PHP will does not know which Lamborghini to call. We have to explicitly state that we want to use one Lamborghini class and one KnockOff class. How? By appending as after the use. Then we can use whatever we defined in our use statement.

<?php

use Vehicles\Cars\ExoticCars\Lamborghini;
use Vehicles\Cars\ExoticCars\KnockOffs\Lamborghini as KnockOff;

// Defining Sub-namespaces
require_once("Lamborghini.php");
require_once("Lamborghini_Two.php");

$lamborghini = new Lamborghini(1999, "Lamborghini", "Murcielago", "1234");
echo $lamborghini->get_year_make_and_model();

$lamborghini = new KnockOff(1999, "Lamborghini", "Murcielago", "1234");
echo $lamborghini->get_year_make_and_model();

If you didn’t want to define the KnockOff class, you would have to append the entire namespace structure within the code itself so that there’s not ambiguity. This leads to messy code.

<?php

// Defining Sub-namespaces
require_once("Lamborghini.php");
require_once("Lamborghini_Two.php");

$lamborghini = new Vehicles\Cars\ExoticCars\Lamborghini(1999, "Lamborghini", "Murcielago", "1234");
echo $lamborghini->get_year_make_and_model();

$lamborghini = new Vehicles\Cars\ExoticCars\KnockOffs\Lamborghini(1999, "Lamborghini", "Murcielago", "1234");
echo $lamborghini->get_year_make_and_model();

If you didn’t want to define the KnockOff class, you would have to append the entire namespace structure within the code itself so that there’s not ambiguity. This leads to messy code.

I hope that you see that it’s not scary. Every time that you see a massive structure, it’s just somebody defining namespaces at the top of the file. Just a virtual directory structure. Nothing more to it.

DIVIDING AND CONQUERING THROUGH NAMESPACE ARCHITECTURE

PHP – P69: DEFINING NAMESPACES

Defining namespaces is pretty simple. At the top of the file, right after the opening PHP tag, the namespace keyword is used, followed by the virtual directory of your choosing.

Cultivating Miniature Worlds through Sub-Namespace

PHP – P70: Sub-namespaces

If you can define namespaces, then it’s no different to define sub-namespaces. We’re also going to look at classes with the same name from different sub-namespaces and how we’re going to handle those naming conflicts that still might arise.

Magic Constants

DISCOVER HIDDEN TRUTHS WITH MAGIC CONSTANTS IN PHP

PHP – P71:MAGIC CONSTANTS

There are a few magic constants in PHP that we’ll go through: __LINE__, __FILE__, __DIR__, __FUNCTION__, __CLASS__, __TRAIT__, __METHOD__, and __NAMESPACE__.

Leave a Reply