20

I've got a class Foo with public and protected properties. Foo needs to have a non-static method, getPublicVars() that returns a list of all the public properties of Foo (this is just an example, I know from outside the Foo object calling get_object_vars() will accomplish this and there is no need for my getPublicVars() method).

Note: This must also return dynamically declared properties assigned at runtime to the class instance (object) that aren't defined in the class's definition.

Here's the example:

class Foo{
    private $bar = '123';
    protect $boo = '456';
    public   $beer = 'yum';

   //will return an array or comma seperated list
   public function getPublicVars(){
      // thar' be magic here...
   } 
}

 $foo = new Foo();
 $foo->tricky = 'dynamically added var';

 $result = $foo->getPublicVars();  
 var_dump($result); // array or comma list with 'tricky' and 'beer'   

What is the most concise way to get the only the public properties of an object from inside a class's own methods where both public and protected are visible?

I've looked at:

But this doesn't seem to address my question as it points to using get_object_vars() from outside the object.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Ray
  • 40,256
  • 21
  • 101
  • 138

6 Answers6

38

Since PHP 8.1 (Nov 2021) first class callable syntax is available to create the closure, then __invoke() it:

return get_object_vars(...)->__invoke($this);

Since PHP 7.1 (Dec 2016) creating a closure from a callable is available to create the closure, then __invoke() it:

return \Closure::fromCallable("get_object_vars")->__invoke($this);

Since PHP 7.0 (Dec 2015) a closure can be created, bound out of scope:

return (function($object){return get_object_vars($object);})->bindTo(null, null)($this);

(ikkez has a PHP 7.4 variation of it in their answer back in 2022.)

Before PHP 7.0, the implementation-specified behaviour of call_user_func() could be used, this is Brad Kents' example:

return call_user_func('get_object_vars', $this);

As you already realized, PHP's build in get_object_vars is scope-sensitive. You want the public object properties only.

So from that function to the public variant is not a large step:

function get_object_public_vars($object) {
    return get_object_vars($object);
}

Calling this get_object_public_vars will give you only the public properties then because it is a place out of scope for the current $object.

If you need more fine-grained control, you can also make use of the ReflectionObject:

(new ReflectionObject($this))->getProperties(ReflectionProperty::IS_PUBLIC);

Which has the benefit that you don't need to introduce another function in the global namespace.


The function create_user_func() shouldn't be used (because eval() and IIRC each time a new function was created). Luckily, since PHP 5.3 (Jun 2009) there are anonymous functions.

However, it is a correct answer to the question given when jumc answered in Apr 2013 Brad Kents' hack was not there yet and no other answer existed to show changing the scope of the closure that requires PHP 5.4 (Mar 2012; "Implemented closure rebinding as parameter to bindTo." ref).

Therefore, for completeness, the PHP 5.4 syntax closure example:

$scope = array ( function ($object) { return get_object_vars($object); }, 'bindTo' );
return call_user_func($scope(null, null), $this);

as the alternative form of:

$scope = create_function('$object', 'return get_object_vars($object);');
return $scope($this);
hakre
  • 193,403
  • 52
  • 435
  • 836
  • Also should be noted this only works if the function is a function in the object in question ... as per the main example from: http://www.php.net/manual/en/function.get-object-vars.php – Brian Oct 29 '12 at 15:02
  • That function in the answer is in the global namespace and not part of the same class naturally, otherwise this won't work. – hakre Oct 29 '12 at 15:03
  • Example demo for the global function: http://codepad.org/AgW8IAyy and also for the `ReflectionObject`: http://codepad.org/pIqBv4Vi – hakre Oct 29 '12 at 15:06
  • @hakre will this work with run time added properties? See my edited question. – Ray Oct 29 '12 at 15:08
  • @Ray, yes: http://codepad.org/HBYNXklr & http://codepad.org/pClcEhbb - albeit that was really trivial to mock ;) you would have tested it faster than editing the quesiton. --- http://codepad.org/GANWsQtA – hakre Oct 29 '12 at 15:09
  • @hakre I added the edit to the question before reading your answer. – Ray Oct 29 '12 at 15:11
  • @Ray: No problemo. Just was joking a bit. – hakre Oct 29 '12 at 15:14
  • @hakre Your answer solved my original question. However, I am having a bit of an issue when I try this across several layers of inheritance. I'm going to bash on it, but could I message you if I think I've found an issue--like if a runtime property added in the parent class (via a method) isn't showing up? Don't know if it's user error at this point... – Ray Oct 29 '12 at 15:18
  • @Ray: Classes don't have runtime properties. Only objects have. So you should not run into such a problem. If you want to filter based on which class has the property *originally*, the reflection property has the classname. But in any case, just leave a comment here if you run into a problem. No problem. – hakre Oct 29 '12 at 15:21
  • @hakre actually, your example requires a function outside the object. I'm trying to do this all internal to the object. – Ray Oct 29 '12 at 15:29
  • @Ray: Take the reflection example(s). See the codepad links placed in the comments. E.g. http://codepad.org/GANWsQtA - don't get irritated that the global function is still on top, it's unused, I just copied it over and forgot to remove it. – hakre Oct 29 '12 at 15:30
  • @hakre Can you look at http://codepad.org/4wNQFHhC . Not sure why the property 'runtime' isn't showing. I'm assuming Reflection only can work with properties defined in the class. – Ray Oct 29 '12 at 15:35
  • @hakre Doh! I'm using ReflectionClass, not ReflectionObject... my bad. Been looking at the same code block too long!!!! – Ray Oct 29 '12 at 15:36
  • @Ray: It took a while, in 2013 Brad Kent has shown a hack how to do it, which broke in PHP 7.0. I've now updated this answer in the spirit of Bard's across all the PHP versions on how to solve it dynamically in-place, that is w/o adding a global defined/named function nor reflection. Perhaps esoteric, but it's good to keep things updated. – hakre Aug 07 '23 at 23:11
