19

In some contexts, we can use an array($this, 'variable') syntax, to refer to object properties. Why doesn't compact(array($this, 'variable')) work? Is there a way to work around this?


class someclass {

    $result = 'something';

    public function output() {
        compact($this->result); // $this is a OOP keyword and I don't know how to use it inside a compact() brackets
    }
}

I have found only one solution at the moment:

$result = $this->result;
compact('result');

But this is ugly.

miken32
  • 42,008
  • 16
  • 111
  • 154
Buddy
  • 1,808
  • 3
  • 19
  • 28
  • I don't know a lot about this function, or "extract", but I can tell you you're missing a parentheses at the end there. Possibly just a transposing issue, but maybe that's your problem? In other news, I would venture to guess that $this is not part of the object's symbol table, and thus doesn't pack properly. – rockerest Feb 14 '11 at 16:49
  • there is no need for `array()`, and you must specify 'this', not $this. – greg0ire Feb 14 '11 at 16:53
  • @greg0rie, is that works by you? Which PHP version do you use? – Buddy Feb 17 '11 at 17:07

7 Answers7

16

I know this is old, but I wanted something like this for a project I'm working on. Thought I'd share the solution I came up with:

extract(get_object_vars($this));
return compact('result');

It scales rather nicely. For example:

<?php

class Thing {
    private $x, $y, $z;

    public function __construct($x, $y, $z) {
        $this->x = $x;
        $this->y = $y;
        $this->z = $z;
    }

    public function getXYZ() {
        extract(get_object_vars($this));
        return compact('x', 'y', 'z');
    }
}


$thing = new Thing(1, 2, 3);
print_r($thing->getXYZ());

Use with care.

Stoney
  • 698
  • 5
  • 11
  • `$vars = get_object_vars($this); extract($vars); return compact(array_keys($vars));` to get all the properties of an object – user151841 Dec 11 '21 at 01:53
11

Short answer: don't use compact(). In this situation, it's pointless (in most situations it's pointless, but that's another story). Instead, what's wrong with just returning an array?

return array('variable' => $this->variable);
ircmaxell
  • 163,128
  • 34
  • 264
  • 314
  • 3
    "in most situations it's pointless" - would you please elaborate? – goat Dec 16 '12 at 16:20
  • 3
    i dont get your note too. Compact help me a lot of times – kirugan Apr 23 '13 at 18:21
  • Although I agree in this scenario, as @chris asks, can you explain why the function is pointless so others can understand please? – Sam Delaney May 05 '13 at 18:19
  • i think he just meant to say you dont need a function to do array('key' => $val).. lol – chesscov77 Jul 14 '14 at 21:22
  • 1
    Personally I don't like to use compact. At a glance PHPStorm doesn't highlight a problem when I use variable names that haven't been defined. Also refactoring variable names is a little more difficult (Have to use rename all occurrences, which I don't love doing) – Carlton Oct 22 '18 at 11:29
9

compact() looks for the variable name in the current symbol table. $this does not exist in there. What do you expect the name of $this to be anyway?

You can do:

class Foo
{
    function __construct()
    {
        $that = $this;
        $var = '2';
        print_r( compact(array('that', 'var')) );
    }
}

Ironically, once you assigned $this to $that, $this can also be compacted with 'this' nistead of 'that'. See http://bugs.php.net/bug.php?id=52110. For performance reasons $this and super-globals are only populated when they are needed. If the aren't needed they don't exist.


EDIT after Update

Your compact($this->result); looks for 'something' defined within the local/current scope of the output() method. Since there is no such variable, the resulting array will be empty. This would work:

public function output() 
{
    $something = 1;
    print_r( compact($this->result) );
}
Gordon
  • 312,688
  • 75
  • 539
  • 559
6

You'll want to use get_class_vars(), which returns an associative array of all properties (class vars) of the provided class, with the var name as the key. Use get_class_vars( get_called_class() ) from within the class to get that class' name as a string. get_class( $this ) works as well.

Of course, this gives you all the properties of the class (regardless of access control modifier), so chances are you want to also filter this list. An easy (if somewhat obfuscated) way to do this is to use array_intersect_key() along with array_flip() as per this SE Answer. So the whole thing looks like:

