I want to see what PDO is preparing without looking into the MySQL logs. Basically the final query it has built right before it executes the query.
Is there a way to do this?
There is no built-in way to do it. bigwebguy created a function to do it in one of his answers:
/**
* Replaces any parameter placeholders in a query with the value of that
* parameter. Useful for debugging. Assumes anonymous parameters from
* $params are are in the same order as specified in $query
*
* #param string $query The sql query with parameter placeholders
* #param array $params The array of substitution parameters
* #return string The interpolated query
*/
public static function interpolateQuery($query, $params) {
$keys = array();
# build a regular expression for each parameter
foreach ($params as $key => $value) {
if (is_string($key)) {
$keys[] = '/:'.$key.'/';
} else {
$keys[] = '/[?]/';
}
}
$query = preg_replace($keys, $params, $query, 1, $count);
#trigger_error('replaced '.$count.' keys');
return $query;
}
This is just a derivation of #Maerlyn code above which accept non-asociative arrays for params like and where if the key starts already with ':' don't add it.
/**
* Replaces any parameter placeholders in a query with the value of that
* parameter. Useful for debugging. Assumes anonymous parameters from
* $params are are in the same order as specified in $query
*
* #param string $query The sql query with parameter placeholders
* #param array $params The array of substitution parameters
* #return string The interpolated query
*
* #author maerlyn https://stackoverflow.com/users/308825/maerlyn
*/
function interpolateQuery($query, $params) {
$keys = array();
# build a regular expression for each parameter
if (!isAssoc($params)){
$_params = []; // associative array
foreach($params as $param){
$key = $param[0];
$value = $param[1];
// $type = $param[2];
$_params[$key] = $value;
}
$params = $_params;
}
foreach ($params as $key => $value) {
if (is_string($key)) {
$keys[] = '/'.((substr($key,0,1)==':') ? '' : ':').$key.'/';
} else {
$keys[] = '/[?]/';
}
}
$query = preg_replace($keys, $params, $query, 1, $count);
#trigger_error('replaced '.$count.' keys');
return $query;
}
Related
I've been having trouble with storing a password_hash into my MySql database. It's cutting off characters from the beginning and some in between.
I've tried setting the encoding to UTF 8 in the HTML, setting the same in the connect function and also I've changed the collation to every possible type. Yet I am able to paste the correct hash from an echo into the database and that works fine.
$password = $_POST['pno'];
$pno = password_hash($password,PASSWORD_DEFAULT);
MySqlDb::query('INSERT INTO user (name, email, pno, address_line_1, address_line_2, town, county, post_code, phone) VALUES (:name, :email, :pno, :address_line_1, :address_line_2, :town, :county, :post_code, :phone)',
[':name'=>$name, ':email'=>$email, ':pno'=>$pno, ':address_line_1'=>$address_line_1, ':address_line_2'=>$address_line_2, ':town'=>$town, ':county'=>$county, ':post_code'=>$post_code, ':phone'=>$phone]);
echo $pno;
So the echo will give me this: $2y$10$rtlUaDeXsyhtWS5.SS4nuu.xapBdrHXG7V.DpSLCLAAwYqXPJHKWi
But in the database it stores this:
y$rtlUaDeXsyhtWS5.SS4nuu.xapBdrHXG7V.DpSLCLAAwYqXPJHKWi
I've also tried the encode and decode functions, pretty much out of ideas.
class MySqlDb {
/**
* #var MySQLi instance
*/
protected static $link;
/**
* #var bool set to true to print query before executing
*/
public static $debug = false;
/**
* Creates a new MySQLi instance use getConnection() to retrieve
* #return none
*/
private function __construct() {
self::$link = #mysqli_connect(DATABASE_HOST, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_NAME);
if (mysqli_connect_errno()) {
//die('MySqlDb Error: Could not connect to database ('.mysqli_connect_errno().')');
throw new Exception('MySqlDb::() Could not connect to database (' . mysqli_connect_errno() . ')');
}
}
/**
* Returns the MySQLi connection
* #return MySQLi instance
*/
public static function getConnection() {
if (!self::$link) {
new MySqlDb();
}
return self::$link;
}
/**
* Helper function for mysqli_query if params are given will use quote function
* see below MySqlDb:quote this is to help avoid sql injection attacks and automatically escape slashes etc
*
* #param string $sql
* #param array $params
* #return mysqli_query resource ...
*
* Example:
* MySqlDb::query("drop from user where id=$_GET['id']"); // very bad 'id' could contain "' or 1=1"
* MySqlDb::query("drop from user where id=:id", [':id' => $_GET['id']]); // much better
*/
public static function query($sql, array $params = null) {
$con = self::getConnection();
if (!empty($params)) {
$sql = self::quote($sql, $params);
}
if (self::$debug) {
echo $sql;
}
$res = mysqli_query($con, $sql);
if (!$res) {
//die('MySqlDb Error: query: '.mysqli_error($con));
throw new Exception('MySqlDb::query() ' . mysqli_error($con));
}
return $res;
}
/**
* Helper function to quote sql values similar to pdo::bindvalue using named parameters
* to help avoid sql injection attacks and avoid using stuff like addslashes
*
* #param string $sql sql string to quote
* #param array $params key value pairs of parameters and values to placed into them
* #return string sql quoted string
*
* Example:
* $sql = MySqlDb::quote("select * from users where id=:unique1 or id=:unique2", [':unique1'=>2, ':unique2'=>4])
* TODO:
* MySqlDb::quote("select * from users where id+5 = :id or id = :id",[':id'=>3] fails on number params dont match like pdo
* could convert 'substr_count($sql, ':')' into '$unique_sql_param_count'
* e.g. preg_match_all("/:[\w-_]+\b/i", $test, $matches); $paramcount = count(array_flip($matches[0]));
*/
public static function quote($sql, array $params) {
// check correct number of params
if (substr_count($sql, ':') != count($params)) {
//die('MySqlDb Error: quote: number params do not match');
throw new Exception('MySqlDb::quote() number params do not match');
}
$cnt = 0;
foreach ($params as $param => $value) {
//$sql = str_replace($param, "'".self::escape($value)."'", $sql, $cnt); // was matching sub strings :(
$sql = preg_replace("/$param\b/i", "'" . self::escape($value) . "'", $sql, -1, $cnt);
if ($cnt !== 1) {
//die("MySqlDb Error: quote: param '{$param}' not matched or is duplicate");
throw new Exception("MySqlDb::quote() param '{$param}' not matched or is duplicate");
}
}
return $sql;
}
/**
* Wrapper for mysqli_escape_string
* #param mixed $value
* #return escaped value
*/
public static function escape($value) {
return mysqli_escape_string(self::getConnection(), $value);
}
/**
* Helper function to return all rows in result as an associative array
* #param string $sql
* #return array array of arrays
*/
public static function all($sql, array $params = null) {
return mysqli_fetch_all(self::query($sql, $params), MYSQLI_ASSOC);
}
/**
* Helper function to return first row in result from potential multiple values
* #param string $sql
* #return array single array
*/
public static function first($sql, array $params = null) {
$res = mysqli_fetch_array(self::query($sql, $params), MYSQLI_ASSOC);
return is_array($res) ? $res : [];
}
/**
* Helper function to return first field in result from potential multiple values
* #param string $sql
* #return mixed single variable
*/
public static function scalar($sql, array $params = null) {
$res = mysqli_fetch_array(self::query($sql, $params), MYSQLI_NUM);
return is_array($res) ? $res[0] : null;
}
}
This line in your quote method is the direct cause of the problem.
$sql = preg_replace("/$param\b/i", "'" . self::escape($value) . "'", $sql, -1, $cnt);
The result of self::escape($value) will be your original hash,
$2y$10$rtlUaDeXsyhtWS5.SS4nuu.xapBdrHXG7V.DpSLCLAAwYqXPJHKWi
In the context of preg_replace, the $2 and $10 are meaningful. In the Parameters section of the preg_replace documentation it says
replacement may contain references of the form \n or $n, with the latter form being the preferred one. Every such reference will be replaced by the text captured by the n'th parenthesized pattern.
You have no parenthesized patterns, so those values are replaced with nothing.
You could fix it by escaping any $ in the replacement parameter, but I would rather recommend replacing your escape/replace-based quote method with a method that creates a prepared statement and binds the parameters before executing it.
My deprecation checker is throwing this error:
Using deprecated language feature assign by reference(&=) Since PHP 5.3 use normal assignment instead.
So, I am trying to figure out how to re-code the methods in this class to not use by reference or at least use it properly (if it is allowed at all - which I'm not clear on either).
below is the portion of the class using by reference. The entire class is here, the test and the deprecation checker log is here.
I would like some help recoding the class to remove the use of by reference
class ParameterBag
{
/**
* Sets value.
* can use 'key' = ['subkey' => value, 'subkey2' => value2]
* or
* 'key.subkey' = value
* 'key.subkey2' = value2
*
* #param $key
* #param $value
*/
public function set($key, $value)
{
$parameters = &$this->resolvePath($key, true);
$key = $this->resolveKey($key);
$parameters[$key] = $value;
}
/**
* Resolves a path in parameters property and returns it as a reference.
*
* This method allows structured namespacing of parameters.
*
* #param string $key Key name
* #param boolean $writeContext Write context, default false
*
* #return array
*/
private function &resolvePath($key, $writeContext = false)
{
$array = &$this->parameters;
$key = (strpos($key, $this->ns) === 0) ? substr($key, 1) : $key;
// Check if there is anything to do, else return
if (!$key) {
return $array;
}
$parts = explode($this->ns, $key);
if (count($parts) < 2) {
if (!$writeContext) {
return $array;
}
$array[$parts[0]] = [];
return $array;
}
unset($parts[count($parts) - 1]);
foreach ($parts as $part) {
if (!array_key_exists($part, $array)) {
if (!$writeContext) {
return $array;
}
$array[$part] = [];
}
$array = &$array[$part];
}
return $array;
}
}
This appears to be a bug in the deprecation tool. According to Deprecated features in PHP 5.3.x:
Assigning the return value of new by reference is now deprecated.
Call-time pass-by-reference is now deprecated.
But assignment by reference in general is not deprecated, and Returning References says:
To use the returned reference, you must use reference assigment
I'm working on a project that has an Arr helper class and I am curious about something: Is there a benefit in doing this:
/**
* Sets an array value
*
* #param array $array
* #param string $path
* #param mixed $value
*
* #return void
*/
public static function set(array &$array, $path, $value)
{
$segments = explode('.', $path);
while (count($segments) > 1) {
$segment = array_shift($segments);
if ( ! isset( $array[$segment] ) || ! is_array($array[$segment])) {
$array[$segment] = [];
}
$array =& $array[$segment];
}
$array[array_shift($segments)] = $value;
}
Arr::set($data['stories'], 'fields.age', '3');
Over this:
$data['stories']['fields']['age'] = '3';
Or is there a better, faster way?
The function is just easier to type - you do not have to make so many brackets and quotation mark. It makes things easier.
For performance: If you set the array values with normal syntax, it is faster, because you do not have to explode the path or check for existence before. But this takes not so many time.
I wrote this function inside my Repository class to receive a simple key value from Doctrine. Isn't there a build in Doctrine function to do this? (I couldn't find it). Or maybe the code can be improved.
Here's my function:
public function getListBy($criteria=null, $key, $value) {
$dql = "SELECT i.".$key." as k,
i.".$value." as v
FROM MbFooBundle:Input i";
if (isset($criteria) && is_array($criteria)) {
foreach($criteria as $cKey => $cValue) {
if (!isset($where))
$where = " WHERE ";
else
$where .= " AND ";
$where .= "i.".$cKey." = ".(is_numeric($cValue) ? $cValue : "'".$cValue."'");
}
$dql .= $where;
}
$query = $this->getEntityManager()
->createQuery($dql);
$result = $query->getArrayResult();
$list = array();
if (count($result)) {
foreach($result as $data) {
$list[$data['k']] = $data['v'];
}
}
return $list;
}
I wouldn't do this. Yet this code is vulnerable to SQL Injection but it also breaks some standards.
Here's my way of thinking.
I would create a method which will manipulate the results of the standard doctrine's findBy
/**
* Data Manipulator
*/
class DataManipulator
{
/**
* Associates any traversable input into its key and value
*
* #param mixed $input A Traversable input
* #param string $key Key to associate
* #param string $value Value to associate
* #return array Associated array
*
* #throws InvalidArgumentException When Input is not traversable
*/
public function associate($input, $key, $value)
{
if (!is_array($input) && !($input instanceof Traversable)) {
throw new InvalidArgumentException("Expected traversable");
}
$out = array();
foreach ($input as $row) {
$out[$this->getInput($row, $key)] = $this->getInput($row, $value);
}
return $out;
}
/**
* Fetches the input of a given property
*
* #param mixed $row An array or an object
* #param string $find Property to find
* #return mixed Property's value
*
* #throws UnexpectedValueException When no matching with $find where found
*/
protected function getInput($row, $find)
{
if (is_array($row) && array_key_exists($find, $row)) {
return $row[$find];
}
if (is_object($row)) {
if (isset($row->$find)) {
return $row->$find;
}
$method = sprintf("get%s", $find);
if (method_exists($row, $method)) {
return $row->$method();
}
}
throw new UnexpectedValueException("Could not find any method to resolve");
}
}
Then you can use it
$em = $this->getDoctrine()->getManager();
$results = $em->getRepository('AcmeFooBundle:Input')
->findBy(array('category' => 'foo'));
$manipulator = new DataManipulator;
$filtered = $manipulator->associate($results, 'key', 'value');
You can see it working
If you need to select only partial objects, you should create a method in your repository which will fetch your partial input.
This function must only fetch the object, not associate its content.
public function findPartialBy(array $values, array $criterias = array())
{
$qb = $this->createQueryBuilder('i');
$qb->select($values);
foreach ($criterias as $key => $value) {
$qb->andWhere(sprintf("i.%s", $key), sprintf(":%s", $key))
$qb->setParameter(sprintf(":%s", $key), $value);
}
return $qb->getQuery()->getResult();
}
Then you can use it
$fetch = array('key', 'value');
$em = $this->getDoctrine()->getManager();
$results = $em->getRepository('AcmeFooBundle:Input')
->findPartialBy($fetch, array('category' => 'foo'));
$manipulator = new DataManipulator;
$filtered = $manipulator->associate($results, 'key', 'value');
Read more about partial objects
Read more about the query builder
Read more on how to select partial objects with a query builder
This question already has answers here:
Call to undefined method mysqli_stmt::get_result
(10 answers)
Closed 5 years ago.
On my server i get this error
Fatal error: Call to undefined method mysqli_stmt::get_result() in /var/www/virtual/fcb/htdocs/library/mysqlidbclass.php on line 144
And I am having a wrapper like this:
<?php
/* https://github.com/aaron-lord/mysqli */
class mysqlidb {
/**
* Set up the database connection
*/
public function __construct($server,$user,$password,$db){
$this->connection = $this->connect($server, $user, $password, $db, true);
}
/**
* Connect to the database, with or without a persistant connection
* #param String $host Mysql server hostname
* #param String $user Mysql username
* #param String $pass Mysql password
* #param String $db Database to use
* #param boolean $persistant Create a persistant connection
* #return Object Mysqli
*/
private function connect($host, $user, $pass, $db, $persistant = true){
$host = $persistant === true ? 'p:'.$host : $host;
$mysqli = new mysqli($host, $user, $pass, $db);
if($mysqli->connect_error)
throw new Exception('Connection Error: '.$mysqli->connect_error);
$mysqli->set_charset('utf8');
return $mysqli;
}
/**
* Execute an SQL statement for execution.
* #param String $sql An SQL query
* #return Object $this
*/
public function query($sql){
$this->num_rows = 0;
$this->affected_rows = -1;
if(is_object($this->connection)){
$stmt = $this->connection->query($sql);
# Affected rows has to go here for query :o
$this->affected_rows = $this->connection->affected_rows;
$this->stmt = $stmt;
return $this;
}
else {
throw new Exception;
}
}
/**
* Prepare an SQL statement
* #param String $sql An SQL query
* #return Object $this
*/
public function prepare($sql){
unset($this->stmt);
$this->num_rows = 0;
$this->affected_rows = -1;
if(is_object($this->connection)){
# Ready the stmt
$this->stmt = $this->connection->prepare($sql);
if (false===$this->stmt)
{
print('prepare failed: ' . htmlspecialchars($this->connection->error)."<br />");
}
return $this;
}
else {
throw new Exception();
}
}
public function multi_query(){ }
/**
* Escapes the arguments passed in and executes a prepared Query.
* #param Mixed $var The value to be bound to the first SQL ?
* #param Mixed $... Each subsequent value to be bound to ?
* #return Object $this
*/
public function execute(){
if(is_object($this->connection) && is_object($this->stmt)){
# Ready the params
if(count($args = func_get_args()) > 0){
$types = array();
$params = array();
foreach($args as $arg){
$types[] = is_int($arg) ? 'i' : (is_float($arg) ? 'd' : 's');
$params[] = $arg;
}
# Stick the types at the start of the params
array_unshift($params, implode($types));
# Call bind_param (avoiding the pass_by_reference crap)
call_user_func_array(
array($this->stmt, 'bind_param'),
$this->_pass_by_reference($params)
);
}
if($this->stmt->execute()){
# Affected rows to be run after execute for prepares
$this->affected_rows = $this->stmt->affected_rows;
return $this;
}
else {
throw new Exception($this->connection->error);
}
}
else {
throw new Exception;
}
}
/**
* Fetch all results as an array, the type of array depend on the $method passed through.
* #param string $method Optional perameter to indicate what type of array to return.'assoc' is the default and returns an accociative array, 'row' returns a numeric array and 'array' returns an array of both.
* #param boolean $close_stmt Optional perameter to indicate if the statement should be destroyed after execution.
* #return Array Array of database results
*/
public function results($method = 'assoc', $close_stmt = false){
if(is_object($this->stmt)){
$stmt_type = get_class($this->stmt);
# Grab the result prepare() & query()
switch($stmt_type){
case 'mysqli_stmt':
$result = $this->stmt->get_result();
$close_result = 'close';
break;
case 'mysqli_result':
$result = $this->stmt;
$close_result = 'free';
break;
default:
throw new Exception;
}
$this->num_rows = $result->num_rows;
# Set the results type
switch($method) {
case 'assoc':
$method = 'fetch_assoc';
break;
case 'row':
//return 'fetch_row';
$method = 'fetch_row';
break;
default:
$method = 'fetch_array';
break;
}
$results = array();
while($row = $result->$method()){
$results[] = $row;
}
$result->$close_result();
return $results;
}
else {
throw new Exception;
}
}
/**
* Turns off auto-committing database modifications, starting a new transaction.
* #return bool Dependant on the how successful the autocommit() call was
*/
public function start_transaction(){
if(is_object($this->connection)){
return $this->connection->autocommit(false);
}
}
/**
* Commits the current transaction and turns auto-committing database modifications on, ending transactions.
* #return bool Dependant on the how successful the autocommit() call was
*/
public function commit(){
if(is_object($this->connection)){
# Commit!
if($this->connection->commit()){
return $this->connection->autocommit(true);
}
else {
$this->connection->autocommit(true);
throw new Exception;
}
}
}
/**
* Rolls back current transaction and turns auto-committing database modifications on, ending transactions.
* #return bool Dependant on the how successful the autocommit() call was
*/
public function rollback(){
if(is_object($this->connection)){
# Commit!
if($this->connection->rollback()){
return $this->connection->autocommit(true);
}
else {
$this->connection->autocommit(true);
throw new Exception;
}
}
}
/**
* Return the number of rows in statements result set.
* #return integer The number of rows
*/
public function num_rows(){
return $this->num_rows;
}
/**
* Gets the number of affected rows in a previous MySQL operation.
* #return integer The affected rows
*/
public function affected_rows(){
return $this->affected_rows;
}
/**
* Returns the auto generated id used in the last query.
* #return integer The last auto generated id
*/
public function insert_id(){
if(is_object($this->connection)){
return $this->connection->insert_id;
}
}
/**
* Fixes the call_user_func_array & bind_param pass by reference crap.
* #param array $arr The array to be referenced
* #return array A referenced array
*/
private function _pass_by_reference(&$arr){
$refs = array();
foreach($arr as $key => $value){
$refs[$key] = &$arr[$key];
}
return $refs;
}
}
?>
Is there any way to use another function so that I won't have to rewrite whole app? Please tell me if any.
Please read the user notes for this method:
http://php.net/manual/en/mysqli-stmt.get-result.php
It requires the mysqlnd driver. if it isn't installed on your webspace you will have to work with BIND_RESULT & FETCH
http://www.php.net/manual/en/mysqli-stmt.bind-result.php
http://www.php.net/manual/en/mysqli-stmt.fetch.php
Extracted from here
The reason for this error is that your server doesn't have the mysqlnd driver driver installed. (See here.) If you have admin privileges you could install it yourself, but there is also an easier way:
I have written two simple functions that give the same functionality as $stmt->get_result();, but they don't require the mysqlnd driver.
You simply replace
$result = $stmt->get_result(); with $fields = bindAll($stmt);
and
$row= $stmt->get_result(); with $row = fetchRowAssoc($stmt, $fields);.
(To get the numbers of returned rows you can use $stmt->num_rows.)
You just have to place these two functions I have written somewhere in your PHP Script. (for example right at the bottom)
function bindAll($stmt) {
$meta = $stmt->result_metadata();
$fields = array();
$fieldRefs = array();
while ($field = $meta->fetch_field())
{
$fields[$field->name] = "";
$fieldRefs[] = &$fields[$field->name];
}
call_user_func_array(array($stmt, 'bind_result'), $fieldRefs);
$stmt->store_result();
//var_dump($fields);
return $fields;
}
function fetchRowAssoc($stmt, &$fields) {
if ($stmt->fetch()) {
return $fields;
}
return false;
}
How it works:
My code uses the $stmt->result_metadata(); function to figure out how many and which fields are returned and then automatically binds the fetched results to pre-created references. Works like a charm!
Also posted here.