Completed
Push — master ( 6f65b3...b6714d )
by Hannes
02:04
created

TestCommand::bootstrap()   C

Complexity

Conditions 8
Paths 6

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 6.6037
c 0
b 0
f 0
cc 8
eloc 11
nc 6
nop 2
1
<?php
2
3
namespace hanneskod\readmetester\Command;
4
5
use Symfony\Component\Console\Command\Command;
6
use Symfony\Component\Console\Input\InputArgument;
7
use Symfony\Component\Console\Input\InputOption;
8
use Symfony\Component\Console\Input\InputInterface;
9
use Symfony\Component\Console\Output\OutputInterface;
10
use hanneskod\readmetester\ReadmeTester;
11
12
/**
13
 * CLI command to run test
14
 */
15
class TestCommand extends Command
16
{
17
    protected function configure()
18
    {
19
        $this->setName('test')
20
            ->setDescription('Test examples in readme file')
21
            ->addArgument(
22
                'filename',
23
                InputArgument::OPTIONAL | InputArgument::IS_ARRAY,
24
                'Name of file to test',
25
                ['README.md']
26
            )
27
            ->addOption(
28
                'bootstrap',
29
                null,
30
                InputOption::VALUE_REQUIRED,
31
                'A "bootstrap" PHP file that is run before testing'
32
            )
33
            ->addOption(
34
                'no-auto-bootstrap',
35
                null,
36
                InputOption::VALUE_NONE,
37
                "Don't try to load a local composer autoloader when boostrap is not definied"
38
            )
39
        ;
40
    }
41
42
    protected function execute(InputInterface $input, OutputInterface $output)
43
    {
44
        $this->bootstrap($input, $output);
45
46
        $tester = new ReadmeTester;
47
        $exitStatus = 0;
48
49
        foreach ($input->getArgument('filename') as $filename) {
50
            if (!is_file($filename) || !is_readable($filename)) {
51
                throw new \Exception("Not able to read $filename");
52
            }
53
54
            $output->writeln("Testing examples in <comment>$filename</comment>");
55
56
            foreach ($tester->test(file_get_contents($filename)) as $example => $returnObj) {
57
                if ($returnObj->isSuccess()) {
58
                    $output->writeln(" <info>Example $example: {$returnObj->getMessage()}</info>");
59
                    continue;
60
                }
61
                $output->writeln(" <error>Example $example: {$returnObj->getMessage()}</error>");
62
                $exitStatus = 1;
63
            }
64
        }
65
66
        return $exitStatus;
67
    }
68
69
    private function bootstrap(InputInterface $input, OutputInterface $output)
70
    {
71
        if ($filename = $input->getOption('bootstrap')) {
72
            if (!file_exists($filename) || !is_readable($filename)) {
73
                throw new \RuntimeException("Unable to bootstrap $filename");
74
            }
75
76
            if ($output->isVerbose()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method isVerbose() does only exist in the following implementations of said interface: Symfony\Component\Console\Output\BufferedOutput, Symfony\Component\Console\Output\ConsoleOutput, Symfony\Component\Console\Output\NullOutput, Symfony\Component\Console\Output\Output, Symfony\Component\Console\Output\StreamOutput, Symfony\Component\Consol...ts\Fixtures\DummyOutput, Symfony\Component\Console\Tests\Output\TestOutput.

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...
77
                $output->writeln("Loading bootstrap <comment>$filename</comment>");
78
            }
79
80
            return require_once $filename;
81
        }
82
83
        if (!$input->getOption('no-auto-bootstrap') && is_readable('vendor/autoload.php')) {
84
            if ($output->isVerbose()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method isVerbose() does only exist in the following implementations of said interface: Symfony\Component\Console\Output\BufferedOutput, Symfony\Component\Console\Output\ConsoleOutput, Symfony\Component\Console\Output\NullOutput, Symfony\Component\Console\Output\Output, Symfony\Component\Console\Output\StreamOutput, Symfony\Component\Consol...ts\Fixtures\DummyOutput, Symfony\Component\Console\Tests\Output\TestOutput.

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...
85
                $output->writeln("Loading bootstrap <comment>vendor/autoload.php</comment>");
86
            }
87
88
            return require_once 'vendor/autoload.php';
89
        }
90
    }
91
}
92