Completed
Push — v4.1 ( f3a0c7...588d2a )
by Masiukevich
07:33
created

DoctrineDBALAdapter::execute()   A

Complexity

Conditions 3
Paths 7

Size

Total Lines 27
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 11
c 1
b 0
f 0
nc 7
nop 2
dl 0
loc 27
ccs 8
cts 8
cp 1
crap 3
rs 9.9
1
<?php
2
3
/**
4
 * SQL databases adapters implementation.
5
 *
6
 * @author  Maksim Masiukevich <[email protected]>
7
 * @license MIT
8
 * @license https://opensource.org/licenses/MIT
9
 */
10
11
declare(strict_types = 1);
12
13
namespace ServiceBus\Storage\Sql\DoctrineDBAL;
14
15
use function Amp\call;
16
use Amp\Failure;
17
use Amp\Promise;
18
use Amp\Success;
19
use Doctrine\DBAL\Connection;
20
use Doctrine\DBAL\DriverManager;
21
use Psr\Log\LoggerInterface;
22
use Psr\Log\NullLogger;
23
use ServiceBus\Storage\Common\DatabaseAdapter;
24
use ServiceBus\Storage\Common\Exceptions\InvalidConfigurationOptions;
25
use ServiceBus\Storage\Common\StorageConfiguration;
26
27
/**
28
 * DoctrineDBAL adapter.
29
 *
30
 * Designed primarily for testing. Please do not use this adapter in your code
31
 */
32
final class DoctrineDBALAdapter implements DatabaseAdapter
33
{
34
    /** @var StorageConfiguration StorageConfiguration */
35
    private $configuration;
36
37
    /** @var Connection|null */
38
    private $connection = null;
39
40
    /** @var LoggerInterface */
41
    private $logger;
42
43 6
    public function __construct(StorageConfiguration $configuration, LoggerInterface $logger = null)
44
    {
45 6
        $this->configuration = $configuration;
46 6
        $this->logger        = $logger ?? new NullLogger();
47 6
    }
48
49
    /**
50
     * {@inheritdoc}
51
     *
52
     * @psalm-suppress MixedReturnTypeCoercion
53
     */
54 25
    public function execute(string $queryString, array $parameters = []): Promise
55
    {
56 25
        $this->logger->debug($queryString, $parameters);
57
58
        try
59
        {
60 25
            $statement = $this->connection()->prepare($queryString);
61 25
            $isSuccess = $statement->execute($parameters);
62
63 25
            if ($isSuccess === false)
64
            {
65
                // @codeCoverageIgnoreStart
66
                /** @var array{0:string, 1:int, 2:string} $errorInfo */
67
                $errorInfo = $this->connection()->errorInfo();
68
69
                /** @var string $message Driver-specific error message */
70
                $message = $errorInfo[2];
71
72
                throw new \RuntimeException($message);
73
                // @codeCoverageIgnoreEnd
74
            }
75
76 25
            return new Success(new DoctrineDBALResultSet($this->connection(), $statement));
77
        }
78 4
        catch (\Throwable $throwable)
79
        {
80 4
            return new Failure(adaptDbalThrowable($throwable));
81
        }
82
    }
83
84
    /**
85
     * {@inheritdoc}
86
     *
87
     * @psalm-suppress MixedReturnTypeCoercion
88
     */
89 2
    public function transactional(callable $function): Promise
90
    {
91 2
        return call(
92
            function () use ($function): \Generator
93
            {
94
                /** @var \ServiceBus\Storage\Common\Transaction $transaction */
95 2
                $transaction = yield $this->transaction();
96
97
                try
98
                {
99
                    /** @var \Generator<null> $generator */
100 2
                    $generator = $function($transaction);
101
102 2
                    yield from $generator;
103
104 1
                    yield $transaction->commit();
105
                }
106 1
                catch (\Throwable $throwable)
107
                {
108 1
                    yield $transaction->rollback();
109
110 1
                    throw $throwable;
111
                }
112
                finally
113 1
                {
114 2
                    unset($transaction);
115
                }
116 2
            }
117
        );
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     *
123
     * @psalm-suppress MixedReturnTypeCoercion
124
     */
125 5
    public function transaction(): Promise
126
    {
127
        try
128
        {
129 5
            $this->logger->debug('START TRANSACTION');
130
131 5
            $this->connection()->beginTransaction();
132
133 5
            return new Success(new DoctrineDBALTransaction($this->connection(), $this->logger));
134
        }
135
        // @codeCoverageIgnoreStart
136
        catch (\Throwable $exception)
137
        {
138
            return new Failure(adaptDbalThrowable($exception));
139
        }
140
        // @codeCoverageIgnoreEnd
141
    }
142
143
    /**
144
     * {@inheritdoc}
145
     */
146 2
    public function unescapeBinary($payload): string
147
    {
148
        /** @var resource|string $payload */
149 2
        if (\is_resource($payload) === true)
150
        {
151
            $result = \stream_get_contents($payload, -1, 0);
152
153
            if ($result !== false)
154
            {
155
                return $result;
156
            }
157
        }
158
159 2
        return (string) $payload;
160
    }
161
162
    /**
163
     * Get connection instance.
164
     *
165
     * @throws \ServiceBus\Storage\Common\Exceptions\InvalidConfigurationOptions
166
     */
167 25
    private function connection(): Connection
168
    {
169 25
        if ($this->connection === null)
170
        {
171
            try
172
            {
173 6
                $this->connection = DriverManager::getConnection(['url' => $this->configuration->originalDSN]);
174
            }
175 1
            catch (\Throwable $throwable)
176
            {
177 1
                throw new InvalidConfigurationOptions($throwable->getMessage(), (int) $throwable->getCode(), $throwable);
178
            }
179
        }
180
181 25
        return $this->connection;
182
    }
183
}
184