I have documents stored on my server, and their information like title, filepath, date, category, etc., stored in a database. My goal is to be able to group the documents together by their categories. The document's category is stored in the database as a string like "COVID-19 | Guidance | Mortuary Affairs", and I want to convert it to an array like the following:
[
"COVID-19" => [
"Guidance"=> [
"Mortuary Affairs" => [
// Where the document info will be
]
]
]
]
The first step would be looping through the documents and exploding each category by the | delimiter:
foreach($all_documents as $document)
{
$categories = array_map('trim', explode('|', $document['category']));
}
Doing so results in the following:
[
"COVID-19",
"Guidance",
"Mortuary Affairs"
]
How would I then take this array and turn it into the nested array displayed at the top?
After the creation of the array with categories, you could iterate those categories and "drill" down a result object with a node-reference -- creating any missing properties, and then referencing them. Finally append the document to the deepest node's array (so multiple documents can end up at the same place):
$result = [];
foreach ($all_documents as $document) {
$categories = array_map('trim', explode('|', $document['category']));
$node =& $result; // reference the "root"
$last = array_pop($categories);
foreach ($categories as $category) {
if (!isset($node[$category])) $node[$category] = []; // create child
$node =& $node[$category]; // change the reference to the child
}
$node[$last][] = $document; // append document to this list
}
At the end $result will have the desired hierarchical structure.
Related
I have a list of objects, each object could belong as a child of another object and each one has an element ('parent_id') that specifies which object it's a child of.
An example of what the expected tree should look like after children are properly imbedded:
Current Array
element 1
element 2
element 3
element 4
element 5
Organized into a hierarchy
-----------------------
element 2
-children:
element 1
-children
element 5
element 3
-children:
element 4
There should be two top elements, the first element would contain the element1 as a child, which would itself contain element 5 as a child. The third element would contain element 4 as a child.
The final result should produce a proper tree structure from it, however I'm running into an issue that when I generate the tree some of the elements are null.
From what I understand it is because of the weird scoping rules PHP has and that the last reference in the loop remains bound even after the end of the loop. So I included the unset($var) statements after each loop, however I'm still getting null values pushed into the tree.
Here is the fully contained example of the code:
$response = array(
array( "kind"=> "t1", "data" => array( "id" => 25, "parent_id" => 30 )),
array("kind"=> "t1", "data" => array( "id" => 30,"parent_id" => 0)),
array("kind"=> "t1", "data" => array("id" => 32, "parent_id" => 0 )),
array("kind"=> "t1", "data" => array("id" => 33,"parent_id" => 32)),
array("kind"=> "t1", "data" => array("id" => 35,"parent_id" => 25))
);
$json_str = json_encode($response);
$tree = array();
foreach($response as &$firstObj)
{
$parentFound = null;
foreach($response as &$secondObject)
{
if($firstObj['data']['parent_id'] == $secondObject['data']['id'])
{
$parentFound = &$secondObject;
break;
}
}
unset($secondObject);
if($parentFound)
{
$parentFound['data']['children'] = array(&$firstObj);
}
else{
$tree[] = $firstObj;
}
}
unset($firstObj);
print_r($tree);
The expected tree should contain only the topmost elements that are not children of other elements, the children should be embedded through references into the appropriate spaces of the top tree elements.
While I would probably opt for a recursive approach on a professional project, I've managed to produce a non-recursive approach using references like your posted code but with a single loop.
Code: (Demo)
$result = [];
foreach ($array as $obj) {
$id = $obj['data']['id'];
$parentId = $obj['data']['parent_id'];
if (isset($ref[$id])) { // child array populated before parent encountered
$obj['children'] = $ref[$id]['children']; // don't lose previously stored data
}
$ref[$id] = $obj;
if (!$parentId) {
$result[] = &$ref[$id]; // push top-level reference into tree
} else {
$ref[$parentId]['children'][] = &$ref[$id]; // push into parent-specific collection of references
}
}
var_export($result);
For a stacking process (not a recursive one) to build a hierarchical structure, your sample data is a little unexpected in that a child id integer is less than a parent id. I mean, in nature, parents are born first and then children are born. With your sample data, the input could not be pre-sorted by id value before looping -- this would have ensured that all parents where declared before children were encountered.
As a consequence, my snippet needs to push previously encountered children data into a newly encountered parent so that the declaration of the parent as a reference does not erase the cached children data.
I extended your sample data by one extra row while testing. If you find any breakages with my script, please supply new test data that exposes the issue.
I found the solution with the help of GPT/Bing:
So while I was unsetting the other variables, I wasn't unsetting the $parentFound, which has to be done as well.
Another thing was that I was not saving the item by reference when saving the item to the tree, which also has to be done in order for the whole reference tree to work.
The final code is:
$json_str = json_encode($response);
$tree = array();
foreach($response as &$firstObj)
{
$parentFound = null;
foreach($response as &$secondObject)
{
if($firstObj['data']['parent_id'] == $secondObject['data']['id'])
{
$parentFound = &$secondObject;
break;
}
}
if($parentFound)
{
$parentFound['data']['children'] = array(&$firstObj);
}
else{
$tree[] = &$firstObj; //has to be passed by reference
}
unset($secondObject);
unset($parentFound); //have to also include the $parentFound in the unset
}
unset($firstObj);
print_r($tree);
I have a multidimensional array, consisting of products. Each sub-array has a product type. The productType is is in an array inside the Product array, such that;
0 => product [
productType [
id: 2
]
]
1 => product [
productType [
id: 1
]
]
2 => product [
productType [
id: 2
]
]
]
I need to remove an entire array element, if the id already exists, in this example, I would need to remove EITHER array[0] or array[2], it doesn't matter as I only need the productType[id] to populate the box.
I have made a loop that creates an array of the ID's that already exist, but it involves making 2 new arrays:
//This works but seems a bit inefficient
$productFinal = [];
$ids = [];
foreach ($products as $product) {
if (!in_array($product->getproductType()->getid(), $ids)) {
$productFinal[] = $product;
}
$ids[] = $product->getproductType()->getid();
}
I get the results I want, however I am sure that there is a more efficient way to do this, ideally using an inbuilt php function.
If you also get the key of each element, you could remove the element if necessary inside the foreach-loop:
$ids = [];
foreach ($products as $key => $product {
$id = $product->getproductType()->getid();
if (in_array($id, $ids)) {
unset($product[$key];
} else {
$ids[] = $id;
}
}
There is no need for a loop, you can use array_column to make the array associative, which will remove any duplicates.
Then use array_values to make the array indexed again.
$arr = array_values(array_column($arr, Null, "id"));
I'm currently building an array off of an object and I've got one element called images that has multiple sub elements called 'urls' structured like so
categories": [
{
"images": [
{
"urls": [
"path/test.jpg",
"path/test2.jpg",
"path/test3.jpg"
],
},
{
"urls": [
"path/test4.jpg",
"path/test5.jpg",
"path/test6.jpg"
],
},
{
"urls": [
"path/test7.jpg",
"path/test8.jpg",
"path/test9.jpg"
],
},
]
The values there don't have keys, it's just the url path but I'd like to add these to my $groupItem array and just have each url be it's own element on the same level as the group number (basically I'm exporting and need each url as it's own column)
The structure I want
0 =>"path/test.jpg",
1 =>"path/test2.jpg",
2 =>"path/test3.jpg"
3 =>"path/test4.jpg",
4 =>"path/test5.jpg",
5 =>"path/test6.jpg"
6 =>"path/test7.jpg",
7 =>"path/test8.jpg",
8 =>"path/test9.jpg"
The loop/array:
foreach($prices->groups as $group){
$groupItem = array();
$groupItem["number"] = $group->number;
foreach($group->images as $images){
$groupItem["urls"] = $images->urls;
}
}
How can I simply just add on any url to the groupItem level of that array?
Outside the outer loop, init the value to an empty array:
$groupItem["urls"] = [];
Then use the empty array reference operator to append new values to the end of an array:
foreach($group->images as $images){
$groupItem["urls"][] = $images->urls; // add this url to the end of the list
}
Alternatively, use array_push():
foreach($group->images as $images){
array_push($groupItem["urls"], $images->urls);
}
I think you can probably also skip the inner loop and just use the array explode operator like this:
array_push($groupItem["urls"], ...$images->urls);
You might also use array_column with (from php 5.6) a variable length argument list:
For example, for the images which contains an array of objects where each object has a property urls and contains an array of image urls:
foreach ($prices->groups as $group) {
$groupItem = array();
$groupItem["number"] = $group->number;
$groupItem= array_merge($groupItem, ...array_column($group->images, "urls"));
}
Demo
I have an XML document and i'm using SimpleXMLElement to parse it with PHP like :
<Document>
<Hello>
<Name>Jason</Name>
</Hello>
</Document>
Example to access to the name loading my XML and then i do :
$xml->Document->Hello->Name
I would like to store all this routes in associative array like
$array = [
"Document->Hello->Name" => "name"
];
The problem is when i loop on this array my field is empty
I do this :
foreach($array as $key => $v)
{
$hereIsempty = $xml->$key
}
Is someone have a solution to get the value i want from my array mapping plz
Storing full route path in one key is a bad idea.
if you know in advance the meaning of the path: set the tags name to const value and then use it.
But if you realy need to save path as key in array, you can separate all tags in first sub-array and set needs value as second element of array; something like this:
$needs = [];
$routes = [
[
'tags' => ['Document', 'Hello'],
'need' => 'Name'
]
];
foreach ($routes as $route) {
tempXml = $xml;
foreach ($route['tags'] as $tag) {
$tempXml = $tempXml->{$tag};
}
$need[] = (string)$tempXml->{$route['need']};
}
I have an object where there are all my articles.
I'm currently looping my object to fill an array where I create an associative table for each article.
In my object I also have a Categories object and I would like to add the label of each category at the end of each associative array previously completed, but
I don't know how to do that.. In the Categories object there may be multiple labels.
My code :
$articles = $this->entityManager->getRepository('SGBundle:Article')->findBy([], ['id'=>'desc']);
$arrayCollection = [];
foreach($articles as $article) {
$arrayCollection[] = [
'id' => $article->getId(),
'date_publication' => $article->getDatePublication(),
...
];
foreach($article->getCategories() as $categorie) {
$arrayCollection[] = ['categorie' => $categorie->getLibelle()];
}
}
On my screenshot, for each article there is an array with 36 values and an array with 1 value and I would like this table to be in the table where there are 36 values. It's possible ?
First gather categories, then add'em to article item:
foreach($articles as $article) {
$categories = [];
foreach($article->getCategories() as $categorie) {
$categories[] = $categorie->getLibelle();
}
$arrayCollection[] = [
'id' => $article->getId(),
'date_publication' => $article->getDatePublication(),
...
//
'categorie' => $categories,
];
}
If the article.categories field is marked as lazy, then it won't be hydrated by default and the $article->getCategories() will perform a new query on each loop round.
Instead of a simple findBy, you might want a custom DQL query in this case to optimize this and get the exact array you want in one single request.
Also note that your current query is fetching all articles of your database. While this is probably your purpose, keep in mind that this could get pretty heavy with the data growing. In most cases, this kind of query should be paginated.