Prepared statements from security viewpoint I've decided to go with OOP style and prepared statements, and so far I like it a lot more than the procedural style.  It's much more understandable in my opinion.

For this code review, I've just included my PHP code that interacts with my MySQL database. Everything is working how it should, but I'd like to know if you see anything that could be improved upon. Also, I'd like a security analysis/review of the code. Is my code safe from SQL injection since I'm using prepared statements? Is there anything else I should be doing?



I have this function in a folder/file above public_html, and just require it on the pages that need it.

function connectToDatabase() {
    $connect = new mysqli('localhost', 'myUserName123', 'myPassword123', 'myDatabaseName');
    if ($connect-&gt;connect_error) {
        exit;
    }
    return $connect;
}


This is the function I use to pull the question text, and answer integers from the database to display on the website.

function getQuestionsAndAnswers($connect) {
    $questionArray = array();
    $answersArray = array();
    $getStuff = $connect-&gt;prepare('SELECT `question`, `answer` FROM `Quizzes` WHERE `questionNumber`&lt;? ORDER BY `questionNumber` ASC');
    $getStuff-&gt;bind_param('i', $questnum);
    $questnum = 21;
    $getStuff-&gt;execute();
    $getStuff-&gt;bind_result($singleQuestion, $singleAnswer);
    while ($getStuff-&gt;fetch()) {
       array_push($questionArray, $singleQuestion);
       array_push($answersArray, $singleAnswer);
    }
    $getStuff-&gt;close();
    $connect-&gt;close();
    return array($questionArray, $answersArray);
}


This is the code that gets run after the user takes the math quiz, submits his answers, and I validate that the answers are all integers, etc.

$connect = connectToDatabase(); // open database connection here

$stmt1 = $connect-&gt;prepare("SELECT MAX(`id`) FROM QuizAdministration");
$stmt1-&gt;execute();
$stmt1-&gt;bind_result($maxId);
$stmt1-&gt;fetch();
$stmt1-&gt;close();

$stmt2 = $connect-&gt;prepare("INSERT INTO PlayerAnswers (`QuizAdministrationId`, `questionNumber`, `playerAnswer`) VALUES (?, ?, ?)");
    for ($yty = 1; $yty &lt; 21; $yty += 1) {
        $stmt2-&gt;bind_param('iii', $QuizAdministrationid, $questnum, $questansw);
        $QuizAdministrationid = $maxId + 1;
        $questnum = $yty;
        $questansw = $_POST['qora'.$yty];
        $stmt2-&gt;execute();
    }
    $stmt2-&gt;close();

$stmt3 = $connect-&gt;prepare("INSERT INTO QuizAdministration (`quizNumber`, `cookie`, `ip`, `score`) VALUES (?, ?, ?, ?)");
$stmt3-&gt;bind_param('issi', $quizNumber, $playerCookie, $playerIP, $playersPercent);
$quizNumber = 1;
$playerCookie = session_id();
$playerIP = getUserIp();
$playersPercent = 90;
$stmt3-&gt;execute();
$stmt3-&gt;close();

$connect-&gt;close(); // close database connection here
header('Location: http://www.myWebsite.com/quizResults.php');
exit;
This looks pretty good.  I'm glad to see the improvement.

You inexplicably call $connect-&gt;bind_params() using variables that have not yet been set.

The main issue I see now is $QuizAdministrationid = $maxId + 1,  which is prone to a race condition.  If two users submit the form simultaneously, it's possible that both pages could see the same result from SELECT MAX(id) … and generate the same next $QuizAdministrationid.

The solution is to ask the database to generate the next ID.  Every SQL database supports this in a slightly different way.  For MySQL, you should declare the id column of QuizAdministration to be AUTO_INCREMENT.  Then, leave id column unspecified when you do the INSERT.  MySQL will automatically fill in the id column with the next available number.  To find out what number MySQL picked for you, use $connect-&gt;insert_id.

To make this work for you, you'll need to insert a row into QuizAdministration first, then use the id of the new row to insert the PlayerAnswers.

$stmt2 = $connect-&gt;prepare("INSERT INTO QuizAdministration (`quizNumber`, `cookie`, `ip`, `score`) VALUES (?, ?, ?, ?)");
$quizNumber = 1;
$playerCookie = session_id();
$playerIP = getUserIp();
$playersPercent = 90;
$stmt2-&gt;bind_param('issi', $quizNumber, $playerCookie, $playerIP, $playersPercent);
$stmt2-&gt;execute();
$stmt2-&gt;close();

$quizAdministrationid = $connect-&gt;insert_id;

$stmt3 = $connect-&gt;prepare("INSERT INTO PlayerAnswers (`QuizAdministrationId`, `questionNumber`, `playerAnswer`) VALUES (?, ?, ?)");
for ($yty = 1; $yty &lt;= 20; $yty += 1) {
    $questnum = $yty;
    $questansw = $_POST['qora'.$yty];
    $stmt3-&gt;bind_param('iii', $QuizAdministrationid, $questnum, $questansw);
    $stmt3-&gt;execute();
}
$stmt3-&gt;close();