Test Failed
Push — master ( b78772...2b811b )
by Mr
03:11
created

CouchDbMigrationAdapter::write()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 4
nop 2
1
<?php
2
3
namespace Daikon\CouchDb\Migration;
4
5
use Daikon\CouchDb\Connector\CouchDbConnector;
6
use Daikon\Dbal\Connector\ConnectorInterface;
7
use Daikon\Dbal\Exception\MigrationException;
8
use Daikon\Dbal\Migration\MigrationAdapterInterface;
9
use Daikon\Dbal\Migration\MigrationList;
10
use GuzzleHttp\Exception\RequestException;
11
use GuzzleHttp\Psr7\Request;
12
13
final class CouchDbMigrationAdapter implements MigrationAdapterInterface
14
{
15
    private $connector;
16
17
    private $settings;
18
19
    public function __construct(CouchDbConnector $connector, array $settings = [])
20
    {
21
        $this->connector = $connector;
22
        $this->settings = $settings;
23
    }
24
25
    public function read(string $identifier): MigrationList
26
    {
27
        try {
28
            $response = $this->request($identifier, 'GET');
29
            $rawResponse = json_decode($response->getBody(), true);
30
        } catch (RequestException $error) {
31
            if ($error->hasResponse() && $error->getResponse()->getStatusCode() === 404) {
32
                return new MigrationList;
33
            } else {
34
                throw new MigrationException($error->getMessage());
35
            }
36
        }
37
        return $this->createMigrationList($rawResponse['migrations']);
38
    }
39
40
    public function write(string $identifier, MigrationList $executedMigrations): void
41
    {
42
        $body = [
43
            'target' => $identifier,
44
            'migrations' => $executedMigrations->toArray()
45
        ];
46
47
        if ($revision = $this->getCurrentRevision($identifier)) {
48
            $body['_rev'] = $revision;
49
        }
50
51
        $response = $this->request($identifier, 'PUT', $body);
52
        $rawResponse = json_decode($response->getBody(), true);
53
54
        if (!isset($rawResponse['ok']) || !isset($rawResponse['rev'])) {
55
            throw new MigrationException('Failed to write migration data for '.$identifier);
56
        }
57
    }
58
59
    public function getConnector(): ConnectorInterface
60
    {
61
        return $this->connector;
62
    }
63
64
    private function createMigrationList(array $migrationData)
65
    {
66
        $migrations = [];
67
        foreach ($migrationData as $migration) {
68
            $migrationClass = $migration['@type'];
69
            /*
70
             * Explicitly not using a service locator to make migration classes here because
71
             * it could enable unusual behaviour.
72
             */
73
            $migrations[] = new $migrationClass(new \DateTimeImmutable($migration['executedAt']));
74
        }
75
        return new MigrationList($migrations);
76
    }
77
78
    private function request(string $identifier, string $method, array $body = [], array $params = [])
79
    {
80
        $requestPath = $this->buildRequestPath($identifier, $params);
81
82
        if (empty($body)) {
83
            $request = new Request($method, $requestPath, ['Accept' => 'application/json']);
84
        } else {
85
            $request = new Request(
86
                $method,
87
                $requestPath,
88
                ['Accept' => 'application/json', 'Content-Type' => 'application/json'],
89
                json_encode($body)
90
            );
91
        }
92
93
        return $this->connector->getConnection()->send($request);
94
    }
95
96
    private function getCurrentRevision(string $identifier): ?string
97
    {
98
        try {
99
            $response = $this->request($identifier, 'HEAD');
100
            $revision = trim(current($response->getHeader('ETag')), '"');
101
        } catch (RequestException $error) {
102
            if (!$error->hasResponse() || $error->getResponse()->getStatusCode() !== 404) {
103
                throw new MigrationException($error->getMessage());
104
            }
105
        }
106
107
        return $revision ?? null;
108
    }
109
110
    private function buildRequestPath(string $identifier, array $params = [])
111
    {
112
        $settings = $this->connector->getSettings();
113
        $requestPath = sprintf('/%s/%s', $settings['database'], $identifier);
114
        if (!empty($params)) {
115
            $requestPath .= '?'.http_build_query($params);
116
        }
117
118
        return str_replace('//', '/', $requestPath);
119
    }
120
}
121