| Total Complexity | 53 | 
| Total Lines | 346 | 
| Duplicated Lines | 0 % | 
| Changes | 0 | ||
Complex classes like anonymous//bin/migrate.php$0 often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use anonymous//bin/migrate.php$0, and based on these observations, apply Extract Interface, too.
| 1 | #!/usr/bin/php  | 
            ||
| 24 | (new class ($argv, $opt) { | 
            ||
| 25 | |||
| 26 | private array $argv;  | 
            ||
| 27 | |||
| 28 | private array $opt;  | 
            ||
| 29 | |||
| 30 | private DB $db;  | 
            ||
| 31 | |||
| 32 | private Schema $schema;  | 
            ||
| 33 | |||
| 34 | public function __construct(array $argv, array $opt)  | 
            ||
| 35 |     { | 
            ||
| 36 | $this->argv = $argv;  | 
            ||
| 37 | $opt['connection'] ??= 'default';  | 
            ||
| 38 | $opt['config'] ??= 'db.config.php';  | 
            ||
| 39 | $this->opt = $opt;  | 
            ||
| 40 | $this->db = DB::fromConfig($opt['connection'], $opt['config']);  | 
            ||
| 41 | $realLogger = $this->db->getLogger();  | 
            ||
| 42 | $this->db->setLogger(fn($sql) => $this->_stdout($sql) or $realLogger($sql));  | 
            ||
| 43 | $this->schema = $this->db->getSchema();  | 
            ||
| 44 | }  | 
            ||
| 45 | |||
| 46 | private function _stderr(string $text): void  | 
            ||
| 47 |     { | 
            ||
| 48 |         fputs(STDERR, "{$text}\n\n"); | 
            ||
| 49 | }  | 
            ||
| 50 | |||
| 51 | private function _stdout(string $text): void  | 
            ||
| 52 |     { | 
            ||
| 53 |         echo "{$text}\n\n"; | 
            ||
| 54 | }  | 
            ||
| 55 | |||
| 56 | private function _usage_exit(): void  | 
            ||
| 57 |     { | 
            ||
| 58 | $this->_stderr(<<< USAGE  | 
            ||
| 59 | |||
| 60 |         $ php {$this->argv[0]} [OPTIONS] ACTION | 
            ||
| 61 | |||
| 62 | OPTIONS:  | 
            ||
| 63 | |||
| 64 | --config=db.config.php  | 
            ||
| 65 | |||
| 66 | Chooses the configuration file.  | 
            ||
| 67 | |||
| 68 | --connection=default  | 
            ||
| 69 | |||
| 70 | Chooses the connection from the configuration file.  | 
            ||
| 71 | |||
| 72 | ACTIONS:  | 
            ||
| 73 | |||
| 74 | -h  | 
            ||
| 75 | --help  | 
            ||
| 76 | |||
| 77 | Prints this usage information to STDERR and calls exit(1)  | 
            ||
| 78 | |||
| 79 | --status  | 
            ||
| 80 | |||
| 81 | Outputs the current migration sequence.  | 
            ||
| 82 | |||
| 83 | --up=  | 
            ||
| 84 | --down=  | 
            ||
| 85 | |||
| 86 | Migrates up or down, optionally to a target sequence.  | 
            ||
| 87 | For upgrades, the default target is all the way.  | 
            ||
| 88 | For downgrades, the default target is the previous sequence.  | 
            ||
| 89 | |||
| 90 | --record=<CLASS>  | 
            ||
| 91 | --junction=<INTERFACE>  | 
            ||
| 92 | |||
| 93 | The FQN of an annotated class or interface,  | 
            ||
| 94 | which the DB instance can use to return Record or Junction access.  | 
            ||
| 95 | |||
| 96 | The access object's tables are inspected against the database,  | 
            ||
| 97 | and appropriate migration is then generated into the migrations  | 
            ||
| 98 | directory. Statically generated migrations preserve history.  | 
            ||
| 99 | |||
| 100 | To make CLI execution easier, forward-slashes in the FQN are  | 
            ||
| 101 | converted to namespace separators.  | 
            ||
| 102 | USAGE  | 
            ||
| 103 | );  | 
            ||
| 104 | exit(1);  | 
            ||
| 105 | }  | 
            ||
| 106 | |||
| 107 | /**  | 
            ||
| 108 | * @uses h()  | 
            ||
| 109 | * @uses help()  | 
            ||
| 110 | * @uses status()  | 
            ||
| 111 | * @uses up()  | 
            ||
| 112 | * @uses down()  | 
            ||
| 113 | * @uses record()  | 
            ||
| 114 | * @uses junction()  | 
            ||
| 115 | */  | 
            ||
| 116 | public function _exec(): void  | 
            ||
| 117 |     { | 
            ||
| 118 |         foreach (['h', 'help', 'status', 'up', 'down', 'record', 'junction'] as $action) { | 
            ||
| 119 |             if (isset($this->opt[$action])) { | 
            ||
| 120 |                 $this->{$action}($this->opt[$action] ?: null); | 
            ||
| 121 | return;  | 
            ||
| 122 | }  | 
            ||
| 123 | }  | 
            ||
| 124 | $this->_usage_exit();  | 
            ||
| 125 | }  | 
            ||
| 126 | |||
| 127 | private function h(): void  | 
            ||
| 128 |     { | 
            ||
| 129 | $this->_usage_exit();  | 
            ||
| 130 | }  | 
            ||
| 131 | |||
| 132 | private function help(): void  | 
            ||
| 133 |     { | 
            ||
| 134 | $this->_usage_exit();  | 
            ||
| 135 | }  | 
            ||
| 136 | |||
| 137 | private function status(): void  | 
            ||
| 138 |     { | 
            ||
| 139 | $migrator = $this->db->getMigrator();  | 
            ||
| 140 | $transaction = $this->db->newTransaction();  | 
            ||
| 141 | $current = $migrator->getCurrent() ?? 'NONE';  | 
            ||
| 142 |         $this->_stdout("-- Current Migration State: {$current}"); | 
            ||
| 143 | unset($transaction);  | 
            ||
| 144 | }  | 
            ||
| 145 | |||
| 146 | private function up(?string $to): void  | 
            ||
| 147 |     { | 
            ||
| 148 | $migrator = $this->db->getMigrator();  | 
            ||
| 149 | $transaction = $this->db->newTransaction();  | 
            ||
| 150 | $current = $migrator->getCurrent();  | 
            ||
| 151 | $currentString = $current ?: 'NONE';  | 
            ||
| 152 |         if ($to) { | 
            ||
| 153 |             $this->_stdout("-- Upgrading from \"{$currentString}\" to \"{$to}\" ..."); | 
            ||
| 154 |         } else { | 
            ||
| 155 |             $this->_stdout("-- Upgrading ALL starting from \"{$currentString}\" ..."); | 
            ||
| 156 | }  | 
            ||
| 157 | sleep(3); // time to cancel  | 
            ||
| 158 |         if ($current === $migrator->up($to ?: null)) { | 
            ||
| 159 |             $this->_stdout("-- Nothing to do."); | 
            ||
| 160 |         } else { | 
            ||
| 161 | $transaction->commit();  | 
            ||
| 162 | }  | 
            ||
| 163 | }  | 
            ||
| 164 | |||
| 165 | private function down(?string $to): void  | 
            ||
| 166 |     { | 
            ||
| 167 | $migrator = $this->db->getMigrator();  | 
            ||
| 168 | $transaction = $this->db->newTransaction();  | 
            ||
| 169 | $current = $migrator->getCurrent();  | 
            ||
| 170 | $currentString = $current ?: 'NONE';  | 
            ||
| 171 |         if ($to) { | 
            ||
| 172 |             $this->_stdout("-- Downgrading from \"{$currentString}\" to \"{$to}\" ..."); | 
            ||
| 173 |         } else { | 
            ||
| 174 |             $this->_stdout("-- Downgrading once from \"{$currentString}\" ..."); | 
            ||
| 175 | }  | 
            ||
| 176 | sleep(3); // time to cancel  | 
            ||
| 177 |         if ($current === $migrator->down($to ?: null)) { | 
            ||
| 178 |             $this->_stdout("-- Nothing to do."); | 
            ||
| 179 |         } else { | 
            ||
| 180 | $transaction->commit();  | 
            ||
| 181 | }  | 
            ||
| 182 | }  | 
            ||
| 183 | |||
| 184 | private function _toClass(string $path): string  | 
            ||
| 185 |     { | 
            ||
| 186 |         return str_replace('/', '\\', $path); | 
            ||
| 187 | }  | 
            ||
| 188 | |||
| 189 | private function record(string $class): void  | 
            ||
| 190 |     { | 
            ||
| 191 | $class = $this->_toClass($class) or $this->_usage_exit();  | 
            ||
| 192 | $record = $this->db->getRecord($class);  | 
            ||
| 193 | $up = [];  | 
            ||
| 194 | $down = [];  | 
            ||
| 195 |         if (!$this->schema->getTable($record)) { | 
            ||
| 196 | $this->record_create($record, $up, $down);  | 
            ||
| 197 |         } else { | 
            ||
| 198 | $this->record_add_columns($record, $up, $down);  | 
            ||
| 199 | $this->record_drop_columns($record, $up, $down);  | 
            ||
| 200 | }  | 
            ||
| 201 | $this->record_create_eav($record, $up, $down);  | 
            ||
| 202 | $this->write($class, $up, $down);  | 
            ||
| 203 | }  | 
            ||
| 204 | |||
| 205 | private function record_create(Record $record, &$up, &$down)  | 
            ||
| 206 |     { | 
            ||
| 207 | $serializer = $record->getSerializer();  | 
            ||
| 208 | |||
| 209 | // define the table  | 
            ||
| 210 | $columns = [];  | 
            ||
| 211 |         foreach ($serializer->getStorageTypes() as $column => $type) { | 
            ||
| 212 | $T_CONST = Schema::T_CONST_NAMES[$type];  | 
            ||
| 213 |             if ($serializer->isNullable($column)) { | 
            ||
| 214 | $T_CONST .= '_NULL';  | 
            ||
| 215 | }  | 
            ||
| 216 |             $columns[$column] = "'{$column}' => Schema::{$T_CONST}"; | 
            ||
| 217 | }  | 
            ||
| 218 | $columns['id'] = "'id' => Schema::T_AUTOINCREMENT";  | 
            ||
| 219 |         $columns = "[\n\t\t\t" . implode(",\n\t\t\t", $columns) . "\n\t\t]"; | 
            ||
| 220 |         $up[] = "\$schema->createTable('{$record}', {$columns});"; | 
            ||
| 221 |         $down[] = "\$schema->dropTable('{$record}');"; | 
            ||
| 222 | |||
| 223 | // add unique constraints  | 
            ||
| 224 |         foreach ($serializer->getUnique() as $column) { | 
            ||
| 225 |             $up[] = "\$schema->addUniqueKey('{$record}', ['{$column}']);"; | 
            ||
| 226 |             $down[] = "\$schema->dropUniqueKey('{$record}', ['{$column}']);"; | 
            ||
| 227 | }  | 
            ||
| 228 |         foreach ($serializer->getUniqueGroups() as $columns) { | 
            ||
| 229 |             $columns = implode("', '", $columns); | 
            ||
| 230 |             $up[] = "\$schema->addUniqueKey('{$record}', ['{$columns}']);"; | 
            ||
| 231 |             $down[] = "\$schema->dropUniqueKey('{$record}', ['{$columns}']);"; | 
            ||
| 232 | }  | 
            ||
| 233 | }  | 
            ||
| 234 | |||
| 235 | private function record_create_eav(Record $record, &$up, &$down)  | 
            ||
| 236 |     { | 
            ||
| 237 |         foreach ($record->getEav() as $eav) { | 
            ||
| 238 |             if (!$this->schema->getTable($eav)) { | 
            ||
| 239 | $T_CONST = Schema::T_CONST_NAMES[$eav->getType()];  | 
            ||
| 240 | $columns = [  | 
            ||
| 241 | "'entity' => Schema::T_INT | Schema::I_PRIMARY",  | 
            ||
| 242 | "'attribute' => Schema::T_STRING | Schema::I_PRIMARY",  | 
            ||
| 243 |                     "'value' => Schema::{$T_CONST}" | 
            ||
| 244 | ];  | 
            ||
| 245 |                 $columns = "[\n\t\t\t" . implode(",\n\t\t\t", $columns) . "\n\t\t]"; | 
            ||
| 246 |                 $foreign = "[\n\t\t\t'entity' => \$schema['{$record}']['id']\n\t\t]"; | 
            ||
| 247 |                 $up[] = "\$schema->createTable('{$eav}', {$columns}, {$foreign});"; | 
            ||
| 248 |                 $down[] = "\$schema->dropTable('{$eav}');"; | 
            ||
| 249 | }  | 
            ||
| 250 | }  | 
            ||
| 251 | }  | 
            ||
| 252 | |||
| 253 | private function record_add_columns(Record $record, &$up, &$down)  | 
            ||
| 254 |     { | 
            ||
| 255 | $columns = $this->schema->getColumnInfo($record);  | 
            ||
| 256 | $serializer = $record->getSerializer();  | 
            ||
| 257 |         foreach ($serializer->getStorageTypes() as $property => $type) { | 
            ||
| 258 |             if (!isset($columns[$property])) { | 
            ||
| 259 | $T_CONST = Schema::T_CONST_NAMES[$type];  | 
            ||
| 260 |                 if ($serializer->isNullable($property)) { | 
            ||
| 261 | $T_CONST .= '_NULL';  | 
            ||
| 262 | }  | 
            ||
| 263 |                 $up[] = "\$schema->addColumn('{$record}', '{$property}', Schema::{$T_CONST});"; | 
            ||
| 264 | }  | 
            ||
| 265 | }  | 
            ||
| 266 |         foreach ($serializer->getUnique() as $property) { | 
            ||
| 267 |             if (!$this->schema->hasUniqueKey($record, [$property])) { | 
            ||
| 268 |                 $up[] = "\$schema->addUniqueKey('{$record}', ['{$property}']);"; | 
            ||
| 269 |                 $down[] = "\$schema->dropUniqueKey('{$record}', ['{$property}']);"; | 
            ||
| 270 | }  | 
            ||
| 271 | }  | 
            ||
| 272 |         foreach ($serializer->getUniqueGroups() as $properties) { | 
            ||
| 273 |             if (!$this->schema->hasUniqueKey($record, $properties)) { | 
            ||
| 274 |                 $properties = "'" . implode("','", $properties) . "'"; | 
            ||
| 275 |                 $up[] = "\$schema->addUniqueKey('{$record}', [{$properties}]);"; | 
            ||
| 276 |                 $down[] = "\$schema->dropUniqueKey('{$record}', [{$properties}]);"; | 
            ||
| 277 | }  | 
            ||
| 278 | }  | 
            ||
| 279 | }  | 
            ||
| 280 | |||
| 281 | private function record_drop_columns(Record $record, &$up, &$down)  | 
            ||
| 282 |     { | 
            ||
| 283 | $columns = $this->schema->getColumnInfo($record);  | 
            ||
| 284 |         foreach ($columns as $column => $info) { | 
            ||
| 285 |             if (!$record[$column]) { | 
            ||
| 286 |                 if ($this->schema->hasUniqueKey($record, [$column])) { | 
            ||
| 287 |                     $up[] = "\$schema->dropUniqueKey('{$record}', ['{$column}']);"; | 
            ||
| 288 |                     $down[] = "\$schema->addUniqueKey('{$record}', ['{$column}']);"; | 
            ||
| 289 | }  | 
            ||
| 290 | }  | 
            ||
| 291 | }  | 
            ||
| 292 | }  | 
            ||
| 293 | |||
| 294 | private function junction(string $class): void  | 
            ||
| 295 |     { | 
            ||
| 296 | $class = $this->_toClass($class) or $this->_usage_exit();  | 
            ||
| 297 | $junction = $this->db->getJunction($class);  | 
            ||
| 298 | $up = [];  | 
            ||
| 299 | $down = [];  | 
            ||
| 300 | |||
| 301 |         if (!$this->schema->getTable($junction)) { | 
            ||
| 302 | $records = $junction->getRecords();  | 
            ||
| 303 | $columns = array_map(  | 
            ||
| 304 |                 fn(string $column) => "'{$column}' => Schema::T_INT | Schema::I_PRIMARY", | 
            ||
| 305 | array_keys($records)  | 
            ||
| 306 | );  | 
            ||
| 307 |             $columns = "[\n\t\t\t" . implode(",\n\t\t\t", $columns) . "\n\t\t]"; | 
            ||
| 308 | $foreign = array_map(  | 
            ||
| 309 |                 fn(string $column, Record $record) => "'{$column}' => \$schema['{$record}']['id']", | 
            ||
| 310 | array_keys($records),  | 
            ||
| 311 | $records  | 
            ||
| 312 | );  | 
            ||
| 313 |             $foreign = "[\n\t\t\t" . implode(",\n\t\t\t", $foreign) . "\n\t\t]"; | 
            ||
| 314 |             $up[] = "\$schema->createTable('{$junction}', {$columns}, {$foreign});"; | 
            ||
| 315 |             $down[] = "\$schema->dropTable('{$junction}');"; | 
            ||
| 316 | }  | 
            ||
| 317 | |||
| 318 | $this->write($class, $up, $down);  | 
            ||
| 319 | }  | 
            ||
| 320 | |||
| 321 | private function write(string $class, array $up, array $down): void  | 
            ||
| 370 | }  | 
            ||
| 371 | |||
| 372 | };  | 
            ||
| 373 | |||
| 374 | MIGRATION  | 
            ||
| 375 | );  | 
            ||
| 376 | }  | 
            ||
| 377 | |||
| 378 | })->_exec();  | 
            ||
| 379 |