For a metering project, I have a class MeterReadings, with a constructor that takes a previous reading as basis for all of its property values except the timestamp. In the example, the Object has only 3 properties, in real life there are many more. I could line by line for each property X do
$this->propertyX=$prevMS->propertyX
but that quickly becomes tedious.
What is best practice for copying all property values from the $prevMS object?
<?php class MeterReadings{
function __construct($prevMS=NULL){
if($prevMS===NULL){
$this->gas=0;
$this->water=0;
$this->electricity=0;
}else{
//PHP can duplicate an object by assignment,
//so I expected to be able to create a copy of $prevMS
//but cannot $this=$prevMS;
//and cannot $this= clone $that;
//which both throw error 'Cannot re-assign $this'
foreach($prevMS as $key => $value){
//logs "Undefined property: MeterReadings::$0"
print "$key => $value\n";
$this->$key = $prevMS->$value;
}
}
$this->date=time();//set timestamp for this object
}
}
$test= new MeterReadings();
$test2 = new MeterReadings($test);
print_r($test);
print_r($test2);
?>gets me
gas => 0
water => 0
electricity => 0
date => 1434448706
MeterReadings Object
(
[gas] => 0
[water] => 0
[electricity] => 0
[date] => 1434448706
)
MeterReadings Object
(
[gas] =>
[water] =>
[electricity] =>
[date] => 1434448706
)
You can use clone and the __clone Magic Method to manage what get's cloned.
For example:
class Test
{
public $value1;
public $value2;
public $timestamp;
public function __clone()
{
$this->timestamp = null;
}
}
$test = new Test();
$test->value1 = 1;
$test->value2 = 2;
$test->timestamp = time();
$test2 = clone $test;
print_r($test2);
// Test Object
// (
// [value1] => 1
// [value2] => 2
// [timestamp] =>
// )
Alternative:
To keep things as in the __constuct as specified the comment below:
class Test
{
public $value1;
public $value2;
public $timestamp;
public function __construct (Test $copy = null)
{
if($copy) {
foreach ($copy as $key => $value) {
$this->$key = $value;
}
}
$this->timestamp = 'whoop';
}
}
$test = new Test();
$test->value1 = 1;
$test->value2 = 2;
$test->timestamp = 'whatev\'s';
$test2 = new Test($test);
print_r($test2);
// Test Object
// (
// [value1] => 1
// [value2] => 2
// [timestamp] => 'whoop'
// )
This will work for objects that are not itterable and for properties that are protected (but not private). For objects that are itterable and return something else, you can use get_object_vars(), documentation here. For stuff that's private (are you sure you need private, that's unusual) you'll need to create getters. They can be protected if you don't want them externally available.
Related
I am looping through an object and i try to duplicate one of the items while changing one of it's variables.
But when i copy the original and then change the title in the new one, the old one changes along with it. Which it shouldn't, since i did not initialised it as a reference.
$calendar = array(
(object)[
'id' => 1,
'title' => 'original 1',
],
(object)[
'id' => 2,
'title' => 'original 2',
],
(object)[
'id' => 3,
'title' => 'original 3',
],
);
foreach ($calendar AS $key => $item){
if($item->id == 2){
$item->title = 'new 2';
array_splice($calendar, $key, 0, [1]);
$calendar[$key] = $item;
}
}
echo "<pre>";
print_r($calendar);
die();
I would expect the output of this to keep original 2 intact. But it changes it along with it.
(
[0] => stdClass Object
(
[id] => 1
[title] => original 1
)
[1] => stdClass Object
(
[id] => 2
[title] => new 2
)
[2] => stdClass Object
(
[id] => 2
[title] => new 2
)
[3] => stdClass Object
(
[id] => 3
[title] => original 3
)
)
Even if i make a new object and use that one to make the changes, it still changes the orignial.
foreach ($calendar AS $key => $item){
if($item->id == 2){
$new_item = $item;
$new_item->title = 'new 2';
array_splice($calendar, $key, 0, [1]);
$calendar[$key] = $new_item;
}
}
Now i could probably fix this by just making a new object from scratch and copy the values one by one in it. But where's the fun in that?
So my question is...Why does this happen? Even though i didn't cast $item as &$item
Assignment or clone ?
Because, you're using objects, the problem is that $new_item = $item; doesn't create a new object, it creates a new reference of $item, named $new_item.
In the following example, $a and $b are the same object:
$a = new stdclass;
$b = $a;
var_dump($a, $b);
Output is:
object(stdClass)#1 (0) {...} // same object #1
object(stdClass)#1 (0) {...} // same object #1
Clone
You could use the keyword clone to create a new instance:
$a = new stdclass;
$b = clone $a; // Clone the object
var_dump($a, $b);
Output:
object(stdClass)#1 (0) {...} // object #1
object(stdClass)#2 (0) {...} // new object #2
So, in your case, you could use:
if ($item->id == 2) {
$clone = clone $item; // << Create a COPY of $item
$clone->title = 'new 2'; // Update the copy, not the reference
$calendar[$key] = $clone; // Add this copy to final array
// ...
}
A note about parameters
When you're using objects as parameters for some functions, objects are reference, so, the given object can be updated in that function. Here is a simple example (demo):
function updateObject(object $object): void {
$object->newProperty = true;
}
$obj = new stdClass;
var_dump($obj);
updateObject($obj);
var_dump($obj);
The code above gives the following (condensed) output:
object(stdClass)#1 (0) { }
object(stdClass)#1 (1) { ["newProperty"]=> bool(true) }
Further reading : Objects and references
As simple PHP Object assignment does not create a new object. It simply creates a new pointer to the same object.
I think you want to use the clone keyword:
foreach ($calendar AS $key => $item){
if($item->id == 2){
$new_item = clone $item;
$new_item->title = 'new 2';
array_splice($calendar, $key, 0, [1]);
$calendar[$key] = $new_item;
}
}
First, I have tried to find solution within the source here but couldn't find what I am looking so posting as a new question. Thanks for your help
I want to convert Array to Object. Here is what I am getting output
Array
(
[0] => stdClass Object
(
[id] => 1
[username] => robustsolver
[first_name] => John
[last_name] => Smith
)
[1] => stdClass Object
(
[id] => 2
[username] => pickypacker
[first_name] => Peter
[last_name] => Packer
)
)
So if I want any column for all users than I have to write code $users[0]->first_name; which gives me only one item. But what I am looking is to use $users->first_name and this should return an array of all user's column (here is first_name)
Hope I have explain in better way. :S
You can try this where $arr is your array:
function filter_callback($element) {
return $element->first_name;
}
$result= array_map('filter_callback', $arr);
From a quick test, this seems to work. It keeps objects of the array without your wanted property but its value is set to NULL. Not sure if that's what you want but you can edit the filter_callback to remove such elements.
Maybe something like this will work (not tested):
$arr = array(...); // array of objects (in)
$obj = new Object; // object of arrays (out)
foreach($arr as $a) {
foreach(get_object_vars($a) as $k=>$v) {
if(!property_exists($obj, $k)) {
$obj->{$k} = array();
}
$obj->{$k}[] = $v;
}
}
consider defining a new class:
class User_objects
{
public $first_name;
public $username;
public $last_name;
public function __construct()
{
$this->first_name = array();
$this->username = array();
$this->last_name = array();
}
}
then:
consider $array_of_objects to be your array of objects (input).
$users = new User_objects();
foreach ($array_of_objects as $object)
{
$users->first_name[] = $object->first_name; // append to array
$users->last_name[] = $object->last_name;
$users->username[] = $object->username;
}
then you can get your array from $users->first_name
You can write a simple helper function that will aid in the columns you wish to select:
function prop_selector($prop)
{
return function($item) use ($prop) {
return $item->{$prop};
};
}
$first_names = array_map(prop_selector('first_name'), $users);
$last_names = array_map(prop_selector('last_name'), $users);
class A {
protected $a = 'aaa';
}
class B extends A {
protected $a = 'bbb';
public function __construct(){
echo parent::$a; // Fatal error: Access to undeclared static property: A::$a in main.php on line 11
}
}
$b = new B();
I want to access $a variable from class A in constructor of class B. Be aware that $a variable is overwritten in class B. How can I access parent::$a?
The only way to do this would be to declare $a as static:
protected static $a = 'aaa';
But that will make the value of parent::$a the same for all instances. If you want separate values, this cannot be done, and you'd be better off renaming the variables, eg one is $a and the other is $b.
class A {
protected $a = 'aaa';
}
class B extends A {
protected $a = 'bbb';
public function __construct(){
echo parent::$a; // Fatal error: ...
}
}
$b = new B();
How can I access parent::$a?
You cant, parent::$a means you are trying to access a static property from a parent class.
instead of doing this,use the constructor to modify $a
class B extends A {
public function __construct(){
// do something with $this->a value here;
}
}
or you'll always overwrite $a if your redeclare it as a property in B.
I just read your comment so I understand your use case a little better now. If you are adding/merging configurations in inheriting classes I'd suggest an alternative approach, adding some behaviour.
As you confirmed above:
class A has a default configuration
class B can optionally pass in config values that can update/add to the default config
In this case, something like this could work for you:
class A
{
protected $config = array(
'foo' => 'foo',
'bar' => 'bar',
'baz' => 'baz',
);
public function __construct(array $config = array())
{
$this->config = array_merge($this->config, $config);
}
public function getConfig()
{
return $this->config;
}
}
class B extends A
{
// implement
}
$b = new B(array(
'foo' => 'OVERWRITTEN',
'new' => 'NEW',
));
print_r($b->getConfig());
Yields:
Array
(
[foo] => OVERWRITTEN
[bar] => bar
[baz] => baz
[new] => NEW
)
You can also overwrite your default config in the same way when using class A directly.
Alternatively, instead of implementing the merge in __construct() you could implement that as a setConfig() method.
Hope this helps :)
EDIT
I just want to add one more thing: if your config is a multidimensional array, you will have to change how you merge arrays. At first glance array_merge_recursive() might seem like the obvious candidate. However:
$old = array(
'foo' => 'foo',
'bar' => 'bar',
'baz' => array(
'baa' => 'baa',
'boo' => 'boo',
),
);
$new = array(
'foo' => 'FOO',
'baz' => array(
'baa' => 'BAA',
),
'new' => 'new'
);
$merge = array_merge_recursive($old, $new);
print_r($merge);
actually yields:
Array
(
[foo] => Array
(
[0] => foo
[1] => FOO
)
[bar] => bar
[baz] => Array
(
[baa] => Array
(
[0] => baa
[1] => BAA
)
[boo] => boo
)
[new] => new
)
Probably not what you are looking for! Instead use array_replace_recursive():
$merge = array_replace_recursive($old, $new);
print_r($merge);
This yields:
Array
(
[foo] => FOO
[bar] => bar
[baz] => Array
(
[baa] => BAA
[boo] => boo
)
[new] => new
)
#Darragh I made it little different because I didn't want to change my constructors:
abstract class A
{
protected $a = array('a' => 1, 'b' => 2);
public function __construct()
{
$this->mixA();
}
protected function a()
{
return array();
}
protected function mixA()
{
foreach ($this->a() as $key => $val) {
$this->a[$key] = $val; // $val can be an array too (in my case it is)
}
}
}
class B extends A
{
protected function a()
{
return array(
'b' => 'new value',
'c' => 'new variable'
);
}
public function dumpA()
{
var_dump($this->a);
}
}
$b = new B();
$b->dumpA();
So now if I want to change my default configs I just overwrite a() method. mixA() method can be expanded as needed.
When I do the following:
$arUserStuff = array ('name' => 'username', 'email' => 'test#test.com');
$object = (object) $arUserStuff;
print_r($object);
The print function returns me the following:
stdClass Object ( [name] => username [email] => test#test.com )
How can I change std class object in let's say's User Object?
Create that class, then create an object of it:
class User {
public $name, $email; // public for this example, or set these by constructor
public function __construct( array $fields) {
foreach( $fields as $field => $value)
$this->$field = $value;
}
}
$object = new User;
$object->name = 'username';
$object->email = 'test#test.com';
Or, you can do:
$arUserStuff = array ('name' => 'username', 'email' => 'test#test.com');
$object = new User( $arUserStuff);
Now, from print_r( $object);, you'll get something like this:
User Object ( [name] => username [email] => test#test.com )
actually to do what you want, you should make it like:
$arUserStuff = new ArrayObject(
array (
'name' => 'username', 'email' => 'test#test.com'
)
);
to change the class name you need to create a new class.
It's a rather complex process but you can learn about it here:
http://php.net/manual/en/language.oop5.php
Here's a generic function that converts an array into any type of object, assuming the fields are public
class User { public $name, $email; }
class Dog { public $name, $breed; }
function objFromArray($className, $arr) {
$obj = new $className;
foreach(array_keys(get_class_vars($className)) as $key) {
if (array_key_exists($key, $arr)) {
$obj->$key = $arr[$key];
}
}
return $obj;
}
print_r(objFromArray('User',
array ('name' => 'username', 'email' => 'test#test.com')));
echo "<br/>";
print_r(objFromArray('Dog',
array ('name' => 'Bailey', 'breed' => 'Poodle')));
Output
User Object ( [name] => username [email] => test#test.com )
Dog Object ( [name] => Bailey [breed] => Poodle )
I wanted to make a trait out of it but don't have PHP 5.4 installed to test it. This wouldn't require the fields to be public
trait ConvertibleFromArray {
public static function fromArray($arr) {
var $cls = get_called_class();
var $obj = new $cls;
foreach($arr as $key=>$value) {
if (property_exists($obj, $arr)) {
$obj->$key = $value;
}
}
return $obj;
}
}
class User {
use ConvertibleFromArray;
public $name, $email;
}
class Dog {
use ConvertibleFromArray;
public $name, $breed;
}
print_r(User::fromArray(array ('name' => 'username', 'email' => 'test#test.com')));
print_r(Dog::fromArray(array('name' => 'Bailey', 'breed' => 'Poodle')));
?>
I need to create a nested structure class of a multidimensional array, this is my array:
Array
(
[days] => Array
(
[0] => Array
(
[rows] => Array
(
[0] => Array
(
[activity_id] => 1
[name] => Activity 2
[city] => London
[info] => fsdsdshgsfd
)
[1] => Array
(
[activity_id] => 3
[name] => Activity 1
[city] => London
[info] => fsdhgsfd
)
)
)
[1] => Array
(
[rows] => Array
(
[0] => Array
(
[activity_id] => 3
[name] => Activity 1
[city] => London
[info] => fsdhgsfd
)
...
)
)
)
...
)
)
I have been trying to rewrite my code to make it class-driven, but I am struggling with that, what is the right way to build a class structure Itinerary->Days->Rows to replace an array? I tried something like this, I am not sure if it makes sense, I don't really understand the way how it has to be done:
class Itinerary
{
private $days = array();
public static function addDay($day) {
$this->$days[] = new ItineraryDay($day);
}
}
class ItineraryDay implements Countable
{
private $rows = array();
public static function addRow($row) {
$this->$rows[] = new ItineraryRow($row);
}
public function count()
{
return count($this->rows);
}
}
class ItineraryRow implements Countable
{
private $name;
private $city;
...
function __get($key)
{
...
}
function __set($key, $value)
{
...
}
public function count()
{
return count($this->rows);
}
}
$itinerary1 = new Itinerary();
$day1 = new ItineraryDay();
$itinerary1->addDay($day1);
$row1 = new ItineraryRow();
$day1->addRow($row1);
Can someone guide me?
It really depends what you ultimately want to do with said structure, but for a general idea I typically do something like this:
class Itinerary implements Countable
{
private $days;
public function __construct( array $days = array() )
{
$this->setDays( $days );
}
public function addDay( ItineraryDay $day )
{
$this->days[] = $day;
}
public function setDays( array $days )
{
$this->days = array();
foreach( $days as $day )
{
$this->addDay( $day );
}
}
public function count()
{
return count( $this->days );
}
}
class ItineraryDay implements Countable
{
private $rows;
public function __construct( array $rows = array() )
{
$this->setRows( $rows );
}
public function addRow( ItineraryRow $row )
{
$this->rows[] = $row;
}
public function setRows( array $rows )
{
$this->rows = array();
foreach( $rows as $row )
{
$this->addRow( $row );
}
}
public function count()
{
return count( $this->rows );
}
}
class ItineraryRow
{
private $id;
private $name;
private $city;
private $info;
public function __construct( $id, $name, $city, $info )
{
$this->id = $id;
$this->name = $name;
$this->city = $city;
$this->info = $info;
}
/* ... */
}
Then using it with the structure of your current array of data:
$days = array();
foreach( $originalData[ 'days' ] as $days )
{
$rows = array();
foreach( $days[ 'rows' ] as $row )
{
$rows[] = new ItineraryRow( $row[ 'activity_id' ], $row[ 'name' ], $row[ 'city' ], $row[ 'info' ] );
}
$days[] = new ItineraryDay( $rows );
}
$itinerary = new Itinerary( $days );
Or:
$itinerary = new Itinerary;
foreach( $originalData[ 'days' ] as $days )
{
$day = new ItineraryDay;
foreach( $days[ 'rows' ] as $row )
{
$row = new ItineraryRow( $row[ 'activity_id' ], $row[ 'name' ], $row[ 'city' ], $row[ 'info' ] )
$day->addRow( $row );
}
$itinerary->addDay( $day );
}
So, you can either pass "child" objects to the constructor (the method that constructs a new object), or add them with methods after construction. If you want the objects to be immutable, meaning you don't want to allow the objects to accept any more rows / days after construction, just make the addDay, setDays, addRow and setRows methods protected or private thereby only allowing passing "child" object through the constructors.
Be aware that, as PeeHaa already mentioned, you don't want static methods, because they operate class wide, not on individual instances of classes (objects). As a matter of fact, you cannot even use static methods the way you intended, because $this is only available in object context, not in class wide context.
But, to be honest, the question is a little bit to vague to be answered properly. We'd have to have a little more details about how you are going to construct the objects, and how you are going to use them later on.