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"
Related
Need dynamically append method to class.
My code:
<?php
class stdClass1 {
public function __call($method, $arguments) {
return call_user_func_array(Closure::bind($this->$method, $this, get_called_class()), $arguments);
}
}
class stdClass2 {
function stdRunMethod() {
$obj = new stdClass1();
$obj->test = function() {
echo 'a simple function';
};
$obj->test();
$obj2 = new stdClass1();
$obj2->test();
}
}
$obj = new stdClass2();
$obj->stdRunMethod();
Question: why test method run only for first instance of stdClass1 class? How to append this method for all new instances?
try this instead (demo):
<?php
class stdClass1 extends \stdClass
{
private static $addedClosures = array();
public function __set($name, $value)
{
if ($value instanceof \Closure) {
self::$addedClosures[$name] = $value;
}
else {
parent::__set($name, $value);
}
}
public function __call($method, $arguments)
{
if (isset(self::$addedClosures[$method]))
return call_user_func_array(self::$addedClosures[$method], $arguments);
return call_user_func_array($method, $arguments);
}
}
class stdClass2 extends \stdClass
{
function stdRunMethod()
{
$obj = new stdClass1();
$obj->test = function () {
print_r('a simple function');
};
$obj->test();
$obj2 = new stdClass1();
$obj2->test();
}
}
The reason it only runs once is that each copy of stdClass1 maintains their own set of variables. In the following
$obj1 = new stdClass1();
$obj1->a = '1';
$obj2 = new stdClass1();
$obj2->a = '2';
echo $obj1->a;
You'll get the value 1 as the output. Because in most cases they maintain different references. Unless you use the static keyword. Static properties are shared between all instances of the class, and should be used carefully, but that's what you're thinking of. What you're thinking of can be done like this
<?php
class stdClass1 {
private static $methods = [];
public function __call($method, $arguments) {
return call_user_func_array(Closure::bind($this->methods[$method], $this, get_called_class()), $arguments);
}
public function __set($name, $value) {
if (is_callable($value)) {
$this->methods[$name] = $value;
} else {
parent::__set($name, $value);
}
}
}
Here we're using a static, defined property to hold all of the dynamic methods, and we're using the magic __set property to set the methods in to the array.
That being said, dynamically loading methods in to an object is bad. Don't do that
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.
PHP calls private method in parent class instead of method define in current class called by call_user_func
class Car {
public function run() {
return call_user_func(array('Toyota','getName')); // should call toyota
}
private static function getName() {
return 'Car';
}
}
class Toyota extends Car {
public static function getName() {
return 'Toyota';
}
}
$car = new Car();
echo $car->run(); //Car instead of Toyota
$toyota = new Toyota();
echo $toyota->run(); //Car instead of Toyota
I have found a solution with a different approach..
<?php
class Car {
public static function run() {
return static::getName();
}
private static function getName() {
return 'Car';
}
}
class Toyota extends Car {
public static function getName() {
return 'Toyota';
}
}
echo Car::run();
echo Toyota::run();
?>
Using Late Static Binding..
You might use something like this:
<?php
class Car {
public function run() {
return static::getName();
}
private static function getName(){
return 'Car';
}
}
class Toyota extends Car {
public static function getName(){
return 'Toyota';
}
}
$car = new Car();
echo $car->run();
echo PHP_EOL;
$toyota = new Toyota();
echo $toyota->run();
?>
Output:
Car
Toyota
PHP 5.4.5
This is a bug that appears to have fluctuated in and out of existence for a long time (see #deceze's tests in comments on the question). It is possible to "fix" this issue - that is, give consistent behaviour across PHP versions - using reflection:
Works in PHP 5.3.2 and later due to a dependency on ReflectionMethod::setAccessible() to invoke private/protected methods. I will add further explanation for this code, what it can and can't do and how it works very shortly.
Unfortunately it's not possible to test this directly on 3v4l.org because the code is too large, however this is the first ever real use case for minifying PHP code - it does work on 3v4l if you do this, so feel free to play around and see if you can break it. The only issue I'm aware of is that it doesn't currently understand parent. It is also restricted by the lack of $this support in closures before 5.4, there's not really anything that can be done about this though.
<?php
function call_user_func_fixed()
{
$args = func_get_args();
$callable = array_shift($args);
return call_user_func_array_fixed($callable, $args);
}
function call_user_func_array_fixed($callable, $args)
{
$isStaticMethod = false;
$expr = '/^([a-z_\x7f-\xff][\w\x7f-\xff]*)::([a-z_\x7f-\xff][\w\x7f-\xff]*)$/i';
// Extract the callable normalized to an array if it looks like a method call
if (is_string($callable) && preg_match($expr, $callable, $matches)) {
$func = array($matches[1], $matches[2]);
} else if (is_array($callable)
&& count($callable) === 2
&& isset($callable[0], $callable[1])
&& (is_string($callable[0]) || is_object($callable[0]))
&& is_string($callable[1])) {
$func = $callable;
}
// If we're not interested in it use the regular mechanism
if (!isset($func)) {
return call_user_func_array($func, $args);
}
$backtrace = debug_backtrace(); // passing args here is fraught with complications for backwards compat :-(
if ($backtrace[1]['function'] === 'call_user_func_fixed') {
$called = 'call_user_func_fixed';
$contextKey = 2;
} else {
$called = 'call_user_func_array_fixed';
$contextKey = 1;
}
try {
// Get a reference to the target static method if possible
switch (true) {
case $func[0] === 'self':
case $func[0] === 'static':
if (!isset($backtrace[$contextKey]['object'])) {
throw new Exception('Use of self:: in an invalid context');
}
$contextClass = new ReflectionClass($backtrace[$contextKey][$func[0] === 'self' ? 'class' : 'object']);
$contextClassName = $contextClass->getName();
$method = $contextClass->getMethod($func[1]);
$ownerClassName = $method->getDeclaringClass()->getName();
if (!$method->isStatic()) {
throw new Exception('Attempting to call instance method in a static context');
}
$invokeContext = null;
if ($method->isPrivate()) {
if ($ownerClassName !== $contextClassName
|| !method_exists($method, 'setAccessible')) {
throw new Exception('Attempting to call private method in an invalid context');
}
$method->setAccessible(true);
} else if ($method->isProtected()) {
if (!method_exists($method, 'setAccessible')) {
throw new Exception('Attempting to call protected method in an invalid context');
}
while ($contextClass->getName() !== $ownerClassName) {
$contextClass = $contextClass->getParentClass();
}
if ($contextClass->getName() !== $ownerClassName) {
throw new Exception('Attempting to call protected method in an invalid context');
}
$method->setAccessible(true);
}
break;
case is_object($func[0]):
$contextClass = new ReflectionClass($func[0]);
$contextClassName = $contextClass->getName();
$method = $contextClass->getMethod($func[1]);
$ownerClassName = $method->getDeclaringClass()->getName();
if ($method->isStatic()) {
$invokeContext = null;
if ($method->isPrivate()) {
if ($ownerClassName !== $contextClassName || !method_exists($method, 'setAccessible')) {
throw new Exception('Attempting to call private method in an invalid context');
}
$method->setAccessible(true);
} else if ($method->isProtected()) {
if (!method_exists($method, 'setAccessible')) {
throw new Exception('Attempting to call protected method in an invalid context');
}
while ($contextClass->getName() !== $ownerClassName) {
$contextClass = $contextClass->getParentClass();
}
if ($contextClass->getName() !== $ownerClassName) {
throw new Exception('Attempting to call protected method in an invalid context');
}
$method->setAccessible(true);
}
} else {
$invokeContext = $func[0];
}
break;
default:
$contextClass = new ReflectionClass($backtrace[$contextKey]['object']);
$method = new ReflectionMethod($func[0], $func[1]);
$ownerClassName = $method->getDeclaringClass()->getName();
if (!$method->isStatic()) {
throw new Exception('Attempting to call instance method in a static context');
}
$invokeContext = null;
if ($method->isPrivate()) {
if (empty($backtrace[$contextKey]['object'])
|| $func[0] !== $contextClass->getName()
|| !method_exists($method, 'setAccessible')) {
throw new Exception('Attempting to call private method in an invalid context');
}
$method->setAccessible(true);
} else if ($method->isProtected()) {
$contextClass = new ReflectionClass($backtrace[$contextKey]['object']);
if (empty($backtrace[$contextKey]['object']) || !method_exists($method, 'setAccessible')) {
throw new Exception('Attempting to call protected method outside a class context');
}
while ($contextClass->getName() !== $ownerClassName) {
$contextClass = $contextClass->getParentClass();
}
if ($contextClass->getName() !== $ownerClassName) {
throw new Exception('Attempting to call protected method in an invalid context');
}
$method->setAccessible(true);
}
break;
}
// Invoke the method with the passed arguments and return the result
return $method->invokeArgs($invokeContext, $args);
} catch (Exception $e) {
trigger_error($called . '() expects parameter 1 to be a valid callback: ' . $e->getMessage(), E_USER_ERROR);
return null;
}
}
Use "protected" modifier if you want to get access from parent and descendants only. IMO, it's obvious. For example:
<?php
class Car {
public function run() {
return call_user_func(array('static','getName'));
}
protected static function getName() {
return 'Car';
}
}
class Toyota extends Car {
protected static function getName() {
return 'Toyota';
}
}
$car = new Car();
echo $car->run(); // "Car"
$toyota = new Toyota();
echo $toyota->run(); // "Toyota"
You can use get_called_class() instead of 'static'.
The problem is, I think, with the different access levels of the two getname functions. If you make the base class version of getname() public (the same as the derived class version), then in php 5.3.15 (on my Mac), you get Toyota. I think that, because of the different access levels, you end up with two different versions of the getname() function in the Toyota class, rather than the derived class version overriding the base class version. In other words, you have overloading rather than overriding. Therefore, when the run() function looks for a getname() function in the Toyota class to execute, it finds two and takes the first one, which would be the first to be declared (from the base class).
Granted this is just supposition on my part, but it sounds plausible.
use the get_called_called function todo this
public function run() {
$self = get_called_class();
return $self::getName();
}
I believe you're functions are overriding each other and by default going to the first one. Unless you change the parameters of one function, or rename the function it will always default to the parent class function.
If I have the following class example:
<?php
class Person
{
private $prefix;
private $givenName;
private $familyName;
private $suffix;
public function setPrefix($prefix)
{
$this->prefix = $prefix;
}
public function getPrefix()
{
return $this->prefix;
}
public function setGivenName($gn)
{
$this->givenName = $gn;
}
public function getGivenName()
{
return $this->givenName;
}
public function setFamilyName($fn)
{
$this->familyName = $fn;
}
public function getFamilyName()
{
return $this->familyName;
}
public function setSuffix($suffix)
{
$this->suffix = $suffix;
}
public function getSuffix()
{
return $suffix;
}
}
$person = new Person();
$person->setPrefix("Mr.");
$person->setGivenName("John");
echo($person->getPrefix());
echo($person->getGivenName());
?>
I there a way in PHP (5.4 preferably), to combine these return values into one function, this way it models a little bit more like the revealing module pattern in JavaScript?
UPDATE:
OK, I am now beginning to learn that within PHP, it is normative to return a single value from a function, but you "can" return an array of multiple values. This is the ultimate answer to my question and what I will dive into some practices with this understanding.
small example -
function fruit () {
return [
'a' => 'apple',
'b' => 'banana'
];
}
echo fruit()['b'];
Also an article I ran across on stackoverflow on the topic...
PHP: Is it possible to return multiple values from a function?
Good luck!
You sound like you want the __get() magic method.
class Thing {
private $property;
public function __get($name) {
if( isset( $this->$name ) {
return $this->$name;
} else {
throw new Exception('Cannot __get() class property: ' . $name);
}
}
} // -- end class Thing --
$athing = new Thing();
$prop = $athing->property;
In the case that you want all of the values returned at once, as in Marc B's example, I'd simplify the class design for it thusly:
class Thing {
private $properties = array();
public function getAll() {
return $properties;
}
public function __get($name) {
if( isset( $this->properties[$name] ) {
return $this->properties[$name];
} else {
throw new Exception('Cannot __get() class property: ' . $name);
}
}
} // -- end class Thing --
$athing = new Thing();
$prop = $athing->property;
$props = $athing-> getAll();
Perhaps
public function getAll() {
return(array('prefix' => $this->prefix, 'givenName' => $this->giveName, etc...));
}
We have a class that holds a public array called $saved that contains lots of data required to share between methods (example below)...
class Common {
public $saved = array();
public function setUser($data) {
$this->saved['user_data'] = $data;
}
public function getUserID() {
return $this->saved['user_data']['id'];
}
}
There are literally thousands of lines of code that work like this.
The problem is that new instance of classes that extend Common are being made within some methods so when they access $saved it does not hold the same data.
The solution is to make $saved a static variable, however I can't change all of the references to $this->saved so I want to try and keep the code identical but make it act static.
Here is my attempt to make $this->saved calls static...
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]);
}
}
class Common {
public $saved;
private static $_instance;
public function __construct() {
$this->saved = self::getInstance();
}
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new PropertyTest();
self::$_instance->foo = array();
}
return self::$_instance->foo;
}
}
This doesn't quite work when setting a variable it doesn't seem to stay static (test case below)...
class Template extends Common {
public function __construct() {
parent::__construct();
$this->saved['user_data'] = array('name' => 'bob');
$user = new User();
}
}
class User extends Common {
public function __construct() {
parent::__construct();
$this->saved['user_data']['name'] .= " rocks!";
$this->saved['user_data']['id'] = array(400, 10, 20);
}
}
$tpl = new Template();
print_r($tpl->saved['user_data']);
$this->saved is empty when User gets initialized and doesn't seem to be the same variable, the final print_r only shows an array of name => bob.
Any ideas?
First of all, I have to say that, IMO, it is not that good to use an instance's property as a class's property ($saved is not declared as static but its value is shared with all instance).
Here is a working version http://codepad.org/8hj1MOCT, and here is the commented code. Basically, the trick is located in using both ArrayAccess interface and the singleton pattern.
class Accumulator implements ArrayAccess {
private $container = array();
private static $instance = null;
private function __construct() {
}
public function getInstance() {
if( self::$instance === null ) {
self::$instance = new self();
}
return self::$instance;
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
class Common {
public $saved = null;
public function __construct() {
// initialize the "saved" object's property with the singleton
// that variable can be used with the array syntax thanks to the ArrayAccess interface
// so you won't have to modify your actual code
// but also, since it's an object, this local "$this->saved" is a reference to the singleton object
// so any change made to "$this->saved" is in reality made into the Accumulator::$instance variable
$this->saved = Accumulator::getInstance();
}
public function setUser($data) {
$this->saved['user_data'] = $data;
}
public function getUser() {
return $this->saved['user_data'];
}
}
class Template extends Common {
// you can redeclare the variable or not. Since the property is inherited, IMO you should not redeclare it, but it works in both cases
// public $saved = null;
public function __construct() {
// maybe we can move this initialization in a method in the parent class and call that method here
$this->saved = Accumulator::getInstance();
}
}
I think there are a number of issues with this implementation that could well come back to bite you. However, in your current implementation your contructing a new instance (albeit through a static call) every time.
Instead use getInstance() as your singleton hook, and make your __construct private, as you'll only be accessing it from with the context of the Common class.
Like so:
class Common {
public $saved;
private static $_instance;
private function __construct() {
}
public static function getInstance() {
if (self::$_instance === null) {
self::$_instance = new self();
... any other modifications you want to make ....
}
return self::$_instance;
}
}
And don't ever run parent::_construct(), instead always use the getInstance() method.
You might also want to ditch the idea of extending this singleton class. This is really a bad antipattern and could cost you a number of issues in the long run. Instead just maintain a Common class that other classes can read / write to. As its a singleton you don't need to worry about injection.
I seem to have solved the problem, by making $this->saved a reference to a static variable it works...
class Common {
private static $savedData = array();
public $saved;
public function __construct() {
$this->saved =& self::$savedData;
}
}