Make a php method run itself until a condition is met - php

I have a table with three columns: id, ref and catName. Every row contains a category which can be a sub category; if so than the ref column references to the id of the main category. Off course this method makes sure you can create numerous sub categories.
Now I want to make a url for every category containing of the catName's of all its parents.
private function getCatUrl($cat, $ur = array()){
$sql = "SELECT * FROM shop_cat WHERE id = :id LIMIT 1";
$st = $this->db->prepare($sql);
$st->bindParam('id', $cat, PDO::PARAM_INT);
$st->execute();
$rij = $st->Fetch();
$ur[] = urlencode($rij['naam']);
if($rij['ref'] == 0){
//Done, I've reached the top parent;
return implode('/',$ur);
}else{
//there is a parent/reference above this level
//getting there
$this->getCatUrl($rij['ref'], $ur);
}
}
Somehow this only produces a $ur for the top parent and not for the childs.
What am I doing wrong?
Sample of the database:
id ref catName
1 0 GroundFloor
4 1 Portal
5 1 Stairs
2 0 FirstFloor
6 2 DiningArea
12 6 Chair
7 2 Toilet
9 2 SittingRoom
10 9 Couch
11 9 Dresser
3 0 Roof
8 3 LoungeChair

You are doing it wrong.
I would recommend for you to look into Closure Tables [1] [2] [3], and also read the SQL Antipatterns book ( it covers the trees in second chapter).

The error is that you do not make sure that when the recursion stops, the results "bubble up" the call stack until finally the first invocation of the function returns to the caller. In code:
return $this->getCatUrl($rij['ref'], $ur); // add the return!

Related

MYSQL / PHP Calculating number of occurrences

