Completed
Push — master ( d3a073...5737c8 )
by Greg
02:21
created

src/Robo.php (2 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace Robo;
3
4
use Composer\Autoload\ClassLoader;
5
use League\Container\Container;
6
use League\Container\ContainerInterface;
7
use Robo\Common\ProcessExecutor;
8
use Consolidation\Config\ConfigInterface;
9
use Consolidation\Config\Loader\ConfigProcessor;
10
use Consolidation\Config\Loader\YamlConfigLoader;
11
use Symfony\Component\Console\Input\StringInput;
12
use Symfony\Component\Console\Application as SymfonyApplication;
13
use Symfony\Component\Process\Process;
14
15
/**
16
 * Manages the container reference and other static data.  Favor
17
 * using dependency injection wherever possible.  Avoid using
18
 * this class directly, unless setting up a custom DI container.
19
 */
20
class Robo
21
{
22
    const APPLICATION_NAME = 'Robo';
23
    const VERSION = '1.3.5-dev';
24
25
    /**
26
     * The currently active container object, or NULL if not initialized yet.
27
     *
28
     * @var ContainerInterface|null
29
     */
30
    protected static $container;
31
32
    /**
33
     * Entrypoint for standalone Robo-based tools.  See docs/framework.md.
34
     *
35
     * @param string[] $argv
36
     * @param string $commandClasses
37
     * @param null|string $appName
38
     * @param null|string $appVersion
39
     * @param null|\Symfony\Component\Console\Output\OutputInterface $output
40
     *
41
     * @return int
42
     */
43
    public static function run($argv, $commandClasses, $appName = null, $appVersion = null, $output = null, $repository = null)
44
    {
45
        $runner = new \Robo\Runner($commandClasses);
46
        $runner->setSelfUpdateRepository($repository);
47
        $statusCode = $runner->execute($argv, $appName, $appVersion, $output);
48
        return $statusCode;
49
    }
50
51
    /**
52
     * Sets a new global container.
53
     *
54
     * @param ContainerInterface $container
55
     *   A new container instance to replace the current.
56
     */
57
    public static function setContainer(ContainerInterface $container)
58
    {
59
        static::$container = $container;
60
    }
61
62
    /**
63
     * Unsets the global container.
64
     */
65
    public static function unsetContainer()
66
    {
67
        static::$container = null;
68
    }
69
70
    /**
71
     * Returns the currently active global container.
72
     *
73
     * @return \League\Container\ContainerInterface
74
     *
75
     * @throws \RuntimeException
76
     */
77
    public static function getContainer()
78
    {
79
        if (static::$container === null) {
80
            throw new \RuntimeException('container is not initialized yet. \Robo\Robo::setContainer() must be called with a real container.');
81
        }
82
        return static::$container;
83
    }
84
85
    /**
86
     * Returns TRUE if the container has been initialized, FALSE otherwise.
87
     *
88
     * @return bool
89
     */
90
    public static function hasContainer()
91
    {
92
        return static::$container !== null;
93
    }
94
95
    /**
96
     * Create a config object and load it from the provided paths.
97
     */
98
    public static function createConfiguration($paths)
99
    {
100
        $config = new \Robo\Config\Config();
101
        static::loadConfiguration($paths, $config);
102
        return $config;
103
    }
104
105
    /**
106
     * Use a simple config loader to load configuration values from specified paths
107
     */
108
    public static function loadConfiguration($paths, $config = null)
109
    {
110
        if ($config == null) {
111
            $config = static::config();
112
        }
113
        $loader = new YamlConfigLoader();
114
        $processor = new ConfigProcessor();
115
        $processor->add($config->export());
116
        foreach ($paths as $path) {
117
            $processor->extend($loader->load($path));
118
        }
119
        $config->import($processor->export());
120
    }
121
122
    /**
123
     * Create a container and initiailze it.  If you wish to *change*
124
     * anything defined in the container, then you should call
125
     * \Robo::configureContainer() instead of this function.
126
     *
127
     * @param null|\Symfony\Component\Console\Input\InputInterface $input
128
     * @param null|\Symfony\Component\Console\Output\OutputInterface $output
129
     * @param null|\Robo\Application $app
130
     * @param null|ConfigInterface $config
131
     * @param null|\Composer\Autoload\ClassLoader $classLoader
132
     *
133
     * @return \League\Container\Container|\League\Container\ContainerInterface
134
     */
135
    public static function createDefaultContainer($input = null, $output = null, $app = null, $config = null, $classLoader = null)
136
    {
137
        // Do not allow this function to be called more than once.
138
        if (static::hasContainer()) {
139
            return static::getContainer();
140
        }
141
142
        if (!$app) {
143
            $app = static::createDefaultApplication();
144
        }
145
146
        if (!$config) {
147
            $config = new \Robo\Config\Config();
148
        }
149
150
        // Set up our dependency injection container.
151
        $container = new Container();
152
        static::configureContainer($container, $app, $config, $input, $output, $classLoader);
153
        static::configureApplication($app, $container);
154
155
        return $container;
156
    }
157
158
    public static function configureApplication($app, $container)
159
    {
160
        // Set the application dispatcher
161
        $app->setDispatcher($container->get('eventDispatcher'));
162
163
        if ($app instanceof \Robo\Contract\IOAwareInterface) {
164
            $app->setIOStorage($container->get('ioStorage'));
165
        }
166
        if ($app instanceof \Psr\Log\LoggerAwareInterface) {
167
            $app->setLogger($container->get('logger'));
168
        }
169
    }
170
171
    /**
172
     * Initialize a container with all of the default Robo services.
173
     * IMPORTANT:  after calling this method, clients MUST call:
174
     *
175
     *   \Robo\Robo::configureApplication($app, $container);
176
     *
177
     * Any modification to the container should be done prior to fetching
178
     * objects from it.
179
     *
180
     * It is recommended to use \Robo::createDefaultContainer()
181
     * instead, which does all required setup for the caller, but has
182
     * the limitation that the container it creates can only be
183
     * extended, not modified.
184
     *
185
     * @param \League\Container\ContainerInterface $container
186
     * @param \Symfony\Component\Console\Application $app
187
     * @param ConfigInterface $config
188
     * @param null|\Symfony\Component\Console\Input\InputInterface $input
189
     * @param null|\Symfony\Component\Console\Output\OutputInterface $output
190
     * @param null|\Composer\Autoload\ClassLoader $classLoader
191
     */
192
    public static function configureContainer(ContainerInterface $container, SymfonyApplication $app, ConfigInterface $config, $input = null, $output = null, $classLoader = null)
193
    {
194
        // Self-referential container refernce for the inflector
195
        $container->add('container', $container);
196
        static::setContainer($container);
197
198
        // Create default input and output objects if they were not provided
199
        if (!$input) {
200
            $input = new StringInput('');
201
        }
202
        if (!$output) {
203
            $output = new \Symfony\Component\Console\Output\ConsoleOutput();
204
        }
205
        if (!$classLoader) {
206
            $classLoader = new ClassLoader();
207
        }
208
        $config->set(Config::DECORATED, $output->isDecorated());
209
        $config->set(Config::INTERACTIVE, $input->isInteractive());
210
211
        $container->share('application', $app);
212
        $container->share('config', $config);
213
        $container->share('input', $input);
214
        $container->share('output', $output);
215
        $container->share('outputAdapter', \Robo\Common\OutputAdapter::class);
216
        $container->share('classLoader', $classLoader);
217
218
        // Register logging and related services.
219
        $container->share('logStyler', \Robo\Log\RoboLogStyle::class);
220
        $container->share('roboLogger', \Robo\Log\RoboLogger::class)
221
            ->withMethodCall('setLogOutputStyler', ['logStyler'])
222
            ->withArgument('output');
223
        $container->share('logger', \Consolidation\Log\LoggerManager::class)
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface League\Container\Definition\DefinitionInterface as the method withMethodCall() does only exist in the following implementations of said interface: League\Container\Definition\ClassDefinition.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
224
            ->withMethodCall('setLogOutputStyler', ['logStyler'])
225
            ->withMethodCall('fallbackLogger', ['roboLogger']);
226
        $container->add('progressBar', \Symfony\Component\Console\Helper\ProgressBar::class)
227
            ->withArgument('output');
228
        $container->share('progressIndicator', \Robo\Common\ProgressIndicator::class)
229
            ->withArgument('progressBar')
230
            ->withArgument('output');
231
        $container->share('resultPrinter', \Robo\Log\ResultPrinter::class);
232
        $container->add('simulator', \Robo\Task\Simulator::class);
233
        $container->share('globalOptionsEventListener', \Robo\GlobalOptionsEventListener::class)
234
            ->withMethodCall('setApplication', ['application']);
235
        $container->share('injectConfigEventListener', \Consolidation\Config\Inject\ConfigForCommand::class)
236
            ->withArgument('config')
237
            ->withMethodCall('setApplication', ['application']);
238
        $container->share('collectionProcessHook', \Robo\Collection\CollectionProcessHook::class);
239
        $container->share('alterOptionsCommandEvent', \Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent::class)
240
            ->withArgument('application');
241
        $container->share('hookManager', \Consolidation\AnnotatedCommand\Hooks\HookManager::class)
242
            ->withMethodCall('addCommandEvent', ['alterOptionsCommandEvent'])
243
            ->withMethodCall('addCommandEvent', ['injectConfigEventListener'])
244
            ->withMethodCall('addCommandEvent', ['globalOptionsEventListener'])
245
            ->withMethodCall('addResultProcessor', ['collectionProcessHook', '*']);
246
        $container->share('eventDispatcher', \Symfony\Component\EventDispatcher\EventDispatcher::class)
247
            ->withMethodCall('addSubscriber', ['hookManager']);
248
        $container->share('formatterManager', \Consolidation\OutputFormatters\FormatterManager::class)
249
            ->withMethodCall('addDefaultFormatters', [])
250
            ->withMethodCall('addDefaultSimplifiers', []);
251
        $container->share('prepareTerminalWidthOption', \Consolidation\AnnotatedCommand\Options\PrepareTerminalWidthOption::class)
252
            ->withMethodCall('setApplication', ['application']);
253
        $container->share('symfonyStyleInjector', \Robo\Symfony\SymfonyStyleInjector::class);
254
        $container->share('parameterInjection', \Consolidation\AnnotatedCommand\ParameterInjection::class)
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface League\Container\Definition\DefinitionInterface as the method withMethodCall() does only exist in the following implementations of said interface: League\Container\Definition\ClassDefinition.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
255
            ->withMethodCall('register', ['Symfony\Component\Console\Style\SymfonyStyle', 'symfonyStyleInjector']);
256
        $container->share('commandProcessor', \Consolidation\AnnotatedCommand\CommandProcessor::class)
257
            ->withArgument('hookManager')
258
            ->withMethodCall('setFormatterManager', ['formatterManager'])
259
            ->withMethodCall('addPrepareFormatter', ['prepareTerminalWidthOption'])
260
            ->withMethodCall('setParameterInjection', ['parameterInjection'])
261
            ->withMethodCall(
262
                'setDisplayErrorFunction',
263
                [
264
                    function ($output, $message) use ($container) {
265
                        $logger = $container->get('logger');
266
                        $logger->error($message);
267
                    }
268
                ]
269
            );
270
        $container->share('ioStorage', \Robo\Symfony\IOStorage::class);
271
        $container->share('stdinHandler', \Consolidation\AnnotatedCommand\Input\StdinHandler::class);
272
        $container->share('commandFactory', \Consolidation\AnnotatedCommand\AnnotatedCommandFactory::class)
273
            ->withMethodCall('setCommandProcessor', ['commandProcessor']);
274
        $container->share('relativeNamespaceDiscovery', \Robo\ClassDiscovery\RelativeNamespaceDiscovery::class)
275
            ->withArgument('classLoader');
276
277
        // Deprecated: favor using collection builders to direct use of collections.
278
        $container->add('collection', \Robo\Collection\Collection::class);
279
        // Deprecated: use CollectionBuilder::create() instead -- or, better
280
        // yet, BuilderAwareInterface::collectionBuilder() if available.
281
        $container->add('collectionBuilder', \Robo\Collection\CollectionBuilder::class);
282
283
        static::addInflectors($container);
284
285
        // Make sure the application is appropriately initialized.
286
        $app->setAutoExit(false);
287
    }
288
289
    /**
290
     * @param null|string $appName
291
     * @param null|string $appVersion
292
     *
293
     * @return \Robo\Application
294
     */
295
    public static function createDefaultApplication($appName = null, $appVersion = null)
296
    {
297
        $appName = $appName ?: self::APPLICATION_NAME;
298
        $appVersion = $appVersion ?: self::VERSION;
299
300
        $app = new \Robo\Application($appName, $appVersion);
301
        $app->setAutoExit(false);
302
        return $app;
303
    }
304
305
    /**
306
     * Add the Robo League\Container inflectors to the container
307
     *
308
     * @param \League\Container\ContainerInterface $container
309
     */
310
    public static function addInflectors($container)
311
    {
312
        // Register our various inflectors.
313
        $container->inflector(\Robo\Contract\ConfigAwareInterface::class)
314
            ->invokeMethod('setConfig', ['config']);
315
        $container->inflector(\Psr\Log\LoggerAwareInterface::class)
316
            ->invokeMethod('setLogger', ['logger']);
317
        $container->inflector(\League\Container\ContainerAwareInterface::class)
318
            ->invokeMethod('setContainer', ['container']);
319
        $container->inflector(\Robo\Symfony\IOAwareInterface::class)
320
            ->invokeMethod('setIOStorage', ['ioStorage']);
321
        $container->inflector(\Symfony\Component\Console\Input\InputAwareInterface::class)
322
            ->invokeMethod('setInput', ['input']);
323
        $container->inflector(\Robo\Contract\OutputAwareInterface::class)
324
            ->invokeMethod('setOutput', ['output']);
325
        $container->inflector(\Robo\Contract\ProgressIndicatorAwareInterface::class)
326
            ->invokeMethod('setProgressIndicator', ['progressIndicator']);
327
        $container->inflector(\Consolidation\AnnotatedCommand\Events\CustomEventAwareInterface::class)
328
            ->invokeMethod('setHookManager', ['hookManager']);
329
        $container->inflector(\Robo\Contract\VerbosityThresholdInterface::class)
330
            ->invokeMethod('setOutputAdapter', ['outputAdapter']);
331
        $container->inflector(\Consolidation\AnnotatedCommand\Input\StdinAwareInterface::class)
332
            ->invokeMethod('setStdinHandler', ['stdinHandler']);
333
    }
334
335
    /**
336
     * Retrieves a service from the container.
337
     *
338
     * Use this method if the desired service is not one of those with a dedicated
339
     * accessor method below. If it is listed below, those methods are preferred
340
     * as they can return useful type hints.
341
     *
342
     * @param string $id
343
     *   The ID of the service to retrieve.
344
     *
345
     * @return mixed
346
     *   The specified service.
347
     */
348
    public static function service($id)
349
    {
350
        return static::getContainer()->get($id);
351
    }
352
353
    /**
354
     * Indicates if a service is defined in the container.
355
     *
356
     * @param string $id
357
     *   The ID of the service to check.
358
     *
359
     * @return bool
360
     *   TRUE if the specified service exists, FALSE otherwise.
361
     */
362
    public static function hasService($id)
363
    {
364
        // Check hasContainer() first in order to always return a Boolean.
365
        return static::hasContainer() && static::getContainer()->has($id);
366
    }
367
368
    /**
369
     * Return the result printer object.
370
     *
371
     * @return \Robo\Log\ResultPrinter
372
     */
373
    public static function resultPrinter()
374
    {
375
        return static::service('resultPrinter');
376
    }
377
378
    /**
379
     * @return ConfigInterface
380
     */
381
    public static function config()
382
    {
383
        return static::service('config');
384
    }
385
386
    /**
387
     * @return \Consolidation\Log\Logger
388
     */
389
    public static function logger()
390
    {
391
        return static::service('logger');
392
    }
393
394
    /**
395
     * @return \Robo\Application
396
     */
397
    public static function application()
398
    {
399
        return static::service('application');
400
    }
401
402
    /**
403
     * Return the output object.
404
     *
405
     * @return \Symfony\Component\Console\Output\OutputInterface
406
     */
407
    public static function output()
408
    {
409
        return static::service('output');
410
    }
411
412
    /**
413
     * Return the input object.
414
     *
415
     * @return \Symfony\Component\Console\Input\InputInterface
416
     */
417
    public static function input()
418
    {
419
        return static::service('input');
420
    }
421
422
    public static function process(Process $process)
423
    {
424
        return ProcessExecutor::create(static::getContainer(), $process);
425
    }
426
}
427