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
![]() |
|||||||
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
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
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. ![]() |
|||||||
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 |