I am looking for a way to intercept the action in array_push, because when it will be retrieve it each value of the array has another info like:
class ClassName {
var $test = array();
function __set($attr, $value) {
$this->$attr = 'My extra value'.$value;
}
function index(){
array_push($this->test, "some val");
array_push($this->test, "some other val");
print_r($this->test);
}
}
$o = new ClassName();
$o->index();
And expected to get something like:
Array
(
[0] => My extra value some val
[1] => My extra value some other val
)
But i get:
Array
(
[0] => some val
[1] => some other val
)
Thanks to all
Instead of using an array, you can use a class that implements the ArrayAccess interface. This way you have full control over what occurs when you append to the array.
http://php.net/manual/en/class.arrayaccess.php
The drawback is that not all array functions will work on the object (ie sorting etc), but you can push like so:
$object[] = 'new value';
The alternative is to simply make a wrapper function for adding to the array.
public function addToArray($key, $value) {
if ($key === null) {
$this->test[] = 'My extra value ' . $value;
} else {
$this->test[$key] = 'My extra value ' . $value;
}
}
I don't totally understand what you're asking, but are you trying to do this:
$this->test['key'] = "some val";
That will allow you to setup your own output nicely. Because array_push() will throw on another nested level.
Update: Maybe something along these lines?
function push($prefix)
{
$refl = new ReflectionClass($this);
$prop = $refl->getProperties();
foreach($prop as $p) {
$this->{$p} = $prefix . $this->{$p};
}
}
To achieve what you're looking for, I suggest you create yourself a function that prefixes any value independent to it's use:
function prefix($value) {
return 'My extra value '.$value;
}
You can then make use of that function inside the index() function:
function index()
{
$test = array("some val", "some other val");
foreach($test as $value)
{
$this->test[] = $this->prefix($value);
}
print_r($this->test);
}
From the PHP manual:
__set() is run when writing data to inaccessible properties.
__get() is utilized for reading data from inaccessible properties.
This is only called on reading/writing inaccessible properties. Your property however is public, which means it is accessible. Changing the access modifier to protected solves the issue.
Try this:
class ClassName {
private $test = array();
public function __set($attr, $value) {
$this->test[$attr] = $value;
}
public function __get($attr) {
return $this->test[$attr];
}
public function index(){
array_push($this->test, "some val");
array_push($this->test, "some other val");
return $this->test;
}
}
$o = new ClassName();
$o->setData = 'My extra value';
print_r($o->index());
The PHP manual says the following about magic methods:
__set() is run when writing data to inaccessible properties. (Emphasis mine)
Because the test property is inside the method and the function that is accessing it is inside the method, the function will see the variable directly and will not use the magic setter.
Additionally, even if you try to use array_push on the magic setter outside the class itself, that still will not work. You will get the error array_push() expects parameter 1 to be array, object given.
If you want to support array_push, you should write your own push($item) method:
function push($item) {
$this->test[] = 'My extra value' . $item
}
or:
function push() {
$items = func_get_args();
// Using array_map is one of the many ways to do this.
// You could also do it with a simpler `foreach` but I like this way.
$prepend = function($item) {
return 'My extra value' . $item;
};
$items = array_map($prepend, $items);
array_push($this->test, $items);
}
if you want to support pushing multiple items at once.
Related
Is there a way to make a read-only property of an object in PHP? I have an object with a couple arrays in it. I want to access them as I normally would an array
echo $objObject->arrArray[0];
But I don't want to be able to write to those arrays after they're constructed. It feels like a PITA to construct a local variable:
$arrArray = $objObject->getArray1();
echo $arrArray[0];
And anyways, while it keeps the array in the object pristine, it doesn't prevent me from re-writing the local array variable.
Well, the question is where do you want to prevent writing from?
The first step is making the array protected or private to prevent writing from outside of the object scope:
protected $arrArray = array();
If from "outside" of the array, a GETTER will do you fine. Either:
public function getArray() { return $this->arrArray; }
And accessing it like
$array = $obj->getArray();
or
public function __get($name) {
return isset($this->$name) ? $this->$name : null;
}
And accessing it like:
$array = $obj->arrArray;
Notice that they don't return references. So you cannot change the original array from outside the scope of the object. You can change the array itself...
If you really need a fully immutable array, you could use a Object using ArrayAccess...
Or, you could simply extend ArrayObject and overwrite all of the writing methods:
class ImmutableArrayObject extends ArrayObject {
public function append($value) {
throw new LogicException('Attempting to write to an immutable array');
}
public function exchangeArray($input) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetSet($index, $newval) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetUnset($index) {
throw new LogicException('Attempting to write to an immutable array');
}
}
Then, simply make $this->arrArray an instance of the object:
public function __construct(array $input) {
$this->arrArray = new ImmutableArrayObject($input);
}
It still supports most array like usages:
count($this->arrArray);
echo $this->arrArray[0];
foreach ($this->arrArray as $key => $value) {}
But if you try to write to it, you'll get a LogicException...
Oh, but realize that if you need to write to it, all you need to do (within the object) is do:
$newArray = $this->arrArray->getArrayCopy();
//Edit array here
$this->arrArray = new ImmutableArrayObject($newArray);
If you're using PHP 5+ you can do it with __set() and __get() methods.
You have to define how they work but should do just this.
Edit an example would be like this.
class Example {
private $var;
public function __get($v) {
if (is_array($v)) {
foreach () {
// handle it here
}
} else {
return $this->$v;
}
}
}
This might not be the "best" way of doing it but it'll work depending on what you need
If defined, the magic functions __get() and __set() will be called whenever a non-existing or private property is accessed. This can be used to create "get" and "set" methods for private properties, and for instance make them read-only or manipulate the data when stored or retrieved in it.
For instance:
class Foo
{
private $bar = 0;
public $baz = 4; // Public properties will not be affected by __get() or __set()
public function __get($name)
{
if($name == 'bar')
return $this->bar;
else
return null;
}
public function __set($name, $value)
{
// ignore, since Foo::bar is read-only
}
}
$myobj = new Foo();
echo $foo->bar; // Output is "0"
$foo->bar = 5;
echo $foo->bar; // Output is still "0", since the variable is read-only
See also the manual page for overloading in PHP.
For PHP 8.1+, you can use readonly properties:
class Test
{
public readonly array $arrArray;
public function __construct()
{
$this->arrArray = [1, 2, 3];
}
}
$test = new Test();
var_dump($test->arrArray); // OK
$test->arrArray = [4, 5, 6]; // Error
in the class, do this:
private $array;
function set_array($value) {
$this->array = $value;
}
then you just set like this:
$obj->set_array($new_array);
I have the following class:
/**
* #property int $barMagic
*/
class Foo
{
public $barNormal;
private $attributes = [];
public function __get($name) {
return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
}
public function __set($name, $value)
{
$this->attributes[$name] = $value;
}
}
As you can see, the $barMagic public property is not defined explicitly, it's accessed via the magic methods.
When setting and then modifying an array element in the normal attribute, it works fine:
$foo = new Foo();
$foo->barNormal = ['baz' => 1];
echo $foo->barNormal['baz'];
$foo->barNormal['baz'] = 2;
echo ',' . $foo->barNormal['baz'];
It outputs "1,2", just as intended.
But when using the magic property, it does not:
$foo = new Foo();
$foo->barMagic = ['baz' => 1];
echo $foo->barMagic['baz'];
$foo->barMagic['baz'] = 2;
echo ',' . $foo->barMagic['baz'];
It outputs "1,1"!
Is there a way in PHP to access array elements in magic properties the same way as normal ones?
The ArrayAccess interface seems to deal with array access one level higher than I need it.
The real answer is tricky and involves some bug/inconsistency in the PHP engine. As commentors suggested, I added the "&" (return by reference) character before __get(). So new code:
public function &__get($name) {
return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
}
but this gives
Notice: Only variable references should be returned by reference in ....
I had to change it to
public function &__get($name) {
if (isset($this->attributes[$name])) {
return $this->attributes[$name];
} else {
return null;
}
}
and now it works. Note that the two snippets should be completely equivalent, but they are not. Thank you all for the contribution, you took me halfway there.
I know this question has been asked several times, but none of them have a real answer for a workaround. Maybe there's one for my specific case.
I'm building a mapper class which uses the magic method __get() to lazy load other objects. It looks something like this:
public function __get ( $index )
{
if ( isset ($this->vars[$index]) )
{
return $this->vars[$index];
}
// $index = 'role';
$obj = $this->createNewObject ( $index );
return $obj;
}
In my code I do:
$user = createObject('user');
$user->role->rolename;
This works so far. The User object doesn't have a property called 'role', so it uses the magic __get() method to create that object and it returns its property from the 'role' object.
But when i try to modify the 'rolename':
$user = createUser();
$user->role->rolename = 'Test';
Then it gives me the following error:
Notice: Indirect modification of overloaded property has no effect
Not sure if this is still some bug in PHP or if it's "expected behaviour", but in any case it doesn't work the way I want. This is really a show stopper for me... Because how on earth am I able to change the properties of the lazy loaded objects??
EDIT:
The actual problem only seems to occur when I return an array which contains multiple objects.
I've added an example piece of code which reproduces the problem:
http://codepad.org/T1iPZm9t
You should really run this in your PHP environment the really see the 'error'. But there is something really interesting going on here.
I try to change the property of an object, which gives me the notice 'cant change overloaded property'. But if I echo the property after that I see that it actually DID change the value... Really weird...
All you need to do is add "&" in front of your __get function to pass it as reference:
public function &__get ( $index )
Struggled with this one for a while.
Nice you gave me something to play around with
Run
class Sample extends Creator {
}
$a = new Sample ();
$a->role->rolename = 'test';
echo $a->role->rolename , PHP_EOL;
$a->role->rolename->am->love->php = 'w00';
echo $a->role->rolename , PHP_EOL;
echo $a->role->rolename->am->love->php , PHP_EOL;
Output
test
test
w00
Class Used
abstract class Creator {
public function __get($name) {
if (! isset ( $this->{$name} )) {
$this->{$name} = new Value ( $name, null );
}
return $this->{$name};
}
public function __set($name, $value) {
$this->{$name} = new Value ( $name, $value );
}
}
class Value extends Creator {
private $name;
private $value;
function __construct($name, $value) {
$this->name = $name;
$this->value = $value;
}
function __toString()
{
return (string) $this->value ;
}
}
Edit : New Array Support as requested
class Sample extends Creator {
}
$a = new Sample ();
$a->role = array (
"A",
"B",
"C"
);
$a->role[0]->nice = "OK" ;
print ($a->role[0]->nice . PHP_EOL);
$a->role[1]->nice->ok = array("foo","bar","die");
print ($a->role[1]->nice->ok[2] . PHP_EOL);
$a->role[2]->nice->raw = new stdClass();
$a->role[2]->nice->raw->name = "baba" ;
print ($a->role[2]->nice->raw->name. PHP_EOL);
Output
Ok die baba
Modified Class
abstract class Creator {
public function __get($name) {
if (! isset ( $this->{$name} )) {
$this->{$name} = new Value ( $name, null );
}
return $this->{$name};
}
public function __set($name, $value) {
if (is_array ( $value )) {
array_walk ( $value, function (&$item, $key) {
$item = new Value ( $key, $item );
} );
}
$this->{$name} = $value;
}
}
class Value {
private $name ;
function __construct($name, $value) {
$this->{$name} = $value;
$this->name = $value ;
}
public function __get($name) {
if (! isset ( $this->{$name} )) {
$this->{$name} = new Value ( $name, null );
}
if ($name == $this->name) {
return $this->value;
}
return $this->{$name};
}
public function __set($name, $value) {
if (is_array ( $value )) {
array_walk ( $value, function (&$item, $key) {
$item = new Value ( $key, $item );
} );
}
$this->{$name} = $value;
}
public function __toString() {
return (string) $this->name ;
}
}
I've had this same error, without your whole code it is difficult to pinpoint exactly how to fix it but it is caused by not having a __set function.
The way that I have gotten around it in the past is I have done things like this:
$user = createUser();
$role = $user->role;
$role->rolename = 'Test';
now if you do this:
echo $user->role->rolename;
you should see 'Test'
Though I am very late in this discussion, I thought this may be useful for some one in future.
I had faced similar situation. The easiest workaround for those who doesn't mind unsetting and resetting the variable is to do so. I am pretty sure the reason why this is not working is clear from the other answers and from the php.net manual. The simplest workaround worked for me is
Assumption:
$object is the object with overloaded __get and __set from the base class, which I am not in the freedom to modify.
shippingData is the array I want to modify a field of for e.g. :- phone_number
// First store the array in a local variable.
$tempShippingData = $object->shippingData;
unset($object->shippingData);
$tempShippingData['phone_number'] = '888-666-0000' // what ever the value you want to set
$object->shippingData = $tempShippingData; // this will again call the __set and set the array variable
unset($tempShippingData);
Note: this solution is one of the quick workaround possible to solve the problem and get the variable copied. If the array is too humungous, it may be good to force rewrite the __get method to return a reference rather expensive copying of big arrays.
I was receiving this notice for doing this:
$var = reset($myClass->my_magic_property);
This fixed it:
$tmp = $myClass->my_magic_property;
$var = reset($tmp);
I agree with VinnyD that what you need to do is add "&" in front of your __get function, as to make it to return the needed result as a reference:
public function &__get ( $propertyname )
But be aware of two things:
1) You should also do
return &$something;
or you might still be returning a value and not a reference...
2) Remember that in any case that __get returns a reference this also means that the corresponding __set will NEVER be called; this is because php resolves this by using the reference returned by __get, which is called instead!
So:
$var = $object->NonExistentArrayProperty;
means __get is called and, since __get has &__get and return &$something, $var is now, as intended, a reference to the overloaded property...
$object->NonExistentArrayProperty = array();
works as expected and __set is called as expected...
But:
$object->NonExistentArrayProperty[] = $value;
or
$object->NonExistentArrayProperty["index"] = $value;
works as expected in the sense that the element will be correctly added or modified in the overloaded array property, BUT __set WILL NOT BE CALLED: __get will be called instead!
These two calls would NOT work if not using &__get and return &$something, but while they do work in this way, they NEVER call __set, but always call __get.
This is why I decided to return a reference
return &$something;
when $something is an array(), or when the overloaded property has no special setter method, and instead return a value
return $something;
when $something is NOT an array or has a special setter function.
In any case, this was quite tricky to understand properly for me! :)
This is occurring due to how PHP treats overloaded properties in that they are not modifiable or passed by reference.
See the manual for more information regarding overloading.
To work around this problem you can either use a __set function or create a createObject method.
Below is a __get and __set that provides a workaround to a similar situation to yours, you can simply modify the __set to suite your needs.
Note the __get never actually returns a variable. and rather once you have set a variable in your object it no longer is overloaded.
/**
* Get a variable in the event.
*
* #param mixed $key Variable name.
*
* #return mixed|null
*/
public function __get($key)
{
throw new \LogicException(sprintf(
"Call to undefined event property %s",
$key
));
}
/**
* Set a variable in the event.
*
* #param string $key Name of variable
*
* #param mixed $value Value to variable
*
* #return boolean True
*/
public function __set($key, $value)
{
if (stripos($key, '_') === 0 && isset($this->$key)) {
throw new \LogicException(sprintf(
"%s is a read-only event property",
$key
));
}
$this->$key = $value;
return true;
}
Which will allow for:
$object = new obj();
$object->a = array();
$object->a[] = "b";
$object->v = new obj();
$object->v->a = "b";
I have run into the same problem as w00, but I didn't had the freedom to rewrite the base functionality of the component in which this problem (E_NOTICE) occured. I've been able to fix the issue using an ArrayObject in stead of the basic type array(). This will return an object, which will defaulty be returned by reference.
Is there a way to make a read-only property of an object in PHP? I have an object with a couple arrays in it. I want to access them as I normally would an array
echo $objObject->arrArray[0];
But I don't want to be able to write to those arrays after they're constructed. It feels like a PITA to construct a local variable:
$arrArray = $objObject->getArray1();
echo $arrArray[0];
And anyways, while it keeps the array in the object pristine, it doesn't prevent me from re-writing the local array variable.
Well, the question is where do you want to prevent writing from?
The first step is making the array protected or private to prevent writing from outside of the object scope:
protected $arrArray = array();
If from "outside" of the array, a GETTER will do you fine. Either:
public function getArray() { return $this->arrArray; }
And accessing it like
$array = $obj->getArray();
or
public function __get($name) {
return isset($this->$name) ? $this->$name : null;
}
And accessing it like:
$array = $obj->arrArray;
Notice that they don't return references. So you cannot change the original array from outside the scope of the object. You can change the array itself...
If you really need a fully immutable array, you could use a Object using ArrayAccess...
Or, you could simply extend ArrayObject and overwrite all of the writing methods:
class ImmutableArrayObject extends ArrayObject {
public function append($value) {
throw new LogicException('Attempting to write to an immutable array');
}
public function exchangeArray($input) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetSet($index, $newval) {
throw new LogicException('Attempting to write to an immutable array');
}
public function offsetUnset($index) {
throw new LogicException('Attempting to write to an immutable array');
}
}
Then, simply make $this->arrArray an instance of the object:
public function __construct(array $input) {
$this->arrArray = new ImmutableArrayObject($input);
}
It still supports most array like usages:
count($this->arrArray);
echo $this->arrArray[0];
foreach ($this->arrArray as $key => $value) {}
But if you try to write to it, you'll get a LogicException...
Oh, but realize that if you need to write to it, all you need to do (within the object) is do:
$newArray = $this->arrArray->getArrayCopy();
//Edit array here
$this->arrArray = new ImmutableArrayObject($newArray);
If you're using PHP 5+ you can do it with __set() and __get() methods.
You have to define how they work but should do just this.
Edit an example would be like this.
class Example {
private $var;
public function __get($v) {
if (is_array($v)) {
foreach () {
// handle it here
}
} else {
return $this->$v;
}
}
}
This might not be the "best" way of doing it but it'll work depending on what you need
If defined, the magic functions __get() and __set() will be called whenever a non-existing or private property is accessed. This can be used to create "get" and "set" methods for private properties, and for instance make them read-only or manipulate the data when stored or retrieved in it.
For instance:
class Foo
{
private $bar = 0;
public $baz = 4; // Public properties will not be affected by __get() or __set()
public function __get($name)
{
if($name == 'bar')
return $this->bar;
else
return null;
}
public function __set($name, $value)
{
// ignore, since Foo::bar is read-only
}
}
$myobj = new Foo();
echo $foo->bar; // Output is "0"
$foo->bar = 5;
echo $foo->bar; // Output is still "0", since the variable is read-only
See also the manual page for overloading in PHP.
For PHP 8.1+, you can use readonly properties:
class Test
{
public readonly array $arrArray;
public function __construct()
{
$this->arrArray = [1, 2, 3];
}
}
$test = new Test();
var_dump($test->arrArray); // OK
$test->arrArray = [4, 5, 6]; // Error
in the class, do this:
private $array;
function set_array($value) {
$this->array = $value;
}
then you just set like this:
$obj->set_array($new_array);
So I have a couple of arrays
$array_1 = Array('one','two','three');
$array_2 = Array('red','blue','green');
Is there a dynamic way to create the Setters and Getters for an array with single value entries?
So the class would be something like:
class xFromArray() {
}
So the above if I passed $array_1 it would generate something like this:
private $one;
setOne($x) {
$one = $x;
}
getOne() {
return $one;
}
if I passed $array_2 it would generate something like this:
private $red;
setRed($x) {
$red = $x;
}
getRed() {
return $red;
}
So I would call it somehow like this? (My best guess but doesn't seem that this would work)
$xFromArray = new xFromArray;
foreach($array_1 as $key=>$data) {
$xFromArray->create_function(set.ucfirst($data)($data));
echo $xFromArray->create_function(get.ucfirst($data));
}
You can use __call() to invoke dynamic methods. So:
class Wrapper {
private $properties;
public function __construct(array $names) {
$this->properties = array_combine(array_keys($names),
array_fill(0, count($names), null));
}
public function __call($name, $args) {
if (preg_match('!(get|set)(\w+)!', $name, $match)) {
$prop = lcfirst($match[2]);
if ($match[1] == 'get') {
if (count($args) != 0) {
throw new Exception("Method '$name' expected 0 arguments, got " . count($args));
}
return $properties[$prop];
} else {
if (count($args) != 1) {
throw new Exception("Method '$name' expected 1 argument, got " . count($args));
}
$properties[$prop] = $args[0];
}
} else {
throw new Exception("Unknown method $name");
}
}
}
Personally I wouldn't go the route of using getters and setters in PHP. Use the special methods __get() and __set() instead and treat these dynamic properties as object properties rather than adding a (most likely unnecessary) method wrapper.
Edit: to clarify, __call() is invoked when you call an method in an object that either doesn't exist or is inaccessible. So:
$wrapper = new Wrapper($array_1);
$wrapper->setOne("foo");
echo $wrapper->getOne(); // foo
$wrapper->getAbc(); // exception, property doesn't exist
__call() is used here to decipher the method name. If it fits the pattern of get or set followed by a property name (from the initial array) then it works as expected, otherwise it throws an exception. You can of course change this behaviour any way you wish.
See Overloading from the PHP manual for a more detailed explanation of these "magic" methods.
You can use __call() (or __set() && __get()), but they have some overhead.