Completed
Pull Request — master (#73)
by Nghia
64:28
created

ImportCommand::validateBundle()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 4

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 19
ccs 3
cts 3
cp 1
rs 9.2
cc 4
eloc 11
nc 4
nop 2
crap 4
1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[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 ONGR\TranslationsBundle\Command;
13
14
use InvalidArgumentException;
15
use ONGR\TranslationsBundle\Service\Import;
16
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
17
use Symfony\Component\Console\Input\InputArgument;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
22
/**
23
 * Translations import command.
24
 */
25
class ImportCommand extends ContainerAwareCommand
26
{
27
    /**
28
     * @var InputInterface
29
     */
30
    private $input;
31
32
    /**
33
     * @var OutputInterface
34
     */
35
    private $output;
36
37
    /**
38
     * {@inheritdoc}
39
     */
40 6
    protected function configure()
41
    {
42 6
        $this->setName('ongr:translations:import');
43 6
        $this->setDescription('Import all translations from flat files (xliff, yml, php) into the database.');
44 6
        $this->addOption('globals', 'g', InputOption::VALUE_NONE, 'Import only globals (app/Resources/translations.');
45 6
        $this->addOption('config-only', 'c', InputOption::VALUE_NONE, 'Import only bundles specified in config.');
46 6
        $this->addOption(
47 6
            'locales',
48 6
            'l',
49 6
            InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
50 6
            'Import only for these locales, instead of using the managed locales.'
51
        );
52 6
        $this->addOption(
53 6
            'domains',
54 6
            'd',
55 6
            InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
56 6
            'Import only these domains.',
57 6
            []
58
        );
59 6
        $this->addArgument(
60 6
            'bundle',
61 6
            InputArgument::OPTIONAL,
62 6
            'Import translations for this specific bundle. Provide full bundles namespace.',
63 6
            null
64
        );
65 6
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 3
    protected function execute(InputInterface $input, OutputInterface $output)
71
    {
72 3
        $this->input = $input;
73 3
        $this->output = $output;
74
75
        /** @var Import $import */
76 3
        $import = $this->getContainer()->get('ongr_translations.import');
77
78 3
        $locales = $this->input->getOption('locales');
79 3
        if (empty($locales)) {
80 2
            $locales = $this->getContainer()->getParameter('ongr_translations.managed_locales');
81
        }
82 3
        $domains = $input->getOption('domains');
83
84 3
        $bundleName = $this->input->getArgument('bundle');
85
86 3
        $import->setLocales($locales);
87 3
        $import->setDomains($domains);
88
89 3
        if ($input->getOption('config-only')) {
90
            $this->output->writeln('<info>*** Importing configured bundles translation files ***</info>');
91
            $import->importBundlesTranslationFiles($import->getConfigBundles());
92
        } else {
93 3
            if ($bundleName) {
94 2
                $this->validateBundle($bundleName, true);
95 1
                $this->output->writeln("<info>*** Importing {$bundleName} translation files ***</info>");
96 1
                $bundle = $this->getApplication()->getKernel()->getBundle($bundleName);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Console\Application as the method getKernel() does only exist in the following sub-classes of Symfony\Component\Console\Application: Symfony\Bundle\FrameworkBundle\Console\Application. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
97
                $import->importBundlesTranslationFiles([$bundle], true);
98 1
            } else {
99 1
                $this->output->writeln('<info>*** Importing application translation files ***</info>');
100 1
                $import->importAppTranslationFiles();
101 1
                if (!$this->input->getOption('globals')) {
102 1
                    $this->output->writeln('<info>*** Importing bundles translation files ***</info>');
103 1
                    $import->importBundlesTranslationFiles(
104
                        array_merge($import->getBundles(), $import->getConfigBundles())
105 1
                    );
106 1
                    $this->output->writeln('<info>*** Importing component translation files ***</info>');
107
                    $import->importComponentTranslationFiles();
108
                }
109
            }
110 2
        }
111 2
        $import->writeToStorage();
112
    }
113
114
    /**
115
     * Check if provided bundles is correct.
116
     *
117
     * @param string $bundleName
118
     * @param bool   $isShortName
119
     *
120 2
     * @throws InvalidArgumentException
121
     * @return bool
122 2
     */
123 1
    private function validateBundle($bundleName, $isShortName = false)
124 1
    {
125
        if ($isShortName) {
126
            $bundle = $this->getApplication()->getKernel()->getBundle($bundleName);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Console\Application as the method getKernel() does only exist in the following sub-classes of Symfony\Component\Console\Application: Symfony\Bundle\FrameworkBundle\Console\Application. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
127 1
            if (!$bundle) {
128
                throw new InvalidArgumentException(
129
                    "Invalid bundle '{$bundleName}'"
130
                );
131
            }
132
        } else {
133
            if (!class_exists($bundleName)) {
134
                throw new InvalidArgumentException(
135
                    "Invalid bundle namespace '{$bundleName}'"
136
                );
137
            }
138
        }
139
140
        return true;
141
    }
142
}
143