Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Opposite of __set_state() like __get_state()?

Tags:

object

php

class

Is there an opposite PHP function for __set_state() like __get_state()? And I don't mean __sleep() for serialization. I want a simple function which is called after var_export() is called on a object but before var_export() gets the data so I can choose on each object which data will be exported. I know there is a way to implement this with __get() and debug_backtrace() to modify the data only when var_export() is called on a object. But is there a simpler way?

Edit: There is no way to implement this with __get() and debug_backtrace() to modify the data only when var_export() is called on a object because __get() is not called on var_export().

Solution:

<?php
/*
 * @author Christian Mayer <http://fox21.at>
 * @link http://stackoverflow.com/q/21762276/823644
 * @link https://eval.in/163041
 * @link https://eval.in/163462
 * @link https://eval.in/163909
 * @link https://gist.github.com/TheFox/49ff6903da287c30e72f
 */

interface Exportable{
    public function __get_state();
}

function unset_with_get_state($expression){
    $before = clone $expression;
    $classVars = array_keys(get_class_vars(get_class($before)));
    foreach(array_diff($classVars, $before->__get_state()) as $var){
        unset($before->$var);
    }
    return $before;
}

function my_var_export($expression, $return = null){
    $before = $expression;
    if($before instanceof Exportable){
        $before = unset_with_get_state($expression);
    }
    return var_export($before, $return);
}

class Foo implements Exportable{
    public $name = null;
    public $foo = null;
    public $bar = null;

    public function __get_state(){
        // Only show 'name' and 'bar' on my_var_export().
        return array('name', 'bar');
    }
}

$a = 'hello';
my_var_export($a);
print "\n";

$b = new Foo();
$b->name = 'world';
$b->foo = 'foo is foo';
$b->bar = 'bar is bar';
my_var_export($b);
print "\n";

Of course with an own implementation you can do everything. With is there a simpler way? I mean if there is a built-in PHP function or something like that so you don't have to do it yourself. This solution is not really easy because you must extend all your objects from Exportable. And this also only works when your variables are public. In this example I choosed to export only name and bar but not foo. A built-in PHP function (like __set_state() is) would be more nicer.

like image 734
TheFox Avatar asked Feb 13 '14 18:02

TheFox


1 Answers

You write in your question:

I want a simple function which is called after var_export() is called on a object but before var_export() gets the data

That sounds like that you want to adopt the var_export() function and then call the adapter instead of var_export(). Then inside the adapter you would get the data before it is (really) var_export()ed:

function my_var_export($expression, $return = NULL) {
    $before = $expression;
    return var_export($before, $return);
}

$a = 'hello';
my_var_export($a); // 'hello'

With this adapter, you're technically able then to establish what you're looking for, but it is also the precondition.

So we're concerned about objects, not strings. And for those objects, the __get_state() method should be called. As we so far know what such an object should be (it should be exportable), we create an interface:

interface Exportable {
    /**
     * @return static clone of $this for var_export
     */
    public function __get_state();
}

So how could this be implemented? One idea is to clone the real object and then change it so that var_export does not run into any problems with it. This cloning will spare us to manipulate the concrete object just to export it. But this is only a convention, an object which can't be cloned could implement this __get_state() method as well, it would be perhaps just a little more complicated to write the implementation then.

And on the other end, with the help of the interface, the adaptor my_var_export() can be made more intelligent on how to treat $before on passing it to var_export():

function my_var_export($expression, $return = null)
{
    $before = $expression instanceof Exportable
        ? $expression->__get_state()
        : $expression;

    return var_export($before, $return);
}

Just the new case that an Exportable $expression needs special treatment has been inserted. This works just as before for the $a = 'hello'; expression.

So now to give it a go, we need a concrete type that is an Exportable, exemplary I use a Foo here. For testing purposes I give it a private property that is only set in case the implementation of __get_state() has been called in the adopted var_export() operation:

class Foo implements Exportable
{
    private $name = null;
    private $__get_state_called;

    public function __construct($name)
    {
        $this->name = (string)$name;
    }

    public function __get_state()
    {
        $before = clone $this; // or if inherited: parent::__get_state()

        $before->__get_state_called = true;

        return $before;
    }
}

The working example than is:

$b = new Foo('hello');
my_var_export($b);

Which gives the output as wanted:

Foo::__set_state(array(
   'name' => 'hello',
   '__get_state_called' => true,
))

And that's your "magic" function __get_state() that is called before var_export() gets the data but after (my_)var_export() is called.

Adapt the function var_export to add the functionality you need. Use an interface for the objects that need special treatment.

The full example (run it online):

<?php
/*
 * @author hakre <http://hakre.wrodpress.com/>
 * @link http://stackoverflow.com/a/24228153/367456
 * @link https://eval.in/163041
 */

/**
 * Interface Exportable
 */
interface Exportable
{
    /**
     * @return static clone of $this for var_export
     */
    public function __get_state();
}

/**
 * @param mixed $expression
 * @param bool  $return (optional)
 *
 * @return void|string
 */
function my_var_export($expression, $return = null)
{
    $before = $expression instanceof Exportable
        ? $expression->__get_state()
        : $expression;

    return var_export($before, $return);
}

/**
 * Class Foo
 */
class Foo implements Exportable
{
    private $name = null;
    private $__get_state_called;

    public function __construct($name)
    {
        $this->name = (string)$name;
    }

    /**
     * @see Exportable
     * @return Foo|static
     */
    public function __get_state()
    {
        $before = clone $this; // or if inherited: parent::__get_state()

        $before->__get_state_called = true;

        return $before;
    }
}

$a = 'hello';
my_var_export($a);

echo "\n\n";

$b = new Foo('world');
my_var_export($b);
like image 188
hakre Avatar answered Nov 04 '22 21:11

hakre