Pulling Data from database using PDO

Dear All,
I am trying to pull data from the database using PDO, is the code below safe from an attack or do I need to bind anything?

    global $connection;   
   
    if(isset($_GET['p_cat'])){

        $p_cat_id = $_GET['p_cat'];

        $get_p_cat = "SELECT * FROM product_categories WHERE p_cat_id='$p_cat_id'";

        $run_p_cat = $connection->query($get_p_cat);

        $row_p_cat = $run_p_cat->fetch();

        $p_cat_title = $row_p_cat['p_cat_title'];

        $p_cat_desc = $row_p_cat['p_cat_desc'];

        $get_products = "SELECT * FROM products WHERE p_cat_id='$p_cat_id'";

        $run_products = $connection->query($get_products);

        $count = $run_products->rowCount();

        if($count==0){

            echo "

            <div class='box'>

            <h1> No Product Found In This Product Category </h1>

            </div>

            ";

        }else{

            echo "

            <div class='box'>

            <h1>$p_cat_title</h1>

            <p>$p_cat_desc</p>

            </div>

            ";

        }

        while($row_products = $run_products->fetch()){

            $pro_id = $row_products['product_id'];

            $pro_title = $row_products['product_title'];

            $pro_price = $row_products['product_price'];

            $pro_img1 = $row_products['product_img1'];

            echo "
                <div class='col-md-4 col-sm-6 single'>
                    <div class='product'>
                        <a href='details.php?pro_id=$pro_id'>
                        <img
                            src='admin_area/product_images/$pro_img1'
                            class='img-fluid'
                        />
                        </a>

                        <div class='text'>
                        <h3><a href='details.php?pro_id=$pro_id'>$pro_title</a></h3>

                        <p class='price'>$$pro_price</p>

                        <p class='buttons'>
                            <a href='details.php?pro_id=$pro_id' class='btn btn-outline-primary'>View details</a>

                            <a href='details.php?pro_id=$pro_id' class='btn btn-primary'>
                            <i class='fa fa-shopping-cart'></i> Add to cart
                            </a>
                        </p>
                        </div>
                    </div>
                </div>
            
            ";

        }


    }
    $p_cat_id = $_GET['p_cat_id'];
    // Prepare the SQL query with a placeholder
    $sql = "SELECT * FROM products WHERE p_cat_id = :p_cat_id";
    $stmt = $connection->prepare($sql);

    // Bind the id to the placeholder
    $stmt->bindValue(':p_cat_id', $p_cat_id);

    // Execute the prepared statement
    $stmt->execute();

    // Fetch the results and handle them as needed
    $records = $stmt->fetchAll(PDO::FETCH_ASSOC);
     foreach ($records as $record) {
           echo $record['product_title'] . "<br>"; // Just an example and code not tested. 
     }

Definitely Bind and you can also save a lot of coding if you user variable general naming.

1 Like

Data submitted to your site ($_GET, $_POST, $_COOKIE, $_FILES, and some $_SERVER variables) can come from anywhere, not just your links/forms, can be set to anything, and cannot be trusted. You must securely use all this data in whatever context it is being used in.

For data being used in an sql query context, the simplest way of preventing any sql special characters in a value from being able to break the sql query syntax, which is how sql injection is accomplished, is to use a prepared query.

If you use simple positional ? place-holders and implicit binding, by supplying an array of the input values to the ->execute([…]), you can greatly simplify all the database specific code needed for a prepared query.

Here’s a list of point for the posted code -

  1. The global keyword only has meaning inside of a function, and even then it should be avoided. If this code isn’t inside a function the global line does nothing and should be removed. If this code is inside a function, you should supply all inputs to a function as call-time parameters.
  2. The connection variable should be named as to the type of connection, e.g. $pdo, so that anyone reading your code will know what it is using.
  3. Don’t copy variables to other variables for nothing. This is just a waste of your typing time. Just use the original variables that data is in. If you modify or change the meaning of a value, you should use a new variable.
  4. You should trim (mainly so that you can detect they are all white-space characters), then validate all inputs before using them.
  5. You should use a single LEFT JOIN query for this.
  6. There’s no guarantee that the submitted $_GET[‘p_cat’] value will match a row of data in the product_categories table. After you run the query, if there are no matching rows, it means that there was no matching product category.
  7. You should list out the columns you are SELECTing. This helps make your code self-documenting and will result in only selecting the columns you are using in your code.
  8. Just about every query that can match more than one row needs an ORDER BY term in it.
  9. You should use general variable names, e.g. what they are being used for, an $sql query statement, a pdo $stmt object. After you put a main comment in your code describing what it is currently tying to do, all the code up to the next main comment is for the current task. Only the final variable holding task specific data should be uniquely named.
  10. RowCount() is not guaranteed to work with SELECT queries. You should instead just fetch the data and test if there is any data.
  11. You should NOT repeat part of or the whole table name in each column name. This is just more work for you keeping track of all the verbose naming.
  12. Don’t use name-numbered column names. If you do have multiple same meaning data, you would store the multiple values, in a related table, one row per value.
  13. Any link you build should include any existing get parameters, such as the $_GET[‘p_cat’] value. The general purpose way of doing this is to get of copy of any existing $_GET data, set/modify the element the current link is for, then use http_build_query() to build the query string part of the link.
  14. The closing } for the product data’s else {…} conditional branch belongs after the end of the loop, i.e. the code looping over the product data and displaying it should be inside that else {…} branch, not after it.

