PHP MySQL Menu Sorting - php

Ok, so here's my table structure:
+--------------------------+ +----------------+ +-------------------------------+
| pages | | menus | | menu_pages |
+--------------------------+ +----+-----------+ +-------------------------------+
| id | title | slug | | id | name | | menu_id | page_id | parent_id |
+-------+---------+--------+ +----+-----------+ +---------+---------+-----------+
| 1 | Home | index | | 1 | default | | 1 | 1 | 0 |
+-------+---------+--------+ +----+-----------+ +---------+---------+-----------+
| 2 | About | about | | 2 | footer | | 1 | 2 | 0 |
+-------+---------+--------+ +----+-----------+ +---------+---------+-----------+
| 3 | Test 1 | test-1 | | 1 | 3 | 2 |
+-------+---------+--------+ +---------+---------+-----------+
| 4 | Test 2 | test-2 | | 1 | 4 | 2 |
+-------+---------+--------+ +---------+---------+-----------+
| 5 | Test 3 | test-3 | | 1 | 5 | 4 |
+-------+---------+--------+ +---------+---------+-----------+
So basically, we have pages, menus, and a menu_pages linking table which specifies the menu, the page, and the parent of each menu item.
Here's my query:
$query = "SELECT pages.id, pages.title, pages.slug, menu_pages.parent_id
FROM menus, pages, menu_pages WHERE menus.name = '$menu'
AND menus.id = menu_pages.menu_id
AND pages.id = menu_pages.page_id";
$results = $db->Query($query);
Here's the question: How do I get the menu items properly nested under their respective parents in an array? I've tried quite a few things already, but none of them worked beyond simply 2 levels, so I won't clutter up the question with it. Obviously I need some kind of recursion in PHP, or to modify my query maybe in a way that I can get the SQL to return them properly?
It should look something like this in the output:
[0] => array(
'id' => 1,
'title' => 'Home',
'slug' => '/',
'parent_id' => '0'
)
[1] => array(
'id' => 2,
'title' => 'About',
'slug' => 'about',
'parent_id' => 0,
'sub_menu' => array(
[0] => array(
'id' => 3,
'title' => 'Test 1',
'slug' => 'test-1',
'parent_id' => 2
)
[1] => array(
'id' => 4,
'title' => 'Test 2',
'slug' => 'test-2',
'parent_id' => '2',
'sub_menu' => array(
[0] => array(
'id' => 5,
'title' => 'Test 3',
'slug' => 'test-3',
'parent_id' => 4
)
)
)
)
)
Thanks for the help!

