File: TransactionTest.php

package info (click to toggle)
php-doctrine-dbal 4.2.3%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: forky, trixie
  • size: 4,644 kB
  • sloc: php: 46,471; xml: 460; makefile: 22
file content (125 lines) | stat: -rw-r--r-- 3,965 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
<?php

declare(strict_types=1);

namespace Doctrine\DBAL\Tests\Functional;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception\ConnectionLost;
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform;
use Doctrine\DBAL\Platforms\PostgreSQLPlatform;
use Doctrine\DBAL\Schema\Table;
use Doctrine\DBAL\Tests\FunctionalTestCase;
use Doctrine\DBAL\Tests\TestUtil;
use Doctrine\DBAL\Types\Types;

use function func_get_args;
use function restore_error_handler;
use function set_error_handler;

use const E_WARNING;

class TransactionTest extends FunctionalTestCase
{
    public function testBeginTransactionFailure(): void
    {
        $this->expectConnectionLoss(static function (Connection $connection): void {
            $connection->beginTransaction();
        });
    }

    public function testCommitFailure(): void
    {
        $this->connection->beginTransaction();

        $this->expectConnectionLoss(static function (Connection $connection): void {
            $connection->commit();
        });
    }

    public function testRollbackFailure(): void
    {
        $this->connection->beginTransaction();

        $this->expectConnectionLoss(static function (Connection $connection): void {
            $connection->rollBack();
        });
    }

    private function expectConnectionLoss(callable $scenario): void
    {
        $this->killCurrentSession();
        $this->expectException(ConnectionLost::class);

        // prevent the PHPUnit error handler from handling the "MySQL server has gone away" warning
        /** @var callable|null $previous */
        $previous = null;
        $previous = set_error_handler(static function (int $errno) use (&$previous): bool {
            if (($errno & ~E_WARNING) === 0) {
                return true;
            }

            return $previous !== null && $previous(...func_get_args());
        });
        try {
            $scenario($this->connection);
        } finally {
            restore_error_handler();
        }
    }

    private function killCurrentSession(): void
    {
        $this->markConnectionNotReusable();

        $databasePlatform = $this->connection->getDatabasePlatform();

        [$currentProcessQuery, $killProcessStatement] = match (true) {
            $databasePlatform instanceof AbstractMySqlPlatform => [
                'SELECT CONNECTION_ID()',
                'KILL ?',
            ],
            $databasePlatform instanceof PostgreSQLPlatform => [
                'SELECT pg_backend_pid()',
                'SELECT pg_terminate_backend(?)',
            ],
            default => self::markTestSkipped('Unsupported test platform.'),
        };

        $privilegedConnection = TestUtil::getPrivilegedConnection();
        $privilegedConnection->executeStatement(
            $killProcessStatement,
            [$this->connection->executeQuery($currentProcessQuery)->fetchOne()],
        );
    }

    public function testNestedTransactionWalkthrough(): void
    {
        if (! $this->connection->getDatabasePlatform()->supportsSavepoints()) {
            self::markTestIncomplete('Broken when savepoints are not supported.');
        }

        $table = new Table('storage');
        $table->addColumn('test_int', Types::INTEGER);
        $table->setPrimaryKey(['test_int']);

        $this->dropAndCreateTable($table);

        $query = 'SELECT count(test_int) FROM storage';

        self::assertSame('0', (string) $this->connection->fetchOne($query));

        $result = $this->connection->transactional(
            static fn (Connection $connection) => $connection->transactional(
                static function (Connection $connection) use ($query) {
                    $connection->insert('storage', ['test_int' => 1]);

                    return $connection->fetchOne($query);
                },
            ),
        );

        self::assertSame('1', (string) $result);
        self::assertSame('1', (string) $this->connection->fetchOne($query));
    }
}