Skip to content

Commit 57e4b28

Browse files
Merge pull request #94 from dimitriBouteille/v4/33-transaction-commit
[BUG] Fix open transaction internally and at the moment transaction is opened but it is never closed
2 parents 50b0478 + f91c430 commit 57e4b28

File tree

2 files changed

+203
-1
lines changed

2 files changed

+203
-1
lines changed

src/Orm/Database.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,8 @@ public function affectingStatement($query, $bindings = []): int
220220
public function unprepared($query): bool
221221
{
222222
return $this->run($query, [], function (string $query) {
223-
return $this->db->query($query);
223+
$result = $this->db->query($query);
224+
return $this->lastRequestHasError() ? $result : true;
224225
});
225226
}
226227

@@ -250,10 +251,16 @@ public function transaction(\Closure $callback, $attempts = 1)
250251
{
251252
$this->beginTransaction();
252253
try {
254+
255+
// We'll simply execute the given callback within a try / catch block and if we
256+
// catch any exception we can rollback this transaction so that none of this
257+
// gets actually persisted to a database or stored in a permanent fashion.
253258
$data = $callback();
254259
$this->commit();
255260
return $data;
256261
} catch (\Exception $e) {
262+
263+
// If we catch an exception we'll rollback this transaction
257264
$this->rollBack();
258265
throw $e;
259266
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<?php
2+
/**
3+
* Copyright © Dimitri BOUTEILLE (https://github.com/dimitriBouteille)
4+
* See LICENSE.txt for license details.
5+
*
6+
* Author: Dimitri BOUTEILLE <[email protected]>
7+
*/
8+
9+
namespace Dbout\WpOrm\Tests\WordPress\Orm;
10+
11+
use Dbout\WpOrm\Orm\AbstractModel;
12+
use Dbout\WpOrm\Orm\Database;
13+
use Dbout\WpOrm\Tests\WordPress\TestCase;
14+
use Illuminate\Database\QueryException;
15+
16+
class DatabaseTransactionTest extends TestCase
17+
{
18+
private string $tableName = '';
19+
private AbstractModel $model;
20+
private Database $db;
21+
22+
/**
23+
* @return void
24+
*/
25+
public static function setUpBeforeClass(): void
26+
{
27+
global $wpdb;
28+
29+
$tableName = $wpdb->prefix . 'document';
30+
$sql = "CREATE TABLE $tableName (
31+
id INT NOT NULL AUTO_INCREMENT,
32+
name varchar(100) NOT NULL,
33+
url varchar(55) DEFAULT '' NOT NULL,
34+
PRIMARY KEY (id)
35+
);";
36+
37+
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
38+
dbDelta($sql);
39+
define('SAVEQUERIES', true);
40+
}
41+
42+
/**
43+
* @return void
44+
*/
45+
public function setUp(): void
46+
{
47+
$this->model = new class () extends AbstractModel {
48+
protected $primaryKey = 'id';
49+
public $timestamps = false;
50+
protected $table = 'document';
51+
};
52+
53+
global $wpdb;
54+
$this->tableName = $wpdb->prefix . 'document';
55+
$this->model::truncate();
56+
$this->db = Database::getInstance();
57+
}
58+
59+
/**
60+
* @throws \Throwable
61+
* @return void
62+
* @covers Database::transaction
63+
* @covers Database::insert
64+
* @covers Database::commit
65+
*/
66+
public function testTransactionCommit(): void
67+
{
68+
$this->resetLogQueries();
69+
$this->db->transaction(function () {
70+
$query = sprintf('INSERT INTO %s (name, url) VALUES(?, ?);', $this->tableName);
71+
$this->db->insert($query, ['Invoice #15', 'invoice-15']);
72+
$this->db->insert($query, ['Invoice #16', 'invoice-16']);
73+
});
74+
75+
$this->assertTransaction('commit');
76+
$this->assertCount(2, $this->model::all()->toArray());
77+
}
78+
79+
/**
80+
* @throws \Throwable
81+
* @return void
82+
* @covers Database::transaction
83+
* @covers Database::delete
84+
* @covers Database::insert
85+
* @covers Database::rollBack
86+
*/
87+
public function testTransactionRollback(): void
88+
{
89+
$query = sprintf('INSERT INTO %s (name, url) VALUES(?, ?);', $this->tableName);
90+
$this->db->insert($query, ['Deposit #1', 'deposit-1']);
91+
$this->db->insert($query, ['Deposit #2', 'deposit-2']);
92+
93+
$this->resetLogQueries();
94+
try {
95+
$this->db->transaction(function () use ($query) {
96+
$this->db->insert($query, ['Deposit #99', 'deposit-99']);
97+
$this->db->delete(sprintf('DELETE FROM %s;', $this->tableName));
98+
99+
/**
100+
* Throw exception because fake_column is invalid column name.
101+
*/
102+
$this->db->delete(sprintf('DELETE FROM %s WHERE fake_column = %d;', $this->tableName, $query));
103+
});
104+
} catch (\Exception) {
105+
// Off exception
106+
}
107+
108+
$this->assertTransaction('rollback');
109+
110+
$items = $this->model::all();
111+
$this->assertCount(2, $items->toArray(), 'There must be only 2 items because the transaction was rollback.');
112+
$this->assertEquals(['deposit-1', 'deposit-2'], $items->pluck('url')->toArray());
113+
}
114+
115+
/**
116+
* @throws \Throwable
117+
* @return void
118+
* @covers Database::transaction
119+
*/
120+
public function testTransactionThrowsQueryException(): void
121+
{
122+
$this->expectException(QueryException::class);
123+
$this->resetLogQueries();
124+
$this->db->transaction(function () {
125+
$this->db->delete('DELETE FROM fake_table;');
126+
});
127+
128+
$this->assertTransaction('rollback');
129+
}
130+
131+
/**
132+
* @throws \Throwable
133+
* @return void
134+
* @covers Database::beginTransaction
135+
*/
136+
public function testBeginTransaction(): void
137+
{
138+
$this->db->beginTransaction();
139+
$this->assertLastQueryEquals('START TRANSACTION;');
140+
}
141+
142+
/**
143+
* @throws \Throwable
144+
* @return void
145+
* @covers Database::rollBack
146+
*/
147+
public function testRollback(): void
148+
{
149+
$this->db->beginTransaction();
150+
$this->db->rollBack();
151+
$this->assertLastQueryEquals('ROLLBACK;');
152+
}
153+
154+
/**
155+
* @throws \Throwable
156+
* @return void
157+
* @covers Database::commit
158+
*/
159+
public function testCommit(): void
160+
{
161+
$this->db->beginTransaction();
162+
$this->db->commit();
163+
$this->assertLastQueryEquals('COMMIT;');
164+
}
165+
166+
/**
167+
* @param string $mode
168+
* @return void
169+
*/
170+
private function assertTransaction(string $mode): void
171+
{
172+
global $wpdb;
173+
$query = $wpdb->queries;
174+
175+
$firstQuery = reset($query)[0] ?? '';
176+
$lastQuery = end($query)[0] ?? '';
177+
$this->assertEquals('START TRANSACTION;', $firstQuery);
178+
$this->assertEquals(0, $this->db->transactionCount);
179+
180+
if ($mode === 'commit') {
181+
$this->assertEquals('COMMIT;', $lastQuery);
182+
} elseif ($mode === 'rollback') {
183+
$this->assertEquals('ROLLBACK;', $lastQuery);
184+
}
185+
}
186+
187+
/**
188+
* @return void
189+
*/
190+
private function resetLogQueries(): void
191+
{
192+
global $wpdb;
193+
$wpdb->queries = [];
194+
}
195+
}

0 commit comments

Comments
 (0)