This isn't quite as simple as it first sounds - if you want to get into how to do it with SQL, you are looking for a recursive sql statement.
Unfortunately mysql doesn't support this directly, and you would need to write a body of code to get it working. There is an example of how to do it here. Not simple.
http://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/
In case you're interested in how to pick this apart, you would implement it in Oracle like this:
SELECT p.id, p.title, p.slug, mp.parent_id, level
FROM menu_pages mp
JOIN pages p ON ( p.id = mp.page_id )
JOIN menus m ON ( m.id = mp.menu_id )
CONNECT BY PRIOR mp.page_id = mp.parent_id
START WITH ( m.name = 'default' AND mp.parent_id = 0 )
You are basically saying:
START WITH a query for the top level of the menu
CONNECT that back to the result set by joining the parent to the child
You end up with a result set like this:
id title slug parent level
------------------------------------
1 Home index 0 1
2 About about 0 1
3 Test 1 test-1 2 2
4 Test 2 test-2 2 2
5 Test 3 test-3 4 3
All this actually gives you in addition to what you already have is:
The "level" of each point in the menu.
If a sub menu appeared multiple times in your structure it would be repeated correctly.
Sections of the menu that are not connected properly will not be returned.
So, for small, simple and consistent menus it's probably over-kill anyway.
Plus, even in this case you would need to process this in PHP to get the structure you're looking for.
So, using that as inspiration you can see how you could implement it in mysql by just doing the post processing.
You start off with your original query:
SELECT pages.id
, pages.title
, pages.slug
, menu_pages.parent_id
FROM menus
, pages
, menu_pages
WHERE menus.name = 'default'
AND menus.id = menu_pages.menu_id
AND pages.id = menu_pages.page_id
You can then loop over this result and build the array structure yourself manually.
In order to avoid the problem of recursion, we're instead going to take advantage of the fact that we can have two variables pointing at the same data structure - we're going to use references so that changing the value of the variable in one reference will change the value of the variable in the other.
I.E. The difficulty you get is finding the right point in the hierarchy to add each child. With references you don't have to.
Create an empty menu array
Loop over the results from your SQL statement
Create a copy of each menu item and put it into a simply indexed store (by the id of the item)
If you have the root menu item, add it to your menu array (as a reference)
If you don't have the root menu item, find the parent in your simple store and add your new item to it.
At the end you should have the nice nested structure you're looking for.
Like this:
<?php
// As if it came back from mysql...
// Assumed that it's ordered so that every possible parent appears before all its childern
$aResults = array( array( 'id' => 1, 'title' => 'Home', 'slug' => 'index', 'parent_id' => 0 )
, array( 'id' => 2, 'title' => 'About', 'slug' => 'about', 'parent_id' => 0 )
, array( 'id' => 3, 'title' => 'Test 1', 'slug' => 'test-1', 'parent_id' => 2 )
, array( 'id' => 4, 'title' => 'Test 2', 'slug' => 'test-2', 'parent_id' => 2 )
, array( 'id' => 5, 'title' => 'Test 3', 'slug' => 'test-3', 'parent_id' => 4 ) );
// the menu you're creating
$aMenu = array();
// the simple store of the menu items you're going to use to find the parents
$aBaseMenuIndex = array();
foreach( $aResults as $aMenuItem ) {
$aMenuItem['sub_menu'] = array();
// add your menu item to the simple store
$aBaseMenuIndex[ $aMenuItem['id'] ] = $aMenuItem;
if ( $aMenuItem['parent_id'] == 0 ) {
// if it's a base menu item, add it to the menu
$aMenu[] =& $aBaseMenuIndex[ $aMenuItem['id'] ];
} else {
// if it's not a base item, add it to the sub menu, using the simply indexed store to find it
// adding it here will also add it to $aMenu, as $aMenu contains a reference to this
$aBaseMenuIndex[ $aMenuItem['parent_id'] ]['sub_menu'][] =& $aBaseMenuIndex[ $aMenuItem['id'] ];
}
}
var_dump( $aMenu );

Related

Echoing data vertical and horizontal from joined table mySql in PHP

I need your help. Maybe it's all about logical. So my case is:
I have mySql tables, they are:
record table
_______________________________________
Student ID | Question ID | Answer
_______________________________________
1 | 1 | A
2 | 1 | C
3 | 1 | E
1 | 2 | D
2 | 2 | B
3 | 2 | A
....... | .......... | ........
_______________________________________
and student table:
_________________________________
Student ID | Student Name
_________________________________
1 | Ronaldo
2 | Messi
3 | Neymar
.......... | .............
_________________________________
And I want to echoing them in my report page with table like this:
_________________________________________________
Student | Question and Answer
|____________________________________
| 1 | 2 | 3 | ... | ... | ... | ... |
_________________________________________________
Ronaldo | A | D | ...........................
Messi | C | B | ...........................
Neymar | E | A | ...........................
........ | ...................................
_________________________________________________
I use PHP so I repeat with foreach(){} function. But how to echoing data vertically and horizontally in once query?
Sorry for my bad php knowledge and logical. Thanks for your attention.
A simple way to do it (but by no means the only one) :
First fetch your users along with their answers :
SELECT
s.id AS student_id,
s.name AS student_name,
q.question_id,
q.answer
FROM
students AS s
INNER JOIN
questions AS q
ON
s.id = q.student_id;
You should get rows like that :
array(
'student_id' => '1',
'student_name' => 'Ronaldo',
'question_id' => 1,
'answer' => 'A',
)
OR
array(
'student_id' => '2',
'student_name' => 'Messi',
'question_id' => 1,
'answer' => 'C',
),
You could then loop through them to reorganize them :
$data = array();
foreach($rows as $row){
if(!isset($data[$row['student_id']])){
$data[$row['student_id']] = array(
'student_name' => $row['student_name'],
'questions' => array(),
);
}
$data[$row['student_id']]['questions'][] => array(
'question_id' => $row['question_id'],
'answer' => $row['answer'],
);
}
Which will give you this array :
$data = array(
'1' => array(
'student_name' => 'Ronaldo',
'questions' => array(
array(
'question_id' => 1,
'answer' => 'A',
),
array(
'question_id' => 2,
'answer' => 'D',
),
),
),
'2' => array(
'student_name' => 'Messi',
'questions' => array(
array(
'question_id' => 1,
'answer' => 'C',
),
array(
'question_id' => 2,
'answer' => 'B',
),
),
),
);
Finally, loop through the last array to display data the way you want :
foreach($data as $student){
echo $student['student_name'] . ' : ' ;
foreach($student['questions'] as $question){
echo 'Question ' . $question['question_id'] . ' : ' . $question['answer'] . "\n";
}
}
You will need to do some adaptation to suit your need.

