Uncaught ArgumentCountError: Too few arguments to function - php

I want to save the data with $_POST method to the database using PDO with PHP. I wrote the code, but it gave me an error. Is there anyone who can figure this out?
macex.php
function daireekle($daire_no,$dpass,$daire_statu,$daire_adi,$daire_soyadi){
$sql ="insert into users (KULLANICIADI,SIFRE,TIPI,ADI,SOYADI) values ('$daire_no','$dpass','$daire_statu','$daire_adi','$daire_soyadi')";
$insert =$this->connection->query($sql);
}
Blok.html
<?php
$sinif = new macex();
if ($_POST) {
$daire_no = $_POST['daireno'];
$daire_adi = $_POST['daireadi'];
$daire_soyadi = $_POST['dairesoyadi'];
$daire_statu = $_POST['statu'];
$dpass = $_POST['dpass'];
$sinif->daireekle('users', ['KULLANICIADI', 'SIFRE', 'TIPI', 'ADI', 'SOYADI'], [$daire_no, $dpass, $daire_statu, $daire_adi, $daire_soyadi]);
}
?>

Your code is a bit difficult to understand, in terms of structure, but you're definitely misusing PDO by injecting unsanitized POST data.
If I were to attempt to convert your sample into something workable, I would suggest the following amendments:
(Note: Assuming $this->connection-> is your PDO object)
Blok.html
(not sure why you've called this PHP file Blok.html)
<?php
$sinif = new macex();
if ($_POST) {
$sinif->daireekle( $_POST );
}
?>
macex.php
function daireekle( $postArray ){
// Parameterised PDO will mostly prevent SQL Injection, but it's still best practice
// to validate and sanitize your inputs, especially if they are coming from the user
$daire_no = $postArray ['daireno'];
$daire_adi = $postArray ['daireadi'];
$daire_soyadi = $postArray ['dairesoyadi'];
$daire_statu = $postArray ['statu'];
$dpass = $postArray ['dpass'];
// Define the query with positional params
$sql = 'INSERT INTO users (KULLANICIADI, SIFRE, TIPI, ADI, SOYADI) VALUES (?,?,?,?,?)';
// Create an array of your params in the order they apply to the query
$paramsArray = array('$daire_no','$dpass','$daire_statu','$daire_adi','$daire_soyadi');
// Prepare the parameterised query
$query = $this->connection->prepare($sql);
// Execute the INSERT query with the parameters
$query->execute($paramsArray);
}
You could of course use different variations on the above, included named parameters, but it's conciser to explain as per the above.

Related

Converting regular mysql into prepared statements

Im new to database and i have written a LOT of PHP code that accesses a database using MySQL.
I didnt take into account SQL injection attacks so i have to re-write all that PHP code to use mysql prepared statements.
After looking at videos on how to used prepared SQL statements, to perform just ONE SQL command requires a whole lot of "prepared" statements. My existing code has lots of different SQL statements all over the place, it would be a nightmare to change all that code to pack and unpack all the required preparation for each "prepared" statement command.
Is there some kind of wrapper i can use to prevent turning one line of regular SQL into 6 or 7 lines of prepared statements?
For example use to do this line line of SQL
SELECT * from users where userid=10
needs many more lines of prepared SQL statements, especially if there are lots of other SQL statements too it now becomes very complex.
Is there was some sort of one line wrapper that i can call that accepts the template SQL string, plus the parameters, which also executes the command and returns the result in just one line of wrapper for different types of MYSQL statements it would be great and the code would be much less confusing looking and error prone.
For example
$users=WrapAndExecute($db,"SELECT * from users where userid=?","s",$userid);
$data=WrapAndExecute($db,"UPDATE table SET username=?,city=?","ss",$name,$city);
$result=WrapAndExecute($db,"DELETE from table where id=?","s",$userid);
$result=WrapAndExecute($db,"INSERT into ? (name,address) VALUES(?,?)","ss","users",$name,$address);
Each of those lines above would create a prepared statement template, do the bind, execute it and return the result that a regular MYSQL statement would. This would create minimal impact on existing code.
Anybody knows how to do this or if some easy php library or class already exists to do this, that i can just import and start using it?
Thanks
You don't need to change a query to a prepared statement if it has no PHP variables in it. If it has just constant expressions, it's safe from SQL injection.
$sql = "SELECT * from users where userid=10"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
You don't need to change a query that contains PHP variables, as long as the value of that variable is a constant specified in your code. If it doesn't take its value from any external source, it's safe.
$uid = 10;
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
You don't need to change a query that contains PHP variables, as long as you can filter the value to guarantee that it won't risk an SQL injection. A quick and easy way to do this is to cast it to an integer (if it's supposed to be an integer).
$uid = (int) $_GET['uid'];
$sql = "SELECT * from users where userid=$uid"; // Safe!
$stmt = $pdo->query($sql);
$data = $stmt->fetchAll();
That leaves cases where you are using "untrusted" values, which may have originated from user input, or reading a file, or even reading from the database. In those cases, parameters are the most reliable way to protect yourself. It's pretty easy:
$sql = "SELECT * from users where userid=?"; // Safe!
// two lines instead of the one line query()
$stmt = $pdo->prepare($sql);
$stmt->execute([$_GET['uid']]);
$data = $stmt->fetchAll();
In a subset of cases, you need one additional line of code than you would normally use.
So quit your whining! ;-)
Re your comment about doing prepared statements in mysqli.
The way they bind variables is harder to use than PDO. I don't like the examples given in http://php.net/manual/en/mysqli.prepare.php
Here's an easier way with mysqli:
$sql = "SELECT * from users where userid=?"; // Safe!
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('i', $_GET['uid']);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_all();
I don't like the stuff they do in their examples with bind_result(), that's confusing and unnecessary. Just use get_result(). So with mysqli, you need two more lines of code than you would with PDO.
I've written query wrappers for mysqli that emulate the convenience of PDO's execute() function. It's a PITA to get an array mapped to the variable-arguments style of bind_param().
See the solution in my answers to https://stackoverflow.com/a/15933696/20860 or https://stackoverflow.com/a/7383439/20860
I were in the same boat, and I wrote such a wrapper that works exactly the way you want, save for it's being a class, not a function.
$user = $sdb->getRow("SELECT * from users where userid=?s", $userid);
$sdb->query("UPDATE table SET username=?s, city=?s", $name, $city);
$sdb->query("DELETE from table where id=?s", $userid);
$sdb->query("INSERT into ?n (name,address) VALUES(?s,?s)","users", $name, $address);
The above is a working code, as long as you have somewhere in your bootstrap file
$db = mysqli_connect(...);
...
require 'safemysql.class.php';
$sdb = new SafeMySQL('mysqli' => $db);
Note that none of the other suggestions could do anything like that.
Also note that if I were writing it today, I would have used PDO, as this class is duplicating a lot of functionality already exists in PDO.
Take a look at the PDO extension in PHP - http://php.net/manual/en/intro.pdo.php: it it secure against injections thanks to prepared statements; also, it allows you to connect to many different databases (e.g. MySQL, MSSQL, etc.).
You can then build your own wrapper as you wish to keep it clean; for example your own wrapper could be as follows:
(following example will return user rows as objects)
// connect to DB
$GLOBALS['default_db'] = new DB('localhost','db_name','username','password') ;
// Get users and output results
$query = new DBQuery('SELECT * FROM users WHERE userid = ?',array(10)) ;
var_dump($query -> results()) ;
var_dump($query -> num_rows()) ;
// DB connection
class DB {
public $connection;
public function __construct($host , $dbname , $username , $password) {
$this->connection = new \PDO('mysql:host=' . $host . ';dbname=' . $dbname , $username , $password);
}
}
// Wrapper
class DBQuery {
private $num_rows = 0;
private $results = array();
public function __construct($query , $params = null , $class_name = null , DB $db = null) {
if ( is_null($db) ) {
$db = $GLOBALS['default_db'];
}
$statement = $db->connection->prepare($query);
$statement->execute($params);
$errors = $statement->errorInfo();
if ( $errors[2] ) {
throw new \Exception($errors[2]);
}
$fetch_style = ($class_name ? \PDO::FETCH_CLASS : \PDO::FETCH_OBJ);
$this->results = $class_name ? $statement->fetchAll($fetch_style , $class_name) : $statement->fetchAll($fetch_style);
$this->num_rows += $statement->rowCount();
while ( $statement->nextrowset() ) {
$this->results = array_merge($this->results,$class_name ? $statement->fetchAll($fetch_style , $class_name) : $statement->fetchAll($fetch_style));
$this->num_rows += $statement->rowCount();
}
}
public function num_rows() {
return $this->num_rows;
}
public function results() {
return $this->results;
}
}
Since a key requirement seems to be that you can implement this with minimal impact on your current codebase, it would have been helpful if you had told us what interface you currently use for running your queries.
While you could use PDO:
that means an awful lot of work if you are not already using PDO
PDO exceptions are horrible
Assuming you are using procedural mysqli (and have a good reason not to use mysqli_prepare()) its not that hard to write something (not tested!):
function wrapAndExecute()
{
$args=func_get_args();
$db=array_shift($args);
$stmt=array_shift($args);
$stmt_parts=explode('?', $stmt);
if (count($args)+1!=count($stmt_parts)) {
trigger_error("Argument count does not match placeholder count");
return false;
}
$real_statement=array_shift($stmt_parts);
foreach ($args as $k=>$val) {
if (isnull($val)) {
$val='NULL';
} else if (!is_numeric($val)) {
$val="'" . mysqli_real_escape_string($db, $val) . "'";
}
$real_statement.=$val . array_shift($stmt_parts);
}
return mysqli_query($db, $real_statement);
}
Note that this does not handle IS [NOT] NULL nicely nor a literal '?' in the statement nor booleans (but these are trivial to fix).

Real_escape_string prevents INSERT statement from working MYSQL PHP

So I'm making my own blog scripts using MYSQL and PHP.
I had the whole 'writing the blog to a database' thing working perfectly, until I realised that if you tried to write a blog with speech marks, this would prevent the INSERT statement from working (obviously - the speechmarks were ending the SQL statement).
So I tried to use real_escape_string, and now the INSERT doesn't work even if you exclude quotes.
I tried using:
sqlstate
in order to find out the issue, and it returned "42000" - which, after googling for a little bit, refers to a syntax error, which doesn't make much sense as there is no syntax error before the use of real_escape_string.
Also, I'm now getting this error:
Call to a member function close() on a non-object in /postarticle.php on line 37
Which refers to the close() call in the ELSE statement.
Please may you help? Been going round in circles for a while. Here is my code:
<?php
$host = 'CENSORED';
$user = 'CENSORED';
$pass = 'CENSORED';
$db = 'CENSORED';
$connection = new mysqli($host,$user,$pass,$db);
$_SESSION["article"] = $_POST["article"];
$date_of_blog = getdate();
$article = ($_SESSION["article"]);
$sql1 = "SELECT * FROM `Blogs`";
$res1 = $connection->query($sql1);
$newrows = $res1->num_rows + 1;
$sql2 = "INSERT INTO Blogs(BlogID, Blog_Contents, D_O_B) VALUES ('$newrows','$article','$date_of_blog')";
$sql2 = $connection->real_escape_string($sql2);
$res2 = $connection->query($sql2);
if ($res2->num_rows == $newrows)
{
$res->close();
$connection->close();
header( 'Location: adminpanel.php' );
}
else
{
echo ($connection->sqlstate);
$connection->close();
$res->close();
}
exit();
?>
Also, on a side note, the getdate() call that I've got has never worked. In the database every blog post comes up as:
0000:00:00 00:00:00
EDIT:
Issue is now solved. Find the functional code below:
<?php
$host = 'CENSORED';
$user = 'CENSORED';
$pass = 'CENSORED';
$db = 'CENSORED';
$connection = new mysqli($host,$user,$pass,$db);
$_SESSION["article"] = $_POST["article"];
$article = ($_SESSION["article"]);
$article = $connection->real_escape_string($article);
$sql1 = "SELECT * FROM `Blogs`";
$res1 = $connection->query($sql1);
$newrows = $res1->num_rows + 1;
$sql2 = "INSERT INTO Blogs(BlogID, Blog_Contents, D_O_B) VALUES (\"$newrows\",\"$article\",CURDATE())";
$res2 = $connection->query($sql2);
if ($res2 != false)
{
header( 'Location: adminpanel.php' );
}
else
{
echo ($connection->sqlstate);
}
$connection->close();
$res->close();
exit();
?>
I'm very sorry if these questions are basic and annoy the professionals around here; I've tried to follow the guidelines and I've googled for a while etc. I just haven't found any solutions that match my issue(s).
Thankyou for your time.
There are a number issues with the code as originally posted. Chiefly, the cause of the two issues you initially identified is a misuse of mysqli::real_escape_string(). It needs to be called on each variable individually which appears in the code. So instead of calling it on the whole statement, it must be called multiple times for multiple variables like:
$article = $connection->real_escape_string($connection);
The failure of the query due to incorrect quoting (due to real_escape_string()) is the reason for the error message calling close().
As ascertained in the comments, you are using num_rows + 1 to validate that one new row has been inserted based on the previous number of rows returned. This is problematic for a few reasons. Mainly, it exposes a race condition wherein a row may be inserted from two sessions at once and one or both will fail because the expected value for $newrows doesn't match. Really BlogID should be an auto_increment column in your database. That eliminates the need for any logic around it whatsoever. You don't even need to include it in the INSERT because it will be automatically incremented.
That also completely eliminates the need for the first SELECT statement.
Substituting MySQL's native NOW() function for the date value, you can simplify the statement to:
INSERT INTO Blogs (Blog_Contents, D_O_B) VALUES ('$article', NOW())
To test success or failure of the insert, you just need to verify that its variable is not false.
Putting this together, your code can be reduced as:
if (!isset($_POST['article'])) {
// exit or handle an empty post somehow...
}
$connection = new mysqli($host,$user,$pass,$db);
$_SESSION["article"] = $_POST["article"];
// Escape $article for later use
$article = $connection->real_escape_string($_SESSION["article"]);
// Only an INSERT is needed. $article is already escaped
$sql = "INSERT INTO Blogs (Blog_Contents, D_O_B) VALUES ('$article', NOW())";
// Run the query
$res = $connection->query($sql);
// Test for failure by checking for a false value
if ($res) {
// The connection & resource closure can be omitted
// PHP will handle that automatically and implicitly.
header( 'Location: adminpanel.php' );
// Explictly exit as good practice after redirection
exit();
}
else {
// The INSERT failed. Check the error message
echo $connection->error;
}
This should bring your current code into working order. However, since you're learning this it is an excellent time to begin learning to use prepared statements via prepare()/bind_param()/execute() in MySQLi. This is a recommended best practice to prevent SQL injection, although using real_escape_string() works as long as you use it correctly and never forget.
See How can I prevent SQL injection in PHP for examples.
But it would look like:
// connection already established, etc...
// Prepare the statement using a ? placeholder for article
$stmt = $connection->prepare("INSERT INTO Blogs (Blog_Contents, D_O_B) VALUES (?, NOW())");
if ($stmt) {
// bind in the variable and execute
// Note that real_escape_string() is not needed when using
// the ? placeholder for article
$stmt->bind_param('s', $_SESSION['article']);
$stmt->execute();
// Redirect
header( 'Location: adminpanel.php' );
exit();
}
else {
echo $connection->error;
}
You need to apply the real_escape_string function to the variables not the entire SQL string.
$sql2 = "INSERT INTO Blogs(BlogID, Blog_Contents, D_O_B) VALUES ('".$connection->real_escape_string($newrows)."','".$connection->real_escape_string($article)."','".$connection->real_escape_string($date_of_blog)."')";
The purpose is to remove anything that might be misinterpreted as query functions by MySQL, but there are parts of the query that you obviously want to be interpreted as such.

How can I write a PHP function that takes an arbitrary number of parameters?

I am trying to find a way to create a function in PHP that will wrap a SQL query given in the parameter so that I can prevent SQL Injection in the function that can then be called many times throughout my application. Rather than repeating the same statements for each and every query.
For example say I have the following PHP code that prepares and executes a query to prevent SQL injection:
$name = "$_POST['name']";
$stmt = $db->prepare('SELECT * FROM test_table WHERE test_name = ?');
$stmt->execute(array($name));
For each query my application will need to make these statements will need to be repeated. I want a way to prevent having to do this each time, rather I would simply want to call a function each time and pass in the query.
How would I wrap this in a function that can then be called whenever I need to make a query in my application, given that I do not know in advance the amount of parameters that would need to be parameterized. The above query has one parameterized query, but each query may have a different amount.
Note:
I am using PDO statements
Something like this:
public function query($query)
{
// statements here
}
Where the query is passed in as a parameter.
Does anyone know how I can achieve this?
Currently, I am using something like this that might work for you.
Example:
function superQuery($query, $params, $type = null) {
$pdo = new pdo(...);
$stmt = $pdo->prepare($query);
$stmt->execute($params);
if ($type === "select") {
$result = $stmt->fetchAll();
return $result;
} else {
return $stmt;
}
$query = "SELECT row FROM column WHERE row1 = ? AND row2 = ?";
$params = [$row1, $row2];
$type = "select";
$row = selectQuery($query, $params, $type);
// returns multidimensional array or true/false depending if argument is used //
There's lots of ways you can do it. You could also pass a count argument if you wanted to return a count instead of a result set. But hopefully this points you in the right direction and gives you some ideas.

convert mysql to pdo

So i have a function thats supposed to handle all data execute operations: sql
function loadResult($sql)
{
$this->connect();
$sth = mysql_query($sql);
$rows = array();
while($r = mysql_fetch_object($sth)) {$rows[] = $r;}
$this->disconnect();
return $rows;
}
I want to convert it to pdo and this is what i have so far: pdo
function loadResult($sql)
{
$this->connect();
$sth = $this->con->prepare($sql);
//execute bind values here
$sth->execute();
$rows = array();
while ( $r = $sth->fetch(PDO::FETCH_OBJ) ) {$rows[] = $r;}
$this->disconnect();
return $rows;
}
Here is an example of a function on how am using it to view data from the database:
function viewtodolist()
{
$db=$this->getDbo(); //connect to database
$sql="SELECT * FROM mcms_todolist_tasks";
//maybe the bind values are pushed into an array and sent to the function below together with the sql statement
$rows=$db->loadResult($sql);
foreach($rows as $row){echo $row->title; //echo some data here }
}
I have just pulled out the important snippets so some variables and methods are from other php classes. Somehow, the mysql query works fine, but the PDO query is giving me headaches on how to include bindValue paremeters most probably in the viewtodolist() function to make it reusable. Any suggestions/recommendations are welcome.
Since your existing function accepts a fully-formed SQL string, with no placeholders, you don't need to use prepare + bind. Your code as written should work fine, or you could use PDO::query() to execute the SQL in one step.
If you want to use parameterised queries, then your loadResult function is going to have to change a bit, as is the way you write your SQL. The example SQL you give doesn't actually have anything in that could be turned into a parameter (column names and table names can't be parameters as discussed here), but I'll use an imaginary variation:
// Get the todo tasks for a particular user; the actual user ID is a parameter of the SQL
$sql = "SELECT * FROM mcms_todolist_tasks WHERE user_id = :current_user_id";
// Execute that SQL, with the :current_user_id parameter pulled from user input
$rows = $db->loadResult($sql, array(':current_user_id' => $_GET['user']));
This is a nice secure way of putting the user input into the query, as MySQL knows which parts are parameters and which are part of the SQL itself, and the SQL part has no variables that anyone can interfere with.
The simplest way of making this work with your existing loadResult function would be something like this:
// Function now takes an optional second argument
// if not passed, it will default to an empty array, so existing code won't cause errors
function loadResult($sql, $params=array())
{
$this->connect();
$sth = $this->con->prepare($sql);
// pass the parameters straight to the execute call
$sth->execute($params);
// rest of function remains the same...
There are cleverer things you can do with parameterised queries - e.g. binding variables to output parameters, preparing a query once and executing it multiple times with different parameters - but those will require more changes to the way your calling code works.

Passing query to a object, that contains bounded values

I am using PDO to connect with my MySQL db
So before in my users list i did:
Build the query by conditions (online? new user? sex?)
prepared the query
bound the values to the query
execute
fetch in a while() to show the results
Now I am just calling a object, UserCollection, load it and foreach the output.
$list = new UserCollection( $connect );
$list->load();
like that. In load() is the standard query for now:
$stmt = $this->_pdo->query( 'SELECT id FROM users' );
$data = $stmt->fetchAll( PDO::FETCH_ASSOC );
Theres no bounded variables to it, so it is not prepared.
Works fine, and it grabs just all the users.
Now what I wish to do is pass the query, that has been build to UserCollection() and use it to load() in the query.
I could do this easy, if the query was not bounded to variables, like the query above.
So what should I do if i want to pass a variable like this:
SELECT firstname, lastname, id, sex, last_access, bostadsort FROM users WHERE sex=:sex
Then i would need to bind :sex, and i cant just write bindValue() as
the value is not inside the UserCollection object
sometimes it could be only WHERE firstname=:firstname and not :sex, so it would throw error that I have bound a value that I dont use..
So what should I do here? what can i do?
Thanks in forward
You could modify your load method:
function load($params = array()){
$stmt = $this->_pdo->prepare("*your sql here*");
if (!empty($params)){
foreach ($params as $param_key=>$param_value)
$stmt ->bindParam($param_key, $param_value);
}
$data = $stmt->fetchAll( PDO::FETCH_ASSOC );
}
this way you don't have to modify existing code. To use it with new queries with arbitrarry number of parameters you'll just call it like:
$sex = 'male';
$firstname = 'Richard';
$params = array(':sex'=>&$sex, ':firstname'=>&$firstname);
$list->load($parameters);
Don't have PHP at hand, so can't test it, but I think that should work.

Categories