How to tree strcture function in PHP for OOPS? - php

I am creating a comment and reply system in PHP using the tree concept.
I have created a comment table in my database -
CREATE TABLE `comments` (
`cm_id` int(11) NOT NULL,
`cm_message` text NOT NULL,
`postid` int(11) DEFAULT NULL,
`user_id` int(11) NOT NULL,
`created` datetime NOT NULL,
`parentid` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
As you can see, I have contained parent id and comment id with post id.
My tree function
function fetchCategoryTree($parentid = 0,$user_tree_array = '') {
global $db;
global $postid;
if (!is_array($user_tree_array))
$user_tree_array = array();
$stmt = $db->prepare('SELECT
comments.cm_message, comments.cm_id,comments.parentid,comments.created,users.username, users.image
FROM comments
INNER JOIN users ON comments.user_id = users.user_id
WHERE comments.postid=:postid AND parentid =:parentid ORDER by comments.cm_id DESC');
$stmt->execute(array('parentid' => $parentid,
'postid' => $postid
));
if($stmt->rowCount() > 0) {
while ($row = $stmt->fetchObject()) {
$user_tree_array[] = array("cm_id" => $row->cm_id,"image" => $row->image,"parentid" => $row->parentid,"username" => $row->username,"created" => $row->created, "cm_message" =>$row->cm_message);
$user_tree_array = fetchCategoryTree($row->cm_id, $user_tree_array);
}
}
return $user_tree_array;
}
Calling a function
$categoryList = fetchCategoryTree();
It's working but,
As you can see I have made a global postid in the function.
if I give the postid inside
$categoryList = fetchCategoryTree($postid);
and change the function look like -
function fetchCategoryTree($postid) {
global $db;
$parentid = 0;
$user_tree_array = '';
}
then it doesn't work.
I want to convert it to OOPS class and method but it's not working like this.
The main points are here -
I am inserting comments and replies in the same table.
I am fetching comments and replies by postid using while loop on the same page -
like -
post 1
comments for post 1
with reply
post 2
comments for post 2
with reply
post n
comments for n post
with reply n
What are the best ways to do that in OOPS?
class and method
How can I use postid at the calling time without any errors?

Related

Using a variable in a select query

The Problem
I am trying to create a website, where users can post posts and then their friends can see these posts. Similar to facebook, twitter etc.
I got to the point where users can be friends and they can post things. However, I am stuck restricting the shown posts to just the user's friends' posts. At the moment every user can see every post.
I am using PDO in a MVC architecture.
The database structure
TABLE `friends` (
`friendship_id` int(11) NOT NULL AUTO_INCREMENT,
`user_one` int(11) NOT NULL,
`user_two` int(11) NOT NULL,
PRIMARY KEY (`friendship_id`),
FOREIGN KEY (user_one) REFERENCES users(user_id),
FOREIGN KEY (user_two) REFERENCES users(user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
In the table above, depending on who sent the friend request to who, 'user_one' can be either myself or my friend or 'user_two' can be myself or my friend.
TABLE `posts` (
`post_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`post_text` text NOT NULL,
`user_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`post_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
The Model
/**
* Get all posts from the suer's friends
* #return array an array with several objects (the posts)
*/
public static function getAllPosts()
{
$my_id = Session::get('user_id');
$database = DatabaseFactory::getFactory()->getConnection();
// get all of the user's friends
$friendsQuery = $database->prepare("
SELECT user_one FROM friends WHERE user_two = '$my_id';
SELECT user_two FROM friends WHERE user_one = '$my_id';
");
$friendsQuery->execute();
$friends = $friendsQuery->fetchAll();
$friendsQuery->closeCursor();
$query = $database->prepare("SELECT * FROM posts WHERE user_id = '$my_id' OR user_id = '$friends'");
$query->execute(array());
return $query->fetchAll();
}
In the above code my problem lies in the $friends variable. If I replace it manually with the user_id of my friend it works as intended.
E.g.:
"SELECT * FROM posts WHERE user_id = '$my_id' OR user_id = '1'";
The Controller
/**
* Gets all posts (of the user and his friends).
*/
public function index()
{
$this->View->render('post/index', array(
'posts' => PostModel::getAllPosts()
));
}
The View
<?php if ($this->posts) { ?>
<?php foreach($this->posts as $key => $value) { ?>
<p><?= htmlentities($value->post_text); ?></p>
<?php } ?>
<?php } ?>
Summary
I am not able to create a sql query, which just selects posts, which were posted by myself or my friends and does not select any other posts.
I would be very thankful, if somebody could help me out with this!
UPDATE
My model now looks like this:
/**
* Get all posts from the suer's friends
* #return array an array with several objects (the posts)
*/
public static function getAllPosts()
{
$my_id = Session::get('user_id');
$database = DatabaseFactory::getFactory()->getConnection();
// get all of the user's friends
$friendsQuery = $database->prepare("
SELECT user_one FROM friends WHERE user_two = '$my_id';
SELECT user_two FROM friends WHERE user_one = '$my_id';
");
$friendsQuery->execute();
$friends = $friendsQuery->fetchAll();
print_r($friends);
$friendsQuery->closeCursor();
foreach($friends as $friend)
{
$friend_ids[$friend->key] = $friend->val;
}
print_r($friend_ids);
$friendsSQL = implode(',',$friend_ids);
$query = $database->prepare("SELECT * FROM posts WHERE user_id = '$my_id' OR user_id IN('$friendsSQL')");
$query->execute();
return $query->fetchAll();
}
This is my output:
Array ( [0] => stdClass Object ( [user_one] => 1 ) )
Notice: Undefined property: stdClass::$key
Notice: Undefined property: stdClass::$val
Array ( [] => )
You can add all friends_id into an array and then use this code.
//$friends is an array.
$friendsSQL = implode(',',$friends);
$query = $database->prepare("SELECT * FROM posts WHERE user_id = '$my_id' OR user_id IN($friendsSQL)");
You should note that $friendsQuery->fetchAll() will return an Array. Also, if PDO::FETCH_ASSOC is supplied as an additional argument [fetchAll(PDO::FETCH_ASSOC)], it would be an associative array.
The structure of the array could look like this :
Array ( [int] datasetNumber =>
Array (
[string] "friendship_id" => [int] 1,
[string] "user_one" => [int] 1,
[string] "user_two" => [int] 1
)
)
I gave some more Information about returning data from PDO Statements in this post.
your $friends variable is being returned by a fetchAll() function so will probably be a list of results. Your then using that list as a string in the query which won't work. You need to extract the ID of the friend from the $friends list.
if you're using PDO then use the variable handling built into PDO ie:
$query = $database->prepare("SELECT * FROM posts WHERE user_id = :variable_ID OR user_id = '1'");
$query->bindValue( ':variable_ID', $my_id, PDO::PARAM_STR ] );
$query->execute();

Prepared Statement in PHP MVC

I am trying to create a simple forum in a MVC architecture.
This is my database setup (the relevant part):
Table: forum_categories
`forum_categories` (
`cat_id` INT(8) NOT NULL AUTO_INCREMENT,
`cat_title` VARCHAR(255) NOT NULL,
`cat_desc` TEXT NOT NULL,
PRIMARY KEY (`cat_id`),
UNIQUE KEY (`cat_title`)
Table: forum_topics
`forum_topics` (
`topic_id` INT(8) NOT NULL AUTO_INCREMENT,
`cat_id` INT(8) NOT NULL COMMENT 'foreign key with forum_categories table',
`user_id` INT(11) NOT NULL COMMENT 'foreign key with users table',
`topic_title` VARCHAR(255) NOT NULL,
`topic_desc` TEXT NOT NULL,
`topic_date` DATETIME DEFAULT NULL,
PRIMARY KEY (`topic_id`),
FOREIGN KEY (`cat_id`) REFERENCES forum_categories (`cat_id`) ON DELETE CASCADE ON UPDATE CASCADE
Example of the functionality, I would like to achieve:
Category 1 has cat_id = 1
Category 2 has cat_id = 2
Topic 1 has cat_id = 1
Topic 2 has cat_id = 2
Now when category 1 is selected I just want topic 1 to show.
If category2 is selected I just want topic 2 to show.
This prepared SQL statement achieves that:
PREPARE stmnt FROM
'SELECT *
FROM forum_categories fc
JOIN forum_topics ft ON fc.cat_id = ft.cat_id
WHERE fc.cat_id = ?
ORDER BY ft.topic_date DESC';
SET #a = 1;
EXECUTE stmnt USING #a;
My Problem: I would like to move this functionality into my PHP MVC structure.
Here is my attempt, which does not work (it shows all topics in all categories).
Controller
/**
* Show all the topics in the chosen category
*/
public function showForumTopics()
{
$topic_model = $this->loadModel('Forum');
$this->view->forum_topics = $topic_model->getForumTopics();
$this->view->render('forum/viewTopics');
}
Model
/**
* Gets an array that contains all the forum topics in the database.
* Each array element is an object, containing a specific topic's data.
* #return array All the forum topics
*/
public function getForumTopics($cat_id)
{
$sql = 'SELECT * FROM forum_categories fc JOIN forum_topics ft ON fc.cat_id = ft.cat_id WHERE fc.cat_id = :cat_id ORDER BY ft.topic_date DESC';
$query = $this->db->prepare($sql);
$query->execute(array(':cat_id' => $cat_id));
return $query->fetchAll();
}
View
if ($this->forum_topics) {
foreach($this->forum_topics as $key => $value) {
echo '<p><strong>Title:</strong>' . $value->topic_title . '</p>';
echo '<p><strong>Description:</strong> ' . $value->topic_desc . '</p>';
echo '<p><strong>Author:</strong> ' . $value->topic_author . '</p>';
echo '<p><strong>Date:</strong> ' . $value->topic_date . '</p>';
}
} else {
echo 'No forum topics.';
}
Help would be highly appreciated! Thank you!!
For example, your page http://example.com/?cat_id=2
Your code should be like this
Controller
public function showForumTopics()
{
$default_category = 1;
$topic_model = $this->loadModel('Forum');
$cat_id = isset($_GET['cat_id']) ? $_GET['cat_id'] : $default_category;
$this->view->forum_topics = $topic_model->getForumTopics($cat_id);
$this->view->render('forum/viewTopics');
}
Model
public function getForumTopics($cat_id) {
$sql = 'SELECT * FROM forum_categories fc JOIN forum_topics ft ON fc.cat_id = ft.cat_id WHERE fc.cat_id = :cat_id ORDER BY ft.topic_date DESC';
$query = $this->db->prepare($sql);
$query->execute(array(':cat_id' => $cat_id));
return $query->fetchAll(); }
View
if ($this->forum_topics) {
foreach($this->forum_topics as $key => $value) {
echo '<p><strong>Title:</strong>' . $value->topic_title . '</p>';
echo '<p><strong>Description:</strong> ' . $value->topic_desc . '</p>';
echo '<p><strong>Author:</strong> ' . $value->topic_author . '</p>';
echo '<p><strong>Date:</strong> ' . $value->topic_date . '</p>';
}
} else {
echo 'No forum topics.';
}
Your problem is that your backend requires and ID to pull the specific category (and via the join, the correct topic). In your DB Query, you are looking for it here: WHERE fc.cat_id = ?
Your getForumTopics($cat_id) function also requires the ID to pass into that prepared statement. Problem is you aren't passing any ID into that function when you call it:
$this->view->forum_topics = $topic_model->getForumTopics();
So without anything coming through, your function is now broken and should be throwing an error. You have two options at this point:
Provide an ID from the page, through the controller to your function (hint, you'll have to add it to the URL like #kringeltorte suggested so the page knows what to load!)
Make a backup that lists all topics when no specific category is chosen. You'd do that with something like this in your function definition:
// Specifying the backup option null here, for when there is no ID passed
public function getForumTopics($cat_id = null) {
// Simple check for an ID
if ($id) {
// Run the code you had before
$sql = 'SELECT * FROM forum_categories fc JOIN forum_topics ft ON fc.cat_id = ft.cat_id WHERE fc.cat_id = :cat_id ORDER BY ft.topic_date DESC';
$query = $this->db->prepare($sql);
$query->execute(array(':cat_id' => $cat_id));
return $query->fetchAll(); }
} else {
// Otherwise, do the same thing as above but without a WHERE clause
}
}

Echoing Sorted Multidimensional Array

Ok, so I am creating a web app with php and mysqli.
I have a table friends which is a simple set up:
f_id int(11)
uid int(11)
fids TEXT
now its basically like a row for each user with the fids consisting of a lot of numerical values (other userids) separated by commas like: 1,2,3
so I use this function to get each user's friends:
function getFriends($db, $userid)
{
$q = $db->query("SELECT fids FROM friends WHERE uid='$userid'");
$ar = $q->fetch_assoc();
$friends = $ar['fids'];
$fr = explode(",", $friends);
return $fr;
}
but each posts comments that appear to each of their friends. my problem comes from trying to sort these comments by the time they were posted.
lets say my comments table is:
c_id int(11)
uid int(11)
c_text TEXT
c_time int(11)
I want to be able to get the comments posted by each 'friend' put them all into an array together, then sort them from their c_time value, then all the values from that particular row in the comments table.
The problem comes from my how I've set up my friends table.
I'm using:
$fr = getFriends($db, $userid);
$updates = array();
$i = 0;
foreach( $fr as $friend)
{
// Get Updates from friends and from self
$q = $db->query("SELECT up.*, u.* FROM updates up
LEFT JOIN users u ON u.id = '$friend'
WHERE (up.userid = '$userid') ORDER BY up.up_id DESC");
while($ar = $q->fetch_array(MYSQLI_BOTH))
{
$updates[$i] = $ar;
$i++;
}
}
$sortArray = array();
foreach($updates as $update){
foreach($update as $key=>$value){
if(!isset($sortArray[$key])){
$sortArray[$key] = array();
}
$sortArray[$key][] = $value;
}
}
$orderby = "up_id";
array_multisort($sortArray[$orderby],SORT_DESC,$updates);
$updates_limit = array_slice($updates, 0, 20);
to get the comments from each friend, sorting it by time, then slicing it to the first 20.
However when I var_dump($updates_limit) it takes the last row in the comments table, and then makes it look like each friend posted the same comment.
Can anyone see the problem or a better way of addressing this issue?
I'd completely refactor the friends table to look something more like this: (Also, use english - Characters are cheap :c))
CREATE TABLE friends (
user_id int FOREIGN KEY REFERENCES user(id)
, friend_id int FOREIGN KEY REFERENCES user(id)
, PRIMARY KEY (user_id, friend_id)
);
Then you can take essentially the same comment table:
CREATE TABLE comment (
comment_id int PRIMARY KEY
, user_id int FOREIGN KEY REFERENCES user(id)
, comment_text text
, comment_time datetime
);
And your "query for friend's comments" becomes:
SELECT comment_id, comment.user_id, comment_text, comment_time
FROM friends
INNER JOIN comment
ON comment.user_id = friends.friend_id
WHERE friends.user_id = ? #Target it
ORDER BY comment_time DESC
LIMIT 0, 20;
You can even speed this up by adding a few indexes - like comment(user_id).

PHP Recursive Find siblings

working with this for a while.. cant get my head right.. sooo... help here ;-)
It is quite simple I am sure..
Table:
CREATE TABLE IF NOT EXISTS `items` (
`item_id` bigint(20) NOT NULL AUTO_INCREMENT,
`item_parent_id` bigint(20) NOT NULL COMMENT 'itemid which this item belongs to',
`item_name` varchar(255) NOT NULL,
`item_serialnumber` varchar(255) NOT NULL,
`item_model` varchar(255) NOT NULL,
PRIMARY KEY (`item_id`),
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
I am trying to create an array of item_id and the item_id that it belongs to - via the item_parent_id - recursivly -
so that even if you find a child to a parent, check if the child is a parent to others.
Tried with something like this:
function get_item($item_id, $menu)
{
$sql = "
SELECT
items.*,
customers.*
FROM
assets
LEFT JOIN item_customer_rel USING(item_id)
LEFT JOIN customers USING(customer_id)
WHERE
items.item_parent_id = '".$parent."'
ORDER BY
items.item_name
";
$res = mysqli_query($db, $sql) or die("ERROR: SQL Select a2a ancestor", $sql, mysqli_error($db) , $_SESSION["u_id"]);
while ($items = mysqli_fetch_assoc($res))
$menu = build_ancestor_array($parent, $menu);
}
function build_ancestor_array($parent, $menu)
{
GLOBAL $db;
$sql = "
SELECT
items.*,
customers.*
FROM
items
LEFT JOIN item_customer_rel USING(item_id)
LEFT JOIN customers USING(customer_id)
WHERE
items.item_parent_id = '".$parent."'
";
$res = mysqli_query($db, $sql) or cc("ERROR: SQL Select a2a ancestor", $sql, mysqli_error($db) , $_SESSION["u_id"], $this_document);
while ($items = mysqli_fetch_assoc($res))
{
if ($ancestor_item_array[$parent] == $items["item_id"])
$menu = build_ancestor_array($parent, $menu);
$ancestor_item_array[$parent] = $items["item_id"];
// Creates entry into items array with current menu item id ie. $menu['items'][1]
$menu['items'][$items['item_id']] = $items;
$menu['items'][$items['item_id']]["connection_type"] = 2;
// Creates entry into connected_to array. connected_to array contains a list of all items with connected_to
$menu['connected_to'][$items['item_parent_id']][] = $items['item_id'];
}
return $menu;
} // end build item array
It only goes one "level" down.
Refer, the 2 links below, I had recently posted answers for these, this is done in pure SQL
Recursive MySQL Query with relational innoDB
and
How to find all child rows in MySQL?
Recursive worked.. Just needed to try manually with pen and paper ;-)
function get_item_data($parent, $menu, $ancestor_item_array = "")
{
GLOBAL $db;
$sql = "
SELECT
items.*,
customers.*
FROM
items
LEFT JOIN item_customer_rel USING(item_id)
LEFT JOIN customers USING(customer_id)
WHERE
items.item_parent_id = '".$parent."'
ORDER BY
items.item_name
";
$res = mysqli_query($db, $sql) or cc("ERROR: SQL Select a2a ancestor", $sql, mysqli_error($db) , $_SESSION["u_id"], $this_document);
while ($items = mysqli_fetch_assoc($res))
{
$ancestor_item_array[] = $items["item_id"];
if (!in_array($items["item_parent_id"], $ancestor_item_array))
$menu = get_item_data($items["item_id"], $menu, $ancestor_item_array);
// Creates entry into items array with current menu item id ie. $menu['items'][1]
$menu['items'][$items['item_id']] = $items;
$menu['items'][$items['item_id']]["connection_type"] = 2;
// Creates entry into connected_to array. connected_to array contains a list of all items with connected_to
$menu['connected_to'][$items['item_parent_id']][] = $items['item_id'];
}
}
It wont work on pure SQL.
You should take a look at stored procedures, the sql you're trying to make will only go 'inwards' one level because all the relations will be shown as if they're first level connections.
for example.
parent->son->grandson->ggson
parent.item_parent_id = null
son.item_parent_id = parent
grandson.item_parent_id = son
ggson.item_parent_id = grandson
even tough grandson is a lower level connection, he will show up as a first level connection.
it cant be done with pure sql, sadly..
that's one of the reasons that made me go to NOSQL databases.

products categories

I have a simple question but I don't know which term I should use to find the answer (english is not my first language).
I have a classical database design of products like and categories.
CREATE TABLE IF NOT EXISTS `a` (
`id_a` int(11) NOT NULL auto_increment,
`type` varchar(255) NOT NULL,
PRIMARY KEY (`id_a`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=12 ;
CREATE TABLE IF NOT EXISTS `b` (
`id_b` int(11) NOT NULL,
`id_a` int(11) NOT NULL,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id_b`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Where b.id_a is a foreign key to a.id_a
I want to get a hierarchy of all thoses like
A VALUE 1
b_value_1
b_value_2
A VALUE 2
b_value_11
b_value_12
A VALUE 3
b_value_21
b_value_22
b_value_23
The request doesn't matters but I get this kind of anwser:
VALUEOF-TABLE-A | VALUEOF-TABLE-B
A VALUE 1 | b_value_1
A VALUE 1 | b_value_2
and so on.
My current code is something like:
$categ = '';
while ($row = mysql_fetch_row ($ressource))
{
if ($row['VALUEOF-TABLE-A']!=$categ)
{
$categ = $row['VALUEOF-TABLE-A'];
echo '<h3>', $categ, '</h3>';
}
echo '<h4>', $row['VALUEOF-TABLE-B'], '</h4>';
}
But I don't like much the idea of the categ variable, be it a string or an id.
Is there an other way to get the data and display them?
Ideally, I'de like a tree object, having only one node for identical children.
Hope you understood what I want.
When working with foreign keys in Mysql, you should use the InnoDB engine instead of MyISAM.
There seems to be a problem in the conception of the b table, id_b should be the primary key, not id_a.
To solve your problem, maybe you should first retrieve the list of id_a, then make one selection request by id_a to select the corresponding id_b using a JOIN.
EDIT : the script should look like this with a little more presentation :
$category_array = mysql_query("SELECT id_a, type FROM a");
while ($category = mysql_fetch_array($category_array))
{
echo $category['type'];
$product_array = mysql_query("SELECT * FROM b WHERE id_a = $id_a");
while ($product = mysql_fetch_array($product_array))
{
echo $product['name'];
}
}
The example Chico gives is on the right track, but has a problem: when there are many categories, the script will also execute many queries, which is inefficient. The following example is much more efficient:
$categories = array();
$category_array = mysql_query("SELECT id_a, type FROM a");
while ($category = mysql_fetch_array($category_array))
{
$category['products'] = array();
$categories[$category['id_a']][] = $category;
}
$product_array = mysql_query("SELECT * FROM b");
while ($product = mysql_fetch_array($product_array))
{
$categories[$product['id_a']]['products'][] = $product
}
foreach($categories as $category) {
echo $category['type'];
foreach ($category['products'] as $product) {
echo $product['name'];
}
}
As an added bonus, this also separates the retrieval of the data more cleanly from the output.

Categories