Failed Conditions
Pull Request — master (#2849)
by Luís
63:28
created

ReservedWordsCommand::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 47
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 47
ccs 0
cts 38
cp 0
rs 9.0303
c 0
b 0
f 0
cc 1
eloc 13
nc 1
nop 0
crap 2
1
<?php
2
/*
3
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14
 *
15
 * This software consists of voluntary contributions made by many individuals
16
 * and is licensed under the MIT license. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Tools\Console\Command;
21
22
use Doctrine\DBAL\Platforms\Keywords\ReservedKeywordsValidator;
23
use Symfony\Component\Console\Command\Command;
24
use Symfony\Component\Console\Input\InputInterface;
25
use Symfony\Component\Console\Input\InputOption;
26
use Symfony\Component\Console\Output\OutputInterface;
27
28
class ReservedWordsCommand extends Command
29
{
30
    /**
31
     * @var array
32
     */
33
    private $keywordListClasses = [
34
        'mysql'         => 'Doctrine\DBAL\Platforms\Keywords\MySQLKeywords',
35
        'mysql57'       => 'Doctrine\DBAL\Platforms\Keywords\MySQL57Keywords',
36
        'sqlserver'     => 'Doctrine\DBAL\Platforms\Keywords\SQLServerKeywords',
37
        'sqlserver2005' => 'Doctrine\DBAL\Platforms\Keywords\SQLServer2005Keywords',
38
        'sqlserver2008' => 'Doctrine\DBAL\Platforms\Keywords\SQLServer2008Keywords',
39
        'sqlserver2012' => 'Doctrine\DBAL\Platforms\Keywords\SQLServer2012Keywords',
40
        'sqlite'        => 'Doctrine\DBAL\Platforms\Keywords\SQLiteKeywords',
41
        'pgsql'         => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQLKeywords',
42
        'pgsql91'       => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQL91Keywords',
43
        'pgsql92'       => 'Doctrine\DBAL\Platforms\Keywords\PostgreSQL92Keywords',
44
        'oracle'        => 'Doctrine\DBAL\Platforms\Keywords\OracleKeywords',
45
        'db2'           => 'Doctrine\DBAL\Platforms\Keywords\DB2Keywords',
46
        'sqlanywhere'   => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhereKeywords',
47
        'sqlanywhere11' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere11Keywords',
48
        'sqlanywhere12' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere12Keywords',
49
        'sqlanywhere16' => 'Doctrine\DBAL\Platforms\Keywords\SQLAnywhere16Keywords',
50
    ];
51
52
    /**
53
     * If you want to add or replace a keywords list use this command.
54
     *
55
     * @param string $name
56
     * @param string $class
57
     *
58
     * @return void
59
     */
60
    public function setKeywordListClass($name, $class)
61
    {
62
        $this->keywordListClasses[$name] = $class;
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    protected function configure()
69
    {
70
        $this
71
        ->setName('dbal:reserved-words')
72
        ->setDescription('Checks if the current database contains identifiers that are reserved.')
73
        ->setDefinition([
74
            new InputOption(
75
                'list',
76
                'l',
77
                InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
78
                'Keyword-List name.'
79
            )
80
        ])
81
        ->setHelp(<<<EOT
82
Checks if the current database contains tables and columns
83
with names that are identifiers in this dialect or in other SQL dialects.
84
85
By default SQLite, MySQL, PostgreSQL, Microsoft SQL Server, Oracle
86
and SQL Anywhere keywords are checked:
87
88
    <info>%command.full_name%</info>
89
90
If you want to check against specific dialects you can
91
pass them to the command:
92
93
    <info>%command.full_name% -l mysql -l pgsql</info>
94
95
The following keyword lists are currently shipped with Doctrine:
96
97
    * mysql
98
    * mysql57
99
    * pgsql
100
    * pgsql92
101
    * sqlite
102
    * oracle
103
    * sqlserver
104
    * sqlserver2005
105
    * sqlserver2008
106
    * sqlserver2012
107
    * sqlanywhere
108
    * sqlanywhere11
109
    * sqlanywhere12
110
    * sqlanywhere16
111
    * db2 (Not checked by default)
112
EOT
113
        );
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    protected function execute(InputInterface $input, OutputInterface $output)
120
    {
121
        /* @var $conn \Doctrine\DBAL\Connection */
122
        $conn = $this->getHelper('db')->getConnection();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method getConnection() does only exist in the following implementations of said interface: Doctrine\DBAL\Tools\Cons...Helper\ConnectionHelper.

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...
123
124
        $keywordLists = (array) $input->getOption('list');
125
        if ( ! $keywordLists) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $keywordLists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
126
            $keywordLists = [
127
                'mysql',
128
                'mysql57',
129
                'pgsql',
130
                'pgsql92',
131
                'sqlite',
132
                'oracle',
133
                'sqlserver',
134
                'sqlserver2005',
135
                'sqlserver2008',
136
                'sqlserver2012',
137
                'sqlanywhere',
138
                'sqlanywhere11',
139
                'sqlanywhere12',
140
                'sqlanywhere16',
141
            ];
142
        }
143
144
        $keywords = [];
145
        foreach ($keywordLists as $keywordList) {
146
            if ( ! isset($this->keywordListClasses[$keywordList])) {
147
                throw new \InvalidArgumentException(
148
                    "There exists no keyword list with name '" . $keywordList . "'. " .
149
                    "Known lists: " . implode(", ", array_keys($this->keywordListClasses))
150
                );
151
            }
152
            $class      = $this->keywordListClasses[$keywordList];
153
            $keywords[] = new $class;
154
        }
155
156
        $output->write('Checking keyword violations for <comment>' . implode(", ", $keywordLists) . "</comment>...", true);
157
158
        /* @var $schema \Doctrine\DBAL\Schema\Schema */
159
        $schema  = $conn->getSchemaManager()->createSchema();
160
        $visitor = new ReservedKeywordsValidator($keywords);
161
        $schema->visit($visitor);
162
163
        $violations = $visitor->getViolations();
164
        if (count($violations) == 0) {
165
            $output->write("No reserved keywords violations have been found!", true);
166
        } else {
167
            $output->write('There are <error>' . count($violations) . '</error> reserved keyword violations in your database schema:', true);
168
            foreach ($violations as $violation) {
169
                $output->write('  - ' . $violation, true);
170
            }
171
172
            return 1;
173
        }
174
175
        return 0;
176
    }
177
}
178