I have a PHP site connected to an Interbase DB. The DB contains orders which users can load and are displayed on screen. The user can make changes to the order and save them. This works but if 2 users load and save the same record then the order contains the changes made by the last user who saved.
When the 2nd user tries to save I want a message to pop up saying the order has been changed and stop the order from being saved.
I know that interbase has transactions to do this as I have a desktop app that implements transactions and the above scenario. However, I do not know how to do the same thing with PHP in a web environment.
The desktop app keeps the db open all the time and the transaction is kept alive from the time it was read to committed. With PHP the db and transaction is opened/created only when each query is run. From what I read the transaction is rolled back at the end of the script if it's not committed.
Code loading an order
PHP Code:
public function GetOrderDetails($in_OrderID)
{
$qry = "SELECT ID, ... , FROM CUSTOMER_INVOICE WHERE ID = $in_OrderID";
$this->dbconn = ibase_connect ($this->host, $this->username, $this->password);
$this->dbtrans = ibase_trans( IBASE_DEFAULT,$this->dbconn );
$result = ibase_query ($this->dbtrans, $qry);
while( $row = ibase_fetch_row($qryResult) )
{
}
ibase_free_result($in_FreeQry);
ibase_close($this->dbconn);
}
Code saving order
PHP Code:
public function SaveOrderDetails()
{
$DoCommit = false;
try
{
$this->dbconn = ibase_connect ($this->host, $this->username, $this->password);
$this->dbtrans = ibase_trans( IBASE_DEFAULT,$this->dbconn );
// Insert/Update the order
if( $this->UpdateOrder() )
{
// Insert Order Items
if( $this->InsertOrderItems() )
{
$DoCommit = true;
}
else
{
$this->ErrorMsg = "ERROR 0003: Order Items could not be inserted";
}
}
else
{
$this->ErrorMsg = "ERROR 0002: Order could not be inserted/updated";
}
if( $DoCommit )
{
if( ibase_commit($this->dbtrans) )
{
$OrderResult = true;
}
else
{
ibase_rollback($this->dbtrans);
$this->ErrorMsg = "ERROR 0004: DB Qry Commit Error";
print $this->ErrorMsg ;
}
}
else
{
ibase_rollback($this->dbtrans);
}
}
catch( Exception $e )
{
ibase_rollback($this->dbtrans);
$this->ErrorMsg = "ERROR 0001: DB Exception: " . $e;
}
ibase_close($this->dbconn);
}
If anyone can tell me where I'm going wrong that would be great. Or, if no one uses Interbase how would you do it with MySQL? I don't want to go down the table locking, timestamp route.
Thanks
Ray
you must use primary key to avoid it. u can use generator to get unique id for each order.
Related
i tried to follow this mysql - move rows from one table to another with action to perform a "move to archive" function using PDO and i am failing miserably.
So i have created a job card system, and to cut it short, when a job is complete, i have a "ARCHIVE" button that essentially needs to move the selected job card from table "repairs" into table "archived_repairs". The 2 tables are exactly the same, it just needs to be deleted from repairs table and moved to archived_repairs table in case we need to come back to it at a later stage.
This is the button/link i am using on my CRUD table:
<td>Archive</td>
The above is fine and dandy and goes to a page i named "archive_repair.php" with the following php code:
<?php
require_once "connection.php";
if (isset($_REQUEST['archive_id']))
{
try
{
$job_number = $_REQUEST['archive_id'];
$select_stmt = $db->prepare('SELECT * FROM repairs WHERE job_number =:job_number');
$select_stmt->bindParam(':job_number', $job_number);
$select_stmt->execute();
$row = $select_stmt->fetch(PDO::FETCH_ASSOC);
extract($row);
}
catch(PDOException $e)
{
$e->getMessage();
}
}
if (isset($_REQUEST['btn_archive']))
{
$job_number = $_REQUEST['job_number'];
$date = $_REQUEST['date'];
$client_full_name = $_REQUEST['client_full_name'];
$client_email = $_REQUEST['client_email'];
$client_phone = $_REQUEST['client_phone'];
$item_for_repair = $_REQUEST['item_for_repair'];
$repair_description = $_REQUEST['repair_description'];
$hardware_details = $_REQUEST['hardware_details'];
$diagnostic_fee = $_REQUEST['diagnostic_fee'];
$tech_assigned = $_REQUEST['tech_assigned'];
$current_status = $_REQUEST['current_status'];
$technician_notes = $_REQUEST['technician_notes'];
$admin_notes = $_REQUEST['admin_notes'];
$invoice_status = $_REQUEST['invoice_status'];
$invoice_number = $_REQUEST['invoice_number'];
if (empty($invoice_status))
{
$errorMsg = "Please change Invoice Status Before Archiving this Job Card";
}
else if (empty($invoice_number))
{
$errorMsg = "Please Enter a SAGE Invoice Reference Before Archiving this Job Card";
}
else
{
try
{
if (!isset($errorMsg))
{
$archive_stmt = $db->prepare('INSERT INTO archived_repairs job_number=:job_number, date=:date, client_full_name=:client_full_name, client_email=:client_email, client_phone=:client_phone, item_for_repair=:item_for_repair, repair_description=:repair_description, hardware_details=:hardware_details, diagnostic_fee=:diagnostic_fee, tech_assigned=:tech_assigned, current_status=:current_status, technician_notes=:technician_notes, admin_notes=:admin_notes, invoice_status=:invoice_status, invoice_number=:invoice_number');
$archive_stmt->bindParam(':job_number', $job_number);
$archive_stmt->bindParam(':date', $date);
$archive_stmt->bindParam(':client_full_name', $client_full_name);
$archive_stmt->bindParam(':client_email', $client_email);
$archive_stmt->bindParam(':client_phone', $client_phone);
$archive_stmt->bindParam(':item_for_repair', $item_for_repair);
$archive_stmt->bindParam(':repair_description', $repair_description);
$archive_stmt->bindParam(':hardware_details', $hardware_details);
$archive_stmt->bindParam(':diagnostic_fee', $diagnostic_fee);
$archive_stmt->bindParam(':tech_assigned', $tech_assigned);
$archive_stmt->bindParam(':current_status', $current_status);
$archive_stmt->bindParam(':technician_notes', $technician_notes);
$archive_stmt->bindParam(':admin_notes', $admin_notes);
$archive_stmt->bindParam(':invoice_status', $invoice_status);
$archive_stmt->bindParam(':invoice_number', $invoice_number);
if ($archive_stmt->execute())
{
$delete_stmt = $db->prepare('DELETE FROM repairs WHERE job_number =:job_number');
$delete_stmt->bindParam(':job_number', $job_number);
$delete_stmt->execute();
header("refresh:1;repairs.php");
}
}
}
catch(PDOException $e)
{
echo $e->getMessage();
}
}
}
?>
This is my connection.php file:
<?php
$db_host="localhost"; //localhost server
$db_user="ecemscoz_ecemsapp"; //database username
$db_password="C3m3t3ry!#"; //database password
$db_name="ecemscoz_ecemsapp"; //database name
try
{
$db=new PDO("mysql:host={$db_host};dbname={$db_name}",$db_user,$db_password);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOEXCEPTION $e)
{
$e->getMessage();
}
?>
When i click on the ARCHIVE button/link, the page is just blank (white screen), no errors show, nothing is moved to the other database and nothing is deleted. Ive only been coding PHP since 2020 so still new at this, but from my understanding this should of worked... Am i missing something in my code that i am not seeing?
You'll have a much easier time doing this directly in MySQL.
Something like the following should be essentially all you need.
$archive_stmt = $db->prepare("INSERT INTO archived_repairs (
job_number,
date,
client_full_name,
client_email,
client_phone,
item_for_repair,
repair_description,
hardware_details,
diagnostic_fee,
tech_assigned,
current_status,
technician_notes,
admin_notes,
invoice_status,
invoice_number
) (
SELECT
job_number,
date,
client_full_name,
client_email,
client_phone,
item_for_repair,
repair_description,
hardware_details,
diagnostic_fee,
tech_assigned,
current_status,
technician_notes,
admin_notes,
invoice_status,
invoice_number
FROM
repairs
WHERE
job_number =:job_number )");
$archive_stmt->bindParam(':job_number', $job_number);
if ($archive_stmt->execute())
{
$delete_stmt = $db->prepare('DELETE FROM repairs WHERE job_number =:job_number');
$delete_stmt->bindParam(':job_number', $job_number);
$delete_stmt->execute();
header("refresh:1;repairs.php");
}
i start write a script with php , for installation stage i face to a problem.
when i install sql table with php code and after that "in same code page" i want insert admin_user information to user table , without any error user information not inserted in database but when i install Manually database table and after that run same code for insert admin_user information inserted run good and information inserted in database.
My Query Run PDO Method :
// Execute query
public function Tbmedia_runquery($query_body = "" , $parameter_array ="" ){
$query = $query_body;
$stmt = $this->Tbmedia_connection->prepare($query);
if(is_array($parameter_array)){
// declare bind_param variable
foreach ($parameter_array as $key => &$value) {
$stmt->bindParam(":$key",$value);
//':$key'
}
}
$stmt->execute();
$stmt->closeCursor();
return $stmt;
}
And this is My Create Database Table Function :
//Create DB Table
function Tbmedia_dbcreator($db_con){
$dir_sql = __DIR__."/../../install/tbmedia.sql";
if(!file_exists($dir_sql) or is_writable($dir_sql)){
$line_array = file($dir_sql);
$query_line = "";
$query = "";
foreach ($line_array as $value) {
if(substr($value,0,2) == "--")
continue;
$query_line .=$value;
if (substr(trim($value),-1,1) == ";"){
try{
$query = $db_con->Tbmedia_runquery($query_line);
$query_line = "";
}catch (Exception $e){
$file_name = basename($e->getFile());
$file_line = $e->getLine();
Tbmedia_die();
}
}
}
}else{
Tbmedia_die();
}
}
And this is simple insert sql for insert user user information after run function Tbmedia_dbcreator() :
$db_con->Tbmedia_runquery('INSERT INTO `tbmedia`.`tbmedia_user` (`user_name`,`user_state`) VALUE ("FARAMARz",1)');
Why this is happening ? can't create table and insert data to it in one page code ?
I just switched to PDO from mySQLi (from mySQL) and it's so far good and easy, especially regarding prepared statements
This is what I have for a select with prepared statement
Main DB file (included in all pages):
class DBi {
public static $conn;
// this I need to make the connection "global"
}
try {
DBi::$conn = new PDO("mysql:host=$dbhost;dbname=$dbname;charset=utf8", $dbuname, $dbpass);
DBi::$conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
DBi::$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
catch(PDOException $e) {
echo '<p class="error">Database error!</p>';
}
And in my page:
try {
$sql = 'SELECT pagetitle, pagecontent FROM mypages WHERE pageid = ? LIMIT 1';
$STH = DBi::$conn->prepare($sql);
$STH->execute(array($thispageid)); // $thispageid is from a GET var
}
catch(PDOException $e) {
echo '<p class="error">Database query error!</p>';
}
if ($STH) { // does this really need an if clause for it self?
$row = $STH->fetch();
if (!empty($row)) { // was there found a row with content?
echo '<h1>'.$row['pagetitle'].'</h1>
<p>'.$row['pagecontent'].'</p>';
}
}
It all works. But am I doing it right? Or can I make it more simple some places?
Is using if (!empty($row)) {} an ok solution to check if there was a result row with content? Can't find other decent way to check for numrows on a prepared narrowed select
catch(PDOException $e) {
echo '<p class="error">Database query error!</p>';
}
I would use the opportunity to log which database query error occurred.
See example here: http://php.net/manual/en/pdostatement.errorinfo.php
Also if you catch an error, you should probably return from the function or the script.
if ($STH) { // does this really need an if clause for it self?
If $STH isn't valid, then it should have generated an exception and been caught previously. And if you had returned from the function in that catch block, then you wouldn't get to this point in the code, so there's no need to test $STH for being non-null again. Just start fetching from it.
$row = $STH->fetch();
if (!empty($row)) { // was there found a row with content?
I would write it this way:
$found_one = false;
while ($row = $STH->fetch()) {
$found_one = true;
. . . do other stuff with data . . .
}
if (!$found_one) {
echo "Sorry! Nothing found. Here's some default info:";
. . . output default info here . . .
}
No need to test if it's empty, because if it were, the loop would exit.
I'm currently struggling with a page that allows a user to complete one of two options. They can either update an existing item in the SQL database or they can delete it. When the customer deletes an option everything runs perfectly well, however whenever a customer updated an item it displays the Query failed statement from the delete function before applying the update.
It seems obvious to me that the problem must be in my IF statement and that the DeleteButton function isn't exiting if the $deleteno variable isn't set. Any help would be appreciated. Excuse the horribly messy code PHP isn't a language I am familiar with. (I have not included the connect information for privacy reasons)
function DeleteButton(){
#mysqli_select_db($con , $sql_db);
//Checks if connection is successful
if(!$con){
echo"<p>Database connection failure</p>";
} else {
if(isset($_POST["deleteID"])) {
$deleteno = $_POST["deleteID"];
}
if(!isset($deleteno)) {
$sql = "delete from orders where orderID = $deleteno;";
$result = #mysqli_query($con,$sql);
if((!$result)) {
echo "<p>Query failed please enter a valid ID </p>";
} else {
echo "<p>Order $deleteno succesfully deleted</p>";
unset($deleteno);
}
}
}
}
That is the code for the delete button and the following code is for the UpdateButton minus the connection information (which works fine).
if(isset($_POST["updateID"])) {
$updateno = $_POST["updateID"];
}
if(isset($_POST["updatestatus"])) {
if($_POST["updatestatus"] == "Fulfilled") {
$updatestatus = "Fulfilled";
} elseif ($_POST["updatestatus"] == "Paid") {
$updatestatus = "Paid";
}
}
if(isset($updateno) && isset($updatestatus)) {
$sql ="update orders set orderstatus='$updatestatus' where orderID=$updateno;";
$result = #mysqli_query($con,$sql);
if(!$result) {
echo "<p>Query failed please enter a valid ID</p>";
} else {
echo "<p>Order: $updateno succesfully updated!</p>";
}
}
Once again these are incomplete functions as I have omitted the connection sections.
if(!isset($deleteno)) {
$sql = "delete from orders where orderID = $deleteno;";
Are you sure you want to execute that block if $deleteno is NOT set?
P.S. You shouldn't rely on $_POST['deleteId'] being a number. Please read about SQL injections, how to avoid them and also about using prepared statements.
I've update your code, but you need to write cleaner code ( spaces, indents, etc ) this won't only help you to learn but to find your errors easily.
<?php
function DeleteButton()
{
#mysqli_select_db($con , $sql_db);
/*
Checks if connection is successful
*/
if(!$con){
echo"<p>Database connection failure</p>";
} else {
/*
Check if $_POST["deleteID"] exists, is not empty and it is numeric.
*/
if(isset($_POST["deleteID"]) && ! empty($_POST["deleteID"]) && ctype_digit(empty($_POST["deleteID"]))
$deleteno = $_POST["deleteID"];
$sql = "delete from orders where orderID='$deleteno'";
$result = #mysqli_query($con,$sql);
if(!$result){
echo "<p>Query failed please enter a valid ID </p>"
} else {
echo "<p>Order $deleteno succesfully deleted</p>";
unset($deleteno);
}
} else {
echo "<p>Please enter a valid ID </p>" ;
}
}
}
/*
Part 2:
===========================================================================
Check if $_POST["updateID"] exists, is not empty and it is numeric.
Check if $_POST["updatestatus"] exists, is not empty and equal to Paid or Fullfilled
*/
if( isset($_POST["updateID"]) &&
! empty($_POST["updateID"]) &&
ctype_digit(empty($_POST["updateID"]) &&
isset($_POST["updatestatus"]) &&
! empty($_POST["updatestatus"]) &&
( $_POST["updatestatus"] == "Fulfilled" || $_POST["updatestatus"] == "Paid" ) )
{
$updateno = $_POST["updateID"];
$updatestatus = $_POST["updatestatus"];
$sql ="update orders set orderstatus='$updatestatus' where orderID=$updateno;";
$result = #mysqli_query($con,$sql);
if(!$result){
echo "<p>Query failed please enter a valid ID</p>";
} else {
echo "<p>Order: $updateno succesfully updated!</p>";
}
}
There is an error in MySQL Syntax
$sql = "delete from orders where orderID = $deleteno;";
$deleteno after orderID must be inside single quotes.
change it to this $sql = "delete from orders where orderID = '$deleteno';";
Issue resolved - see: https://stackoverflow.com/a/14719452/1174295
--
I've come across a problem within (at least) Google Chrome and Safari.
Upon the first attempt at logging in, the user is not redirected. The session is created, but it is almost as if it is not detected, and takes the user back to the index page. Upon a second attempt, the correct redirect is issued and the user is taken to the correct page.
The script works fine in Firefox, and I have checked extensively to see if the correct data is being returned, which it is. I've searched and I've searched and I've searched, but unfortunately nothing of use has cropped up.
Access.php - User logging in
<?php
session_start();
ob_start();
include_once('db.class.php');
include_once('register.class.php');
include_once('login.class.php');
$db = null;
$reg = null;
$log = null;
$db = new Database();
$log = new Login($db, null, null, null, null, null, null);
if (isset($_SESSION['login'])) {
$log->redirectUser($_SESSION['login']);
}
include_once('includes/header.html');
?>
Some HTML...
<?php
if (isset($_POST['logsub'])) {
$db = new Database();
$log = new Login($db, $_POST['email'], $_POST['pass']);
$validation = &$log->validate();
if(empty($validation)) {
$log->redirectUser($_SESSION['login']);
} else {
echo "<div id='error'><div class='box-error'><p style='font-weight: bold'>The following errors occured...</p><ul>";
for ($i = 0; $i < count($validation); $i++) {
echo "<li>" . $log->getErrorMessage($validation[$i]) . "</li>";
}
echo "</ul></div></div>";
}
}
?>
Login.class.php - Login class
// Validate the credentials given
public function validateLogin() {
// Hash the plain text password
$this->hashedPass = $this->hashPassword();
try {
$query = $this->dbcon->prepare("SELECT Login_ID, Login_Email, Login_Password FROM Login WHERE Login_Email = :email AND Login_Password = :pass");
$query->bindParam(':email', $this->email, PDO::PARAM_STR);
$query->bindParam(':pass', $this->hashedPass, PDO::PARAM_STR);
$query->execute();
$fetch = $query->fetch(PDO::FETCH_NUM);
$this->loginid = $fetch[0];
// If a match is found, create a session storing the login_id for the user
if ($query->rowCount() == 1) {
$_SESSION['login'] = $this->loginid;
session_write_close();
} else {
return LOG_ERR_NO_MATCH;
}
} catch (PDOException $e) {
$this->dbcon->rollback();
echo "Error: " . $e->getMessage();
}
}
// Fetch the customer ID
private function getCustId() {
try {
$query = $this->dbcon->prepare("SELECT Customer.Cust_ID FROM Customer JOIN Login ON Customer.Login_ID = Login.Login_ID WHERE Customer.Login_ID = :loginid");
$query->bindParam(':loginid', $this->loginid, PDO::PARAM_INT);
$query->execute();
$fetch = $query->fetch(PDO::FETCH_NUM);
return $fetch[0];
} catch (PDOException $e) {
$this->dbcon->rollback();
echo "Error: " . $e->getMessage();
}
}
// Check the registration progress - are they verified? paid?
// This function is used elsewhere hence the $sessionid argument
public function checkRegistration($sessionid) {
$this->loginid = $sessionid;
$this->custid = $this->getCustId();
try {
$queryVer = $this->dbcon->prepare("SELECT Cust_ID FROM Customer_Verify_Email WHERE Cust_ID = :custid");
$queryVer->bindParam(":custid", $this->custid, PDO::PARAM_INT);
$queryVer->execute();
$queryFee = $this->dbcon->prepare("SELECT Cust_ID FROM Initial_Fee_Payment WHERE Cust_ID = :custid");
$queryFee->bindParam(":custid", $this->custid, PDO::PARAM_INT);
$queryFee->execute();
// If a record exists in the verify table, return the value 1. This means the user has not yet verified their email.
if ($queryVer->rowCount() == 1) {
return 1;
} else {
// If a record does not exist in the payment table, no payment has been made. Return 2.
if ($queryFee->rowCount() == 0) {
return 2;
// Otherwise, email is verified and the payment has been made.
} else {
return 0;
}
}
} catch (PDOException $e) {
$this->dbcon->rollback();
echo "Error: " . $e->getMessage();
}
}
// Redirect the user accordingly
public function redirectUser($sessionid) {
$this->loginid = $sessionid;
$logNum = $this->checkRegistration($this->loginid);
if ($logNum == 0) {
header("Location: fbedder/details.php", true, 200);
exit();
} else if ($logNum == 1) {
header("Location: fbedder/verification.php", true, 200);
exit();
} else if ($logNum == 2) {
header("Location: fbedder/payment.php", true, 200);
exit();
}
}
Here's a link to the site: fbedder/ -> I have set-up a test account with the credentials -> email: test# / password: test123321
To reiterate, the problem exists only in Google Chrome and Safari (the Safari being on my iPhone) and lies purely within the logging in aspect. On the first attempt, the session will be ignored (it is created), and on the second attempt, the user will be redirected.
Any ideas? I've tried a multitude of possibilities...
-- Edit --
I know where the issue lies now.
When the redirect is called, it sends the user to the details.php page. However, this page contains the following snippet of code:
if (!isset($_SESSION['login'])) {
header("Location: fbedder/index.php");
}
Obviously what is happening, is that the session is not being detected / saved / "whatevered", and as a result is sending the user back to the index page. Is there are a way to ensure the $_SESSION is not effectively lost. I had read about this before posting here, and is why I inserted session_write_close(), but that doesn't seem to be doing the desired effect.
After diagnosing the problem being within the fact the $_SESSION variable was effectively being lost, I read up about the session_write_close() function and came across this comment:
My sessions were screwing up because I had 2 different session IDs going at once:
1 PHPSESSID cookie for domain.com
1 PHPSESSID cookie for www.domain.com
This fixed it:
//At the beginning of each page...
session_set_cookie_params (1800,"/","domain.com");
session_start();
Source: http://pt2.php.net/manual/en/function.session-write-close.php#84925
Setting the parameters resolved the issue.