| 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() : void |
|||||
| 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
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
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. 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()); |
|||||
|
0 ignored issues
–
show
The class
Doctrine\Common\Proxy\ProxyGenerator has been deprecated: The ProxyGenerator class is deprecated since doctrine/common 3.5.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 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 | $progress->clear(); |
||||||
| 192 | $output->writeln(' Executing <info>' . $sql_line . '</info>'); |
||||||
| 193 | $progress->display(); |
||||||
| 194 | } |
||||||
| 195 | try { |
||||||
| 196 | $conn->executeQuery($sql_line); |
||||||
| 197 | } catch (\Exception $e) { |
||||||
| 198 | if (!$force) { |
||||||
| 199 | throw $e; |
||||||
| 200 | } |
||||||
| 201 | $progress->clear(); |
||||||
| 202 | $output->writeln('<error>' . $e->getMessage() . '</error>'); |
||||||
| 203 | $progress->display(); |
||||||
| 204 | } |
||||||
| 205 | |||||||
| 206 | $progress->advance(); |
||||||
| 207 | } |
||||||
| 208 | $progress->finish(); |
||||||
| 209 | $output->writeln(''); |
||||||
| 210 | } |
||||||
| 211 | } |
||||||
| 212 |