PHP中的嵌套或内部类

问题描述 投票:90回答:10

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

我知道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?我怎样才能实现它?


UPDATE

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

php class oop nested inner-classes
10个回答
123
投票

介绍:

嵌套类与其他类的关系与外部类略有不同。以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 {}

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

定义“包”类

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中模拟innerClasses是一个好主意。我认为代码不那么干净和可读。此外,可能还有其他方法可以使用完善的模式来实现类似的结果,例如Observer,Decorator ou COmposition Pattern。有时,即使是简单的继承就足够了。


-4
投票

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

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

UserProfile.php

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

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

UserHistory.php

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

19
投票

使用qazxsw poi / qazxsw poi / qazxsw poi可访问性的真正嵌套类在2013年被提议用于PHP 5.6作为RFC但没有成功(没有投票,自2013年以来没有更新 - 截至2016/12/29):

public

protected

至少,匿名类使其成为PHP 7

private

从这个RFC页面:

Future Scope

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

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


12
投票

你不能用PHP做到这一点。但是,有一些功能方法可以实现这一目标。

有关详细信息,请查看此帖:https://wiki.php.net/rfc/nested_classes

这种实现方式称为流畅的界面:class foo { public class bar { } }


4
投票

从PHP 5.4版开始,您可以通过反射强制创建具有私有构造函数的对象。它可以用于模拟Java嵌套类。示例代码:

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

3
投票

你不能用PHP做到这一点。 PHP支持“include”,但你甚至不能在类定义中做到这一点。这里没有很多很棒的选择。

这不能直接回答你的问题,但是你可能会对“命名空间”感兴趣,这是一个非常丑陋的\ syntax \ hacked \ on \ top \ of PHP OOP:How to do a PHP nested class or nested methods?


2
投票

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

http://en.wikipedia.org/wiki/Fluent_interface

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

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

http://www.php.net/manual/en/language.namespaces.rationale.php

但是,这有一个非常令人讨厌的警告。如果您使用PHPStorm或NetBeans等IDE,然后将这样的方法添加到Nested Classes (status: withdrawn)类:

Anonymous Classes (status: implemented in PHP 7.0)

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

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

除非您对User的唯一调用来自public function foo() { $this->profile->... } 方法(或任何<?php public class User { public $profile; public function __construct() { $this->profile = new class implements UserProfileInterface { // Some code here for user profile } } } ?> 定义的方法),否则您将不会得到任何类型的提示。您的属性基本上是“隐藏”到您的IDE,如果依靠您的IDE进行自动完成,代码嗅闻和重构,生活会变得非常困难。


1
投票

它正在等待RFC $this->profile投票


1
投票

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

User.php(用户类文件)

__construct()

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

$this->profile

0
投票

你可以像这样在PHP 7中:

https://wiki.php.net/rfc/anonymous_classes
© www.soinside.com 2019 - 2024. All rights reserved.