group array of php objects by object property - php

I have a php object (book) with 3 properties: name, category, description
I then have a list of these book objects in an array.
I want to create a new associative array for these objects grouped by category.
Say I have 4 book objects in an array called $books
name category description
==================================
book1 cat1 this is book 1
book2 cat1 this is book 2
book3 cat2 this is book 3
book4 cat3 this is book 4
how can I create an associative array called $categories
$categories['cat1'] = array(book1, book2)
$categories['cat2'] = array(book2)
$categories['cat3'] = array(book3)
where book? is the book object and not the word

Like this:
foreach($books as $book)
{
$categories[$book->category][] = $book;
}

Simply loop the array of objects into a new array with the key being the category:
$newArray = array();
foreach($array as $entity)
{
if(!isset($newArray[$entity->category]))
{
$newArray[$entity->category] = array();
}
$newArray[$entity->category][] = $entity;
}
Is this what you was looking for ?
Explanation of the code:
/*
* Create a new blank array, to store the organized data in.
*/
$newArray = array();
/*
* Start looping your original dataset
*/
foreach($array as $entity)
{
/*
* If the key in the new array does not exists, set it to a blank array.
*/
if(!isset($newArray[$entity->category]))
{
$newArray[$entity->category] = array();
}
/*
* Add a new item to the array, making shore it falls into the correct category / key
*/
$newArray[$entity->category][] = $entity;
}

You can do it with ouzo goodies:
$categories = Arrays::groupBy($books, Functions::extractField('category'));
See: http://ouzo.readthedocs.org/en/latest/utils/arrays.html#groupby

$categories = array();
for ($i = 0; $i < count($books); $i++){
if (isset($categories[$books[$i]->category]) == false)
$categories[$books[$i]->category] = array();
$categories[$books[$i]->category][] = $books[$i]
}
cheers

Try this:
$categories = array();
foreach ($books as $book){
if (!array_key_exists($book->category , $categories))
$categories[$book->category] = array();
$categories[$book->category][] = $book;
}

This should works:
$categories = array();
foreach ($books as $book) {
$categories[$book['category']][] = $book;
}

I had a similar problem, but a bit more complicated with wordpress and metavalues/metakeys (where $results was an array of associative arrays fetched from a $wpdb->get_results() query.
This was my solution adapted to your problem:
$categories = array();
foreach ($results as $row) {
$id = $row['category'];
$description = $row['category'];
$name = $row['name']
if (!isset($categories[$id])) {
$categories[$id] = array();
}
$categories[$id] = array_merge($categories[$id], 'description'=>$description , 'name'=>$name);
}
Then you can run another for loop to get each array from the categories array:
foreach ($categories as $category) {
var_dump($category);
}

If you want to group objects by getting key from specific method with or without additional arguments, you can use this library method:
Arr::groupObjects(array $objects, string $method, ...$args): array

Related

Merge multiple object in foreach

I have one or more object in foreach and I want to merge all the objects in one in $refJSON.
$refObj = (object) array();
foreach($items as $item) { //here Im looping Two items
$refObj->refId = $item->getId();
$refObj->refLastName = $item->getLastName();
$refObj->refPhone = $item->getPhone();
$orderObj->refEmail = $item->getEmail();
}
$refJSON = json_encode($orderObj);
var_dump($refJSON);
Output :
//just the last item object
string(92) "{
"refId":"2",
"refLastName":"Joe",
"refPhone":"xxxxxxx",
"refEmail":"example#domaine.com"
}"
The output expected is to merge all the items ids 1 and 2 something like this:
[
{
"refId":"1",
"refLastName":"Steve",
"refPhone":"xxxxxxx",
"refEmail":"foo#domaine.com"
},
{
"refId":"2",
"refLastName":"Joe",
"refPhone":"xxxxxxx",
"refEmail":"example#domaine.com"
}
]
You are just overwriting the same object each time. Build each object and add this to an array (using []) and encode the result...
$refOut = array();
foreach($items as $item) { //here Im looping Two items
$refOut[] = ['refId' => $item->getId(),
'refLastName' => $item->getLastName(),
'refPhone' => $item->getPhone(),
'refEmail' => $item->getEmail()];
}
$refJSON = json_encode($refOut);

