You say "I believe I understand the basics of SQL injection." but the rest of the question implies that perhaps you may still have some confusion. Either way, this is an important topic and many people continue to be confused about the problem and the solution.
I also know using prepared statements with PHP files is the best way to prevent SQL injection.
Using prepared statements is a necessary but not sufficient condition to protect from SQL injection (SQLi). To understand why, it's important to understand the cause of SQL Injection vulnerabilities. Here are the causes:
- Concatenating SQL statements from user/client input.
That's it. If you take input from the user and put it in a SQL statement which you then compile on the database, you may be (and likely are) subject to SQLi vulnerabilities. If you do this and use a prepared statement, you have resolved nothing. You are still vulnerable.
So why is using prepared statements always given as the answer? If we can't concatenate the SQL from input, that means our SQL needs to be more-or-less hardcoded. But what if you want to pull back the user info for the one who just logged in? We can't write a new SQL statement every time we have a new user.
#Prepared Statements
Prepared Statements
What makes prepared statements useful in eliminating this issue is that they allow for parameters. That means instead of doing something like this:
// Don't do this!  Subject to injection
"SELECT credit_card_number FROM user_data where userid = '" + userinput + "'"
We can do this:
"SELECT credit_card_number FROM user_data where userid = ?"
 And compile this statement on the database.  When we go to run it, we need to pass in a parameter value or the query will fail.  Why does this solve the issue?  Let's say an attacker figures out how to get the string: foo' OR 1 = 1 OR 'a' = 'a in to the userinput value.  In the first case the following SQL will execute:
SELECT credit_card_number FROM user_data where userid = 'foo' OR 1 = 1 OR 'a' = 'a';
Returning every row in the table. In the second case this SQL will execute:
SELECT credit_card_number FROM user_data where userid = ?
 with the parameter foo' OR 1 = 1 OR 'a' = 'a.  Unless you have a user with the unlikely id foo' OR 1 = 1 OR 'a' = 'a there will be no results.  But let's be clear, you can use the first (wrong) approach with prepared statements and you will still be vulnerable.  There is nothing magical in the prepared statement that will sanitize the input.
It's that you can't use the second (correct) approach with a regular statement that doesn't support parameters.
I think often people who understand this will use the shorthand "use prepared statements" because the main reason to use them is because they support parameters. But it's really the parameters that are the solution to this issue.
 
                