MySQL entries with self parent_id into sorted cascade array

I need help with an application I'm creating.
I have a table, looks kind like this:
+----+-----------+---------+
| id | parent_id | name |
+----+-----------+---------+
| 1 | null | test |
+----+-----------+---------+
| 2 | null | test2 |
+----+-----------+---------+
| 4 | 1 | test3 |
+----+-----------+---------+
| 5 | 2 | test4 |
+----+-----------+---------+
And now, I get all the data in one array. I would like to get kinda this structure (php array as an cascade):
array(
0 => array(
'id' => 1,
'parent_id' => null,
'name' => 'test',
'children' => array(
'id' => 4,
'parent_id' => 1,
'name' => 'test3'
)
),
1 => array(
'id' => 2,
'parent_id' => null,
'name' => 'test2',
'children' => array(
'id' => 5,
'parent_id' => 2,
'name' => 'test4'
)
)
)
So there will every entry with a "parent_id=null" be a parent, and every entry with an id in "parent_id" will be in a child array.
I started it like this:
$newArray = array();
foreach($names as $name) {
if($name['parent_id'] == null || $name['parent_id'] == 0) {
// entry is parent
$newArray[$name['id']] = $name['name'];
} else {
// entry is child
}
}
But here is also my end, I don't know how to do that. I think i have to use some kind of recursive loop function, but I don't know how to start.
Would be awesome if somebody could help me.
Kind regards,
Matt.
You can use a recursive function like this (I only added the code which is relevant for understanding):
function get_children($parentId) {
$array = array();
//Load/Find $children
foreach($children as $child) {
$array[] = array(
'id' => $child->id,
'name' => 'YourName',
'children' => get_children($child->id)
);
}
return $array;
}
If you save the data in such an array, it isn't necessary to save the parent_id, because you can get it by searching for the parent elements id.

CakePHP hasAndBelongsToMany Checkbox matrix

I have two models, Component and Group. A Group has many Components, and a Component can be in many Groups. Now, what I need is a view, in which there's a table, n times m field (or matrix) of checkboxes. The Columns would be all the Groups (which would be fewer) and the rows would represent the Component. Basically, when the HaBTM-Relationship exists, the checkbox is checked.
This should be editable, meaning it would be wrapped in a form.
| Group 1 | Group 2 | Group 3
C1 | x | | x
C2 | x | x |
C3 | | | x
What is the least stressful way to achieve this in CakePHP?
Alright.
First of course you load the data from your connecting table. So you get a lot of datasets like:
id => 4
component_id => 4
group_id =>3
Then you have an array of all existing components and another one which contains all existing groups.
Since you have the components in rows I would reorganise this array of arrays, so that you have a multidimensional array which as first key has the component_id and under this id you have an array containing the arrays of all datasets, which contain this group.
e.g.:
array(
1 => array(
0 => array(
'id' => 4,
'component_id' => 1,
'group_id' => 2,
),
1 => array(
'id' => 4,
'component_id' => 1,
'group_id' => 5,
),
),
2 => array(
0 => array(
'id' => 4,
'component_id' => 2,
'group_id' => 1,
),
1 => array(
'id' => 4,
'component_id' => 2,
'group_id' => 3,
),
),
);
After this reorganisation you can pass it to the view, where you can iterate through it to build the form, giving each checkbox the component-group info, which you can collect after sending the form.
Nicer than that (as a version 1.2) would be a AJAX call which saves the combination immediately after activating or deactivating the checkbox. Your choice ;)
Calamity Jane

