PHP: array values lost after for each - php

first let me explain what I am trying to achieve:
I've got an array of user items, consisting of an ID(item_id) and quantity(e.g. 10 items)
If a user purchases an item, it's added to the array including the quantity.
If a user purchases an (in the array) existing item, '1' is added to the quantity.
I got really close with the help of this post: Checking if array value exists in a PHP multidimensional array
this is the code I am using right now:
$item_id = arg(1);
$quantity = '1';
$found = false;
$bought_items = null;
$data = null;
foreach ($user_items as $key => $data) {
if ($data['a'] == $item_id) {
// The item has been found => add the new points to the existing ones
$data['b'] += 1;
$found = true;
break; // no need to loop anymore, as we have found the item => exit the loop
}
}
if ($found === false) {
$bought_items = array('a' => $item_id, 'b' => $quantity);
}
$array = array($bought_items, $data);
If the item_id is non existing, it is added to the array
If the item_id is existing, the quantity will 'receive' +1
so far so good
now the actual problem, let's sketch the scenario:
I purchase item 500 -> array contains: id=500, quantity=1
I purchase item 500 -> array contains: id=500, quantity=2
I purchase item 600 -> array contains: id=500, quantity=2, id=600, quantity=1
after this it goes wrong
I then purchase item 500 or 600, the other item is removed from the array.
So when I purchase item 500, item 600 and its quantities are removed from the array.
I've been puzzling for hours but can't find the mistake, I know I'm overlooking something logical. I think it's going wrong in the for each.

If bought_items is an array then you're overriding your values rather then adding them to the array.
$bought_items = array('a' => $item_id, 'b' => $quantity);
should be:
$bought_items[] = array('a' => $item_id, 'b' => $quantity);

I tried for example this, and it works, so u can change to own use. The code of another post is useless for your purpose
$item_id = 500;
$quantity = 1;
$user_items = array(400, 300, 200, 500, 500, 200, 500, 500);
$found = FALSE;
$bought_items = null;
$data = null;
foreach ($user_items as $data) {
if ($data == $item_id) {
// The item has been found => add the new points to the existing ones
$quantity += 1;
$bought_items[$data]['a'] = $data;
$bought_items[$data]['b'] = $quantity;
$found = TRUE;
}
if ($found === FALSE) {
$bought_items[$data] = array('a' => $data, 'b' => $quantity);
}
$found = FALSE;
}
print_r($bought_items);
Output:
array(4) {
400 => array(2) {
a => 400
b => 1
}
300 => array(2) {
a => 300
b => 1
}
200 => array(2) {
a => 200
b => 3
}
500 => array(2) {
a => 500
b => 5
}
}

Related

How to find pages without parent page?