How to store product quantities in an array

I have a data array that totals all the items in the cart for all the products as one number.
I've been trying to figure out a way to get a data array count() all the different totals of all the different items in the cart and have them presented in my data layer comma separated. I hope that makes sense.
if ($order->getId()) {
$items = $order->getAllVisibleItems();
$itemIds = array();
$itemNames = array();
$itemPrices = array();
$itemMargins = array();
$itemTypes = array();
$itemGenders = array();
$itemSports = array();
$itemCategoryIds = array();
$itemCategoryNames = array();
/** #var Mage_Sales_Model_Quote_Item $item */
foreach ($items as $item) {
// Get the parent item - it is NOT included in the quote due to
// customizations made by the OrganicInternet module for simple
// product pricing. So I had to come up with another way to get it.
$options = $item->getProductOptions();
$parent = $item->getProduct();
if (array_key_exists('info_buyRequest', $options)) {
if (array_key_exists('cpid', $options['info_buyRequest'])) {
$parentId = $options['info_buyRequest']['cpid'];
$parent = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('name')
->addAttributeToSelect('season')
->addAttributeToSelect('gender')
->addAttributeToSelect('sport')
->addAttributeToFilter('entity_id', $parentId)
->getFirstItem();
}
}
$itemIds[] = $item->getSku();
$itemNames[] = $parent->getName();
$itemPrices[] = $item->getBasePrice() ?: 0;
$itemMargins[] = $this->_calculateMargin($parent, null, $item);
$itemTypes[] = $parent->getAttributeText('season');
$itemGenders[] = $parent->getAttributeText('gender');
$itemSports[] = $parent->getAttributeText('sport') ?: 'Other';
$categories = $this->_getAllCategoryIdsAndNames($item->getProduct());
$itemCategoryIds[] = $categories['id'];
$itemCategoryNames[] = $categories['name'];
}
// # Products
$data['u1'] = count($items);
The above will return:
dataLayer = [{"visitorLoginState":"Logged out","visitorType":"NOT LOGGED IN","visitorLifetimeValue":0,"visitorExistingCustomer":"No","u1":2,"u2":["889623392590","889623135517"]
It shows a total of 2 products for the U1 variable and the two sku's for the u2 variable in the data array.
If i have multiple products for the first sku i want it to seperate the quantities. ie.. "u1":1,1,3
Would i use array_sumor some type of multi-dimensional array to acquire my needs?
If i have multiple products for the first sku i want it to seperate
the quantities. ie.. "u1":1,1,3
It is not exactly clear to me is the relationship between sku and product and which variables in your array refer to which. I make the following presumptions:
1) A product is equivalent to one $items element
2) A sku is a unique $itemIds[] value
I use the array key as a simple way to keep track for each unique sku and the value to keep track of the product count for the sku.
if ($order->getId()) {
$items = $order->getAllVisibleItems();
$itemIds = array();
$itemNames = array();
$itemPrices = array();
$itemMargins = array();
$itemTypes = array();
$itemGenders = array();
$itemSports = array();
$itemCategoryIds = array();
$itemCategoryNames = array();
// My addition (UPDATE: fixed to the correct variable name)
$uniqueItemIds = array();
/** #var Mage_Sales_Model_Quote_Item $item */
foreach ($items as $item) {
// Get the parent item - it is NOT included in the quote due to
// customizations made by the OrganicInternet module for simple
// product pricing. So I had to come up with another way to get it.
$options = $item->getProductOptions();
$parent = $item->getProduct();
if (array_key_exists('info_buyRequest', $options)) {
if (array_key_exists('cpid', $options['info_buyRequest'])) {
$parentId = $options['info_buyRequest']['cpid'];
$parent = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('name')
->addAttributeToSelect('season')
->addAttributeToSelect('gender')
->addAttributeToSelect('sport')
->addAttributeToFilter('entity_id', $parentId)
->getFirstItem();
}
}
// *******************************
// My addition / changes
$sku = $item->getSku();
$itemIds[] = $sku; // I don't use this but keep $itemIds for compatibility
// use the array key to track counts for each sku
if (!isset($uniqueItemIds[$sku])){
$uniqueItemIds[$sku] = 1; // UPDATE: fixed to start at 1 not 0
} else {
$uniqueItemIds[$sku]++;
}
// *******************************
$itemNames[] = $parent->getName();
$itemPrices[] = $item->getBasePrice() ?: 0;
$itemMargins[] = $this->_calculateMargin($parent, null, $item);
$itemTypes[] = $parent->getAttributeText('season');
$itemGenders[] = $parent->getAttributeText('gender');
$itemSports[] = $parent->getAttributeText('sport') ?: 'Other';
$categories = $this->_getAllCategoryIdsAndNames($item->getProduct());
$itemCategoryIds[] = $categories['id'];
$itemCategoryNames[] = $categories['name'];
}
// show # Products
// "u1":1,1,3 NOTE: this should be a string => "u1":"1,1,3"
$data['u1'] = "";
foreach ($uniqueItemIds as $key => $val)
// show unique skus in u2
$data['u2'][] = $key;
// show counts for each sku in u1
if (strlen($data['u1'] == 0)){
$data['u1'] = (string)$value;
} else {
$data['u1'] .= ("," . $value);
}
}
How about something like...
if ($order->getId()) {
.....
.....
.....
/** #var Mage_Sales_Model_Quote_Item $item */
$sku_based_array = array();
foreach ($items as $item) {
......
......
......
$categories = $this->_getAllCategoryIdsAndNames($item->getProduct());
$itemCategoryIds[] = $categories['id'];
$itemCategoryNames[] = $categories['name'];
if (isset($sku_based_array[$item->getSku()])) {
$sku_based_array[$item->getSku()] = $sku_based_array[$item->getSku()]++;
} else {
$sku_based_array[$item->getSku()] = 1;
}
}
// # Products
$data['u1'] = array_values($sku_based_array);
Looking at the code it looks like it will only every return one product as the $parent variable is overwritten to get a first item. I have added a new variable named $itemProductCounts this will be returned to the output $data array as itemProductCounts I suspect this will always equal one.
<?php
if ($order->getId()) {
$items = $order->getAllVisibleItems();
$itemIds = array();
$itemNames = array();
$itemPrices = array();
$itemMargins = array();
$itemTypes = array();
$itemGenders = array();
$itemSports = array();
$itemCategoryIds = array();
$itemCategoryNames = array();
$itemProductCounts = array();
/** #var Mage_Sales_Model_Quote_Item $item */
foreach ($items as $item) {
// Get the parent item - it is NOT included in the quote due to
// customizations made by the OrganicInternet module for simple
// product pricing. So I had to come up with another way to get it.
$options = $item->getProductOptions();
$parent = $item->getProduct();
if (array_key_exists('info_buyRequest', $options)) {
if (array_key_exists('cpid', $options['info_buyRequest'])) {
$parentId = $options['info_buyRequest']['cpid'];
$parent = Mage::getModel('catalog/product')->getCollection()
->addAttributeToSelect('name')
->addAttributeToSelect('season')
->addAttributeToSelect('gender')
->addAttributeToSelect('sport')
->addAttributeToFilter('entity_id', $parentId)
->getFirstItem();
}
}
$itemIds[] = $item->getSku();
$itemNames[] = $parent->getName();
$itemPrices[] = $item->getBasePrice() ?: 0;
$itemMargins[] = $this->_calculateMargin($parent, null, $item);
$itemTypes[] = $parent->getAttributeText('season');
$itemGenders[] = $parent->getAttributeText('gender');
$itemSports[] = $parent->getAttributeText('sport') ?: 'Other';
$categories = $this->_getAllCategoryIdsAndNames($item->getProduct());
$itemCategoryIds[] = $categories['id'];
$itemCategoryNames[] = $categories['name'];
$itemProductCounts[$item->getSku()] = count($parent);
}
// # Products
$data['u1'] = count($items);
$data['itemProductCounts'] = $itemProductCounts;
With that all being said, the code above should get you close to what you need, you should replace the line $itemProductCounts[$item->getSku()] = count($parent); with the correct array with the product counts for that SKU.
Part of the issue with your data here is that everything in an $item is hidden behind an accessor. Rather than creating multitudes of arrays, I would suggest either creating a new object to house the information, or just modifying the $item directly.
Messing with the object directly has the risk of you accidentally using a variable name that exists in a protected or private scope though, so probably best to use your own, like so.
if ($order->getId()) {
$items = $order->getAllVisibleItems();
// only need one array, no need for all data points to have their own
$myItems = [];
/** #var Mage_Sales_Model_Quote_Item $item */
foreach ($items as $item) {
// basic shell
$myItem = [];
// get $options and $parent
// ...
// build your own data object
$myItem['sku'] = $item->getSku();
$myItem['name'] = $parent->getName();
$myItem['price'] = $item->getBasePrice() ?: 0;
$myItem['margin'] = $this->_calculateMargin($parent, null, $item);
$myItem['type'] = $parent->getAttributeText('season');
$myItem['gender'] = $parent->getAttributeText('gender');
$myItem['sport'] = $parent->getAttributeText('sport') ?: 'Other';
$categories = $this->_getAllCategoryIdsAndNames($item->getProduct());
$myItem['categoryId'] = $categories['id'];
$myItem['categoryName'] = $categories['name'];
$myItems[] = $myItem;
}
// At this point, $myItems is manipulable by all the array_* functions
// number of items e.g. 3
$data['u1'] = count($myItems);
// array of skus e.g. ["889623392590","889623392590","889623135517"]
// note: can use objects for $myItem if on PHP 7
// if you like -> notation better (in the loop)
$skus = array_column($myItems, 'sku');
// array of skus with counts e.g. ["889623392590" => 2, "889623135517" => 1]
$skus_with_counts = array_count_values($skus);
// just the counts (assuming indexes on other arrays must match) e.g. [2, 1]
// note: might be useful if you want to keep the counts as an array in dataLayer
$sku_counts = array_values($skus_with_counts);
// if you want this as a comma-separated list for u1, e.g. "2,1"
// note: will also work if you implode $skus_with_counts
$data['u1'] = implode(',', $sku_counts);
// get a list of unique SKUs (both will work), e.g. ["889623392590","889623135517"]
$data['u2'] = array_unique($skus);
$data['u2'] = array_keys($skus_with_counts);
}
Most of these kinds of PHP functions will work on your other data types as well if you want to do counting and clustering, and as you point out, you can run sum operations over them as well if you wish.
PHP array manipulation references: array_column, array_count_values, array_values, implode, array_unique, array_keys.
As a sidebar, Mage_Sales_Model_Quote_Item does have a getParentItemId() method available and a getQtyOptions method, which returns both the quantity and the product model.
I think you are mixing things.
In a simple sistem you should have:
Order has an array of OrderedItems
Each OrderedItem stores ProductObject and OrderedQuantity
And the ProductObject contains all product data.
So in your example instead of counting SKUs you must have $item->quantity field and you should work with that when you add/delete/edit order contents.

Nesting Foreach Loops in PHP

I have a class that has this type of structure:
Class League
Array Teams
Class Teams
Array Players
Class Players
String name
However, if I want to get a list of all players in the league, this doesn't seem to work:
foreach ($league->teams->players as $player) {
echo $player->name;
}
What am I missing? Do you have to use two foreach loops?
See this example:
<?php
//Create your players
$player1 = new stdClass;
$player2 = new stdClass;
$player3 = new stdClass;
$player1->name = 'Mike';
$player2->name = 'Luke';
$player3->name = 'Smith';
//Create your teams
$team1 = new stdClass;
$team2 = new stdClass;
//Adding the players to their teams
$team1->Players = array($player1, $player2);
$team2->Players = array($player3);
//Create the league
$league = new stdClass;
//Adding the teams to the league
$league->Teams = array($team1, $team2);
//For each element in the Teams array get the team in $team
foreach ($league->Teams as $teams) {
//For each element in the Players array get the player in $player
foreach($teams->Players as $player) {
//Print the name
echo $player->name . "<br>\n";
}
}
?>
Output:
Mike
Luke
Smith
So those are three separate classes, not a single class. You have shown nothing about how you relate those classes together and how you actually create the data structure. I don't see how you think you will be able to magically be able to list all players with a call such as $league->teams->players without having specific methods within each of the class to deal with aggregation from data stored in nested objects.
Without defining these relationships in your classes, you would need to do nested loops like this:
foreach ($league->Teams as $team) {
foreach($team->Players as $player) {
echo $player->name;
}
}
If you want methods to, for example list all players at the league level, you would need to define a methods to do this. Perhaps something like:
In team class:
public function function get_all_players() {
$return = array();
if(count($this->Players) > 0) {
$return = $this->Players;
}
return $return;
}
In league class:
public function get_all_players() {
$return = array();
if(count($this->Teams) > 0) {
foreach($this->Teams as $team) {
$return = array_merge($return, $team->get_all_players());
}
}
return $return;
}
Usage would be:
foreach($league->get_all_players() as $player) {
echo $player->name;
}
You may need two nested foreach
foreach ($league->teams as $team;){
foreach ($team->players as $player){
$list[] = $player->name;
}
}

PHP foreach Loop Element Index

I have an XPath query that gets Genres of a movie.
$genreXpath = $xml_data->xpath("//category");
I get the attributes from $genreXpath like this
$genreName=array();
$genresID=array();
$i=0;
foreach($genreXpath as $node) {
$genre = $node->attributes();
$genreName[$i] = $node["name"];
$genresID[$i] = $node["id"];
$i++;
}
I'm going to be writing these values to a Db hence the two different arrays.
This code works but I know there has to be a better way of doing this be it with a 2 d array, not using a $i counter or something more obvious that I haven't figured out....any pointers???
foreach($genreXpath as $i=>$node) { //note $i is your index of the current $node
$genre = $node->attributes();
$genreName[$i] = $node["name"];
$genresID[$i] = $node["id"];
}
It auto increments and you do not need to declare it above.
Use foreach($genreXpath as $key => $node) {
If you looking to a multidimensional you could do:
$genres = array();
foreach($genreXpath as $node) {
$genre = $node->attributes();
$genres[] = array($node["name"], $node["id"]);
}

how to loop over looped elements in Php?

I'm creating a tree-structure of categories with parentid's which can be called from children in such a way:
ID | Name | ParentID
1 1 0
2 2 1
3 3 2
4 4 1
Resulting in this:
1 = 1
2 = 1 -> 2
3 = 1 -> 2 -> 3
4 = 1 -> 4
which means 3 is a child of 2, which is a child of 1.
when trying to get this idea (with the -> to show what relations are set) I only get to the second grade (1 -> 2) but not to the third (1->2->3) because of the looping function I use for it.
//put all ID's in an array
while ($row2 = $connector->fetchArray($result2)){
$id = $row2['ID'];
$parents[$id] = $row2['name'];
}
// show the tree-structure
while ($row = $connector->fetchArray($result)){
if($row['parentid']!=0)echo $parents[$row['parentid']].' -> ';
echo $row['name'].' - ';
echo '<br>';
}
I'd like two things to change:
have the code automatically generate a tree sized as necessary.
in the while-loops i have to select the $result twice (once as $result, once as $result2) to make it work. these $result's have exactly the same database-query:SELECT ID,name,parentid FROM categories
to fetch results from. I'd like to only declare this once.
Thanks for all the good answers. I've gone with the easiest, less-code-to-implement approach:
$result = $connector->query('SELECT ID,name,parentid FROM categories');
// Get an array containing the results.
$parents = array();
while ($row = $connector->fetchArray($result)){
$id = $row['ID'];
$parents[$id] = array('ID' => $row['ID'],'name' => $row['name'],'parentid' => $row['parentid']);
}
foreach ($parents as $id => $row){
$pid=$id;
$arrTmp= array();
do { // iterate through all parents until top is reached
$arrTmp[]=$pid;
$pid = $parents[$pid]['parentid'];
}while ($pid != 0);
$arrTmp = array_reverse($arrTmp);
foreach($arrTmp as $id){
echo $parents[$id]['name'].' -> ';
}
echo '<br>';
}
Rather than have PHP organize the items into a tree, why not ask the database to do it for you? I found this article on hierarchical data to be very good and the examples are almost identical to yours.
EDIT
The SQL for getting the full tree using the Adjacency Model is not ideal. As the article explains it requires rather a lot of joins for even a small hierarchy. Is it not possible for you to use the Nested Set approach? The SQL stays the same regardless of the size of the hierarchy and INSERT and DELETE shouldn't be very difficult either.
If you really want to do hierachies with parent ids(suitable only for small number of items/hierachies)
I modified your code a little bit(I did not test it so there may be some syntax errors):
//put all recordsets in an array to save second query
while ($row2 = $connector->fetchArray($result2)){
$id = $row2['ID'];
$parents[$id] = array('name' => $row2['name'],'parent' => $row2['parentid']);
}
// show the tree-structure
foreach ($parents as $id => $row){
$pid = $row['parentid'];
while ($pid != 0){ // iterate through all parents until top is reached
echo $parents[$pid]['name'].' -> ';
$pid = $parents[$pid]['parentid'];
}
echo $parents[$id]['name'].' - ';
echo '<br>';
}
To answer your comment:
$parents = array();
$parents[2] = array('ID'=>2,'name'=>'General','parentid'=>0);
$parents[3] = array('ID'=>3,'name'=>'Gadgets','parentid'=>2);
$parents[4] = array('ID'=>4,'name'=>'iPhone','parentid'=>3);
foreach ($parents as $id => $row){
$pid=$id;
$arrTmp= array();
do { // iterate through all parents until top is reached
$arrTmp[]=$pid;
$pid = $parents[$pid]['parentid'];
}while ($pid != 0);
$arrTmp = array_reverse($arrTmp);
foreach($arrTmp as $id){
echo $parents[$id]['name'].' -> ';
}
echo '<br>';
}
Prints out:
General ->
General -> Gadgets ->
General -> Gadgets -> iPhone ->
Maybe easier with OOP. Just sort the query by parentId
Note: The listChildren method and the printout at the bottom is just there to show it is listed correctly. I did not interpret the question that the display was important.
class Element {
public $id;
public $name;
public $parent = null;
public $children = array();
public function __construct($id, $name)
{
$this->id = $id;
$this->name = $name;
}
public function addChild($element)
{
$this->children[$element->id] = $element;
$element->setParent($this);
}
public function setParent($element)
{
$this->parent = $element;
}
public function hasChildren()
{
return !empty($this->children);
}
public function listChildren()
{
if (empty($this->children)) {
return null;
}
$out = array();
foreach ($this->children as $child) {
$data = $child->id . ':' . $child->name;
$subChildren = $child->listChildren();
if ($subChildren !== null) {
$data .= '[' . $subChildren . ']';
}
$out[] = $data;
}
return implode(',', $out);
}
}
$elements = array();
$noParents = array();
while ($row = $connector->fetchArray($result)) {
$elements[$row['id']] = $element = new Element($row['id'], $row['name']);
if (isset($elements[$row['parent']])) {
$elements[$row['parent']]->addChild($element);
} else {
$noParents[] = $element;
}
}
foreach ($noParents as $element) {
if ($element->hasChildren()) {
echo "Element {$element->id} has children {$element->listChildren()}.\n";
} else {
echo "Element {$element->id} has no children.\n";
}
}
If you are using PostgreSQL as the database, you can use the connectby() function to create the record set:
SELECT *
FROM connectby('tableName', 'id', 'parent_id')
AS t(keyid text, parent_keyid text, level int);
I love this function, and use all the time in my code. It can do some very powerful things, very quickly, and you don't have maintain the left/right values like the (adjacency model).

Categories