Tải bản đầy đủ - 0 (trang)
Your Binary Data Isn’t Safe to Insert...Yet

Your Binary Data Isn’t Safe to Insert...Yet

Tải bản đầy đủ - 0trang

$image_mime_type = $image_info['mime'];

$image_size = $image['size'];

$image_data = file_get_contents($image['tmp_name']);



Your Binary

Data Isn’t

Safe to

Insert...Yet



$insert_image_sql = "INSERT INTO images " .

"(filename, mime_type, file_size, image_data) " .

"VALUES ('{mysql_real_escape_string($image_filename)}', ".

"'{mysql_real_escape_string($image_mime_type)}', " .

"'{mysql_real_escape_string($image_size)}', " .

"'{mysql_real_escape_string($image_data)}');";

mysql_query($insert_image_sql);

 Note  You don’t need mysql_real_escape_string for the $image_size, because it’s a numeric



value. However, if you’re constantly trying to remember whether input data is a string or a number, you’re

eventually going to make a mistake and not escape something you should.

To be safe, just escape everything. It’s more consistent, and it’s another layer of protection. The time it takes PHP

to escape that one bit of data is trivial compared to the problems if malicious data goes unescaped.



Printing a String to a Variable

As natural as this code looks, it’s got a serious problem. Even though the curly

braces surrounding a variable will allow that variable to be printed inside a string

(for example, "{$variable}" prints the value of $variable), PHP draws the line

at doing actual work inside the curly braces. As such, it won’t interpret the call to

mysql_real_escape_string.

You have two ways to get around this. The first is the easiest: you could just move the

calls to mysql_real_escape_string up into the variable assignments, sort of like this:

// Insert the image into the images table

$image = $_FILES[$image_fieldname];

$image_filename = mysql_real_escape_string($image['name']);

$image_info = getimagesize($image['tmp_name']);

$image_mime_type = mysql_real_escape_string($image_info['mime']);

// and so on...



This also looks OK, but it’s not a good idea. Do you see why?

Think about the function you’re calling: it’s specifically for getting values set up to

work with MySQL. However, what if you want to use $image_filename somewhere

else in your script? You’ve turned this variable into a MySQL-specific version of the

file name.

It seems like the original approach—converting the variable by using mysql_real_

escape_string as it’s going into the actual SQL INSERT statement—is the right one.

It allows the variable to just be the image file name, or the image MIME type, and

then you convert that into a MySQL-friendly value when that’s required.

Chapter 10: Binary Objects and Image Loading



www.it-ebooks.info



297



Your Binary

Data Isn’t

Safe to

Insert...Yet



That seems to indicate there’s a need for a way to perform calculations or run functions on values when you’re constructing your SQL string—and there is. You usually

do so by using sprintf, which is a PHP function that prints to a string. In other words,

you construct a string by using any calculations you need and pass all the required

information to sprintf. The sprintf function puts everything together and returns

a string, which you can then assign to your variable, and boom, you’re then ready

to pass that variable in to mysql_query.

How does this work? Well, it’s a little different than anything you’ve done so far.

Instead of just building the string up via concatenation, you indicate the entire string

that you want to create, but every time you come to a spot in the string where you

want to include the value of a variable, you put in a special type specifier. For example, you use %s for a string type:

$hello = sprintf("Hello there, %s %s", $first_name, $last_name);

echo $hello;



Suppose $first_name is “John” and $last_name is “Wayne.” Running a script with

these two lines would give you:

Hello there, John Wayne



The sprintf function replaces the first %s with the first value after the string, which

is $first_name. Then, it replaces the second %s with the second value after the string,

$last_name. Finally, the entire string with the values inserted—is assigned to $hello.

What’s great about sprintf is that you can perform calculations on variables before

you pass them to sprintf. The following example might be a bit silly, but the code

is perfectly legal:

$hello = sprintf("Hello there, %s", $first_name . ' ' . $last_name);

echo $hello;



Of course, there are much better ways to use sprintf, like creating a query string

and using mysql_real_escape_string in the process:

// This replaces the older assignment to $insert_sql

