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.
Related
There's thousands of examples of php __get and __set out there, unfortunately nobody actually tells you how to use them.
So my question is: how do I actually call the __get and __set method from within the class and when using an object.
Example code:
class User{
public $id, $usename, $password;
public function __construct($id, $username) {
//SET AND GET USERNAME
}
public function __get($property) {
if (property_exists($this, $property)) {
return $this->$property;
}
}
public function __set($property, $value) {
if (property_exists($this, $property)) {
$this->$property = $value;
}
return $this;
}
}
$user = new User(1, 'Bastest');
// echo GET THE VALUE;
How would I set the values in the constructor and how would i get the value in the // echo GET THE VALUE;
This feature is called overloading in PHP. As the documentation states the __get or __set methods will be called if you are trying to access non existent or non accessible properties. The problem in your code is, that the properties your are accessing are existent and accessible. That's why __get/__set will not being called.
Check this example:
class Test {
protected $foo;
public $data;
public function __get($property) {
var_dump(__METHOD__);
if (property_exists($this, $property)) {
return $this->$property;
}
}
public function __set($property, $value) {
var_dump(__METHOD__);
if (property_exists($this, $property)) {
$this->$property = $value;
}
}
}
Test code:
$a = new Test();
// property 'name' does not exists
$a->name = 'test'; // will trigger __set
$n = $a->name; // will trigger __get
// property 'foo' is protected - meaning not accessible
$a->foo = 'bar'; // will trigger __set
$a = $a->foo; // will trigger __get
// property 'data' is public
$a->data = '123'; // will not trigger __set
$d = $a->data; // will not trigger __get
I recently studied magic methods, __get and __set, and was wondering how to actually set and get multiple properties in the class.
I know it works perfectly with only one variable or array, but I'm not sure about accessing multiple variables.
Is there anyone who could explain this to me?
class myMagic2 {
public $data;
public $name;
public $age;
public function __set($item, $value) {
$this->item = $value;
}
public function __get($item){
return $this->item;
}
}
Is there a way to access all variables ($data, $name, $age)?
When i work at projects i always have these methods:
public function __set($name, $value)
{
//see if there exists a extra setter method: setName()
$method = 'set' . ucfirst($name);
if(!method_exists($this, $method))
{
//if there is no setter, receive all public/protected vars and set the correct one if found
$vars = $this->vars;
if(array_search("_" . $name, $vars) !== FALSE)
$this->{"_" . $name} = $value;
} else
$this->$method($value); //call the setter with the value
}
public function __get($name)
{
//see if there is an extra getter method: getName()
$method = 'get' . ucfirst($name);
if(!method_exists($this, $method))
{
//if there is no getter, receive all public/protected vars and return the correct one if found
$vars = $this->vars;
if(array_search("_" . $name, $vars) !== FALSE)
return $this->{"_" . $name};
} else
return $this->$method(); //call the getter
return null;
}
public function getVars()
{
if(!$this->_vars)
{
$reflect = new ReflectionClass($this);
$this->_vars = array();
foreach($reflect->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED) as $var)
{
$this->_vars[] = $var->name;
}
}
return $this->_vars;
}
So with them i give myself the freedom to create extra setter/getter for properties if i want to manipulate them before writing/returning. If no setter/getter exists for the property it falls back to the property itself. With the method getVars() you receive all public and protected properties from the class.
My class properties are always defined with an underscorce so you should probably change that.
You could follow this pattern.
Note: in the example in the post the magic methods would not have been called for $obj->data, $obj->name, or $obj->age because these values where already accessible as a public property. I changed them to be protected and altered the name to hide them.
<?php
class myMagic2{
protected $_data;
protected $_name;
protected $_age;
public function __set($item, $value){
switch($item){
case "data":
$this->_data = $value;
break;
case "name":
$this->_name = $value;
break;
case "age":
$this->_age = $value;
break;
default:
throw new Exception("Property $name does not exist.");
}
}
public function __get($item){
switch($item){
case "data":
return $this->_data;
break;
case "name":
return $this->_name;
break;
case "age":
return $this->_age;
break;
default:
throw new Exception("Property $name does not exist.");
break;
}
}
}
Normally you would do a little more then use the magic methods as a proxy to set and get classes properties. You would so validation or filtering or in some other way augmenting the operation. If you are just going to get or set a property you might as well just make the properties public and forgo using the magic methods.
I think its quite a simple question but not sure.
I have a class:
<?PHP
class PropertyTest {
private $data = array();
public function __set($name, $value) {
$this->data[$name] = $value;
}
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
$trace = debug_backtrace();
trigger_error(
'Undefined property via __get(): ' . $name .
' in ' . $trace[0]['file'] .
' on line ' . $trace[0]['line'],
E_USER_NOTICE);
return null;
}
public function __isset($name) {
echo "Is '$name' set?\n";
return isset($this->data[$name]);
}
public function __unset($name) {
echo "Unsetting '$name'\n";
unset($this->data[$name]);
}
public function getHidden() {
return $this->hidden;
}
}
?>
Not sure why but the 'code' block is annoying as hell, anyway.
Just the basic magic get set really. But when I change the __get to pass by reference I cant do this anymore:
$object->$variableName = $variableValue;
I'm not sure why although I assume because it checks if it exists but since it has to return something by reference it will fail to do so if it doesn't exists to begin with. The set function wont be called probably and even if I return a fake value it would never call the set function cause it 'already exists/has a value'.
Am I understanding this correctly? If so, Is there a work around? If not how does it work and is there a workaround?
Unless I'm missing something it's working fine for me
<?php
class PropertyTest
{
private $data = array();
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function &__get($name)
{
if(array_key_exists($name, $this->data))
return $this->data[$name];
return null;
}
public function __isset($name)
{
return isset($this->data[$name]);
}
public function __unset($name)
{
unset($this->data[$name]);
}
public function getHidden()
{
return $this->hidden;
}
}
$oPropTest = new PropertyTest();
$sField = 'my-field';
$oPropTest->$sField = 5;
var_dump($oPropTest);
Outputs:
bash-3.2$ php test-get-set.php
object(PropertyTest)#1 (1) {
["data":"PropertyTest":private]=>
array(1) {
["my-field"]=>
int(5)
}
}
One tweak I'd suggest for your __get implementation is to leverage the __isset method rather than re-implement the check (doing it 2 different ways as you are)
public function __get($name)
{
if($this->__isset($name))
return $this->data[$name];
return null;
}
Regarding the idea of return-by-reference from __get; it will work, but be useless on anything but public attributes, since private and protected attributes won't be settable directly through a reference outside the class scope.
$oPropTest = new PropertyTest();
$sField = 'my-field';
$oPropTest->$sField = 5; // sets $oPropTest->my-field to 5 (effectively)
$iRef = $oPropTest->$sField; // $iRef is a reference to $oPropTest->my-field
$iRef = 6; // this will not set $oPropTest->my-field since it's private
I need to create an immutable class which is simply a member field container. I want its fields to be instantiated once in its constructor (the values should be given as parameters to the constructor). I want the fields to be public but immutable. I could have done it with Java using the final keyword before each field. How is it done in PHP?
You should use __set and __get magic methods and declare that property as protected or private:
/**
* #property-read string $value
*/
class Example
{
private $value;
public function __construct()
{
$this->value = "test";
}
public function __get($key)
{
if (property_exists($this, $key)) {
return $this->{$key};
} else {
return null; // or throw an exception
}
}
public function __set($key, $value)
{
return; // or throw an exception
}
}
Example:
$example = new Example();
var_dump($example->value);
$example->value = "invalid";
var_dump($example->value);
Outputs:
string(4) "test"
string(4) "test"
#property-read should help your IDE acknowledge existence of this magic property.
You could use the __set() magic method and throw an exception when someone tries to set a property directly.
class ClassName {
public function __set($key, $value) {
throw new Exception('Can't modify property directly.');
}
}
However, this would prevent modification of properties regardless of whether they're public or not.
magic methods
so you can do better - if you have a dinamyc count of fields
class ClassName {
private $fields = array();
// use class : $cl = new ClassName(array('f'=>2,'field_4'=>5,''12));
// echo $cl->field_4; echo $cl->f;
public function __construct($data= array())
{
if (!is_array($data) || !count($data)) throw new Exception('Not enough args')
foreach ($data as $key=>$val)
{
if (is_numeric($key))
$this->fields['field_'.$key] = $val;
else
$this->fields[$key] = $val;
}
}
/* in this case you can use this class like $cl = new ClassName(12,14,13,15,12); echo $cl->field_1;
public function __construct()
{
$ata = funcs_get_args();
if (!count($data)) throw new Exception('Not enough args')
foreach ($data as $key=>$val)
{
if (is_numeric($key))
$this->fields['field_'.$key] = $val;
else
$this->fields[$key] = $val;
}
}
*/
public function __get($var) {
if (isset($this->fields[$var]))
return $this->fields[$var];
return false;
//or throw new Exception ('Undeclared property');
}
}
<?php
declare(strict_types=1);
final class Immutable
{
/** #var string */
private $value;
public static function withValue(string $value): self
{
return new self($value);
}
public function __construct(string $value)
{
$this->value = $value;
}
public function value(): string
{
return $this->value;
}
}
// Example of usage:
$immutable = Immutable::withValue("my value");
$immutable->value(); // You can get its value but there is no way to modify it.
public static function __get($value)
does not work, and even if it did, it so happens that I already need the magic __get getter for instance properties in the same class.
This probably is a yes or no question, so, it is possible?
No, it is not possible.
Quoting the manual page of __get :
Member overloading only works in
object context. These magic methods
will not be triggered in static
context. Therefore these methods can
not be declared static.
In PHP 5.3, __callStatic has been added ; but there is no __getStatic nor __setStatic yet ; even if the idea of having/coding them often comes back on the php internals# mailling-list.
There is even a Request for Comments: Static classes for PHP
But, still, not implemented (yet ? )
Maybe someone still need this:
static public function __callStatic($method, $args) {
if (preg_match('/^([gs]et)([A-Z])(.*)$/', $method, $match)) {
$reflector = new \ReflectionClass(__CLASS__);
$property = strtolower($match[2]). $match[3];
if ($reflector->hasProperty($property)) {
$property = $reflector->getProperty($property);
switch($match[1]) {
case 'get': return $property->getValue();
case 'set': return $property->setValue($args[0]);
}
} else throw new InvalidArgumentException("Property {$property} doesn't exist");
}
}
Very nice mbrzuchalski. But it seems to only work on public variables. Just change your switch to this to allow it to access private/protected ones:
switch($match[1]) {
case 'get': return self::${$property->name};
case 'set': return self::${$property->name} = $args[0];
}
And you'd probably want to change the if statement to limit the variables that are accessible, or else it would defeat the purpose of having them be private or protected.
if ($reflector->hasProperty($property) && in_array($property, array("allowedBVariable1", "allowedVariable2"))) {...)
So for example, I have a class designed to pull various data for me out of a remote server using an ssh pear module, and I want it to make certain assumptions about the target directory based on what server it's being asked to look at. A tweaked version of mbrzuchalski's method is perfect for that.
static public function __callStatic($method, $args) {
if (preg_match('/^([gs]et)([A-Z])(.*)$/', $method, $match)) {
$reflector = new \ReflectionClass(__CLASS__);
$property = strtolower($match[2]). $match[3];
if ($reflector->hasProperty($property)) {
if ($property == "server") {
$property = $reflector->getProperty($property);
switch($match[1]) {
case 'set':
self::${$property->name} = $args[0];
if ($args[0] == "server1") self::$targetDir = "/mnt/source/";
elseif($args[0] == "server2") self::$targetDir = "/source/";
else self::$targetDir = "/";
case 'get': return self::${$property->name};
}
} else throw new InvalidArgumentException("Property {$property} is not publicly accessible.");
} else throw new InvalidArgumentException("Property {$property} doesn't exist.");
}
}
try this:
class nameClass{
private static $_sData = [];
private static $object = null;
private $_oData = [];
public function __construct($data=[]){
$this->_oData = $data;
}
public static function setData($data=[]){
self::$_sData = $data;
}
public static function Data(){
if( empty( self::$object ) ){
self::$object = new self( self::$_sData );
}
return self::$object;
}
public function __get($key) {
if( isset($this->_oData[$key] ){
return $this->_oData[$key];
}
}
public function __set($key, $value) {
$this->_oData[$key] = $value;
}
}
nameClass::setData([
'data1'=>'val1',
'data2'=>'val2',
'data3'=>'val3',
'datan'=>'valn'
]);
nameClass::Data()->data1 = 'newValue';
echo(nameClass::Data()->data1);
echo(nameClass::Data()->data2);
Combining __callStatic and call_user_func or call_user_func_array can give access to static properties in PHP class
Example:
class myClass {
private static $instance;
public function __construct() {
if (!self::$instance) {
self::$instance = $this;
}
return self::$instance;
}
public static function __callStatic($method, $args) {
if (!self::$instance) {
new self();
}
if (substr($method, 0, 1) == '$') {
$method = substr($method, 1);
}
if ($method == 'instance') {
return self::$instance;
} elseif ($method == 'not_exist') {
echo "Not implemented\n";
}
}
public function myFunc() {
echo "myFunc()\n";
}
}
// Getting $instance
$instance = call_user_func('myClass::$instance');
$instance->myFunc();
// Access to undeclared
call_user_func('myClass::$not_exist');
Also, you can get static properties accessing them like member properties, using __get():
class ClassName {
private static $data = 'smth';
function __get($field){
if (isset($this->$field)){
return $this->$field;
}
if(isset(self::$$field)){
return self::$$field; // here you can get value of static property
}
return NULL;
}
}
$obj = new ClassName();
echo $obj->data; // "smth"