在PHP嵌套类或内部类(Nested or Inner Class in PHP)

2019-09-02 04:27发布

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

我知道,C ++,JavaRuby的甚至(可能其他编程语言)允许主类,允许进行内部嵌套/内部类的代码更面向对象和组织。

在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版本可能支持嵌套类?

Answer 1:

介绍:

嵌套类涉及到其他类别稍有不同,外班。 以Java作为一个例子:

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

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

有使用它们的一些令人信服的理由:

  • 它的逻辑分组那些只在一个地方使用的类的方法。

如果一个类只有一个其它类有用的,那么它是合乎逻辑的,并涉及在该类别中嵌入它和保持两个在一起。

  • 它增加了封装。

考虑两个顶级类,A和B,其中B需要访问,否则将被声明为private的成员。 通过A类内隐藏类B,A的成员可以声明为private和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

注意:

我真的不认为试图仿效innerClasses在PHP是一个好主意。 我认为的代码是不干净和可读性。 此外,可能有其他的方式来实现用一个完善的模式,如观察相似的结果,装饰鸥组成规律。 有时候,即使是简单的继承就足够了。



Answer 2:

真正的嵌套类与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页:

未来适用范围

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

所以,我们可能会嵌套类在将来的某个版本,但它尚未决定。



Answer 3:

不能在PHP中做到这一点。 不过,也有完成这一功能的方式。

欲了解更多详情,请查看这篇文章: 如何做一个PHP嵌套类或嵌套的方法呢?

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



Answer 4:

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


Answer 5:

你不能做到这一点在PHP。 PHP支持“包括”,但你甚至不能做一个类定义里面。 不是有很多伟大的选择这里。

这并不直接回答你的问题,但你可能有兴趣在“命名空间”,一个十分难看\语法\砍死\ PHP的面向对象的\顶部\: http://www.php.net/manual/en/language .namespaces.rationale.php



Answer 6:

按照氙对阿尼尔Özselgin的答案评论,匿名类已经在PHP 7.0,这是接近嵌套类,你会得到现在执行。 下面是相关RFC:

嵌套类(状态:撤销)

匿名类(状态:在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
            }
        }
    }
?>

其实,这不过配备了一个非常讨厌的警告。 如果使用IDE,比如PHPStorm或NetBeans的,然后这样的方法添加到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
            }
        }
    }
?>

除非你只调用$this->profile是从__construct()方法(或其他方法$this->profile中定义),那么你不会得到任何形式的类型提示。 你的财产基本上是“潜伏”到你的IDE,使生活很辛苦,如果你依靠你的IDE为自动完成,代码气味嗅和重构。



Answer 7:

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



Answer 8:

我想我用命名空间写了一个优雅的解决这个问题。 在我的情况下,内部类并不需要知道他的父类(如Java中的静态内部类)。 作为一个例子我在例如由称为“用户”和所谓的“类型”的子类类,用作用于用户类型的引用(ADMIN,其它)。 问候。

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


Answer 9:

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


Answer 10:

把每个班到单独的文件和“要求”他们。

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
    }

?>


文章来源: Nested or Inner Class in PHP