$insert_sql = sprintf("INSERT INTO users " .

"(first_name, last_name, email, " .

"bio, facebook_url, twitter_handle) " .

"VALUES ('%s', '%s', '%s', '%s', '%s', '%s');",

mysql_real_escape_string($first_name),

mysql_real_escape_string($last_name),

mysql_real_escape_string($email),

mysql_real_escape_string($bio),

mysql_real_escape_string($facebook_url),

mysql_real_escape_string($twitter_handle));



298



PHP & MySQL: The Missing Manual



www.it-ebooks.info



Your Binary

Data Isn’t

Safe to

Insert...Yet



// Insert the user into the database

mysql_query($insert_sql)

or die(mysql_error());



This code doesn’t do anything noticeably different than your older version. This is

because the data being inserted into users was probably not a problem in the first

place. But now, you can take this same approach and apply it to your insertion into

images.

$insert_image_sql = sprintf("INSERT INTO images " .

"(filename, mime_type, " .

"file_size, image_data) " .

"VALUES ('%s', '%s', %d, '%s');",

mysql_real_escape_string($image_filename),

mysql_real_escape_string($image_mime_type),

mysql_real_escape_string($image_size),

mysql_real_escape_string($image_data));

mysql_query($insert_image_sql)

or die(mysql_error());



You can guess what %d means to sprintf: replace that type specifier with a decimal

number, like 1024 or 92048. Thus, this code builds up an INSERT, executes it, and

escapes your values in the process.

POWER USERS’ CLINIC



sprintf Is Your New Best Friend

Most PHP programmers use sprintf initially because it lets

them do things like use mysql_real_escape_string

on variables before they’re inserted into a query string. But

those same programmers discover something else, just as

you will: using sprintf lets you write a lot more robust

and flexible code.

Using sprintf, you can do calculations on your data, escape

values, and do just about anything else you want to your data,



as you’re inserting into or selecting from your database. You no

longer need to calculate things and then assign the results of

those calculations to a variable (or, even worse, a new variable,

based upon some old variable) and then—and only then—use

those variables as part of a SQL construction.

sprintf lets you do all that in a single step. In general, you

should use sprintf as your default means of creating SQL

strings that are executed as queries against your database.



Now, try this out. Head over to create_user.php once again, find a new friend to fill

out the form, let her choose an image, and then submit the form. Your new version

of create_user.php should run, and you’ll get to show_user.php.

This time you won’t see the user’s profile, because that’s not code you’ve written.

In fact, you might see an entirely incorrect user being loaded. You’ll fix that soon.



Chapter 10: Binary Objects and Image Loading



www.it-ebooks.info



299



Your Binary

Data Isn’t

Safe to

Insert...Yet



You should, however, be able to dig into your new images table and see an entry

for the uploaded image:

mysql> SELECT image_id, filename FROM images;

+----------+-----------------------------------------------------+

| image_id | filename

|

+----------+-----------------------------------------------------+

|

1 | 7829_1204001948285_1475710666_1190173_2526636_n.jpg |

+----------+-----------------------------------------------------+

1 row in set (0.43 sec)

 Warning  You most definitely do not want to do a SELECT * here, because you’ll get MySQL’s attempt to



load your actual image data, which might be a few hundred (or a few thousand) kilobytes. But, at least you can

see that an image is indeed in your table.



You can also access your table by using phpMyAdmin (see the box on page 55) if

you’ve got that running, and extract a little extra information about your entries in

images. Figure 10-1 shows you what to expect.



Figure 10-1



PhpMyAdmin reports BLOB

columns—regardless of

what type of BLOB you

used—as BLOB and a size.

In this case, you can see

that the file size, at 27500

bytes, matches up with

the size of the data in

the BLOB column, which

is 26.9 KB. This is a good

way to verify that things

are working: your script is

correctly getting the size

of the image it’s inserting

into your database table.



Getting the Correct ID Before Redirecting

Unfortunately, there’s still a problem. You might have noticed something like Figure

10-2 when you got your image insertion working. You could see a blank screen, or

even a totally different user, as in this scenario.



300



PHP & MySQL: The Missing Manual



www.it-ebooks.info



Your Binary

Data Isn’t