First off all I am slightly confused what the best implemtentation would be for the following problem i.e pure can it be done with only mysql without altering tables or would I need a combination of PHP and mysql as I am currently doing.
Please keep that in mind as you read on:
Question Info
A Pickem game works as follow:
1- Display all matches / fixtures in a round for a tournament.
2- User enters which teams he thinks will win each fixture.
The fixtures are pulled from a table schedule and the users results are recorded in a table picks
Keep In mind
Each round can have a number of matches (anywhere between 1 to 30+ matches)
What I am trying todo / PROBLEM
I am trying to calculate how many users selected team1 to win and how many users selected team2 to win for a given round in a tournament.
Example
Manchester United: 7 users picked |
Arsenal 3: users picked
MYSQL TABLES
schedule table Schedule of upcoming games
picks table User Picks are recorded in this table
Expected Output From Above Tables After Calculations
So for Super Rugby Round 1 it should read as follow:
gameID 1 4 picks recorded, 2 users selected Jaquares 1 user Selected Stormers (ignore draw fro now)
gameID 2 4 picks recorded, 4 users selected Sharks, 0 users selected Lions
My Code
function calcStats($tournament, $week)
{
global $db;
//GET ALL GAMES IN TOURNAMENT ROUND
$sql = 'SELECT * FROMpicks
WHERE picks.tournament = :tournament AND picks.weekNum = :weekNum ORDER BY gameID';
$stmnt = $db->prepare($sql);
$stmnt->bindValue(':tournament', $tournament);
$stmnt->bindValue(':weekNum', $week);
$stmnt->execute();
if ($stmnt->rowCount() > 0) {
$picks = $stmnt->fetchAll();
return $picks;
}
return false;
}
test.php
$picks = calcStats('Super Rugby', '1');
foreach($picks as $index=> $pick) {
if($pick['gameID'] !== $newGameID){
?>
<h1><?php echo $pick['gameID']?></h1>
<?php
//reset counter on new match
$team1 = 0;
$team2 = 0;
}
if($pick['picked'] === $newPick){
//gameID is passed as arrayKey to map array index to game ID
//team name
$team1[$pick['picked']];
//number times selected
$team1Selections[$pick['gameID']] = $team1++;
}
else if($pick['picked'] !== $newPick){
///gameID is passed as arrayKey to map array index to game ID
//team name
$team2[$pick['picked']];
$team2Selections[$pick['gameID']] = $team2++;
}
$newPick = $pick['picked'];
$newGameID = $pick['gameID'];
}
PRINT_R() Of function $picks = calcStats('Super Rugby', '1')
I hoe my question makes sense, if you need any additional information please comment below, thank you for taking the time to read.
It seems that you're doing too much within PHP that can be easily done within MySQL; consider the following query:
SELECT gameID, team, COUNT(*) AS number_of_picks
FROM picks
WHERE picks.tournament = :tournament AND picks.weekNum = :weekNum
GROUP BY gameID, team
ORDER BY gameID, team
This will give the following results, given your example:
1 | Jaquares | 2
1 | Stormers | 1
1 | Draw | 1
2 | Sharks | 4
Then, within PHP, you perform grouping on the game:
$result = array();
foreach ($stmnt->fetchAll() as $row) {
$result[$row['gameID']][] = $row;
}
return $result;
Your array will then contain something like:
[
'1' => [
[
'gameID' => 1,
'team' => 'Jaquares',
'number_of_picks' => 2,
],
'gameID' => 1,
'team' => 'Stormers',
'number_of_picks' => 1,
],
...

MySQL - Outputting correct order for items that have a hierarchy?

I have the following MySQL table and I need to output the results in the correct order.
id title parent_id display_order level
20 company NULL 0 0
25 contact NULL 1 0
26 sales NULL 2 0
21 about 20 0 1
22 team 20 1 1
23 services 20 2 1
24 apps 23 0 2
The correct order would be the following:
Company
about
team
services
apps
Contact
Sales
I can't figure out how to make a query that will order by "level" and then "display_order" but ensuring that child items following their parent.
I'm looking for a MySQL answer but if you have a suggesting on how to parse the results with PHP, I'd consider that as well.
Any help would be greatly appreciated!
I often used php-function for get array categories with level
public function getCategoryTreeForParentId($parent_id = 0) {
$categories = array();
$this->db->from('categories');
$this->db->where('parent_id', $parent_id);
$result = $this->db->get()->result();
foreach ($result as $mainCategory) {
$category = array();
$category['id'] = $mainCategory->id;
$category['name'] = $mainCategory->name;
$category['parent_id'] = $mainCategory->parent_id;
$category['sub_categories'] = $this->getCategoryTreeForParentId($category['id']);
$categories[$mainCategory->id] = $category;
}
return $categories;
}
When you echo in html, you can depend on level value for classify your categories

Sort a php array in a custom order

I have a table called "car_owners", it has three columns known as:
id owner cars
1 Greg 1
2 Gary 3
3 Aaron 2
4 Farb 3
5 REX 1
6 Fred 2
In the following code I get it into array and print it:
$exc = $conn->prepare("SELECT name,state from current_state");
$exc->execute();
while($finalResult = $exc->fetch(PDO::FETCH_ASSOC))
{
$tables[] = $finalResult;
}
var_dump($tables);
once i get this into an array, is there a way i could sort it in a custom order where i could get the out put like follows,
first the owners with 2 cars, then with 1 car and who has 3
owner cars
Aaron 2
Fred 2
Greg 1
REX 1
Farb 3
Gary 3
P.S doing it from the table is not going to work, because im using a loop above the code which makes it impossible to do it from the SQL, can anybody tell me a way to do it from the php
select * from your_table
order by case when cars = 2 then 1
when cars = 1 then 2
when cars = 3 then 3
else 4
end
You can use usort to sort the values. This will also sort by name if two owners have the same number of cars. I have changed the SELECT statement to match the given database definition.
$exc = $conn->prepare("SELECT owner, cars from current_state");
$exc->execute();
while ($finalResult = $exc->fetch(PDO::FETCH_ASSOC))
{
$tables[] = $finalResult;
}
usort(
$tables,
function($a, $b) {
// If same number of cars, sort by name
if ($a['cars'] == $b['cars']) return strcmp($a['owner'], $b['owner']);
// If owner a has two cars, place before b
if ($a['cars'] == 2) return -1;
// If owner b has two cars, place below a
if ($b['cars'] == 2) return 1;
// Neither owner a nor owner b has two cars, sort by number of cars
return ($a['cars'] < $b['cars']) ? -1 : 1;
}
);
foreach ($tables as $row) {
echo $row['owner'], ' => ', $row['cars'], PHP_EOL;
}
Output:
Aaron => 2
Fred => 2
Greg => 1
REX => 1
Farb => 3
Gary => 3
If you have the array prepared from the mysql table, then you can use the following code-
$car_array=array(
"Aaron"=>2,
"Fred"=>2,
"Greg"=>1,
"REX"=>1,
"Farb"=>3,
"Gary"=>3,
);
$sort_array=array("2","1","3");
$new_array=array();
foreach ($sort_array as $key => $value)
{
foreach ($car_array as $key1 => $value1)
{
if ($value1 == $value )
$new_array[$key1]=$value1;
}
}
print_r($new_array);
Consider sorting the resultset through sql itself. The sql provided by #juergen would meet the purpose. The only change I would like to do in the query is that 'add owner field in the order by clause'. Consider the below code snippet
select * from car_owners
order by (case when cars = 2 then 1
when cars = 1 then 2
when cars = 3 then 3
else 4
end), owner
This should meet the purpose and give you the resultset exactly as you needed.
Also, if you explicitly need to sort it through php then you use the php usort() function and sort the array writing a custom defined function.
Using PHP only, you can use uksort function to sort the array using a user-defined comparison function. The following code requires a copy of your $tables variable.
<?php
$tables2=$tables;
uksort($tables2, function($r1, $r2) use ($tables) {
return ($tables[$r1]["cars"]%3) < ($tables[$r2]["cars"]%3);
});
print_r($tables2);

Wrong data retrieved from database

So, I want to retrieve the order of the elements of a list. The order is set before by the user, and are stored in the table below. Because I also want to retrieve name and description of the list elements I need to combine two tables (see below).
However, what is actually retrieved is an array containing 16 elements (should be four because it only exists four elements as for now). The array is too long to post here, but I put it in a phpFiddle to be found here if you're interested.
Well, I have really tried to find what's wrong (probably something easy as always), but with no luck.
Thanks a lot for your time and help!
listModel.php:
public function GetOrderedElements($userId, $listId) {
// $userId = 46
// $listId = 1
$query = "SELECT le.listElemId, le.listElemName, le.listElemDesc, lo.listElemOrderPlace
FROM listElement AS le
INNER JOIN listElemOrder AS lo
ON le.listId = lo.listId
WHERE lo.userId = ?
AND lo.listId = ?
ORDER BY listElemId";
$stmt = $this->m_db->Prepare($query);
$stmt->bind_param("ii", $userId, $listId);
$listElements = $this->m_db->GetOrderedElements($stmt);
return $listElements;
}
database.php:
public function GetOrderedElements(\mysqli_stmt $stmt) {
if ($stmt === FALSE) {
throw new \Exception($this->mysqli->error);
}
if ($stmt->execute() == FALSE) {
throw new \Exception($this->mysqli->error);
}
if ($stmt->bind_result($listElemId, $listElemName, $listElemDesc, $listElemOrderPlace) == FALSE) {
throw new \Exception($this->mysqli->error);
}
$listElements = array();
while ($stmt->fetch()) {
$listElements[] = array('listElemId' => $listElemId,
'listElemName' => $listElemName,
'listElemDesc' => $listElemDesc,
'listElemOrderPlace' => $listElemOrderPlace);
}
var_dump($listElements);
$stmt->Close();
return $listElements;
}
from the database:
listElemOrder:
listElemOrderId | listId | listElemId | userId | listElemOrderPlace
1 1 1 46 1
4 1 2 46 4
2 1 3 46 2
3 1 4 46 3
listElement:
listElemId | listElemName | listId | listElemDesc | listElemOrderPlace
1 Elem A 1 Derp NULL
2 Elem B 1 Herp NULL
3 Elem C 1 Lorum NULL
4 Elem D 1 Ipsum NULL
Note: 'listElemOrderPlace' in the table listElement is the final order of the elements (all users average), not to be mixed with the one with the same name in the other table, that's only a specific user's order of the list elements (which is the interesting one in this case).
You forgot to add listElemId to your join criteria:
FROM listElement AS le
INNER JOIN listElemOrder AS lo
ON le.listId = lo.listId
AND le.listElemId = lo.listElemId -- add this criterion
Since the columns are named identically in both tables, you can abbreviate to:
FROM listElement AS le
INNER JOIN listElemOrder AS lo
USING (listId, listElemId)
This second form also has the advantage of avoiding "ambiguous column" errors when there is in fact no ambiguity.

How to numerate a Hierachical Table of Contents in PHP out of a parent-child (adjacency) table

I'm trying to numerate/create or generate a Table of contents in PHP out of a MySQL database in an adjacency tree model (id, parent_id). So far this is what I have achieved when echoing the output.
1. Category 1
1 Subcategory 1
2 Subcategory 2
3 Subcategory 3
2. Category 2
1. Subcategory 1
1. Subcategory Subcategory 1
2. Subcategory Subcategory 2
2 Subcategory 2
1 Subcategory 1
2 Subcategory 2
I'm very close, but what the output I want is:
1. Category 1
1.1 Subcategory 1
1.2 Subcategory 2
1.3 Subcategory 3
2. Category 2
2.1. Subcategory 1
2.1.1. Subcategory Subcategory 1
2.1.2. Subcategory Subcategory 2
2.2 Subcategory 2
2.2.1 Subcategory 1
2.2.2 Subcategory 2
In other words, I want to use the Table of contents format in a multilevel hierarchical structure as follows: Chapter.Subchapter.Subchapter.Subchapter TITLE.
I have tried by using a recursive array that keeps the current index and concatenates to the previous index but what it ends up adding a weird long number before each item, such,
0.11.2..11.2.3.4.5.6.7..11..11.2.3.4.5.6 Computers,
when instead it should be just:
2.7.6 Computers.
(The other numbers are the numbers of the other items)
This is the code I've been working on
renumber(0,0,1,0);
function renumber($parent_id,$level=0,$counter=1) {
// Counter level keeps track of the current index number
$counterlevel[$level]=$counter;
$query = "SELECT defaultTitle, id, pid FROM defaultChapters WHERE pid=".$parent_id;
$res = mysql_query($query) or die(mysql_error());
// Exit if there are no tree leafs
if(mysql_num_rows($res) == 0) {return;}
while (list ($title, $id) = mysql_fetch_row($res))
{
$leveltext[$level][$counterlevel[$level]] = $section.".".$counterlevel[$level];
echo str_repeat("......",$level)." ".$counterlevel[$level]." ".$section." ".$title."<BR>";
// Increase the counter of the current level
$counterlevel[$level]++;
// Initialize the level counter
if(!$counterlevel[$level+1]) {
$counterlevel[$level+1] = 1;
}
// Start the function again to find children
renumber($id,$level+1,$counterlevel[$level+1]);
} // End While
}
I have browsed all the technical support forums, including this one and it seems nobody has ever published an algorithm for this, simply there is no sample code for this anywhere to be found. There are hundreds of tutorials and codes to get a hierarchical tree in php out of a mysql database without numbering but nothing about numerating a hierarchical Table of Contents in php.
Is it possible to do it with a SQL query too?
I would refactor it a little bit and pass along the numbering up to the current invocation:
function renumber($parent_id = 0, $level = 0, $prefix = '')
{
// we don't need pid in the results
$query = "SELECT defaultTitle, id
FROM defaultChapters
WHERE pid=$parent_id";
$res = mysql_query($query) or die(mysql_error());
// Exit if there are no tree leafs
if (mysql_num_rows($res) == 0) {
return;
}
// start numbering at 1
$nr = 1;
while (list($title, $id) = mysql_fetch_row($res)) {
// dropped section, not sure where it was used
echo str_repeat("......", $level) . " $prefix.$nr $title<BR>";
// Start the function again to find children
renumber($id, $level + 1, strlen($prefix) ? "$prefix.$nr." : "$nr.");
// advance list numbering
++$nr;
}
}
renumber();

Categories