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");
}
}
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.