142

I'm building a User Class for my new website, however this time I was thinking to build it little bit differently...

C++, Java and even Ruby (and probably other programming languages) are allowing the use of nested/inner classes inside the main class, which allows us to make the code more object-oriented and organized.

In PHP, I would like to do something like so:

<?php
  public class User {
    public $userid;
    public $username;
    private $password;

    public class UserProfile {
      // some code here
    }

    private class UserHistory {
      // some code here
    }
  }
?>

Is that possible in PHP? How can I achieve it?


UPDATE

If it's impossible, will future PHP versions might support nested classes?

Lior Elrom
  • 19,660
  • 16
  • 80
  • 92

11 Answers11

149

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 or composition. Sometimes, even simple inheritance is sufficient.

Danon
  • 2,771
  • 27
  • 37
Tivie
  • 18,864
  • 5
  • 58
  • 77
32

Real nested classes with public/protected/private accessibility were proposed in 2013 for PHP 5.6 as an RFC but did not make it (No voting yet, no update since 2013 - as of 2021/02/03):

https://wiki.php.net/rfc/nested_classes

class foo {
    public class bar {
 
    }
}

At least, anonymous classes made it into PHP 7

https://www.php.net/manual/en/language.oop5.anonymous.php https://wiki.php.net/rfc/anonymous_classes

From this RFC page:

Future Scope

The changes made by this patch mean named nested classes are easier to implement (by a tiny bit).

So, we might get nested classes in some future version, but it's not decided yet.

still_dreaming_1
  • 8,661
  • 6
  • 39
  • 56
Fabian Schmengler
  • 24,155
  • 9
  • 79
  • 111
11

You cannot do this in PHP. However, there are functional ways to accomplish this.

For more details please check this post: How to do a PHP nested class or nested methods?

This way of implementation is called fluent interface: http://en.wikipedia.org/wiki/Fluent_interface

Community
  • 1
  • 1
Sumoanand
  • 8,835
  • 2
  • 47
  • 46
7

As per Xenon's comment to Anıl Özselgin's answer, anonymous classes have been implemented in PHP 7.0, which is as close to nested classes as you'll get right now. Here are the relevant RFCs:

Nested Classes (status: withdrawn)

Anonymous Classes RFC (status: implemented in PHP 7.0)

Anonymous Classes documentation

An example to the original post, this is what your code would look like:

<?php
    public class User {
        public $userid;
        public $username;
        private $password;
        
        public $profile;
        public $history;

        public function __construct() {
            $this->profile = new class {
                // Some code here for user profile
            }
            
            $this->history = new class {
                // Some code here for user history
            }
        }
    }
?>

This, though, comes with a very nasty caveat. If you use an IDE such as PHPStorm or NetBeans, and then add a method like this to the User class:

public function foo() {
  $this->profile->...
}

...bye bye auto-completion. This is the case even if you code to interfaces (the I in SOLID), using a pattern like this:

<?php
    public class User {
        public $profile;
        
        public function __construct() {
            $this->profile = new class implements UserProfileInterface {
                // Some code here for user profile
            }
        }
    }
?>

Unless your only calls to $this->profile are from the __construct() method (or whatever method $this->profile is defined in) then you won't get any sort of type hinting. Your property is essentially "hidden" to your IDE, making life very hard if you rely on your IDE for auto-completion, code smell sniffing, and refactoring.

still_dreaming_1
  • 8,661
  • 6
  • 39
  • 56
e_i_pi
  • 4,590
  • 4
  • 27
  • 45
  • If you took the time to write the interface, you can type your variable (like `public UserProfileInterface $profile;`) to get your auto-complete back. – Shayan Toqraee Jul 02 '23 at 12:23
  • @ShayanToqraee I wrote this answer when PHP was v7.1, it looks like typed properties are now available in PHP 7.4, as per your syntax given. – e_i_pi Jul 02 '23 at 23:41
6

Since PHP version 5.4 you can force create objects with private constructor through reflection. It can be used to simulate Java nested classes. Example code:

class OuterClass {
  private $name;

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

  public function getName() {
    return $this->name;
  }

  public function forkInnerObject($name) {
    $class = new ReflectionClass('InnerClass');
    $constructor = $class->getConstructor();
    $constructor->setAccessible(true);
    $innerObject = $class->newInstanceWithoutConstructor(); // This method appeared in PHP 5.4
    $constructor->invoke($innerObject, $this, $name);
    return $innerObject;
  }
}

class InnerClass {
  private $parentObject;
  private $name;

  private function __construct(OuterClass $parentObject, $name) {
    $this->parentObject = $parentObject;
    $this->name = $name;
  }

  public function getName() {
    return $this->name;
  }

  public function getParent() {
    return $this->parentObject;
  }
}

$outerObject = new OuterClass('This is an outer object');
//$innerObject = new InnerClass($outerObject, 'You cannot do it');
$innerObject = $outerObject->forkInnerObject('This is an inner object');
echo $innerObject->getName() . "\n";
echo $innerObject->getParent()->getName() . "\n";
Pascal9x
  • 340
  • 4
  • 4
3

You can't do it in PHP. PHP supports "include", but you can't even do that inside of a class definition. Not a lot of great options here.

This doesn't answer your question directly, but you may be interested in "Namespaces", a terribly ugly\syntax\hacked\on\top\of PHP OOP: http://www.php.net/manual/en/language.namespaces.rationale.php

dkamins
  • 21,450
  • 7
  • 55
  • 59
  • Namespaces can certainly organized the code better but it's not as powerful as nested classes. Thanks for the answer! – Lior Elrom May 07 '13 at 16:57
  • why do you call it "terrible"? i think it is okay and well separated from other syntax contexts. – emfi Apr 11 '18 at 21:30