I have a parent/child structure where the it can happen that parent can be deleted, and it's children are still going to be in the database. If that happen, the lowest parent should be set parent of 0.
I'm stuck with this problem because I'm not sure how to structure my (possibly recursive) loop.
I need to return an array of page ID's which parents do not exist; example: array(5, 9, 8);
This is my data set, and the structure can be connected through the parent id; we can see that page ID 8 and 9 have parent of 7 which does not exist:
evar_export($orphans($pages));
$data = array (
0 => array (
'id' => 1,
'url' => 'Home-Page',
'parent' => 0
),
1 => array (
'id' => 2,
'url' => 'page1',
'parent' => 1
),
4 => array (
'id' => 5,
'url' => 'page4',
'parent' => 4
),
5 => array (
'id' => 6,
'url' => 'page5',
'parent' => 5
),
6 => array (
'id' => 8,
'url' => 'no-parent-1',
'parent' => 7
),
7 => array (
'id' => 9,
'url' => 'no-parent-2',
'parent' => 7
)
);
I've tried recursion, but I don't know how to catch the end of the sub-tree:
$orphans = function($array, $temp = array(), $index = 0, $parent = 0, $owner = 0) use(&$orphans) {
foreach ($array as $p) {
if($index == 0) {
$owner = $p['id'];
}
if ($index == 0 || $p['id'] == $parent) {
$temp[] = $p['id'];
$result = $orphans($array, $temp, $index + 1, $p['parent'], $owner);
if (isset($result)) {
return $result;
}
}
else {
return $temp;
}
}
};
I named your data array "pages" for this example:
$orphans = array();
foreach($pages as $p)
{
if($p['parent'] == 0)
continue; //End this iteration and move on.
$id = $p['id'];
$parent = $p['parent'];
$parentExists = false;
foreach($pages as $p2)
{
if( $p2['id'] == $parent )
{
$parentExists = true;
break; //Found, so stop looking.
}
}
if(!$parentExists)
{
$orphans[] = $id;
}
}
If you var_dump the $orphans array after this runs, you would get:
array(2) {
[0]=>
int(8)
[1]=>
int(9)
}
Which appears to be the desired result. Unfortunately nesting another foreach within the foreach is required unless you modify your data structure so the IDs are the keys (which I would advise to reduce resource usage to process this). Using the continue / break control structures at least limits usage.
Clarification on Nested Foreach
An ideal data structure would use key value pairs over sequential items, especially when processing dynamic data, because the keys are unknown. Taking your data for example, getting the 4th item's URL is easy:
$id = $pages[4]['id'];
But there is no relational / logical association between the 4th item and the associated data. Its sequential based on the what ever built the data. If, instead, you assign the id as the key, then we could easily find the parent id of the page with id 4:
$parent = $pages[4]['parent'];
So when doing a simple parse of your data to find non-existing parents, you would just have to do this:
foreach($pages as $p)
{
if($p['parent'] == 0)
continue; //End this iteration and move on.
$id = $p['id'];
if(! isset($pages[$p['parent']])
{
$orphans[] = $id;
}
}
Because then we would know for sure that the key is the id and then logically process the data in that fashion. And considering something like a page id is a primary key (non-duplicate), this should be entirely possible.
But without having a logical association between the key and value in the array, we have to look at the entire data set to find matches for each iteration, causing an exponential explosion of resource usage to complete the task.

Compare array values with others values from the same array

What I’m trying to achieve is that, it will loop trough the array. Then it will look if the items in the array are the same on three points: product_id, the size value and the color value.
I want to create a new array where the items are listed, the only thing I don’t want is the duplicated values. I want that the duplicated values if they are the same on those three points that the quantity will be count together. Like if I have 3 items same product id same size and same color and both of the three I ordered 3 items in my new array this is just standing 1 time and the quantity will be 9. So there will be no duplicated values in my new array.
Current loop
foreach($orders as $key => $order){
foreach($order['orderProducts'] as $key => $value){
echo '<pre>';
print_r($value['attributes']);
echo '</pre>';
}
}
results in the the following array
Array
(
[id] => 2
[product_id] => 4
[order_id] => 2
[name] => swag3
[description] => haha
[price] => 19.95
[proceeds] => 10.00
[quantity] => 2
[attributes] => [{"id":1,"name":"Size","value":"XS","active":1},{"id":8,"name":"Color","value":"Wit","active":1}]
)
Array
(
[id] => 3
[product_id] => 3
[order_id] => 3
[name] => swag2
[description] => lol
[price] => 19.95
[proceeds] => 10.00
[quantity] => 2
[attributes] => [{"id":2,"name":"Size","value":"S","active":1},{"id":7,"name":"Color","value":"Zwart","active":1}]
)
Array
(
[id] => 4
[product_id] => 3
[order_id] => 4
[name] => swag2
[description] => lol
[price] => 19.95
[proceeds] => 10.00
[quantity] => 1
[attributes] => [{"id":2,"name":"Size","value":"S","active":1},{"id":7,"name":"Color","value":"Zwart","active":1}]
)
Sort of what I’m looking for..
Array
(
[id] => 2
[product_id] => 4
[order_id] => 2
[name] => swag3
[description] => haha
[price] => 19.95
[proceeds] => 10.00
[quantity] => 2
[attributes] => [{"id":1,"name":"Size","value":"XS","active":1},{"id":8,"name":"Color","value":"Wit","active":1}]
)
Array
(
[id] => 3
[product_id] => 3
[order_id] => 3
[name] => swag2
[description] => lol
[price] => 19.95
[proceeds] => 10.00
[quantity] => 3
[attributes] => [{"id":2,"name":"Size","value":"S","active":1},{"id":7,"name":"Color","value":"Zwart","active":1}]
)
Solution
Note it's blade php as frontend.
Backend
$order // is the array with products
$items = [];
foreach($orders as $key => $order){
foreach($order['orderProducts'] as $op){
$i = [
'product'=> Product::findOrFail($op->product_id)->toArray(),
'attributes' =>$op->attributes,
'quantity'=>$op->quantity
];
$matchedResult = false;
$count = count($items);
for($a = 0; $a < $count; $a++){
// Items with the same product_id in the $item array
if($items[$a]['product']['id'] == $i['product']['id']){
//check if the attributes are also the same
if($items[$a]['attributes'] === $i['attributes']){
// The attributes ar ethe same so up the quantity
$items[$a]['quantity'] += $i['quantity'];
$matchedResult = true;
continue; // If its right there are no other matches
}
}
}
if($matchedResult === false){
// only push item if there is not a match.
$items[] = $i;
}
}
}
Frontend
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Product</th>
<th>quantity</th>
</tr>
</thead>
<tbody>
#foreach($items as $item)
<tr>
<td>{{$item['product']['name']}}
#if(count($item['attributes']) > 0) <small>
#foreach($item['attributes'] as $att)
{{$att['name']}} - {{$att['value']}}
#endforeach
</small>
#endif</td>
<td>{{$item['quantity']}}</td>
</tr>
#endforeach
</tbody>
</table>
</div>
You can achieve your goal without using nested loops. You may use hash function of product_id, size and color parameters and use that value as a new array key like this:
$orders = // original array;
$newOrders = []; // new array
foreach($orders as $order) {
$pi = $order["product_id"]; // get product_id
$attr = json_decode($order["attributes"]); // get attributes:
$size = $attr[0]->value; // get size value
$color = $attr[1]->Color; // get color
$hash = sprintf("%s.%s.%s", $pi, $size, $color); // Calculate hash
if ($newOrders[$hash]) {
$newOrders[$hash].quantity++; // If hash is already present then just increase quantity
} else {
// Otherwise add new order
$newOrders[$hash] = [
"order" => $order,
"quantity" => 1
];
}
}
I hope this can help you:
$sortedArray = [];
foreach ($order as $array) {
$tag = getTag($array);
push_to_array($sortedArray,$array,$tag);
}
function push_to_array(&$array1,$array2,$tag)
{
isset($array1[$tag]) ? $array1[$tag]['quantity'] += $array2['quantity'] : $array1[$tag] = $array2;
}
function getTag($array)
{
$attribs = json_decode($array['attributes'],true);
foreach ($attribs as $value) {
($value['name'] =='Size' ) && $size = $value['value'];
($value['name'] =='Color') && $color= $value['value'];
}
return $array['product_id'].$size.$color;
}
Try this (untested but logic should be correct):
$orders = // original array;
$new; // new array
foreach($orders as $order) {
$pi = $order["product_id"]; // get product_id
$attr = json_decode($order["attributes"]); // get attributes:
$size = $attr[0]->value; // get size value
$color = $attr[1]->Color; // get color
$duplicate = false;
foreach($newOrders as $newOrder() { // loop through nested array
$newPi = $newOrder["product_id"];
$newAttr = json_decode($newOrder["attributes"]);
$newSize = $newAttr[0]->value;
$newValue = $newAttr[1]->Color;
// check to see if same
if(
$pi == $newPi &&
$size == $newSize &&
$color == $newColor
) {
$newOrders["quantity"]++;
$duplicate = true;
break;
}
}
if(!$duplicate) {
$new[] = $order;
}
}
Edit: Sorry, I just reread your post and saw you don't want a full solution. Sorry. But I hope this can show you that nested loops are the way to go with this. As mentioned in the comments, there is no built in function for this in PHP (AFAIK).
This is not a solution, but another approach to let you think by Object Oriented Programming
It will helps you a lot in you current and next problems
Now, you have a business case, that it must be resolved in your business layer
I can assist you if you want
<?php
class Attribute {
private $_id;
private $_name;
private $_value;
private $_active;
// TODO implement getter and setter
// lTODO implement constructor
}
class Product {
private $_id;
private $_productId;
// ... order_id, name, ...
private $_attribute_a = array(); // it will be an array of attribute's object
// TODO implement getter and setter
// TODO implement constructor
private function getAttributeByName($name) {
// loop through attribute array object and return the right attribute
// foreach ($this->_attribute_a as $attr) {
// if ($attr->name === $name) return $attr;
// }
}
public function equals($o) {
if (!is_a($o, 'Product')) return FALSE;
if ($this == $o) return TRUE ;
if ($this->_productId === $o->_productId) {
$attr1 = $this->getAttributeByName('Size');
$attr2 = $this->getAttributeByName('Size');
if ($attr1->getValue() !== $attr2->getValue()) return FALSE;
$attr1 = $this->getAttributeByName('Color');
$attr2 = $this->getAttributeByName('Color');
if ($attr1->getValue() !== $attr2->getValue()) return FALSE;
return TRUE;
}
return FALSE;
}
}
Now, you can compare easily 2 Products Object, and later, updating equals() will not affect your code
You have some grate answers from other users.
However i would like to post this for Googlers or other users in the planning stage and for your own knowledge.
With your example your using a shopping basket. You should never have duplicate items in the array you should be using a Quantity measure on the item and before adding to your array you check the array if the matching item is there increase the quantity.
as your current way your code is processing though the array after for no good reason if i was to add 20 of the same item your current system would have an array of 20 to loop though every time i opened the basket.
This other method will also provide you will support for people to add multiple quantities of items at once, also on your basket page edit the quantities
Please note the assumptions below the code
function combineDuplicates($orders) {
$indexArray = array();
$uniqueArray = array();
foreach($orders as $value) {
$productID = $value['product_id'];
$attributes = $value['attributes'];
foreach($attributes as $attribute) {
switch($attribute['name']) {
case 'Size' : $size = $attribute['value'];
break;
case 'Color': $color = $attribute['value'];
break;
default : break;
}
}
if (!isset($indexArray[$productID][$size][$color])) {
$indexArray[$productID][$size][$color]['count'] = 0;
$uniqueArray[] = $value;
}
$indexArray[$productID][$size][$color]['count']++;
}
$orders = array();
foreach($uniqueArray as $key => $value) {
$productID = $value['product_id'];
$attributes = $value['attributes'];
foreach($attributes as $attribute) {
switch($attribute['name']) {
case 'Size' : $size = $attribute['value'];
break;
case 'Color': $color = $attribute['value'];
break;
default : break;
}
}
$uniqueArray[$key]['quantity'] = $indexArray[$productID][$size][$color]['count'];
}
return $uniqueArray;
}
Assumptions :
'attributes' is converted to associative array
product_id, Color & Size values are non-empty in each element

Getting the highest element from an array

I have a query which gives me a tuple of a discount added into database.
Here's the query
$discount_info = $this->autoload_model->get_data_from_table("td_discount,td_userdiscount","*",
"td_discount.discount_id = td_userdiscount.discount_id
AND td_discount.discount_code = '$coupon'")->result_array();
Now i have script which does the specific function.
There will be a condition, if the value of a index will be 1, then the code snippet is like this
if($discount_info[0]['discount_on']=="3")
{
$discount_product = $discount_info[0]['discount_product']; // its an id(autoincrement value)//
if($discount_info[0]['applicable_type']==1)
{
$item_info = $this->autoload_model->get_data_From_table("td_product","*","product_id = '$discount_product'")->result_array();
foreach($this->cart->contents() as $ci)
{
if($ci['name'] = $item_info[0]['product_name']
{
// get the cart_item with the highest price if the product name matches//
}
}
}
}
My cart structure is like this
$data = array(
'id' => $id,
'qty' => $qty,
'price' => $price,
'name' => $name,
'options' => array(
'picture'=>$img,
'item_slug'=>$slug,
'item_color'=>$color,
'item_size'=>$size,
'unit_price'=>$price,
'order_type'=>$order_type,
'product_type'=>$pro_type,
'unit_discount' => 0.00,
'item_discount' => 0.00,
'discount_type' => '',
)
);
Now, its all set up, but I just can't get the login which I shall put over here
// get the cart_item with the highest price if the product name
I imagine you could just define a
$highest = array('price' => 0);
before the loop and then inside the loop go:
// get the cart_item with the highest price if the product name matches//
if ($ci['price'] > $highest['price']) {
$highest = $ci;
}
That way $highest would contain the best match at the end.

PHP update quantity in multidimensional array

My array looks like the following:
Array
(
[0] => Array
(
[index] => 0
[quantity] => 1
[0] => Array
(
[id_product] => 20
[title] => Oranges
)
)
[1] => Array
(
[index] => 1
[quantity] => 1
[0] => Array
(
[id_product] => 24
[title] => Bananas
)
)
)
To make this array, this is my code:
$i = 0;
$content = array();
if(isset($_SESSION['cart'])){
foreach($_SESSION['cart'] as $result){
foreach($result as $item){
$values = $product->getById($item['id']);
if($values != null){ // which means it has product
/*
Checks if the array already contains that ID
this avoids duplicated products
*/
if(main::search_multidimensional($content, "id_product", $item['id_product']) == null){
$content[] = array("index" => $i, "quantity" => 1, $values);
$i++;
}else{ /*
in case it does have already the id_product in the array
I should update the "quantity" according to the "index".
*/
}
}
}
}
}
return $content;
My problem is after the }else{. I've been trying some codes without any success. I have to update the quantity according to the index. Although if you guys think there's a better alternative please let me know.
Edit: Since most of the people is worried about the search_multidimensional and it might be my solution, here's the function:
public function search_multidimensional($array, $key, $value){
$results = array();
if (is_array($array)) {
if (isset($array[$key]) && $array[$key] == $value) {
$results[] = $array;
}
foreach ($array as $subarray) {
$results = array_merge($results, self::search_multidimensional($subarray, $key, $value));
}
}
return $results;
}
EDIT 2:
In that case, would this help? (Since your search_multidimensional only returns a true or false)
$i = 0;
$content = array();
if(isset($_SESSION['cart'])){
foreach($_SESSION['cart'] as $result){
foreach($result as $item){
$values = $product->getById($item['id']);
if($values != null) { // which means it has product
/*
Checks if the array already contains that ID
this avoids duplicated products
*/
$product_exists = false;
foreach($content as &$cItem) {
if($cItem['values']['id_product'] == $item['id_product']) {
$cItem['values']['quantity']++; // Increments the quantity by 1
$product_exists = true;
break;
}
}
// If the product does not exist in $content, add it in.
if(!$product_exists)
$content[] = array("index" => $i, "quantity" => 1, "values" => $values);
$i++;
}
}
}
}
(Edited again to give an array key to $values)
OLD ANSWER:
Since you are recreating the cart array in $content, you could just do this in your else:
$content[] = array("index" => $i, "quantity" => $result['quantity'] + 1, $values);
Such that it would show like this:
$i = 0;
$content = array();
if(isset($_SESSION['cart'])){
foreach($_SESSION['cart'] as $result){
foreach($result as $item){
$values = $product->getById($item['id']);
if($values != null){ // which means it has product
/*
Checks if the array already contains that ID
this avoids duplicated products
*/
if(main::search_multidimensional($content, "id_product", $item['id_product']) == null)
$content[] = array("index" => $i, "quantity" => 1, $values);
else
$content[] = array("index" => $i, "quantity" => $result['quantity'] + 1, $values); // Retrieve current quantity and adds 1
$i++;
}
}
}
}
(I'm assuming you are only increasing the quantity by 1)
Solved.
All I had to do was to forget the $i variable, since it wasn't actually doing something necessary. Since I have id_product, which is unique I need to work with it.
if($values != null){ // Only if it has results
// checks if array already contains or not the product ID
// if does not have, it will add
if(global_::search_multidimensional($content, "id_product", $item['id_product']) == null){
$content[] = array("index" => $item['id_product'], "quantity" => 1, $values);
// index is now the id of the product
}else{
// otherwise, loop all the elements and add +1
foreach($content as $key => $result){
if($item['id_product'] == $content[$key]['index']){
$content[$key]['quantity']++;
}
}
}
}
As your $content array has fixed structure (fixed number of levels) you don't need to use recursive function. Your search_multidimensional function could be much simpler. And it should return index of found element (if any) in the array:
function search_multidimensional($array, $key, $value) {
foreach ($array as $i => $el) {
foreach ($el as $j => $v) {
if (is_int($j) && isset($v[$key]) && $v[$key] == $value) return $i;
}
}
return false;
}
So the snippet building $content should be changed like this:
...
if (($index = search_multidimensional($content, "id_product", $item['id_product'])) === false) {
$content[] = array("index" => $i, "quantity" => 1, $values); $i++;
}
else {
$content[$index]['quantity']++;
}

deleting and changing values in multidimensional array php

I've got the following code to remove 1 from the qty when a remove button is pressed and if the qty=1 the item will be removed from the array at the specific index.
for example if the first item in the array has an ID of '1B' and qty of '5' and name 'item1' second item in the array has the ID of '2B' and qty of '3' and name 'item2' and the remove button for this item is pressed, the qty will change to 2(as required) but the id will change to 1B and the name to 'item1'. The same thing happens if there are more than 2 products in the $_SESSION["Cart"] array.
I'm not sure where i'm going wrong, but this is my code:
code for $_SESSION["Cart"]
$_SESSION["Cart"] = array(
array(
'name' => "namehere",
'id' => "idHere",
'qty' => 1,
'price' => "pricehere"
)
//more arrays here
);
Code for Removing item
$prodID = $_GET["removeProd"];
foreach ($_SESSION["Cart"] as $cartItem) {
//only continue if qty is more than one
//remove item if 0 qty
if ($cartItem["id"] == $prodID) {
if ($cartItem["qty"] > 1) {
$qty = $cartItem["qty"] - 1; //decrease qty by one
$cart[] = array(
'name' => $cartItem["name"],
'id' => $cartItem["id"],
'qty' => $qty,
'price' => $cartItem["price"]
);
} //end if
} else {
$cart[] = array(
'name' => $cartItem["name"],
'id' => $cartItem["id"],
'qty' => $cartItem["qty"],
'price' => $cartItem["price"]
);
} //end else
$_SESSION["Cart"] = $cart;
} //end foreach
The problem is that you're assigning $_SESSION['Cart'] = $cart on each iteration, so it will only ever contain the last item in the $_SESSION['Cart'] array. If you move it below the end of the foreach your code should work.
You could simplify this a bit by passing $cartItem by reference. That way you only modify array elements which match $prodID:
foreach ($_SESSION['Cart'] as $key => &$cartItem) {
if ($cartItem['id'] == $prodID) {
if ($cartItem['qty'] > 1) {
$cartItem['qty'] -= 1;
} else {
unset($_SESSION['Cart'][$key]);
}
}
}
unset($cartItem); // break the binding
Your code has some algorhithmic/logic flaws. This code should do what you need it to do. Please try to find out what it actually does, and where are the flaws in your approach.
foreach ($_SESSION["Cart"] as $key=>$cartItem) {
//only continue if qty is more than one
//remove item if 0 qty
if ($cartItem["id"] == $prodID) {
if ($cartItem["qty"] > 1) {
$qty = $cartItem["qty"]--;// does the same thing as x = x - 1; //decrease qty by one
$cart[$key]['qty'] = $qty;
} //end if
else {
unset($cart[$key]);
}
break;// ends foreach loop ( assuming there can be only one item of the same type in the cart )
}
} //end foreach
$_SESSION["Cart"] = $cart;

Categories