PHP RFC: Nested Classes
- Version: 0.2
- Date: 2013-09-29
- Author: Joe Watkins, krakjoe@php.net
- Status: Withdrawn
- First Published at: http://wiki.php.net/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