Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>tags/v24.0.0beta1
@@ -35,6 +35,7 @@ return array( | |||
'OCP\\AppFramework\\Db\\Mapper' => $baseDir . '/lib/public/AppFramework/Db/Mapper.php', | |||
'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => $baseDir . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php', | |||
'OCP\\AppFramework\\Db\\QBMapper' => $baseDir . '/lib/public/AppFramework/Db/QBMapper.php', | |||
'OCP\\AppFramework\\Db\\TTransactional' => $baseDir . '/lib/public/AppFramework/Db/TTransactional.php', | |||
'OCP\\AppFramework\\Http' => $baseDir . '/lib/public/AppFramework/Http.php', | |||
'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => $baseDir . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php', | |||
'OCP\\AppFramework\\Http\\DataDisplayResponse' => $baseDir . '/lib/public/AppFramework/Http/DataDisplayResponse.php', |
@@ -64,6 +64,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c | |||
'OCP\\AppFramework\\Db\\Mapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Mapper.php', | |||
'OCP\\AppFramework\\Db\\MultipleObjectsReturnedException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/MultipleObjectsReturnedException.php', | |||
'OCP\\AppFramework\\Db\\QBMapper' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/QBMapper.php', | |||
'OCP\\AppFramework\\Db\\TTransactional' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/TTransactional.php', | |||
'OCP\\AppFramework\\Http' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http.php', | |||
'OCP\\AppFramework\\Http\\ContentSecurityPolicy' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/ContentSecurityPolicy.php', | |||
'OCP\\AppFramework\\Http\\DataDisplayResponse' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Http/DataDisplayResponse.php', |
@@ -0,0 +1,70 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace OCP\AppFramework\Db; | |||
use OCP\DB\Exception; | |||
use OCP\IDBConnection; | |||
use Throwable; | |||
/** | |||
* Helper trait for transactional operations | |||
* | |||
* @since 24.0.0 | |||
*/ | |||
trait TTransactional { | |||
/** | |||
* Run an atomic database operation | |||
* | |||
* - Commit if no exceptions are thrown, return the callable result | |||
* - Revert otherwise and rethrows the exception | |||
* | |||
* @template T | |||
* @param callable $fn | |||
* @psalm-param callable():T $fn | |||
* @param IDBConnection $db | |||
* | |||
* @return mixed the result of the passed callable | |||
* @psalm-return T | |||
* | |||
* @throws Exception for possible errors of commit or rollback or the custom operations within the closure | |||
* @throws Throwable any other error caused by the closure | |||
* | |||
* @since 24.0.0 | |||
* @see https://docs.nextcloud.com/server/latest/developer_manual/basics/storage/database.html#transactions | |||
*/ | |||
protected function atomic(callable $fn, IDBConnection $db) { | |||
$db->beginTransaction(); | |||
try { | |||
$result = $fn(); | |||
$db->commit(); | |||
return $result; | |||
} catch (Throwable $e) { | |||
$db->rollBack(); | |||
throw $e; | |||
} | |||
} | |||
} |
@@ -0,0 +1,98 @@ | |||
<?php | |||
declare(strict_types=1); | |||
/* | |||
* @copyright 2022 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @author 2022 Christoph Wurst <christoph@winzerhof-wurst.at> | |||
* | |||
* @license GNU AGPL version 3 or any later version | |||
* | |||
* This program is free software: you can redistribute it and/or modify | |||
* it under the terms of the GNU Affero General Public License as | |||
* published by the Free Software Foundation, either version 3 of the | |||
* License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
* GNU Affero General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Affero General Public License | |||
* along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
*/ | |||
namespace lib\AppFramework\Db; | |||
use OCP\AppFramework\Db\TTransactional; | |||
use OCP\IDBConnection; | |||
use PHPUnit\Framework\MockObject\MockObject; | |||
use RuntimeException; | |||
use Test\TestCase; | |||
class TransactionalTest extends TestCase { | |||
/** @var IDBConnection|MockObject */ | |||
private IDBConnection $db; | |||
protected function setUp(): void { | |||
parent::setUp(); | |||
$this->db = $this->createMock(IDBConnection::class); | |||
} | |||
public function testAtomicRollback(): void { | |||
$test = new class($this->db) { | |||
use TTransactional; | |||
private IDBConnection $db; | |||
public function __construct(IDBConnection $db) { | |||
$this->db = $db; | |||
} | |||
public function fail(): void { | |||
$this->atomic(function () { | |||
throw new RuntimeException('nope'); | |||
}, $this->db); | |||
} | |||
}; | |||
$this->db->expects(self::once()) | |||
->method('beginTransaction'); | |||
$this->db->expects(self::once()) | |||
->method('rollback'); | |||
$this->db->expects(self::never()) | |||
->method('commit'); | |||
$this->expectException(RuntimeException::class); | |||
$test->fail(); | |||
} | |||
public function testAtomicCommit(): void { | |||
$test = new class($this->db) { | |||
use TTransactional; | |||
private IDBConnection $db; | |||
public function __construct(IDBConnection $db) { | |||
$this->db = $db; | |||
} | |||
public function succeed(): int { | |||
return $this->atomic(function () { | |||
return 3; | |||
}, $this->db); | |||
} | |||
}; | |||
$this->db->expects(self::once()) | |||
->method('beginTransaction'); | |||
$this->db->expects(self::never()) | |||
->method('rollback'); | |||
$this->db->expects(self::once()) | |||
->method('commit'); | |||
$result = $test->succeed(); | |||
self::assertEquals(3, $result); | |||
} | |||
} |