76

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ň
  • 31,451
  • 23
  • 125
  • 169
Awais Qarni
  • 17,492
  • 24
  • 75
  • 137
  • 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

8 Answers8

149

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
  • 9,042
  • 9
  • 38
  • 51
  • 7
    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
68

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 then-correct approach was to implement __get() magic method, so always check if there is any and see what it does. This counter intuitive lookup was finally solved in PHP 8.1 with readonly properties.

Since PHP 8.0, there also attributes metadata accessible by ReflectionClass, make sure to check them also before performing attempts to break into protected members. Attributes superseded "Annotations"1, so check them, too.

1: annotations are a very nasty surprise to client coders: they parse comments to add crazy fancy black-box useless confusing functionality, should not be used anymore, but they still exist

Jan Turoň
  • 31,451
  • 23
  • 125
  • 169
  • 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
  • 142,137
  • 41
  • 261
  • 360
  • 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
  • the problem with that is, that if you set it to public the value can be set from everywhere, which you basically never really want. The best way is to make it protected or even private and make __set and __get so you can control what is just readable, but not writable. Add the property to the DocBlock and you have autocomplete. I like this the best. – Lachezar Raychev May 30 '21 at 12:20
  • 1
    If it is a third party library, you cant just extend it. You have as well to rewrite the third party library to use your extended class (which you dont want to do). If it was his own code, Im pretty sure hed not use protected? What I want to say, this is not the correct answer. – Toskan Feb 28 '22 at 11:57
  • @Toskan Yes, it can be tricky or just plain impossible, depending on the library. I didn't mean to provide a generic solution that works for every library. This is something that needs to be addressed for each individual case. But I think that creating your own extended class is the best approach whenever it's feasible. – Álvaro González Feb 28 '22 at 12:18
  • well, if its possible. In 95% of all cases or more, this wont work though. I feel like this answer doesnt lead people in the right direction. – Toskan Feb 28 '22 at 13:42
19

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
  • 3,380
  • 3
  • 22
  • 41
  • 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
8

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
  • 36,310
  • 4
  • 80
  • 113
  • Just in case someone uses php `<7.4` then `\Closure::fromCallable(function () { return $this->data; })->call($test);` should work like the above one. – ssi-anik Oct 28 '21 at 09:21
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
  • 36,310
  • 4
  • 80
  • 113
Charlie
  • 61
  • 3
2
$a=json_encode((array)$obj);
$b=(array)json_decode(str_replace('\u0000*\u0000','',$a));

echo($b['value']);
Andreea Onica
  • 315
  • 5
  • 13
0

What I like to do is declare every property that would be writable from outside as public. The properties that you want to be visible for the outside world but not writable you should declare as protected and write __get() magic method so you can read them. Example:

/**
 * 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

The best would be to have property declaration like:

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

but no such syntax exists yet(or... at least I don't know about it). P.S. you should declare the protected/private properties that will be readable in the Class DockBlock as shown, so you can autocomplete them, otherwise you will be able to access them, but your IDE won't recognize them on autocomplete when you are writing code.

Lachezar Raychev
  • 2,113
  • 4
  • 24
  • 34
  • You're missing the point of the "cause". Most of the times we'd need to access private members we won't have control over the class to change it; for example a class from a package that we can't nor want to alter. Another use case is when writing tests and we need to check internal class members without making these members publicly available. – Christos Lytras Oct 12 '21 at 07:28