[php] Nested or Inner Class in PHP

Intro:

Nested classes relate to other classes a little differently than outer classes. Taking Java as an example:

Non-static nested classes have access to other members of the enclosing class, even if they are declared private. Also, non-static nested classes require an instance of the parent class to be instantiated.

OuterClass outerObj = new OuterClass(arguments);
outerObj.InnerClass innerObj = outerObj.new InnerClass(arguments);

There are several compelling reasons for using them:

  • It is a way of logically grouping classes that are only used in one place.

If a class is useful to only one other class, then it is logical to relate and embed it in that class and keep the two together.

  • It increases encapsulation.

Consider two top-level classes, A and B, where B needs access to members of A that would otherwise be declared private. By hiding class B within class A, A's members can be declared private and B can access them. In addition, B itself can be hidden from the outside world.

  • Nested classes can lead to more readable and maintainable code.

A nested class usually relates to it's parent class and together form a "package"

In PHP

You can have similar behavior in PHP without nested classes.

If all you want to achieve is structure/organization, as Package.OuterClass.InnerClass, PHP namespaces might sufice. You can even declare more than one namespace in the same file (although, due to standard autoloading features, that might not be advisable).

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

If you desire to emulate other characteristics, such as member visibility, it takes a little more effort.

Defining the "package" class

namespace {

    class Package {

        /* protect constructor so that objects can't be instantiated from outside
         * Since all classes inherit from Package class, they can instantiate eachother
         * simulating protected InnerClasses
         */
        protected function __construct() {}

        /* This magic method is called everytime an inaccessible method is called 
         * (either by visibility contrains or it doesn't exist)
         * Here we are simulating shared protected methods across "package" classes
         * This method is inherited by all child classes of Package 
         */
        public function __call($method, $args) {

            //class name
            $class = get_class($this);

            /* we check if a method exists, if not we throw an exception 
             * similar to the default error
             */
            if (method_exists($this, $method)) {

                /* The method exists so now we want to know if the 
                 * caller is a child of our Package class. If not we throw an exception
                 * Note: This is a kind of a dirty way of finding out who's
                 * calling the method by using debug_backtrace and reflection 
                 */
                $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3);
                if (isset($trace[2])) {
                    $ref = new ReflectionClass($trace[2]['class']);
                    if ($ref->isSubclassOf(__CLASS__)) {
                        return $this->$method($args);
                    }
                }
                throw new \Exception("Call to private method $class::$method()");
            } else {
                throw new \Exception("Call to undefined method $class::$method()");
            }
        }
    }
}

Use case

namespace Package {
    class MyParent extends \Package {
        public $publicChild;
        protected $protectedChild;

        public function __construct() {
            //instantiate public child inside parent
            $this->publicChild = new \Package\MyParent\PublicChild();
            //instantiate protected child inside parent
            $this->protectedChild = new \Package\MyParent\ProtectedChild();
        }

        public function test() {
            echo "Call from parent -> ";
            $this->publicChild->protectedMethod();
            $this->protectedChild->protectedMethod();

            echo "<br>Siblings<br>";
            $this->publicChild->callSibling($this->protectedChild);
        }
    }
}

namespace Package\MyParent
{
    class PublicChild extends \Package {
        //Makes the constructor public, hence callable from outside 
        public function __construct() {}
        protected function protectedMethod() {
            echo "I'm ".get_class($this)." protected method<br>";
        }

        protected function callSibling($sibling) {
            echo "Call from " . get_class($this) . " -> ";
            $sibling->protectedMethod();
        }
    }
    class ProtectedChild extends \Package { 
        protected function protectedMethod() {
            echo "I'm ".get_class($this)." protected method<br>";
        }

        protected function callSibling($sibling) {
            echo "Call from " . get_class($this) . " -> ";
            $sibling->protectedMethod();
        }
    }
}

Testing

$parent = new Package\MyParent();
$parent->test();
$pubChild = new Package\MyParent\PublicChild();//create new public child (possible)
$protChild = new Package\MyParent\ProtectedChild(); //create new protected child (ERROR)

Output:

Call from parent -> I'm Package protected method
I'm Package protected method

Siblings
Call from Package -> I'm Package protected method
Fatal error: Call to protected Package::__construct() from invalid context

NOTE:

I really don't think trying to emulate innerClasses in PHP is such a good idea. I think the code is less clean and readable. Also, there are probably other ways to achieve similar results using a well established pattern such as the Observer, Decorator ou COmposition Pattern. Sometimes, even simple inheritance is sufficient.

Examples related to php

I am receiving warning in Facebook Application using PHP SDK Pass PDO prepared statement to variables Parse error: syntax error, unexpected [ Preg_match backtrack error Removing "http://" from a string How do I hide the PHP explode delimiter from submitted form results? Problems with installation of Google App Engine SDK for php in OS X Laravel 4 with Sentry 2 add user to a group on Registration php & mysql query not echoing in html with tags? How do I show a message in the foreach loop?

Examples related to class

String method cannot be found in a main class method Class constructor type in typescript? ReactJS - Call One Component Method From Another Component How do I declare a model class in my Angular 2 component using TypeScript? When to use Interface and Model in TypeScript / Angular Swift Error: Editor placeholder in source file Declaring static constants in ES6 classes? Creating a static class with no instances In R, dealing with Error: ggplot2 doesn't know how to deal with data of class numeric Static vs class functions/variables in Swift classes?

Examples related to oop

How to implement a simple scenario the OO way When to use 'raise NotImplementedError'? PHP: cannot declare class because the name is already in use Python class input argument Call an overridden method from super class in typescript Typescript: How to extend two classes? What's the difference between abstraction and encapsulation? An object reference is required to access a non-static member Java Multiple Inheritance Why not inherit from List<T>?

Examples related to nested

Nested routes with react router v4 / v5 Extract first item of each sublist python "TypeError: 'numpy.float64' object cannot be interpreted as an integer" How can I combine multiple nested Substitute functions in Excel? Retrieving values from nested JSON Object MySQL Nested Select Query? List comprehension on a nested list? Nested ifelse statement Single Line Nested For Loops Nested or Inner Class in PHP

Examples related to inner-classes

Difference between final and effectively final Is not an enclosing class Java Nested or Inner Class in PHP Java - No enclosing instance of type Foo is accessible Can we create an instance of an interface in Java? Why would one use nested classes in C++? Getting hold of the outer class object from the inner class object Nested classes' scope? Java: Static vs inner class Why Would I Ever Need to Use C# Nested Classes