Completed
Push — master ( 6a2cd6...f92254 )
by Sascha-Oliver
02:35 queued 48s
created

PhraseApp   B

Complexity

Total Complexity 23

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 23
lcom 1
cbo 16
dl 0
loc 189
ccs 0
cts 123
cp 0
rs 8.4614
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 13 1
A get() 0 13 3
A create() 0 14 1
A update() 0 14 3
A delete() 0 15 3
B export() 0 33 5
B import() 0 38 5
A getLocaleId() 0 8 2
1
<?php
2
3
namespace Translation\PlatformAdapter\PhraseApp;
4
5
use FAPI\PhraseApp\Model\Key\KeyCreated;
6
use FAPI\PhraseApp\Model\Key\KeySearchResults;
7
use FAPI\PhraseApp\Model\Translation\Index;
8
use FAPI\PhraseApp\PhraseAppClient;
9
use Symfony\Component\Translation\MessageCatalogueInterface;
10
use Translation\Common\Exception\StorageException;
11
use Translation\Common\Model\Message;
12
use Translation\Common\Storage;
13
use Translation\Common\TransferableStorage;
14
use Translation\PlatformAdapter\PhraseApp\Bridge\Symfony\XliffConverter;
15
16
/**
17
 * @author Sascha-Oliver Prolic <[email protected]>
18
 */
19
class PhraseApp implements Storage, TransferableStorage
20
{
21
    /**
22
     * @var PhraseAppClient
23
     */
24
    private $client;
25
26
    /**
27
     * @var string
28
     */
29
    private $projectId;
30
31
    /**
32
     * @var array
33
     */
34
    private $localeToIdMapping;
35
36
    /**
37
     * @var array
38
     */
39
    private $domains;
40
41
    /**
42
     * @var string|null
43
     */
44
    private $defaultLocale;
45
46
    public function __construct(
47
        PhraseAppClient $client,
48
        string $projectId,
49
        array $localeToIdMapping,
50
        array $domains,
51
        string $defaultLocale = null
52
    ) {
53
        $this->client = $client;
54
        $this->projectId = $projectId;
55
        $this->localeToIdMapping = $localeToIdMapping;
56
        $this->domains = $domains;
57
        $this->defaultLocale = $defaultLocale;
58
    }
59
60
    public function get($locale, $domain, $key)
61
    {
62
        /* @var Index $index */
63
        $index = $this->client->translation()->indexLocale($this->projectId, $this->getLocaleId($locale), [
64
            'tags' => $domain
65
        ]);
66
67
        foreach ($index->getTranslations() as $translation) {
68
            if ($translation->getKey()->getName() === $domain.'::'.$key) {
69
                return new Message($key, $domain, $locale, $translation->getContent(), []);
70
            }
71
        }
72
    }
73
74
    public function create(Message $message)
75
    {
76
        /* @var KeyCreated $keyCreated */
77
        $keyCreated = $this->client->key()->create($this->projectId, $message->getDomain().'::'.$message->getKey(), [
78
            'tags' => $message->getDomain(),
79
        ]);
80
81
        $this->client->translation()->create(
82
            $this->projectId,
83
            $this->getLocaleId($message->getLocale()),
84
            $keyCreated->getId(),
85
            $message->getTranslation()
86
        );
87
    }
88
89
    public function update(Message $message)
90
    {
91
        /* @var Index $index */
92
        $index = $this->client->translation()->indexLocale($this->projectId, $this->getLocaleId($message->getLocale()), [
93
            'tags' => $message->getDomain()
94
        ]);
95
96
        foreach ($index->getTranslations() as $translation) {
97
            if ($translation->getKey() === $message->getDomain().'::'.$message->getKey()) {
98
                $this->client->translation()->update($this->projectId, $translation->getId(), $message->getTranslation());
99
                break;
100
            }
101
        }
102
    }
103
104
    public function delete($locale, $domain, $key)
105
    {
106
        /* @var KeySearchResults $results */
107
        $results = $this->client->key()->search($this->projectId, $this->getLocaleId($locale), [
108
            'tags' => $domain,
109
            'name' => $domain.'::'.$key
110
        ]);
111
112
        foreach ($results->getSearchResults() as $searchResult) {
113
            if ($searchResult->getName() === $domain.'::'.$key) {
114
                $this->client->key()->delete($this->projectId, $searchResult->getId());
115
                break;
116
            }
117
        }
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function export(MessageCatalogueInterface $catalogue)
124
    {
125
        $locale = $catalogue->getLocale();
126
        $localeId = $this->getLocaleId($locale);
127
128
        foreach ($this->domains as $domain) {
129
            try {
130
                $response = $this->client->locale()->download($this->projectId, $localeId, 'symfony_xliff', [
131
                    'tag' => $domain
132
                ]);
133
            } catch (\Throwable $e) {
134
                throw new StorageException($e->getMessage());
135
            }
136
137
            try {
138
                $newCatalogue = XliffConverter::contentToCatalogue($response, $locale, $domain);
0 ignored issues
show
Bug introduced by
It seems like $response defined by $this->client->locale()-...rray('tag' => $domain)) on line 130 can also be of type object<Psr\Http\Message\ResponseInterface>; however, Translation\PlatformAdap...r::contentToCatalogue() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
139
140
                $messages = [];
141
142
                foreach ($newCatalogue->all($domain) as $message => $translation) {
143
                    $messages[substr($message, strlen($domain) + 2)] = $translation;
144
                }
145
146
                $newCatalogue->replace($messages, $domain);
147
148
                $catalogue->addCatalogue($newCatalogue);
149
            } catch (\Throwable $e) {
150
                // ignore empty translation files
151
            }
152
        }
153
154
        return $catalogue;
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160
    public function import(MessageCatalogueInterface $catalogue)
161
    {
162
        $locale = $catalogue->getLocale();
163
        $localeId = $this->getLocaleId($locale);
164
165
        foreach ($this->domains as $domain) {
166
            $messages = [];
167
168
            foreach ($catalogue->all($domain) as $message => $translation) {
169
                $messages[$domain . '::' . $message] = $translation;
170
            }
171
172
            $catalogue->replace($messages, $domain);
173
174
            if ($this->defaultLocale) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->defaultLocale of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
175
                $options = ['default_locale' => $this->defaultLocale];
176
            } else {
177
                $options = [];
178
            }
179
180
            $data = XliffConverter::catalogueToContent($catalogue, $domain, $options);
0 ignored issues
show
Compatibility introduced by
$catalogue of type object<Symfony\Component...sageCatalogueInterface> is not a sub-type of object<Symfony\Component...ation\MessageCatalogue>. It seems like you assume a concrete implementation of the interface Symfony\Component\Transl...ssageCatalogueInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
181
182
            $file = sys_get_temp_dir() . '/' . $domain . '.' . $locale . '.xlf';
183
184
            try {
185
                file_put_contents($file, $data);
186
187
                $this->client->upload()->upload($this->projectId, 'symfony_xliff', $file, [
188
                    'locale_id' => $localeId,
189
                    'tags' => $domain,
190
                ]);
191
            } catch (\Throwable $e) {
192
                throw new StorageException($e->getMessage());
193
            } finally {
194
                unlink($file);
195
            }
196
        }
197
    }
198
199
    private function getLocaleId(string $locale): string
200
    {
201
        if (isset($this->localeToIdMapping[$locale])) {
202
            return $this->localeToIdMapping[$locale];
203
        }
204
205
        throw new StorageException(sprintf('Id for locale "%s" has not been configured.', $locale));
206
    }
207
}
208