Here’s example code, with everything but the join query (didn’t feel like spending more time on this), showing these points -

// condition and trim the input
$p_cat = trim($_GET['p_cat']??'');

// validate the input
if($p_cat ==='')
{
	echo "A product category must be selected.";
}
else
{
	$sql = "SELECT title, `desc` FROM product_categories WHERE id=?";
	$stmt = $pdo->prepare($sql);
	$stmt->execute([ $p_cat ]);
	$cat_data = $stmt->fetch();

	$sql = "SELECT id, title, price, img
		FROM products WHERE cat_id=? ORDER BY title";
	$stmt = $pdo->prepare($sql);
	$stmt->execute([ $p_cat ]);
	$product_data = $stmt->fetchAll();

	if(!$cat_data)
	{
		echo "<div class='box'>
		<h1>Product Category doesn't exist</h1>
		</div>";
	}
	else
	{
		if(!$product_data)
		{
			echo "<div class='box'>
			<h1>No Product Found In This Product Category</h1>
			</div>";
		} else {
			echo "<div class='box'>
			<h1>{$cat_data['title']}</h1>
			<p>{$cat_data['desc']}</p>
			</div>";

			// get a copy of any existing $_GET data
			$get = $_GET;
			foreach($product_data as $row)
			{
				// id, title, price, img
				
				// set the pro_id element for the links
				$get['pro_id'] = $row['id'];
				// build the query string part of the links
				$qs = http_build_query($get,'','&amp;');
				
				echo "<div class='col-md-4 col-sm-6 single'>
				<div class='product'>
				<a href='details.php?$qs'>
				<img src='admin_area/product_images/{$row['img']}' class='img-fluid'>
				</a>

				<div class='text'>
				<h3><a href='details.php?$qs'>{$row['title']}</a></h3>
				<p class='price'>\${$row['price']}</p>

				<p class='buttons'>
				<a href='details.php?$qs' class='btn btn-outline-primary'>View details</a>

				<a href='details.php?$qs' class='btn btn-primary'>
				<i class='fa fa-shopping-cart'></i> Add to cart</a>
				</p>
				</div>
				</div>
				</div>";
			}
		}
	}
}
1 Like

Thanks very much. I thought it is only when you want to insert into the database that you have to bind but thanks for this help. I will work on it now.

Thanks for all the explanations, and I got everything you said. Acutally the code was inside a function that it’s why I used global $connection. I will work on all your suggestions and thanks for helping me with this programming stuff.

Sir, It is inside a function called function products-in-products-categories() {
}
Please, can you help me make it elaborate that will execute all that I have written up? because all the videos I watched on e-commerce use mysqli- and procedural method but did not validate anything by saying they want to keep something simple and the one I also bought on PDO did not validate anything that is why I am having issues, I will be glad if you can help me write the one that will find products that is in a particular product-categories that I clicked when filtering the product by product-categories.

Thanks your explanation really helped me but help me look at this one, it is also PDO, I am trying to pull data from the database with LIMIT CLAUSE, here is the code

    function getProPDO() {
      
    global $pdo;

    $recordStart = 0;
    $productSize = 8;
    //$productSize = intval($productSize);

    $sql = $pdo->prepare("SELECT * FROM products ORDER BY 1 DESC LIMIT ?, ?");

    $sql->bindValue(1, $recordStart,  PDO::PARAM_INT );
    $sql->bindValue(2, $productSize,  PDO::PARAM_INT);
    
    $sql->execute();

    $result =  $sql->rowCount();

    if($result === 0) exit('No rows');

    while($row= $sql->fetch()) {
        $pro_id = $row['product_id'];

        $pro_title = $row['product_title'];

        $pro_price = $row['product_price'];

        $pro_img1 = $row['product_img1'];

        echo "
            <div class='col-md-4 col-sm-6 single'>
                <div class='product'>
                    <a href='details.php?pro_id=$pro_id'>
                    <img
                        src='admin_area/product_images/$pro_img1'
                        class='img-fluid'
                    />
                    </a>

                    <div class='text'>
                    <h3><a href='details.php?pro_id=$pro_id'>$pro_title</a></h3>

                    <p class='price'>$$pro_price</p>

                    <p class='buttons'>
                        <a href='details.php?pro_id=$pro_id' class='btn btn-outline-primary'>View details</a>

                        <a href='details.php?pro_id=$pro_id' class='btn btn-primary'>
                        <i class='fa fa-shopping-cart'></i> Add to cart
                        </a>
                    </p>
                    </div>
                </div>
            </div>
        
        ";
    }
}

I want to know if this one is safe.

If you learn the meaning of what you are doing, you can determine for yourself if this is secure.

There are two main reasons for using a prepared query, where you put a place-holder in the sql query statement for each value, then send the values to the database server separately when the query is executed -

  1. To separate the parsing of the sql query syntax from the evaluation and use of the data values, so that any sql special characters in a value cannot break the sql query syntax, which is how sql injection is accomplished.
  2. For a query that will be executed more than once in an instance of your script, it provides a small performance benefit (~5% for an insert query), since the sql query statement is sent to the database server once, where it is parsed and its execution is planned only once, but it can then be executed as many times as needed with different data values.
1 Like
Sponsor our Newsletter | Privacy Policy | Terms of Service