Removing an item from object in a loop - php

I have a set of Db results that are stored in an object. I need to loop through the results and check a property (using another DB query) and then use an if statement to remove the item from the object. Here is a simplified version of what I'm attempting:
foreach ($products as $product) {
if(!$product->active) {
unset($product);
}
}
print_r($products);
However when I print_r the items are still in the object. I'm getting confused.

Thats expected behaviour. There are two main way of doing what you want
foreach ($products as $key => $product) {
if(!$product->active) {
unset($products[$key]);
}
}
Second way would be to use reference
foreach ($products as &$product) {
if(!$product->active) {
unset($product);
}
}

You need to understand that unsetting an object has no effect in php. First of all let me explain you a crucial detail with FOREACH:
if you do:
$a = array(1,2,3,4,5);
foreach($a as $b){
unset($b);
}
$a will be first copied in memory. Its not a brute copy per say, it only copies a reference to the data and augments the count of usage of the array(1,2,3,4,5) in memory. Inside of $b, you will have copies of the data found in $a. Therefore, unsetting it from memory only says, hey, unset $b from inside the copy of $a. Therefore, making no change at all in the real $a.
If you were to do:
$a = array(1,2,3,4,5);
foreach($a as $key => $b){
unset($a[$key]);
}
Then here you would have a copy of $a in memory. The Foreach would iterate (loop) on that copy and provide you with keys to each elements $a that gets copied into $b. When you unset($a[$key]) you tell php to affect the array in $a that got copied when the foreach started, but now, instead of affecting the copy, you are using the $key to reference an element in $a that truly exists in memory and that you will have access to.
Now for the second part, if we look at objects... unsetting an object has no effect because variables containing objects are only references to data in memory with a count. If you $a = new Object() and then $b = $a, you create a new reference to that object whilst keeping it intact (not copied).
If you were to unset($a), you would only unset the reference to the object and $b would still point to that object in memory. If you unset($b), you will then unset the object reference from memory because nothing points to it.
Hope that makes it clearer...
Good luck

You can't unset the variable itself. You need to use the foreach syntax that also gives you the item's key and use that to unset key on the array:
foreach ($products as $key => $product) {
if(!$product->active) {
unset($products[$key]);
}
}

Use this line instead:
foreach ($products as &$product)

try this:
// $id is the key, $product is the value
foreach ($products as $id => $product) {
if(!$product->active) {
unset($products[$id]);
}
}

Alternatively, you can change the loop from using a foreach and operate directly on the array using a for-loop as follows:
<?php
$o1 = new stdClass;
$o2 = new stdClass;
$o3 = new stdClass;
$o1->active = false;
$o2->active = true;
$o3->active = true;
$products = [$o1, $o2, $o3];
for($i=0, $max=count($products); $i < $max; $i++) {
if (!($products[$i]->active)) {
unset($products[$i]);
}
}
print_r($products);
// re-index:
$improvedProducts = array_values($products);
print_r($improvedProducts);
See live code.

Related

Issue with modifying array in foreach and understading why

I always did something like
e.g. from my last project in Laravel
foreach( $sections as $section )
{
$section->date = Carbon::parse($section->date)->diffForHumans();
}
And it worked well. Never problem with that, array was modified as I wanted
But now I did this
$events = $this->events;
foreach($events as $event)
{
$date = new \DateTime($event['date']);
$event['date'] = (int)$date->format('d');
echo $event['date'];
var_dump($event);
}
var_dump($events);
$this->events = $events;
And it doesn't work as I want, different values inside loop and outside loop.
I was looking what is bad and I found what it is about, to give & or do foreach $key => $value...
But it would be okay.
But it isn't.
Because I know. I remember. I did many times just something like
foreach($a as $b)
{
$b = .......
}
And it worked. But now it isn't.
So I don't understand. But I have to and want to.
I was searching for answer, but can find it.
Why for one time it works for me like that, and other time doesn't work and I need apersand or key => value playing?
Please give me understanding <3
Why one time it is working but another not.
foreach iterates over a copy of the array, so modifications to the array elements are only made to that temporary copy unless you create a reference:
foreach($a as &$b) {
$b = 'something';
}
Or modify the original array by key:
foreach($a as $k => $b) {
$a[$k] = 'something';
}
For an array of objects, the array is a copy but the objects in the copy are references to the original array. So modifications to the objects in the copy are reflected in the original array:
foreach($a as $b) {
$b->var = 'something';
}

Manipulate an array when looping

So $tr['tree'] is an array. $dic is an array stored as key values. I want to add the key source to that those arrays. It looks like the following code doesn't work as expected as I'm guessing $dic is a new instance of the array object inside $tr['tree'].
foreach($tr['tree'] as $dic){
$dic['source'] = $tr['source']." > ".$dic['name'];
}
Note, I'm coming from python where this would work brilliantly. So how would I do this in PHP?
foreach() creates copies of the items you're looping on, so $dic in the loop is detached from the array. If you want to modify the parent array, the safe method is to use:
foreach($array as $key => $value) {
$array[$key] = $new_value;
}
You could use a reference:
foreach($array as &$value) {
^---
$value = $new_value;
}
but that can lead to stupidly-hard-to-find bugs later. $value will REMAIN a reference after the foreach terminates. If you re-use that variable name later on for other stuff, you'll be modifying the array, because the var still points at it.

