Passed
Push — master ( 5c6e25...07d32f )
by Andreas
03:40
created

schema::diff()   A

Complexity

Conditions 6
Paths 7

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 9.5384

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 12
c 1
b 0
f 0
nc 7
nop 3
dl 0
loc 21
ccs 7
cts 13
cp 0.5385
crap 9.5384
rs 9.2222
1
<?php
2
/**
3
 * @author CONTENT CONTROL http://www.contentcontrol-berlin.de/
4
 * @copyright CONTENT CONTROL http://www.contentcontrol-berlin.de/
5
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
6
 */
7
8
namespace midgard\portable\command;
9
10
use midgard\portable\storage\connection;
11
use midgard\portable\classgenerator;
12
use Symfony\Component\Console\Command\Command;
13
use Symfony\Component\Console\Input\InputInterface;
14
use Symfony\Component\Console\Input\InputArgument;
15
use Symfony\Component\Console\Output\OutputInterface;
16
use Symfony\Component\Console\Helper\ProgressBar;
17
use Symfony\Component\Console\Question\Question;
18
use midgard_storage;
19
use midgard_connection;
20
use Doctrine\ORM\Tools\SchemaTool;
21
use Doctrine\DBAL\Schema\Comparator;
22
use Symfony\Component\Console\Input\InputOption;
23
use Doctrine\Common\Proxy\ProxyGenerator;
24
use Doctrine\DBAL\Schema\Column;
25
use Doctrine\DBAL\Schema\SchemaDiff;
26
use Doctrine\DBAL\Schema\Schema as dbal_schema;
27
28
/**
29
 * (Re)generate mapping information from MgdSchema XMLs
30
 */
