__get and __set not behaving as expected - php

<?php
class BaseClass {
protected $data = array("foo" => 0, "bar" => 0, "baz" => 0);
public function __set($name, $value) {
if( array_key_exists($name, $this->data)){
$func = "set_$name";
return call_user_func_array(array($this,$func),array($value));
}
}
public function __get($name) {
if ( array_key_exists($name, $this->data)){
$func = "get_$name";
return call_user_func_array(array($this,$func),array());
}
}
public function __call($method, $args) {
if (method_exists($this,$method)) {
return call_user_func_array(array($this,$method),$args);
}
if (substr($method, 0, 4) == 'set_'){
$var_name = substr($method, 4);
if (!(array_key_exists($var_name, $this->data))){
return FALSE;
}
$this->data[$var_name] = $args[0];
return TRUE;
}
if (substr($method, 0, 4) == 'get_'){
$var_name = substr($method, 4);
if (!(array_key_exists($var_name, $this->data))){
return FALSE;
}
return $this->data[$var_name];
}
}
}
class SubClass extends BaseClass {
protected $baz_changed = FALSE;
public function set_baz($value) {
if ($value != $this->baz){
print "\n\nthis->data BEFORE SET: ";
print_r($this->data);
print "\n\nthis->baz: ";
print_r($this->baz);
print "\n\nPASSED baz: ";
print_r($value);
$this->baz = $value;
print "\n\nbaz AFTER SET: ";
print_r($this->baz); // appears it was set
print "\n\nDATA ARRAY: ";
print_r($this->data); // but it wasn't ... what gives?
$this->baz_changed = TRUE;
}
}
}
$sc = new SubClass();
$sc->foo = 1;
print "\n\$sc->foo = $sc->foo\n";
$sc->baz = 5;
print "\$sc->baz = $sc->baz\n";
?>
I get the following results are not as I would have expected:
$sc->foo = 1
this->data BEFORE SET: Array (
[foo] => 1
[bar] => 0
[baz] => 0 )
this->baz: 0
PASSED baz: 5
baz AFTER SET: 5
DATA ARRAY: Array (
[foo] => 1
[bar] => 0
[baz] => 0 ) $sc->baz = 5
As you can see, it appears that baz is set but it never gets set in the data array. Can anyone explain why and how to fix this?
EDIT: Fixed formatting of the results and adding more context to this code section because stackoverflow is complaining I do not have enough.
EDIT: Just noticed that at the end it says $sc->baz = 5. But the data array isn't updated. This is not expected I would rather that the data array version of baz were updated. Instead of a new instance variable created in SubClass.

You're attempting to recursively call __set, and PHP specifically disallows this.
In __set, you call set_baz
In set_baz, you do $this->baz = 5
This would invoke __set, except PHP prevents this from happening. If it didn't, your program would never end.
You cannot trigger __set if you're already within __set. Instead, you are dynamically defining a new member variable called $this->baz exactly as if __set did not exist. If you var_dump your object, you will find that it now contains both $data and $baz members.
Inside set_baz, you need to explicitly write to $this->data. You cannot write to $this->baz.

It's behaving correctly, when you do $sc->baz = 5; it finds that set_baz method is there and executes this line $this->baz = $value; and sets the value of baz property
Assume if the __get and __set working on in the class too then how you access $this->data in the class?

Related

Dynamic assignment of property names based on array values

