MySQL Query in PHP - php

This is my actual query in MySQL:
SELECT sndprefix, COUNT(*) FROM `inbox` WHERE
(
sndprefix='22' OR
sndprefix='23' OR
sndprefix='32' OR
sndprefix='33' OR
sndprefix='34' OR
sndprefix='42' OR
sndprefix='43'
)
GROUP BY sndprefix;
I'm getting the CORRECT response such as:
sndprefix COUNT(*)
22 3
23 5
32 1
33 1
43 1
My question is what is the PHP code to show this query in the browser and to be parsed as json format too.
Thanks in advance.
Apology if I have not posted the code earlier, I was trying to familiarize myself with stackoverlow's UI finding the edit option..
Here's the code I've tried so far:
<?php
$query="SELECT sndprefix, COUNT(*) FROM `inbox` WHERE
(
sndprefix='22' OR
sndprefix='23' OR
sndprefix='32' OR
sndprefix='33' OR
sndprefix='34' OR
sndprefix='42' OR
sndprefix='43'
)
GROUP BY sndprefix;";
$result = mysql_query($query);
while($sunget = mysql_fetch_array($result)){
foreach($sunget AS $key => $value) { $conget[$key] = stripslashes($value); }
echo "". nl2br( $sunget['sndprefix']) ."";
}
?>
Thanks.

Well .. looks like question got updated.
You are doing it wrong .. at multiple levels. I guess bad tutorials are at fault.
The query
You should read more about WHERE clause, and how to use it. This page will give some basic info, but you should find some book about MySQL (and only about MySQL, no PHP included).
This would make the query much easier to read:
SELECT
sndprefix AS prefix,
COUNT(*) AS count
FROM inbox
WHERE sndprefix IN ('22', '23', '32', '33', '34', '42', '43')
GROUP BY sndprefix;
Also, you might want to learn about JOIN statements in MySQL, because I get a feeling, that this list of specific prefixes has something in common.
And since the prefixes are repeating, they might be better off in a separate table. At least from normalization point of view.
Querying database in PHP
You should not writing new code with the ancient mysql_* functions. They are no longer maintained and community has begun the deprecation process, and now there is even has been a warning added in manual: (see the red box) .
Instead you should learn about prepared statements and use either PDO or MySQLi. If you cannot decide, this article will help to choose. If you care to learn, here is a quite good PDO-related tutorial.
Lets assume that you have use PDO to interact with database (I just prefer it over MySQLi).
$connection = new PDO(
'mysql:host=localhost;dbname=my_magc_db;charset=UTF-8',
'username', 'password');
$connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// the connection has been established, and object configured
$sql = 'SELECT
sndprefix AS prefix,
COUNT(*) AS count
FROM inbox
WHERE sndprefix IN ('22', '23', '32', '33', '34', '42', '43')
GROUP BY sndprefix';
$statement = $connection->query( $sql );
// no need to prepare the statement this time, because values are predefined
// if values '22', '23', etc. depend on user input, code would be different
$data = $statement->fetchAll(PDO::FETCH_ASSOC);
// now $data contains all the values.
// you van use var_dump($data) to "look inside" variable
making JSON
Since $data already is an array with all the values, you can simply write this echo json_encode( $data ); to generate JSON.
making output
Since you have already the array, you can just write:
foreach ( $data as $row ) {
echo $row['prefix'], ' ', $row['count'], PHP_EOL, '<br>';
}
Any questions ?

Related

PHP/Mysql: Array instead of stdClass Object

