How to use bindParam property correctly? - php

How could this code inserted 0 values to my database?
<?php
$params = [];
$table = "log";
$values = [
"text" => "missing socket file named ../../app/shop/socket/sectionheaderNavigationView.php",
"level" => 2,
"created_at" => 1672806059253,
"created_by" => 0
];
$queryColumn = "";
$queryValues = "";
$n = 0;
foreach($values as $x => $y) {
$queryColumn .= "__" . $x;
$queryValues .= ":" . $x;
$params[":" . $x] = $y;
$n++;
if($n < count($values)) {
$queryColumn .= ", ";
$queryValues .= ", ";
}
}
$query = "INSERT INTO $table($queryColumn) VALUES($queryValues)";
$db = new PDO("mysql:host=127.0.0.1;dbname=db_shop", "root", "");
$stmt = $db->prepare($query);
if(count($params) > 0) {
foreach($params as $x => $y) {
switch(true) {
case is_int($y): $type = PDO::PARAM_INT; break;
case is_string($y): $type = PDO::PARAM_STR; break;
case is_bool($y): $type = PDO::PARAM_BOOL; break;
default: $type = PDO::PARAM_NULL; break;
}
var_dump($x . " " . $y . " " . $type, $query);
if(!$stmt->bindParam($x, $y, $type)) {
break;
}
}
}
try {
$stmt->execute();
} catch(PDOException $e) {
echo "Exception: " . $e;
}
?>
The above code resulted
MariaDB [db_shop]> select * from log;
+-------+-----------+--------+---------+--------------+--------------+
| __key | __message | __text | __level | __created_at | __created_by |
+-------+-----------+--------+---------+--------------+--------------+
| 1 | | 0 | 0 | 0 | 0 |
| 2 | | 0 | 0 | 0 | 0 |
Isn't if even its null when checking $params shouldn't it be empty not 0 instead?
MariaDB [db_shop]> desc log;
+--------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+----------------+
| __key | int(11) | NO | PRI | NULL | auto_increment |
| __text | varchar(82) | NO | | NULL | |
| __level | int(11) | NO | | NULL | |
| __created_at | char(16) | NO | | NULL | |
| __created_by | int(11) | NO | | NULL | |
+--------------+-------------+------+-----+---------+----------------+
How could i properly handle this error, there also no exception.
Here the var_dump inside the looping checking for $params.
string(87) ":text missing socket file named ../../app/shop/socket/sectionheaderNavigationView.php 2"
string(108) "INSERT INTO log(__text, __level, __created_at, __created_by) VALUES(:text, :level, :created_at, :created_by)"
string(10) ":level 2 1"
string(108) "INSERT INTO log(__text, __level, __created_at, __created_by) VALUES(:text, :level, :created_at, :created_by)"
string(27) ":created_at 1672806059253 2"
string(108) "INSERT INTO log(__text, __level, __created_at, __created_by) VALUES(:text, :level, :created_at, :created_by)"
string(15) ":created_by 0 1"
string(108) "INSERT INTO log(__text, __level, __created_at, __created_by) VALUES(:text, :level, :created_at, :created_by)"
php -v;
PHP 8.1.10 (cli) (built: Aug 30 2022 18:05:49) (ZTS Visual C++ 2019 x64)
Thanks for any feedback