I am trying to create a class that is going to generate dynamic class properties according to a user input.
There will be an array created from user input data. This array should work as an example:
$array = array(
# The boolean values are not relevant in this example
# The keys are important
'apple' => true,
'orange' => false,
'pear' => false,
'banana' => true,
);
Right now I want to create a new class with the array keys as the class properties:
class Fruit {
public $apple;
public $orange;
public $pear;
public $banana;
(etc.)
}
I had to manually write down all four properties now.
Is there a way to make it automated?
<?php
class MyClass
{
public function __construct ($config = [])
{
foreach ($config as $key => $value) {
$this->{$key} = $value;
}
}
}
$myClass = new MyClass(['apple' => 1, 'orange' => 2]);
echo $myClass->apple;
?>
this should help you
Here you go,
I put a few bonus things in there:
class MyClass implements Countable, IteratorAggregate
{
protected $data = [];
public function __construct (array $data = [])
{
foreach ($data as $key => $value) {
$this->{$key} = $value;
}
}
public function __set($key, $value){
$this->data[$key] = $value;
}
public function __get($key)
{
if(!isset($this->{$key})) return null; //you could also throw an exception here.
return $this->data[$key];
}
public function __isset($key){
return isset($this->data[$key]);
}
public function __unset($key){
unset($this->data[$key]);
}
public function __call($method, $args){
$mode = substr($method, 0, 3);
$property = strtolower(substr($method, 3)); //only lowercase properties
if(isset($this->{$property})) {
if($mode == 'set'){
$this->{$property} = $args[0];
return null;
}else if($mode == 'get'){
return $this->{$property};
}
}else{
return null; //or throw an exception/remove this return
}
throw new Exception('Call to undefined method '.__CLASS__.'::'.$method);
}
//implement Countable
public function count(){
return count($this->data);
}
//implementIteratorAggregate
public function getIterator() {
return new ArrayIterator($this->data);
}
}
Test it:
$myClass = new MyClass(['one' => 1, 'two' => 2]);
echo $myClass->two."\n";
//Countable
echo count($myClass)."\n";
//dynamic set
$myClass->three = 3;
echo count($myClass)."\n";
//dynamic get/set methods. I like camel case methods, and lowercase properties. If you don't like that then you can change it.
$myClass->setThree(4);
echo $myClass->getThree()."\n";
//IteratorAggregate
foreach($myClass as $key=>$value){
echo $key.' => '.$value."\n";
}
Outputs
2 //value of 2
2 //count of $data
3 //count of $data after adding item
4 //value of 3 after changing it with setThree
//foreach output
one => 1
two => 2
three => 4
Test it online
Disclamer
Generally though it's better to define the class by hand, that way things like IDE's work. You may also have issues because you won't necessarily know what is defined in the class ahead of time. You don't have a concrete definition of the class as it were.
Pretty much any method(at least in my code) that starts with __ is a PHP magic method (yes, that's a thing). When I first learned how to use these I thought it was pretty cool, but now I almost never use them...
Now if you want to create an actual .php file with that code in it, that's a different conversation. (it wasn't 100% clear, if you wanted functionality or an actual file)
Cheers.

How can I mention keyword arguments in php constructor?

I am new to PHP OOP.
I have this constructor
protected $path='default';
function __construct($var1, $var2 ..){
$this->var1=$var1;
$this->var2=$var2;
}
$obj = MyClass($var1='www')
I want that if I make object and don't pass any arguments then I get the object with empty values.
But if mention the properties in the constructor like above then the object should have those properties set.
Currently if I define the constructor with args and don't supply any then I get error
.
You could make use func_get_args() in PHP
<?php
class foo
{
private $arrParams;
function __construct()
{
if (func_num_args() != 0) {
$this->arrParams = $this->setValues(func_get_args());
}
}
public function setValues($params)
{
return $params;
}
public function dispParams()
{
print_r($this->arrParams);
}
public function retVal($var)
{
return $this->arrParams[$var];
}
}
$foo1 = new Foo(3, 45, 64, 34);
$foo1->dispParams();
OUTPUT :
Array
(
[0] => 3
[1] => 45
[2] => 64
[3] => 34
)
For getting a corresponding value from the array... You could just call
$foo1->retVal(2); // Prints you 64
you need to declare class property like this:
class yourClass{
protected $path='default';
public $var1;
public $var2;
function __construct($var1="dd", $var2='ss'){
$this->var1=$var1;
$this->var2=$var2;
}
}
$obj = new yourClass();
echo "without params: ".$obj->var1.$obj->var2;
$obj2 = new yourClass("sdf","sfdsdf");
echo " with params : ".$obj2->var1.$obj2->var2;
Demo

Print_R of class as array

