OK, I'm fully expecting someone to come along and tell me I missed something obvious, but I've spent days trying to figure out what's wrong, and I at this point, if someone can point out a stupid mistake, I'll just be glad to solve the problem. I'm just starting to figure out PDO, so I'm guessing the problem is there, but I really don't know.
This is mostly working. It opens, closes, reads, and does garbage collection. It will write, but only the id, not the data. Destroy is not working either, which seems to be related. If I put echo into the functions, the variables are being passed. The queries work when I try them directly. What seems to be happening is that the binding isn't working. I've tried both bindParam and bindValue. I've tried so many other things I can't even list them all. So anyway, here's the class:
class Sessions {
private $id;
private $data;
protected $pdo;
private $maxTime;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
// Set handler to overide SESSION
#session_set_save_handler(
array($this, "openSession"),
array($this, "closeSession"),
array($this, "readSession"),
array($this, "writeSession"),
array($this, "destroySession"),
array($this, "gcSession")
);
session_start();
}
public function openSession()
{
// If successful
if ($this->pdo) {
return true;
} else {
return false;
}
}
public function closeSession() {
// Close the database connection
if ($pdo = NULL) {
#session_write_close();
return true;
} else {
return false;
}
}
public function readSession($id) {
if (isset($id)) {
// Set query
$q = 'SELECT data FROM sessions WHERE id = :id';
$stmt = $this->pdo -> prepare($q);
// Bind the Id
$stmt -> bindValue(':id', $id);
$stmt -> execute();
$r = $stmt -> fetch();
// Return the data
if (!empty($r['data'])) {
return $r['data'];
} else {
// Return an empty string
return '';
}
} else {
return '';
}
}
// when session started and updated
public function writeSession($id, $data) {
// Set query
$q = "INSERT INTO sessions
(id, `data`, startTime)
VALUES (:id, :data, NOW())
ON DUPLICATE KEY UPDATE
`data` = :data2,
startTime = NOW()";
$stmt = $this->pdo->prepare($q);
// Bind data
$stmt->bindParam(':id', $id);
$stmt->bindParam(':data', $data);
$stmt->bindParam(':data2', $data);
$stmt->execute();
if ($stmt->rowCount()) {
return TRUE;
} else {
return FALSE;
}
}// when session started and updated
public function writeSession($id, $data) {
// Set query
$q = "INSERT INTO sessions (id, `data`, startTime) VALUES (:id, :data, NOW()) ON DUPLICATE KEY UPDATE `data` = :data2, startTime = NOW()";
$stmt = $this->pdo -> prepare($q);
// Bind data
$stmt->bindParam(':id', $id);
$stmt->bindParam(':data', $data);
$stmt->bindParam(':data2', $data);
$stmt->execute();
if ($stmt->rowCount()) {
return TRUE;
} else {
return FALSE;
}
}
public function destroySession($id) {
echo $id;
$q = "DELETE FROM `sessions` WHERE `id`= :id";
$stmt = $this->pdo -> prepare($q);
$stmt->execute(array(':id' => $id));
// Attempt execution
if ($stmt === true) {
echo " worked ";
return TRUE;
} else {
echo " no ";
return FALSE;
}
}
public function gcSession($maxTime) {
$q="DELETE FROM sessions WHERE (NOW()-startTime > $maxTime)";
$stmt = $this->pdo -> prepare($q);
if ($stmt->execute()) {
return TRUE;
} else {
return FALSE;
}
}
}
The function is being called this way:
// Store the user in the session and redirect:
$startSessions = new Sessions($pdo);
$_SESSION['data'] = 'IDnotOK';
$startSessions -> writeSession(session_id(), 'IDallOK');
$startSessions -> gcSession(1800);
The 'IDnotOK' is being sent to the database (sort of - it's 'data|s:7:"IDnotOK";'), instead of 'IDallOK'. If the $_SESSION['data'] isn't there, nothing is sent at all. The id is passed just fine. The timestamp also updates just fine. When I call:
$startSessions -> destroySession(session_id());
it doesn't matter how I try to pass the id, it will echo from within the function, but won't do anything to the database entry. Garbage collections works (probably because it only relies on the time and doesn't care about the id).
I wish I could list everything I've tried, but I've lost track. If there are any other questions that will help track down the problem, please ask. Thank You!
Related
i'm training SOLID/Architectures and trying to make an INSERT on my code, but its insert four times on DB. There's any error on my logic? I'm following Repositories/Service Pattern.
i think my service is executing two times, but i cant find the reason.
Repositorie Code
public function inserirEstoque($dadosPost)
{
if (empty($dadosPost)) {
return false;
}
$pdo = $this->dbConnection->conectar();
$sql = $pdo->prepare('INSERT INTO estoque (nomeProduto, descriptions, price, quantity)
VALUES (:nome, :descriptions, :price, :quantity)');
$sql->bindValue(':nome', $dadosPost['nomeProduto']);
$sql->bindValue(':descriptions', $dadosPost['descriptions']);
$sql->bindValue(':price', $dadosPost['price']);
$sql->bindValue(':quantity', $dadosPost['quantity']);
$res = $sql->execute();
if($res == false)
{
return false;
}
return $res;
}
Service
public function insertEstoque()
{
$db = new MySQL();
$insert = new EstoqueRepositories($db);
if(!empty($insert->inserirEstoque($_POST))){
return $insert->inserirEstoque($_POST);
} else {
return false;
}
}
Controller
public function insert()
{
$insert = new EstoqueService();
$insert->insertEstoque();
header('Location: ../../index.php');
}
It's executing twice because of this
if(!empty($insert->inserirEstoque($_POST))){
return $insert->inserirEstoque($_POST);
} else {
return false;
}
if you wanna check if the POST data is empty just remove where it inserts the data then it should just insert it 1 time
if(!empty($_POST["whatevername"])){
return $insert->inserirEstoque($_POST);
} else {
return false;
}
As an addition to Reed's answer, if you just want to check the result of a function call before carrying on, assign the result to a variable and use that variable.
$res = $insert->inserirEstoque($_POST)
if(!empty($res)){
return $res;
} else {
return false;
}
Watching this online tutorial about MYSQL's session handler and got really confused about this part:
table_XXX == Table XXX;
col_XXX == Column XXX;
sid == Session id
Read method:
public function read($session_id)
{
$this->db->exec('SET TRANSACTION ISOLATION LEVEL READ COMMITTED');
$this->db->beginTransaction();
/**
* the data is selected and no other ppl can interfere
* the writing process until COMMIT is reached
*/
$sql = "SELECT $this->col_expiry, $this->col_data
FROM $this->table_sess
WHERE $this->col_sid = :sid FOR UPDATE";
$selectStmt = $this->db->prepare($sql);
$selectStmt->bindParam(':sid', $session_id);
$selectStmt->execute();
$results = $selectStmt->fetch(\PDO::FETCH_ASSOC);
if ($results) {
if ($results[$this->col_expiry] < time()) {
// return empty if data out of date
return '';
}
return $results[$this->col_data];
}
return $this->initializeRecord($selectStmt);
}
Protected method:
protected function initializeRecord(\PDOStatement $selectStmt)
{
try {
$sql = "INSERT INTO $this->table_sess
($this->col_sid, $this->col_expiry, $this->col_data)
VALUES (:sid, :expiry, :data)";
$insertStmt = $this->db->prepare($sql);
$insertStmt->bindParam(':sid', $session_id);
$insertStmt->bindParam(':expiry', $this->expiry); // expiry is defined
$insertStmt->bindValue(':data', '');
$insertStmt->execute();
return '';
} catch(\PDOException $e) {
$this->db->rollBack();
throw $e;
}
}
Write method:
public function write($session_id, $data)
{
try {
$sql = "INSERT INTO $this->table_sess ($this->col_sid,
$this->col_expiry, $this->col_data)
VALUES (:sid, :expiry, :data)
ON DUPLICATE KEY UPDATE
$this->col_expiry = :expiry,
$this->col_data = :data";
$stmt = $this->db->prepare($sql);
$stmt->bindParam(':expiry', $this->expiry, \PDO::PARAM_INT);
$stmt->bindParam(':data', $data);
$stmt->bindParam(':sid', $session_id);
$stmt->execute();
return true;
} catch (\PDOException $e) {
if ($this->db->inTransaction()) {
$this->db->rollback();
}
throw $e;
}
}
In 'Protected method', line 8, there is a $session_id, and clearly no $session_id is passed to the protected method, so bindParam() for that line simply binded nothing?
So initializeRecord() simply initiated a row that has expiry time but nothing else? And then the sid and data is inserted after write method is called?
This is doing a lot of string-construction trickery with WHERE $this->col_sid = :sid and so forth, as it creates SQL statements.
You might try echoing or dumping those SQL statements to see what they contain right before you run ->execute() on them. That will help you troubleshoot.
It's pretty clear your protected method is missing $session_id. Is it possible there's a value for $this->sid you could use there?
I am using a few functions inside another function that updates certain things, deletes some and inserts, now my problem, if 2 above are successful or 1 and the rest aren't it could cause catastrophic outcomes. So is it possible to use 1 transaction for all 3 functions... for example:
public static function do_stuff()
{
//run sql in function
SELF::function_sql_one_insert();
SELF::function_sql_two_update();
SELF::function_sql_three_delete();
}
Like so:
public static function test()
{
SELF::function_sql_one_insert();
SELF::function_sql_two_update();
SELF::function_sql_three_delete();
}
public static function function_sql_one_insert()
{
//sql to run
$sql = "INSERT INTO table
(
fake_row_one,
fake_row_two
)
VALUES
(
?,
?
)";
//run sql
$fake_insert = $database->prepare($sql);
$fake_insert->execute("yeah", "okay");
}
public static function function_sql_two_update()
{
//sql to run
$sql = "UPDATE table
SET fake_row_one = ?
WHERE fake_row_two = ?";
//run sql
$fake_update = $database->prepare($sql);
$fake_update->execute("blahblah", "okay");
}
public static function function_sql_three_delete()
{
//sql to run
$sql = "DELETE FROM TABLE
WHERE fake_row_two = ?";
//run sql
$fake_delete = $database->prepare($sql);
$fake_delete->execute("okay");
}
What I am trying to acomplish is if one fails revert all of them back. Is this possible? If not what can I do instead, if so, is there any cons to this?
Php functions has absolutely nothing to do with database transactions. It's just irrelevant matters.
A database transaction is bound to database connection only. Thetefore, as long as all your functions use the same connection, there is no problem to run all three in a transaction.
You should use database transactions for this.
Essentially,
you start a transaction,
you do your SQL queries
if it fails somewhere, you do a rollback and otherwise you do a commit
But there are a few gotcha's such as certain sql statements that commit on their own, so read all the official docs for the database you're using.
More info: http://php.net/manual/en/pdo.begintransaction.php
But remember that not all databases support transactions to start with.
E.g. MyISAM tables in MySQL do not support transactions.
You might want to convert those tables to InnoDB see here e.g.: How to convert all tables from MyISAM into InnoDB?
I use this way:
class Teste {
public $mysqli;
public $erro = array();
public function __construct(){
$this->mysqli = new \mysqli(DB_HOST,DB_USERNAME,DB_PASSWORD,DATABASE);
$this->mysqli->set_charset("utf8");
}
public function start_trans(){
$this->mysqli->autocommit(false);
}
public function end_trans(){
if(count($this->erro) == 0){
$this->mysqli->commit();
echo "success";
} else {
$this->mysqli->rollback();
echo "error";
}
}
public function example1(){
$stmt = $this->mysqli->query("insert into veiculos (placa, modelo) values (1,1)");
if(!$stmt){
$this->erro[] = "Erro #143309082017 <code>" . $this->mysqli->error . "</code>";
}
return (count($this->erro) < 1)? true : false;
}
public function example2(){
$stmt = $this->mysqli->query("insert into veiculos (placa, modelo) values (2,2)");
if(!$stmt){
$this->erro[] = "Erro #143309082017 <code>" . $this->mysqli->error . "</code>";
}
return (count($this->erro) < 1)? true : false;
}
public function example3(){
$this->mysqli->autocommit(false);
$stmt = $this->mysqli->query("insert into veiculos (placa, modelo) values (3,3)");
if(!$stmt){
$this->erro[] = "Erro #143309082017 <code>" . $this->mysqli->error . "</code>";
}
return (count($this->erro) < 1)? true : false;
}
}
$action = new Teste;
$action->start_trans();
$action->example1();
$action->example2();
$action->example3();
$action->end_trans();
Maybe you can try something like this:
$working = true;
try
{
SELF::function_sql_one_insert();
} catch (Exception $e)
{
if($e != "")
$working = false;
}
Try this for all functions. If $working is true, you can execute all commands.
I am developing an e-commerce website on which i need to store sessions inside database.I did that by implementing SessionHandlerInterface Class that is provided by the php itself.However it works totally fine and did store sessions inside the database , as well as read them properly.
However I am facing problem when I am using unset to unset a session variable.Sometimes it does work.Sometimes it doesn't.
For example:If i have a session variable by the name ABC unset might delete it from the database or it doesn't deletes the variable.
<?php
//inc.session.php
require_once 'RemoteAddress.php';
class SysSession implements SessionHandlerInterface
{
private $remote_write;
private $remote_read;
private $link;
private $ip_address_write;
private $ip_address_read;
public function open($savePath, $sessionName)
{
$link = new mysqli("localhost","root","","cakenbake");
if($link){
$this->link = $link;
return true;
}else{
return false;
}
}
public function close()
{
mysqli_close($this->link);
return true;
}
public function read($id)
{
$this->remote_read=new RemoteAddress();
$this->ip_address_read=$this->remote_read->getIpAddress();
$stmt=$this->link->prepare("SELECT `Session_Data`,`ip_address` FROM Session WHERE `Session_Id` = ? AND `Session_Expires` > '".date('Y-m-d H:i:s')."'");
$stmt->bind_param("s",$id);
$stmt->execute();
//$result = mysqli_query($this->link,"SELECT Session_Data FROM Session WHERE Session_Id = '".$id."' AND Session_Expires > '".date('Y-m-d H:i:s')."'");
/*$result=$this->link->prepare("Some query inside")
* This shows up an error stating prepare method not found
*
*/
$res=$stmt->get_result();
if($row=$res->fetch_assoc()){
if($this->ip_address_read==$row['ip_address'])
return $row['Session_Data'];
else return "";
}else{
return "";
}
}
public function write($id, $data)
{
$this->remote_write=new RemoteAddress();
$this->ip_address_write=$this->remote_write->getIpAddress();
if(!empty($data))
{
$DateTime = date('Y-m-d H:i:s');
$NewDateTime = date('Y-m-d H:i:s',strtotime($DateTime.' + 1 hour'));
$stmt=$this->link->prepare("REPLACE INTO Session SET Session_Id = ?, Session_Expires = '".$NewDateTime."', Session_Data = '".$data."', ip_address = '".$this->ip_address_write."'");
$stmt->bind_param("s",$id);
// $result = mysqli_query($this->link,"REPLACE INTO Session SET Session_Id = '".$id."', Session_Expires = '".$NewDateTime."', Session_Data = '".$data."'");
if($stmt->execute()){
return true;
}else{
return false;
}
}
}
public function destroy($id)
{
$stmt = $this->link->prepare("DELETE FROM Session WHERE Session_Id =?");
$stmt->bind_param("s",$id);
if($stmt->execute()){
return true;
}else{
return false;
}
}
public function gc($maxlifetime)
{
$result = $this->link->query("DELETE FROM Session WHERE ((UNIX_TIMESTAMP(Session_Expires) + ".$maxlifetime.") < UNIX_TIMESTAMP(NOW()))");
if($result){
return true;
}else{
return false;
}
}
}
$handler = new SysSession();
session_set_save_handler($handler, true);
?>
The above code stores and read sessions from the database.
Structure of the session table.
What could be the possible reason for this weird behaviour. Do i have to implement unset function as well?.
How should i resolve this problem?
If you could suggest me someother already written code for storing in database.That would work as well but i dont need any frameworks such as codeigniter and Yii2.
If you need more information regarding this problem.I will update my question.
Thanks in advance!
The problem is not with the unset function but with your write function.The write function is responsible for any updates that are made to the specific session id.
The wierd behiviour is not with the unset but it is with the write funciton you have implemented.
See ,the !empty constraint checks if your data is empty or not.What i can guess is that your database for that specific id must be empty after the removal of the specific variable .So the write tries to update your row with an empty value but with that constraint it isn't able to do so.
Just remove the !empty tag and it will work like a charm.
I have an odd issue. The first time a visitor comes to the site and I set anything is the session, it doesn't stick. The second and all the following times I try to set something it sticks. After the initial try I can destroy the session and set something and it sticks. Its just the initial attempt to save something fails. I'm trying to save something to the session with $_SESSION['uid'] = $row["Id"];. I know the $row["Id"] is valid and holds data (I echoed it).
I am not using standard sessions. I am saving the session into a database. My session class is below. Is there anything I'm missing or doing wrong to explain this behavior?
Update:
Well I tested the session class on its own and it seems to be working :-/ But when I use it in my larger application _write never gets called, though __destruct does get called. Any idea why that may be?
<?php
include_once('db.php');
class PDOSession
{
protected $pdo;
protected $table = 'SessionData';
public function __construct()
{
// Get a database connection
$db = new PDOConnectionFactory();
$this->pdo = $db->getConnection(true);
// Start session
session_set_save_handler(array($this, '_open'),
array($this, '_close'),
array($this, '_read'),
array($this, '_write'),
array($this, '_destroy'),
array($this, '_gc'));
session_start();
}
public function __destruct()
{
session_write_close();
}
protected function fetchSession($id)
{
$stmt = $this->pdo->prepare('SELECT id, data FROM '.$this->table.' WHERE id = :id AND unixtime > :unixtime');
$stmt->execute(array(':id' => $id, ':unixtime' => (time() - (int)ini_get('session.gc_maxlifetime'))));
$sessions = $stmt->fetchAll();
return empty($sessions) ? false : $sessions[0];
}
public function _open($savePath, $sessionName)
{
return true;
}
public function _close()
{
return true;
}
public function _read($id)
{
$session = $this->fetchSession($id);
return ($session === false) ? false : $session['data'];
}
public function _write($id, $sessionData)
{
$session = $this->fetchSession($id);
if($session === false) {
$stmt = $this->pdo->prepare('INSERT INTO '.$this->table.' (id, data, unixtime) VALUES (:id, :data, :time)');
} else {
$stmt = $this->pdo->prepare('UPDATE '.$this->table.' SET data = :data, unixtime = :time WHERE id = :id');
}
$stmt->execute(array(
':id' => $id,
':data' => $sessionData,
':time' => time()
));
}
public function _destroy($id)
{
$stmt = $this->pdo->prepare('DELETE FROM '.$this->table.' WHERE id = :id');
$stmt->execute(array(':id' => $id));
}
public function _gc($maxlifetime)
{
$stmt = $this->pdo->prepare('DELETE FROM '.$this->table.' WHERE unixtime < :time');
$stmt->execute(array(':time' => (time() - (int) $maxlifetime)));
}
}
$newPDOSessionStartHere = new PDOSession();
I'm a bit of an idiot I guess. I was calling session_destroy() rather than session_unset() to clear things out at the top of my authentication script. The class works fine.
I think that you should start a session after you define your class. Not inside.