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 User
和public 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->profile
的唯一调用来自__construct()
方法(或$this->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中的嵌套或内部类的主要内容,如果未能解决你的问题,请参考以下文章