I have a class, that actually operates over a complicated array to make the manipulation more simple. The format of original array looks like this:
array(
array(
"name" =>"foo",
"type" =>8, //The array is NBT format and 8 stands for string
"value" =>"somevalue"
)
}
The class takes array like above as constructor:
class NBT_traverser implements ArrayAccess {
function __construct(&$array) {
$this->array = $array;
}
}
Then, this is how the members are accessed:
$parser = new NBT_traverser($somearray);
echo $parser["foo"]; //"somevalue"
When I print_R the class, I get the list of its values and the original complicated array. Like this:
object(NBT_traverser)#2 (1) {
["nbt":"NBT_traverser":private]=> &array(1) {
/*Tons of ugly array contents*/
}
Instead, I'd like to get this like the output of print_r:
array(
"foo" => "somevalue"
)
Is it possible to trick the print_r to do this? Current behavior makes it harder to debug with the class, than without it.
Of course, I can write my own method to print it, but I want to make the usage more simple for the users of the class. Instead, I wanted to give print_R something, that It will print as array.
You should not have issues if you are extending ArrayAccess just write a method to get your values
Example
$random = range("A", "F");
$array = array_combine($random, $random);
$parser = new NBT_traverser($array);
echo $parser->getPrint();
Output
Array
(
[A] => A
[B] => B
[C] => C
[D] => D
[E] => E
[F] => F
)
Class Used
class NBT_traverser implements ArrayAccess {
private $used; // you don't want this
protected $ugly = array(); // you don't want this
public $error = 0202; // you don't want this
private $array = array(); // you want this
function __construct(&$array) {
$this->array = $array;
}
function getPrint() {
return print_r($this->array, true);
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->array[] = $value;
} else {
$this->array[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->array[$offset]);
}
public function offsetUnset($offset) {
unset($this->array[$offset]);
}
public function offsetGet($offset) {
return isset($this->array[$offset]) ? $this->array[$offset] : null;
}
}
You could use the __toString function in your class
class Test
{
private $_array = array();
public function __toString()
{
return print_r($this->_array, true);
}
}
And then just echo out your class
$test = new Test();
echo $test;
I think this would print out your array as you want it to be?
Array
(
)

PHP getters and setters with array

If i use magic __set to set a value to private var how could i set a var as an array ?
Im thinking of something like this, pretend i have a class with __get __set
$myclass->names = 'Name'; // Works
$myclass->names = array('n1'=>'Name1', 'n2' => 'Name2'); // works as well
//this does not work
$myclass->names['n1'] = 'Name1';
$myclass->names['n2'] = 'Name2';
Its the 2 last examples i want to get to work. Have tested various ways but cant figure it out.
You obviously don't output notices, otherwise you'd have gotten the error
Notice: Indirect modification of overloaded property Foo::$bar has no
effect
What you're trying to do is simply not possible. There is exactly one way to make arrays received through __get writable, but that is most likely not what you want.
<?php
class Foo {
protected $bar = array();
public function &__get($name) {
return $this->$name;
}
public function __set($name, $value) {
return $this->$name = $value;
}
}
$foo = new Foo();
$foo->bar = array('a', 'b', 'c');
echo $foo->bar[0]; // output "a"
$foo->bar[0] = 'z'; // fires warning
echo $foo->bar[0]; // output "z"
// all fine, but here's the catch:
$t =& $foo->bar;
$t = array('y');
echo $foo->bar[0]; // output "y"
Now that you've seen how returning values by reference can be a problem, you may be interested in ArrayObject. Something like
<?php
class Foo {
protected $bar = array();
public function __get($name) {
return new ArrayObject(&$this->$name);
}
public function __set($name, $value) {
return $this->$name = $value;
}
}
$foo = new Foo();
$foo->bar = array('a', 'b', 'c');
echo $foo->bar[0]; // output "a"
$foo->bar[0] = 'z'; // fires warning
echo $foo->bar[0]; // output "z"
// all fine, and no catch
$t =& $foo->bar;
$t = array('y');
echo $foo->bar[0]; // still outputs "z"
It won't work. $class->arr['key'] will execute the getter. So basically, what your code will look like is:
array('key' => 'value')['key'] = 'new value';
Which, obviously, does nothing. If you want that to work, you will have to declare the names as a public property.
This expression will invoke the getter:
$myclass->names['n1'] = 'Name1';
^^^^^^^^^^^^^^^
needs to be get
^^^^^^^^^^^^^^^^
assignment later
The only way to make that work is a fugly workaround. By letting the getter return an reference to the know array the following assignment could work.
function & __get($name) {
if (is_array($this->$name)) {
return & $this->$name;
}
else ...
}
So it's really only advisable if it significantly simplifies your API.
Try this code:
class Foo
{
private $bar;
public function __construct()
{
$this->bar = new ArrayObject(array());
}
public function __get($item)
{
if(property_exists($this, $item)) {
return $this->$item;
}
}
public function __set($item, $value)
{
if(property_exists($this, $item)) {
$this->{$item} = $value;
}
}
}
$obj = new Foo();
$obj->bar['color'] = 'green';
foreach($obj->bar as $attribute => $value) {
echo '<p>' . $attribute . ' : ' . $value . '</p>' . PHP_EOL;
}
// output => color : green

