I have a nested list of categories which DB looks something like this:
id parent_id title
83 81 test3
86 83 test1
87 83 test2
94 87 subtest2.1
95 87 subtest2.2
...etc...
I need to add all child element ids into the $checked_elements array of each parent id.
So if some specific id is selected, it's automatically added into the array of $checked_elements. Here how it looks like:
I'am stuck with the recursive function, on how to add recursively child items of each parent item id ? My function won't go deeper then the 2nd level, can anyone tell me how to work it out so it will check for all child items ?
private function delete( ){
// Collect all checked elements into the array
$checked_elements = $this->input->post('checked');
// Recursive function to check for child elementts
foreach( $checked_elements as $key => $value ){
// Get records where parent_id is equal to $value (checked item's id)
$childs = $this->categories_model->get_by(array('parent_id' => $value));
// Add found record's id into the array
foreach( $childs as $child ){
$checked_elements[] => $child->id;
}
}
}
You can try passing the accumulator array around by reference:
function collect($ids, &$items) {
foreach($ids as $id){
$items[] = $id;
$childs = $this->categories_model->get_by(array('parent_id' => $id));
collect(array_column($childs, 'id'), $items);
}
return $items;
}
function delete( ){
$items = array();
collect($this->input->post('checked'), $items);
//... delete $items
}
In php 5.5+ you can also use generators in a way similar to this:
function collect($ids) {
foreach($ids as $id) {
yield $id;
$childs = $this->categories_model->get_by(array('parent_id' => $id));
foreach(collect(array_column($childs, 'id')) as $id)
yield $id;
}
function delete( ){
$ids = collect($this->input->post('checked'));
I assume your tree is rather small, otherwise I'd suggest a more efficient approach, like nested sets.
If your php version doesn't support array_column, you can use this shim.
Related
Below lists the output of a mySQL query in PHP PDO. The object contains multiple columns from two tables that are then to be combined into a single object.
Some rows in the same table are children of others as identified by the column parent_ID. These children then need to be added to the object of the parent as do their children and so on and so on.
As much as I can achieve this simply for the first two levels of children I cannot see a way without performing another foreach to achieve this beyond the first to layers of the object.
This example should add clarity to the above:
foreach($components as $component){
if($component->parent_ID < 0){
$output->{$component->ID} = $component;
}
else if($output->{$content->parent_ID}){
$output->{$content->parent_ID}->child->{$component->ID} = $component;
}
else if($output->?->child->{$conent->parent_ID}){
$output->?->child->{$content->parent_ID}->child->{$component->ID} = $component;
}
}
Not on the third line there is an ? where there would normally be an ID. This is because we now do not know what that ID is going to be. In the first layer we did because it would be the parent_ID but this line is dealing with the children of children of a parent.
So, as I far I understood from comments and assuming that you don't have a lot of records in DB, it's seems to me the best way is to preload all rows from DB and then build a tree using this function
public function buildTree(array &$objects) {
/** thanks to tz-lom */
$index = array();
$relations = array();
foreach($objects as $key => $object) {
$index[$object->getId()] = $object->setChildren(array());
$relations[$object->getParentId()][] = $object;
if ($object->getParentId()) {
unset($objects[$key]);
}
}
foreach ($relations as $parent => $children) {
foreach ($children as $_children) {
if ($parent && isset($index[$parent])) {
$index[$parent]->addChildren($_children->setParent($index[$parent]));
}
}
}
return $this;
}
P.S. Really, I don't see other way without foreach in foreach. At least, it's not recursive
I'm currently working on a comment system using PHP,and I'm using 'parent ID' solution as to connect one reply to another.The problem is I haven't figured out how to cast these 'parent ID' connected data stored in mysql to a PHP array and render them out.I've been searching for iteration solution but nothing found out.My database structure is as follow:
Parent_id 0 means top level comment.
comment_id content parent_id
1 xxx 0
2 xxx 0
3 xxx 1
4 xxx 3
5 xxx 4
6 xxx 3
... ... ...
Here is what I have done,I fetched all the comments out in an array,and the array looks like this:
$comment_list = array(0=>array('comment_id'=>1,'content'=>'xxx','parent_id'=>0),
0=>array('comment_id'=>2,'content'=>'xxx','parent_id'=>0),
0=>array('comment_id'=>3,'content'=>'xxx','parent_id'=>1),
0=>array('comment_id'=>4,'content'=>'xxx','parent_id'=>3),
...
)
I need to attach comment with parent_id 1 to comment with comment_id 1,and so on,depth should be unlimited,and working for hours still can't find a way to iterate properly,can someone give me some pointers on how to do this?I know a solution but it makes new request to the database upon every iteration,so I prefer to do it using PHP array once for all,thank you !
When faced with a complex structure like this, it is sometimes better to create an object oriented solution, and then use the objects to create the array that you require.
For example, based on your above, I might define the following class:
class Comment{
protected $id;
protected $children;
protected $content;
public function __construct( $id, $content ){
$this->id = $id;
$this->content = $content;
$this->children = array();
}
public function addChild( $child ){
$this->children[] = $child;
}
}
Now, we use this object to transfer your database into the working memory as follows:
$workingMemory = array(); //a place to store our objects
$unprocessedRows = array(); //a place to store unprocessed records
// here, add some code to fill $unproccessedRows with your database records
do{
$row = $unprocessedRows; //transfer unprocessed rows to a working array
$unprocessedRows = array(); //clear unprocessed rows to receive any rows that we need to process out of order.
foreach( $row as $record ){
$id = $record[0]; //assign your database value for comment id here.
$content = $record[1]; //assign your database value for content here.
$parentId = $record[2]; //assign your database value for parent id here
$comment = new Comment( $id, $content );
//for this example, we will refer to unlinked comments as
//having a parentId === null.
if( $parentId === null ){
//this is just a comment and does not need to be linked to anything, add it to working memory indexed by it's id.
$workingMemory[ $id ] = $comment;
}else if( isset( $workingMemory[ $parentId ] ) ){
//if we are in this code block, then we processed the parent earlier.
$parentComment = $workingMemory[ $parentId ];
$parentComment->addChild( $comment );
$workingMemory[ $id] = $comment;
}else{
//if we are in this code block, the parent has not yet been processed. Store the row for processing again later.
$unprocessedRows[] = $record;
}
}
}while( count( $unprocessedRows ) > 0 );
Once all the unprocessedRows are complete, you now have a representation of your comments entirely stored in the variable $workingMemory, and each cell of this array is a Comment object that has an $id, a $content, and links to all children $comments.
We can now iterate through this array and make whatever data arrays or tables we want. We must remember, that the way that we stored arrays, we have direct access to any comment directly from the $workingMemory array.
If I were using this to generate HTML for a website, I would loop through the workingMemory array and process only the parent comments. Each process would then iterate through the children. By starting with the parents and not the children, we would guarantee that we are not processing the same comment twice.
I would alter my Comment class to make this easier:
class Comment{
protected $id;
protected $children;
protected $content;
protected $isRoot;
public function __construct( $id, $content ){
$this->id = $id;
$this->content = $content;
$this->children = array();
$this->isRoot = true;
}
public function addChild( $child ){
$child->isRoot = false;
$this->children[] = $child;
}
public function getChildren(){ return $this->children; }
public function getId(){ return $this->id; }
public function getContent(){ return $this->content; }
}
After this change, I can create my HTML as follows:
function outputCommentToHTML( $aComment, $commentLevel = 0 ){
//I am using commentLevel here to set a special class, which I would use to indent the sub comments.
echo "<span class'comment {$commentLevel}' id='".($aComment->getId())."'>".($aComment->getContent())."</span>";
$children = $aComment->getChildren();
foreach( $children as $child ){
outputCommentToHTML( $child, $commentLevel + 1 );
}
}
foreach( $workingMemory as $aComment ){
if( $aComment->isRoot === true ){
outputCommentToHTML( $aComment );
}
}
This would convert database columns into the format you require. For example, if we had the following data:
comment_id content parent_id
1 xxx 0
2 xxx 0
3 xxx 1
4 xxx 3
5 xxx 4
6 xxx 3
... ... ...
It would output in HTML:
Comment_1
Comment_3
Comment_4
Comment_5
Comment_6
Comment_2
This is done recursively in the function, which processes Comment_1 fully before moving to Comment 2. It also processes Comment_3 fully before moving to Comment 2, which is how Comments 4, 5 and 6 all get output before Comment 2.
The above example will work for you, but if it were my personal project, I would not mix linear and Object oriented code, so I would create a code factory to convert Comments into HTML. A factory make data strings from source objects. You can create an Object that acts as a factory for HTML, and another factory that acts as a generator of SQL, and by layer objects with solutions like this, you can create an entirely Object Oriented solution, which is easier to understand to the average reader and sometimes even to non-coders to produce something like this:
//these definition files get hidden and tucked away for future use
//you use include, include_once, require, or require_once to load them
class CommentFactory{
/**** other Code *****/
public function createCommentArrayFromDatabaseRecords( $records ){
/*** add the data conversion here that we discussed above ****/
return $workingMemory;
}
}
class HTMLFactory{
public function makeCommentTableFromCommentArray( $array ){
$htmlString = "";
foreach( $array as $comment ){
if( $comment->isRoot ){
$htmlString .= $this->getHTMLStringForComment( $comment );
}
}
return $htmlString;
}
private function getHTMLStringForComment( $comment, $level=0 ){
/*** turn your comment and all it's children into HTML here (recursively) ****/
return $html;
}
}
Done properly, it can clean up your active code file so that it reads almost like a list of instructions like this:
//let database be a mysqli or other database connection
//let the query function be whatever method works for your database
// of choice.
//let the $fetch_comment_sql variable hold your SQL string to fetch the
// comments
$records = $database->query( $fetch_comment_sql )
$comFactory = new CommentFactory();
$commentArray = $comFactory->createCommentArrayFromDatabaseRecords( $records );
$htmlFactory = new HTMLFactory();
$htmlResult = $htmlFactory->makeCommentTableFromCommentArray( $commentArray );
echo $htmlResult;
Given the following array and a given id, how can I return the corresponding elem? For instance, given id=6, it should return goodby. The id values are the PKs from a database, thus will always be unique.
Obviously, I could just iterate over the first array, check the values, and break and return the elem upon match, but I expect there is a much more elegant way to do this.
array(
array('id'=>2,'elem'=>"hello"),
array('id'=>6,'elem'=>"goodby"),
array('id'=>8,'elem'=>"goodnight")
);
A basic alternative to consider would be to collect the columns with array_column (indexed by the id column) and then dereferencing the array to access the passed index value $id, something like:
$myArray = array(
array('id'=>2,'elem'=>"hello"),
array('id'=>6,'elem'=>"goodby"),
array('id'=>8,'elem'=>"goodnight")
);
function getValue($id, $a)
{
return array_column($a, 'elem', 'id')[$id];
}
var_dump(getValue(6, $myArray));
Enhancing the proof to get more control:
function getValueAlternative($where, $get, $fromSource)
{
$where = explode("=", $where);
return array_column($fromSource, $get, $where[0])[$where[1]];
}
var_dump(getValueAlternative("id=2", "elem", $myArray));
var_dump(getValueAlternative("elem=hello", "id", $myArray));
See this :
myfunction($products, $needle)
{
foreach($products as $key => $product)
{
if ( $product['id'] === $needle )
return $key;
}
return false;
}
Similar : PHP Multidimensional Array Searching (Find key by specific value)
Why don't you use the following structure?:
array(
2 => array('elem'=>"hello", ...),
6 => array('elem'=>"goodby", ...),
8 => array('elem'=>"goodnight", ...)
);
Then you could access them like:
$array[$id];
You told that you've used PDOStatement::fetchAll() to obtain your array. You can tell fetchAll() to return a structure like I've suggested, you need to use the fetch mode PDO::FETCH_GROUP:
$pdo->query('SELECT * FROM table')->fetchAll(PDO::FETCH_GROUP);
Well defined data structures are the essential basics of a good program. In your case an associative array having the ids as their keys and the whole records as the value portion,will be better than a numeric array as it allows you to perform fast, id based access. Of course you have to pay for this - with a little more memory.
You could use the array_search PHP function:
/**
* #param array $array
* #param int $id
* #return string|null
*/
function getElem(array $array, $id) {
foreach($array as $subArr) {
if(($key = array_search($id, $subArr, true)) !== false) {
if(strcmp($key, "id") === 0) {
return $subArr['elem'];
}
}
}
return null;
}
$array = array(
array('id'=>2,'elem'=>"hello"),
array('id'=>6,'elem'=>"goodby"),
array('id'=>8,'elem'=>"goodnight")
);
echo getElem($array, 6);
Using CActiveRecord my table looks like this:
A column parent_id has relation many to id, and it works properly.
id | parent_id
---+----------
1 1 <- top
2 1 <- means parent_id with 1 has parent with id=1
3 1
4 2 <- parent for is id=2
5 2
6 2 and so many nested levels....
A goal is how to properly get nested as PHP classically way nested arrays data (arrays inside arrays).
array(1,1) {
array(2,1) {
array(4,2) ....
}
}
Problem is Yii. I didn't find properly way how to pick up a data as nested array using properly CActiveRecord.
What is best way to make nested array results? A main goal is to easy forward to render view so I don't separate with too many functions and calling many models outside from modules or models.
A good is one function to get a result.
Solved using this: Recursive function to generate multidimensional array from database result
You need get a data as arrays from model:
$somemodel = MyModel::model()->findAll();
Then put all in array rather then Yii objects model or what you need:
foreach ($somemodel as $k => $v)
{
$arrays[$k] = array('id' => $v->id, 'parent_id' => $v->parent_id, 'somedata' => 'Your data');
}
Then call a function:
function buildTree(array $elements, $parentId = 0) {
$branch = array();
foreach ($elements as $element) {
if ($element['parent_id'] == $parentId) {
$children = buildTree($elements, $element['id']);
if ($children) {
$element['children'] = $children;
}
$branch[] = $element;
}
}
return $branch;
}
Then call put all $arrays data into buildTree function to rebuild nesting arrays data.
$tree = buildTree($arrays);
Now your $tree is nested arrays data.
Note: there aren't depth into function but in convient way you can add using like this sample: Create nested list from Multidimensional Array
Im doing a project in php CodeIgniter which has a table where all attributes_values can be kept and it is designed such that it can have its child in same tbl. the database structure is
fld_id fld_value fld_attribute_id fld_parent_id
1 att-1 2 0
2 att-2 2 0
3 att-1_1 2 1
4 att-1_2 2 1
5 att-1_1_1 2 3
here above att-1 is the attribute value of any attribute and it has two child att-1_1 and att-1_2 with parent id 1. and att-1_1 has too its child att-1_1_1 with parent_id 3. fld_parent_id is the fld_id of the same table and denotes the child of its. Now i want to show this in tree structure like this
Level1 level2 level3 ..... level n
att-1
+------att-1_1
| +------att-1_1_1
+------att-1_2
att-2
and this tree structure can vary upto n level. the attribute values with parent id are on level one and i extracted the values from level one now i have to check the child of its and if it has further child and display its child as above. i used a helper and tired to make it recursive but it didnt happen. So how could i do it such: the code is below
foreach($attributes_values->result() as $attribute_values){
if($attribute_values->fld_parent_id==0 && $attribute_values->fld_attribute_id==$attribute->fld_id){
echo $attribute_values->fld_value.'<br/>';
$children = get_children_by_par_id($attribute_values->fld_id); //helper function
echo '<pre>';
print_r($children);
echo '</pre>';
}
}
and the helper code is below:
function get_children_by_par_id($id){ //parent id
$children = get_children($id);
if($children->num_rows()!=0){
foreach($children->result() as $child){
get_children_by_par_id($child->fld_id);
return $child;
}
}
}
function get_children($id){
$CI = get_instance();
$CI->db->where('fld_parent_id',$id);
return $CI->db->get('tbl_attribute_values');
}
please help me...............
The key of recursion is an "endless" call. This can be done with a function that calls it self.
So
function get_children($parent_id)
{
// database retrieve all stuff with the parent id.
$children = Array();
foreach($results as $result)
{
$result['children'] = get_children($result['id']);
$children[] = $result;
}
return $children;
}
Or use the SPL library built into PHP already PHP recursive iterator