Вложенный или внутренний класс в 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 поддерживать вложенные классы?

Author: Lior Elrom, 2013-05-07

9 answers

Вступление:

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

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

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

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

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

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

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

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

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

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

В PHP

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

Если все, чего вы хотите достичь, - это структура/организация, как пакет.Вне класса.Внутреннего класса, пространств имен 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

ПРИМЕЧАНИЕ:

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

 112
Author: Tivie, 2014-12-01 12:50:44

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

Будущая область применения

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

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

 18
Author: Fabian Schmengler, 2016-12-29 15:07:48

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

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

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

 8
Author: Sumoanand, 2017-05-23 11:54:59

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

Это не дает прямого ответа на ваш вопрос, но вас могут заинтересовать "Пространства имен", ужасно уродливый \синтаксис\взломанный\поверх\PHP ООП: http://www.php.net/manual/en/language.namespaces.rationale.php

 3
Author: dkamins, 2013-05-07 16:43:56

Начиная с версии 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";
 3
Author: Pascal9x, 2016-01-04 20:23:21

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

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

Анонимные классы (статус: реализованы на 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
            }
        }
    }
?>

Однако это связано с очень неприятной оговоркой. Если вы используете Интегрированная среда разработки, такая как 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), то вы не получите никаких намеков на тип. Ваша собственность, по сути, "скрыта" для вашей среды разработки, что очень усложняет жизнь, если вы полагаетесь на свою среду разработки для автоматическое завершение, обнюхивание кода и рефакторинг.

 2
Author: e_i_pi, 2017-01-31 23:14:22

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

 1
Author: Anıl Özselgin, 2015-03-19 11:53:24

Я думаю, что написал элегантное решение этой проблемы с помощью пространств имен. В моем случае внутреннему классу не нужно знать свой родительский класс (например, статический внутренний класс в Java). В качестве примера я создал класс "Пользователь" и подкласс "Тип", используемый в качестве ссылки для типов пользователей (АДМИНИСТРАТОР, ДРУГИЕ) в моем примере. С уважением.

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;
?>
 1
Author: Rogerio Souza, 2016-08-22 16:25:46

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

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
    }

?>
 -2
Author: priyabagus, 2017-11-24 22:59:28