Passed
Push — master ( 0d0b31...20df5d )
by Andreas
04:23
created

schema::process_updates()   B

Complexity

Conditions 7
Paths 16

Size

Total Lines 41
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 8
Bugs 0 Features 0
Metric Value
cc 7
eloc 27
c 8
b 0
f 0
nc 16
nop 4
dl 0
loc 41
ccs 0
cts 26
cp 0
crap 56
rs 8.5546
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
        $sm = $em->getConnection()->createSchemaManager();
93
        foreach ($cms as $cm) {
94
            if ($sm->tablesExist([$cm->getTableName()])) {
95
                $to_update[] = $cm;
96
            } else {
97
                $to_create[] = $cm;
98
            }
99
        }
100
101
        if (!empty($to_create)) {
102
            $output->writeln('Creating <info>' . count($to_create) . '</info> new tables');
103
            $tool = new SchemaTool($em);
104
            try {
105
                $tool->createSchema($to_create);
106
            } catch (\Exception $e) {
107
                if (!$force) {
108
                    throw $e;
109
                }
110
                $output->writeln('<error>' . $e->getMessage() . '</error>');
111
            }
112
        }
113
        if (!empty($to_update)) {
114
            $delete = $input->getOption('delete');
115
            $this->process_updates($to_update, $output, $force, $delete);
116
        }
117
        $output->writeln('Generating proxies');
118
        $this->generate_proxyfiles($cms);
119
120
        $output->writeln('Done');
121
        return 0;
122
    }
123
124
    private function generate_proxyfiles(array $cms)
125
    {
126
        $em = connection::get_em();
127
        $generator = new ProxyGenerator($em->getConfiguration()->getProxyDir(), $em->getConfiguration()->getProxyNamespace());
0 ignored issues
show
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

127
        $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...
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

127
        $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...
128
        $generator->setPlaceholder('baseProxyInterface', 'Doctrine\ORM\Proxy\Proxy');
129
130
        foreach ($cms as $cm) {
131
            $filename = $generator->getProxyFileName($cm->getName());
132
            if (file_exists($filename)) {
133
                unlink($filename);
134
            }
135
            $generator->generateProxyClass($cm, $filename);
136
        }
137
    }
138
139
    /**
140
     * Since we normally don't delete old columns, we have to disable DBAL's renaming
141
     * detection, because otherwise a new column might just reuse an outdated one (keeping the values)
142
     */
143 1
    public static function diff(dbal_schema $from, dbal_schema $to, bool $delete = false) : SchemaDiff
144
    {
145 1
        $diff = (new Comparator)->compare($from, $to);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\DBAL\Schema\Comparator::compare() has been deprecated: Use non-static call to {@see compareSchemas()} instead. ( Ignorable by Annotation )

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

145
        $diff = /** @scrutinizer ignore-deprecated */ (new Comparator)->compare($from, $to);

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...
146
147 1
        foreach ($diff->changedTables as $changed_table) {
148 1
            if (!empty($changed_table->renamedColumns)) {
149
                if (empty($changed_table->addedColumns)) {
150
                    $changed_table->addedColumns = [];
151
                }
152
153
                foreach ($changed_table->renamedColumns as $name => $column) {
154
                    $changed_table->addedColumns[$column->getName()] = $column;
155
                    $changed_table->removedColumns[$name] = new Column($name, $column->getType());
156
                }
157
                $changed_table->renamedColumns = [];
158
            }
159 1
            if (!$delete) {
160 1
                $changed_table->removedColumns = [];
161
            }
162
        }
163 1
        return $diff;
164
    }
165
166
    private function process_updates(array $to_update, OutputInterface $output, $force, $delete)
167
    {
168
        $em = connection::get_em();
169
        $conn = $em->getConnection();
170
        $tool = new SchemaTool($em);
171
        $from = $conn->createSchemaManager()->createSchema();
172
        $to = $tool->getSchemaFromMetadata($to_update);
173
174
        $diff = self::diff($from, $to, $delete);
175
176
        if ($delete) {
177
            $sql = $diff->toSql($conn->getDatabasePlatform());
178
        } else {
179
            $sql = $diff->toSaveSql($conn->getDatabasePlatform());
180
        }
181
182
        if (empty($sql)) {
183
            return;
184
        }
185
186
        $output->writeln('Executing <info>' . count($sql) . '</info> updates');
187
        $progress = new ProgressBar($output);
188
        $progress->start(count($sql));
189
190
        foreach ($sql as $sql_line) {
191
            if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) {
192
                $output->writeln(' Executing <info>' . $sql_line . '</info>');
193
            }
194
            try {
195
                $conn->executeQuery($sql_line);
196
            } catch (\Exception $e) {
197
                if (!$force) {
198
                    throw $e;
199
                }
200
                $output->writeln('<error>' . $e->getMessage() . '</error>');
201
            }
202
203
            $progress->advance();
204
        }
205
        $progress->finish();
206
        $output->writeln('');
207
    }
208
}
209