Weird PHP behaviour - What's going on? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Strange behavior Of foreach
Just came across this bug recently in a PHP app. Not sure what's going on.
Basically, it shows up when using a combination of two foreach (one with &, one without).
Here's a test code that reproduce the problem:
$items = array(
array('id'=>1, 'name'=>'foo', 'value'=>150),
array('id'=>2, 'name'=>'bar', 'value'=>190)
);
foreach($items as &$item)
{
$item['percentage'] = $item['value'] * 0.75;
}
var_dump($items); // All Good
foreach($items as $item)
{
var_dump($item); // Shows 1st item twice
}
The second foreach loop runs the block twice, as expected, but $item remains stuck on the first item.
I understand this is likely caused by the use of the reference & in the first loop but I don't see why it should behave like this..
Any idea? is that a bug?
Getting the same result on 5.3.8, 5.3.10 & 5.4
Firstly, it is not a bug as Rasmus said. See https://bugs.php.net/bug.php?id=29992
In this, right implementation of modifying array with its loop variable with &.
<?php
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)
unset($value); // break the reference with the last element
var_dump($arr); // All Good
foreach($arr as $value) {
var_dump($value); // All good
}
?>
This is odd PHP behavior that's been around pretty much forever, and it happens when you mix the use of a variable as reference then not reference like you did.
I deal with it with a naming convention as follows: When I am using foreach with a &$item, I name it like &$refItem. This keeps me from mixing types.
You need to unset the pointer after using foreach with a referenced array.
http://php.net/unset
This may be something that look more understand the problem of foreach
$last_value_of_first_foreach = 1;
$item = 2;
$c = 3;
$item = &$last_value_of_first_foreach ; // Think that this statement is first foreach loop
// Here $item is pointer to $last_value_of_first_foreach
// To Better understanding, let change the name ($reference_to_last_value = $item;)
now, the new loop is
$item = $c;
// Here, what it do is update value where the $item pointer refer to
// (mean $last_value_of_first_foreach )
// so, at here $last_value_of_first_foreach has value of $c
Now, back to your case, from the first foreach, the $item reference to the last element of array. now, when you assign something to $item in second foreach, what it do, is put something inside that one.
In the end of first loop
$item is pointer to $items[1]
The first of second loop
it will push the first element to the location where the $item point to (that mean $items[1], so that why $items[1] is replaced by $items[0].
In case you want to prevent this one, just unset the $item variable before next time usage.
This is a normal, not a weird behavior. Just read about reference here.
When you add & in front of a variable, you store the reference to a variable. So, when you re-use it, it will also change the contents of the referenced variable.
foreach($items as &$item) // You have $item here, prefixed with &.
{
$item['percentage'] = $item['value'] * 0.75;
}
var_dump($items);
foreach($items as $item) // And your re-use it here.
{
var_dump($item);
}
To solve this, add unset($item) in the 1st loop:
foreach($items as &$item)
{
$item['percentage'] = $item['value'] * 0.75;
unset($item); // add this, because you use &$item previously.
}

Reference in the foreach

Having the reference in the foreach, what does it mean and what the benefit?
For example:
foreach ($delivery as &$store) {
$store = ClassName::FunctionName($store);
}
unset($store);
I never really use reference when I do some coding in PHP.
If you don't pass by reference into the foreach loop, any changes and updates won't automatically be retained in the initial data structure after the loop has completed.
For example:
$test = array('cat'=>'meow','dog'=>'woof');
foreach($test as $a){
$a='test';
}
print_r($test);
In this case, the array will still contain:
array('cat'=>'meow','dog'=>'woof');
However in this example using references:
$test=array('cat'=>'meow','dog'=>'woof');
foreach($test as &$a){
$a='test';
}
var_dump($test);
...the array will contain:
array('cat'=>'test','dog'=>'test');

Assigning a new Index to an array causes a new array creation?

I was working OpenCart and added such code to Controller to show all the manufacturers to User:
$this->load->model("catalog/manufacturer");
$manufacturers = $this->model_catalog_manufacturer->getManufacturers();
$allbrands = array();
foreach ($manufacturers as $brand)
{
$brand["url"] = $this->url->link("product/manufacturer/product&manufacturer_id=".(string) $brand["manufacturer_id"],"","SSL");
$allbrands[] = $brand;
}
$this->data["manufacturers"] = $allbrands;
It worked just fine but my previous code didn't work which is below:
$this->load->model("catalog/manufacturer");
$manufacturers = $this->model_catalog_manufacturer->getManufacturers();
$allbrands = array();
foreach ($manufacturers as $brand)
{
$brand["url"] = $this->url->link("product/manufacturer/product&manufacturer_id=".(string) $brand["manufacturer_id"],"","SSL");
}
$this->data["manufacturers"] = $manufactures;
What I was thinking is arrays are objects so they are pointed at references so if I change $brand variable then $manufacturers will also have arrays that have "url" as index but didn't work and PHP complains that it doesn't have any "url" index.
Assigning a new index to an array cause it to be recreated with new object in the heap or it extends the current object's place in the heap?
Any ideas, what might it happen?
foreach [docs] is creating copies of the array values:
In order to be able to directly modify array elements within the loop precede $value with &. In that case the value will be assigned by reference.
and
Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().
This should work:
foreach ($manufacturers as &$brand) {
$brand["url"] = $this->url->link("product/manufacturer/product&manufacturer_id=".(string) $brand["manufacturer_id"],"","SSL");
}
unset($brand);
foreach creates a temporary copy of the objects. Its not a good idea to modify the arrays being referenced in the foreach inside the loop.
You should use pointers to do the modification inside the loop.
Here is an example copied from the docs.
<?php
$arr = array(1, 2, 3, 4);
foreach ($arr as &$value) {
$value = $value * 2;
}
// $arr is now array(2, 4, 6, 8)
unset($value); // break the reference with the last element
?>

Categories