Make my own (non-database) fetch_object function

In php mysql / mysqli / postgre / etc... there are fetch_object functions where you can get an object for your row of data. By default it will return an object of stdClass, but you can also define a class_name and an array of params for the constructor.
I would like to do the same thing with a plain set of values. Preferably, setting the properties of the object before calling the constructor, which is the same behaviour the database-functions show. However, this doesn't seem to be possible.
The only way to even create an object with properties set seems to be to unserialize a preconstructed string. But that example still creates a new object as well, then sets the properties of that object from the unserialized object to ensure the constructor is called. But this means the constructor is called before the properties are set.
In short: I would like the following:
array_fetch_object(array $properties, string $class_name [, array $params ])
with the constructor called after the properties are set.
In the end I wrote the following class which performs the task using unserializing a fabricated string. It uses reflection to determine what the access-type of properties is and what the name of the constructor is (if any).
The heart of the class is the following line:
$object = unserialize('O:'.strlen($class_name).':"'.$class_name.'"'.substr(serialize($properties), 1));
which serializes the property-array and renames the serialized to the desired class_name.
class ObjectFactory {
private $properties;
private $constructors;
public function __construct() {
$this->properties = array();
$this->constructors = array();
}
private function setClass($class_name) {
$class = new ReflectionClass($class_name);
$this->properties[$class_name] = array();
foreach($class->getProperties() as $property) {
$name = $property->getName();
$modifier = $property->getModifiers();
if($modifier & ReflectionProperty::IS_STATIC) {
continue;
} else if($modifier & ReflectionProperty::IS_PUBLIC) {
$this->properties[$class_name][$name] = $name;
} else if($modifier & ReflectionProperty::IS_PROTECTED) {
$this->properties[$class_name][$name] = "\0*\0".$name; // prefix * with \0's unserializes to protected property
} else if($modifier & ReflectionProperty::IS_PRIVATE) {
$this->properties[$class_name][$name] = "\0".$class_name."\0".$name; // prefix class_name with \0's unserializes to private property
}
}
if($constructor = $class->getConstructor()) {
$this->constructors[$class_name] = $constructor->getName();
}
}
private function hasClassSet($class_name) {
return array_key_exists($class_name, $this->properties);
}
private function hasClassProperty($class_name, $property_name) {
if(!$this->hasClassSet($class_name))
$this->setClass($class_name);
return array_key_exists($property_name, $this->properties[$class_name]);
}
private function getClassProperty($class_name, $property_name) {
if(!$this->hasClassProperty($class_name, $property_name))
return false;
return $this->properties[$class_name][$property_name];
}
private function hasClassConstructor($class_name) {
if(!$this->hasClassSet($class_name))
$this->setClass($class_name);
return $this->constructors[$class_name] !== false;
}
private function getClassConstructor($class_name) {
if(!$this->hasClassConstructor($class_name))
return false;
return $this->constructors[$class_name];
}
public function fetch_object(array $assoc, $class_name = 'stdClass', array $params = array()) {
$properties = array();
foreach($assoc as $key => $value) {
if($property = $this->getClassProperty($class_name, $key)) {
$properties[$property] = $value;
}
}
$object = unserialize('O:'.strlen($class_name).':"'.$class_name.'"'.substr(serialize($properties), 1));
if($constructor = $this->getClassConstructor($class_name)) {
call_user_func_array(array($object, $constructor), $params);
}
return $object;
}
}
Well, you can cast an array to an object, like this:
$array = array('a' => 'a', 'b' => 'c');
$object = (object) $array;
or just:
$object = (object) array('a' => 'a', 'b' => 'c');
This will give you a stdClass object with the properties of the array.
Ok, I didn't expect this to work, but it does:
class TestObject {
public $property;
public $preset;
public function __construct($param) {
$this->property = $param;
}
}
$object = unserialize('O:10:"TestObject":1:{s:6:"preset";i:1;}');
$object->__construct(1);
print_r($object);
results:
TestObject Object
(
[property] => 1
[preset] => 1
)
I just have to check the access type of the property before creating the serialized string because the classname is prepended for private properties. However, is calling the constructor of an already constructed object expected to stay working?

Categories