Passed
Push — master ( c37485...6f459e )
by Andreas
03:16
created

schema::configure()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 3
b 0
f 0
nc 1
nop 0
dl 0
loc 7
ccs 0
cts 6
cp 0
crap 2
rs 10
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;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Command\Command was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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;
0 ignored issues
show
Bug introduced by
The type Symfony\Component\Console\Question\Question was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use midgard_storage;
19
use midgard_connection;
20
use Doctrine\ORM\Tools\SchemaTool;
21
use Symfony\Component\Console\Input\InputOption;
22
use Doctrine\Common\Proxy\ProxyGenerator;
23
use Doctrine\DBAL\Schema\Column;
24
use Doctrine\DBAL\Schema\SchemaDiff;
25
use Doctrine\DBAL\Schema\Schema as dbal_schema;
26
27
/**
28
 * (Re)generate mapping information from MgdSchema XMLs
29
 */
30
class schema extends Command
31
{
32
    public bool $connected = false;
33
34
    protected function configure()
35
    {
36
        $this->setName('schema')
37
            ->setDescription('(Re)generate mapping information from MgdSchema XMLs')
38
            ->addArgument('config', InputArgument::OPTIONAL, 'Full path to midgard-portable config file')
39
            ->addOption('force', null, InputOption::VALUE_NONE, 'Ignore errors from DB')
40
            ->addOption('delete', null, InputOption::VALUE_NONE, 'Delete columns/tables that are not defined in mgdschema');
41
    }
42
43
    protected function execute(InputInterface $input, OutputInterface $output) : int
44
    {
45
        if (!$this->connected) {
46
            $path = $input->getArgument('config');
47
            if (empty($path)) {
48
                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...
49
                    $path = OPENPSA_PROJECT_BASEDIR . 'config/midgard-portable.inc.php';
50
                } else {
51
                    $dialog = $this->getHelper('question');
52
                    $path = $dialog->ask($input, $output, new Question('<question>Enter path to config file</question>'));
53
                }
54
            }
55
            if (!file_exists($path)) {
56
                throw new \RuntimeException('Config file ' . $path . ' not found');
57
            }
58
            //we have to delay startup so that we can delete the entity class file before it gets included
59
            connection::set_autostart(false);
60
            require $path;
61
        }
62
63
        $mgd_config = midgard_connection::get_instance()->config;
64
        $mgdschema_file = $mgd_config->vardir . '/mgdschema_classes.php';
65
        if (   file_exists($mgdschema_file)
66
            && !unlink($mgdschema_file)) {
67
            throw new \RuntimeException('Could not unlink ' . $mgdschema_file);
68
        }
69
        if (connection::get_parameter('dev_mode') !== true) {
70
            $driver = connection::get_parameter('driver');
71
            $classgenerator = new classgenerator($driver->get_manager(), $mgdschema_file);
72
            $classgenerator->write($driver->get_namespace());
73
        }
74
        if (!file_exists($mgd_config->blobdir . '/0/0')) {
75
            $mgd_config->create_blobdir();
0 ignored issues
show
Bug introduced by
The method create_blobdir() does not exist on null. ( Ignorable by Annotation )

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

75
            $mgd_config->/** @scrutinizer ignore-call */ 
76
                         create_blobdir();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
76
        }
77
        connection::startup();
78
        $em = connection::get_em();
79
        connection::invalidate_cache();
80
        $cms = $em->getMetadataFactory()->getAllMetadata();
81
82
        // create storage
83
        if (    !midgard_storage::create_base_storage()
84
             && midgard_connection::get_instance()->get_error_string() != 'MGD_ERR_OK') {
85
            throw new \Exception("Failed to create base database structures" . midgard_connection::get_instance()->get_error_string());
86
        }
87
        $force = $input->getOption('force');
88
        $to_update = [];
89
        $to_create = [];
90
91
        $sm = $em->getConnection()->createSchemaManager();
92
        foreach ($cms as $cm) {
93
            if ($sm->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());
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
        $comparator = connection::get_em()->getConnection()->createSchemaManager()->createComparator();
145 1
        $diff = $comparator->compareSchemas($from, $to);
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
        if (!$delete) {
164 1
            $diff->removedTables = [];
165
        }
166 1
        return $diff;
167
    }
168
169
    private function process_updates(array $to_update, OutputInterface $output, $force, $delete)
170
    {
171
        $em = connection::get_em();
172
        $conn = $em->getConnection();
173
        $tool = new SchemaTool($em);
174
        $from = $conn->createSchemaManager()->introspectSchema();
175
        $to = $tool->getSchemaFromMetadata($to_update);
176
177
        $diff = self::diff($from, $to, $delete);
178
179
        $sql = $conn->getDatabasePlatform()->getAlterSchemaSQL($diff);
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