3

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!

5
  • 2
    "the classes in context manipulates production, live database" -- consider setting up a development environment with a dummy database. Having only a production environment limits your development capabilities severely. Commented Oct 21, 2014 at 14:52
  • 1
    you seem to have bugs in code if ($name = "property2") - I assume you wanted to use if ($name === "property2") Commented Oct 21, 2014 at 14:55
  • Thank you @Elias! I managed to create a dummy for my purpose. Commented Oct 21, 2014 at 15:15
  • Thank you @Valter ! I have some typos in above code that is fixed now. Commented Oct 21, 2014 at 15:15
  • Or simply don't use magic getters/setters at all (other than to throw Exception possibly) and enforce proper use of the defined getter/setter functions. After all the properties were made inaccessible for a reason right? Commented Oct 21, 2014 at 15:34

2 Answers 2

3

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);
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! This is brilliant! but I guess instead of $this I should provide method_exists with static to work for inheritance!
Of course if you go through the exercise of defining proper getter/setter functions then you have no need for the magic functions at all other than to allow a more lax access paradigm (i.e. $object->property instead of $object->getProperty()) by the caller.
@MikeBrant it can be more complex. There are times when the getter/setter methods are set as private or protected for packaging. That way, the proxy looks neat. Though the end user doesn't have direct access to call $object->getProperty abundantly, it can access it through trying to reach an inaccessible property by $object->userFriendlyNameForProperty.
1

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?

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.