【问题标题】:How to get protected property of object in PHP如何在 PHP 中获取对象的受保护属性
【发布时间】:2013-12-18 12:26:58
【问题描述】:

我有一个对象,它有一些我想要获取和设置的受保护属性。对象看起来像

Fields_Form_Element_Location Object
(
[helper] => formText
[_allowEmpty:protected] => 1
[_autoInsertNotEmptyValidator:protected] => 1
[_belongsTo:protected] => 


[_description:protected] => 
[_disableLoadDefaultDecorators:protected] => 
[_errorMessages:protected] => Array
    (
    )

[_errors:protected] => Array
    (
    )
[_isErrorForced:protected] => 
[_label:protected] => Current City


[_value:protected] => 93399
[class] => field_container field_19 option_1 parent_1
)

我想获取对象的value 属性。当我尝试$obj->_value$obj->value 时,它会产生错误。我搜索并找到了使用PHP Reflection Class 的解决方案。它在我的本地工作,但在服务器 PHP 版本是 5.2.17 所以我不能在那里使用这个函数。那么任何解决方案如何获得这样的属性?

【问题讨论】:

  • 使用getter和setter
  • 您是否省略了一些上下文?您只需要编写适当的 setter/getter 方法对。如果你不能修改这个类,你可以简单地扩展它。
  • @Arnaud 我认为他已经了解 OOP 的基础知识。我认为这里真正的问题是他不能修改 Fields_Form_Element_Location 类。
  • 查看类代码或文档是否为您提供任何 getter 来访问这些数据。如果没有,您不应该访问它。找出为什么。如果您仍然需要访问它,则需要修改该类和/或与其作者交谈。
  • “付费工具”没有文档?那你付什么钱? D-;

标签: php object protected php-5.2


【解决方案1】:

这就是“受保护”的含义,正如Visibility 章节所解释的那样:

声明为受保护的成员只能在类本身内以及被继承类和父类访问。

如果您需要从外部访问该物业,请选择一个:

  • 不要将其声明为受保护,而是将其公开
  • 编写几个函数来获取和设置值(getter 和 setter)

如果您不想修改原始类(因为它是您不想乱用的第三方库),请创建一个扩展原始类的自定义类:

class MyFields_Form_Element_Location extends Fields_Form_Element_Location{
}

...并在那里添加您的 getter/setter。

【讨论】:

  • 但是如果你使用一些外部库并且你必须调试它并且想要打印一些受保护字段的值怎么办?
  • @KamilKiełczewski 对不起,我不明白你的意思。你可以debug any time。可见性是应用程序设计的一个概念。
  • 关键是当您有非常大的对象(在我的情况下是 laravel sqs queue )并且您必须通过 ssh 进行远程调试,并且您只想看到选择的(受保护/私有)字段(不是所有对象字段)。
  • @KamilKiełczewski 但是...您是想在我的回答中发表评论还是提出新问题?
  • @KamilKiełczewski 抱歉,我就是听不懂。如果你的意思是标准的 OOP 原则会以某种方式干扰调试,我强烈反对,但当然你总能找到一个更容易处理意大利面条的场景 ;-)
【解决方案2】:

如果你不能修改原始类并且扩展它也不是一个选项,你可以使用 ReflectionProperty 接口。

phptoolcase 库有一个方便的方法:

$value = PtcHandyMan::getProperty($your_object , 'propertyName');

来自单例类的静态属性:

$value = PtcHandyMan::getProperty('myCLassName', 'propertyName');

您可以在此处找到该工具:http://phptoolcase.com/guides/ptc-hm-guide.html

