Inner Classes in PHP Concept

I’d like to present my concept of inner classes for PHP based on other languages and PHP limitations.

What are those inner classes?

Nested Inner Class UML

In object-oriented programming (OOP), an inner class or nested class is a class declared entirely within the body of another class or interface. It is distinguished from a subclass. – Wikipedia

So basically inner class is a class that is declared inside the scope of another class. A nested class has access to the variables and other symbols of the classes it is nested inside.

In other languages there are examples of inner classes adoptions. They all have little different point of view. Firstable and most complex inner classes exists in Java. There are Static Nested Classes and Inner Classes. Also there are two special kinds of inner classes:

Similar to Java there are also inner classes in D language. Other non significant adoptions are Ruby inner classes which only adds class prefix in name exactly same as using module and in Python they are somewhat uncommon and doesn’t automatically imply any sort of special relationship between the classes.

Are there any benefits?

Compelling reasons for using nested classes include the following:

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 embed it in that class and keep the two together. Nesting such helper classes makes their package more streamlined.

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.

It can lead to more readable and maintainable code: Nesting small classes within top-level classes places the code closer to where it is used.

The main advantage is organization and encapsulation. That allows your code to be even more object-oriented than it would be without inner classes. Anything that can be accomplished with inner classes can be accomplished without them using traditional classes with little more boilerplate in case of publicly accessible inner classes.

How could it look like?

Given example presents inner class example as it may be implemented in PHP.

class Person
{
  private interface Name
  {
    public function __construct(string $name);
    public function __toString() : string;
  }

  private class FirstName implements self\Name
  {
    private $name;

    public function __construct(string $name) {
      // validation
      if (strlen($name) < 3) {
        throw new InvalidargumentException(
          "Firstname requires min 3 letters long, given: {$name}"
        );
      }
      $this->name = $name;
    }

    public function __toString() : string {
      return $this->name;
    }
  }

  private class SecondName implements self\Name
  {
    private $name;

    public function __construct(string $name) {
      // it would be reasonable to have access to owning object
      // but `parent` already taken by super class
      // shouldn't `parent` be renamed to `super`!?
      // then `$parent` could be owning object `$this`
      if (is_null($parent->firstName)) {
        throw new RuntimeException(
          "It is impossible to have secondname without firstname"
        );
      }
      // validatiojn
      if (strlen($name) < 1) {
        throw new InvalidargumentException(
          "Secondname requires min 1 letter long, given: {$name}"
        );
      }
      $this->name = $name;
    }

    public function __toString() : string
    {
      return $this->name;
    }
  }

  /** @var self\FirstName */
  private $firstName;
  /** @var self\SecondName */
  private $secondName;

  public function __construct(string $fname, string $sname) {
    $this->firstName = new self\FirstName($fname);
    $this->secondName = new self\SecondName($sname);
  }

  public function getName() : string {
    return "{$this->firstName} {$this->secondName}";
  }
}

$me = new Person("Michał", "Marcin");
echo $me->getName(); // "Michał Marcin"

In above example we can see an interface called Name as all names are constructed from string and need __toString() method implemented.

Most people have more than one name and their validation rules may differ. Let’s say firstname should be more than 3 letters and secondname more than one which is implemented inside inner class FirstName and SecondName implementing Name interface. As is observable the interface was declared as self\Name and such syntax ensures clear type name which points that this interface belong to owning class Person. Additionally self is special keyword inside class and it is in this case. Note self should then be restricted namespace name to avoid ambiguity.

Top class Person use inner classes in it’s __constructor() to instantiate Name objects and store them in properties for usage in getName() method. Note tat inner classes and interface have private modifier in their declarations - that means any instance used or returned outside Person object should cause sort of fatal error at runtime.

That implies our inner classes and interfaces need to have PPP(public, private, protected) visibilities so they can be useful. When extending Person class it is reasonable to change private modifier to protected one so extending class will be able to deal with inner class instances.

What about publicly accessible inner classes?

They may have wider use. For example presenting some related objects like stock and options. This example presents only concept.

class Stock implements IteratorAggregate
{
  public class Option
  {
    private $name;
    private $value;

    public function __construct(string $name, int $value)
    {
      if (strlen($name) < 3) {
        throw new InvalidArgumentException(
          "Stock option name requires min. 3 letters long, given: {$name}"
        );
      }
      if ($value <= 0) {
        throw new InvalidArgumentException(
          "Stock option must have positive value, given: {$value}"
        );
      }
      $this->name = $name;
      $this->value = $value;
      // if there were access to owning `$this` by `$parent`
      // then could automatically add to options table
      $parent->options[] = $this;
    }
  }

  private $options = [];

  public function getIterator() {
    return new ArrayIterator($this->options);
  }
}
$stock = new Stock();
$option = new $stock\Option("MAD", 1500);
var_dump($stock);
// class Stock#1 (1) {
//   private $options =>
//   array(1) {
//     [0] =>
//     class Stock#1Option#2 (2) {
//       private $name =>
//       string(3) "MAD"
//       private $value =>
//       int(1500)
//     }
//   }
// }
$incorectlyNamedOption = new $stock\Option("a", 1);
// InvalidArgumentException: Stock option name requires min. 3 letters long, given: a
$incorectValueOption = new $stock\Option("ORA", -1);
// InvalidArgumentException: Stock option must have positive value, given: -1"

In above example Option have public visibility but cannot be instantiated without owning class instance that’s why syntax creating new object out of Stock scope is little weird:

$stock = new Stock();
$option = new $stock\Option("MAD", 1500);

Giving $stock in front of class name indicates that Option class belongs to Stock as inner class.

Type hint and return type

Every inner class and interface should be accessible to use on type hint and as return type limited to owning class with restrictions on returning non-public type in public methods.

Little weird syntax plays on our favor because it is not possible to address self\Option outside Stock class as it would point to different scope because of self prefix. Also syntax with variable prefix $stock\Option do the same job so we can avoid autoloading the same way. The reason of inner classes need to have such syntax is avoiding non predictable behaviour outside owning class.

Restrictions:

PHP 8.0: Object Initializer Proposal

September 26, 2019
Concept of the Object Initializer Expression as a simplification of object instantiation and properties initialization
class object initializer

Why Object Types are useful in Hydrators and Extractors

August 29, 2016
Object Type-hint described on examples based on Hydration pattern from authored RFC
dependency-injection hydration object

Simple Dic With Object Typehint

August 21, 2016
Simple DIC implementation based on proposed RFC:Object-typehint implementation to providing a proof-of-concept and real life example of usage
dependency-injection object
comments powered by Disqus