Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
Any idea to avoid to repeat this long messy code every time i want to get the data from the database in secure way?
public function test($param) {
$sql = "SELECT * FROM users WHERE id = :id AND item_id :item_id";
$this->_query = $this->_db->pdo()->prepare($sql);
$this->_query->bindValue(':id', $param);
$this->_query->bindValue(':item_id', $parmas);
$this->_query->execute();
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
}
i created good way not to mess around for a simple one like this
public function query($sql, $params = array()) {
$this->_error = false;
if($this->_query = $this->_pdo->prepare($sql)) {
if(count($params)) {
//outside of the loop
$x = 1;
foreach($params as $param) {
$this->_query->bindValue($x, $param);
$x++;
}
}
if($this->_query->execute()) {
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
$this->_count = $this->_query->rowCount();
} else {
$this->_error = true;
}
}
return $this;
}
pdo is already defined in the construct function in the class.
//im adding more details here and this is what im trying to make.
public function example($table, $params = array(), $extn = array(), $values = array()) {
$x = '';
$y = 0;
foreach($params as $param) {
$x .= $param;
if($y < count($params)) {
$x .= $extn[$y];
}
$y++;
}
$sql = "SELECT * FROM {$table} WHERE {$x}";
$this->_query = $this->_pdo->prepare($sql);
foreach($values as $value) {
$this->_query->bindValue($value);
}
$this->_query->execute();
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
}
but im getting this error Warning: PDOStatement::bindValue() expects at least 2 parameters,
with this code down below
$test = new Example();
$test->example('users', array('id = :id', 'username = :username', 'id = :username', 'username = :id'), array(' AND ', ' OR ', ' AND '), array("':id', 4", "':username', 'alex'"));
any suggestion will be helpful to me!!
Not to sound harsh, but there is a multitude o things wrong with your approach. Most immediate to your problem is that the passed $values are garbled.
I would expect the bindValues() in example to look something like this:
foreach ($values as $param => $value) {
$this->_query->bindValue($param, $value);
}
Consequently $values given to example() should look something like this:
$values = array(':id' => 4, ':username' => 'alex')
For reference see the php-docs on PDOStatement::bindValue()
Apart from that:
You just pass the variable $table into the query:
$sql = "SELECT * FROM {$table} WHERE {$x}";
This is unsafe! While you are more concerned about the values passed to the query (which is understandable) your will still have a vulnerability here.
Your class database stores the query and its result in a class variable. This is unnecessary (database engines have query caches, if you want those files cached in php you should results in a cache, e.g. by wrapping database in a class cached_database) and potentially will cause bugs when your query/results are accidentally reused or mixed up.
There are many ways to mess up your queries by giving the wrong parameters and because almost all of them are arrays it is hard to figure out what values to put in there. This makes your whole setup very fragile, e.g. it depends on the right number of $params and $extn passed to example which almost certainly will make problems in the future. Not only because it's hard to figure out what happens there, but also because it (most likely) lacks functionality you might want to use in the future such as queries with IN or BETWEEN. It may safe you from writing a few lines of code, but after not using it for a few weeks you will spend that time trying to figure out what happens and how to use it (and probably more). Trust me, we have all been there. ;)
I think you are much safer repeating the PDO-stuff which looks a bit repetitive, but is easily understood - especially when someone else takes over or is to help you in the future - and is well documented.
Should you feel the need to simplify common tasks like SELECT-ing similar data you should instead consider an ORM like Doctrine. Even just the Doctrine DBAL might help you because you get a powerful SQL Query Builder.
If you really want to keep your code and don't want to use other libraries try simplifying it using baby steps. Whenever a small part is repeated in exactly the same way put it into a private method. You could do this for example with these 3 lines in your first example:
$this->_query->execute();
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
return $this->_results;
Take your first code snippet:
class database {
$_results = null;
/***
#param array $param should be an array of two elements
***/
public function test($param = []) {
$sql = "SELECT * FROM users WHERE id = :id AND item_id :item_id";
$this->_query = $this->_db->pdo()->prepare($sql);
$this->_query->bindValue(':id', $param);
$this->_query->bindValue(':item_id', $param);
$this->_query->execute();
$this->_results = $this->_query->fetchAll(PDO::FETCH_OBJ);
return $this->_results;
}
}
You could simplify it by just extracting bits you repeat often into smaller private methods:
/*
* Don't just call it "database", that's way to generic.
* Try to give it an explicit name, for example UserRepository
*/
class UserRepository
{
public function getUsersByIdAndItemId($id, $itemId) {
$sql = "SELECT * FROM users WHERE id = :id AND item_id :item_id";
$query = $this->getPreparedQuery($sql);
// No need to simplify this:
$query->bindValue(':id', $id);
$query->bindValue('item_id', $itemId);
return $this->executeQuery($statement);
}
public function getAllUsers()
{
$sql = "SELECT * FROM users";
$query = $this->getPreparedQuery($sql);
return $this->executeQuery($query);
}
private function getPreparedQuery($sqlQueryString)
{
/*
* No "$this->_query"
* You don't have to reuse it! Worst case scenario a diffeent
* method accidentally reuses the query and you get a PDOException.
*/
return $this->_db->pdo()->prepare($sqlQueryString);
}
private function executeQuery(\PDOStatement $query)
{
$statement->execute();
/**
* Again no need to store this in a class variable.
* Worst case scenario you end up with a dirty state
* or mixed up data
*/
return $statement->fetchAll(PDO::FETCH_OBJ);
}
}
This is much safer than your approach, because it's simple and clear. Your input is always secured and whenever you use UserRepsitory::findUsersByIdAndItemId() you can't accidentally send to many params or give them the wrong name or otherwise mess up the sql query. You just pass exactly the values you need. Sometimes violating "Don't repeat yourself" is ok if it makes your code more secure and better to understand.
Related
Im learning, and am still struggling to get the hang of many OOP concepts so please keep that in mind when reading / answering the question.
So one of the primary purposes of object orientated programming is not to repeat yourself right. However, when I am creating methods, I constantly find myself repeating the same statements when querying the database. As can be seen in the below code taken from class I created.
function getCategory()
{
$sql = "SELECT * FROM jobs";
$stmnt = $db->prepare($sql);
$stmnt->execute();
$results = $stmnt->fetchAll();
foreach ($results as $result) {
$cat[] = $result['category'];
}
return $this->category = $cat
}
function selectCategory($selectedCategory){
$sql = "SELECT * FROM jobs
WHERE category =:category";
$stmnt = $db->prepare($sql);
$stmnt->bindValue(':category', $selectedCategory);
$stmnt->execute();
$results = $stmnt->fetchAll();
foreach($results as $result){
$result= array('category' => $result['category'], 'headline' => $result['headline']);
}
return $this->category = $result
}// selectCategory
My question.
Is there a way / what should I do to avoid having to continuously write the same database queries in my methods? I feel im a little out of depth here, however its not going to stop me from trying. Any help, advice welcomed (please keep in mind im a beginner)
You can run the query to get all data, then extract the data from fetched data using PHP.
But I don't like it, and it is not efficient.
You have 2 different query , so you need to calls, just improve your code a little more, you can make a model like this:
class Category
{
private $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
function getAll()
{
return $this->db->query('SELECT * FROM jobs');
}
function getByCategory($category){
$stmt = $this->db->prepare(
"SELECT * FROM jobs WHERE category =:category"
);
$stmt->bindValue(':category', $category);
$stmt->execute();
return $stmt->fetchAll();
}
}
This is perfectly fine, and it is not really repeating
This question already has answers here:
Insert query on page load, inserts twice?
(3 answers)
Closed 2 years ago.
I usually don't post detailed versions of my code; however, in this case it may be necessary to figure out the problem. I have a class method that I cannot stop from executing twice. Is there any particular information I am missing on MySQLi prepared statements? I have read similar questions that asks the same to no avail.
I previously asked a question about using eval for dynamic queries in prepared statements as far as it's convention and best practices. I was told to use call_user_func_array() and it worked perfectly; however, I failed to notice that the statement executed twice each time, even with the old eval() code. So I put together a snippet of my ACTUAL code which should pretty much explain itself through my comments
function insert($table, $query)
{
/**
*
* This code assumes you have a MySQLi connection stored in variable $db
* USAGE: insert(table, array('field' => 'value');
*
**/
// Sets the beginning of the strings for the prepared statements
$fields = $values = "(";
$types = "";
$params = array();
foreach($query as $key => $val)
{
// array keys = fields, and array values = values;
$fields.= $key;
// concatenate the question marks for statement
$values.= "?";
// concatenate the type chars
$types.= is_string($val) ? "s" : (is_int($val) ? "i" : (is_double($val) ? "d" : "b"));
// pass variables to array params by reference for call_user_func_array();
$params[] = &$query[$key];
if($val == end($query))
{
$fields .= ")";
$values .= ")";
array_unshift($params, $types);
}
else
{
$fields .= ", ";
$values .= ", ";
}
}
$str = "INSERT INTO {$table} {$fields} VALUES {$values}";
if($stmt = $db->prepare($str))
{
call_user_func_array(array($stmt, 'bind_param'), $params);
/**
*
* This is where I am pulling my hair out of my head and being 3
* nothces away from banging my own head into the screen and
* being without a computer at all.
*
* I have tried everything I can think of. I gotta be missing
* something
*
* IT JUST KEEPS SENDING 2 ROWS DANG IT!
*
**/
/////////////////////
$stmt->execute();//// <---Help is needed here
/////////////////////
//-- Close connection;
$stmt->close();
}
else
{
//-- Send a nice readable error msg
die("<center><h3>FAULTY QUERY STRING</h3><h4>Please check query string</h4><p>{$str}</p>");
}
}
Changed code format from OOP to regular function for testing without having to create a class.
old question, but people might still stumble upon it, I did.
I think the mysqli_query function is mainly for retrieving data, I had the same problem, and fixed it by using mysqli_real_query on insert and update queries.
Because I find PDO executions extremely hard to remember and find myself looking back at previous projects or other websites just to remember how to select rows from a database, I decided that I would try and create my own functions that contain the PDO executions and just plug in the data I need. It seemed a lot simpler than it actually is though...
So far I have already created a connect function successfully, but now when it comes to create a select function I'm stumped for multiple reasons.
For starters there could be a variating amount of args that can be passed into the function and secondly I can't figure out what I should pass to the function and in which order.
So far the function looks like this. To keep me sane, I've added the "id" part to it so I can see what exactly I need to accomplish in the final outcome, and will be replaced by variables accordingly when I work out how to do it.
function sql_select($conn, **what to put here**) {
try {
$stmt = $conn->prepare('SELECT * FROM myTable WHERE id = :id');
$stmt->execute(array('id' => $id));
$result = $stmt->fetchAll();
if ( count($result) ) {
foreach($result as $row) {
print_r($row);
}
} else {
return "No rows returned.";
}
} catch(PDOException $e) {
echo 'ERROR: ' . $e->getMessage();
}
}
So far what I've established that the function will need to do is
Connect to the database (using another function to generate the $conn variable, already done)
Select the table
Specify the column
Supply the input to match
Allow for possible args such as ORDER by 'id' DESC
Lastly from this I would need to create a function to insert, update and delete rows from the database.
Or, is there a better way to do this rather than functions?
If anyone could help me accomplish my ambitions to simply simplify PDO executions it would be greatly appreciated. Thanks in advance!
First of all, I have no idea where did you get 10 lines
$stmt = $conn->prepare('SELECT * FROM myTable WHERE id = ?');
$stmt->execute(array($id));
$result = $stmt->fetchAll();
is ALL the code you need, and it's actually three lines, which results with a regular PHP array that you can use wherever you wish. Without the need of any PDO code. Without the need of old mysql code.
Lastly from this I would need to create a function to insert, update and delete rows from the database.
DON'T ever do it.
Please read my explanations here and here based on perfect examples of what you'll end up if continue this way.
accomplish my ambitions to simply simplify PDO executions
That's indeed a great ambition. However, only few succeeded in a real real simplification, but most resulted with actually more complex code. For starter you can try code from the first linked answer. Having a class consists of several such functions will indeed improve your experience with PDO.
. . . and find myself looking back at previous projects or other
websites just to remember how to select rows from a database . . .
FYI, we all do that.
You had a problem with the PDO API and now you have two problems. My best and strongest suggestion is this: If you want a simpler/different database API, do not roll your own. Search http://packagist.org for an ORM or a DBAL that looks good and use it instead of PDO.
Other people have already done this work for you. Use their work and focus instead on whatever awesome thing is unique to your app. Work smart, not hard and all that.
Writting a wrapper, should start form connecting the DB, and all the possible method could be wrapped. Passing connection to the query method, doesn't look good.
A very rough example would be the code bellow, I strongly do not suggest this mixture, but it will give you the direction.
You connection should be made either from the constructor, or from another method called in the constructor, You can use something like this:
public function __construct($driver = NULL, $dbname = NULL, $host = NULL, $user = NULL, $pass = NULL, $port = NULL) {
$driver = $driver ?: $this->_driver;
$dbname = $dbname ?: $this->_dbname;
$host = $host ?: $this->_host;
$user = $user ?: $this->_user;
$pass = $pass ?: $this->_password;
$port = $port ?: $this->_port;
try {
$this->_dbh = new PDO("$driver:host=$host;port=$port;dbname=$dbname", $user, $pass);
$this->_dbh->exec("set names utf8");
} catch(PDOException $e) {
echo $e->getMessage();
}
}
So you can either pass connection credentials when you instantiate your wrapper or use default ones.
Now, you can make a method that just recieves the query. It's more OK to write the whole query, than just pass tables and columns. It will not make a whole ORM, but will just make the code harder to read.
In my first times dealing with PDO, I wanted everything to be dynamically, so what I achieved, later I realized is immature style of coding, but let's show it
public function query($sql, $unset = null) {
$sth = $this->_dbh->prepare($sql);
if($unset != null) {
if(is_array($unset)) {
foreach ($unset as $val) {
unset($_REQUEST[$val]);
}
}
unset($_REQUEST[$unset]);
}
foreach ($_REQUEST as $key => $value) {
if(is_int($value)) {
$param = PDO::PARAM_INT;
} elseif(is_bool($value)) {
$param = PDO::PARAM_BOOL;
} elseif(is_null($value)) {
$param = PDO::PARAM_NULL;
} elseif(is_string($value)) {
$param = PDO::PARAM_STR;
} else {
$param = FALSE;
}
$sth->bindValue(":$key", $value, $param);
}
$sth->execute();
$result = $sth->fetchAll();
return $result;
}
So what all of these spaghetti does?
First I though I would want all of my post values to be send as params, so if I have
input name='user'
input name='password'
I can do $res = $db->query("SELECT id FROM users WHERE username = :user AND password = :password");
And tada! I have fetched result of this query, $res is now an array containing the result.
Later I found, that if I have
input name='user'
input name='password'
input name='age'
In the same form, but the query remains with :user and :password and I submit the form, the called query will give mismatch in bound params, because the foreach against the $_REQUEST array will bind 3 params, but in the query I use 2.
So, I set the code in the beginning of the method, where I can provide what to exclude. Calling the method like $res = $db->query("SELECT id FROM users WHERE username = :user AND password = :password", 'age'); gave me the possibility to do it.
It works, but still is no good.
Better have a query() method that recieves 2 things:
The SQL string with the param names
The params as array.
So you can use the foreach() logic with bindValue, but not on the superglobal array, but on the passed on.
Then, you can wrap the fetch methods
public function fetch($res, $mode = null)
You should not directly return the fetch from the query, as it might be UPDATE, INSERT or DELETE.
Just pass the $res variable to the fetch() method, and a mode like PDO::FETCH_ASSOC. You can use default value where it would be fetch assoc, and if you pass something else, to use it.
Don't try to be so abstract, as I started to be. It will make you fill cracks lately.
Hum... IMHO I don't think you should try to wrap PDO in functions, because they're already "wrapped" in methods. In fact, going from OOP to procedural seems a step back (or at least a step in the wrong direction). PDO is a good library and has a lot of methods and features that you will surely lose if you wrap them in simple reusable functions.
One of those features is the BeginTransaction/Rollback (see more here)
Regardless, In a OOP point of view you can decorate the PDO object itself, adding some simple methods.
Here's an example based on your function
Note: THIS CODE IS UNTESTED!!!!
class MyPdo
{
public function __construct($conn)
{
$this->conn = $conn;
}
public function pdo()
{
return $this->conn;
}
public function selectAllById($table, $id = null)
{
$query = 'SELECT * FROM :table';
$params = array('table'=>$table);
if (!is_null($id)) {
$query .= ' WHERE id = :id';
$params['id'] = $id;
}
$r = $this->conn->prepare($query)
->execute($params)
->fetchAll();
//More stuff here to manipulate $r (results)
return $r;
}
public function __call($name, $params)
{
call_user_func_array(array($this->conn, $name), $params);
}
}
Note: THIS CODE IS UNTESTED!!!!
ORM
Another option is using an ORM, which would let you interact with your models/entities directly without bothering with creating/destroying connections, inserting/deleting, etc... Doctrine2 or Propel are good bets for PHP.
Howeveran ORM is a lot more complex than using PDO directly.
How do I change this function to another function. I don't want to use the get_result
I searched online but could not find an answer that could help me.
public function Select($Table_Name, $Conditions='' ,$Array_Conditions_Limit=NULL , $OrderBy='', $Limit='', $Selected_Fields='*')
{
$Query = "SELECT ".$Selected_Fields." FROM ".$Table_Name;
if(!empty($Conditions))
$Query .= " WHERE ".$Conditions;
if(!empty($OrderBy))
$Query .= " ORDER BY ".$OrderBy;
if(!empty($Limit))
$Query .= " LIMIT ".$Limit;
$Statment = $this->ConnectionResult->prepare($Query);
if(isset($Array_Conditions_Limit) )
{
$Statment = $this->DynamicBindVariables($Statment, $Array_Conditions_Limit);
$Statment->execute();
return $Statment->get_result();
}
else
$Statment->execute();
return $Statment->get_result();
}
This also functions dynamic bind variables
private function DynamicBindVariables($Statment, $Params)
{
if (is_array($Params) && $Params != NULL)
{
// Generate the Type String (eg: 'issisd')
$Types = '';
foreach($Params as $Param)
{
$Types .= $this->GetType($Param);
}
// Add the Type String as the first Parameter
$Bind_names[] = $Types;
// Loop thru the given Parameters
for ($i=0; $i<count($Params);$i++)
{
$Bind_name = 'bind' . $i;
// Add the Parameter to the variable
$$Bind_name = $Params[$i];
// Associate the Variable as an Element in the Array
$Bind_names[] = &$$Bind_name;
}
// Call the Function bind_param with dynamic Parameters
call_user_func_array(array($Statment,'bind_param'), $Bind_names);
}
elseif(isset($Params) && !empty($Params))
{
$Types = '';
$Types .= $this->GetType($Params);
$Statment->bind_param($Types ,$Params);
}
return $Statment;
}
I using the return value as follows:
$myresult =Select('post','post_category=?' ,2 );
$row = $myresul2->fetch_object()
First of all, I find this approach utterly useless. What are you actually doing is dismembering fine SQL sentence into some anonymous parts.
"SELECT * FROM post WHERE post_category=?"
looks WAY better than your anonymous parameters of which noone have an idea.
'post','post_category=?'
One can tell at glance what does first statement to do. and have no idea on the second. Not to mention it's extreme:
'post','post_category=?',NULL, NULL, 'username, password'
So, instead of this kindergarten query builder I would rather suggest a function that accepts only two parameters - a query itself and array with bound data:
$myresult = Select("SELECT * FROM post WHERE post_category=?", [2]);
To make it more useful, I wouild make separate functions to get different result types, making your second line with fetch_object() obsolete (however, speaking of objects, they are totally useless to represent a table row). Example:
$row = $db->selectRow("SELECT * FROM post WHERE post_category=?", [2]);
Look: it's concise yet readable!
As a further step you may wish to implement more placeholder types, to allow fields for ORDER BY clause be parameterized as well:
$data = $db->getAll('id','SELECT * FROM t WHERE id IN (?a) ORDER BY ?n', [1,2],'f');
you can see how it works, as well as other functions and use cases in my safeMysql library
I'm pretty new to using PDO so I'm not sure if I have it down correctly, however with the following test I'm able to do some injection which I would like to bypass.
In my models class I have some shortcut methods. One of them is called return_all($table,$order,$direction) which simply returns all rows from a table:
public function return_all($table,$order = false, $direction = false) {
try {
if($order == false) {
$order = "create_date";
}
if($direction != false && !in_array($direction,array("ASC","DESC"))) {
$direction = "DESC";
}
$sql = "SELECT * FROM ".mysql_real_escape_string($table)." ORDER BY :order ".$direction;
$query = $this->pdo->prepare($sql);
$query->execute(array("order" => $order));
$query->setFetchMode(PDO::FETCH_ASSOC);
$results = $query->fetchAll();
} catch (PDOException $e) {
set_debug($e->getMessage(), true);
return false;
}
return $results;
}
This works fine, except, if I pass the following as $table into the method:
$table = "table_name; INSERT INTO `users` (`id`,`username`) VALUES (UUID(),'asd');";
Now it's unlikely that someone will ever be able to change the $table value as it's hard-coded into my controller functions, but, i'm a little concerned that I'm still able to do some injection even when I use PDO. What's more surprising is that the mysql_real_escape_string() did absolutely nothing, the SQL still ran and created a new user in the users array.
I also tried to make the table name a bound parameter but got a sql error I assume due to the `` PDO adds around the table name.
Is there a better way to accomplish my code below?
You have already solved your problem with direction.
if($direction != false && !in_array($direction,array("ASC","DESC"))) {
$direction = "DESC";
}
Use the same technique for table names
$allowed_tables = array('table1', 'table2');//Array of allowed tables to sanatise query
if (in_array($table, $allowed_tables)) {
$sql = "SELECT * FROM ".$table." ORDER BY :order ".$direction;
}