Prepared Statement in PHP MVC - php

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
}
}

Related

How to tree strcture function in PHP for OOPS?

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?

Separate hashtags and text PHP

Hi this is an example of my table, I want to be able to search like this: #mat#godis and be able to get the 2 results having those tags (ID 5 and ID 7)
This i my PHP code so far and it works while searching for example (Globen), (Globen #mat) and (#mat)
//Strip Search
function selectSearch($search){
$lol = preg_replace('/(^|\s)#(\w*[a-zA-Z_]+\w*)/'," ",$search);
$keywords = explode(" ", preg_replace("/\s+/", " ", $lol));
foreach($keywords as $keyword){
$wherelike[] = "name LIKE '%$keyword%'";
}
$search = implode(" and ", $wherelike);
return $search;
}
//Strip tags
function selectTags($tags){
$str = $tags;
$bits = explode(' ', $str);
$newquery = array();
foreach($bits as $bit){
if(strlen($bit) > 0 && $bit[0] === '#') $newquery[] = $bit;
}
$newquery = implode('', $newquery);
$keywords = explode(" ", preg_replace("/\s+/", " ", $newquery));
foreach($keywords as $keyword){
$wherelike[] = "tags LIKE '%".ltrim($keyword,'#')."%'";
}
$tags = implode(" or ", $wherelike);
return $tags;
}
It returns (searching for #mat#godis)
select * from stores where name LIKE '%%' and name LIKE '%#godis%' and tags LIKE '%mat#godis%'order by id desc limit 8
I want the query to be something like this
select * from stores where name LIKE '%%'and tags LIKE '%mat%' and tags LIKE '%godis%' order by id desc limit 8
This design is really bad. You need to use a relation table. The problem is, if you try to search %#mat%, then, if there is a tag like #matheus, that will also give you result.
Of course, there are workaround for this situation, but that is too complicated.
And i do not understand what is this: name LIKE '%%'
So what you need is to create table, as ChrisForrence mentioned, called store_tag like this:
CREATE TABLE IF NOT EXISTS `store_tag` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag` varchar(255) NOT NULL COMMENT 'Tag like #mat',
`user_id` int(11) NOT NULL COMMENT 'The foreign ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
After this, you need to write a little script, what is loop thorugh on all the records in your original table, get out the id and the tags, explodes the tags, insert it individually in your new table store_tag.
And then, use:
SELECT * FROM store_tag, orig_table INNER JOIN origTable ON origTable.id = store_tag.user_id WHERE tag = '#mat' OR tag = '#godis'
(The query above is not tested, this is just for example, and of course you do not need to SELECT *).
This will give you back the desired results.
It's much more easier to maintain your data.
Of course befor this creat a backup from your existing database.
As I mentioned in my comment, I would create a separate table for tags, like such:
# The original table. Notice the lack of a 'tags' column
CREATE TABLE `stores` (
`id` INTEGER UNSIGNED AUTO_INCREMENT,
`name` VARCHAR(64) UNIQUE,
PRIMARY KEY(`id`)
);
# A new table, relating stores to tags
CREATE TABLE `store_tags` (
`store_id` INTEGER UNSIGNED,
`label` VARCHAR(64),
PRIMARY KEY(`store_id`, `label`),
FOREIGN KEY(`store_id`) REFERENCES `stores`(`id`) ON DELETE CASCADE
);
From here, you can use a variation of the following query to check for stores matching all of the tags:
SELECT * FROM `stores`
WHERE `id` IN (SELECT `store_id` FROM `store_tags` WHERE `label`='mat')
AND `id` IN (SELECT `store_id` FROM `store_tags` WHERE `label`='godis')
ORDER BY `id` DESC LIMIT 8
You can use this to split the tags out into the proper WHERE clause, along with an array of tags used as input parameters for your PDO query
/* Returns an array containing the WHERE clauses, along
* with the tags themselves (used to populate the input parameters for PDO)
*/
function searchTags($in) {
$rQ = '';
// Filter out empty array input elements
$tags = array_filter(explode('#', $in));
if(!count($tags)) {
return false;
}
$first = true;
foreach($tags as $tag) {
$rQ .= ($first ? ' WHERE' : ' AND') . ' `id` IN (SELECT `store_id` FROM `store_tags` WHERE `label`=?)';
if($first) $first = false;
}
return array($rQ, $tags);
}

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.

PHP: Active record table joins

I have an app that uses the codeigniter CXTags tagging library.
The database structure is as follows:
posts
id
tags_ref
row_id
table
tag_id
tags
id
safe_tag
tag
My query basically goes if $safe_tag is not null then join tags_ref on post.id = tags_ref.row_id, join tags on tags_ref.tag_id = tags.id, where tags_ref.table = 'posts' and tags.safe_tag = 'food'
SELECT * FROM posts
JOIN tags_ref ON posts.id = tags_ref.row_id
JOIN tags ON tags_ref.tag_id = tags.id
WHERE tags.safe_tag = $safe_id
Unfortunately the query I've written in active record is not functioning properly. The query works perfectly when £safe_tag is null but when it's not I get wrong results.
function get_posts($id = NULL, $safe_tag = NULL) {
if($safe_tag != NULL){
echo $safe_tag;//debugging
$table = 'posts';
$this->db->join('tags_ref', 'posts.id = tags_ref.row_id');
$this->db->join('tags', 'tags_ref.tag_id = tags.id');
$this->db->where('tags_ref.table', $table);
$this->db->where('tags.safe_tag',$safe_tag);
}
//if an id was supplied
if ( $id != NULL ) {
$this->db->where('posts.city_id',$id);
}
// execute query
$query = $this->db->get('posts');
...
Here is the query with profiling on:
SELECT *
FROM (`posts`)
INNER JOIN `tags_ref` ON `posts`.`id` = `tags_ref`.`row_id`
INNER JOIN `tags` ON `tags_ref`.`tag_id` = `tags`.`id`
WHERE `tags_ref`.`table` = 'posts'
AND `tags`.`safe_tag` = 'food'
AND `posts`.`city_id` = '2'
Can someone have a look? I think I need a fresh set of eyes on it.
Your forgot to actually run the query inside your first if{}
if($safe_tag != NULL){
echo $safe_tag;//debugging
$table = 'posts';
$this->db->join('tags_ref', 'posts.id = tags_ref.row_id');
$this->db->join('tags', 'tags_ref.tag_id = tags.id');
$this->db->where('tags_ref.table', $table);
$this->db->where('tags.safe_tag',$safe_tag);
$this->db->get(); // here
}

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