PHP 中的嵌套类或内部类

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16424257/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-25 11:06:41  来源:igfitidea点击:

Nested or Inner Class in PHP

phpclassoopnestedinner-classes

提问by Lior Elrom

I'm building a User Classfor my new website, however this time I was thinking to build it little bit differently...

我正在为我的新网站构建一个用户类,但是这次我想以不同的方式构建它......

C++, Javaand even Ruby(and probably other programming languages) are allowing the use of nested/inner classes inside the main class, which allows us to make the code more object-oriented and organized.

C++Java甚至Ruby(可能还有其他编程语言)允许在主类中使用嵌套/内部类,这使我们能够使代码更加面向对象和组织。

In PHP, I would like to do something like so:

在 PHP 中,我想做这样的事情:

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

    public class UserProfile {
      // some code here
    }

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

Is that possible in PHP? How can I achieve it?

这在 PHP 中可能吗?我怎样才能实现它?



UPDATE

更新

If it's impossible, will future PHP versions might support nested classes?

如果不可能,未来的 PHP 版本会支持嵌套类吗?

回答by Tivie

Intro:

介绍:

Nested classes relate to other classes a little differently than outer classes. Taking Java as an example:

嵌套类与其他类的关系与外部类略有不同。以Java为例:

Non-static nested classes have access to other members of the enclosing class, even if they are declared private. Also, non-static nested classes require an instance of the parent class to be instantiated.

非静态嵌套类可以访问封闭类的其他成员,即使它们被声明为私有。此外,非静态嵌套类需要实例化父类的实例。

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

There are several compelling reasons for using them:

使用它们有几个令人信服的理由:

  • 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 relate and embed it in that class and keep the two together.

如果一个类只对另一个类有用,那么将它关联并嵌入到该类中并将两者保持在一起是合乎逻辑的。

  • 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.

考虑两个顶级类 A 和 B,其中 B 需要访问 A 的成员,否则这些成员将被声明为私有。通过将 B 类隐藏在 A 类中,A 的成员可以被声明为私有,而 B 可以访问它们。此外,B 本身可以对外界隐藏。

  • Nested classes can lead to more readable and maintainable code.
  • 嵌套类可以产生更具可读性和可维护性的代码。

A nested class usually relates to it's parent class and together form a "package"

嵌套类通常与它的父类相关并一起形成一个“包”

In PHP

在 PHP 中

You can have similarbehavior in PHP without nested classes.

在没有嵌套类的情况下,您可以在 PHP 中拥有类似的行为。

If all you want to achieve is structure/organization, as Package.OuterClass.InnerClass, PHP namespaces might sufice. You can even declare more than one namespace in the same file (although, due to standard autoloading features, that might not be advisable).

如果您只想实现结构/组织,如 Package.OuterClass.InnerClass,PHP 命名空间可能就足够了。您甚至可以在同一个文件中声明多个命名空间(尽管由于标准的自动加载功能,这可能是不可取的)。

namespace;
class OuterClass {}

namespace OuterClass;
class InnerClass {}

If you desire to emulate other characteristics, such as member visibility, it takes a little more effort.

如果您希望模拟其他特征,例如成员可见性,则需要更多的努力。

Defining the "package" class

定义“包”类

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


Use case

用例

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

Testing

测试

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

Output:

输出:

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


NOTE:

笔记:

I really don't think trying to emulate innerClasses in PHP is such a good idea. I think the code is less clean and readable. Also, there are probably other ways to achieve similar results using a well established pattern such as the Observer, Decorator ou COmposition Pattern. Sometimes, even simple inheritance is sufficient.

我真的不认为尝试在 PHP 中模拟 innerClasses 是一个好主意。我认为代码不太干净和可读。此外,可能还有其他方法可以使用完善的模式(例如观察者、装饰者或组合模式)来实现类似的结果。有时,即使是简单的继承也足够了。

回答by Fabian Schmengler

Real nested classes with public/protected/privateaccessibility were proposed in 2013 for PHP 5.6 as an RFC but did not make it (No voting yet, no update since 2013 - as of 2016/12/29):

真正的嵌套类与public/ protected/private无障碍于2013年(自2013年以来没有投票到目前为止,还没有更新-提出了PHP 5.6作为RFC,但没能为2016年12月29日):

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

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

class foo {
    public class bar {

    }
}

At least, anonymous classes made it into PHP 7

至少,匿名类进入了 PHP 7

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

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

From this RFC page:

从这个 RFC 页面:

Future Scope

The changes made by this patch mean named nested classes are easier to implement (by a tiny bit).

未来范围

这个补丁所做的改变意味着命名嵌套类更容易实现(一点点)。

So, we might get nested classes in some future version, but it's not decided yet.

因此,我们可能会在未来的某个版本中获得嵌套类,但尚未决定。

回答by Sumoanand

You cannotdo this in PHP. However, there are functionalways to accomplish this.

不能在 PHP 中执行此操作。但是,有一些功能方法可以实现这一点。

For more details please check this post: How to do a PHP nested class or nested methods?

