rikudou / friend-classes
允许其他类访问您的类的私有方法/属性
Requires
- php: ^8.0
- composer-plugin-api: ^2.0
Requires (Dev)
- composer/composer: ^2.1
- friendsofphp/php-cs-fixer: ^3.0
- jetbrains/phpstorm-attributes: ^1.0
- phpstan/phpstan: ^0.12.89
README
受到 C++ 中友元类概念的启发,您可以指定可以访问其他无法访问的属性的类。
C++ 世界中的示例
class ClassWithPrivateProperty { private: int myPrivateProperty = 0; friend class MyOtherClass; // here we declare MyOtherClass as a friend } class MyOtherClass { public: void someMethod() { ClassWithPrivateProperty privateObject; auto result = privateObject.myPrivateProperty; // allowed because MyOtherClass is declared as a friend of ClassWithPrivateProperty } }
安装
composer require rikudou/friend-classes
这就完成了,该包现在自动启用并直接可用。
用法
安装此包后,您可以在 PHP 中使用类似的概念
<?php use Rikudou\FriendClasses\Attribute\FriendClass; #[FriendClass(MyOtherClass::class)] class ClassWithPrivateProperty { private int $myPrivateProperty = 0; private function myPrivateMethod(): bool { return true; } } class MyOtherClass { public function someMethod(): void { $privateObject = new ClassWithPrivateProperty(); $result = $privateObject->myPrivateProperty; $result = $privateObject->myPrivateMethod(); } }
注意上面 ClassWithPrivateProperty
类的 #[FriendClass]
属性。
您可以将其用于属性和方法。
您可以使用该属性多次以定义多个友元类
<?php use Rikudou\FriendClasses\Attribute\FriendClass; #[FriendClass('FriendClass1')] #[FriendClass('FriendClass2')] class MyClass { }
仅允许某些方法/属性
如果您只想允许访问某些属性和方法,可以直接在属性/方法上定义 FriendClass
属性。在这种情况下,类需要具有 #[HasFriendClasses]
属性或至少一个 #[FriendClass]
属性。
<?php use Rikudou\FriendClasses\Attribute\HasFriendClasses; use Rikudou\FriendClasses\Attribute\FriendClass; #[HasFriendClasses] class MyPrivateClass { #[FriendClass(ClassWithAccessToPrivateProperties::class)] private int $someProperty = 1; private int $someOtherProperty = 2; #[FriendClass(ClassWithAccessToPrivateProperties::class)] private function someMethod(): void { // nothing to do } private function someOtherMethod(): void { // nothing to do } } class ClassWithAccessToPrivateProperties { public function __construct() { $privateClass = new MyPrivateClass(); var_dump($privateClass->someProperty); // will dump 1 var_dump($privateClass->someOtherProperty); // will throw an error because this class is not a friend $privateClass->someMethod(); // won't fail $privateClass->someOtherMethod(); // will throw an error because this class is not a friend } }
如前所述,如果类已经包含 #[FriendClass]
属性,则不需要 #[HasFriendClasses]
。 #[HasFriendClasses]
仅是解析器检查类的提示,如果存在 #[FriendClass]
属性,也会发生这种情况。
在以下示例中,没有 #[HasFriendClass]
属性,而 Class1
可以访问 PrivateClass
的所有私有属性和方法,而 Class2
只能访问一些。
<?php use Rikudou\FriendClasses\Attribute\FriendClass; #[FriendClass(Class1::class)] class PrivateClass { #[FriendClass(Class2::class)] private int $accessibleToBothClasses = 1; private int $accessibleOnlyToClass1 = 2; } class Class1 { public function __construct() { $instance = new PrivateClass(); $instance->accessibleToBothClasses; $instance->accessibleOnlyToClass1; var_dump('This will get dumped because Class1 is a friend of the whole class and thus has access to everything'); } } class Class2 { public function __construct() { $instance = new PrivateClass(); $instance->accessibleToBothClasses; $instance->accessibleOnlyToClass1; var_dump('This will not get dumped because Class2 is only a friend to the $accessibleToBothClasses field'); } }
配置
所有配置都在 composer.json
文件中的 extra
.friendClasses
内完成,并且是可选的。
模式
您可以设置是否要访问属性、方法或两者。默认为两者。
{ "extra": { "friendClasses": { "mode": "methods" // or "both" or "properties" } } }
预加载
是否在生产模式下启用类预加载,请参阅以下说明。默认为 false。
{ "extra": { "friendClasses": { "preload": true } } }
要求
类不能有魔法 __get()
和 __call()
方法。
所有类都必须使用 composer 自动加载器加载。
带有注释的类必须使用一些标准的缩进,以便它能够工作,例如这个类将无法工作
<?php use Rikudou\FriendClasses\Attribute\FriendClass; #[FriendClass('SomeFriendClass')] class MyClass{private $property = 1;}
这是因为我很懒,不想处理这类情况。
它慢吗?我应该在生产环境中使用它吗?
有点慢。在生产模式下应该足够快,但当然没有使用它时快。
至于是否应该使用它,完全取决于您。友元类是一个功能强大的特性,但很容易误用。此外,此实现远非完美,更多的是为了演示目的。
但如果你想使用它,你可以。
它是如何工作的?
此库挂钩到 composer 并替换自动加载器。
每次您使用自动加载器加载一个类时,都会检查它是否包含 #[FriendClass]
属性,如果包含,则将一些特性注入到类中,这些特性完成了所有的工作。
特性定义了一个魔法 __get()
方法,并使用 debug_backtrace()
检查调用者。如果调用者是友元类之一,则返回属性的值,否则抛出 Error
。如果属性不存在,则生成一个警告(与 php 本身的行为相同)。
生产模式与开发模式
如果您处于开发模式,则每次都会将特性注入到类中,这意味着它有点慢,因为自动加载器必须检查类的内容并为每次运行注入特性。这确保了当您更改原始类时,注入的类也会更新。
在生产模式下,一旦注入的类被生成,它就不会被重新评估,直到你清除缓存(当Composer生成新的自动加载器时清除缓存,例如在install
、update
、require
、dump-autoload
等操作期间)。
要启用生产模式,只需在生成Composer自动加载器时使用标志--optimize
(或--classmap-authoritative
或快捷键-o
或-a
)。
生产模式下的预加载
当你处于生产模式时,可以在自动加载器导出期间注入类。当预加载被启用时,运行时不会进行类注入,这可以显著加快过程。
缺点是预加载器无法注入在导出过程中已经加载的类,这在大多数情况下不会成问题,但在一些边缘情况下可能会破坏你的应用。
另一个缺点是,当无法加载类(例如,由于扩展了不存在的类)时,它会失败,例如Symfony经常这样做。
如果你想启用预加载,可以通过在composer.json中设置配置来实现,如下所示
{ "require": { // your requires }, "extra": { "friendClasses": { "preload": true } } }
示例
- 生产模式不会被启用
composer install
composer update
composer require vendor/package
composer dump-autoload
- 生产模式将被启用
composer install --optimize
composer install --classmap-authoritative
composer install -o
composer install -a
composer update --optimize
composer update --classmap-authoritative
composer update -o
composer update -a
composer require vendor/package --optimize
composer require vendor/package --classmap-authoritative
composer require vendor/package -o
composer require vendor/package -a
composer dump-autoload --optimize
composer dump-autoload --classmap-authoritative
composer dump-autoload -o
composer dump-autoload -a