Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PHP __set and __get overloading in inheritance

I am implementing an OOP design using PHP. I wonder how PHP handles inheritance for its magic methods like __get and __set.

class Foo
{
    protected $property1;

    public function __get($name)
    {
        if ($name == "property1")
        {
            // do some logic
            return $result; // may be null
        }
        return;
    }

    public function __set($name, $value)
    {
        if ($name == "property1")
        {
            // do some logic
            return $result; // may be null
        }
        result;
    }
}

Now for extending Foo:

class Bar extends Foo
{
    protected $property2;

    public function __get($name)
    {
        if (($result = parent::__get($name)) !== null)
            return $result; // may be null
        if ($name == "property2")
        {
            // do some logic
            return $result; // may be null
        }
        return;
    }

    public function __set($name, $value)
    {
        if (($result = parent::__set($name, $value)) !== null)
            return $result; // may be null
        if ($name == "property2")
        {
            // do some logic
            return $result; // may be null
        }
        return;
    }
}

As PHP returns null as the result of a function with nothing to return... this may lead in ambiguity of whether the parent::__get() or parent::__set() returned null truely or returned with no value; and leads to overhead.

Now if PHP considers the static::_get() and static::__set() first and fall backs to the parent versions on failure, this could be simplified as:

class Bar extends Foo
{
    protected $property2;

    public function __get($name)
    {
        if ($name = "property2")
        {
            // do some logic
            return $result; // may be null
        }
        return;
    }

    public function __set($name, $value)
    {
        if ($name = "property2")
        {
            // do some logic
            return $result; // may be null
        }
        return;
    }
}

I can't test it on current implementation because the classes in context manipulates production, live database. Which is the correct implementation?

Thanks!

like image 827
Cunning Avatar asked Jun 22 '26 00:06

Cunning


2 Answers

Use magic methods only as proxy methods. Your code will be much cleaner and you don't have to deal with problems caused by inheritance.

Simple example:

class Foo
{
    protected $property1;

    public function setProperty1($property1)
    {
        // do some logic
        $this->property1 = $property1;
        return $this;
    }

    public function getProperty1()
    {
        // do some logic
        return $this->property1;
    }

    public function __get($name)
    {
        $method = 'get' . ucfirst($name);
        if (method_exists($this, $method)) {
            return $this->$method();
        }
    }

    public function __set($name, $value)
    {
        $method = 'set' . ucfirst($name);
        if (method_exists($this, $method)) {
            $this->$method($value);
        }
    }
}

class Bar extends Foo
{
    protected $property2;

    public function setProperty2($property2)
    {
        // do some logic
        $this->property2 = $property2;
        return $this;
    }

    public function getProperty2()
    {
        // do some logic
        return $this->property2;
    }
}

Example call:

$bar = new Bar;
$bar->property1 = 'foo';
$bar->property2 = 'bar';
var_dump($bar);
like image 166
BreyndotEchse Avatar answered Jun 23 '26 14:06

BreyndotEchse


I have managed to create an Isolated environment and test both methods.

Method 1

class Foo
{
    protected $property1;

    public function __get($name)
    {
        if ($name == "name")
        {
            echo "been here @line(" . __LINE__ . ") Foo::__get('$name') <br/>";
            $result = $this->property1;
            return $result; // may be null
        }
        return;
    }

    public function __set($name, $value)
    {
        if ($name == "name")
        {
            echo "been here @line(" . __LINE__ . ") Foo::__set('$name', '$value') <br/>";
            $result = $this->property1 = $value;
            return $result; // may be null
        }
        return;
    }
}

class Bar extends Foo
{
    protected $property2;

    public function __get($name)
    {
        if ($name == "place")
        {
            echo "been here @line(" . __LINE__ . ") Bar::__get('$name') <br/>";
            $result = $this->property2;
            return $result; // may be null
        }
        return;
    }

    public function __set($name, $value)
    {
        if ($name == "place")
        {
            echo "been here @line(" . __LINE__ . ") Bar::__set('$name', '$value') <br/>";
            $result = $this->property2 = $value;
            return $result; // may be null
        }
        return;
    }
}

$bar = new Bar;

$bar->name = 'Alice';
$bar->place = 'Wonderland';

echo "done that: '{$bar->name} in {$bar->place}'";

The above code results are:

been here @line(59) Bar::__set('place', 'Wonderland') 
been here @line(43) Bar::__get('place') 
done that: ' in Wonderland'

That is clearly not the answer.

Method 2

class Foo
{
    protected $property1;

    public function __get($name)
    {
        if ($name == "name")
        {
            echo "been here @line(" . __LINE__ . ") Foo::__get('$name') <br/>";
            $result = $this->property1;
            return $result; // may be null
        }
        return;
    }

    public function __set($name, $value)
    {
        if ($name == "name")
        {
            echo "been here @line(" . __LINE__ . ") Foo::__set('$name', '$value') <br/>";
            $result = $this->property1 = $value;
            return $result; // may be null
        }
        return;
    }
}

class Bar extends Foo
{
    protected $property2;

    public function __get($name)
    {
        if (($result = parent::__get($name)) !== null)
        {
            echo "been here @line(" . __LINE__ . ") parent::__get('$name') <br/>";
            return $result; // may be null
        }
        if ($name == "place")
        {
            echo "been here @line(" . __LINE__ . ") Bar::__get('$name') <br/>";
            $result = $this->property2;
            return $result; // may be null
        }
        return;
    }

    public function __set($name, $value)
    {
        if (($result = parent::__set($name, $value)) !== null)
        {
            echo "been here @line(" . __LINE__ . ") parent::__set('$name', '$value') <br/>";
            return $result; // may be null
        }
        if ($name == "place")
        {
            echo "been here @line(" . __LINE__ . ") Bar::__set('$name', '$value') <br/>";
            $result = $this->property2 = $value;
            return $result; // may be null
        }
        return;
    }
}

$bar = new Bar;

$bar->name = 'Alice';
$bar->place = 'Wonderland';

echo "done that: '{$bar->name} in {$bar->place}'";

Which results in:

been here @line(22) Foo::__set('name', 'Alice') 
been here @line(54) parent::__set('name', 'Alice') 
been here @line(59) Bar::__set('place', 'Wonderland') 
been here @line(11) Foo::__get('name') 
been here @line(38) parent::__get('name') 
been here @line(43) Bar::__get('place') 
done that: 'Alice in Wonderland'

This is the correct answer for overloading __get and __set magic functions while dealing with inheritance, but this has overheads. It is a solution but is there any better solution?

like image 41
Cunning Avatar answered Jun 23 '26 14:06

Cunning



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!