PHP 中的嵌套类或内部类

2022-08-30 06:59:47

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

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版本会支持嵌套类吗?


答案 1

介绍:

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

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

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命名空间可能会 sufice。您甚至可以在同一文件中声明多个命名空间(尽管由于标准的自动加载功能,这可能不可取)。

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是一个好主意。我认为代码不太干净和可读。此外,可能还有其他方法可以使用完善的模式(例如观察者,装饰器ou COmposition模式)获得类似的结果。有时,即使是简单的继承也足够了。


答案 2

2013 年,PHP 5.6 作为 RFC 提出了具有 // 可访问性的真正嵌套类,但没有成功(尚未投票,自 2013 年以来没有更新 - 截至 2021/02/03):publicprotectedprivate

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

class foo {
    public class bar {
 
    }
}

至少,匿名类进入了PHP 7。

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

从此 RFC 页面:

未来范围

此修补程序所做的更改意味着命名的嵌套类更容易实现(稍微容易实现)。

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


推荐