2
\$\begingroup\$

I'm developing a shop system and using this method to store the customers with their respective orders:

try
{
    $connection = new PDO("mysql:host={$HOST};dbname={$DB_NAME}", $USERNAME, $PASS);
}

$connection->beginTransaction();
$sql = "INSERT INTO orders (customer_id, customer_name, order_value, order_date)
        VALUES (?, ?, ?, ?)";

$query = $connection->prepare($sql);
$query->execute(array
(
    $user_id,
    $user['user_name'],
    $order_value,
    $date
));

$id_of_respective_order = $connection->lastInsertId();

$sql = "INSERT INTO purchased_products (order_id, product_name, product_price, quantity)
        VALUES (?, ?, ?, ?)";

$query = $connection->prepare($sql);

foreach($_SESSION['cart'] as $product)
{
    $query->execute(array
    (
        $id_of_respective_order,
        $product['product_name'],
        $product['product_price'],
        $product['quantity']
    ));
}

$connection->commit();

By using this, later I can show all the products that belongs to their respective customers by using the Foreign Key order_id.

My question is simple: is this secure? If many people buy at the same time can I have problems? Is there a possibility of a product being directed to the wrong customer? Can I trust in this method?

\$\endgroup\$
1
  • \$\begingroup\$ Welcome to CodeReview, Dyan. I hope you get some fine answers. \$\endgroup\$
    – Legato
    Commented Jun 9, 2015 at 0:23

2 Answers 2

1
\$\begingroup\$

My question is simple: is this secure?

It's safe from SQL injection attacks.

If many people buy at the same time can I have problems?

Since you insert the order and the cart contents in a transaction, the table will be locked until you have inserted everything, preventing other users from placing their orders. So the transaction can become a bottleneck. However, this is the right way of doing it, if it's important for you to avoid partial orders.

Is there a possibility of a product being directed to the wrong customer?

One thing is not clear, what happens if the first query fails. You didn't check for errors, though I would guess that in that case you won't get a meaningful insert ID (would have to check the docs). In any case, you should check for errors after all insert operations and handle gracefully.

Other than that, the transaction guarantees that order IDs won't get mixed up. Even without the transaction, probably it wouldn't happen, but I'd have to dig into the docs for that.

Can I trust in this method?

Yes, keeping in mind the potential bottleneck and other considerations above.

\$\endgroup\$
3
  • \$\begingroup\$ Thank you for your answer, I use AUTO_INCREMENT for the order_id in the orders table. About the insertions, and if the first insertion did not fail, why the second one will? Do you think I need to put a check in both insertions like the answer below suggested? \$\endgroup\$
    – Dyan
    Commented Jun 11, 2015 at 14:44
  • \$\begingroup\$ You check the success/failure of all sql updates (including inserting rows). At the minimum you might want to let the user know that something went wrong. Depending on the level of integrity you want to ensure, you might want to roll back all partial changes. \$\endgroup\$
    – janos
    Commented Jun 11, 2015 at 14:58
  • \$\begingroup\$ Ok, I will do a check in all my sql insertions now. Another thing I want to figure out. If too many people buy in same time, by using this method, it will be like a bottleneck as you said, what will happen? The table will be locked but all data will be inserted, one-by-one, in sequence, waiting for the previous order or the user that purchased when someone data is being inserted will receive an error and will required to try finish his order later? \$\endgroup\$
    – Dyan
    Commented Jun 24, 2015 at 0:35
1
\$\begingroup\$

There are one thing which makes me wonder. You are using a transaction, which is good, but there is no rollback() if the query fails.

I would pass an array of connection parameters to the PDO object to make sure exceptions are thrown and prepared statements are forced (if the driver doesn't support prepared statements emulated prepares will be used anyways).

$options = [
    PDO::ATTR_ERRMODE          => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false
];

$pdo = new PDO('mysql:host=127.0.01;dbname=example', $username, $password, $options);

I would then enclose every query within a try-catch block and rollback if an error occurs.

$pdo->beginTransaction();

try {

    // Insert query into orders table.

}catch(PDOException $exception) {

   $pdo->rollback(); // Undo any changes.

   // Handle the exception gracefully or throw it again.

}

// Same procedure for purchased_products table

$pdo->commit();

This should help you avoid inconsistent data if one of your queries fails.

Happy coding!

\$\endgroup\$
3
  • 1
    \$\begingroup\$ Good point! And I would add: also rollback() could be used in simple reponse if customer decides to cancel its whole order. \$\endgroup\$
    – cFreed
    Commented Jun 9, 2015 at 15:37
  • \$\begingroup\$ Thank you for your answer, but if the first insertion did not fail, why the second one will? This check is really necessary? \$\endgroup\$
    – Dyan
    Commented Jun 11, 2015 at 14:36
  • \$\begingroup\$ I have always believed you should code with a healthy bit of paranoia. There could be some insane/very weird scenario where your your second query failed. This could be because your database had errors and only some queries ran successfully. You will be much more resistant to edge cases and have a better error detection if such a case should arise. There is no guarantee, but it doesn't hurt. \$\endgroup\$
    – AnotherGuy
    Commented Jun 11, 2015 at 14:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.