0

How should one use PHP's magic __get() and __set() methods and limit which properties are supported?

I've typically seen PHP's magic methods used for overloading the below two ways, and neither do so.

I recognize I can hardcode some logic, but doing so doesn't make the classes very extendable.

$obj1=new Entity1(new Foo, new Bar);
$obj1->propAccessible1='propAccessible1';           //Valid
var_dump($obj1->propAccessible1);                   //Valid
$obj1->privateObject1='privateObject1';             //Should not be allowed
var_dump($obj1->privateObject1);                    //Should not be allowed
$obj1->unsupportedProperty='unsupportedProperty';   //Correctly is not allowed
var_dump($obj1->unsupportedProperty);               //Correctly is not allowed

$obj2=new Entity2(new Foo, new Bar);
$obj2->propAccessible1='propAccessible1';           //Valid
var_dump($obj2->propAccessible1);                   //Valid
$obj2->privateObject1='privateObject1';             //Should not be allowed
var_dump($obj2->privateObject1);                    //Should not be allowed (will be if first set using setter)
$obj2->unsupportedProperty='unsupportedProperty';   //Should not be allowed
var_dump($obj2->unsupportedProperty);               //Should not be allowed

class Foo{}
class Bar{}

class Entity1
{
    private $privateObject1, $privateObject2;
    private $propAccessible1, $propAccessible2;

    public function __construct($injectedObject1, $injectedObject2) {
        $this->privateObject1=$injectedObject1;
        $this->privateObject2=$injectedObject2;
    }

    public function __get($property) {
        if (property_exists($this, $property)) return $this->$property;
        else throw new \Exception("Property '$property' does not exist");
    }

    public function __set($property, $value) {
        if (!property_exists($this, $property)) throw new \Exception("Property '$property' is not allowed");
        $this->$property = $value;
        return $this;
    }
}

class Entity2
{
    private $privateObject1, $privateObject2;
    private $data=[];

    public function __construct($injectedObject1, $injectedObject2) {
        $this->privateObject1=$injectedObject1;
        $this->privateObject2=$injectedObject2;
    }

    public function __set($property, $value) {
        $this->data[$property] = $value;
    }

    public function __get($property) {
        if (array_key_exists($property, $this->data)) {
            return $this->data[$property];
        }
        else throw new \Exception("Property '$property' does not exist");
    }
}
user1032531
  • 24,767
  • 68
  • 217
  • 387
  • what are properties , please give us some examples of the properties, if you want to restrict only string or only int.. etc, I think you can use type declaration, if it's any other property I think you need to hardcode the logic in setter – Sugumar Venkatesan Jun 15 '18 at 14:13
  • @vSugumar My primary concern is not wanted to allow injected objects from being publicly accessed. The second approach actually meets this goal, however, it doesn't limit what properties are supported. Previously, I've used the first approach, but after going though this exercise, I think the second is better. True? – user1032531 Jun 15 '18 at 14:19
  • If I understand correctly what you're trying to do, it seems like you could just give your accessible properties public visibility and not use the magic methods at all. I must be missing something. – Don't Panic Jun 15 '18 at 14:30
  • Yes in the second approach no way to access your injected objects, but still I am not getting your point "it doesn't limit what properties are supported" please make me understand – Sugumar Venkatesan Jun 15 '18 at 14:32
  • @Don'tPanic Yes I could, but many will claim that I shouldn't be making them public. – user1032531 Jun 15 '18 at 14:32
  • @vSugumar The class cannot limit which property names can be set. Maybe not a big deal. – user1032531 Jun 15 '18 at 14:34

1 Answers1

1

You could modify the second approach just a little. Define your accessible keys in $data, and the check if those exist in __set() like you're already doing in __get().

class Entity2
{
    private $privateObject1, $privateObject2;
    private $data = [
        'accessible1' => null,
        'accessible2' => null
    ];

    public function __construct($injectedObject1, $injectedObject2)
    {
        $this->privateObject1 = $injectedObject1;
        $this->privateObject2 = $injectedObject2;
    }

    public function __set($property, $value)
    {
        if (array_key_exists($property, $this->data)) {
            $this->data[$property] = $value;
        } else throw new \Exception("Property '$property' does not exist");
    }

    public function __get($property)
    {
        if (array_key_exists($property, $this->data)) {
            return $this->data[$property];
        } else throw new \Exception("Property '$property' does not exist");
    }
}

I'm not really a believer in strictly avoiding public properties in PHP though. If they're going to be publicly accessible through magic methods anyway, I'd rather just declare them as public to make it more clear how the class works.

Don't Panic
  • 41,125
  • 10
  • 61
  • 80