array_intersect_key( get_class_vars( get_called_class() ), array_flip( array( 'var_1', 'var_2', 'var_3' ) ) );

You'll have to decide if the resulting code is really worth it. It's probably easier to read array( 'var_1' => $this->var_1, 'var_2' => $this->var_2 ); as @ircmaxell points out.

However, because a) I like Yak Shaving and b) because I am a very industrious lazy programmer and c) there was probably a one-liner in Perl that did this elegantly, here's the code in the context of the problem I was trying to address: overriding an associative array (that's also a property of the current class) with a number (but not all) of other properties of the same class.

<?php
class Test {
    protected $var_1 = 'foo';
    private $var_2 = 'bar';
    public $var_3 = 'baz';
    private $ignored_var = 'meh';

    public $ary = array( 
        'var_1' => 'bletch',
        'var_2' => 'belch',
        'var_4' => 'gumbo',
        'var_5' => 'thumbo'
    );

    public function test(){
        $override = array_intersect_key( get_class_vars( get_called_class() ), array_flip( array( 'var_1', 'var_2', 'var_3' ) ) );
        return array_merge( $this->ary, $override );
    }
}

$test = new Test();
var_dump( $test->test() );

Which produces the expected output:

array(5) { ["var_1"]=> string(3) "foo" ["var_2"]=> string(3) "bar" ["var_4"]=> string(5) "gumbo" ["var_5"]=> string(6) "thumbo" ["var_3"]=> string(3) "baz" }

Note that in my own usage, I wouldn't have broken this out into 2 lines, but I wanted to keep the code that refers to the OP separate.

Community
  • 1
  • 1
Tom Auger
  • 19,421
  • 22
  • 81
  • 104
  • 1
    *grunt grunt grunt* - [the sound a yak makes](http://www.youtube.com/watch?v=nZ_zm5oOuLc) – Eric Holmes Jul 23 '13 at 15:48
  • As a humorous anecdote, after shaving this particular Yak and writing this lovely SE Answer, it turns out that it won't work for me at all: my class properties are not named the same as the expected parameters in my associative array. Looks like I'll be using 'var_1' => $this->some_other_var. DOH – Tom Auger Jul 23 '13 at 15:48
2

I needed something like this today – the functionality of compact but in the scope of an object/array instead of the current symbol table. So I wrote this:

/**
 * Returns an associative array of object properties or array elements with values
 *
 * @see https://www.php.net/compact
 * @param mixed $obj The object from which to take properties
 * @param mixed $args The property names as strings or arrays of strings
 * @return array<string,mixed>
 */
function compact_with(mixed $obj, ...$args): array
{
    $ret = [];
    array_map(
        function ($v) use ($obj, &$ret) {
            if (is_array($v)) {
                $ret += compact_with($obj, ...$v);
            } else {
                $ret[$v] = is_array($obj) ? $obj[$v] : $obj->$v;
            }
        },
        $args
    );

    return $ret ?? [];
}

As with compact, it can accept strings or arrays as arguments, and works recursively with the latter, giving precedence to the first instance of a string label. And like compact since PHP 7.3, it emits a notice if an invalid property name is included as an argument.

Example:

$ob = new class {
    public $foo = 123;
    public $bar = 456;
    public $baz = 789;
};
print_r(compact_with($ob, "baz", "foo"));

$arr = [
    "foo" => 123,
    "bar" => 456,
    "baz" => 789
];
print_r(compact_with($arr, "baz", "foo"));

Output:

Array
(
    [baz] => 789
    [foo] => 123
)
Array
(
    [baz] => 789
    [foo] => 123
)
miken32
  • 42,008
  • 16
  • 111
  • 154
-1

You must define index-fields. Something like: $array = compact('this', 'variable')

http://php.net/manual/en/function.compact.php

Mano Kovacs
  • 1,484
  • 1
  • 13
  • 16
-1

I was searching for the same exact thing, wanting to preventing the ugliness. Later I ended up using the follow code, thought I should contribute it here:

return array_merge(compact('other', 'beautiful', 'variables'), 
  ['result' => $this->result]);

This way you can continue using compact for your other beautiful variables!

gX.
  • 922
  • 1
  • 11
  • 18