如何在PHP中获取对象的受保护属性

78

我有一个对象,其中包含一些受保护的属性,我想获取和设置它们。该对象如下所示:

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反射类 的解决方案。它在我的本地上工作了,但在服务器上,PHP版本是 5.2.17,因此我无法在那里使用此函数。有没有任何解决方案可以获得这样的属性?


你是否省略了一些上下文?你只需要编写相应的setter/getter方法对即可。如果你无法修改该类,你可以简单地继承它。 - Álvaro González
1
@Arnaud 我认为他已经理解了面向对象编程的基础知识。我认为真正的问题在于他无法修改 Fields_Form_Element_Location 类。 - idmean
1
查看类代码或文档,是否提供了任何 getters 来访问这些数据。如果没有,您不应该访问它。找出原因。如果您需要访问它,您需要修改类和/或与其作者交谈。 - deceze
你也没有相关文档吗...? - deceze
6
没有文档的“付费工具”?那你支付的是什么呢?D-; - deceze
显示剩余6条评论
8个回答

153

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

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

我知道你说你只能使用5.2版本,但那是两年前的事了。5.5是最老的受支持版本,我希望能够帮助使用现代版本的人们。


7
这确实是一种hack方法,但在我的情况下,我所使用的框架限制了我的操作。这个方法有帮助且可以使用,只要你知道自己在做什么以及原因-谢谢! :) - Stan Smulders
1
是的,在单元测试中,如果您想检查对私有属性的赋值,但不一定想将其公开,那么这很有用。 - drewish
@PhilM 但是 ReflectionProperty::setAccessible 是 PHP 5 >= 5.3.0。 - drewish
2
反射是其中一件事情,如果你发现自己在使用它,你应该好好审视一下确保你需要它。话虽如此,万物皆有时机和场合。我赞同这种方法在单元测试中非常有用,可以确保对象能够访问到它应该访问的数据。在我的情况下,我使用Laravel的队列模拟来验证一个给定的作业(Job)不仅已经启动,而且还被赋予了正确的数据,存储在受保护的属性中。由于是异步的,没有这个方法会使得这个断言变得有点棘手。感谢@drewish。 - kmuenkel
运行得非常顺利。将 obj 转换为数组的方法对我来说并不像这个例子(https://dev59.com/mGIj5IYBdhLWcg3wYUGQ#27754169)那样有效。 - Eje
显示剩余2条评论

68

对象可以被强制转换为(关联)数组,受保护的成员具有以 chr(0).'*'.chr(0) 为前缀的键(请参见@fardelian在这里的评论)。使用这个未记录的特性,您可以编写一个“曝光器”:

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

或者,您可以从序列化字符串中解析该值,在那里(似乎)受保护的成员具有相同的前缀。

在 PHP 5.2 中,这样做可以避免使用 ReflectionClass 带来的开销。但是,某些属性被保护并且对客户端代码隐藏的原因是有原因的。读取或写入可能会使数据不一致,或者作者提供了其他方式来公开它,以尽可能简洁地呈现接口。当有理由直接读取受保护的属性时,正确的方法是实现__get() 魔术方法,因此始终检查是否存在该方法并查看其功能。这种反直觉的查找通过只读属性在 PHP 8.1 中得到解决。

自 PHP 8.0 以来,ReflectionClass 还可以访问属性元数据,在尝试突破受保护成员之前,请确保也检查它们。 属性已取代“注释”1,因此也要检查它们。

1:注释对客户端编码人员来说是非常讨厌的惊喜:他们解析注释以添加复杂的黑盒无用混乱功能,不应再使用,但它们仍然存在。


啊,这很有道理。当我尝试查看对象时,它只是看起来像 *propertyName,所以当我无法使用它时感到困惑。 - mbomb007

23
这就是“protected”所用的,正如可见性章节所解释的那样:
成员声明为protected只能在类内部以及继承和父类中访问。
如果你需要从外部访问该属性,请选择以下之一:
- 不要将其声明为protected,而是声明为public - 编写一对函数来获取和设置值(getter和setter)
如果你不想修改原始类(因为它是一个你不想搞乱的第三方库),可以创建一个扩展原始类的自定义类。
class MyFields_Form_Element_Location extends Fields_Form_Element_Location{
}

...然后在那里添加你的getter/setter。

5
如果您使用了一些外部库,并且必须对其进行调试,想要打印一些受保护字段的值,该怎么办? - Kamil Kiełczewski
5
@KamilKiełczewski 抱歉,但我无法理解。如果你的意思是标准OOP原则会影响调试,我强烈反对,但当然你可以总是找到一种情况,其中意大利面更容易处理;-) - Álvaro González
1
如果这是第三方库,你不能仅仅扩展它。你还需要重写第三方库以使用你的扩展类(这是你不想做的)。如果这是他自己的代码,我敢肯定他不会使用protected关键字。我的意思是,这不是正确的答案。 - Toskan
@Toskan 是的,这可能会很棘手或根本不可能,这取决于库。我并不是要提供适用于每个库的通用解决方案。这是需要针对每个具体情况进行解决的问题。但我认为,只要可行,创建自己的扩展类是最好的方法。 - Álvaro González
好的,如果可能的话。然而,在95%或更多的情况下,这并不起作用。我觉得这个答案没有引导人们走向正确的方向。 - Toskan
显示剩余5条评论

19

如果您想要调整类而不添加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 

正是我需要的,但由于我只想要变量“_eventPrefix”的受保护值,所以只需要进行简单的更改:$prefix = function() {return $this->_eventPrefix;}; $result = $prefix->call($obj); - kiatng

8

PHP 7.4+ 版本中,我们可以使用 箭头函数Closure::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);应该像上面的代码一样工作。 - ssi-anik

3
如果您无法修改原始类且扩展也不是选项,您可以使用ReflectionProperty接口。 phptoolcase库有一个方便的方法来实现这一点:
$value = PtcHandyMan::getProperty($your_object , 'propertyName');

单例类的静态属性:
$value = PtcHandyMan::getProperty('myCLassName', 'propertyName');

你可以在这里找到这个工具:http://phptoolcase.com/guides/ptc-hm-guide.html

2
$a=json_encode((array)$obj);
$b=(array)json_decode(str_replace('\u0000*\u0000','',$a));

echo($b['value']);

我知道这很hacky,可能不是最好的方法...但它能用。 - Mike Willis

0
我喜欢将所有可从外部写入的属性声明为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

但是目前还没有这样的语法(或者至少我不知道)。P.S. 你应该像示例中一样声明将在类DockBlock中可读取的protected/private属性,以便您可以自动完成它们,否则您将能够访问它们,但是当您编写代码时,您的IDE将无法识别它们。


你没有理解“原因”的重点。大多数情况下,我们需要访问私有成员,但我们无法控制类来更改它;例如,来自我们无法或不想更改的包的类。另一个用例是在编写测试时,我们需要检查内部类成员,而不使这些成员公开可用。 - Christos Lytras

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接