Verification: a143cc29221c9be0

Php class with class inside

Introduction

Following on from anonymous classes, this RFC proposes we support nested classes in PHP.

Proposal

A nested class is a class declared in the virtual namespace of another class:

class foo {
    class bar {
 
    }
}

foo\bar is a nested class in the virtual namespace of foo.

A nested class has the ability to declare it's accessibility to other classes in the same way as class members:

class foo {
    public class bar {
 
    }
}

The first and second examples given here are therefore the same, by default classes are public, just like class members.

The following describes the functionality of access modifiers for nested classes:

  • public - the class is accessible everywhere

  • private - the class may be accessed by any class declared in the outer class

  • protected - the class may be access by any class in the same virtual namespace

Private Classes

The following example shows how to blackbox some of your functionality in a private nested class:

/*
* \foo
* @package \foo
*/
class foo 
{
    /*
    * \foo\bar supporting class for foo
    * @subpackage \foo\bar
    * @private
    */
    private class bar
    {
        public function __construct() {
            /* ... */
        }
    }
 
    /* PUBLIC API METHODS HERE */
 
    public function __construct() 
    {
        $this->bar = new \foo\bar();
    }
}
 
var_dump(new \foo());
?>

In the example \foo is the public facing API, \foo\bar contains supporting logic never to be exposed outside of \foo, any class declared in the virtual namespace \foo will be able to access the \foo\bar class.

Attempting:

var_dump(new \foo\bar());

will result in

Fatal error: Cannot access private class foo\bar from an unknown scope in %s on line %d

Private classes are very private, the following example demonstrates this:

/*
* foo
* @package foo
*/
class foo 
{
    /*
    * foo\bar supporting class for foo
    * @subpackage foo\bar
    * @private
    */
    private class bar
    {
 
        /*
        * \foo\bar\baz supporting class for foo\bar
        * @subpackage foo\bar\baz
        * @private
        */
        private class baz 
        {
 
            public function __construct() {
 
            }    
        }
 
        public function __construct() {
            $this->baz = new foo\bar\baz();
        }
    }
 
    /* PUBLIC API METHODS HERE */
 
    public function __construct() 
    {
        $this->bar = new \foo\bar();
        $this->baz = new \foo\bar\baz(); /* line 39 */
    }
}
 
new \foo();
?>

Output:

Fatal error: Cannot access private class foo\bar\baz from foo in %s on line 39

Protecting bits of your Privates

The following example shows how protected and private classes can be used in conjunction to provide versatile encapsulation:

/*
* foo
* @package foo
*/
class foo
{
    /*
    * foo\bar supporting class for foo
    * @subpackage foo\bar
    * @private
    */
    private class bar
    {
 
        /*
        * \foo\bar\baz supporting class for foo\bar
        * @subpackage foo\bar\baz
        * @protected
        */
        protected class baz
        {
 
            public function __construct() {
 
            }
        }
 
        public function __construct() {
            $this->baz = new foo\bar\baz();
        }
    }
 
    /* PUBLIC API METHODS HERE */
 
    public function __construct()
    {
        $this->bar = new \foo\bar();
        $this->baz = new \foo\bar\baz();
    }
}
 
var_dump(new \foo());

Output:

object(foo)#1 (2) {
  ["bar"]=>
  object(foo\bar)#2 (1) {
    ["baz"]=>
    object(foo\bar\baz)#3 (0) {
    }
  }
  ["baz"]=>
  object(foo\bar\baz)#4 (0) {
  }
}

The protected class \foo\bar\baz can now be used in \foo, for example:

/*
* foo
* @package foo
*/
class foo
{
    /*
    * foo\bar supporting class for foo
    * @subpackage foo\bar
    * @private
    */
    private class bar
    {
 
        /*
        * \foo\bar\baz supporting class for foo\bar
        * @subpackage foo\bar\baz
        * @protected
        */
        protected class baz
        {
 
            public function __construct() {
 
            }
        }
 
        public function __construct() {
            $this->baz = new foo\bar\baz();
        }
    }
 
    /*
    * \foo\qux supporting class for foo
    */
    private class qux extends foo\bar\baz
    {
        public function __construct() {
 
        }
    }
 
    /* PUBLIC API METHODS HERE */
 
    public function __construct()
    {
        $this->bar = new \foo\bar();
        $this->baz = new \foo\bar\baz();
        $this->qux = new \foo\qux();
    }
}
 
var_dump(new \foo());

Output:

object(foo)#1 (3) {
  ["bar"]=>
  object(foo\bar)#2 (1) {
    ["baz"]=>
    object(foo\bar\baz)#3 (0) {
    }
  }
  ["baz"]=>
  object(foo\bar\baz)#4 (0) {
  }
  ["qux"]=>
  object(foo\qux)#5 (0) {
  }
}

Backward Incompatible Changes

A single test that was defined in Zend/tests to check that an error is emitted when you declare a nested class; in reality, nothing core is broken.

Proposed PHP Version(s)

5.6

SAPIs Impacted

All

Impact to Existing Extensions

Existing libraries that work directly with zend_class_entry structures will need to update to include the additional member of the struct “super”; the member is used to store a pointer to the class that created it.

Such an update should not cause any real inconvenience.

Reflection requires patching to be able to report information about outer classes and access levels.

Open Issues

Access to private statics members in outer classes (access to methods requires some adjustment too).

Proposed Voting Choices

We are not there yet ...

Implementation

References

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 "
Siblings
"; $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
"; } 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
"; } 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

How to declare a method?

Let's declare a method inside a class named Example class to echo out a simple string that we give.


We use the public keyword to make the method available inside and outside the class. You will learn more about this in the visibility chapter.

How to call a method?


$example = new Example();
$example -> echo('Hello World');

Result: Hello World

Explained:
  • First, we create an object ($example) from the class Example
  • Next, we call the method echo with -> (object operator) and () (parentheses)
  • The parentheses contain the arguments as usual

The thing you need to understand is that we call methods on objects, not classes.