3

I think I wrote an elegant solution to this problem by using namespaces. In my case, the inner class does not need to know his parent class (like the static inner class in Java). As an example I made a class called 'User' and a subclass called 'Type', used as a reference for the user types (ADMIN, OTHERS) in my example. Regards.

User.php (User class file)

<?php
namespace
{   
    class User
    {
        private $type;

        public function getType(){ return $this->type;}
        public function setType($type){ $this->type = $type;}
    }
}

namespace User
{
    class Type
    {
        const ADMIN = 0;
        const OTHERS = 1;
    }
}
?>

Using.php (An example of how to call the 'subclass')

<?php
    require_once("User.php");

    //calling a subclass reference:
    echo "Value of user type Admin: ".User\Type::ADMIN;
?>
3

You can, like this, in PHP 7:

class User{
  public $id;
  public $name;
  public $password;
  public $Profile;
  public $History;  /*  (optional declaration, if it isn't public)  */
  public function __construct($id,$name,$password){
    $this->id=$id;
    $this->name=$name;
    $this->password=$password;
    $this->Profile=(object)[
      'get'=>function(){
        return 'Name: '.$this->name.''.(($this->History->get)());
      }
    ];
    $this->History=(object)[
      'get'=>function(){
        return ' History: '.(($this->History->track)());
      }
      ,'track'=>function(){
        return (lcg_value()>0.5?'good':'bad');
      }
    ];
  }
}
echo ((new User(0,'Lior','nyh'))->Profile->get)();

This is the only solution on this page that is actually nested within the outer class's code and also actually works. The solution above that looks similar but uses new class{} instead of the way I wrote it here as: (object)[]; does not actually work, not for me anyway, not in PHP version 7.1.33. If I try that solution and put any method in, and call it, the error output is

'PHP parse error: syntax error, unexpected "[function name (get)]" ..expecting function (T_FUNCTION) or const (T_CONSTANT) in (path) on line (line #)

Even if I change the function to just function get(){...} the error is

'PHP Fatal Error: Uncaught Error: function name must be a string' in (path):(Line #)

And for the very top solution, while referencing the 'inner' class as if it were nested, the code doesn't actually nest the inner class within the outer class. enter image description here

In addition, you can take this paradigm and run with it, make as deep of a hierarchy of inner classes as you want to:

class Inner_Inner_Inner_Inner_class_demonstrator{
  public function __construct(){
    $this->Writing=(object)[
      'Files'=>(object)[
        'Third_Level_Inner_Class'=>(object)[
          'Fourth_Level_Inner_Class'=>(object)[
            'write'=>function($_what,$_where,$_append){
              $Handle;
              if(!$_append)$Handle = fopen($_where, 'w');
              else $Handle = fopen($_where, 'a');
              fwrite($Handle, $_what);
              fclose($Handle);
            }
          ]
        ]
      ]
    ];
  }
}
((new Inner_Inner_Inner_Inner_class_demonstrator())->Writing->Files->Third_Level_Inner_Class->Fourth_Level_Inner_Class->write)('four levels of inner classes!','tester.html',true);
3

This page keeps coming up in my Internet searches on this subject so figured I should chime in even though this is an 8-year old post. The documentation for PHP5 demonstrates that anonymous classes can be defined within a class method. The object created can extend, implement, and even use other classes, interfaces, and traits. Consider the following OOP paradigm of factory object production. Similar to what @e-i-pi pointed out ...

class Factory {
    /**
     *  Method to manufacture an inner-class object.
     *
     *  @param  string  $args   Arguments to be passed to
     *                          the inner-class constructor.
     */
    static function manufacture_object($args) {
        /**
         *  Here's the definition of the inner-class.
         */
        return new class($args) {
            static $remembers = 'Nothing';
            private $args;
            function __construct($args) {
                $this->$args = $args;
            }
            function says() {
                return $this->args;
            }
        };
    }
}

/**
 *  Create an inner-class object and have it do its thing.
 */
$mort = Factory::manufacture_object("Hello World!");
echo $mort->says();         // Echoes "Hello World!"

The objects are one-off, so one would expect the static values of the objects returned would not bind from one instance to another. After all, the anonymous class is unique from one object to another. However, late static binding works as one would otherwise expect from a nested class.

$mort = Factory::manufacture_object("I can remember that.");
$mort2 = Factory::manufacture_object("I'll live vicariously through you.");
$mort::$remembers = 'Something';
echo $mort2::$remembers;    // Echoes "Something"

So, there you go: inner/nested classes and creation of their objects with static functionality has been possible since September 22, 2013 (right about the time this question was asked).

Mort 1305
  • 141
  • 4
2

It is waiting for voting as RFC https://wiki.php.net/rfc/anonymous_classes

Anıl Özselgin
  • 420
  • 1
  • 4
  • 9
-7

Put each class into separate files and "require" them.

User.php

<?php

    class User {

        public $userid;
        public $username;
        private $password;
        public $profile;
        public $history;            

        public function __construct() {

            require_once('UserProfile.php');
            require_once('UserHistory.php');

            $this->profile = new UserProfile();
            $this->history = new UserHistory();

        }            

    }

?>

UserProfile.php

<?php

    class UserProfile 
    {
        // Some code here
    }

?>

UserHistory.php

<?php

    class UserHistory 
    {
        // Some code here
    }

?>
priyabagus
  • 2,718
  • 1
  • 14
  • 10