Safe to

Insert...Yet



Figure 10-2



This screen is hardly what

you want to see after

all that work on getting

images into your database.

So, what gives?



This isn’t as much of a mystery as it first seems. Here’s the last bit of your code from

create_user.php:

// This replaces the older assignment to $insert_sql

$insert_sql = sprintf("INSERT INTO users " .

"(first_name, last_name, email, " .

"bio, facebook_url, twitter_handle) " .

"VALUES ('%s', '%s', '%s', '%s', '%s', '%s');",

mysql_real_escape_string($first_name),

mysql_real_escape_string($last_name),

mysql_real_escape_string($email),

mysql_real_escape_string($bio),

mysql_real_escape_string($facebook_url),

mysql_real_escape_string($twitter_handle));

// Insert the user into the database

mysql_query($insert_sql)

or die(mysql_error());



Chapter 10: Binary Objects and Image Loading



www.it-ebooks.info



301



Your Binary

Data Isn’t

Safe to

Insert...Yet



$insert_image_sql = sprintf("INSERT INTO images " .

"(filename, mime_type, " .

"file_size, image_data) " .

"VALUES ('%s', '%s', %d, '%s');",

mysql_real_escape_string($image_filename),

mysql_real_escape_string($image_mime_type),

mysql_real_escape_string($image_size),

mysql_real_escape_string($image_data));

mysql_query($insert_image_sql)

or die(mysql_error());

// Redirect the user to the page that displays user information

header("Location: show_user.php?user_id=" . mysql_insert_id());

exit();



What’s the problem? It’s in that second-to-last line. Remember, mysql_insert_id

returns the ID of the last INSERT query, which is no longer the INSERT for your users

table; it’s your new INSERT for images. The redirect to show_user.php is in fact working, but it’s sending the ID of the image inserted rather than the user. Fortunately,

you can easily fix that:

// This replaces the older assignment to $insert_sql

$insert_sql = sprintf("INSERT INTO users " .

"(first_name, last_name, email, " .

"bio, facebook_url, twitter_handle) " .

"VALUES ('%s', '%s', '%s', '%s', '%s', '%s');",

mysql_real_escape_string($first_name),

mysql_real_escape_string($last_name),

mysql_real_escape_string($email),

mysql_real_escape_string($bio),

mysql_real_escape_string($facebook_url),

mysql_real_escape_string($twitter_handle));

// Insert the user into the database

mysql_query($insert_sql);

$user_id = mysql_insert_id();

$insert_image_sql = sprintf("INSERT INTO images " .

"(filename, mime_type, " .

"file_size, image_data) " .

"VALUES ('%s', '%s', %d, '%s');",

mysql_real_escape_string($image_filename),

mysql_real_escape_string($image_mime_type),

mysql_real_escape_string($image_size),

mysql_real_escape_string($image_data));



302



PHP & MySQL: The Missing Manual



www.it-ebooks.info



Connecting

Users and

Images



mysql_query($insert_image_sql);

// Redirect the user to the page that displays user information

header("Location: show_user.php?user_id=" . $user_id);

exit();

?>



Try this out again, and you should be back to what you expect: a slightly broken

version of show_user.php, but broken in the way that you expect (see Figure 10-3).



Figure 10-3



As odd as it seems, you

sometimes want things to

be broken. In this case, you

want to see a missing image because you haven’t

written any code to display

the image just INSERTed.

What you don’t want to

see—and what you just

fixed—is the missing user

information other than the

image.



Connecting Users and Images

At this point, you have two tables—users and images—but no connection between

them. That’s your next challenge. When you load a user from the users table and

display his profile by using show_user.php, how do you determine which image in

the images table you should display?

Clearly, you need some linkage between those two tables. You already have a unique

ID for each entry in users (user_id) and in images (image_id), which is a good

starting place. The question becomes, does a user reference an image, or does an

image reference a user?



Chapter 10: Binary Objects and Image Loading



www.it-ebooks.info



303



Connecting

Users and

Images



Here’s the fundamental question you’ll ask over and over when you’re connecting

two tables in a database: how are the two tables related? Better still, how are the

