schema::execute()   F
last analyzed

Complexity

Conditions 17
Paths 359

Size

Total Lines 78
Code Lines 55

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 39
CRAP Score 22.3296

Importance

Changes 12
Bugs 0 Features 1
Metric Value
cc 17
eloc 55
c 12
b 0
f 1
nc 359
nop 2
dl 0
loc 78
ccs 39
cts 53
cp 0.7358
crap 22.3296
rs 2.3958

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 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 1
    protected function configure()
35
    {
36 1
        $this->setName('schema')
37 1
            ->setDescription('(Re)generate mapping information from MgdSchema XMLs')
38 1
            ->addArgument('config', InputArgument::OPTIONAL, 'Full path to midgard-portable config file')
39 1
            ->addOption('force', null, InputOption::VALUE_NONE, 'Ignore errors from DB')
40 1
            ->addOption('delete', null, InputOption::VALUE_NONE, 'Delete columns/tables that are not defined in mgdschema');
41
    }
42
43 1
    protected function execute(InputInterface $input, OutputInterface $output) : int
44
    {
45 1
        if (!$this->connected) {
46 1
            $path = $input->getArgument('config');
47 1
            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 1
            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 1
            connection::set_autostart(false);
60 1
            require $path;
61
        }
62
63 1
        $mgd_config = midgard_connection::get_instance()->config;
64 1
        $mgdschema_file = $mgd_config->vardir . '/mgdschema_classes.php';
65 1
        if (   file_exists($mgdschema_file)
66 1
            && !unlink($mgdschema_file)) {
67
            throw new \RuntimeException('Could not unlink ' . $mgdschema_file);
68
        }
69 1
        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 1
        if (!file_exists($mgd_config->blobdir . '/0/0')) {
75 1
            $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 1
        connection::startup();
78 1
        $em = connection::get_em();
79 1
        connection::invalidate_cache();
80 1
        $cms = $em->getMetadataFactory()->getAllMetadata();
81
82
        // create storage
83 1
        if (    !midgard_storage::create_base_storage()
84 1
             && 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 1
        $force = $input->getOption('force');
88 1
        $to_update = [];
89 1
        $to_create = [];
90
91 1
        $sm = $em->getConnection()->createSchemaManager();
92 1
        foreach ($cms as $cm) {
93 1
            if ($sm->tablesExist([$cm->getTableName()])) {
94 1
                $to_update[] = $cm;
95
            } else {
96 1
                $to_create[] = $cm;
97
            }
98
        }
99
100 1
        if (!empty($to_create)) {
101 1
            $output->writeln('Creating <info>' . count($to_create) . '</info> new tables');
102 1
            $tool = new SchemaTool($em);
103
            try {
104 1
                $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 1
        if (!empty($to_update)) {
113 1
            $delete = $input->getOption('delete');
114 1
            $this->process_updates($to_update, $output, $force, $delete);
115
        }
116 1
        $output->writeln('Generating proxies');
117 1
        $this->generate_proxyfiles($cms);
118
119 1
        $output->writeln('Done');
120 1
        return Command::SUCCESS;
121
    }
122
123 1
    private function generate_proxyfiles(array $cms)
124
    {
125 1
        $em = connection::get_em();
126 1
        $generator = new ProxyGenerator($em->getConfiguration()->getProxyDir(), $em->getConfiguration()->getProxyNamespace());
127 1
        $generator->setPlaceholder('baseProxyInterface', 'Doctrine\ORM\Proxy\Proxy');
128
129 1
        foreach ($cms as $cm) {
130 1
            $filename = $generator->getProxyFileName($cm->getName());
131 1
            if (file_exists($filename)) {
132 1
                unlink($filename);
133
            }
134 1
            $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 2
    public static function diff(dbal_schema $from, dbal_schema $to, bool $delete = false) : SchemaDiff
143
    {
144 2
        $comparator = connection::get_em()->getConnection()->createSchemaManager()->createComparator();
145 2
        $diff = $comparator->compareSchemas($from, $to);
146
147 2
        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 2
        if (!$delete) {
164 2
            $diff->removedTables = [];
165
        }
166 2
        return $diff;
167
    }
168
169 1
    private function process_updates(array $to_update, OutputInterface $output, $force, $delete)
170
    {
171 1
        $em = connection::get_em();
172 1
        $conn = $em->getConnection();
173 1
        $tool = new SchemaTool($em);
174 1
        $from = $conn->createSchemaManager()->introspectSchema();
175 1
        $to = $tool->getSchemaFromMetadata($to_update);
176
177 1
        $diff = self::diff($from, $to, $delete);
178
179 1
        $sql = $conn->getDatabasePlatform()->getAlterSchemaSQL($diff);
180
181 1
        if (empty($sql)) {
182 1
            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