Вложенный или внутренний класс в PHP

Я создаю пользовательский класс для моего нового веб-сайта, но на этот раз я думал построить его немного по-другому …

Я знаю, что C ++ , Java и даже Ruby (и, возможно, другие языки программирования) позволяют вложенным / внутренним классам внутри основного класса, что позволяет сделать код более объектно-ориентированным и организованным.

В PHP я хотел бы сделать что-то вроде этого:

<?php public class User { public $userid; public $username; private $password; public class UserProfile { // Some code here } private class UserHistory { // Some code here } } ?> 

Возможно ли это в PHP? Как я могу это достичь?


ОБНОВИТЬ

Если это невозможно, будущие версии PHP могут поддерживать вложенные классы?

Solutions Collecting From Web of "Вложенный или внутренний класс в PHP"

Вступление:

Вложенные классы относятся к другим классам немного иначе, чем к внешним классам. Взятие Java в качестве примера:

Нестатические вложенные классы имеют доступ к другим членам охватывающего класса, даже если они объявлены частными. Кроме того, для нестатических вложенных классов требуется экземпляр родительского класса для создания экземпляра.

 OuterClass outerObj = new OuterClass(arguments); outerObj.InnerClass innerObj = outerObj.new InnerClass(arguments); 

Есть несколько веских причин для их использования:

  • Это способ логически группировать классы, которые используются только в одном месте.

Если класс полезен только для одного другого класса, тогда логично связать и вставить его в этот класс и сохранить вместе.

  • Он увеличивает инкапсуляцию.

Рассмотрим два класса верхнего уровня: A и B, где B нуждается в доступе к членам A, которые в противном случае были бы объявлены частными. Скрывая класс B в классе A, члены A могут быть объявлены частными, а B может получить к ним доступ. Кроме того, сам Б можно скрывать от внешнего мира.

  • Вложенные классы могут привести к более читабельному и поддерживаемому коду.

Вложенный класс обычно относится к его родительскому классу и вместе образует «пакет»,

В PHP

Вы можете иметь подобное поведение в PHP без вложенных классов.

Если все, что вы хотите достичь, это структура / организация, так как Package.OuterClass.InnerClass, пространства имен PHP могут быть достаточными. Вы даже можете объявить несколько пространств имен в одном файле (хотя из-за стандартных функций автозагрузки это может быть нежелательно).

 namespace; class OuterClass {} namespace OuterClass; class InnerClass {} Пространство namespace; class OuterClass {} namespace OuterClass; class InnerClass {} 

Если вы хотите подражать другим характеристикам, таким как видимость элементов, требуется немного больше усилий.

Определение класса «пакет»

 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()"); } } } } 

Случай использования

 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(); } } } 

тестирование

 $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) , $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) 

Вывод:

 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 и 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 

ЗАМЕТКА:

Я действительно не думаю, что попытка эмулировать innerClasses на PHP – такая хорошая идея. Я думаю, что код менее чист и читабельен. Кроме того, есть, вероятно, другие способы достижения аналогичных результатов с использованием хорошо налаженного шаблона, такого как шаблон Observer, Decorator ou COmposition. Иногда даже простого наследования достаточно.

В 2013 году для PHP 5.6 в качестве RFC были предложены реальные вложенные классы с public / protected / private доступностью, но не сделали этого (пока нет голосования, нет обновления с 2013 года по состоянию на 2016/12/29 ):

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

 class foo { public class bar { } } 

По крайней мере, анонимные классы превратились в PHP 7

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

На этой странице RFC:

Будущий масштаб

Изменения, сделанные этим патчем, означают, что вложенные классы легче реализовать (крошечным битом).

Итак, мы можем получить вложенные классы в какой-то будущей версии, но пока не решили.

Вы не можете сделать это на PHP. Тем не менее, есть функциональные способы достижения этого.

Для получения дополнительной информации, пожалуйста, проверьте этот пост: Как сделать PHP вложенный класс или вложенные методы?

Этот способ реализации называется свободным интерфейсом: http://en.wikipedia.org/wiki/Fluent_interface

Вы не можете сделать это в PHP. PHP поддерживает «include», но вы даже не можете сделать это внутри определения класса. Здесь не так много отличных вариантов.

Это не отвечает на ваш вопрос напрямую, но вы можете быть заинтересованы в «Пространства имен», ужасно уродливый \ syntax \ hacked \ on \ top \ PHP OOP: http://www.php.net/manual/en/language .namespaces.rationale.php

С PHP версии 5.4 вы можете принудительно создавать объекты с помощью частного конструктора посредством отражения. Его можно использовать для моделирования Java-вложенных классов. Пример кода:

 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"; 

Он ждет голосования в качестве RFC https://wiki.php.net/rfc/anonymous_classes

Я думаю, что я написал элегантное решение этой проблемы, используя пространства имен. В моем случае внутреннему классу не нужно знать его родительский класс (например, статический внутренний класс в Java). В качестве примера я создал класс под названием «Пользователь» и подкласс «Тип», который использовался в качестве ссылки для пользовательских типов (ADMIN, OTHERS) в моем примере. С уважением.

User.php (файл пользовательского класса)

 <?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; } } ?> Пространство <?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 (пример того, как вызвать «подкласс»)

 <?php require_once("User.php"); //calling a subclass reference: echo "Value of user type Admin: ".User\Type::ADMIN; ?> 

Согласно комментарию Ксенона к ответу Аниля Оззельгина, анонимные классы были реализованы в PHP 7.0, который находится как можно ближе к вложенным классам, так как вы получите прямо сейчас. Вот соответствующие RFC:

Вложенные классы (статус: снято)

Анонимные классы (статус: реализован в PHP 7.0)

Пример исходного сообщения, вот как выглядит ваш код:

 <?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 } } } ?> 

Это, однако, идет с очень неприятным предостережением. Если вы используете среду IDE, такую ​​как PHPStorm или NetBeans, а затем добавьте такой метод в класс User :

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

… до свидания авто-пополнение. Это так, даже если вы кодируете интерфейсы (I в SOLID), используя такой шаблон:

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

Если только ваши вызовы в $this->profile из метода __construct() (или любого $this->profile метода $this->profile определены в), то вы не получите какой-либо тип намека. Ваша собственность по сути «скрыта» для вашей среды разработки, что делает жизнь очень трудной, если вы полагаетесь на свою среду IDE для автоматического завершения, обнюхивания запаха кода и рефакторинга.

Поместите каждый класс в отдельные файлы и «потребуйте» их.

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 } ?>