I have the below code:
$result = $conn->query($sql)->fetch(PDO::FETCH_COLUMN);
$myArray = explode(',', $result);
$sql = ("SELECT username,status,location FROM Users WHERE username IN ('" . implode("','",$myArray) . "') AND status > 0 ORDER BY username");
$data = $conn->query($sql)->fetchAll(PDO::FETCH_OBJ);
print_r ($data);
FYI $result is "kk,bb"
The output is as follows:
Array ( [0] => stdClass Object ( [username] => bb [status] => 1 [location] => ) [1] => stdClass Object ( [username] => kk [status] => 1 [location] => ) )
But I really just want to see a two-dimensional array without it saying stdClass Object in front of each item. I tried different kinds of fetching, but this is the only one I could make work. I know there are functions for converting object to array, but I´d rather if there´s a way to do this right, if there is such a thing, in the pasted code.
I hope someone can help, thanks in advance!
You requested objects by using PDO::FETCH_OBJECT. If you want to fetch as arrays, use another fetch type:
$data = $conn->query($sql)->fetchAll(PDO::FETCH_ASSOC);
or
$data = $conn->query($sql)->fetchAll(PDO::FETCH_NUM);
The other choices are documented here: http://php.net/manual/en/pdostatement.fetch.php
By the way, why do you run one query to get the values in $myArray, then use these in an SQL-injection vulnerable way in a second query? Wouldn't it be easier to use a JOIN?
Re your comment:
Fist of all, I strongly urge you to code safely the first time, don't rely on "going back later." You probably won't have time to go back later, because once the code seems to work "good enough," the temptation is to go live immediately. Even if you do go back later to fix security flaws, you might miss one. Either way, your development process creates a high risk that you will go live with insecure code.
A piece of old advice from famous computer scientist Andrew S. Tanenbaum: "Security, like correctness, is not an add-on feature."
Now about joins. If you use SQL, you should understand how to do a joins. Not knowing joins in SQL is like thinking you know PHP even though you don't know how to use foreach(). Technically, you can still do some stuff, but you're missing out on a major part of the language.
Joins allow you to query multiple tables in one query, and find rows from each table that match.
In your example, I don't know your first query, but I'll take a guess that it queries some other table to get a list of usernames. Maybe it queries a list of users who are recipients of a email, like this:
SELECT username FROM EmailRecipients WHERE EmailId = 1234
Then your code uses the result list of usernames in a second query. But you can do it all in one query:
SELECT Users.username, Users.status, Users.location
FROM Users JOIN EmailRecipients
ON Users.username = EmailRecipients.username
WHERE EmailRecipients.EmailId = 1234
The SQL engine searches the EmailRecipients table and say it finds six rows for the recipients of email 1234. For each of the six rows, it looks up the corresponding row in the Users table with the matching username. Then it uses columns from those rows in the SELECT result.
When you join tables, there's a possibility both tables might have some columns with the same name. That's why I showed column named by qualifying them as belonging to one table or the other. This removes the ambiguity about which table you meant in each case.
It doesn't matter which table you list first. This type of join is the same left-to-right as it is right-to-level, like some expressions in algebra (2 + 4 is the same as 4 + 2, etc.). MySQL automatically figures out which table is most efficient to read first.
There's more to learn about joins, but that should get you started. I suggest you pick up a tutorial or a book on SQL sometime.
Re your comment:
Where is the "SQL-injection vulnerable way in a second query"?
In your code:
$result = $conn->query($sql)->fetch(PDO::FETCH_COLUMN);
$myArray = explode(',', $result);
$sql = ("SELECT username,status,location FROM Users WHERE username IN ('" . implode("','",$myArray) . "') AND status > 0 ORDER BY username");
$data = $conn->query($sql)->fetchAll(PDO::FETCH_OBJ);
There's no guarantee that the elements in $myArray are safe to use in an SQL statement. Just because they came out of the database doesn't make them safe. What if they contain names like "O'Reilly"? That will upset your quote-implosion.
You can never be 100% sure the data is safe, so it's better to use query parameters. Then you don't care if it's safe, because bound parameters are never combined with the SQL query string.
Here's how to create a dynamic list for the IN ( ) predicate from an array, and bind parameter values to the query using PDO:
$result = $conn->query($sql)->fetch(PDO::FETCH_COLUMN);
$myArray = explode(',', $result);
$placeholders = implode(',', array_fill(0, count($myArray), '?'));
$sql = "
SELECT username,status,location FROM Users
WHERE username IN ($placeholders) AND status > 0
ORDER BY username");
$stmt = $conn->prepare($sql);
$stmt->execute($myArray);
$data = $stmt->fetchAll(PDO::FETCH_OBJ);
While both answers are perfectly valid, I just wanted to add in that there is another solution to your issue.
You can change the default fetch style by defining it where you connect to the database:
$host = 'localhost';
$user = 'user';
$pass = 'pass';
$mydb = 'mydbname';
$conn = new PDO("mysql:host=$host;dbname=$mydb", $user, $pass, [
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
Or you can change it by using setAttribute() using the existing connection:
$conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
Then you can simply use:
$conn->query($sql)->fetchAll();
And you still have the ability to overwrite the default value:
$conn->query($sql)->fetchAll(PDO::FETCH_NUM);
using PDO::FETCH_ASSOC you can make it as an array. So
$data = $conn->query($sql)->fetchAll(PDO::FETCH_ASSOC);
Documentation link:
PDO Statement

php : Issue on multidimensional array with PMA

i'm working with php (ubuntu software).
I have some data into a PMA database, that i extract like this :
<?php
try
{
$db = mysql_connect('localhost', 'dbName', 'dbPwd');
mysql_select_db('dbName', $db);
}
catch(Exception $e)
{
die('Erreur : '. $e->getMessage());
}
$sql = "SELECT p.ean13, pl.id_product, pl.name FROM $product_lang pl INNER JOIN $product p ON pl.id_product = p.id_product WHERE name LIKE '1 agapanthe %'";
$result_agpt = mysql_query($sql, $db);
$data = mysql_fetch_row($result_agpt);
Now that I have my data into an array like this :
ean13 - id_product - name
NULL - 5389 - 1 agapanthe ivoire //////////////////////////////////////////////////////////////////////////////////////////////////////////////
4027234262147 - 5387 - 1 agapanthe lilas ////////////////////////////////////////////////////////////////////////////////////////////////
NULL - 5388 - 1 agapanthe parme ////////////////////////////////////////////////////////////////////////////////////////////////////////////
NULL - 5386 - 1 agapanthe violet //////////////////////////////////////////////////////////////////////////////////////////////////////////////
I want to display, for each row of my result, the following informations :
ean13 : NULL ////
id_product : 5389 ////
product_name : 1 agapanthe ivoire
And for each of this row, i must truncate the name like this :
"1 agapanthe ivoire" => "1 agapanthe"
I'm doing this using the preg_match function, as below :
foreach($data as $value)
{
// preg_match to truncate product names : '1 agapanthe verte' => '1 agapanthe'
$pattern = '/^(1\sagapanthe)\s(\w*)/is';
$match_truncate = preg_match($pattern, $data[2], $matches);
$name = $matches[1];
echo "Name : ".$name; echo "<br />";echo "<br />";
$attribute = $matches[2];
echo "Attribut : ".$attribute; echo "<br />";
I tried several ways of doing this foreach, but i can't manage to make it work.
EDIT : My aim here is to truncate every name of my product, as to modify its content into PMA. Should I consider using PDO over mysql connection ?
Any help or comment would be greatly appreciated :) !
Regards,
Stelio Kontos.
You appear to have several issues here. First, and most important is stop using the mysql database extension. Instead use mysqli or pdo. The older mysql extension is deprecated in all supported version of PHP, and has been dropped from PHP 7.0.
Second the manner in which you built your SQL string is susceptible to SQL Injection. It is always a bad idea to concatenate data that could come from a user into an sql statement string. Even if it passes through the database first. Best way is to use parameterized sql strings which is available in mysqli and pdo (but not mysql).
Here is a link to the docs http://php.net/manual/en/function.mysql-fetch-row.php
But to answer your question, using the mysql extension, the fetch needs to be inside the loop. It returns a single row at a time as an array with numerical indexes. So after:
$data = mysql_fetch_row($result_agpt);
$data will contain
[0] => 'NULL',
[1] => 5389,
[2] => '1 agapanthe ivoire'
The foreach is incorrect in this instance. You will need to do something more like
$data = mysql_fetch_row($result_agpt);
while($data !== false) {
$ean13 = $data[0];
$id = $data[1];
$name = $data[2];
// do something with it
$data = mysql_fetch_row($result_agpt);
}
Finally, regular expressions are hard to get just right. I use an online regex tester like the one at https://regex101.com/ to make sure my patterns work. Here is one I think might work for you /([0-9]+ \w+) (\w+)/is.
Not sure if I understand the question properly, but if you want to display only portion of the value from the database column pl.name, you could rework the SQL query:
$sql = "SELECT p.ean13, pl.id_product, '1 agapanthe' FROM $product_lang pl INNER JOIN $product p ON pl.id_product = p.id_product WHERE name LIKE '1 agapanthe %'"
The above query will display always 1 agapanthe as product name, no need for complex regular expressions here ;)
EDIT:
You can provide the portion for the LIKE clause dynamically and then use it to select it as column:
$like = "something";
$sql = "SELECT p.ean13, pl.id_product, '".$like."' FROM $product_lang pl INNER JOIN $product p ON pl.id_product = p.id_product WHERE name LIKE '".$like."%'";
Also - do not forget to escape the contents, if they are going to be user-defined or use prepared statements and PDO to access/query your database. This way you will be protected from SQL injection attacks.

Values from mysql php array not being displayed

This is probably very simple and I apologise in advance, if its a silly question.
I working on a web page, and I have a database which I am calling data from that in the most part is working fine, however I need to use a mysql query which isn't just select all or specific fields:
$sqlAuthors = ('SELECT authors.Forename, authors.Surname
FROM authors
LEFT JOIN BookAuthor ON authors.AuthorID = BookAuthor.AuthorID
LEFT JOIN Books ON BookAuthor.BookID = Books.BookID
WHERE Books.BookID="'.$loopnumber.'"');
This query I've tested on the database and works fine, the query will in cases have multiple records.
The code I have is:
$conn = mysql_connect('localhost', 'root', '');
mysql_select_db('library', $conn);
$sql = "SELECT * FROM Books";
$result = mysql_query($sql, $conn) or die("Can't run query");
$loopnumber = 1;
if (mysql_num_rows($result) ==0 ){echo "No Books have been found";}
else
{
while ($row = mysql_fetch_object($result))
{
$sqlAuthors = ('SELECT authors.Forename, authors.Surname
FROM authors
LEFT JOIN BookAuthor ON authors.AuthorID = BookAuthor.AuthorID
LEFT JOIN Books ON BookAuthor.BookID = Books.BookID
WHERE Books.BookID="'.$loopnumber.'"');
$Authors = mysql_query($sqlAuthors,$conn);
echo'<article class="topContent">
<header><h2>'.$row->BookName.'</h2></header>
<footer><p class="post-info">Written By: '.print_r(mysql_fetch_array($Authors)).' '.$Authors[Forename].'</p></footer>
The last line being where I try to output the values.
However if I have $Authors[Forename] it gives me the error:
Notice: Use of undefined constant Forename - assumed 'Forename' in I:\xamptest\htdocs\Library\index.php on line 51
Even though I have:
print_r(mysql_fetch_array($Authors))
and that works and outputs:
Array ( [0] => J.R.R [Forename] => J.R.R [1] => Tolkien [Surname] => Tolkien )
So it should recognise it, also the array print out appears no where near the line it is meant to be in, not even n the same DIV, but where it should be there is just "1" echoed.
If I try:
'.$Authors[1].'
Then I get no errors but nothing is printed out.
No idea if it'll help but heres what the section of the site page looks like: http://i.imgur.com/mMEQke5.png
Should be printing the Authors name where it says "Written By:" but instead just has "1".
I'm still fairly new to this, but it has me stumped.
All help much appreciated -Tom
A great amount of your code needs cleaning up, but we can focus on that after fixing the problem at hand.
$Authors = mysql_query($sqlAuthors,$conn);
That returns a result set from the query, no issues there (besides mysql_query() being deprecated, but we can talk about that later :)
Now, let's assume for a moment a book could have multiple authors, as is the case in many reference texts, and since you're already looping over all the books you are returning. Let's just go ahead and put our new result set inside a similar loop to what you've already done:
while($author_row = mysql_fetch_array($Authors)){
echo $author_row['Forename'];
}
The code above is totally incomplete, but hopefully it's a starting point for what you need to do.
Just a few items of note: mysql_*() is deprecated, so switch over to using the mysqli API as soon as you can! People will start yelling at you here if you don't ;)
Make sure you're using prepared statements after cleaning up for security. Easy to protect against if you do it right, but sql injections are nasty.

A more efficient way of binding a big insert or update?

Ok so im new to binding, here is some code that works. I learned this format from a tutorial but i imagine there is more efficent ways to do it. In my example there is 4 names but in reality i will be doing a lot of inserts and updates in a project im working on that will have 20 or so fields. I like this approach for its clarity but obviously when your talking 20 fields or more it does take a lot of real estate. Lets look at my code first.
Here are the functions it uses:
// prepare the statement
public function query($query){
$this->stmt = $this->dbh->prepare($query);
}
public function bind($param, $value, $type = null){
if (is_null($type)) {
switch (true) {
case is_int($value):
$type = PDO::PARAM_INT;
break;
case is_bool($value):
$type = PDO::PARAM_BOOL;
break;
case is_null($value):
$type = PDO::PARAM_NULL;
break;
default:
$type = PDO::PARAM_STR;
}
}
// run the binding process
$this->stmt->bindValue($param, $value, $type);
}
// execute the prepared statement
public function execute(){
return $this->stmt->execute();
}
and now the actual code
$database->query("
INSERT INTO users(
user_name,
user_password_hash,
user_email,
user_activation_hash
)
VALUES(
:user_name,
:user_password_hash,
:user_email,
:user_activation_hash
)
");
// bind the values
$database->bind(":user_name", "$this->user_name");
$database->bind(":user_password_hash", "$this->user_password_hash");
$database->bind(":user_email", "$this->user_email");
$database->bind(":user_activation_hash", "$this->user_activation_hash");
// execute the statement and insert the values into the database
$database->execute();
it just cries out for a loop, especially since i have a habit of calling post fields, input fields, variables and placeholders the same name, not sure if that is a good or a bad thing but i find its helpful for me when dealing with large forms which i will be.
in any case i could do something like this:
$placeholder_array = array(
"user_name" => "\$this->user_name",
"user_password_hash" => "\$this->user_password_hash",
"user_email" => "\$this->user_email",
"user_activation_hash" => "\$this->user_activation_hash"
);
// well use this copy to edit the array keys and keep original for the binding
$placeholder_copy = $placeholder_array;
// turn the array into a string i.e user_name, user_password_hash....
$fields = implode (", ", array_keys($placeholder_array));
// foreach to add : placeholder prefix for binding
foreach ($placeholder_copy as $key => $value){
$placeholder_copy [':'.$key] = $value;
unset($placeholder_copy[$key]);
}
// turn the copy array which has prefix :user_name into a string
$placeholders = implode (", ", array_keys($placeholder_copy));
$database->query("
INSERT INTO users($fields)
VALUES($placeholders)
");
// bind the values
foreach ($placeholder_copy as $bind_values => $value){
echo '$database->bind("'.$bind_values.'", "'.$value.'");' . "<br />";
}
// execute the statement and insert the values into the database
$database->execute();
i could then turn this into a function with parameters for passing in the associative array and the table name to keep my main code much cleaner.
Now imagine i am going to be doing any amount of these as the project im working on involves tons of big forms submitting data to users. I'm new to PDO and trying to grasp it so there maybe a simpler way of structuring these types of queries, i had a look on google and stackflow but i didnt really get what they were doing so i thought doing my own one would allow people to explain to me better what is going on, i would rather get this right starting my project than have to go back and change everything later. So is there a better approach or is this one ok?
Really appreciate any feedback and im glad now i took peoples advice on here and made the move to PDO.
No, sadly, but PDO offers no help for the matter.
Yet your own approach doesn't seem to me an efficient one either.
First of all, let me indicate that your set of functions is quite useless. I understand no such direct rewriting of API functions. PDO already doing all these things.
With raw PDO you can have even more concise code:
$stm = $pdo->prepare("INSERT INTO users VALUES(NULL,?,?,?,?)");
$data = array($this->user_name,
$this->user_password_hash,
$this->user_email,
$this->user_activation_hash
);
$stm->execute($data);
Regarding your dynamical query building, I find it too much bloated yet still insecure. Some flaws are
there is no point in bothering with ":named" placeholders as they are supposed to be human-readable but no human supposed to read them here. Not to mention PDO will convert them back into ?s before sending to mysql
another objection against named placeholders - while Mysql (as well as HTTP) can let you have a space in a field name all right, a placeholder with a space will just crash your query.
there is no real benefit in this code from your approach of the equal names - you are bound to write them by hand a dozen times each, mind you.
yet if it would be used, you have no white list to check your field list against, which could be a great security breach
too much code
no support for UPDATE query which could be extremely handy as well (if you are using mysql)
when you put this code into function, you will slip to the same bad grounds as others, tempted by the possibility of saving you whole two words!
Here is my earlier approach for the matter:
$allowed = array(
"user_name", "user_password_hash", "user_email", "user_activation_hash"
);
$sql = "INSERT INTO users SET ".pdoSet($allowed, $values);
$stmt = $dbh->prepare($sql);
$stmt->execute($values);
And here goes my current approach, the best of the best (though implemented using mysqli, not PDO), using custom placeholder for the array data:
$placeholder_array = array(
"user_name" => $this->user_name,
"user_password_hash" => $this->user_password_hash,
"user_email" => $this->user_email,
"user_activation_hash" => $this->user_activation_hash
);
$db->query("INSERT INTO users SET ?u", $placeholder_array);
or, in case of direct connection between form fields and SQL columns
$allowed = array(
"user_name", "user_password_hash", "user_email", "user_activation_hash"
);
$insert = $db->filterArray($_POST,$allowed);
$db->query("INSERT INTO users SET ?u", $insert);
This way let me use INSERT IGNORE, INSERT DELAYED, INSERT.. ON DUPLICATE UPDATE, UPDATE, UPDATE with join, and innumerable other options, supporting full SQL language.
What I've done recently and continuing to improve, is building a helper class to simplify PDO based SQL statements in my applications.
Lets take a tour with your example. You want to insert data into a user table:
INSERT INTO users(
user_name,
user_password_hash,
user_email,
user_activation_hash
) VALUES(
:user_name,
:user_password_hash,
:user_email,
:user_activation_hash
)
For inserting a single record, my SQL is constructed like this:
function myInsertSingle($PDO, $table, $ins_array) {
$SQL = "INSERT INTO `".$table."` (".dbFieldList($ins_array)
.") VALUES (".dbValuePList($ins_array).")";
...continued with preparing.
$PDO: My PDO connection.
$table: The table I want to insert to.
$ins_array: A structured array for inserting data.
For your example, the array $ins_array will look like this:
$ins_array = array(
"user_name" => "your_user",
"user_password_hash" => "your_pw_hash",
"user_email" => "your#mail.xyz",
"user_activation_hash" => "your_other_Hash"
);
Notice the similarity to your array!
Two functions are involved. The first one gives me a list of (escaped) fieldnames.
function dbFieldList($fields) {
$set = '';
foreach ($fields as $field => $item) {
$set .= "`".$field."`,";
}
return rtrim($set, ',');
}
Basically from the above array example, the function returns
`user_name`, `user_password_hash`, `user_email`, `user_activation_hash`
...which is the list of the fields needed in the INSERT-statement.
The other one does something similar for the values.
function dbValuePList($fields) {
$set = '';
foreach ($fields as $field => $item) {
$set .= ":".$field.",";
}
return rtrim($set, ',');
}
You guessed it, the result looks like this:
:user_name, :user_password_hash, :user_email, :user_activation_hash
Now you have your SQL statement which you can prepare. To bind the values, if I understand your class correctly, just use a loop which looks like this:
foreach ($ins_array as $field => $item) {
$PDO->bind(':'.$field,$item);
}
Now you can execute the statement.
Summary. Basically with my approach a single INSERT statement is reduced to this:
$ins_array = array(
"user_name" => "your_user",
"user_password_hash" => "your_pw_hash",
"user_email" => "your#mail.xyz",
"user_activation_hash" => "your_other_Hash"
);
myInsertSingle($PDO, 'your_table', $ins_array);
If this looks just like another piece of overhead, keep in mind, I use these functions in other SQL statements like SELECT, UPDATE, DELETE and so on.

How do you embedded your sql queries in php scripts (coding-style)?

how do you embed your sql scripts in php? Do you just write them in a string or a heredoc or do you outsource them to a sql file? Are there any best practices when to outsource them ? Is there an elegant way to organize this?
Use a framework with an ORM (Object-Relational Mapping) layer. That way you don't have to put straight SQL anywhere. Embedded SQL sucks for readability, maintenance and everything.
Always remember to escape input. Don't do it manually, use prepared statements. Here is an example method from my reporting class.
public function getTasksReport($rmId, $stage, $mmcName) {
$rmCondition = $rmId ? 'mud.manager_id = :rmId' : 'TRUE';
$stageCondition = $stage ? 't.stage_id = :stageId' : 'TRUE';
$mmcCondition = $mmcName ? 'mmcs.username = :mmcName' : 'TRUE';
$sql = "
SELECT
mmcs.id AS mmc_id,
mmcs.username AS mmcname,
mud.band_name AS mmc_name,
t.id AS task_id,
t.name AS task,
t.stage_id AS stage,
t.role_id,
tl.id AS task_log_id,
mr.role,
u.id AS user_id,
u.username AS username,
COALESCE(cud.full_name, bud.band_name) AS user_name,
DATE_FORMAT(tl.completed_on, '%d-%m-%Y %T') AS completed_on,
tl.url AS url,
mud.manager_id AS rm_id
FROM users AS mmcs
INNER JOIN banduserdetails AS mud ON mud.user_id = mmcs.id
LEFT JOIN tasks AS t ON 1
LEFT JOIN task_log AS tl ON tl.task_id = t.id AND tl.mmc_id = mmcs.id
LEFT JOIN mmc_roles AS mr ON mr.id = t.role_id
LEFT JOIN users AS u ON u.id = tl.user_id
LEFT JOIN communityuserdetails AS cud ON cud.user_id = u.id
LEFT JOIN banduserdetails AS bud ON bud.user_id = u.id
WHERE mmcs.user_type = 'mmc'
AND $rmCondition
AND $stageCondition
AND $mmcCondition
ORDER BY mmcs.id, t.stage_id, t.role_id, t.task_order
";
$pdo = new PDO(.....);
$stmt = $pdo->prepare($sql);
$rmId and $stmt->bindValue('rmId', $rmId); // (1)
$stage and $stmt->bindValue('stageId', $stage); // (2)
$mmcName and $stmt->bindValue('mmcName', $mmcName); // (3)
$stmt->execute();
return $stmt->fetchAll();
}
In lines marked (1), (2), and (3) you will see a way for conditional binding.
For simple queries I use ORM framework to reduce the need for building SQL manually.
It depends on a query size and difficulty.
I personally like heredocs. But I don't use it for a simple queries.
That is not important. The main thing is "Never forget to escape values"
You should always really really ALWAYS use prepare statements with place holders for your variables.
Its slightly more code, but it runs more efficiently on most DBs and protects you against SQL injection attacks.
I prefer as such:
$sql = "SELECT tbl1.col1, tbl1.col2, tbl2.col1, tbl2.col2"
. " FROM Table1 tbl1"
. " INNER JOIN Table2 tbl2 ON tbl1.id = tbl2.other_id"
. " WHERE tbl2.id = ?"
. " ORDER BY tbl2.col1, tbl2.col2"
. " LIMIT 10, 0";
It might take PHP a tiny bit longer to concatenate all the strings but I think it looks a lot nicer and is easier to edit.
Certainly for extremely long and specialized queries it would make sense to read a .sql file or use a stored procedure. Depending on your framework this could be as simple as:
$sql = (string) View::factory('sql/myfile');
(giving you the option to assign variables in the view/template if necessary). Without help from a templating engine or framework, you'd use:
$sql = file_get_contents("myfile.sql");
Hope this helps.
I normally write them as function argument:
db_exec ("SELECT ...");
Except cases when sql gonna be very large, I pass it as variable:
$SQL = "SELECT ...";
$result = db_exec ($SQL);
(I use wrapper-functions or objects for database operations)
$sql = sprintf("SELECT * FROM users WHERE id = %d", mysql_real_escape_string($_GET["id"]));
Safe from MySQL injection
You could use an ORM or an sql string builder, but some complex queries necessitate writing sql. When writing sql, as Michał Słaby illustrates, use query bindings. Query bindings prevent sql injection and maintain readability. As for where to put your queries: use model classes.
Once you get to a certain level, you realise that 99% of the SQL you write could be automated. If you write so much queries that you think of a properties file, you're probably doing something that could be simpler:
Most of the stuff we programmers do is CRUD: Create Read Update Delete
As a tool for myself, I built Pork.dbObject. Object Relation Mapper + Active Record in 2 simple classes (Database Abstraction + dbObject class)
A couple of examples from my site:
Create a weblog:
$weblog = new Weblog(); // create an empty object to work with.
$weblog->Author = 'SchizoDuckie'; // mapped internally to strAuthor.
$weblog->Title = 'A test weblog';
$weblog->Story = 'This is a test weblog!';
$weblog->Posted = date("Y-m-d H:i:s");
$weblog->Save(); // Checks for any changed values and inserts or updates into DB.
echo ($weblog->ID) // outputs: 1
And one reply to it:
$reply = new Reply();
$reply->Author = 'Some random guy';
$reply->Reply = 'w000t';
$reply->Posted = date("Y-m-d H:i:s");
$reply->IP = '127.0.0.1';
$reply->Connect($weblog); // auto-saves $reply and connects it to $weblog->ID
And, fetch and display the weblog + all replies:
$weblog = new Weblog(1); //Fetches the row with primary key 1 from table weblogs and hooks it's values into $weblog;
echo("<h1>{$weblog->Title}</h1>
<h3>Posted by {$weblog->Author} # {$weblog->Posted}</h3>
<div class='weblogpost'>{$weblog->Story}</div>");
// now fetch the connected posts. this is the real magic:
$replies = $weblog->Find("Reply"); // fetches a pre-filled array of Reply objects.
if ($replies != false)
{
foreach($replies as $reply)
{
echo("<div class='weblogreply'><h4>By {$reply->Author} # {$reply->Posted}</h4> {$reply->Reply}</div>");
}
}
The weblog object would look like this:
class Weblog extends dbObject
{
function __construct($ID=false)
{
$this->__setupDatabase('blogs', // database table
array('ID_Blog' => 'ID', // database field => mapped object property
'strPost' => 'Story', // as you can see, database field strPost is mapped to $this->Story
'datPosted' => 'Posted',
'strPoster' => 'Author',
'strTitle' => 'Title',
'ipAddress' => 'IpAddress',
'ID_Blog', // primary table key
$ID); // value of primary key to init with (can be false for new empty object / row)
$this->addRelation('Reaction'); // define a 1:many relation to Reaction
}
}
See, no manual SQL writing :)
Link + more examples: Pork.dbObject
Oh yeah i also created a rudimentary GUI for my scaffolding tool: Pork.Generator
I like this format. It was mentioned in a previous comment, but the alignment seemed off to me.
$query = "SELECT "
. " foo, "
. " bar "
. "FROM "
. " mytable "
. "WHERE "
. " id = $userid";
Easy enough to read and understand. The dots line up with the equals sign keeping everything in a clean line.
I like the idea of keeping your SQL in a separate file too, although I'm not sure how that would work with variables like $userid in my example above.

Categories