Completed
Push — master ( f92254...2ed046 )
by Sascha-Oliver
04:01
created

PhraseApp::delete()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 15
ccs 0
cts 13
cp 0
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 3
crap 12
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
        $localeId = $this->getLocaleId($message->getLocale());
77
78
        /* @var KeySearchResults $result */
79
        $result = $this->client->key()->search($this->projectId, ['tags' => $message->getDomain()]);
0 ignored issues
show
Documentation introduced by
array('tags' => $message->getDomain()) is of type array<string,string,{"tags":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
80
81
        foreach ($result->getSearchResults() as $key) {
82
            if ($key->getName() === $message->getDomain().'::'.$message->getKey()) {
83
                /* @var Index $index */
84
                $index = $this->client->translation()->indexKey($this->projectId, $key->getId(), ['tags' => $message->getDomain()]);
85
86
                foreach ($index->getTranslations() as $translation) {
87
                    if ($translation->getLocale()->getId() !== $localeId) {
88
                        continue;
89
                    }
90
91
                    if ($translation->getContent() !== $message->getTranslation()) {
92
                        $this->client->translation()->update($this->projectId, $translation->getId(), $message->getTranslation());
93
                        return;
94
                    }
95
                }
96
97
                $this->client->translation()->create($this->projectId, $localeId, $key->getId(), $message->getTranslation());
98
                return;
99
            }
100
        }
101
102
        /* @var KeyCreated $keyCreated */
103
        $keyCreated = $this->client->key()->create($this->projectId, $message->getDomain().'::'.$message->getKey(), [
104
            'tags' => $message->getDomain(),
105
        ]);
106
107
        $this->client->translation()->create(
108
            $this->projectId,
109
            $this->getLocaleId($message->getLocale()),
110
            $keyCreated->getId(),
111
            $message->getTranslation()
112
        );
113
    }
114
115
    public function update(Message $message)
116
    {
117
        /* @var Index $index */
118
        $index = $this->client->translation()->indexLocale($this->projectId, $this->getLocaleId($message->getLocale()), [
119
            'tags' => $message->getDomain()
120
        ]);
121
122
        foreach ($index->getTranslations() as $translation) {
123
            if ($translation->getKey() === $message->getDomain().'::'.$message->getKey()) {
124
                $this->client->translation()->update($this->projectId, $translation->getId(), $message->getTranslation());
125
                break;
126
            }
127
        }
128
    }
129
130
    public function delete($locale, $domain, $key)
131
    {
132
        /* @var KeySearchResults $results */
133
        $results = $this->client->key()->search($this->projectId, $this->getLocaleId($locale), [
134
            'tags' => $domain,
135
            'name' => $domain.'::'.$key
136
        ]);
137
138
        foreach ($results->getSearchResults() as $searchResult) {
139
            if ($searchResult->getName() === $domain.'::'.$key) {
140
                $this->client->key()->delete($this->projectId, $searchResult->getId());
141
                break;
142
            }
143
        }
144
    }
145
146
    /**
147
     * {@inheritdoc}
148
     */
149
    public function export(MessageCatalogueInterface $catalogue)
150
    {
151
        $locale = $catalogue->getLocale();
152
        $localeId = $this->getLocaleId($locale);
153
154
        foreach ($this->domains as $domain) {
155
            try {
156
                $response = $this->client->locale()->download($this->projectId, $localeId, 'symfony_xliff', [
157
                    'tag' => $domain
158
                ]);
159
            } catch (\Throwable $e) {
160
                throw new StorageException($e->getMessage());
161
            }
162
163
            try {
164
                $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 156 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...
165
166
                $messages = [];
167
168
                foreach ($newCatalogue->all($domain) as $message => $translation) {
169
                    $messages[substr($message, strlen($domain) + 2)] = $translation;
170
                }
171
172
                $newCatalogue->replace($messages, $domain);
173
174
                $catalogue->addCatalogue($newCatalogue);
175
            } catch (\Throwable $e) {
176
                // ignore empty translation files
177
            }
178
        }
179
180
        return $catalogue;
181
    }
182
183
    /**
184
     * {@inheritdoc}
185
     */
186
    public function import(MessageCatalogueInterface $catalogue)
187
    {
188
        $locale = $catalogue->getLocale();
189
        $localeId = $this->getLocaleId($locale);
190
191
        foreach ($this->domains as $domain) {
192
            $messages = [];
193
194
            foreach ($catalogue->all($domain) as $message => $translation) {
195
                $messages[$domain . '::' . $message] = $translation;
196
            }
197
198
            $catalogue->replace($messages, $domain);
199
200
            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...
201
                $options = ['default_locale' => $this->defaultLocale];
202
            } else {
203
                $options = [];
204
            }
205
206
            $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...
207
208
            $file = sys_get_temp_dir() . '/' . $domain . '.' . $locale . '.xlf';
209
210
            try {
211
                file_put_contents($file, $data);
212
213
                $this->client->upload()->upload($this->projectId, 'symfony_xliff', $file, [
214
                    'locale_id' => $localeId,
215
                    'tags' => $domain,
216
                ]);
217
            } catch (\Throwable $e) {
218
                throw new StorageException($e->getMessage());
219
            } finally {
220
                unlink($file);
221
            }
222
        }
223
    }
224
225
    private function getLocaleId(string $locale): string
226
    {
227
        if (isset($this->localeToIdMapping[$locale])) {
228
            return $this->localeToIdMapping[$locale];
229
        }
230
231
        throw new StorageException(sprintf('Id for locale "%s" has not been configured.', $locale));
232
    }
233
}
234