I'm working on a custom CMS using PHP OOP. Basically I have made a class that can add a new row to db.
My Class:
<?php
class Navigation
{
private $db;
public function __construct()
{
$this->db = new Connection();
$this->db = $this->db->dbConnect();
}
public function NewMenu($menu_name,$menu_numbers)
{
if (!empty($menu_name) && !empty($menu_numbers)) {
$sql = "INSERT INTO menu_nav "
. "(menu_name, menu_items) VALUES (?, ?)";
$ins = $this->db->prepare($sql);
$ins->bindParam(1,$menu_name);
$ins->bindParam(2,$menu_numbers);
$ins->execute();
} else {
header("Location: maint/php/includes/errors/009.php");
exit();
}
}
}
This class works fine but the problem is that I don't know how to check if the menu_name exists already in the table or not. And if yes ,it should receive the error message that "Data can not be inserted to db" for example. So if you know how to do this feature in PHP OOP ,please let me know cause I really need it.
There are two ways for this to work. For the first method you'll need to have a primary/unique key, which would cause the query to fail. Set PDO up with throwing exceptions on failures, and you can check the exceptions message for "duplicate key".
To do this you'd need to change the connection options, like this[1]:
$dsn = "mysql:host=$host;dbname=$db;charset=$charset";
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, $user, $pass, $opt);
Then pdo::exec() will throw an exception instead of just returning false.
For the second method you need to query the DB first, and then insert it if 0 rows are returned. The problem with this approach is that it can trigger race conditions. In which a duplicate row is inserted between the check and the INSERT query, by another, simultaneously running, script.
That's why I recommend doing it in the first manner. As you'd need to do it anyway, to catch the race conditions.
PS: As an alternative way of doing method 1, you could use ON DUPLICATE KEY in the SQL query. If you wanted to insert or update the data.
[1]: Copied from here https://phpdelusions.net/pdo
so you should check if row exists in table, and insert the data only if row doesn't exist(if i correctly understand your question). at first select the row with given menu name $ins = $this->db->prepare("SELECT FROM menu_nav (menu_name, menu_items) VALUES (?, ?)"); and then check if you got any data. go here , you will get the clue)
Related
<?php
$sql = "insert into user (firstname, lastname) values (:firstname, :lastname)";
$arg = array("John", "Doe");
$stmt = pdo($sql, $arg);
$id = $pdo->lastInsertId();
print $id;
function pdo($sql, $args = NULL){
$dsn = "mysql:host=localhost;dbname=db;charset=utf8mb4";
try {
$pdo = new \PDO($dsn, "user", "123");
} catch (\PDOException $e) {
throw new \PDOException($e->getMessage(), (int)$e->getCode());
}
if (!$args){
return $pdo->query($sql);
}
$stmt = $pdo->prepare($sql);
$stmt->execute($args);
return $stmt;
}
?>
I use a wrapper to call database queries, this has always worked well.
Except for today when I need to get the last insert ID. The error is "Undefined variable pdo"
I see why there is an error message, but not sure what is the solution while at the same time keeping the wrapper function?
You might just want to have a function create the DB connection and let the application code use the PDO API directly. That would be simpler than trying to wrap all the functionality of the PDO API. It would get complicated to do that.
If you really need a wrapper around your PDO connection, then you may want to create a class with different methods operating on the same connection. The class could have a special insert method that returns the inserted ID as well as a fetch method to retrieve results.
Sorry, but i'm new to PHP, so i will look like a noob.
As the title says, i made this method which updates user data:
function update($userid, $name){
Try{
$stmt=$this->db->prepare("UPDATE users
SET
name=:name,
WHERE userid=:userid");
$stmt->execute(array(':name'=>$name));
} Catch(PDOException $e){
echo $e->getMessage();
}
}
That code is working right, but i want to know if it's possible, the "name" column just update if the variable coming from:
$user->update($userid, $name);
From $name, is not null or not empty. If it's null or Empty, the MYSQL UPDATE function should not be done.
Try this instead.
function update($userid, $name) {
try {
if (!empty($name) and !empty($userid)) {
$stmt = $this->db->prepare("UPDATE users
SET
name=:name
WHERE userid=:userid");
$stmt->execute(array(':name' => $name, ':userid' => $userid));
}
}
Catch(PDOException $e) {
echo $e->getMessage();
}
}
Explanation
Removal of the trailing comma.
As stated by #Fred -ii- you had a trailing comma in your SQL query after the SET (i.e SET name=:name,).
The comma in SQL queries are used to separate multiple updates from one another so UPDATE table SET col1 = "val1", col2 = "val2" and so on. Since you are only updating one column, you don't need the comma
The empty method checks whether the variable $name has been set and is not false. See documentation.
Removed the SQL-Injection-Vulnerable in :userid=$userid
Why i think it is better to have an if-statement inside the function
A function should be reusable and it costs nothing to call a function
In clean code you should avoid using if-statements, which means, you should not always have surround an if-statment before calling a function which could be called in other parts of the code, too. What happens when you add another parameter?
I know this is discussable.
I am new to PHP and am trying to update a deprecated code from mysql to PDO.
Considering that the variable $insert contains all values to bulk insert such as:
('82817cf5-52be-4ee4-953c-d3f4ed1459b0','1','EM3X001P.1a','04.03.10.42.00.02'),
('82817cf5-52be-4ee4-953c-d3f4ed1459b0','2','EM3X001P.2a','04.03.10.33.00.02'),
...etc 13k lines to insert
here is the deprecated code:
mysql_connect('localhost', 'root', '') or die(mysql_error());
mysql_select_db("IPXTools") or die(mysql_error());
if ($insert != '')
{
$insert = "INSERT INTO IPXTools.MSSWireList (ID,Record,VlookupNode,HostWireLocation) VALUES ".$insert;
$insert .= "ON DUPLICATE KEY UPDATE Record=VALUES(Record),VlookupNode=VALUES(VlookupNode),HostWireLocation=VALUES(HostWireLocation)";
mysql_query($insert) or die(mysql_error());
$insert = '';
}
here is the new code:
try
{
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); //set the PDO error mode to exception
// prepare sql and bind parameters
$stmt = $conn->prepare("INSERT INTO IPXTools.MSSWireList (ID, Record, VlookupNode, HostWireLocation)
VALUES (:ID, :Record, :VlookupNode, :HostWireLocation)");
$stmt->bindParam(':ID', $ID);
$stmt->bindParam(':Record', $Record);
$stmt->bindParam(':VlookupNode', $VlookupNode);
$stmt->bindParam(':HostWireLocation', $HostWireLocation);
// insert a row
// loop through all values inside the $insert variable??????? how?
$stmt->execute();
}
catch(PDOException $e)
{
echo "Error: " . $e->getMessage();
}
$conn = null;
During my research I found an excellent post:
PDO Prepared Inserts multiple rows in single query
One method says I would have to change my $insert variable to include all the field names.
And other method says I dont have to do that. I am looking at Chris M. suggestion:
The Accepted Answer by Herbert Balagtas works well when the $data array is small. With larger $data arrays the array_merge function becomes prohibitively slow. My test file to create the $data array has 28 cols and is about 80,000 lines. The final script took 41s to complete
but I didnt understand what he is doing and I am trying to adapt my code to his. The PHP sintax is new to me so I am strugling with handling the arrays, etc...
I guess the starting point would be the variable $insert which contains all the database values I need.
Do I need to modify my $insert variable to include the field names?
Or I could just use its content and extract the values (how?) and include the values in a loop statement? (that would probably execute my 13k rows one at at time)
Thank you
If you have 13k records to insert, it is good for performance to do not use prepared SQL statement. Just generate SQL query in format like this:
INSERT INTO IPXTools.MSSWireList
(ID, Record, VlookupNode, HostWireLocation)
VALUES
('id1', 'r1', 'node1', 'location1'),
('id2', 'r2', 'node2', 'location2'),
...
('id13000', 'r13000', 'node13000', 'location13000');
What you may do for it - use maner of your legacy code. Your try block will looks loke this:
try
{
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $conn->exec($insert);
}
I managed to run the following code to insert into my table on first try. Then, I deleted that row in PHPMyAdmin to test my code further. I also noticed that it didn't get deleted on the 1st try. Only after few try. This might be due to I didn't set the $pdoHandle to NULL after I'm done with the query.
Then, unfortunately I couldn't insert new row on subsequent run. I even tried to change the input value and to avail I was unable to insert new row. The following are my PHP codes:
public function CreateNewCustomer($userId,$password,$name,$email)
{
$userId = filter_var($userId,FILTER_SANITIZE_STRING);
$password = filter_var($password,FILTER_SANITIZE_STRING);
$password = sha1($password);
$name = filter_var($name,FILTER_SANITIZE_STRING);
$email = filter_var($email,FILTER_SANITIZE_EMAIL);
do{
$customerId = hexdec(bin2hex(openssl_random_pseudo_bytes(4,$isStrong)));
echo $customerId;
$result = $this->connObject->exec("SELECT COUNT(id) FROM customer_tbl WHERE id=$customerId");
var_dump($result);
}while($result>0);
$statement = $this->connObject->prepare("INSERT INTO customer_tbl (id,name,email) VALUES ($customerId,:name,:email)");
$result = $statement->execute(array(':name'=>$name,':email'=>$email ));
var_dump($result);
$statement = $this->connObject->prepare("INSERT INTO login_tbl (username,password,customer_id) VALUES (:userName,PASSWORD(:password),$customerId)");
$result = $statement->execute(array(':userName'=>$userId,':password'=>$password ));
var_dump($result);
}
I used the following code to access the above method.
function Test($userName,$password,$name,$email)
{
try
{
$dbConnect = new DbConnect();
$pdoHandle = $dbConnect->Connect();
$userAccess = new UserAccess($pdoHandle);
$userAccess->CreateNewCustomer($userName,$password,$name,$email);
}
catch(PDOException $e)
{
$pdoHandle = null;
var_dump($e);
}
$pdoHandle = null;
}
Test('tester','password','TestX','test#example.com');
The var_dump of results is always false.
Is there any problem with my codes or is it something wrong with the database?
UPDATE/SOLUTION:
I just read through the PHP document on PDO::exec() and one of the user contributed notes mentioned that you can't use any SELECT statements (even thou the above only returns the count value) and any statements which might return a rows. The return value of PDO::exec() is the number of affected rows (integer), so the PDOStatement::closeCursor() can't be used to solve it. Even when I set the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY=>true, it still doesn't work.
So, don't use PDO::exec() for any SELECT. I changed my code to PDO::query() instead as below,
do{
$customerId = hexdec(bin2hex(openssl_random_pseudo_bytes(4,$isStrong)));
$statement = $this->connObject->query("SELECT COUNT(id) FROM customer_tbl WHERE id=$customerId");
$statement->execute();
}while($statement->fetchColumn(0)>0);
Hope this would be helpful to anyone looking for a solution with similar problem and always remember to read the PHP document first including the user contributions.
Maybe not the answer but here are some things that you can do if you cannto see an obvious error:
If execute returns false, you can get more information about the error that happened by:
$arr = $statement->errorInfo();
print_r($arr);
or you can set different error reporting modes (e.g. throw an exception instead of the defaultsilent mode):
$dsn = 'mysql:dbname=testdb;host=127.0.0.1';
$user = 'dbuser';
$password = 'dbpass';
$dbh = new PDO($dsn, $user, $password);
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
This should help you to find the "real" error.
As it turned out (see comments below question), in this case the real error was:
"Cannot execute queries while other unbuffered queries are active.
Consider using PDOStatement::fetchAll(). Alternatively, if your code
is only ever going to run against mysql, you may enable query
buffering by setting the PDO::MYSQL_ATTR_USE_BUFFERED_QUERY"
In this case you have 2 options:
you can set the option to use buffered queries
$dbh = new PDO(’mysql:host=localhost;dbname=test’, ‘root’, ” ,array(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true))
or change your code and close an open cursor (may depend on the db driver you are using). You always should read the documentation which covers a lot of default problems.
Hope this helps.
I'm assuming the method is inside the UserAccess class and the connection you pass in is set to the local $this->connObject.
I suspect after you deleted the record, $customerId is being set to null in your interesting do-while loop with the select statement. If the id column in the DB is a non-null primary key field and you try to insert an explicit null it will fail.
Also, no need to keep setting your DB connection to null... this isn't C and connections aren't persistent (unless you explicitly declare them as such).
I am trying PDO transactions for the first time. The below code doesnt work. the email address we are trying to insert has a duplicate so it should fail. It does give me an error. but the first insert get inserted into the DB and it doesnt roll back. I know rollback work cuase if i move PDO::rollBack into the Try{ before commit, it does roll back. I think the problem is its not catching the error, therefore not calling PDO::rollBack. Any ideas?
try {
PDO::beginTransaction();
$sql = "INSERT INTO .`tblUsersIDvsAgencyID` (`id`, `agency_id`) VALUES (NULL, :agencyID)";
$STH = $this->prepare($sql);
$STH->bindParam(':agencyID', $AgencyUser['agency_id']);
$STH->execute();
$userID = parent::lastInsertId();
$sql = "INSERT INTO `tblUsersEmailAddress` (`id`, `user_id`, `email_address`, `primary`, `created_ts`, `email_verified`) VALUES (NULL , :userID , :EmailAddress , '1', CURRENT_TIMESTAMP , '0' )";
$STH = $this->prepare($sql);
$STH->bindParam(':userID', $userID);
$STH->bindParam(':EmailAddress', $email_address);
$STH->execute();
PDO::commit();
echo 'Data entered successfully<br />';
}
catch(PDOException $e)
{
/*** roll back the transaction if we fail ***/
PDO::rollBack();
echo "failed";
}
PDO::beginTransaction() is not a static method. From your question, it looks like you're extending the PDO class. I wouldn't do that as I doubt you're adding anything significant to the base class. Instead, you should the set the PDO connection as a class property.
For example
class ParentClass
{
/**
* #var PDO
*/
protected $dbh;
public function __construct(PDO $dbh)
{
$this->dbh = $dbh;
// Make sure PDO is set to throw exceptions
$this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
}
class ChildClass extends ParentClass
{
public function insertStuff()
{
$this->dbh->beginTransaction();
try {
// do stuff
$this->dbh->commit();
} catch (PDOException $e) {
$this->dbh->rollBack();
throw $e;
}
}
}
I'm just going to start by quoting the docs:
Beware: Some MySQL table types (storage engines) do not support transactions. When writing transactional database code using a table type that does not support transactions, MySQL will pretend that a transaction was initiated successfully. In addition, any DDL queries issued will implicitly commit any pending transactions.
Your problem may be expected behavior. Additionally:
beginTransaction is not static. (I repeat Phil's statement that you should not be extending PDO).
Call $pdo->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
You never call closeCursor on your statement (this will often cause problems). (Phil points out that it is not explicitly necessary in this case. It is still best practice though).
Using bindParam does not yield any benefit for the userID variable as you are using it (a locally defined variable which is not re-used).