PHP中的嵌套或内部类

Posted

技术标签:

【中文标题】PHP中的嵌套或内部类【英文标题】:Nested or Inner Class in PHP 【发布时间】:2013-05-01 17:08:39 【问题描述】:

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

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

【问题讨论】:

这在 PHP 中是不可能的 你可以让它扩展User,例如:public class UserProfile extends Userpublic class UserHestory extends User 你也可以从一个抽象的用户类开始,然后扩展它。 php.net/manual/en/language.oop5.abstract.php @DaveChen 我熟悉扩展类,但我正在寻找更好的 OOP 解决方案 :( Thx. 扩展与包含不同......当你扩展时,你会得到用户类的重复 3 次(作为用户、作为用户配置文件和作为用户历史) 【参考方案1】:

简介:

嵌套类与其他类的关系与外部类略有不同。以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 是一个好主意。我认为代码不太干净和可读。此外,可能还有其他方法可以使用成熟的模式(例如观察者、装饰器或组合模式)来实现类似的结果。有时,即使是简单的继承也足够了。

【讨论】:

【参考方案2】:

具有public/protected/private 可访问性的真正嵌套类于 2013 年作为 RFC 被提议用于 PHP 5.6,但没有实现(尚未投票,自 2013 年以来没有更新 - 截至 2021 年/02/03):

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

class foo 
    public class bar 
 
    

至少,匿名类进入 PHP 7

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

来自此 RFC 页面:

未来范围

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

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

【讨论】:

最近的 PHP 8 怎么样? @T.Todua 不,什么都没发生 不适合我。 Parse error: syntax error, unexpected 'class' (T_CLASS), expecting function (T_FUNCTION) or const (T_CONST) in C:\xampp\htdocs\fastAuth\Fast-Auth\class.FastAuth.php on line 5 是的,我使用的是 PHP 版本 7.3.16 @ShubhamGupta 这就是我写的。这是一个 RFC,尚未实施,甚至尚未获得批准。它可能永远不会到来。【参考方案3】:

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

更多详情请查看此帖: How to do a PHP nested class or nested methods?

这种实现方式称为fluent interface:http://en.wikipedia.org/wiki/Fluent_interface

【讨论】:

是的,不幸的是这是传统方式 Fluent 接口与声明“嵌套”或“内部”类不同。【参考方案4】:

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

Nested Classes (status: withdrawn)

Anonymous Classes (status: implemented in 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 等 IDE,然后在 User 类中添加这样的方法:

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

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

<?php
    public class User 
        public $profile;

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

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

【讨论】:

【参考方案5】:

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

【讨论】:

【参考方案6】:

您可以像这样在 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)();

【讨论】:

【参考方案7】:

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

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

【讨论】:

命名空间当然可以更好地组织代码,但它不如嵌套类强大。感谢您的回答! 为什么说它“可怕”?我认为这没关系,并且与其他语法上下文很好分开。【参考方案8】:

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

【讨论】:

我不相信匿名类会提供嵌套类的功能。 在 RFC 页面中,如果您搜索“nested”,您可以看到它支持。与Java方式不完全相同,但它支持。 在 PHP 7 中实现。【参考方案9】:

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

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

【讨论】:

【参考方案10】:

从 PHP 8.1 开始,它变得更好了。结合Anonymous classes 和New in initializers 功能,你会得到这样的结果:

class User

    public $profile = new class 
        // Some code here
    ;
    private $history = new class 
        // Some code here
    ;

    public function __construct()
    

好处是,与 PHP7 天不同(由 e_i_pi 回答),您不需要在构造函数中定义类。这样,(1)构造函数不会被污染,并且(2)在许多嵌套类(甚至一个)的情况下,您不会过度缩进(即,您不会将缩进加倍)您定义了一个新的嵌套类)。很酷。

等等...但是如果你想多次实例化一个类怎么办?好吧,让我们把更多的东西结合起来:你可以将Arrow functions 带入行动中(好吧,包括其他新功能作为它的调味料,比如Constructor property promotion、Enumerations 和Readonly properties):

class UserHistory

    public $newAction = fn(...$args) => new class(...$args) 
        public function __construct(
            // Think ActionType a defined enumeration
            public readonly ActionType $type,
            public readonly string $description = '',
        ) 
    ;

    private array $actions = [];

    public function add($action)
    
        $this->actions[] = $action;
    


// Test
$userHistory = new UserHistory();
$userHistory->add(
    // Again, suppose ActionType enumeration is defined
    $userHistory->newAction(ActionType::CREATE_ACCOUNT)
);

令人印象深刻!是的,但它有一个主要缺点:您不能使用匿名类的类型(例如查看UserHistory::add())。可能还有其他问题,例如编辑器提供的自动完成功能无法正常工作。但是,忍受它;因为当你取得其他成就时,你经常会失去一些东西。

【讨论】:

【参考方案11】:

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

用户.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
    

?>

【讨论】:

您可以将所有类放在一个文件中。

以上是关于PHP中的嵌套或内部类的主要内容,如果未能解决你的问题,请参考以下文章

私有的嵌套类(内部或静态)是不是可能具有具有公共访问权限的方法?

动态实例化嵌套在抽象类中的内部类

如何避免内部类中的内存泄漏

Java | 内部类(Inner Class)

《java基础知识》Java内部类及其实例化

静态类和内部类的区别是啥