【讨论】:

    【解决方案3】:

    对象可以被类型转换为(关联)数组,并且受保护的成员具有以chr(0).'*'.chr(0) 为前缀的键(参见@fardelian 的评论here)。使用这个未记录的功能,您可以编写一个“exposer”:

    function getProtectedValue($obj, $name) {
      $array = (array)$obj;
      $prefix = chr(0).'*'.chr(0);
      return $array[$prefix.$name];
    }
    

    或者,您可以解析来自serialized 字符串的值,其中(似乎)受保护的成员具有相同的前缀。

    这在 PHP 5.2 中有效,没有 ReflectionClass 的开销。但是,某些属性受到保护并隐藏在客户端代码中是有原因的。读取或写入可能会导致数据不一致,或者作者提供了一些其他方式来公开它,以使界面尽可能精简。当有理由直接读取受保护的属性时,正确的方法是实现__get() 魔术方法,因此请始终检查是否有任何方法并查看它的作用。这种反直觉的查找最终在 PHP 8.1 中用readonly properties 解决了。

    从 PHP 8.0 开始,ReflectionClass 也可以访问 attributes metadata,在尝试闯入受保护的成员之前,请务必检查它们。 Attributes superseded "Annotations"1,所以也检查一下。

    1:注解对客户端编码人员来说是一个非常令人讨厌的惊喜:它们解析 cmets 以添加疯狂的花哨的黑盒无用的混乱功能,不应再使用,但它们仍然存在

    【讨论】:

    • 啊,有道理。当我尝试查看该对象时,它看起来就像*propertyName,所以当我无法访问它时我很困惑。
    【解决方案4】:

    这是一个非常简单的例子(没有错误检查)如何使用ReflectionClass

    function accessProtected($obj, $prop) {
      $reflection = new ReflectionClass($obj);
      $property = $reflection->getProperty($prop);
      $property->setAccessible(true);
      return $property->getValue($obj);
    }
    

    我知道你说你仅限于 5.2,但那是 2 年前的事了,5.5 is the oldest supported version,我希望能帮助人们使用现代版本。

    【讨论】:

    • 这是一个明确的 hack,但在我的情况下,我正在使用的框架限制了我的动作。只要您知道自己在做什么以及为什么,这个帮助并且可以很好地使用 - 谢谢! :)
    • 是的,我发现它在单元测试中非常有用,您希望检查分配给私有属性但不一定要将其公开。
    • ReflectionClass 在 5.x 的所有版本中都可用,并且不需要库、要求或配置(在所有 PHP 版本中可用)。
    • @PhilM 但ReflectionProperty::setAccessible 是 PHP 5 >= 5.3.0
    • Reflection 是其中之一,如果您发现自己在使用它,您应该仔细查看以确保您需要。也就是说,每件事都有时间和地点。我赞同这样的观点,即这在单元测试中派上用场,以确保对象可以访问它应该访问的数据。在我的例子中,我使用 Laravel 的 Queue 模拟来验证给定的 Job 已经被触发,而且它被赋予了正确的数据,存储在一个受保护的属性中。没有这个,异步使得这个断言有点棘手。谢谢@drewish。
    【解决方案5】:

    如果你想在不添加 getter 和 setter 的情况下修改一个类......

    PHP 7 在闭包上添加了一个 call($obj) 方法(比旧的 bindTo 更快),允许您调用一个函数,因此 $this 变量的行为就像在类中一样 - 具有完全权限。

     //test class with restricted properties
     class test{
        protected $bar="protected bar";
        private $foo="private foo";
        public function printProperties(){
            echo $this->bar."::".$this->foo;   
         }
     }
    
    $testInstance=new test();
    //we can change or read the restricted properties by doing this...
    $change=function(){
        $this->bar="I changed bar";
        $this->foo="I changed foo";
    };
    $change->call($testInstance);
    $testInstance->printProperties();
    //outputs I changed bar::I changed foo in php 7.0 
    

    【讨论】:

    • 正是我所需要的,但由于我只想要 var _eventPrefix 的受保护值,因此只需要简单的更改:$prefix = function() {return $this->_eventPrefix;}; $result = $prefix->call($obj);
    【解决方案6】:

    对于 PHP 7.4+,我们可以使用 Arrow FunctionClosure::call 来访问私有成员和受保护成员,只需一小行:

    PHP 7.4+

    检索受保护/私有成员:

    class Test {
      protected $data = 'Protected variable!';
    }
    
    // Will output "Protected variable!"
    echo (fn() => $this->data)->call(new Test);
    

    更改受保护/私有成员:

    class Test {
      protected $data = 'Testing';
    }
    
    $test = new Test;
    
    (fn() => $this->data = "New Data!")->call($test);
    
    // Will output "New Data!"
    echo (fn() => $this->data)->call($test);
    

    当然,如果我们想更改/使用多个成员,我们可以使用普通的Closure 函数:

    class Test {
      protected $data = 'Data!';
    }
    
    $test = new Test;
    
    (function() {
      $this->new_data = "New {$this->data}";
    })->call($test);
    
    // Will output "New Data!"
    echo (fn() => $this->new_data)->call($test);
    

    【讨论】:

    • 万一有人使用 php <7.4 然后 \Closure::fromCallable(function () { return $this->data; })->call($test); 应该像上面那样工作。
    【解决方案7】:

    我喜欢做的是将每个可以从外部写入的属性声明为 public。您希望对外界可见但不可写的属性应声明为 protected 并编写 __get() 魔术方法,以便您可以阅读它们。示例:

    /**
     * Class Test
     *
     * @property int $protected
     *
     */
    class Test
    {
        
        private const READABLE = ['protected'];
        
        protected $protected = 1;
        
        public $public = 2;
        
        public function __get($property)
        {
            //if you want to read every protected or private
            return $this->$property ?? null;
        
            //if you want only some protected and private values to be readable
            if (in_array($property, self::READABLE)) {
                return $this->$property;
            }
        }
    }
    
    $test = new Test();
    echo $test->protected; //outputs 1
    echo $test->public; //outputs 2
    
    $test->protected = 3; //outputs error - protected property
    

    最好的属性声明如下:

    public readonly $protected = 1; //only readable from the outside
    public  $public = 2; //readable and writable from the outside
    

    但是还没有这样的语法(或者……至少我不知道)。 PS您应该声明将在 Class DockBlock 中可读的 protected/private 属性,如图所示,以便您可以自动完成它们,否则您将能够访问它们,但在您编写代码时,您的 IDE 在自动完成时无法识别它们。

    【讨论】:

    • 你错过了“原因”的重点。大多数时候,我们需要访问私有成员,我们无法控制类来更改它;例如,我们不能也不想改变的包中的一个类。另一个用例是在编写测​​试时,我们需要在不公开这些成员的情况下检查内部类成员。
    猜你喜欢
    • 1970-01-01
    • 2019-05-21
    • 2011-06-06
    • 2019-12-30
    相关资源
    最近更新 更多