Completed
Push — master ( c9c04e...359b8c )
by Tobias
19:00
created

Importer::extractToCatalogues()   C

Complexity

Conditions 11
Paths 34

Size

Total Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 132

Importance

Changes 0
Metric Value
dl 0
loc 53
ccs 0
cts 44
cp 0
rs 6.8787
c 0
b 0
f 0
cc 11
nc 34
nop 3
crap 132

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the PHP Translation package.
5
 *
6
 * (c) PHP Translation team <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Translation\Bundle\Service;
13
14
use Symfony\Component\Finder\Finder;
15
use Symfony\Component\Translation\MessageCatalogue;
16
use Translation\Bundle\Model\ImportResult;
17
use Translation\Bundle\Model\Metadata;
18
use Translation\Bundle\Twig\Visitor\DefaultApplyingNodeVisitor;
19
use Translation\Bundle\Twig\Visitor\RemovingNodeVisitor;
20
use Translation\Extractor\Extractor;
21
use Translation\Extractor\Model\SourceCollection;
22
use Translation\Extractor\Model\SourceLocation;
23
use Translation\Bundle\Catalogue\Operation\ReplaceOperation;
24
25
/**
26
 * Use extractors to import translations to message catalogues.
27
 *
28
 * @author Tobias Nyholm <[email protected]>
29
 */
