With an example class such as this:
class Test{
public function &__get($name){
print_r($name);
}
}
An instance of Test will kick back output as such:
$myTest = new Test;
$myTest->foo['bar']['hello'] = 'world';
//outputs only foo
Is there a way I can get more information about what dimension of the array is being accessed, showing me (from the previous example) that the bar element of foo, and the hello element of bar are being targeted?
You can't with the current implementation. In order for this to work, you will have to create an array object (i.e.: an object that implements ArrayAccess). Something like:
class SuperArray implements ArrayAccess {
protected $_data = array();
protected $_parents = array();
public function __construct(array $data, array $parents = array()) {
$this->_parents = $parents;
foreach ($data as $key => $value) {
if (is_array($value)) {
$value = new SuperArray($value, array_merge($this->_parents, array($key)));
}
$this[$key] = $value;
}
}
public function offsetGet($offset) {
if (!empty($this->_parents)) echo "['".implode("']['", $this->_parents)."']";
echo "['$offset'] is being accessed\n";
return $this->_data[$offset];
}
public function offsetSet($offset, $value) {
if ($offset === '') $this->_data[] = $value;
else $this->_data[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->_data[$offset]);
}
public function offsetExists($offset) {
return isset($this->_data[$offset]);
}
}
class Test{
protected $foo;
public function __construct() {
$array['bar']['hello'] = 'world';
$this->foo = new SuperArray($array);
}
public function __get($name){
echo $name.' is being accessed.'.PHP_EOL;
return $this->$name;
}
}
$test = new Test;
echo $test->foo['bar']['hello'];
Should output:
foo is being accessed.
['bar'] is being accessed
['bar']['hello'] is being accessed
world
No you can't.
$myTest->foo['bar']['hello'] = 'world'; goes through the following translation
$myTest->__get('foo')['bar']['hello'] = 'world'; breaking them in parts become
$tmp = $myTest->__get('foo')
$tmp['bar']['hello'] = 'world';
What you can do is to create an ArrayAccess Derived Object. Where you define your own offsetSet() and return that from __get()
Instead of returning an array, you could return an object that implements ArrayAccess. Objects are always returned and passed by reference. This pushes the problem at least on level down.
Related
Is it possible to pass an anonymous function as a parameter in PHP? And if yes - how?
I am trying to pass an anonymous function to a setter which will fill an array with values returned from that function.
class MyClass
{
private $arr = array();
public function __construct()
{
$this->setArrElm('New', function(){return 123;});
}
private function setArrElm($name, $val)
{
// here: gettype($val) == object
$this->arr[$name] = $val;
}
}
Please note the comment - the type of val is object and I expect an int.
In PHP 7 you can self execute the closure
class MyClass
{
private $arr = array();
public function __construct()
{
$this->setArrElm('New', (function(){return 123;})()); //<-- self execute
}
private function setArrElm($name, int $val) //<-- added typehint
{
// here: gettype($val) == object
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Output
123
Sandbox
This takes a form similar to JS (probably other languages too):
(function(){return 123;})()
It's important to know that it's executing the function, then passing the result. You can pass the closure (which is an object) and then execute it, too. But if you have strict types and need an int, you can self execute the closure too.
It really only makes sense to do this if you need an int as the argument. Even in that case you can execute it beforehand and then pass the result. This just saves you a local variable.
For < PHP7 or just because
Alt1
class MyClass
{
private $arr = array();
public function __construct()
{
$var = function(){return 123;};
$this->setArrElm('New', $var()); //<-- execute
}
private function setArrElm($name, $val) //<-- added typehint
{
// here: gettype($val) == object
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Alt2
class MyClass
{
private $arr = array();
public function __construct()
{
$var = function(){return 123;};
$this->setArrElm('New', $var);
}
private function setArrElm($name, $val) //<-- mixed
{
if(gettype($val) == 'object' && is_a($val, '\Closure')){
//is a closure, you could use is_callable etc. too. see __invoke()
$val = $val();
}
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Alt3
class MyClass
{
private $arr = array();
public function __construct()
{
$var = function(){return 123;};
$this->setArrElm('New', $var);
}
private function setArrElm($name, $val) //<-- mixed
{
if(is_callable($val)){
//pass functions (as a string) or arrays or closures(executable classes with __invoke)
$val = call_user_func($val);
}
$this->arr[$name] = $val;
print_r($val);
}
}
new MyClass;
Cheers
Does anyone know of an efficient technique in PHP to auto assign class parameters with identically named __construct() method arguments?
For instance, I've always thought it was highly inefficient to do something like the following:
<?php
class Foo
{
protected $bar;
protected $baz;
public function __construct($bar, $baz)
{
$this->bar = $bar;
$this->baz = $baz;
}
}
I'm wondering if there's a better/more efficient way/magic method to auto-assign class properties with identically named method parameters.
Thanks,
Steve
PHP 8
Constructor Promotion
function __construct(public $bar, public $baz) {}
PHP 5
function _promote(&$o) {
$m = debug_backtrace(0, 2)[1];
$ref = new ReflectionMethod($m['class'], $m['function']);
foreach($ref->getParameters() as $i=>$p) {
$o->{$p->name} = $m['args'][$i] ?? $p->getDefaultValue();
}
}
class Foo {
function __construct($bar, $baz) {
_promote($this);
}
}
I think this way is a pretty generally accepted way to do it, although you could make getters and setters. Or, if you're looking for magic methods in php: http://php.net/manual/en/language.oop5.magic.php
Not in a constructor. You can always wrap your properties into an array, instead, and only have a single property that needs to be set:
<?php
class SomeClass
{
protected $data = [];
public function __construct(array $data = [])
{
$this->data = $data;
}
public function getData()
{
return $this->data;
}
}
$params = ['bar' => 'bar', 'baz' => 'baz'];
$someClass = new SomeClass($params);
echo $someClass->getData()['bar'] . PHP_EOL;
There is the magic method __set, but it is only called when attempting to write data to inaccessible properties:
<?php
class SomeClass
{
protected $data = [];
public function __construct(array $data = [])
{
$this->data = $data;
}
public function __set($name, $value)
{
$this->data[$name] = $value;
}
public function __get($name)
{
if(isset($this->data[$name])) {
return $this->data[$name];
}
return null;
}
}
$class = new SomeClass;
$class->bar = 'bar';
$class->baz = 'baz';
echo $class->bar . PHP_EOL . $class->baz . PHP_EOL;
If your class is starting to have a lot of parameters being passed in to the constructor, it may be a sign that your class is getting too big and trying to do too much. A refactoring may be in order.
Is any way to create class static var inside method?
something like this..
class foo {
public function bind($name, $value) {
self::$name = $value;
}
};
or is there other solution to bind variables to class and later use it without long and ugly syntax "$this->"
I'm not sure I understand the question. But if you'd like to attach variables at runtime, you could do this:
abstract class RuntimeVariableBinder
{
protected $__dict__ = array();
protected function __get($name) {
if (isset($this->__dict__[$name])) {
return $this->__dict__[$name];
} else {
return null;
}
}
protected function __set($name, $value) {
$this->__dict__[$name] = $value;
}
}
class Foo
extends RuntimeVariableBinder
{
// Explicitly allow calling code to get/set variables
public function __get($name) {
return parent::__get($name);
}
public function __set($name, $value) {
parent::__set($name, $value);
}
}
$foo = new Foo();
$foo->bar = "Hello, world!";
echo $foo->bar; // Prints "Hello, world!"
http://codepad.org/H9bz2uVp
Using self would result in a fatal error, as the property is undeclared. You would have to use $this which would then be accessible as a public variable:
<?php
class foo {
public function bind($name, $value) {
$this->$name = $value;
}
}
$foo = new Foo;
$foo->bind('bar','Hello World');
echo '<pre>';
print_r($foo);
echo $foo->bar;
echo '</pre>';?>
For example, I have a object like this:
class myObj{
private $a;
private $b;
//getter , setter
}
And I would like to do something like:
$myObj = initWitharray(array('a'=> 'myavalue',
'b'=> 'mybvalue'));
And the myObj will have all the a value and b value. How can I do so ? Thank you.
As NullUserException suggested:
<?php
class myObj {
private $a;
private $b;
public function initWithArray(array $arr) {
foreach ($arr as $k => $v) {
$this->$k = $v;
}
return $this;
}
public function get($name) {
return $this->$name;
}
}
// usage
$myObj = new myObj();
echo $myObj->initWithArray(array(
'a' => 'myavalue',
'b' => 'mybvalue'))
->get('a');
function initWithArray(array $a){
$myObj = new myObj();
foreach($a as $k => $v){
$myObj->$k = $v;
}
return $myObj;
}
class myObj {
private $a;
private $b;
public function __set($name, $value) {
$this->$name = $value;
}
public function __get($name){
if($this->$name != null)
return $this->$name;
return null;
}
}
Or, as said in the comments, it's better if init function would be a member of a class.
Try the following:
class myObj {
private $a;
private $b;
function __construct($passedArray){
$this->a = array_key_exists('a', $passedArray) ? $passedArray['a'] : 'default_value_for_a';
$this->b = array_key_exists('b', $passedArray) ? $passedArray['b'] : 'default_value_for_b';
}
//Rest of the code
}
Then:
newObj = new myObj(array('a'=> 'myavalue', 'b'=> 'mybvalue'))
You could use the class constructor to pass in options when you create a new object. Doing it this way, you should also separate out the setOptions method so you can update the options after init as well.
Use this class like this: (shows both ways to set options)
$object = new myClass(array('a'=>'foo'));
$object->setOptions(array('b'=>'bar'));
Also, try not to confuse object with class. An object is an instance of a class.
class myClass
{
private $a;
private $b;
public function __construct(array $options = null)
{
if (null !== $options) {
$this->setOptions($options);
}
}
public function setOptions(array $options)
{
foreach ($options as $key => $value) {
if (isset($this->$key)) {
$this->$key = $value;
}
}
return $this;
}
}
I usually adopts the approach which gives me total control over the object, like allowing someone to access the property. denying the permission, allowing access to only those which i think is appropriate according to application etc. and that's the purpose of object.
Have a look at the example below.
Example
class MyObj {
private $data = array('one' => null, 'two' => null);
public function __set($property, $value) {
//Only allow to set those properties which is declared in $this->data array
if(array_key_exists($property, $this->data)) {
return $this->data[$property] = $value;
} else {
//if you want to throw some error.
}
}
//you can allow or disallow anyone from accessing the class property directly.
public function __get($property) {
//To deny the access permission, simply throw an error and return false.
$error = 'access denied to class property {' . $property . '}';
return false;
//Or Else Allow permission to access class property
//return $this->data[$property];
}
}
the above example demonstrates on how you can gain more control over the class property, by declaring class property $data as private you are basically disallowing anyone to do any sort of manipulation on the class property directly. whatever operation is to be carried out is done through PHP's getter __get() and setter __set() method. of course you can modify the above code according to your need, you just new a very few line of changes and it will behave the way you want it to.
I have an array of reflectionClasses.
I need to get a reflectionObject from one of these and then call its constructor with some parameters.
The point is to instantiate an object without knowing the class name (i'll know it at runtime).
Example, just to render the idea:
foreach (Conf::get_array() as $reflection_class) {
//it's not right, just to render the idea
$reflectionObject = new ReflectionObject ($reflection_class);
$objects[] = $reflectionObject->construct($param_1, $param_2);
}
Another example:
foreach (Conf::get_array() as $reflection_class) {
$objects[] = new $reflection_class($param_1, $param_2); //not right. maybe from php 5.3?
}
You don't need an instance of ReflectionObject for that. ReflectionClass has the two methods
public stdclass newInstance(mixed args)
public stdclass newInstanceArgs(array args)
example:
<?php
class Foo {
public function __construct($a, $b) { echo "Foo($a,$b) "; }
}
class Bar {
public function __construct($a, $b) { echo "Bar($a,$b) "; }
}
class Conf {
public static function get_array() {
return array(new ReflectionClass('Foo'), new ReflectionClass('Bar'));
}
}
$args = array('A', 'B');
$object = array();
foreach (Conf::get_array() as $reflection_class) {
$objects[] = $reflection_class->newInstanceArgs($args);
}
var_dump($objects);