63

I have a object having some protected property that I want to get and set. The object looks like

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
)

I want to get value property of the object. When I try $obj->_value or $obj->value it generates error. I searched and found the solution to use PHP Reflection Class. It worked on my local but on server PHP version is 5.2.17 So I cannot use this function there. So any solution how to get such property?

Jan Turoň
  • 26,696
  • 21
  • 102
  • 153
Awais Qarni
  • 14,876
  • 22
  • 72
  • 134
  • Use getter and setter – luxcem Dec 02 '13 at 17:35
  • Have you omitted some context? You just need to write the appropriate setter/getter pair of methods. And if you cannot modify the class you can simply extend it. – Álvaro González Dec 02 '13 at 17:36
  • 1
    @Arnaud I think he has understood the basics of OOP. I think the real problem here is that he can't modify Fields_Form_Element_Location class. – idmean Dec 02 '13 at 17:37
  • @ÁlvaroG.Vicario Yes I have only shown some properties of object. The object was too large to show here. – Awais Qarni Dec 02 '13 at 17:38
  • 1
    Look at the class code or documentation whether it offers you any *getters* to access this data. If not, you're not supposed to access it. Find out why. If you need to access it anyway, you need to modify the class and/or talk to its author. – deceze Dec 02 '13 at 17:47
  • @deceze I cannot access the class as it is encrypted :-( – Awais Qarni Dec 02 '13 at 17:57
  • I wasn't clear enough. With "missing context" I didn't mean "a larger dump". Since you ask an obvious question, you mention reflection and you post a `print_r()` rather than PHP code I assumed you're doming something very specific or using a concrete tool. – Álvaro González Dec 02 '13 at 17:58
  • And you've got no documentation for it either...? – deceze Dec 02 '13 at 17:59
  • @ÁlvaroG.Vicario Actually I am overriding the core behaviour of system – Awais Qarni Dec 02 '13 at 18:11
  • @deceze No documentation either :-( Its a paid tool – Awais Qarni Dec 02 '13 at 18:19
  • 6
    "Paid tool" without documentation? What are you paying for then? D-; – deceze Dec 03 '13 at 09:51

7 Answers7

125

Here's the really simple example (with no error checking) of how to use ReflectionClass:

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

I know you said you were limited to 5.2, but that was 2 years ago, 5.5 is the oldest supported version and I'm hoping to help people with modern versions.

drewish
  • 8,029
  • 8
  • 36
  • 49
  • 6
    It's a definite hack, but in my case the framework I was working with restricted my movements. This one helped and is fine to use as long as you know what you're doing and why - thanks! :) – Stan Smulders Nov 10 '15 at 09:01
  • 1
    Yeah, I found it really useful in unit tests where you want to check an assignment to a private property but don't necessarily want to make it public. – drewish Oct 05 '16 at 14:06
  • ReflectionClass is available in all versions of 5.x, and has no libraries, requirements or configuration required (available in all builds of PHP). – Phil M Jan 14 '17 at 00:06
  • @PhilM but `ReflectionProperty::setAccessible` is PHP 5 >= 5.3.0 – drewish Feb 13 '18 at 20:24
  • 2
    Reflection is one of those things where if you find yourself using it, you should take a good hard look to make sure you need to. That said, there's a time and place for everything. I second the opinion that this comes in handy in unit-tests for ensure that an object has access to the data that it should. In my case, I'm using Laravel's Queue mocking to verify not only that a given Job has fired, but that it was given the proper data, stored in a protected property. Being asynchronous makes that assertion a little tricky to make without this. Thanks @drewish. – kmuenkel Apr 19 '18 at 14:29
  • Worked flawlessly. Converting the obj to array didn't work for me like this one: https://stackoverflow.com/a/27754169/9933916 – Eje Apr 11 '20 at 00:04
  • @Jee Both work for me. However, I think using reflection would probably be easier if you wanted to set the property, not just get it. – mbomb007 Oct 27 '20 at 21:33
  • I love you !! saved me a lot of time , freakin protected property ... – Alin Razvan Jan 25 '21 at 06:22
59

Object can be typecasted into (associative) array and the protected members have keys prefixed with chr(0).'*'.chr(0) (see @fardelian's comment here). Using this undocummented feature you can write an "exposer":

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

Alternatively, you can parse the value from serialized string, where (it seems) protected members have the same prefix.

This works in PHP 5.2 without the overhead of ReflectionClass. However, there are reasons why some property is protected and hidden from client code. The reading or writing can make the data inconsistent or the author provides some other way to expose it in effort to make the interface as lean as possible. When there are reasons to read the protected property directly, the correct approach is to implement __get() magic method, so always check if there is any.

Jan Turoň
  • 26,696
  • 21
  • 102
  • 153
  • Ah, that makes sense. When I tried viewing the object, it just looked like `*propertyName`, so I was confused when I couldn't access it with that. – mbomb007 Oct 27 '20 at 21:11
23

That's what "protected" is meant for, as the Visibility chapter explains:

Members declared protected can be accessed only within the class itself and by inherited and parent classes.

If you need to access the property from outside, pick one:

  • Don't declare it as protected, make it public instead
  • Write a couple of functions to get and set the value (getters and setters)

If you don't want to modify the original class (because it's a third-party library you don't want to mess) create a custom class that extends the original one:

class MyFields_Form_Element_Location extends Fields_Form_Element_Location{
}

... and add your getter/setter there.

Álvaro González
  • 128,942
  • 37
  • 233
  • 325
  • 5
    but what if you use some external library and you must debug it and want to print values of some protected fields? – Kamil Kiełczewski Jan 10 '17 at 09:09
  • @KamilKiełczewski Sorry, I don't understand what your point is. You can [debug any time](https://3v4l.org/LMWp4). Visibility is a concept for application design. – Álvaro González Jan 10 '17 at 09:23
  • the point is when you have very large object (in my case is laravel sqs queue ) and you must remotley debug via ssh, and you want see only choosen (protected/private) fields (not all object fields). – Kamil Kiełczewski Jan 10 '17 at 10:46
  • @KamilKiełczewski But... Are you trying to comment something in my answer or asking a new question? – Álvaro González Jan 10 '17 at 11:09
  • it's only comment about something that you not include in your answer - but don't worry :P – Kamil Kiełczewski Jan 10 '17 at 11:17
  • 5
    @KamilKiełczewski Sorry but I simply can't get it. If you mean that standard OOP principles somehow interfere with debugging I strongly disagree, but of course you can always find a scenario where spaghetti is easer to deal with ;-) – Álvaro González Jan 10 '17 at 11:21
18

If you want to tinker with a class without adding getters and setters....

PHP 7 adds a call($obj) method (faster than old bindTo) on closures allowing you to call a function so the $this variable will act just as it would within a class -with full permissions.

 //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 
user2782001
  • 2,928
  • 2
  • 15
  • 32
  • Just what I need, but since I just wanted the protected value of var `_eventPrefix`, it just needs simple change: `$prefix = function() {return $this->_eventPrefix;}; $result = $prefix->call($obj);`. – kiatng Mar 11 '20 at 05:25
3

If you cannot modify the original class and extending it is not an option either, you can use the ReflectionProperty interface.

The phptoolcase library has a handy method for this:

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

Static property from a singleton class:

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

You can find the tool here: http://phptoolcase.com/guides/ptc-hm-guide.html

Christos Lytras
  • 31,296
  • 3
  • 53
  • 82
Charlie
  • 61
  • 3
1

For PHP 7.4+, we can use an Arrow Function and the Closure::call to access private and protected members using just one small line:

PHP 7.4+

Retrieving protected/private members:

class Test {
  protected $data = 'Protected variable!';
}

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

Altering protected/private members:

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);

Of course, we can use a normal Closure function if we want to alter/use multiple members:

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);
Christos Lytras
  • 31,296
  • 3
  • 53
  • 82
-1
  $propGetter = Closure::bind(  function($prop){return $this->$prop;}, $element['field_text']['#object'], $element['field_text']['#object'] );
  drupal_set_message('count='.count($propGetter('hostEntity')->field_captioned_carousel['und']));