two objects that your tables represent related?

 Note  You can find the finished example code for this section on this book’s Missing CD page at www.



missingmanuals.com/cds/phpmysqlmm2e.



For example, does a user have an image? Does a user have lots of images? In this case,

a single user has a single profile image. In database terms, that’s called a one-to-one

(or 1-1) relationship. One user is related to one image. As a result, you can create

a new column in your users table, and in that column you can store the image_id

of that user’s profile image. You can make that change to your database like this:

mysql> ALTER TABLE users

->

ADD profile_pic_id int;

Query OK, 6 rows affected (0.11 sec)

Records: 6 Duplicates: 0 Warnings: 0

DESIGN TIME



Foreign Keys and Column Names

The profile_pic_id column in the code above is setting

up what’s called a foreign key relationship. This column is a

foreign key because it relates to the key in a different, “foreign”

table: images .

In most databases, you not only define a column in your table

that relates to the referenced table’s primary key, you also

define a FOREIGN KEY at the database level. That way, your

database knows that profile_pic_id is storing IDs that

are in the images table’s image_id column.

You can use foreign keys in MySQL, but you have to use the

MySQL InnoDB table engine, which you haven’t seen yet. This

requires some extra setup, and not all hosting providers support InnoDB. Besides, programmers have been using MySQL

without foreign key support for years, so if you write your code

properly, you can work around this limitation. If you want to

use InnoDB and foreign key support at the database level, start

with this command on your tables:

ALTER TABLE [table-name]

ENGINE = InnoDB;



Then Google “MySQL foreign keys” and you’ll find a wealth of

information at your fingertips.

Regardless of whether you use foreign keys through your

programming or add support at the database level by using



304



InnoDB, naming your foreign key columns is a big deal. The

typical practice here is to name the foreign key [singulartable-name]_id. For example, for a foreign key connecting

users to images , you’d typically take the singular name of

the table you’re connecting to—“image” from images—and

append “_id”. This results in get image_id for your foreign

key column name.

Why use profile_pic_id in users? Because you could

very well store more than just profile pictures in images . You

might store several images for a user, only one of which is a

profile picture. You might keep up with user’s candid photos,

or icons for logging in, or images for companies to which your

users connect.

In all of these cases, then, image_id in users doesn’t

provide enough specificity. In these cases—where you’re

not just setting up a foreign key, but setting up both a foreign key and indicating a particular type of usage—using

a different name makes sense. For instance, you could end

up with a profile_pic_id column in users , and then

perhaps a company_logo_id in a potential companies

table, and who knows what other images you’ll use? By using

profile_pic_id now, you’re indicating that you’re relating

to an image and the specific purpose for which that image is

being used.



PHP & MySQL: The Missing Manual



www.it-ebooks.info



 Warning  You’ve already made changes to your scripts to accommodate storing images in your database,



rather than on your file system. With the ALTER in the preceding example, you’re now making the same sort of

changes to your database. These changes reflect a deviation in how your application works. To be safe, you want

to back things up at this point in your database.



Connecting

Users and

Images



Of course, backing up a script is a lot easier than backing up a database. You might want to give your hosting

company a call and see if and how you can backup your database. Or, you can just figure out how to undo these

changes if you decide that you want to go back to storing images on your file system.

Either way, you’re going to get some PHP and MySQL practice switching between the two approaches. That’s a

good thing no matter what solution you end up using.



Inserting an Image and then Inserting a User

Once an image is in images, you need to get that image’s ID and insert it into a

user’s profile_pic_id column. At the moment, though, your script inserts into users

before inserting into images:

// This replaces the older assignment to $insert_sql

$insert_sql = sprintf("INSERT INTO users " .

"(first_name, last_name, email, " .

"bio, facebook_url, twitter_handle) " .

"VALUES ('%s', '%s', '%s', '%s', '%s', '%s');",

mysql_real_escape_string($first_name),

mysql_real_escape_string($last_name),

mysql_real_escape_string($email),

mysql_real_escape_string($bio),

mysql_real_escape_string($facebook_url),

mysql_real_escape_string($twitter_handle));

// Insert the user into the database

mysql_query($insert_sql)