2 attribute in 2-dimensional array php

I want to create a 2 dimensional array, which the second array has 2 attributes. Is it possible in php? Becuase I know it's possible in Pascal
example
| Doc | Term |
| 0 | 0 => 'Term1' |
| | 1 => 5 |
----------------------------
| 1 | 0 => 'Term'2' |
| | 1 => 2 |
My question is, How to create this 2-dimensional array and how to access each value?
Thank you
This is simple array nesting:
$a = array(array('Term1', 5), array('Term2', 2));
$a[1][1] === 2;
This is an extremely basic question. Consider consulting a php book or tutorial.
Yes, you just make the value of the item in the array, another array, you can do this as deep as you like. e.g.,
Creating the array
$doc = array(
array(
'Term1',
5
),
array(
'Term 2',
2
)
)
Since no ID is set, the id's are automatically generated, starting at 0. You can set the ID if you want like this:
$doc = array(
0 => array(
3 => 'Term1',
9 => 5
),
1 => array(
3 => 'Term 2',
10 => 2
)
)
Retrieving data from the array
$term1 = $doc[0][0];
echo $term1; // outputs 'Term 1'

How to print specific array entry using variable for position e.g. $array[$x]

With help from others on here, I've got a nested loop on the go that pull a list of months from one sql table and then, for each of those months, it goes through an events table and pulls the respective events.
Table structures are along the lines of:
MonthTable
ID | MonthShort | MonthLong
1 | 2012Oct | October 2012
2 | 2012Sep | September 2012
EventTable
ID | MonthID | Event | Guests | Adults | Children
1 | 1 | Wedding | 200 | 150 | 50
2 | 1 | Bar Mitzvah | 100 | 50 | 50
3 | 1 | Funeral | 100 | 50 | 50
4 | 2 | Birthday | 50 | 30 | 20
5 | 2 | Birthday | 300 | 200 | 100
6 | 2 | Wedding | 200 | 180 | 20
My loop works so that it populates menu A with all available months, then populates menu B with all of the events for that month. You can then click on the event and it displays the relevant information - this is where I'm a bit stuck.
The arrays I've got are similar to the following, the guests array is what I'm trying out atm:
$events = array();
$months = array();
$guests = array();
while ($row = mysql_fetch_array($result)) {
$months[$row["MonthID"]] = $row["MonthLong"];
$events[$row["MonthID"]][] = $row["Event"];
$guests[$row["MonthID"]][] = $row["Guests"];
}
I use a foreach to populate menu B with ($events[$x] as $event). The screen for each event will have an entry similar to the following and this is what I'd like to do (obviously I know this won't work bu it should serve for illustrative purposes):
echo ' Number of guests: ' . print_r($guests[$x])
With guests and events both on the same counter I though it would allow me to print the array entry in the relevant position.
So what I'd like it if you click on "October 2012" and then select "Funeral", the screen would say:
Number of guests: 100
There are actually several dozen records per event but no point going into all of them...
Apologies for the rambling and if this makes no sense! I'm new to PHP and am only really stuck on this bit.
SQL query is built on the following:
$sql = "
SELECT
a.id, b.id AS monthId, a.event, b.monthshort, b.monthlong
FROM
events_table_name AS a
INNER JOIN
month_table_name AS b ON b.id = a.monthId
ORDER BY
b.id, a.id ASC
";
You need make use of the index in the foreach statement. I mean
foreach ($events[$x] as $i => $event) {
...
echo ' Number of guests: ' . print_r($guests[$x][$i]);
}
I would go for a different data structure in PHP. How about this? You might have to change your SQL query to get it, but this is the data structure I'd aim for:
$months = array(
'1' => array(
'long' => 'October 2012',
'events' => array(
'1' => array(
'name' => 'Wedding',
'guests' => '200'
),
'2' => array(
'name' => 'Bar Mitzvah',
'guests' => '100'
),
'3' => array(
'name' => 'Funeral',
'guests' => '100'
)
)
),
'2' => array(
// etc.
)
);
This way, you're able to look up a month; for each month, its events; for each event, its attendance and name.

Categories