PHP 中的嵌套类或内部类

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

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

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

141149 次浏览

在 PHP 中不能这样做。PHP 支持“ include”,但是在类定义中甚至不能这样做。这里没什么好选择。

这并不能直接回答你的问题,但是你可能会对“名称空间”感兴趣,这是一个在 PHP OOP 上被破解的非常丑陋的语法: Http://www.php.net/manual/en/language.namespaces.rationale.php

您可以用 不能在 PHP 中完成这项工作,但是,有一些 功能性的方法可以完成这项工作。

更多细节请查看这篇文章: 如何做一个 PHP 嵌套类或嵌套方法?

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

简介:

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

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

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

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

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

如果一个类只对另一个类有用,那么对 将它们联系起来并嵌入到课堂中,使两者保持在一起。

  • 它增加了封装。

考虑两个顶级类,A 和 B,其中 B 需要访问 通过隐藏类 在类 A 中,A 的成员可以声明为 private,B 可以访问 此外,B 本身也可以对外界隐藏起来。

  • 嵌套类可以产生更具可读性和可维护性的代码。

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

在 PHP 中

您可以在 PHP 中使用 相似行为,而不需要嵌套类。

如果您想实现的全部目标是结构/组织,则为 Package。户外课程。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 中模拟 innerClass 是一个好主意。我认为代码不那么干净和可读。此外,可能还有其他方法可以使用一个完善的模式来实现类似的结果,如观察者、装饰者或组合模式。有时,即使是简单的继承也足够了。

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

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

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

class foo {
public class bar {
 

}
}

至少,匿名类进入了 PHP7

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

从这个 RFC 页面:

未来范围

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

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

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

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

Php (User class file)

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

Php (如何调用‘ subclass’的示例)

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


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

根据 Xenon 对 An l Özselgin 的回答的评论,匿名类已经在 PHP 7.0中实现了,这与您现在所了解的嵌套类非常接近。以下是相关的区域合作框架协议:

嵌套类(状态: 撤回)

匿名类(状态: 在 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->profile的唯一调用来自 __construct()方法(或定义 $this->profile的任何方法) ,否则不会得到任何类型提示。您的属性本质上是“隐藏”在 IDE 中的,如果您依赖 IDE 进行自动完成、代码嗅探和重构,那么生活将变得非常困难。

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

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
}


?>

用户历史记录

<?php


class UserHistory
{
// Some code here
}


?>

在 PHP7中,您可以这样做:

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

这个页面不断出现在我的互联网搜索这个主题,所以认为我应该插入,即使这是一个8岁的职位。PHP5文件演示了可以在类方法中定义匿名类。创建的对象可以扩展、实现甚至使用其他类、接口和特性。考虑下面的工厂对象生产的 OOP 范例。就像@e-i-pi 指出的..。

class Factory {
/**
*  Method to manufacture an inner-class object.
*
*  @param  string  $args   Arguments to be passed to
*                          the inner-class constructor.
*/
static function manufacture_object($args) {
/**
*  Here's the definition of the inner-class.
*/
return new class($args) {
static $remembers = 'Nothing';
private $args;
function __construct($args) {
$this->$args = $args;
}
function says() {
return $this->args;
}
};
}
}


/**
*  Create an inner-class object and have it do its thing.
*/
$mort = Factory::manufacture_object("Hello World!");
echo $mort->says();         // Echoes "Hello World!"

这些对象是一次性的,因此可以预期返回的对象的静态值不会从一个实例绑定到另一个实例。毕竟,匿名类从一个对象到另一个对象是唯一的。但是,后期静态绑定的工作方式与嵌套类的工作方式相同。

$mort = Factory::manufacture_object("I can remember that.");
$mort2 = Factory::manufacture_object("I'll live vicariously through you.");
$mort::$remembers = 'Something';
echo $mort2::$remembers;    // Echoes "Something"

所以,就是这样: 内部/嵌套类以及使用静态功能创建它们的对象已经成为可能的 从2013年9月22日开始(就在提出这个问题的时候)。