CouchDbMigrationAdapter::getCurrentRevision()   A
last analyzed

Complexity

Conditions 4
Paths 5

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 4
eloc 8
c 3
b 0
f 0
nc 5
nop 1
dl 0
loc 15
ccs 0
cts 9
cp 0
crap 20
rs 10
1
<?php declare(strict_types=1);
2
/**
3
 * This file is part of the daikon-cqrs/couchdb-adapter project.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace Daikon\CouchDb\Migration;
10
11
use Daikon\CouchDb\Connector\CouchDbConnector;
12
use Daikon\Dbal\Connector\ConnectorInterface;
13
use Daikon\Dbal\Exception\DbalException;
14
use Daikon\Dbal\Migration\MigrationAdapterInterface;
15
use Daikon\Dbal\Migration\MigrationList;
16
use DateTimeImmutable;
17
use GuzzleHttp\Exception\BadResponseException;
18
use GuzzleHttp\Psr7\Request;
19
20
final class CouchDbMigrationAdapter implements MigrationAdapterInterface
21
{
22
    private CouchDbConnector $connector;
23
24
    private array $settings;
25
26
    public function __construct(CouchDbConnector $connector, array $settings = [])
27
    {
28
        $this->connector = $connector;
29
        $this->settings = $settings;
30
    }
31
32
    public function read(string $identifier): MigrationList
33
    {
34
        try {
35
            $response = $this->request($identifier, 'GET');
36
            $rawResponse = json_decode((string)$response->getBody(), true);
37
        } catch (BadResponseException $error) {
38
            /** @psalm-suppress PossiblyNullReference */
39
            if ($error->hasResponse() && $error->getResponse()->getStatusCode() === 404) {
40
                return new MigrationList;
41
            }
42
            throw new DbalException("Failed to read migrations for '$identifier'.");
43
        }
44
45
        return $this->createMigrationList($rawResponse['migrations']);
46
    }
47
48
    public function write(string $identifier, MigrationList $migrationList): void
49
    {
50
        if ($migrationList->isEmpty()) {
51
            return;
52
        }
53
54
        $body = [
55
            'target' => $identifier,
56
            'migrations' => $migrationList->toNative()
57
        ];
58
59
        if ($revision = $this->getCurrentRevision($identifier)) {
60
            $body['_rev'] = $revision;
61
        }
62
63
        $response = $this->request($identifier, 'PUT', $body);
64
        $rawResponse = json_decode((string)$response->getBody(), true);
65
66
        if (!isset($rawResponse['ok']) || !isset($rawResponse['rev'])) {
67
            throw new DbalException("Failed to write migrations for '$identifier'.");
68
        }
69
    }
70
71
    public function getConnector(): ConnectorInterface
72
    {
73
        return $this->connector;
74
    }
75
76
    private function createMigrationList(array $migrationData): MigrationList
77
    {
78
        $migrations = [];
79
        foreach ($migrationData as $migration) {
80
            $migrationClass = $migration['@type'];
81
            /*
82
             * Explicitly not using a service locator to make migration classes here because
83
             * it could enable unusual behaviour.
84
             */
85
            $migrations[] = new $migrationClass(new DateTimeImmutable($migration['executedAt']));
86
        }
87
        return (new MigrationList($migrations))->sortByVersion();
88
    }
89
90
    private function getCurrentRevision(string $identifier): ?string
91
    {
92
        $revision = null;
93
94
        try {
95
            $response = $this->request($identifier, 'HEAD');
96
            $revision = trim(current($response->getHeader('ETag')), '"');
97
        } catch (BadResponseException $error) {
98
            /** @psalm-suppress PossiblyNullReference */
99
            if (!$error->hasResponse() || $error->getResponse()->getStatusCode() !== 404) {
100
                throw new DbalException("Failed to get current migration for '$identifier'.");
101
            }
102
        }
103
104
        return $revision;
105
    }
106
107
    /** @return mixed */
108
    private function request(string $identifier, string $method, array $body = [], array $params = [])
109
    {
110
        $uri = $this->buildUri($identifier, $params);
111
112
        $request = empty($body)
113
            ? new Request($method, $uri)
114
            : new Request($method, $uri, [], json_encode($body));
115
116
        return $this->connector->getConnection()->send($request);
117
    }
118
119
    private function buildUri(string $identifier, array $params = []): string
120
    {
121
        $settings = $this->connector->getSettings();
122
        $uri = sprintf('/%s/%s', $settings['database'], $identifier);
123
        if (!empty($params)) {
124
            $uri .= '?'.http_build_query($params);
125
        }
126
        return str_replace('//', '/', $uri);
127
    }
128
}
129