22

Does not work with php version >=7
As such, I can't really recommend solution any longer.
Use reflection instead

To get the public properties from within the class

$publicProperties = call_user_func('get_object_vars', $this);

the "trick" is that get_object_vars is being called from the scope of call_user_func and not the scope of the object

no need for reflection, stand-alone functions, closures, etc

Brad Kent
  • 4,982
  • 3
  • 22
  • 26
  • 1
    thank you, this is the best answer IMHO, as `get_object_vars` will return protected and private attributes if used in the same class like here http://pastebin.com/FjqzYvLC – vincent Aug 24 '15 at 08:50
  • 12
    **Does not work with php version >=7** looks like this relies on a bug that was fixed in php 7. Whilst a valid answer at the time of writing, it is no longer a good idea to use this - use `ReflectionObject` instead – Steve Jan 07 '16 at 12:06
  • @Steve: You can call it a bug, but I'd prefer to say implementation-specified behaviour. I've now triple-checked this Q&A today and Brad's answer is a pretty good one, even it didn't stand the test of time for PHP 7+. I've updated my own (shameless plug) and put upfront what is a replacement of call_user_func() + get_object_vars() on $this for public ones since PHP 7.0, and it takes up to PHP 8.1 for a similar easy way and with better representation: `get_object_vars(...)->__invoke($this)`. And it would not be without this answer by Brad. And reflection was never needed, I got this wrong, too. – hakre Aug 07 '23 at 22:50
1

Another (PHP 7.* compatible) way is to cast $this to an array and filter out any binary keys. This is a little less verbose than using Reflection.

return array_filter(
    (array) $this,
    static fn(string $key): bool => strpos($key, "\0") !== 0, ARRAY_FILTER_USE_KEY,
);
Doeke Norg
  • 56
  • 10
0

According to this article (written by Vance Lucas), you can create a new call scope inside your "Foo" class definition using an "anonymous" function, and then you can call get_object_vars() from within. This allow you to get only the public properties from inside your class, even if these have been created dynamically later from the outside.

So adapted to your example it would be:

<?php
class Foo {
    private $bar = '123';
    protected $boo = '456';
    public   $beer = 'yum';

   // return an array of public properties 
   public function getPublicVars(){
      $publicVars = create_function('$obj', 'return get_object_vars($obj);');
        return $publicVars($this);
   } 
}

 $foo = new Foo();
 $foo->tricky = 'dynamically added var';

 $result = $foo->getPublicVars();  
 print_r($result);

and the output will be:

Array
(
    [beer] => yum
    [tricky] => dynamically added var
)

There is a second example in the article mentioned above that shows another way to do the same using the so-called "closures" (from php 5.3) but for some reason it doesn't work for me with php v5.4 so the private and protected properties remains included in the resulting array.

Community
  • 1
  • 1
jumc
  • 57
  • 3
0

Using the ReflectionClass, you can do this easily. For example, below, I make an associative array of all the public properties.

$rc = new \ReflectionClass($this);

//get all the public properties.
$props = $rc->getProperties(\ReflectionProperty::IS_PUBLIC);

$ret = [];

foreach($props as $p) {
    //get the name and value of each of the public properties.
    $ret[$p->getName()] = $p->getValue($this);
}
nucc1
  • 324
  • 2
  • 8
0

You can wrap it into a closure and bind it to nowhere, then you'll have an outer scope view to the local public properties.

public function getPublicVars(){
  return \Closure::bind(fn($obj) => get_object_vars($obj),null,null)($this);
}
ikkez
  • 2,052
  • 11
  • 20