Completed
Push — master ( dc5b69...0eb8a8 )
by Michiel
05:16 queued 02:56
created

ReplayEventsCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * Copyright 2014 SURFnet bv
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 *     http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18
19
namespace Surfnet\StepupMiddleware\MiddlewareBundle\Console\Command;
20
21
use Surfnet\StepupMiddleware\MiddlewareBundle\Service\EventStreamReplayer;
22
use Symfony\Component\Console\Command\Command;
23
use Symfony\Component\Console\Helper\FormatterHelper;
24
use Symfony\Component\Console\Helper\QuestionHelper;
25
use Symfony\Component\Console\Input\InputInterface;
26
use Symfony\Component\Console\Input\InputOption;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Symfony\Component\Console\Question\ConfirmationQuestion;
29
use Symfony\Component\HttpKernel\KernelInterface;
30
31
class ReplayEventsCommand extends Command
32
{
33
    /**
34
     * @var EventStreamReplayer
35
     */
36
    private $replayer;
37
38
    public function __construct(EventStreamReplayer $eventStreamReplayer)
39
    {
40
        parent::__construct();
41
        $this->replayer = $eventStreamReplayer;
42
    }
43
44
    protected function configure()
45
    {
46
        $this
47
            ->setName('middleware:event:replay')
48
            ->setDescription(
49
                'Wipes all read models and repopulates the tables from the event store. Use the 
50
                --no-interaction option to perform the event replay without the additional confirmation question.'
51
            )
52
            ->addOption(
53
                'increments',
54
                'i',
55
                InputOption::VALUE_REQUIRED,
56
                'The amount of events that are replayed at once (repeated until all events are replayed)',
57
                1000
58
            );
59
    }
60
61
    protected function execute(InputInterface $input, OutputInterface $output)
62
    {
63
        /** @var KernelInterface $kernel */
64
        $kernel = $this->getApplication()->getKernel();
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...
65
        $environment = $kernel->getEnvironment();
66
        /** @var FormatterHelper $formatter */
67
        $formatter = $this->getHelper('formatter');
68
69
        // Be careful, when using the no-interaction option you will not get the confirmation question
70
        $noInteraction = $input->getOption('no-interaction');
71
72
        if (!in_array($environment, ['dev_event_replay', 'prod_event_replay', 'smoketest_event_replay'])) {
73
            $output->writeln($formatter->formatBlock(
74
                [
75
                    '',
76
                    'This command may only be executed using env "dev_event_replay", "prod_event_replay", or 
77
                    "smoketest_event_replay"',
78
                    ''
79
                ],
80
                'error'
81
            ));
82
83
            return;
84
        }
85
86
        /** @var QuestionHelper $interrogator */
87
        $interrogator = $this->getHelper('question');
88
        if ($environment === 'prod_event_replay') {
89
            $wantToRunOnProd = new ConfirmationQuestion(
90
                '<question>You have selected to run this on production. Have you disabled all access to the production '
91
                .'environment? (y/N)</question>',
92
                false
93
            );
94
95
            if (!$interrogator->ask($input, $output, $wantToRunOnProd)) {
96
                $output->writeln('<comment>Not starting the replay</comment>');
97
98
                return;
99
            }
100
        }
101
102
        $increments = (int)$input->getOption('increments');
103
        if ($increments < 1) {
104
            $output->writeln(
105
                $formatter->formatBlock(
106
                    sprintf('Increments must be a positive integer, "%s" given', $input->getOption('increments')),
107
                    'error'
108
                )
109
            );
110
111
            return;
112
        }
113
114
        if (!$noInteraction) {
115
            $output->writeln(['', $formatter->formatBlock(['', 'WARNING!!!!', ''], 'error'), '']);
0 ignored issues
show
Documentation introduced by
array('', $formatter->fo...!!', ''), 'error'), '') is of type array<integer,string,{"0..."string","2":"string"}>, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

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...
116
117
            $question = <<<QUESTION
118
You are about to WIPE all read data and recreate all data based on the events present, in steps of %d.
119
120
  <%s>>> This can take a while and should not be interrupted <<</%s>
121
122
Are you sure you wish to continue? (y/N)
123
QUESTION;
124
125
            $question = sprintf($question, $increments, 'error', 'error');
126
            $areYouSure = new ConfirmationQuestion(sprintf("<question>%s</question>\n", $question), false);
127
            if (!$interrogator->ask($input, $output, $areYouSure)) {
128
                $output->writeln('<comment>Replay cancelled!</comment>');
129
130
                return;
131
            }
132
        }
133
134
        $output->writeln(['', $formatter->formatBlock('Starting Event Replay', 'info')]);
0 ignored issues
show
Documentation introduced by
array('', $formatter->fo...Event Replay', 'info')) is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a string|object<Symfony\Co...onsole\Output\iterable>.

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...
135
        $output->writeln(
136
            $formatter->formatBlock(' >> If it is interrupted it must be rerun till completed', 'comment')
137
        );
138
139
        $this->replayer->replayEvents($output, $increments);
140
    }
141
}
142