|
home—lects—exams—hws
D2L—breeze (snow day)
From PHP Visual Quickstart Guide by Larry Ullman.
Originally base on notes by Jack Davis (jcdavis@radford.edu)
This chapter will focus on accessing MySql database tables from PHP programs. A database is a collection of tables (rows & columns) that store information. Databases are created, updated, and read using SQL (Structured Query Language). There are surprisingly few commands in SQL. SQL was designed to be written a lot like the English language, which makes it very user friendly; but it does take some thought to create more elaborate SQL statements with only the handful of available terms.
The process is quite simple: PHP is used to send SQL statements to the database application, where they are executed. The result of the execution - the creation of a table, the insertion of a record, the retrieval of some records, or even an error - is then returned by the database to the PHP script.
$conn = mysqli_connect(hostname, username, password, schemaToUse ); // @param hostname For php.radford.edu, use 'localhost' // @param username the database account, which is the same as your RU userid // @param password the *database* password. // @param schemaToUse your RU userid (again!) -- it's a DB account *and* a DB-schema. |
If the database connection fails, you want to throw an exception:
A common expediency is to crash — by calling
$connect = mysqli_connect(...) || die('connect failed.'); echo "The connection ", ($connect ? "" : "NOT "), "established.\n"; |
Common strategy:
Put your '
One kludgy way of doing this is to have the include-file store
the connection it creates in a special variable — perhaps
“
Better,
I suggest having that file define a function named something like
It's also possible to simply have the external file
define a couple variables
(like
$var = 'Amy Johnson'; $qu = "INSERT INTO $tablename (numItems,product,custName) VALUES (17,'abc','$var')"; $allRows = mysqli_query($connect, $qu); |
SELECT -
$qu = "SELECT * FROM $tablename WHERE (product = 'abc')"; |
while ($oneRow = mysqli_fetch_array($allRows)) { # Note use of `=` for assignment *and* return value echo $oneRow['custName'], " bought ", $oneRow['product']; // We could also use $oneRow[0], $oneRow[1], $oneRow[2] but it's preferred to use column-name when possible. } |
See also:
You might want to check for an error — something like the database complaining that it wasn't a valid SQL statement, or that the database connection had been lost (after being previously created).
$allRows = mysqli_query($connect, $qry); // A table object (possibly w/ 0 rows); NULL on query-failure. if (!$allRows) echo "query failed -- lost connection?"; |
If you know your query should always return 1 row, so you won't have a loop.
Other times, you just want to know whether the query returned 0 or 1 rows
(e.g., you queried something like
$allRows = mysqli_query($connect, $qry); // A table object (possibly w/ 0 rows); NULL on query-failure. if (!$allRows) echo "query failed -- lost connection?"; $topRow = mysqli_fetch_array($allRows); if (!$topRow) { echo "table had 0 rows" } else { echo "table had (at least) 1 row". } |
$allRows = mysqli_query($connect, $qry); // A table object (possibly w/ 0 rows); NULL on query-failure. if (!$allRows) echo "query failed -- lost connection?"; $numRows = mysqli_num_rows($allRows); if ($numRows == 0) { echo "table had 0 rows" } else if ($numRows == 1) { echo "table had exactly 1 row". $topRow = mysqli_fetch_array($allRows); } else { echo <<<END_OF_MSG Table had more than 1 row. If I think my query shouldn't every yield > 1 row, then I might just have built my query incorrectly. OR, if I incorporated user-input when building my query, then my code may have just succumbed to a SQL-injection attack! Read below, about using |
$qu = "DELETE FROM $tablename WHERE (field1 = 'abc')"; |
$qu = "UPDATE $tablename SET $field_name1 = 'abc' , $field_name2 = '$newval' WHERE ($field_name1 = '$var')"; $allRows = mysqli_query($connect, $qu); |
Warning: In order to get a Foreign Key constraint, you must create both tables involved with the Storage Engine option as "InnoDB". (Sadly, on php.radford.edu, this isn't installed, and you have to settle for the MyISAM engine which doesn't allow Foreign Key constraints.) Then, after the table is created, you can add the FK constraint one of two ways:
- Via a raw SQL command: In yourTable > SQL, type in the exact SQL command, such as:
-- To create a FK alter table Goo add constraint Goo_ij_FK Foreign key Goo(i,j) references Foo(m,n) -- To delete: alter table Goo drop foreign key Goo_ij_FK -- it seems odd to me, to say 'drop foreign key ...' rather than 'drop constraint ...'.- Via the GUI: In yourTable > Structure > Relation View, in the column to the right of “internal”, you can set the foreign key. You can't use the GUI approach if your key involves a composite key.
Warning: MySQL ignores check constraints — it lets you specify them, but ignores them w/o letting you know(!).
Rumor: I saw one reasonable-sounding post that claimed that you also have to build an index on both the source and destination key-columns.
tip: If googling problems, stackoverflow tends to have better-curated answers than most sites. (e.g. this one)
$theItem = "coffee"; $theQuery = "SELECT * FROM Inventory WHERE (product = $theItem)"; $results = mysqli_query($connect, $theQuery ); |
This is a very common sort of error, and
it can be detected/debugged by printing
A2: If somebody had entered the four-character string “M&Ms” we would not want want to look in our database for something including the characters “amp”. (Our database should include the correct name of the product; we'll turn “M&Ms” into “M&Ms” only when we display that on a web page — but not in emails, internal reports, etc..)
A3: What if the user had entered a name that contains a single-quote? See below, re SQL Injection!
Here is some typical code which validates a user, by looking up their name/password in a table we have previously created:
$user = $_POST['username']; $passwd = $_POST['password']; $qry = "SELECT * FROM MyPasswords WHERE name='$user' and pass='$passwd'"; // DANGER! SQL injection possible. $result = mysqli_query($connect,$qry); // A table object (possibly w/ 0 rows); NULL on query-failure. if (mysqli_fetch_assoc($result)) { // Safer: `if (mysqli_num_rows($result) == 1) ...` // login successful -- we found that name/password in our db! } |
$qry = "SELECT * FROM MyPasswords WHERE name='fred' and pass='xyz123'"; |
BUT:
What if the user had typed in a single-quote in their input?
Suppose they typed O'Toole — why will the query fail?
WORSE:
Can the user type in something so that the SELECT statment
always returns a result?
Alas, yes!
Something like:
' or 'hi' = 'hi leads to a query that always turns true.
$qry = "SELECT * FROM MyPasswords WHERE name='fred' and pass='' or 'hi' = 'hi'";2 |
Warning: Always callmysqli_real_escape_string on any user-provided string that is getting integrated into a database query.
Now you may feel that if you've already done other validation
on the data and know it doesn't contain any single-quotes or other
characters SQL might mis-interpret,
then you're safe.
That's true.
However, you should still call
(Another reason to call
Why use prepared statements (in order of importance):
What they are:
SQL statements where you replace specific values with a question-mark
(“
Example:
Note:Don't confuse “prepared statement” with “stored procedure” — the latter is just a PL/SQL function. (They are similar though — both set up what is in essence a function-call, so you can just call it with actual-parameters later. However, a prepared-statement only lives while you keep a reference to it while the stored-procedure is something the database remembers forever (and is PL/SQL code, not a SQL query).
In php, prepared statements are a bit convoluted. You have to:
$stmt = mysqli_stmt_init($link); $stmt = mysqli_stmt_prepare($stmt, "SELECT numOnShelf,seller from Inventory where Product=? and containerSize>?"); |
$stmt = mysqli_prepare($link, "SELECT numOnShelf,seller from Inventory where Product=? and containerSize>?"); |
In php, you must specify the type of each input (which is the second argument to
mysqli_stmt_bind_param($stmt, 'sd', $productName, $minSize); $productName = "M&M's"; $minSize = 37; |
Note that the contents of the input-variables (“
mysqli_stmt_fetch($stmt) // If successful, puts the (next) result-row into the php variables $col1,$col2. |
Securing the data stored in the database.
Suppose we store passwords or credit-cards in our database.
What might someday happen (inferring from headlines)?
Sadly, somebody might breach the db itself (aside from the website entirely) —
either a hacker exploiting a DB security hole,
or somebody accidentaly revealing the DB account password,
or an inside employee stealing data.
Solution:
Don't store the password in the database!
“But Barland, how the heck can we verify that they've typed in the
correct password?”
We'll instead store a hashed form of the password.
Then, when a user later types in the password on a form,
we'll hash whatever they type,
and see if the two hashes are equal.
php > echo hash('sha256', 'hello'); 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 php > echo hash('sha256', 'helmo'); 05c21a42da66f4e2d217e35b74ef9dc2d65c8d46f9aec0d6397a65dd3c766f21 |
Of course, to help protect against brute-force dictionary attacks,
you want to add a few extra "salt" characters to the password — say, 20 characters.
And ideally, these should be different characters for each person
(else, if an attacker found out the one salt you used, they could
use that on every word in the dictionary and look for matches).
…
But it's admittedly a bit of a hassle, to generate a salt for each user,
and store that in the database as well as the hashed password.
Solution:
The function
php > echo password_hash("kittee",PASSWORD_BCRYPT); $2y$10$Pj/26IW2mmF7LrA7WZGKeOf7YMIX5EKT7h8bhoBCwQs67PB5DDhUi php > echo password_verify("kittee",'$2y$10$Pj/26IW2mmF7LrA7WZGKeOf7YMIX5EKT7h8bhoBCwQs67PB5DDhUi'); 1 php > echo password_verify("doge",'$2y$10$Pj/26IW2mmF7LrA7WZGKeOf7YMIX5EKT7h8bhoBCwQs67PB5DDhUi'); php > echo password_hash("kittee",PASSWORD_BCRYPT); $2y$10$Ije9mY4Mm9LwEFWSySGBmOGjzg1/Y6VLYLPnsmWVuWHzoYdiz5lyGFor more details, read the php docs: us.php.net/manual/en/faq.passwords.php.
1
Well, in this case, there are two other minor differences:
to create the connection
call “
2
A saucier attacker would type in something like
“
home—lects—exams—hws
D2L—breeze (snow day)
©2015, Ian Barland, Radford University Last modified 2015.Nov.11 (Wed) |
Please mail any suggestions (incl. typos, broken links) to ibarlandradford.edu |