My problem is that I have an object shared through two classes that contains an array inside of it and along the script, someone will request some of the classes the value and a foreach loop will change such value and I want this change to affect every reference of the value.
class bar {
protected $obj;
function __construct(&$obj) {
$this->obj = $obj;
}
public function output() {
print_r($this->obj->value);
}
}
class foo {
protected $obj;
function __construct(&$obj) {
$this->obj = $obj;
}
public function val() {
$result = array();
foreach($this->obj->value as $it){
$result[] = $it;
}
return $result;
}
}
// Shared Object
$obj = new stdClass();
// Default value
$obj->value = array('teste', 'banana', 'maca');
// Class 1
$bar = new bar($obj);
// Class 2
$foo = new foo($obj);
// Someone requests from class 2 the values and changes it
$new = $foo->val();
$new[] = 'abc';
// Class 1 outputs the value
$bar->output(); // this will print the default value. I want this to also have 'abc' value.
The main problem, is that you are building a new array at foo:val, you must return the original object to be modified.
I suggest use ArrayObject, have the same behavior of array but is a object, then always is passed by reference.
<?php
class MyArrayObject extends ArrayObject {
public function replace(Array $array)
{
foreach($this->getArrayCopy() as $key => $value) {
$this->offsetUnset($key);
}
foreach ($array as $key => $value) {
$this[$key] = $value;
}
}
}
class bar {
protected $obj;
function __construct(MyArrayObject $obj) {
$this->obj = $obj;
}
public function output() {
print_r($this->obj);
}
}
class foo {
protected $obj;
function __construct(MyArrayObject $obj) {
$this->obj = $obj;
}
public function val() {
$result = array('foo', 'bar');
$this->obj->replace($result);
return $this->obj;
}
}
// Shared Object
$obj = new MyArrayObject(array('teste', 'banana', 'maca'));
// Class 1
$bar = new bar($obj);
// Class 2
$foo = new foo($obj);
// Someone requests from class 2 the values and changes it
$new = $foo->val();
$new[] = 'abc';
// Class 1 outputs the value
$bar->output(); // this will print the default value. I want this to also
var_dump($obj);
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
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.
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.
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?
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);