Completed
Pull Request — master (#87)
by Tim
11:40
created

AbstractImportCommand::getVendorDir()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 6
cp 0
cc 1
eloc 4
nc 1
nop 0
crap 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Cli\Command\ImportCommandTrait
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import-cli-simple
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Cli\Command;
22
23
use Monolog\Logger;
24
use Monolog\Handler\ErrorLogHandler;
25
use TechDivision\Import\Utils\LoggerKeys;
26
use TechDivision\Import\Utils\OperationKeys;
27
use TechDivision\Import\App\Simple;
28
use TechDivision\Import\App\Utils\SynteticServiceKeys;
29
use TechDivision\Import\Cli\Utils\DependencyInjectionKeys;
30
use TechDivision\Import\Configuration\Jms\Configuration;
31
use TechDivision\Import\Configuration\Jms\Configuration\Database;
32
use TechDivision\Import\Configuration\Jms\Configuration\LoggerFactory;
33
use Symfony\Component\Config\FileLocator;
34
use Symfony\Component\Console\Command\Command;
35
use Symfony\Component\Console\Input\InputOption;
36
use Symfony\Component\Console\Input\InputArgument;
37
use Symfony\Component\Console\Input\InputInterface;
38
use Symfony\Component\Console\Output\OutputInterface;
39
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
40
41
/**
42
 * The abstract import command implementation.
43
 *
44
 * @author    Tim Wagner <[email protected]>
45
 * @copyright 2016 TechDivision GmbH <[email protected]>
46
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
47
 * @link      https://github.com/techdivision/import-cli-simple
48
 * @link      http://www.techdivision.com
49
 */
50
abstract class AbstractImportCommand extends Command implements ImportCommandInterface
51
{
52
53
    /**
54
     * Configures the current command.
55
     *
56
     * @return void
57
     * @see \Symfony\Component\Console\Command\Command::configure()
58
     */
59
    protected function configure()
60
    {
61
62
        // initialize the command with the required/optional options
63
        $this->addArgument(
64
            InputArgumentKeys::OPERATION_NAME,
65
            InputArgument::OPTIONAL,
66
            'The operation that has to be used for the import, one of "add-update", "replace" or "delete"',
67
            OperationKeys::ADD_UPDATE
68
        )
69
        ->addOption(
70
            InputOptionKeys::INSTALLATION_DIR,
71
            null,
72
            InputOption::VALUE_REQUIRED,
73
            'The Magento installation directory to which the files has to be imported',
74
            getcwd()
75
        )
76
        ->addOption(
77
            InputOptionKeys::SYSTEM_NAME,
78
            null,
79
            InputOption::VALUE_REQUIRED,
80
            'Specify the system name to use',
81
            gethostname()
82
        )
83
        ->addOption(
84
            InputOptionKeys::PID_FILENAME,
85
            null,
86
            InputOption::VALUE_REQUIRED,
87
            'The explicit PID filename to use',
88
            sprintf('%s/%s', sys_get_temp_dir(), Configuration::PID_FILENAME)
89
        )
90
        ->addOption(
91
            InputOptionKeys::MAGENTO_EDITION,
92
            null,
93
            InputOption::VALUE_REQUIRED,
94
            'The Magento edition to be used, either one of "CE" or "EE"'
95
        )
96
        ->addOption(
97
            InputOptionKeys::MAGENTO_VERSION,
98
            null,
99
            InputOption::VALUE_REQUIRED,
100
            'The Magento version to be used, e. g. "2.1.2"'
101
        )
102
        ->addOption(
103
            InputOptionKeys::CONFIGURATION,
104
            null,
105
            InputOption::VALUE_REQUIRED,
106
            'Specify the pathname to the configuration file to use'
107
        )
108
        ->addOption(
109
            InputOptionKeys::ENTITY_TYPE_CODE,
110
            null,
111
            InputOption::VALUE_REQUIRED,
112
            'Specify the entity type code to use, either one of "catalog_product", "catalog_category" or "eav_attribute"'
113
        )
114
        ->addOption(
115
            InputOptionKeys::SOURCE_DIR,
116
            null,
117
            InputOption::VALUE_REQUIRED,
118
            'The directory that has to be watched for new files'
119
        )
120
        ->addOption(
121
            InputOptionKeys::TARGET_DIR,
122
            null,
123
            InputOption::VALUE_REQUIRED,
124
            'The target directory with the files that has been imported'
125
        )
126
        ->addOption(
127
            InputOptionKeys::SOURCE_DATE_FORMAT,
128
            null,
129
            InputOption::VALUE_REQUIRED,
130
            'The date format used in the CSV file(s)'
131
        )
132
        ->addOption(
133
            InputOptionKeys::USE_DB_ID,
134
            null,
135
            InputOption::VALUE_REQUIRED,
136
            'The explicit database ID used for the actual import process'
137
        )
138
        ->addOption(
139
            InputOptionKeys::DB_PDO_DSN,
140
            null,
141
            InputOption::VALUE_REQUIRED,
142
            'The DSN used to connect to the Magento database where the data has to be imported, e. g. mysql:host=127.0.0.1;dbname=magento;charset=utf8'
143
        )
144
        ->addOption(
145
            InputOptionKeys::DB_USERNAME,
146
            null,
147
            InputOption::VALUE_REQUIRED,
148
            'The username used to connect to the Magento database'
149
        )
150
        ->addOption(
151
            InputOptionKeys::DB_PASSWORD,
152
            null,
153
            InputOption::VALUE_REQUIRED,
154
            'The password used to connect to the Magento database'
155
        )
156
        ->addOption(
157
            InputOptionKeys::LOG_LEVEL,
158
            null,
159
            InputOption::VALUE_REQUIRED,
160
            'The log level to use'
161
        )
162
        ->addOption(
163
            InputOptionKeys::DEBUG_MODE,
164
            null,
165
            InputOption::VALUE_REQUIRED,
166
            'Whether use the debug mode or not'
167
        );
168
    }
169
170
    /**
171
     * Executes the current command.
172
     *
173
     * This method is not abstract because you can use this class
174
     * as a concrete class. In this case, instead of defining the
175
     * execute() method, you set the code to execute by passing
176
     * a Closure to the setCode() method.
177
     *
178
     * @param \Symfony\Component\Console\Input\InputInterface   $input  An InputInterface instance
179
     * @param \Symfony\Component\Console\Output\OutputInterface $output An OutputInterface instance
180
     *
181
     * @return null|int null or 0 if everything went fine, or an error code
182
     * @throws \LogicException When this abstract method is not implemented
183
     * @see \Symfony\Component\Console\Command\Command::execute()
184
     */
185
    protected function execute(InputInterface $input, OutputInterface $output)
186
    {
187
188
        // load the actual vendor directory
189
        $vendorDir = $this->getVendorDir();
190
191
        // the path of the JMS serializer directory, relative to the vendor directory
192
        $jmsDir = DIRECTORY_SEPARATOR . 'jms' . DIRECTORY_SEPARATOR . 'serializer' . DIRECTORY_SEPARATOR . 'src';
193
194
        // try to find the path to the JMS Serializer annotations
195
        if (!file_exists($annotationDir = $vendorDir . DIRECTORY_SEPARATOR . $jmsDir)) {
196
            // stop processing, if the JMS annotations can't be found
197
            throw new \Exception(
198
                sprintf(
199
                    'The jms/serializer libarary can not be found in one of "%s"',
200
                    implode(', ', $vendorDir)
201
                )
202
            );
203
        }
204
205
        // register the autoloader for the JMS serializer annotations
206
        \Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
207
            'JMS\Serializer\Annotation',
208
            $annotationDir
209
        );
210
211
        // load the container instance
212
        $container = $this->getApplication()->getContainer();
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 getContainer() does only exist in the following sub-classes of Symfony\Component\Console\Application: TechDivision\Import\Cli\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...
213
214
        // initialize the default loader and load the DI configuration for the this library
215
        $defaultLoader = new XmlFileLoader($container, new FileLocator($vendorDir));
216
217
        // initialize and load the importer configuration
218
        $configuration = $container->get(DependencyInjectionKeys::CONFIGURATION_LOADER)->load($input, $this->getEntityTypeCode());
219
220
        // load the DI configuration for all the extension libraries
221
        foreach ($configuration->getExtensionLibraries() as $library) {
222
            if (file_exists($diConfiguration = sprintf('%s/%s/symfony/Resources/config/services.xml', $vendorDir, $library))) {
223
                $defaultLoader->load($diConfiguration);
224
            } else {
225
                throw new \Exception(
226
                    sprintf(
227
                        'Can\'t load DI configuration for library "%s"',
228
                        $diConfiguration
229
                    )
230
                );
231
            }
232
        }
233
234
        // register autoloaders for additional vendor directories
235
        $customLoader = new XmlFileLoader($container, new FileLocator());
236
        foreach ($configuration->getAdditionalVendorDirs() as $additionalVendorDir) {
237
            // load the vendor directory's auto loader
238
            if (file_exists($autoLoader = $additionalVendorDir->getVendorDir() . '/autoload.php')) {
239
                require $autoLoader;
240
            } else {
241
                throw new \Exception(
242
                    sprintf(
243
                        'Can\'t find autoloader in configured additional vendor directory "%s"',
244
                        $additionalVendorDir->getVendorDir()
245
                    )
246
                );
247
            }
248
249
            // try to load the DI configuration for the configured extension libraries
250
            foreach ($additionalVendorDir->getLibraries() as $library) {
251
                // prepare the DI configuration filename
252
                $diConfiguration = realpath(sprintf('%s/%s/symfony/Resources/config/services.xml', $additionalVendorDir->getVendorDir(), $library));
253
                // try to load the filename
254
                if (file_exists($diConfiguration)) {
255
                    $customLoader->load($diConfiguration);
256
                } else {
257
                    throw new \Exception(
258
                        sprintf(
259
                            'Can\'t load DI configuration for library "%s"',
260
                            $diConfiguration
261
                        )
262
                    );
263
                }
264
            }
265
        }
266
267
        // add the configuration as well as input/outut instances to the DI container
268
        $container->set(SynteticServiceKeys::INPUT, $input);
269
        $container->set(SynteticServiceKeys::OUTPUT, $output);
270
        $container->set(SynteticServiceKeys::CONFIGURATION, $configuration);
271
        $container->set(SynteticServiceKeys::APPLICATION, $this->getApplication());
272
273
        // initialize the PDO connection
274
        $dsn = $configuration->getDatabase()->getDsn();
275
        $username = $configuration->getDatabase()->getUsername();
276
        $password = $configuration->getDatabase()->getPassword();
277
        $connection = new \PDO($dsn, $username, $password);
278
        $connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
279
280
        // add the PDO connection to the DI container
281
        $container->set(SynteticServiceKeys::CONNECTION, $connection);
282
283
        // initialize the system logger
284
        $loggers = array();
285
286
        // initialize the default system logger
287
        $systemLogger = new Logger('techdivision/import');
288
        $systemLogger->pushHandler(
289
            new ErrorLogHandler(
290
                ErrorLogHandler::OPERATING_SYSTEM,
291
                $configuration->getLogLevel()
292
            )
293
        );
294
295
        // add it to the array
296
        $loggers[LoggerKeys::SYSTEM] = $systemLogger;
297
298
        // append the configured loggers or override the default one
299
        foreach ($configuration->getLoggers() as $loggerConfiguration) {
300
            // load the factory class that creates the logger instance
301
            $loggerFactory = $loggerConfiguration->getFactory();
302
            // create the logger instance and add it to the available loggers
303
            $loggers[$loggerConfiguration->getName()] = $loggerFactory::factory($configuration, $loggerConfiguration);
304
        }
305
306
        // add the system loggers to the DI container
307
        $container->set(SynteticServiceKeys::LOGGERS, $loggers);
308
309
        // start the import process
310
        $container->get(SynteticServiceKeys::SIMPLE)->process();
311
    }
312
313
    /**
314
     * Return's the absolute path to the actual vendor directory.
315
     *
316
     * @return string The absolute path to the actual vendor directory
317
     * @throws \Exception Is thrown, if none of the possible vendor directories can be found
318
     */
319
    public function getVendorDir()
320
    {
321
        return $this->getApplication()
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 getContainer() does only exist in the following sub-classes of Symfony\Component\Console\Application: TechDivision\Import\Cli\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...
322
                    ->getContainer()
323
                    ->getParameter(DependencyInjectionKeys::CONFIGURATION_VENDOR_DIR);
324
    }
325
}
326