or die(mysql_error());

$user_id = mysql_insert_id();

$insert_image_sql = sprintf("INSERT INTO images " .

"(filename, mime_type, " .

"file_size, image_data) " .

"VALUES ('%s', '%s', %d, '%s');",

mysql_real_escape_string($image_filename),

mysql_real_escape_string($image_mime_type),

mysql_real_escape_string($image_size),

mysql_real_escape_string($image_data));

mysql_query($insert_image_sql)

or die(mysql_error());



Chapter 10: Binary Objects and Image Loading



www.it-ebooks.info



305



Connecting

Users and

Images



// Redirect the user to the page that displays user information

header("Location: show_user.php?user_id=" . $user_id);

exit();

?>



At this point, you could look up the ID of the user you inserted using mysql_

insert_id and store that in a variable. Then, you could get the image ID by using

mysql_insert_id again. Finally, you could update the profile_pic_id column of

the new user’s row in users. That would work, and you’d end up with three different

database interactions:

1. An INSERT to put the user’s information into users .

2. An INSERT to put the image information into images .

3. An UPDATE to drop the new image’s ID into users .

These three steps might not seem like much, but every interaction with your database

consumes time and resources. As a general principle, you want to interact with your

database as little as possible. That’s not to say you don’t work with a database; you

just don’t make three or four calls if you can pull off the same task with one or two.

In this case, you can reduce the number of MySQL interactions from three to two:

1. INSERT the image into the images table (and get the ID of that image in

the process).

2. INSERT the new user into users , and use the image ID you just grabbed as

part of the data you put into that INSERT.

Going from three MySQL interactions to two might sound like a minor issue. Then

again, you just cut your database interactions by a third. If you can make fewer

calls, do it.

Go ahead and wire up your INSERT statements accordingly:

// Get image data

$insert_image_sql = sprintf("INSERT INTO images " .

"(filename, mime_type, " .

"file_size, image_data) " .

"VALUES ('%s', '%s', %d, '%s');",

mysql_real_escape_string($image_filename),

mysql_real_escape_string($image_mime_type),

mysql_real_escape_string($image_size),

mysql_real_escape_string($image_data));

mysql_query($insert_image_sql)

or die(mysql_error());



306



PHP & MySQL: The Missing Manual



www.it-ebooks.info



// This replaces the older assignment to $insert_sql

$insert_sql = sprintf("INSERT INTO users " .

"(first_name, last_name, email, " .

"bio, facebook_url, twitter_handle) " .

"VALUES ('%s', '%s', '%s', '%s', '%s', '%s');",

mysql_real_escape_string($first_name),

mysql_real_escape_string($last_name),

mysql_real_escape_string($email),

mysql_real_escape_string($bio),

mysql_real_escape_string($facebook_url),

mysql_real_escape_string($twitter_handle));



Connecting

Users and

Images



// Insert the user into the database

mysql_query($insert_sql)

or die(mysql_error());

// Redirect the user to the page that displays user information

header("Location: show_user.php?user_id=" . $user_id);

exit();

?>

 Note  There’s no additional code here. It’s just a wholesale move of the insertion creation and mysql_



query call related to a user from before the image-related code to after that code.



But you can remove some code. Now that you have the insertion into users coming second, you can go back to

using mysql_insert_id in your redirection.



From here, it’s just a matter of getting the ID from your images INSERT and using it in

the users INSERT. But you know how to do that: you can use mysql_insert_id to grab

the ID of the row inserted into images and then add that to your INSERT for users:

// Get image data

$insert_image_sql = sprintf("INSERT INTO images " .

"(filename, mime_type, " .

"file_size, image_data) " .

"VALUES ('%s', '%s', %d, '%s');",

mysql_real_escape_string($image_filename),

mysql_real_escape_string($image_mime_type),

mysql_real_escape_string($image_size),

mysql_real_escape_string($image_data));

mysql_query($insert_image_sql)

or die(mysql_error());



Chapter 10: Binary Objects and Image Loading



www.it-ebooks.info



307



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Your Binary Data Isn’t Safe to Insert...Yet

Tải bản đầy đủ ngay(0 tr)

×