Completed
Push — master ( 59ebde...3174b5 )
by ReliQ
01:08
created

Publisher::publish()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 41
rs 9.264
c 0
b 0
f 0
cc 4
nc 5
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace ReliqArts\Docweaver\Service\Product;
6
7
use ReliqArts\Docweaver\Contract\Exception;
8
use ReliqArts\Docweaver\Contract\Filesystem;
9
use ReliqArts\Docweaver\Contract\Logger;
10
use ReliqArts\Docweaver\Contract\Product\Publisher as PublisherContract;
11
use ReliqArts\Docweaver\Contract\VCSCommandRunner;
12
use ReliqArts\Docweaver\Exception\Product\InvalidAssetDirectory;
13
use ReliqArts\Docweaver\Exception\Product\PublicationFailed;
14
use ReliqArts\Docweaver\Model\Product;
15
use ReliqArts\Docweaver\Result;
16
use ReliqArts\Docweaver\Service\Publisher as BasePublisher;
17
use Symfony\Component\Process\Exception\ProcessFailedException;
18
19
/**
20
 * Publishes and updates product versions.
21
 */
22
final class Publisher extends BasePublisher implements PublisherContract
23
{
24
    /**
25
     * @var VCSCommandRunner
26
     */
27
    private VCSCommandRunner $vcsCommandRunner;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
28
29
    /**
30
     * Publisher constructor.
31
     */
32
    public function __construct(Filesystem $filesystem, Logger $logger, VCSCommandRunner $vcsCommandRunner)
33
    {
34
        parent::__construct($filesystem, $logger);
35
36
        $this->vcsCommandRunner = $vcsCommandRunner;
37
    }
38
39
    /**
40
     * @throws Exception
41
     */
42
    public function publish(Product $product, string $source): Result
43
    {
44
        $result = new Result();
45
        $versions = [Product::VERSION_MASTER];
46
        $versionsPublished = [];
47
        $versionsUpdated = [];
48
        $productDirectory = $product->getDirectory();
49
        $masterDirectory = $product->getMasterDirectory();
50
51
        $this->setExecutionStartTime();
52
53
        if (!$this->readyResourceDirectory($productDirectory)) {
54
            return $result->setError(sprintf('Product directory %s is not writable.', $productDirectory))
55
                ->setExtra((object)['executionTime' => $this->getExecutionTime()]);
56
        }
57
58
        if (!$this->filesystem->isDirectory($masterDirectory)) {
59
            $this->publishVersion($product, $source, Product::VERSION_MASTER);
60
            $versionsPublished[] = Product::VERSION_MASTER;
61
        } else {
62
            $this->updateVersion($product, Product::VERSION_MASTER);
63
            $versionsUpdated[] = Product::VERSION_MASTER;
64
        }
65
66
        $tagResult = $this->publishTags($product, $source);
67
        $tagData = $tagResult->getExtra();
68
        $versions = array_merge($versions, $tagData->tags ?? []);
69
        $versionsPublished = array_merge($versionsPublished, $tagData->tagsPublished ?? []);
70
71
        if ($result->isSuccess()) {
72
            $result = $result->setMessage(sprintf('%s was successfully published.', $product->getName()))
73
                ->setExtra((object)[
74
                    'versions' => $versions,
75
                    'versionsPublished' => $versionsPublished,
76
                    'versionsUpdated' => $versionsUpdated,
77
                    'executionTime' => $this->getExecutionTime(),
78
                ]);
79
        }
80
81
        return $result;
82
    }
83
84
    public function update(Product $product): Result
85
    {
86
        $result = new Result();
87
        $publishedVersions = array_keys($product->getVersions());
88
        $availableTags = $this->listAvailableProductTags($product);
89
        $source = $this->getProductSource($product);
90
        $branches = array_diff($publishedVersions, $availableTags);
91
        $unpublishedTags = array_diff($availableTags, $publishedVersions);
92
        $versions = array_unique(array_merge($publishedVersions, $availableTags));
93
        $versionsPublished = [];
94
        $versionsUpdated = [];
95
96
        foreach ($branches as $version) {
97
            try {
98
                $this->updateVersion($product, $version);
99
                $versionsUpdated[] = $version;
100
            } catch (Exception $exception) {
101
                $this->logger->info($exception->getMessage(), [$exception]);
102
103
                $result = $result->addMessage($exception->getMessage());
104
            }
105
        }
106
107
        $tagResult = $this->publishTags($product, $source, $unpublishedTags);
108
        $tagData = $tagResult->getExtra();
109
        $versionsPublished = array_merge($versionsPublished, $tagData->tagsPublished ?? []);
110
111
        if ($result->isSuccess()) {
112
            $result = $result->setMessage(sprintf('%s was successfully updated.', $product->getName()));
113
        }
114
115
        return $result->setExtra((object)[
116
            'versions' => $versions,
117
            'versionsPublished' => $versionsPublished,
118
            'versionsUpdated' => $versionsUpdated,
119
        ]);
120
    }
121
122
    /**
123
     * @throws Exception
124
     */
125
    private function publishVersion(Product $product, string $source, string $version): bool
126
    {
127
        try {
128
            $this->vcsCommandRunner->clone($source, $version, $product->getDirectory());
129
        } catch (ProcessFailedException $e) {
130
            throw PublicationFailed::forProductVersion($product, $version);
131
        }
132
133
        $this->publishProductAssets($product, $version);
134
135
        return true;
136
    }
137
138
    /**
139
     * @throws Exception
140
     */
141
    private function updateVersion(Product $product, string $version): bool
142
    {
143
        try {
144
            $this->vcsCommandRunner->pull(sprintf('%s/%s', $product->getDirectory(), $version));
145
        } catch (ProcessFailedException $e) {
146
            throw new PublicationFailed(sprintf('Failed to update version `%s` of product `%s`.', $version, $product->getName()));
147
        }
148
149
        $this->publishProductAssets($product, $version);
150
151
        return true;
152
    }
153
154
    private function publishTags(Product $product, string $source, array $tags = []): Result
155
    {
156
        $result = new Result();
157
        $tagsPublished = [];
158
159
        try {
160
            $tags = empty($tags) ? $this->listAvailableProductTags($product) : $tags;
161
162
            foreach ($tags as $tag) {
163
                $tagDirectory = sprintf('%s/%s', $product->getDirectory(), $tag);
164
165
                if (!$this->filesystem->isDirectory($tagDirectory)) {
166
                    $this->publishVersion($product, $source, $tag);
167
                    $result = $result->addMessage(sprintf('Successfully published tag `%s`.', $tag));
168
                    $tagsPublished[] = $tag;
169
                } else {
170
                    $message = sprintf('Version `%s` already exists.', $tag);
171
                    $result = $result->addMessage($message);
172
                }
173
            }
174
        } catch (Exception $e) {
175
            $errorMessage = $e->getMessage();
176
177
            return $result->setMessage($errorMessage)->setError($errorMessage);
178
        }
179
180
        return $result->setExtra((object)[
181
            'tags' => $tags,
182
            'tagsPublished' => $tagsPublished,
183
        ]);
184
    }
185
186
    private function publishProductAssets(Product $product, string $version): void
187
    {
188
        try {
189
            $product->publishAssets($version);
190
        } catch (Exception $exception) {
191
            if ($exception instanceof InvalidAssetDirectory) {
192
                $this->logger->info($exception->getMessage());
193
            } else {
194
                $this->logger->error($exception->getMessage());
195
            }
196
        }
197
    }
198
199
    /**
200
     * @throws ProcessFailedException
201
     */
202
    private function listAvailableProductTags(Product $product): array
203
    {
204
        return $this->vcsCommandRunner->listTags($product->getMasterDirectory());
205
    }
206
207
    /**
208
     * @throws ProcessFailedException
209
     */
210
    private function getProductSource(Product $product): string
211
    {
212
        return $this->vcsCommandRunner->getRemoteUrl($product->getMasterDirectory());
213
    }
214
}
215