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


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

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

в PHP, я хотел бы сделать что-то вроде так:

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

        public class UserProfile {
            // Some code here
        }

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

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


обновление

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

9 81

9 ответов:

интро:

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

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

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

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

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

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

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

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

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

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

в PHP

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

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

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)

выход:

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-это такая хорошая идея. Я думаю, что код менее чистый и читаемый. Кроме того, вероятно, существуют и другие способы достижения аналогичных результатов с использованием хорошо установленного шаблона, такого как наблюдатель, декоратор или композиционный шаблон. Иногда достаточно даже простого наследования.

реальные вложенные классы с 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:

Будущий Объем

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

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

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

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

этот способ реализации называется fluent interface:http://en.wikipedia.org/wiki/Fluent_interface

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

Это не отвечает на ваш вопрос напрямую, но вас могут заинтересовать "пространства имен", ужасно уродливый \ синтаксис\взломанный\на\top\PHP ООП: 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";

согласно комментарию 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->...
}

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

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

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

пользователей.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 (пример того, как вызвать 'подкласс')

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

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

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

пользователей.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();

        }            

    }

?>

Профиль_пользователя.php

<?php

    class UserProfile 
    {
        // Some code here
    }

?>

UserHistory.php

<?php

    class UserHistory 
    {
        // Some code here
    }

?>