rikudou/friend-classes

允许其他类访问您的类的私有方法/属性

安装: 18

依赖关系: 0

建议者: 0

安全性: 0

星标: 4

关注者: 1

分支: 0

开放问题: 0

类型:composer-plugin

v2.0.0 2021-06-15 12:32 UTC

This package is auto-updated.

Last update: 2024-09-15 19:40:04 UTC


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生成新的自动加载器时清除缓存,例如在installupdaterequiredump-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