有关更多详细信息,请查看此帖子: 如何执行 PHP 嵌套类或嵌套方法?

This way of implementation is called fluent interface: http://en.wikipedia.org/wiki/Fluent_interface

这种实现方式称为流畅接口:http: //en.wikipedia.org/wiki/Fluent_interface

回答by Pascal9x

Since PHP version 5.4 you can force create objects with private constructor through reflection. It can be used to simulate Java nested classes. Example code:

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

回答by e_i_pi

As per Xenon's comment to An?l ?zselgin's answer, anonymous classes have been implemented in PHP 7.0, which is as close to nested classes as you'll get right now. Here are the relevant RFCs:

根据 Xenon 对 An?l ?zselgin 的回答的评论,匿名类已在 PHP 7.0 中实现,这与您现在所获得的嵌套类非常接近。以下是相关的 RFC:

Nested Classes (status: withdrawn)

嵌套类(状态:已撤销)

Anonymous Classes (status: implemented in PHP 7.0)

匿名类(状态:在 PHP 7.0 中实现)

An example to the original post, this is what your code would look like:

原始帖子的示例,这就是您的代码的样子:

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

This, though, comes with a very nasty caveat. If you use an IDE such as PHPStorm or NetBeans, and then add a method like this to the Userclass:

但是,这带有一个非常讨厌的警告。如果您使用 PHPStorm 或 NetBeans 等 IDE,然后将这样的方法添加到User类中:

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

...bye bye auto-completion. This is the case even if you code to interfaces (the I in SOLID), using a pattern like this:

...再见自动完成。即使您使用如下模式编码接口(SOLID 中的 I)也是如此:

<?php
    public class User {
        public $profile;

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

Unless your only calls to $this->profileare from the __construct()method (or whatever method $this->profileis defined in) then you won't get any sort of type hinting. Your property is essentially "hidden" to your IDE, making life very hard if you rely on your IDE for auto-completion, code smell sniffing, and refactoring.

除非您唯一的调用$this->profile来自__construct()方法(或$this->profile定义的任何方法),否则您将不会获得任何类型的提示。您的属性本质上是“隐藏”到您的 IDE 中的,如果您依赖 IDE 进行自动完成、代码异味嗅探和重构,这将使您的生活变得非常艰难。

回答by dkamins

You can't do it in PHP. PHP supports "include", but you can't even do that inside of a class definition. Not a lot of great options here.

你不能在 PHP 中做到这一点。PHP 支持“包含”,但您甚至不能在类定义中执行此操作。这里没有很多不错的选择。

This doesn't answer your question directly, but you may be interested in "Namespaces", a terribly ugly\syntax\hacked\on\top\of PHP OOP: http://www.php.net/manual/en/language.namespaces.rationale.php

这不会直接回答您的问题,但您可能对“命名空间”感兴趣,这是一个非常丑陋的\语法\hacked\on\top\of PHP OOP:http: //www.php.net/manual/en/language .namespaces.rationale.php

回答by An?l ?zselgin

It is waiting for voting as RFC https://wiki.php.net/rfc/anonymous_classes

它正在等待投票为 RFC https://wiki.php.net/rfc/anonymous_classes

回答by Rogerio Souza

I think I wrote an elegant solution to this problem by using namespaces. In my case, the inner class does not need to know his parent class (like the static inner class in Java). As an example I made a class called 'User' and a subclass called 'Type', used as a reference for the user types (ADMIN, OTHERS) in my example. Regards.

我想我通过使用命名空间为这个问题编写了一个优雅的解决方案。在我的例子中,内部类不需要知道他的父类(就像 Java 中的静态内部类)。作为示例,我创建了一个名为“User”的类和一个名为“Type”的子类,在我的示例中用作用户类型(ADMIN、OTHERS)的参考。问候。

User.php (User class file)

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 (An example of how to call the 'subclass')

Using.php(如何调用“子类”的示例)

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

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

回答by Arlon Arriola

You can, like this, in PHP 7:

你可以像这样,在 PHP 7 中:

class User{
  public $id;
  public $name;
  public $password;
  public $Profile;
  public $History;  /*  (optional declaration, if it isn't public)  */
  public function __construct($id,$name,$password){
    $this->id=$id;
    $this->name=$name;
    $this->name=$name;
    $this->Profile=(object)[
        'get'=>function(){
          return 'Name: '.$this->name.''.(($this->History->get)());
        }
      ];
    $this->History=(object)[
        'get'=>function(){
          return ' History: '.(($this->History->track)());
        }
        ,'track'=>function(){
          return (lcg_value()>0.5?'good':'bad');
        }
      ];
  }
}
echo ((new User(0,'Lior','nyh'))->Profile->get)();

回答by priyabagus

Put each class into separate files and "require" them.

将每个类放入单独的文件并“要求”它们。

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

<?php

    class UserProfile 
    {
        // Some code here
    }

?>

UserHistory.php

用户历史.php

<?php

    class UserHistory 
    {
        // Some code here
    }

?>