rfc:nested_classes

PHP RFC: Nested Classes

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:

<?php
/*
* \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:

<?php
/*
* 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:

<?php
/*
* 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:

<?php
/*
* 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

Rejected Features

N/A

rfc/nested_classes.txt · Last modified: 2013/10/13 20:59 by krakjoe