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

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

Я знаю, что С++, 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? Как я могу это достичь?


UPDATE

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

Ответ 1

Введение:

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

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

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

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

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

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

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

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

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

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

В PHP

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

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

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

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

Определение класса "package"

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)

Вывод:

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. Иногда даже достаточно простого наследования.

Ответ 2

Реальные вложенные классы с доступностью доступности public/protected/private были предложены в 2013 году для PHP 5.6 в качестве RFC, но не сделали этого (пока нет голосования, обновление с 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:

Дальность видимости

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

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

Ответ 7

Я думаю, что я написал элегантное решение этой проблемы, используя пространства имен. В моем случае внутреннему классу не нужно знать его родительский класс (например, статический внутренний класс в 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;
    }
}
?>

Using.php(пример того, как вызвать "подкласс" )

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

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

Ответ 8

Согласно комментарию Xenon к ответу Anıl Özselgin, анонимные классы были реализованы в 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->...
}

... bye bye byo auto-completion. Это так, даже если вы кодируете интерфейсы (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), то вы не получите никакого типа намека. Ваша собственность по сути "скрыта" для вашей среды разработки, что делает жизнь очень трудной, если вы полагаетесь на свою среду IDE для автоматического завершения, обнюхивания запаха кода и рефакторинга.

Ответ 9

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

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
    }

?>