So I have an object, let's call it Example.
class Example {
public function __construct ($id) {
$this->Id = $id;
}
}
$objectA = new Example(10);
It has an id (it pulls this from somewhere), the goal of the object is to overwrite a similar object with this object's properties (post to an external service). This is done by simply changing the ID of the object, and running an ->update() method. However, it must first change its ID (among other properties) to match the ids of object B.
So what I do is clone the current object, reassign the needed properties, and then pass that cloned object to the update method, so that the update method uses the $post values for the update.
public function createPost ($id) {
$post = clone $this;
$post->Id = $id;
return $post;
}
$objectA->update($objectA->createPost(12));
$objectA->update($objectA->createPost(16));
$objectA->update($objectA->createPost(21));
The issue I'm having is this object A needs to be used for multiple different updates, and it uses the ID it is originally assigned as a pointer to what IDs it must later use for $post, and in this scenario, the value of $this->ID is getting reassigned to the $id that is passed in ->setParameters(), even though I'm trying to assign it to a clone of $this, rather than $this itself.
My impression is that $objectA = $objectB assigns ObjectB to the same pointer that points to ObjectA, but that "clone" was supposed to actually make a copy of that object, so that if properties of the clone are changed, the original object remains unaffected, but that doesn't seem to be the case here. Is there a particular method I should instead be using to ensure that the original object's value aren't ever changed when a clone of it is?
I think what you want is a deep clone. Take a look at this link.
The issue I ran into here was a shallow cloning issue it would appear.
Forget what I referenced above, the problem was more so this: -
class Example {
public function __construct ($id) {
$this->ObjId = (object)array("Id" => 5);
$this->Id = $id;
}
}
$example = new Example (5);
$clone = clone $example;
$clone->ObjId->Id = 10;
In this example, I was trying to change the ->Id of a standard php object that was stored within my main object (I had used json_encode to pull a whole structure of objects) that was called "Id". The result of the above was this: -
echo $example->ObjId->Id; //10
echo $example->Id; //5
echo $clone->ObjId->Id; //10
echo $clone->Id; //5
So I had to write this method as part of the Example class: -
public function __clone () {
foreach ($this as $key => $val) {
if (is_object($val) || (is_array($val))) {
$this->{$key} = unserialize(serialize($val));
}
}
}
So now, the result was this: -
echo $example->ObjId->Id; //5
echo $example->Id; //5
echo $clone->ObjId->Id; //10
echo $clone->Id; //5
This is because when I cloned the main Example object, it was creating an new variable for it's properties, but if one of it's properties was an object or array, it was copying the address of that object or array for the new cloned object. The above method prevents that, and does a "deep" copy.
Related
In my project, I've an object Absence, which has lot of attributes including a "dateDebut" attribute.
In a function, I process this object to get some information. And in this function I happen to have to add a day to the dateTime like this
$processAbsence->setDateDebut($processAbsence->getDateDebut()->modify('+1 day'));
My goal is obviously not to modify the initial object. This is why I cloned the object before:
public function getNotWorkingDays(Absence $absence)
{
//$absence is my original object. I clone it to not apply some modifications on it.
$processAbsence = (clone $absence)
while($this->canAbsenceProgress($processAbsence))
{
$processAbsence = $this->doAbsenceProgress($processAbsence);
}
//...
}
private function doAbsenceProgress(Absence $absence)
{
// Here too, I cloned the previous cloned object, I make some modifications on it and return it
$processAbsence = (clone $absence);
if ('matin' == $processAbsence->getMomentDebut()) {
$processAbsence->setMomentDebut('après-midi');
} else {
$processAbsence->setDateDebut($processAbsence->getDateDebut()->modify('+1 day'));
$processAbsence->setMomentDebut('matin');
}
return $processAbsence;
}
But when I dump my $absence object, I can see that the $absence->getDateDebut() has been changed just like the cloned object should be ...
I do not understand why
From the docs
When an object is cloned, PHP will perform a shallow copy of all of the object's properties. Any properties that are references to other variables will remain references.
As shown in the docs, you'll probably need to give your Absence class a __clone method inside of which, you'll need to explicitly clone the properties that are references to datetime objects so you end up with clones of those as well instead of pointers to the same objects:
class Absence
{
public $dateDebut;
function __clone()
{
// Force a copy of this->dateDebut, otherwise
// it will point to same object.
$this->dateDebut= clone $this->dateDebut;
}
}
I try to clone all records in my data entity that have the item value cf7c1ae00f
$dataEntity= new Data();
$cloneArray = $this->em->getRepository(Data::class)->findBy(['item' => 'cf7c1ae00f']);
foreach ($cloneArray as $cloneItem) {
$fieldClone = clone $cloneItem;
$dataEntity->setItem($fieldClone);
$this->em->persist($dataEntity);
}
$this->em->flush();
In my database there are 5 records. So I expect that another 5 records are added. But only one record is added.
You are writing the same $dataEntity 5 times with different contents. You could construct that object in the loop to solve your problem but you could also persist $fieldClone directly instead and skip the $dataEntity variable completely.
However, entities have unique ids and that will lead to errors when you try to persist a cloned entity. You would have to empty the id and other attributes that have to be unique in the collection / database.
You can easily set some initial values on a new object when the clone keyword is used, using the __clone() method of the class that the object belongs to.
So if you only need to empty the id, you would add a clone method to the Data class and change the loop to:
Data class:
public function __clone() {
$this->id = null;
}
Cloning code:
$cloneArray = $this->em->getRepository(Data::class)->findBy(['item' => 'cf7c1ae00f']);
foreach ($cloneArray as $cloneItem) {
# Clone the object and automatically run the `__clone()` method of the class
$fieldClone = clone $cloneItem;
$this->em->persist($fieldClone);
}
$this->em->flush();
As I got it, in the first example an object is created, and in the second one I don't see an object created. I am trying to understand, what is the difference between the two ways of method calling :
<?php
class Animal{
public $voice;
public function speak($sound){
echo $this->voice = $sound;
}
}
// Example 1
$tiger = new Animal();
$tiger->speak('Roar');
// Example 2
(new Animal)->speak("Cloak Cloak");
Whenever you use "new", you're creating an instance of an object (it can be temporary). The difference in your code is that in the first example, you're storing the instance in "$tiger", so it'll persist, but in the second example you're only instantiating a temporary object to call a method.
In the first example, you are assigning the variable $tiger as a new Object, by which you can then call the functions and variables associated with that object, by referencing $tiger.
i.e. as Tiger now equals an Object of Class Animal, it can speak.
However in the second example, you are still creating a new Object of class Animal, and as such it can speak - but you have not assigned it to a variable. So you cannot reference that same Object any longer.
So in the first example, if we wanted to name our $tiger, we could have the Class look something like this.
class Animal{
public $voice;
public $name = "I have no name.";
public function speak($sound){
echo $this->voice = $sound;
}
public function sayYourName(){
echo $this->name;
}
}
Now if we say,
$tiger = new Animal();
$tiger->speak('Roar');
$tiger->name = "Rory";
$tiger->sayYourName(); // This will echo "Rory"
However, if you try your second example instead :
(new Animal)->sayYourName(); // This will echo "I have no name."
So if you say :
(new Animal)->name = "Rory";
(new Animal)->sayYourName(); // This will still echo "I have no name".
This is because we haven't assigned a reference to the new animal, so while we can access methods of the function, and even predefined variables, we can't then reference them again later on.
To do that, we should stick to the first method (i.e. referencing)
$tiger = new Animal();
$tiger->name = "Rory";
$tiger->sayYourName();
In conclusion, use referencing to refer to an Object later on. i.e to get the animals attention, you have to call it by its name.
I have a problem with php variables. I use a code that looks like this:
for($i = 0; $i <= $nbRecurrence; $i++) {
$res = new Reservation();
$res->setDateDebut($DateDebut->add(new \DateInterval('P1D')));
$res->setDateFin($DateFin->add(new \DateInterval('P1D')));
$lesRes[] = $res;
$this->app['orm.ems']['gestionReservationAuto']->persist($res);
$this->app['orm.ems']['gestionReservationAuto']->flush();
}
The problem is that although I adds each element in the array, but when I use a var_dump for analysis, all $res in $lesRes are identical. Registered data is yet different in the database ...
How can I do to have an array with $res that do not like?
(if I make a request to have the items I just added in database, I have the same problem, I have an array of x elements $res, which are all identical.)
I guess $DateDebut is a DateTime object.
I also guess that Reservation::setDateDebut() looks something like:
class Reservation
{
private $dateDebut;
public function setDateDebut(DateTime $dateDebut)
{
$this->dateDebut = $dateDebut;
}
}
And let's write again the code that uses it:
$res = new Reservation();
$res->setDateDebut($DateDebut->add(new \DateInterval('P1D')));
What you miss is the fact that DateTime::add() does not create a new DateTime object but returns a reference to the current object (i.e. return $this;).
This means on each iteration you change the value of object $DateDebut then you pass it to Reservation::setDateDebut() which also doesn't make a copy of it but just links to the object it gets as argument.
After the loop you still have only two instances of DateTime; one of them is accessible through the variable $DateDebut and the members $dateDebut of all the Reservation objects created during the loop. The other instance is $DateFin and the same discussion is valid for it too.
Your code is a victim of variables aliasing.
How to fix it:
You need to create copies of $DateDebut somewhere, either in the loop code (and pass the copies to Reservation::setDateDebut()) or in the body of Reservation::setDateDebut():
// Either
$res->setDateDebut(clone $DateDebut->add(new \DateInterval('P1D')));
// Or
public function setDateDebut(DateTime $dateDebut)
{
$this->dateDebut = clone $dateDebut;
}
You decide where is the most appropriate place to do it, depending how the rest of the code works with these objects.
Because you change the value of $DateDebut in the loop you should make copies of the object there.
If the class Reservation changes the value of its $dateDebut member then you should (also) clone it in the setter method. This is because the caller of Reservation::setDateDebut() does not expect the Reservation class make changes on the value it passes as an argument.
I have a custom class object in PHP named product:
final class product
{
public $id;
public $Name;
public $ProductType;
public $Category;
public $Description;
public $ProductCode;
}
When passing an object of this class to my Data Access Layer I need to cast the object passed into a type of the product class so I can speak to the properties within that function. Since type casting in PHP works only with basic types what is the best solution to cast that passed object?
final class productDAL
{
public function GetItem($id)
{
$mySqlConnection = mysql_connect('localhost', 'username', 'password');
if (!$mySqlConnection) { trigger_error('Cannot connect to MySql Server!'); return; }
mysql_select_db('databaseName');
$rs = mysql_query("SELECT * FROM tblproduct WHERE ID='$id';");
$returnObject = mysql_fetch_object($rs, 'product');
return $returnObject;
}
public function SaveItem($objectToSave, $newProduct = false)
{
$productObject = new product();
$productObject = $objectToSave;
echo($objectToSave->Name);
$objectToSave->ID;
}
}
Right now I am creating a new object cast as a type of product and then setting it equal to the object passed to the function. Is there a better way of accomplishing this task? Am I going about the wrong way?
EDITED FOR CLARITY - ADD FULL PRODCUTDAL CLASS
You don't need to cast the object, you can just use it as if it was a product.
$name = $objectToSave->Name;
I´m not sure what you are trying to achieve, but if $objectToSave is already of class product:
You can simply call $objectToSave->SaveItem() (assuming SaveItem() is part of the product class) and access it´s properties in the function like $this->Name, etc.;
In your code $productObject and $objectToSave will hold a reference to the same object.
Type casts in PHP are done like this:
$converted = (type) $from;
Note, that this won't work if the object types are not compatible (if for example $form happens to be a string or object of mismatching type).
But usual solution (called Active Record pattern, present for example in Zend Framework) is to have a base class for a database item called Row. Individual items (for example the class product from your sample) then inherit from this class.
Typical ZF scenario:
$table = new Product_Table();
$product = $table->find($productId); // load the product with $productId from DB
$product->someProperty = $newPropertyValue;
$product->Save(); // UPDATE the database
Which is IMO much better than your solution.
EDIT:
You can't cast between two unrelated objects, it is not possible.
If you want to use the DAL like this, skip the "product" object and go for simple associative array. You can enumerate over its members with foreach, unlike object's properties (you could use reflection, but that's overkill).
My recommendation: Go for the Active Record pattern (it is easy to implement with magic methods). It will save you a lot of trouble.
Currently, you are creating a new Product, then discarding it immediately (as its reference is replaced by $objectToSave.) You will need to copy its properties one by one, I regret.
foreach (get_object_vars($objectToSave) as $key => $value)
{
$product->$key = $value;
}
(If the properties of $objectToSave are private, you will need to a expose a method to_array() that calls get_object_vars($this).)