Completed
Push — master ( 7ab492...80642d )
by Mike
04:13
created

DiffCommand::buildCodeFromSql()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 41
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 7.0601

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 41
ccs 25
cts 28
cp 0.8929
rs 6.7272
cc 7
eloc 24
nc 11
nop 3
crap 7.0601
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 LGPL. For more information, see
17
 * <http://www.doctrine-project.org>.
18
 */
19
20
namespace Doctrine\DBAL\Migrations\Tools\Console\Command;
21
22
use Doctrine\DBAL\Migrations\Configuration\Configuration;
23
use Doctrine\DBAL\Version as DbalVersion;
24
use Doctrine\DBAL\Migrations\Provider\SchemaProviderInterface;
25
use Doctrine\DBAL\Migrations\Provider\OrmSchemaProvider;
26
use Symfony\Component\Console\Input\InputInterface;
27
use Symfony\Component\Console\Output\OutputInterface;
28
use Symfony\Component\Console\Input\InputOption;
29
30
/**
31
 * Command for generate migration classes by comparing your current database schema
32
 * to your mapping information.
33
 *
34
 * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
35
 * @link    www.doctrine-project.org
36
 * @since   2.0
37
 * @author  Jonathan Wage <[email protected]>
38
 */
39
class DiffCommand extends GenerateCommand
40
{
41
    /**
42
     * @var     SchemaProviderInterface
43
     */
44
    protected $schemaProvider;
45
46 5
    public function __construct(SchemaProviderInterface $schemaProvider=null)
47
    {
48 5
        $this->schemaProvider = $schemaProvider;
49 5
        parent::__construct();
50 5
    }
51
52 5
    protected function configure()
53
    {
54 5
        parent::configure();
55
56 5
        $this
57 5
            ->setName('migrations:diff')
58 5
            ->setDescription('Generate a migration by comparing your current database to your mapping information.')
59 5
            ->setHelp(<<<EOT
60 1
The <info>%command.name%</info> command generates a migration by comparing your current database to your mapping information:
61
62
    <info>%command.full_name%</info>
63
64
You can optionally specify a <comment>--editor-cmd</comment> option to open the generated file in your favorite editor:
65
66
    <info>%command.full_name% --editor-cmd=mate</info>
67
EOT
68 5
            )
69 5
            ->addOption('filter-expression', null, InputOption::VALUE_OPTIONAL, 'Tables which are filtered by Regular Expression.')
70 5
            ->addOption('formatted', null, InputOption::VALUE_NONE, 'Format the generated SQL.')
71 5
            ->addOption('line-length', null, InputOption::VALUE_OPTIONAL, 'Max line length of unformatted lines.', 120)
72
        ;
73 5
    }
74
75 5
    public function execute(InputInterface $input, OutputInterface $output)
76
    {
77 5
        $isDbalOld = (DbalVersion::compare('2.2.0') > 0);
78 5
        $configuration = $this->getMigrationConfiguration($input, $output);
79
80 5
        $conn = $configuration->getConnection();
81 5
        $platform = $conn->getDatabasePlatform();
82
83 5
        if ($filterExpr = $input->getOption('filter-expression')) {
84
            if ($isDbalOld) {
85
                throw new \InvalidArgumentException('The "--filter-expression" option can only be used as of Doctrine DBAL 2.2');
86
            }
87
88
            $conn->getConfiguration()
89
                ->setFilterSchemaAssetsExpression($filterExpr);
90
        }
91
92 5
        $fromSchema = $conn->getSchemaManager()->createSchema();
93 5
        $toSchema = $this->getSchemaProvider()->createSchema();
94
95
        //Not using value from options, because filters can be set from config.yml
96 5
        if ( ! $isDbalOld && $filterExpr = $conn->getConfiguration()->getFilterSchemaAssetsExpression()) {
97 2
            foreach ($toSchema->getTables() as $table) {
98 2
                $tableName = $table->getName();
99 2
                if ( ! preg_match($filterExpr, $this->resolveTableName($tableName))) {
100 1
                    $toSchema->dropTable($tableName);
101 1
                }
102 2
            }
103 2
        }
104
105 5
        $up = $this->buildCodeFromSql($configuration, $input, $fromSchema->getMigrateToSql($toSchema, $platform));
106 5
        $down = $this->buildCodeFromSql($configuration, $input, $fromSchema->getMigrateFromSql($toSchema, $platform));
107
108 5
        if (! $up && ! $down) {
109
            $output->writeln('No changes detected in your mapping information.', 'ERROR');
110
111
            return;
112
        }
113
114 5
        $version = date('YmdHis');
115 5
        $path = $this->generateMigration($configuration, $input, $version, $up, $down);
116
117 5
        $output->writeln(sprintf('Generated new migration class to "<info>%s</info>" from schema differences.', $path));
118 5
    }
119
120 5
    private function buildCodeFromSql(Configuration $configuration, InputInterface $input, array $sql)
121
    {
122 5
        $currentPlatform = $configuration->getConnection()->getDatabasePlatform()->getName();
123 5
        $code = [];
124 5
        foreach ($sql as $query) {
125 5
            if (stripos($query, $configuration->getMigrationsTableName()) !== false) {
126
                continue;
127
            }
128
129 5
            if ($input->getOption('formatted')) {
130 1
                if (!class_exists('\SqlFormatter')) {
131
                    throw new \InvalidArgumentException(
132
                        'The "--formatted" option can only be used if the sql formatter is installed.'.
133
                        'Please run "composer require jdorn/sql-formatter".'
134
                    );
135
                }
136
137 1
                $maxLength = $input->getOption('line-length') - 18 - 8; // max - php code length - indentation
138
139 1
                if (strlen($query) > $maxLength) {
140 1
                    $query = \SqlFormatter::format($query, false);
141 1
                }
142 1
            }
143
144 5
            $code[] = sprintf("\$this->addSql(%s);", var_export($query, true));
145 5
        }
146
147 5
        if (!empty($code)) {
148 5
            array_unshift(
149 5
                $code,
150 5
                sprintf(
151 5
                    "\$this->abortIf(\$this->connection->getDatabasePlatform()->getName() != %s, %s);",
152 5
                    var_export($currentPlatform, true),
153 5
                    var_export(sprintf("Migration can only be executed safely on '%s'.", $currentPlatform), true)
154 5
                ),
155
                ""
156 5
            );
157 5
        }
158
159 5
        return implode("\n", $code);
160
    }
161
162 5
    private function getSchemaProvider()
163
    {
164 5
        if (!$this->schemaProvider) {
165 1
            $this->schemaProvider = new OrmSchemaProvider($this->getHelper('entityManager')->getEntityManager());
166 1
        }
167
168 5
        return $this->schemaProvider;
169
    }
170
171
    /**
172
     * Resolve a table name from its fully qualified name. The `$name` argument
173
     * comes from Doctrine\DBAL\Schema\Table#getName which can sometimes return
174
     * a namespaced name with the form `{namespace}.{tableName}`. This extracts
175
     * the table name from that.
176
     *
177
     * @param   string $name
178
     * @return  string
179
     */
180 2
    private function resolveTableName($name)
181
    {
182 2
        $pos = strpos($name, '.');
183
184 2
        return false === $pos ? $name : substr($name, $pos + 1);
185
    }
186
}
187