30
final class Importer
31
{
32
    /**
33
     * @var Extractor
34
     */
35
    private $extractor;
36
37
    /**
38
     * @var array
39
     */
40
    private $config;
41
42
    /**
43
     * @var \Twig_Environment
44
     */
45
    private $twig;
46
47
    /**
48
     * @var string
49
     */
50
    private $defaultLocale;
51
52
    /**
53
     * @param Extractor         $extractor
54
     * @param \Twig_Environment $twig
55
     * @param string            $defaultLocale
56
     */
57
    public function __construct(Extractor $extractor, \Twig_Environment $twig, $defaultLocale)
58
    {
59
        $this->extractor = $extractor;
60
        $this->twig = $twig;
61
        $this->defaultLocale = $defaultLocale;
62
    }
63
64
    /**
65
     * @param Finder             $finder
66
     * @param MessageCatalogue[] $catalogues
67
     * @param array              $config     {
68
     *
69
     *     @var array $blacklist_domains Blacklist the domains we should exclude. Cannot be used with whitelist.
70
     *     @var array $whitelist_domains Whitelist the domains we should include. Cannot be used with blacklist.
71
     *     @var string $project_root The project root will be removed from the source location.
72
     * }
73
     *
74
     * @return ImportResult
75
     */
76
    public function extractToCatalogues(Finder $finder, array $catalogues, array $config = [])
77
    {
78
        $this->processConfig($config);
79
        $this->disableTwigVisitors();
80
        $sourceCollection = $this->extractor->extract($finder);
81
        $results = [];
82
        foreach ($catalogues as $catalogue) {
83
            $target = new MessageCatalogue($catalogue->getLocale());
84
            $this->convertSourceLocationsToMessages($target, $sourceCollection);
85
86
            // Remove all SourceLocation and State form catalogue.
87
            foreach ($catalogue->getDomains() as $domain) {
88
                foreach ($catalogue->all($domain) as $key => $translation) {
89
                    $meta = $this->getMetadata($catalogue, $key, $domain);
90
                    $meta->removeAllInCategory('file-source');
91
                    $meta->removeAllInCategory('state');
92
                    $this->setMetadata($catalogue, $key, $domain, $meta);
93
                }
94
            }
95
96
            $merge = new ReplaceOperation($target, $catalogue);
97
            $result = $merge->getResult();
98
            $domains = $merge->getDomains();
99
100
            // Mark new messages as new/obsolete
101
            foreach ($domains as $domain) {
102
                foreach ($merge->getNewMessages($domain) as $key => $translation) {
103
                    $meta = $this->getMetadata($result, $key, $domain);
0 ignored issues
show
Compatibility introduced by
$result 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...
104
                    $meta->setState('new');
105
                    $this->setMetadata($result, $key, $domain, $meta);
0 ignored issues
show
Compatibility introduced by
$result 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...
106
107
                    // Add custom translations that we found in the source
108
                    if (null === $translation) {
109
                        if (null !== $newTranslation = $meta->getTranslation()) {
110
                            $result->set($key, $newTranslation, $domain);
111
                            // We do not want "translation" key stored anywhere.
112
                            $meta->removeAllInCategory('translation');
113
                        } elseif (null !== $newTranslation = $meta->getDesc() && $catalogue->getLocale() === $this->defaultLocale) {
0 ignored issues
show
Comprehensibility introduced by
Consider adding parentheses for clarity. Current Interpretation: $newTranslation = ($meta...= $this->defaultLocale), Probably Intended Meaning: ($newTranslation = $meta...== $this->defaultLocale
Loading history...
114
                            $result->set($key, $newTranslation, $domain);
0 ignored issues
show
Documentation introduced by
$newTranslation is of type boolean, 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...
115
                        }
116
                    }
117
                }
118
                foreach ($merge->getObsoleteMessages($domain) as $key => $translation) {
119
                    $meta = $this->getMetadata($result, $key, $domain);
0 ignored issues
show
Compatibility introduced by
$result 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...
120
                    $meta->setState('obsolete');
121
                    $this->setMetadata($result, $key, $domain, $meta);
0 ignored issues
show
Compatibility introduced by
$result 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...
122
                }
123
            }
124
            $results[] = $result;
125
        }
126
127
        return new ImportResult($results, $sourceCollection->getErrors());
128
    }
129
130
    /**
131
     * @param MessageCatalogue $catalogue
132
     * @param SourceCollection $collection
133
     */
134
    private function convertSourceLocationsToMessages(MessageCatalogue $catalogue, SourceCollection $collection)
135
    {
136
        /** @var SourceLocation $sourceLocation */
137
        foreach ($collection as $sourceLocation) {
138
            $context = $sourceLocation->getContext();
139
            $domain = isset($context['domain']) ? $context['domain'] : 'messages';
140
            // Check with white/black list
141
            if (!$this->isValidDomain($domain)) {
142
                continue;
143
            }
144
145
            $key = $sourceLocation->getMessage();
146
            $catalogue->set($key, null, $domain);
147
            $trimLength = 1 + strlen($this->config['project_root']);
148
149
            $meta = $this->getMetadata($catalogue, $key, $domain);
150
            $meta->addCategory('file-source', sprintf('%s:%s', substr($sourceLocation->getPath(), $trimLength), $sourceLocation->getLine()));
151
            if (isset($sourceLocation->getContext()['desc'])) {
152
                $meta->addCategory('desc', $sourceLocation->getContext()['desc']);
153
            }
154
            if (isset($sourceLocation->getContext()['translation'])) {
155
                $meta->addCategory('translation', $sourceLocation->getContext()['translation']);
156
            }
157
            $this->setMetadata($catalogue, $key, $domain, $meta);
158
        }
159
    }
160
161
    /**
162
     * @param MessageCatalogue $catalogue
163
     * @param $key
164
     * @param $domain
165
     *
166
     * @return Metadata
167
     */
168
    private function getMetadata(MessageCatalogue $catalogue, $key, $domain)
169
    {
170
        return new Metadata($catalogue->getMetadata($key, $domain));
171
    }
172
173
    /**
174
     * @param MessageCatalogue $catalogue
175
     * @param $key
176
     * @param $domain
177
     * @param Metadata $metadata
178
     */
179
    private function setMetadata(MessageCatalogue $catalogue, $key, $domain, Metadata $metadata)
180
    {
181
        $catalogue->setMetadata($key, $metadata->toArray(), $domain);
182
    }
183
184
    /**
185
     * @param string $domain
186
     *
187
     * @return bool
188
     */
189
    private function isValidDomain($domain)
190
    {
191 View Code Duplication
        if (!empty($this->config['blacklist_domains']) && in_array($domain, $this->config['blacklist_domains'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
192
            return false;
193
        }
194 View Code Duplication
        if (!empty($this->config['whitelist_domains']) && !in_array($domain, $this->config['whitelist_domains'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
195
            return false;
196
        }
197
198
        return true;
199
    }
200
201
    /**
202
     * Make sure the configuration is valid.
203
     *
204
     * @param array $config
205
     */
206
    private function processConfig($config)
207
    {
208
        $default = [
209
            'project_root' => '',
210
            'blacklist_domains' => [],
211
            'whitelist_domains' => [],
212
        ];
213
214
        $config = array_merge($default, $config);
215
216 View Code Duplication
        if (!empty($config['blacklist_domains']) && !empty($config['whitelist_domains'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
217
            throw new \InvalidArgumentException('Cannot use "blacklist_domains" and "whitelist_domains" at the same time');
218
        }
219
220 View Code Duplication
        if (!empty($config['blacklist_domains']) && !is_array($config['blacklist_domains'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
221
            throw new \InvalidArgumentException('Config parameter "blacklist_domains" must be an array');
222
        }
223
224 View Code Duplication
        if (!empty($config['whitelist_domains']) && !is_array($config['whitelist_domains'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
225
            throw new \InvalidArgumentException('Config parameter "whitelist_domains" must be an array');
226
        }
227
228
        $this->config = $config;
229
    }
230
231
    private function disableTwigVisitors()
232
    {
233
        foreach ($this->twig->getNodeVisitors() as $visitor) {
234
            if ($visitor instanceof DefaultApplyingNodeVisitor) {
235
                $visitor->setEnabled(false);
236
            }
237
            if ($visitor instanceof RemovingNodeVisitor) {
238
                $visitor->setEnabled(false);
239
            }
240
        }
241
    }
242
}
243