The answer to your question of why it inserted 0 is that bindParam() doesn't take the value of $y when you bind it. It takes the value of $y when you execute the query. But by then the loop has finished, so $y has a different value — the last value it had during the loop. The last value is 0, as we can see:
"created_by" => 0
You could fix this by using bindValue() instead of bindParam(). Then it would make a copy of the value of $y at the time you bind it, and use that when you eventually execute the query.
You could also fix this by simply passing an array of values to execute().
Here's how I would write it:
$queryColumns = implode(",", array_map(function($col) { return "`__$col`"; }, array_keys($values)));
$paramPlaceholders = implode(",", array_fill(0, count($values), "?");
$query = "INSERT INTO `$table`($queryColumns) VALUES($paramPlaceholders)";
$db = new PDO("mysql:host=127.0.0.1;dbname=db_shop", "root", "");
try {
$stmt = $db->prepare($query);
$stmt->execute(array_values($values));
} catch(PDOException $e) {
error_log($e);
// display a more friendly error to the client
}
There is no need to bind each value individually, and there is no need to use the PDO::PARAM_STR or other type constants when using the MySQL PDO driver. They are all treated as strings regardless of how you bind them.

Related

How does the mysqli execute function work internally?

I have a DB where I have some tables, most of them irrelevant for this question:
Content_handler:
+-------------+-------------------+---------------------------+
| app_data_id | app_menu_entry_id | content_item_container_id |
+-------------+-------------------+---------------------------+
| 0 | 0 | NULL |
| 0 | 1 | bm9zb3Ryb3MtMC0w |
+-------------+-------------------+---------------------------+
App_menu_entry:
+-------------------+----------+---------+--------------+
| app_menu_entry_id | position | name | icon_id |
+-------------------+----------+---------+--------------+
| 0 | 0 | Nombre | asd |
| 1 | 1 | Precios | icontest.png |
+-------------------+----------+---------+--------------+
Content_item_container:
+---------------------------+--------------+
| content_item_container_id | content_name |
+---------------------------+--------------+
| bm9zb3Ryb3MtMC0w | Nosotros |
+---------------------------+--------------+
content_handler.app_menu_entry_id REFERENCES TO app_menu_entry.app_menu_entry_id
content_handler.content_item_container_id REFERENCES TO content_item_container.content_item_container_id
Now, when I want to update app_menu_entry I DELETE all records from this table (with cascade, so also deletes content from content_handler) and then I insert data with PHP from an array so I use a foreach with prepared statements like this:
$menuQuery = $connection->prepare("INSERT INTO app_menu_entry(app_menu_entry_id,position,name,icon_id) VALUES(?,?,?,?)");
$handlerQuery = $connection->prepare("INSERT INTO content_handler(app_data_id, app_menu_entry_id, content_item_container_id) VALUES(?,?,?)");
$id = 0;
if (!$menuQuery || !$handlerQuery) {
ErrorHandler::setError(ErrorHandler::DB_QUERY_ERROR, true);
throw new \Exception('Error al realizar la consulta');
}
foreach ($menu['menu_items'] as $menuItem) {
$name = $menuItem['name'];
$position = $menuItem['position'];
$icon = $menuItem['icon_id'];
$contentId = ($menuItem['content'] != null) ? base64_encode($menuItem['content'] . '-' . $userId . '-' . $appId) : null;
$menuQuery->bind_param('ssss', $id, $position, $name, $icon);
$menuQuery->execute();
$handlerQuery->bind_param('sss', $appId, $id, $contentId);
$handlerQuery->execute();
$id++;
}
The problem is that this loop insert data in app_menu_entry but not in content_handler. This can be solved adding sleep(1000); between execute() function and that make me think how can I prevent using sleep() and how this execute function works? Does it uses a thread, makes a async queue...?

select with join using a standard php function

Im trying to use a php function to select data from a database, Im using a "standard" php function:
function select($table, $where, $other=''){
try{
$a = array();
$w = "";
$other=" ".$other;
foreach ($where as $key => $value) {
$w .= " and " .$key. " like :".$key;
$a[":".$key] = $value;
}
$stmt = $this->bd->prepare("select * from ".$table." where 1=1 ". $w.$other);
$stmt->execute($a);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
if(count($rows)<=0){
$reponse["statut"] = "warning";
$reponse["message"] = "no data found.";
}else{
$reponse["statut"] = "Success";
$reponse["message"] = "Success";
}
$reponse["data"] = $rows;
}catch(Exception $e){
$reponse["statut"] = "Error";
$reponse["message"] = 'selection failed: ' .$e->getMessage();
$reponse["data"] = null;
}
return $reponse;
}
The function works satisfyingly I faced problems only when I aimed to use a join and selecting from two tables.
say we have these two tables:
id-cat | cat-name |
1 | aaaaaa |
2 | aaadda |
3 | saaaca |
4 | ahhaaa |
and
id-cat | sub-cat |
1 | 2 |
1 | 3 |
1 | 4 |
2 | 3 |
2 | 4 |
How to select the a category's subs name with given id-cat using MY FUNCTION ??
You just need a simple LEFT JOIN to the second table, using the column that exists in both tables.
$stmt = $this->bd->prepare("SELECT *
FROM $table
LEFT JOIN $table2 USING id-cat
WHERE 1=1 ". $w.$other);

PHP mysql create tree-like hierarchy and their count

I'm still new with PHP & MySQL. This quetion looks simple, but somehow I can't figure the recursive formulae to create tree-like hierarchy by using foreach and array correctly.
This is the table structure
CREATE TABLE IF NOT EXISTS `table` (
`id` int(2) NOT NULL,
`lecturer` varchar(50) NOT NULL,
`subject` varchar(9) NOT NULL,
`section` int(2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=6 ;
INSERT INTO `table` (`id`, `lecturer`, `subject`, `section`) VALUES
(1, 'Prof A', 'info2222', 1),
(2, 'Prof A', 'info2222', 2),
(3, 'Prof A', 'info3333', 1),
(4, 'Prof A', 'info3333', 3),
(5, 'Prof B', 'info4444', 1);
This is sample output that I want:
=================================================
| lecturer > subject > section | count total |
=================================================
| Prof A | 4 |
| |---info2222 | 2 |
| | |---1 | 1 |
| | |---2 | 1 |
| | | |
| |---info3333 | 2 |
| |---1 | 1 |
| |---3 | 1 |
| | |
| Prof B | 1 |
| |---info4444 | 1 |
| |---1 | 1 |
=================================================
my full code (currently)
<html>
<head>
<?php
mysql_select_db('testing',mysql_connect('localhost','root',''))or die(mysql_error());
function count_recursive($array)
{
$c = 0;
foreach($array as $value)
{
if(is_array($value))
$c += count_recursive($value);
else
$c++;
return $c;
}
}
?>
</head>
<body>
<?php
$query = $pdo->query("Select * from table");
$arr = [];
while($data = $query->fetch())
{
$arr[$data['lecturer']][$data['subject']][] = $data['section'];
}
foreach($arr as $lecturer => $lvalues)
{
echo $query['lecturer'] ;
foreach($lvalues as $subject => $svalues)
{
echo $query['subject'] ;
foreach($svalues as $section)
{
echo $query['section'] ;
}
}
}
?>
</body>
</html>
Something like this should work:
$query = $pdo->query("Your select...");
$arr = [];
while($data = $query->fetch()){
$arr[$data['lecturer']][$data['subject']][] = $data['section'];
}
Afterwards you can foreach over the (3d) array:
foreach($arr as $lecturer => $lvalues){
//echo your lecturer here
foreach($lvalues as $subject => $svalues){
//echo your subject here
foreach($svalues as $section)
//echo sour section here
}
to count everything recursively you can use:
function count_recursive($array){
$c = 0;
foreach($array as $value)
if(is_array($value))
$c += count_recursive($value);
else
$c++;
return $c;
}

getting an empty array when my pdo should be returning many mysql rows

I get array(0){} where I should be returning rows;
<?php
require_once('auth.php');
$conn = new PDO("mysql:host=localhost;dbname=$dbname", $username, $pw);
$file = fopen('nameList1.txt','r');
$firstname = ''; $lastname = ''; $index = -1; $id = -1;
$tab = ' ';
try{
while ($line = fgets($file)) {
$index = strrpos($line, $tab);
$firstname = substr($line, 0, $index);
//var_dump($firstname);
$lastname = substr($line, $index + strlen($tab));
//var_dump($lastname);
$stmt = $conn->prepare("SELECT * FROM dbname WHERE FName = :firstname AND LName = :lastname");
$stmt->bindvalue(':firstname', $firstname);
$stmt->bindvalue(':lastname', $lastname);
$stmt->execute();
$result = $stmt->fetchAll();
var_dump($result);
}
}
catch ( Exception $e )
{
echo "Exception at $i";
}
fclose($file);
?>
********* working SQL *************
mysql> SELECT * FROM list WHERE FName='TARA' AND LName='AABA';
+----+--------------+---------------+-------+-------+-------+-----------+------------+-----------+--------+----------+-----------+-----------+-----------+-------------+-------------------+--------------------+------------+------------+----------+------------+------------+--------------+--------------+---------------------+-----------------------+-------+-------+-------+-------+----------+---------+-------------+------------------+--------------+------------+------------+
| id | StateVoterID | CountyVoterID | Title | FName | MName | LName | NameSuffix | Birthdate | Gender | RegStNum | RegStFrac | RegStName | RegStType | RegUnitType | RegStPreDirection | RegStPostDirection | RegUnitNum | RegCity | RegState | RegZipCode | CountyCode | PrecinctCode | PrecinctPart | LegislativeDistrict | CongressionalDistrict | Mail1 | Mail2 | Mail3 | Mail4 | MailCity | MailZip | MailCountry | Registrationdate | AbsenteeType | LastVoted | StatusCode |
+----+--------------+---------------+-------+-------+-------+-----------+------------+-----------+--------+----------+-----------+-----------+-----------+-------------+-------------------+--------------------+------------+------------+----------+------------+------------+--------------+--------------+---------------------+-----------------------+-------+-------+-------+-------+----------+---------+-------------+------------------+--------------+------------+------------+
| 9 | 0 | 458198 | | TARA | L | AABRA | | 2 | F | 5804 | | 1ST | PL | | | NE | 0 | MARYSVILLE | WA | 98271 | 0 | 101231 | 0 | 39 | 2 | | | | | | | | 05/05/2004 | P | 11/08/2011 | A |
*********** var_dump($result) **************
array(0) { } array(0) { } array(0) { } array(0) { } array(0) { } array(0) { } array(0) { } array(0) { } ....
Empty array returned by fetchAll means there was no data found.
This is a fact. While "working SQL" is working somewhere else.
So - you have to check your input data, database, environment, typos and such.
Sincerely,

PDO inserts an id as 0 for some reason

So I wrote this method, for my chrome plugin (which does an ajax request to run this method), and when it runs, file_put_contents shows an id of what ever was inserted, but then when it gets to the insert ignore into songs, it puts in 0 for the artist_id. I have no idea why... Can someone help my find the part where I am going wrong?
<?php
public function saveLyrics($artist, $title, $lyric){
$this->db->query("insert ignore into artists (artist_name) value (:artist)", array("artist" => $artist));
$artist_id = (int)$this->db->insertID();
file_put_contents(__DIR__ . "/../process/page", "artist id: $artist_id");
//return;
if($artist_id == 0){
$artist_id = (int)$this->db->getOne("select artist_id from artists where artist_name = :artist", array("artist" => $artist));
}
if($artist_id == 0){
return false;
}
$this->db->query("insert ignore into songs (artist_id, song_name) values (:aid, :title)", array("aid" => $artist_id, "title" => $title));
$song_id = (int)$this->db->insertID();
if($song_id == 0){
$song_id = (int)$this->db->getOne("select song_id from songs where artist_id = aid and song_name = :title", array("aid" => $artist_id, "title" => $title));
}
}
PDO Wrapper:
<?php
/**
* #property PDO $pdo Description
* #property PDOStatement $sql Description
*/
class DB{
protected $sql = null;
protected $pdo = null;
public function connect(){
$this->pdo = new PDO("mysql:dbname=envne;host=xxx", "xxx", "xxx");
}
public function query($query, $params = array()){
if($this->pdo === null){
$this->connect();
}
$this->sql = $this->pdo->prepare($query);
foreach($params as $key => $value){
$this->sql->bindParam($key, $value);
}
$this->sql->execute();
if(!$this->sql)
return false;
return true;
}
public function insertID(){
return (int)$this->pdo->lastInsertId();
}
public function getAll($query, $params = array()){
$this->query($query, $params);
return $this->sql->fetchAll();
}
public function getOne($query, $params = array()){
$this->query($query, $params);
return $this->sql->fetchColumn();
}
}
Artists:
mysql> describe artists;
+-------------+------------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+------------------+------+-----+-------------------+----------------+
| artist_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| artist_name | char(50) | YES | UNI | NULL | |
| add_date | timestamp | YES | | CURRENT_TIMESTAMP | |
+-------------+------------------+------+-----+-------------------+----------------+
3 rows in set (0.00 sec)
Songs:
mysql> describe songs;
+------------+------------------+------+-----+-------------------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+------------------+------+-----+-------------------+----------------+
| song_id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| artist_id | int(11) unsigned | YES | MUL | NULL | |
| album_id | int(11) | YES | MUL | NULL | |
| song_name | char(50) | YES | | NULL | |
| track_id | int(11) | YES | | NULL | |
| date_added | timestamp | NO | | CURRENT_TIMESTAMP | |
+------------+------------------+------+-----+-------------------+----------------+
6 rows in set (0.01 sec)
I just decided to put the Id directly into the query, and that works.
$artist_id = (int)$this->db->insertID();
$this->db->query("insert ignore into songs (artist_id, song_name) values ($artist_id, :title)", array("title" => $title));
Another way that works is using a question mark instead
$artist_id = (int)$this->db->insertID();
$this->db->query("insert ignore into songs (artist_id, song_name) values (?, ?)", array($artist_id, $title));
I just had the same problem : new inserted items get an id of 0 even though the ID field is set to AUTO_INCRIMENT.
The solution I found is very similar to yours. Using your code this is what we get :
$this->db->query("insert ignore into songs (artist_id, song_name) values (LAST_INSERT_ID(), :title)", array("title" => $title));
As you can see, I replaced $artist_id = (int)$this->db->insertID(); and $artist_id with SQL function LAST_INSERT_ID().
I hope this can help someone someday :)
You're placeholders are incorrectly defined: (You're missing the colons)
I would do something like this:
public function saveLyrics($artist, $title, $lyric){
$this->db->query("insert ignore into artists (artist_name) value (:artist)", array(":artist" => $artist));
$artist_id = (int)$this->db->insertID();
file_put_contents(__DIR__ . "/../process/page", "artist id: $artist_id");
//return;
if($artist_id == 0){
$artist_id = (int)$this->db->getOne("select artist_id from artists where artist_name = :artist", array(":artist" => $artist));
return false;
}
$this->db->query("insert ignore into songs (artist_id, song_name) values (:aid, :title)", array(":aid"=>$artist_id, ":title"=>$title));
$song_id = (int)$this->db->insertID();
if($song_id == 0){
$song_id = (int)$this->db->getOne("select song_id from songs where artist_id = :aid and song_name = :title", array(":aid"=>$artist_id, ":title"=>$title));
}
}
Taking a look at your PDO-wrapper you have this code:
if(!$this->sql)
return false;
Because of this you would never notice an actual error. I guess the error is about the placeholder in this case.
(If $this->db->query("insert ignore into songs (... fails $song_id would just be false if there is an error when executing the query).
Use exceptions instead and catch the errors, that would be better.
I also noticed that:
$song_id = (int)$this->db->insertID();
would cast the value twice, first in above code and then in the actual function insertID() in the PDO-Wrapper. Maybe this is an issue also to consider.

Categories