A few weeks back I had an interesting inquiry regarding the handling/processing of an inventory/payment system and I've decided to implement the method of the accepted answer being it was both the most simplistic and more likely the most efficient solution given.
I'm using Laravel's Eloquent ORM in conjunction with the Slim Framework, and I'd like to perform a MYSQL transaction with a specific query regarding the decrementation of the stock of items within an inventory system. The code that I'd like to use a transaction for is the following:
// decrement each items stock following successful payment
try {
// here is where i'd like to perform a transaction, rather than simple eloquent query
foreach ($this->c->basket->all() as $product) {
$product->decrement('stock', $product->quantity);
}
} catch (Exception $e) {
// if error is caused during decremention of item(s),
// handle accordingly
}
Now being that I am using Slim as aforementioned, I believe I don't have native access to the Laravel DB Facade which to my knowledge handles transactions.
Suggestions?
SIDE-NOTE
My DB Connection is set globally in an initialization file like so:
$capsule = new Illuminate\Database\Capsule\Manager();
$capsule->addConnection($container['settings']['db']);
$capsule->setAsGlobal();
$capsule->bootEloquent();
You could get the Capsule object from the slim container and then just use the Connection object to start and commit a database transaction. If an exception occurs you just need to call the rollBack() method.
Pseudo code:
$capsule = new \Illuminate\Database\Capsule\Manager();
// Get a registered connection instance.
$connection = $capsule->getConnection();
// begin a transaction manually
$connection->beginTransaction();
try {
// do something...
$product->decrement('stock', $product->quantity);
// commit a transaction via the commit method
$connection->commit();
} catch (Exception $e) {
// you can rollback the transaction via the rollBack method
$connection->rollBack();
}
Related
The Eloquent ORM is quite nice, though I'm wondering if there is an easy way to setup MySQL transactions using innoDB in the same fashion as PDO, or if I would have to extend the ORM to make this possible?
You can do this:
DB::transaction(function() {
//
});
Everything inside the Closure executes within a transaction. If an exception occurs it will rollback automatically.
If you don't like anonymous functions:
try {
DB::connection()->pdo->beginTransaction();
// database queries here
DB::connection()->pdo->commit();
} catch (\PDOException $e) {
// Woopsy
DB::connection()->pdo->rollBack();
}
Update: For laravel 4, the pdo object isn't public anymore so:
try {
DB::beginTransaction();
// database queries here
DB::commit();
} catch (\PDOException $e) {
// Woopsy
DB::rollBack();
}
If you want to use Eloquent, you also can use this
This is just sample code from my project
/*
* Saving Question
*/
$question = new Question;
$questionCategory = new QuestionCategory;
/*
* Insert new record for question
*/
$question->title = $title;
$question->user_id = Auth::user()->user_id;
$question->description = $description;
$question->time_post = date('Y-m-d H:i:s');
if(Input::has('expiredtime'))
$question->expired_time = Input::get('expiredtime');
$questionCategory->category_id = $category;
$questionCategory->time_added = date('Y-m-d H:i:s');
DB::transaction(function() use ($question, $questionCategory) {
$question->save();
/*
* insert new record for question category
*/
$questionCategory->question_id = $question->id;
$questionCategory->save();
});
If you want to avoid closures, and happy to use facades, the following keeps things nice and clean:
try {
\DB::beginTransaction();
$user = \Auth::user();
$user->fill($request->all());
$user->push();
\DB::commit();
} catch (Throwable $e) {
\DB::rollback();
}
If any statements fail, commit will never hit, and the transaction won't process.
I'm Sure you are not looking for a closure solution, try this for a more compact solution
try{
DB::beginTransaction();
/*
* Your DB code
* */
DB::commit();
}catch(\Exception $e){
DB::rollback();
}
For some reason it is quite difficult to find this information anywhere, so I decided to post it here, as my issue, while related to Eloquent transactions, was exactly changing this.
After reading THIS stackoverflow answer, I realized my database tables were using MyISAM instead of InnoDB.
For transactions to work on Laravel (or anywhere else as it seems), it is required that your tables are set to use InnoDB
Why?
Quoting MySQL Transactions and Atomic Operations docs (here):
MySQL Server (version 3.23-max and all versions 4.0 and above) supports transactions with the InnoDB and BDB transactional storage engines. InnoDB provides full ACID compliance. See Chapter 14, Storage Engines. For information about InnoDB differences from standard SQL with regard to treatment of transaction errors, see Section 14.2.11, “InnoDB Error Handling”.
The other nontransactional storage engines in MySQL Server (such as MyISAM) follow a different paradigm for data integrity called “atomic operations.” In transactional terms, MyISAM tables effectively always operate in autocommit = 1 mode. Atomic operations often offer comparable integrity with higher performance.
Because MySQL Server supports both paradigms, you can decide whether your applications are best served by the speed of atomic operations or the use of transactional features. This choice can be made on a per-table basis.
If any exception occurs, the transaction will rollback automatically.
Laravel Basic transaction format
try{
DB::beginTransaction();
/*
* SQL operation one
* SQL operation two
..................
..................
* SQL operation n */
DB::commit();
/* Transaction successful. */
}catch(\Exception $e){
DB::rollback();
/* Transaction failed. */
}
The best and clear way:
DB::beginTransaction();
try {
DB::insert(...);
DB::insert(...);
DB::insert(...);
DB::commit();
// all good
} catch (\Exception $e) {
DB::rollback();
// something went wrong
}
When starting my Yii2/PHP application, how can I check if / wait until the database is up?
Currently with MySQL I use:
$time = time();
$ok = false;
do {
try {
$pdo = new PDO($dsn,$username,$password);
if ($pdo->query("SELECT 1 FROM INFORMATION_SCHEMA.SCHEMATA"))
$ok=true;
} catch (\Exception $e) {
sleep(1);
}
} while (!$ok && time()<$time+30);
Now I want make my application running with MySQL and PostgreSQL.
But SELECT 1 FROM INFORMATION_SCHEMA.SCHEMATA does not work in PostgreSQL.
Is there a SQL-statement (using PDO database connectivity) that works on both database systems to check if the database is up and running?
Yii2 has a property to verify if a connection exists or not, it is really not necessary to create a script for this, since this framework has an abstraction implemented for the databases it supports ($isActive property).
$isActive public read-only property Whether the DB connection is
established
public boolean getIsActive ( )
You can do the check in your default controller in the following way:
<?php
class DefaultController extends Controller
{
public function init()
{
if (!Yii::$app->db->isActive) {
// The connection does not exist.
}
parent::init();
}
}
It is not good practice to force waiting for a connection to a database unless there are very specific requirements. The availability of a connection to the database must be a mandatory requirement for an application to start and the application should not "wait" for the database to be available.
There are ways to run containers in docker in an orderly manner or with a specific requirement, this link could give you a better option instead of delegating this to the application.
You could use SELECT 1 which is standard SQL.
You can use dbfiddle to test against various servers.
The server could go away an any time so checking the error response with every query is a much better approach.
I need to run a query outside the transaction that I have started:
$transaction = \Yii::$app->db->beginTransaction();
try {
//... other database queries within the transaction ...
//Query I want to be inserted regardless:
\Yii::$app->db->createCommand("INSERT INTO...")->execute();
$transaction->commit();
} catch (\Exception $e) {
$transaction->rollBack();
throw $e;
}
Is there a smart way to do that, or do I need to clone/create a new database connection - and if so, what's the best way to do that without having to specify the database parameters again and just use the same config?
You need to use separate connection or move this query after/before transaction.
For copying DB component you can simply use clone - new instance should open new connection on first query:
$connection = clone Yii::$app->db;
$connection->createCommand("INSERT INTO...")->execute();
It is used in this way in yii\log\DbTarget.
But you may consider declaring separate DB component for this task (Yii::$app->db2) - then you will be able to reuse this additional connection.
In my PHP application, I am trying to use Redis (rather Predis, the PHP client for Redis) for data caching, and for that I am following this tutorial.
Now I will need to store data in the cache in one PHP script/page. For that I will create a Predis client object $redis and then use this $redis object to call its functions like set() and expire() on it to store the data as key value pairs. An example snippet is given below.
Now the problem is that, in another PHP script/page, I will need to read the data stored in the cache. For that, I will need to call the functions like get() or exists() on the $redis object again.
Now my question is that in this second PHP script/page, should I create a new Predis client object like the $redis mentioned above (then I'll need to create a new $redis object every time I want to access data stored in cache in a new PHP script/page), or should I declare the first one as GLOBAL, so that it is accessible from all PHP scripts/pages in my web application? What is the correct practice
Example PHP first script:
<?php
require "predis/autoload.php";
PredisAutoloader::register();
try {
$redis = new PredisClient();
}
catch (Exception $e) {
die($e->getMessage());
}
// sets message to contian "Hello world"
$redis->set('message', 'Hello world');
?>
Example PHP Second Script:
<?php
/* THE FOLLOWING PART WHICH CREATES A $redis
OBJECT SEEMS TO BE AN OVERDO. SHOULDN'T WE BE
USING THE SAME $redis OBJECT FROM THE ABOVE SNIPPET? */
require "predis/autoload.php";
PredisAutoloader::register();
try {
$redis = new PredisClient();
}
catch (Exception $e) {
die($e->getMessage());
}
// gets the value of message
$value = $redis->get('message');
?>
The answer really depends on the complexity of your web application. One simple way to do this that you can implement easily is to make a singleton class and use a static variable in that class.
class MyRedis {
private static $redis;
static function shared() {
if ( empty(self::$redis) ) {
try {
self::$redis = new PredisClient();
} catch (Exception $e) {
die($e->getMessage());
}
}
return self::$redis;
}
}
Now when you want your object you do this:
$myRedisObject = MyRedis::shared();
// or
$message = MyRedis::shared()->get('message');
It will make one if it has not done so already.
You may not need to do this - you could run some tests to check but according to the Predis docs:
Connections to Redis are lazy meaning that the client connects to a
server only if and when needed. While it is recommended to let the
client do its own stuff under the hood, there may be times when it is
still desired to have control of when the connection is opened or
closed: this can easily be achieved by invoking $client->connect() and
$client->disconnect().
It also depends how many connections you are making -- it could be a case of over-engineering as the connection overhead, while real, is pretty low.
I also assume in your example both scripts are being included in one request. You could have some kind of messaging/cache component that stores the data in Redis as well as makes it available to the local script environment, just depends on the specifics of your data requirements.
The Eloquent ORM is quite nice, though I'm wondering if there is an easy way to setup MySQL transactions using innoDB in the same fashion as PDO, or if I would have to extend the ORM to make this possible?
You can do this:
DB::transaction(function() {
//
});
Everything inside the Closure executes within a transaction. If an exception occurs it will rollback automatically.
If you don't like anonymous functions:
try {
DB::connection()->pdo->beginTransaction();
// database queries here
DB::connection()->pdo->commit();
} catch (\PDOException $e) {
// Woopsy
DB::connection()->pdo->rollBack();
}
Update: For laravel 4, the pdo object isn't public anymore so:
try {
DB::beginTransaction();
// database queries here
DB::commit();
} catch (\PDOException $e) {
// Woopsy
DB::rollBack();
}
If you want to use Eloquent, you also can use this
This is just sample code from my project
/*
* Saving Question
*/
$question = new Question;
$questionCategory = new QuestionCategory;
/*
* Insert new record for question
*/
$question->title = $title;
$question->user_id = Auth::user()->user_id;
$question->description = $description;
$question->time_post = date('Y-m-d H:i:s');
if(Input::has('expiredtime'))
$question->expired_time = Input::get('expiredtime');
$questionCategory->category_id = $category;
$questionCategory->time_added = date('Y-m-d H:i:s');
DB::transaction(function() use ($question, $questionCategory) {
$question->save();
/*
* insert new record for question category
*/
$questionCategory->question_id = $question->id;
$questionCategory->save();
});
If you want to avoid closures, and happy to use facades, the following keeps things nice and clean:
try {
\DB::beginTransaction();
$user = \Auth::user();
$user->fill($request->all());
$user->push();
\DB::commit();
} catch (Throwable $e) {
\DB::rollback();
}
If any statements fail, commit will never hit, and the transaction won't process.
I'm Sure you are not looking for a closure solution, try this for a more compact solution
try{
DB::beginTransaction();
/*
* Your DB code
* */
DB::commit();
}catch(\Exception $e){
DB::rollback();
}
For some reason it is quite difficult to find this information anywhere, so I decided to post it here, as my issue, while related to Eloquent transactions, was exactly changing this.
After reading THIS stackoverflow answer, I realized my database tables were using MyISAM instead of InnoDB.
For transactions to work on Laravel (or anywhere else as it seems), it is required that your tables are set to use InnoDB
Why?
Quoting MySQL Transactions and Atomic Operations docs (here):
MySQL Server (version 3.23-max and all versions 4.0 and above) supports transactions with the InnoDB and BDB transactional storage engines. InnoDB provides full ACID compliance. See Chapter 14, Storage Engines. For information about InnoDB differences from standard SQL with regard to treatment of transaction errors, see Section 14.2.11, “InnoDB Error Handling”.
The other nontransactional storage engines in MySQL Server (such as MyISAM) follow a different paradigm for data integrity called “atomic operations.” In transactional terms, MyISAM tables effectively always operate in autocommit = 1 mode. Atomic operations often offer comparable integrity with higher performance.
Because MySQL Server supports both paradigms, you can decide whether your applications are best served by the speed of atomic operations or the use of transactional features. This choice can be made on a per-table basis.
If any exception occurs, the transaction will rollback automatically.
Laravel Basic transaction format
try{
DB::beginTransaction();
/*
* SQL operation one
* SQL operation two
..................
..................
* SQL operation n */
DB::commit();
/* Transaction successful. */
}catch(\Exception $e){
DB::rollback();
/* Transaction failed. */
}
The best and clear way:
DB::beginTransaction();
try {
DB::insert(...);
DB::insert(...);
DB::insert(...);
DB::commit();
// all good
} catch (\Exception $e) {
DB::rollback();
// something went wrong
}