31
class schema extends Command
32
{
33
    public $connected = false;
34
35
    protected function configure()
36
    {
37
        $this->setName('schema')
38
            ->setDescription('(Re)generate mapping information from MgdSchema XMLs')
39
            ->addArgument('config', InputArgument::OPTIONAL, 'Full path to midgard-portable config file')
40
            ->addOption('force', null, InputOption::VALUE_NONE, 'Ignore errors from DB')
41
            ->addOption('delete', null, InputOption::VALUE_NONE, 'Delete columns/tables that are not defined in mgdschema');
42
    }
43
44
    protected function execute(InputInterface $input, OutputInterface $output) : int
45
    {
46
        if (!$this->connected) {
47
            $path = $input->getArgument('config');
48
            if (empty($path)) {
49
                if (file_exists(OPENPSA_PROJECT_BASEDIR . 'config/midgard-portable.inc.php')) {
0 ignored issues
show
Bug introduced by
The constant midgard\portable\command\OPENPSA_PROJECT_BASEDIR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
50
                    $path = OPENPSA_PROJECT_BASEDIR . 'config/midgard-portable.inc.php';
51
                } else {
52
                    $dialog = $this->getHelper('question');
53
                    $path = $dialog->ask($input, $output, new Question('<question>Enter path to config file</question>'));
54
                }
55
            }
56
            if (!file_exists($path)) {
57
                throw new \RuntimeException('Config file ' . $path . ' not found');
58
            }
59
            //we have to delay startup so that we can delete the entity class file before it gets included
60
            connection::set_autostart(false);
61
            require $path;
62
        }
63
64
        $mgd_config = midgard_connection::get_instance()->config;
65
        $mgdschema_file = $mgd_config->vardir . '/mgdschema_classes.php';
66
        if (   file_exists($mgdschema_file)
67
            && !unlink($mgdschema_file)) {
68
            throw new \RuntimeException('Could not unlink ' . $mgdschema_file);
69
        }
70
        if (connection::get_parameter('dev_mode') !== true) {
71
            $driver = connection::get_parameter('driver');
72
            $classgenerator = new classgenerator($driver->get_manager(), $mgdschema_file);
73
            $classgenerator->write($driver->get_namespace());
74
        }
75
        if (!file_exists($mgd_config->blobdir . '/0/0')) {
76
            $mgd_config->create_blobdir();
77
        }
78
        connection::startup();
79
        $em = connection::get_em();
80
        connection::invalidate_cache();
81
        $cms = $em->getMetadataFactory()->getAllMetadata();
82
83
        // create storage
84
        if (    !midgard_storage::create_base_storage()
85
             && midgard_connection::get_instance()->get_error_string() != 'MGD_ERR_OK') {
86
            throw new \Exception("Failed to create base database structures" . midgard_connection::get_instance()->get_error_string());
87
        }
88
        $force = $input->getOption('force');
89
        $to_update = [];
90
        $to_create = [];
91
92
        foreach ($cms as $cm) {
93
            if ($em->getConnection()->getSchemaManager()->tablesExist([$cm->getTableName()])) {
94
                $to_update[] = $cm;
95
            } else {
96
                $to_create[] = $cm;
97
            }
98
        }
99
100
        if (!empty($to_create)) {
101
            $output->writeln('Creating <info>' . count($to_create) . '</info> new tables');
102
            $tool = new SchemaTool($em);
103
            try {
104
                $tool->createSchema($to_create);
105
            } catch (\Exception $e) {
106
                if (!$force) {
107
                    throw $e;
108
                }
109
                $output->writeln('<error>' . $e->getMessage() . '</error>');
110
            }
111
        }
112
        if (!empty($to_update)) {
113
            $delete = $input->getOption('delete');
114
            $this->process_updates($to_update, $output, $force, $delete);
115
        }
116
        $output->writeln('Generating proxies');
117
        $this->generate_proxyfiles($cms);
118
119
        $output->writeln('Done');
120
        return 0;
121
    }
122
123
    private function generate_proxyfiles(array $cms)
124
    {
125
        $em = connection::get_em();
126
        $generator = new ProxyGenerator($em->getConfiguration()->getProxyDir(), $em->getConfiguration()->getProxyNamespace());
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\Configuration::getProxyNamespace() has been deprecated: 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

126
        $generator = new ProxyGenerator($em->getConfiguration()->getProxyDir(), /** @scrutinizer ignore-deprecated */ $em->getConfiguration()->getProxyNamespace());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
Deprecated Code introduced by
The function Doctrine\ORM\Configuration::getProxyDir() has been deprecated: 2.7 We're switch to `ocramius/proxy-manager` and this method isn't applicable any longer ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

126
        $generator = new ProxyGenerator(/** @scrutinizer ignore-deprecated */ $em->getConfiguration()->getProxyDir(), $em->getConfiguration()->getProxyNamespace());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
127
        $generator->setPlaceholder('baseProxyInterface', 'Doctrine\ORM\Proxy\Proxy');
128
129
        foreach ($cms as $cm) {
130
            $filename = $generator->getProxyFileName($cm->getName());
131
            if (file_exists($filename)) {
132
                unlink($filename);
133
            }
134
            $generator->generateProxyClass($cm, $filename);
135
        }
136
    }
137
138
    /**
139
     * Since we normally don't delete old columns, we have to disable DBAL's renaming
140
     * detection, because otherwise a new column might just reuse an outdated one (keeping the values)
141
     */
142 1
    public static function diff(dbal_schema $from, dbal_schema $to, bool $delete = false) : SchemaDiff
143
    {
144 1
        $diff = (new Comparator)->compare($from, $to);
145
146 1
        foreach ($diff->changedTables as $changed_table) {
147 1
            if (!empty($changed_table->renamedColumns)) {
148
                if (empty($changed_table->addedColumns)) {
149
                    $changed_table->addedColumns = [];
150
                }
151
152
                foreach ($changed_table->renamedColumns as $name => $column) {
153
                    $changed_table->addedColumns[$column->getName()] = $column;
154
                    $changed_table->removedColumns[$name] = new Column($name, $column->getType());
155
                }
156
                $changed_table->renamedColumns = [];
157
            }
158 1
            if (!$delete) {
159 1
                $changed_table->removedColumns = [];
160
            }
161
        }
162 1
        return $diff;
163
    }
164
165
    private function process_updates(array $to_update, OutputInterface $output, $force, $delete)
166
    {
167
        $em = connection::get_em();
168
        $conn = $em->getConnection();
169
        $tool = new SchemaTool($em);
170
        $from = $conn->getSchemaManager()->createSchema();
171
        $to = $tool->getSchemaFromMetadata($to_update);
172
173
        $diff = self::diff($from, $to, $delete);
174
175
        if ($delete) {
176
            $sql = $diff->toSql($conn->getDatabasePlatform());
177
        } else {
178
            $sql = $diff->toSaveSql($conn->getDatabasePlatform());
179
        }
180
181
        if (empty($sql)) {
182
            return;
183
        }
184
185
        $output->writeln('Executing <info>' . count($sql) . '</info> updates');
186
        $progress = new ProgressBar($output);
187
        $progress->start(count($sql));
188
189
        foreach ($sql as $sql_line) {
190
            if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
191
                $output->writeln(' Executing <info>' . $sql_line . '</info>');
192
            }
193
            try {
194
                $conn->executeQuery($sql_line);
195
            } catch (\Exception $e) {
196
                if (!$force) {
197
                    throw $e;
198
                }
199
                $output->writeln('<error>' . $e->getMessage() . '</error>');
200
            }
201
202
            $progress->advance();
203
        }
204
        $progress->finish();
205
        $output->writeln('');
206
    }
207
}
208