Completed
Push — master ( c365cd...202f3c )
by Tom
05:19
created

Command/System/Setup/IncrementalCommand.php (8 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace N98\Magento\Command\System\Setup;
4
5
use Exception;
6
use N98\Magento\Command\AbstractMagentoCommand;
7
use ReflectionClass;
8
use ReflectionMethod;
9
use ReflectionProperty;
10
use RuntimeException;
11
use Symfony\Component\Console\Input\InputInterface;
12
use Symfony\Component\Console\Input\InputOption;
13
use Symfony\Component\Console\Output\OutputInterface;
14
15
/**
16
 * Class IncrementalCommand
17
 *
18
 * @package N98\Magento\Command\System\Setup
19
 * @codeCoverageIgnore
20
 */
21
class IncrementalCommand extends AbstractMagentoCommand
22
{
23
    const TYPE_MIGRATION_STRUCTURE = 'structure';
24
    const TYPE_MIGRATION_DATA      = 'data';
25
26
    /**
27
     * @var OutputInterface
28
     */
29
    protected $_output;
30
31
    /**
32
     * @var InputInterface
33
     */
34
    protected $_input;
35
36
    /**
37
     * Holds our copy of the global config.
38
     *
39
     * Loaded to avoid grabbing the cached version, and so
40
     * we still have all our original information when we
41
     * destroy the real configuration
42
     * @var mixed $_secondConfig
43
     */
44
    protected $_secondConfig;
45
46
    protected $_eventStash;
47
48
    /**
49
     * @var array
50
     */
51
    protected $_config;
52
53
    protected function configure()
54
    {
55
        $this
56
            ->setName('sys:setup:incremental')
57
            ->setDescription('List new setup scripts to run, then runs one script')
58
            ->addOption('stop-on-error', null, InputOption::VALUE_NONE, 'Stops execution of script on error')
59
            ->setHelp('Examines an un-cached configuration tree and determines which ' .
60
                'structure and data setup resource scripts need to run, and then runs them.');
61
    }
62
63
    /**
64
     * @param InputInterface  $input
65
     * @param OutputInterface $output
66
     *
67
     * @return int|null|void
68
     */
69
    protected function execute(InputInterface $input, OutputInterface $output)
70
    {
71
        $this->_config = $this->getCommandConfig();
72
73
        //sets output so we can access it from all methods
74
        $this->_setOutput($output);
75
        $this->_setInput($input);
76
        if (false === $this->_init()) {
77
            return;
78
        }
79
        $needsUpdate = $this->_analyzeSetupResourceClasses();
80
81
        if (count($needsUpdate) == 0) {
82
            return;
83
        }
84
        $this->_listDetailedUpdateInformation($needsUpdate);
85
        $this->_runAllStructureUpdates($needsUpdate);
86
        $output->writeln('We have run all the setup resource scripts.');
87
    }
88
89
    protected function _loadSecondConfig()
90
    {
91
        $config = new \Mage_Core_Model_Config;
92
        $config->loadBase(); //get app/etc
93
        $this->_secondConfig = \Mage::getConfig()->loadModulesConfiguration('config.xml', $config);
94
    }
95
96
    /**
97
     * @return array
98
     */
99
    protected function _getAllSetupResourceObjects()
100
    {
101
        $config         = $this->_secondConfig;
102
        $resources      = $config->getNode('global/resources')->children();
103
        $setupResources = array();
104
        foreach ($resources as $name => $resource) {
105
            if (!$resource->setup) {
106
                continue;
107
            }
108
            $className = 'Mage_Core_Model_Resource_Setup';
109
            if (isset($resource->setup->class)) {
110
                $className = $resource->setup->getClassName();
111
            }
112
113
            $setupResources[$name] = new $className($name);
114
        }
115
        return $setupResources;
116
    }
117
118
    /**
119
     * @return \Mage_Core_Model_Resource
120
     */
121
    protected function _getResource()
122
    {
123
        return \Mage::getResourceSingleton('core/resource');
124
    }
125
126
    /**
127
     * @param \Mage_Core_Model_Resource_Setup $setupResource
128
     * @param array                           $args
129
     *
130
     * @return array|mixed
131
     */
132 View Code Duplication
    protected function _getAvaiableDbFilesFromResource($setupResource, $args = array())
133
    {
134
        $result = $this->_callProtectedMethodFromObject('_getAvailableDbFiles', $setupResource, $args);
0 ignored issues
show
'_getAvailableDbFiles' is of type string, but the function expects a object<ReflectionMethod>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
135
136
        //an install runs the install script first, then any upgrades
137
        if ($args[0] == \Mage_Core_Model_Resource_Setup::TYPE_DB_INSTALL) {
138
            $args[0] = \Mage_Core_Model_Resource_Setup::TYPE_DB_UPGRADE;
139
            $args[1] = $result[0]['toVersion'];
140
            $result  = array_merge(
141
                $result,
142
                $this->_callProtectedMethodFromObject('_getAvailableDbFiles', $setupResource, $args)
0 ignored issues
show
'_getAvailableDbFiles' is of type string, but the function expects a object<ReflectionMethod>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
143
            );
144
        }
145
146
        return $result;
147
    }
148
149
    /**
150
     * @param \Mage_Core_Model_Resource_Setup $setupResource
151
     * @param array                           $args
152
     *
153
     * @return array|mixed
154
     */
155 View Code Duplication
    protected function _getAvaiableDataFilesFromResource($setupResource, $args = array())
156
    {
157
        $result = $this->_callProtectedMethodFromObject('_getAvailableDataFiles', $setupResource, $args);
0 ignored issues
show
'_getAvailableDataFiles' is of type string, but the function expects a object<ReflectionMethod>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
158
        if ($args[0] == \Mage_Core_Model_Resource_Setup::TYPE_DATA_INSTALL) {
159
            $args[0] = \Mage_Core_Model_Resource_Setup::TYPE_DATA_UPGRADE;
160
            $args[1] = $result[0]['toVersion'];
161
            $result  = array_merge(
162
                $result,
163
                $this->_callProtectedMethodFromObject('_getAvailableDbFiles', $setupResource, $args)
0 ignored issues
show
'_getAvailableDbFiles' is of type string, but the function expects a object<ReflectionMethod>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
164
            );
165
        }
166
167
        return $result;
168
    }
169
170
    /**
171
     * @param ReflectionMethod $method
172
     * @param Object            $object
173
     * @param array             $args
174
     *
175
     * @return mixed
176
     */
177
    protected function _callProtectedMethodFromObject(ReflectionMethod $method, $object, $args = array())
0 ignored issues
show
The parameter $method is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
178
    {
179
        $r = new ReflectionClass($object);
180
        $m = $r->getMethod('_getAvailableDbFiles');
181
        $m->setAccessible(true);
182
        return $m->invokeArgs($object, $args);
183
    }
184
185
    /**
186
     * @param string $property
187
     * @param Object $object
188
     * @param mixed  $value
189
     */
190 View Code Duplication
    protected function _setProtectedPropertyFromObjectToValue($property, $object, $value)
191
    {
192
        $r = new ReflectionClass($object);
193
        $p = $r->getProperty($property);
194
        $p->setAccessible(true);
195
        $p->setValue($object, $value);
196
    }
197
198
    /**
199
     * @param ReflectionProperty $property
200
     * @param Object              $object
201
     *
202
     * @return mixed
203
     */
204 View Code Duplication
    protected function _getProtectedPropertyFromObject($property, $object)
205
    {
206
        $r = new ReflectionClass($object);
207
        $p = $r->getProperty($property);
208
        $p->setAccessible(true);
209
        return $p->getValue($object);
210
    }
211
212
    /**
213
     * @param string $name
214
     *
215
     * @return string
216
     */
217
    protected function _getDbVersionFromName($name)
218
    {
219
        return $this->_getResource()->getDbVersion($name);
220
    }
221
222
    /**
223
     * @param string $name
224
     *
225
     * @return string
226
     */
227
    protected function _getDbDataVersionFromName($name)
228
    {
229
        return $this->_getResource()->getDataVersion($name);
230
    }
231
232
    /**
233
     * @param Object $object
234
     *
235
     * @return mixed
236
     */
237
    protected function _getConfiguredVersionFromResourceObject($object)
238
    {
239
        $moduleConfig = $this->_getProtectedPropertyFromObject('_moduleConfig', $object);
0 ignored issues
show
'_moduleConfig' is of type string, but the function expects a object<ReflectionProperty>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
240
241
        return $moduleConfig->version;
242
    }
243
244
    /**
245
     * @param bool|array $setupResources
246
     *
247
     * @return array
248
     */
249
    protected function _getAllSetupResourceObjectThatNeedUpdates($setupResources = false)
250
    {
251
        $setupResources = $setupResources ? $setupResources : $this->_getAllSetupResourceObjects();
252
        $needsUpdate    = array();
253
        foreach ($setupResources as $name => $setupResource) {
254
            $db_ver      = $this->_getDbVersionFromName($name);
255
            $db_data_ver = $this->_getDbDataVersionFromName($name);
256
            $config_ver  = $this->_getConfiguredVersionFromResourceObject($setupResource);
257
258
            if (
259
                (string) $config_ver == (string) $db_ver && //structure
260
                (string) $config_ver == (string) $db_data_ver //data
261
            ) {
262
                continue;
263
            }
264
            $needsUpdate[$name] = $setupResource;
265
        }
266
267
        return $needsUpdate;
268
    }
269
270
    /**
271
     * @param string $message
272
     */
273
    protected function _log($message)
274
    {
275
        $this->_output->writeln($message);
276
    }
277
278
    /**
279
     * @param OutputInterface $output
280
     */
281
    protected function _setOutput(OutputInterface $output)
282
    {
283
        $this->_output = $output;
284
    }
285
286
    /**
287
     * @param InputInterface $input
288
     */
289
    protected function _setInput(InputInterface $input)
290
    {
291
        $this->_input = $input;
292
    }
293
294
    /**
295
     * @param array $needsUpdate
296
     */
297
    protected function _outputUpdateInformation(array $needsUpdate)
298
    {
299
        $output = $this->_output;
300
        foreach ($needsUpdate as $name => $setupResource) {
301
            $dbVersion     = $this->_getDbVersionFromName($name);
302
            $dbDataVersion = $this->_getDbDataVersionFromName($name);
303
            $configVersion = $this->_getConfiguredVersionFromResourceObject($setupResource);
304
305
            $moduleConfig = $this->_getProtectedPropertyFromObject('_moduleConfig', $setupResource);
0 ignored issues
show
'_moduleConfig' is of type string, but the function expects a object<ReflectionProperty>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
306
            $output->writeln(
307
                array('+--------------------------------------------------+',
308
                    'Resource Name:             ' . $name,
309
                    'For Module:                ' . $moduleConfig->getName(),
310
                    'Class:                     ' . get_class($setupResource),
311
                    'Current Structure Version: ' . $dbVersion,
312
                    'Current Data Version:      ' . $dbDataVersion,
313
                    'Configured Version:        ' . $configVersion
314
                )
315
            );
316
317
            $args = array(
318
                '',
319
                (string) $dbVersion,
320
                (string) $configVersion,
321
            );
322
323
            $args[0] = $dbVersion ? \Mage_Core_Model_Resource_Setup::TYPE_DB_UPGRADE : \Mage_Core_Model_Resource_Setup::TYPE_DB_INSTALL;
324
            $output->writeln('Structure Files to Run: ');
325
            $filesStructure = $this->_getAvaiableDbFilesFromResource($setupResource, $args);
326
            $this->_outputFileArray($filesStructure, $output);
327
            $output->writeln("");
328
329
            $args[0] = $dbVersion ? \Mage_Core_Model_Resource_Setup::TYPE_DATA_UPGRADE : \Mage_Core_Model_Resource_Setup::TYPE_DATA_INSTALL;
330
            $output->writeln('Data Files to Run: ');
331
            $filesData = $this->_getAvaiableDataFilesFromResource($setupResource, $args);
332
            $this->_outputFileArray($filesData, $output);
333
            $output->writeln('+--------------------------------------------------+');
334
            $output->writeln('');
335
        }
336
    }
337
338
    /**
339
     * @param array $files
340
     */
341
    protected function _outputFileArray($files)
342
    {
343
        $output = $this->_output;
344
        if (count($files) == 0) {
345
            $output->writeln('No files found');
346
            return;
347
        }
348
        foreach ($files as $file) {
349
            $output->writeln(str_replace(\Mage::getBaseDir() . '/', '', $file['fileName']));
350
        }
351
    }
352
353
    /**
354
     * Runs a single named setup resource
355
     *
356
     * This method nukes the global/resources node in the global config
357
     * and then repopulates it with **only** the $name resource. Then it
358
     * calls the standard Magento `applyAllUpdates` method.
359
     *
360
     * The benefit of this approach is we don't need to recreate the entire
361
     * setup resource running logic ourselves.  Yay for code reuse
362
     *
363
     * The downside is we should probably exit quickly, as anything else that
364
     * uses the global/resources node is going to behave weird.
365
     *
366
     * @todo     Repopulate global config after running?  Non trivial since setNode escapes strings
367
     *
368
     * @param string $name
369
     * @param array  $needsUpdate
370
     * @param string $type
371
     *
372
     * @throws RuntimeException
373
     * @internal param $string
374
     */
375
    protected function _runNamedSetupResource($name, array $needsUpdate, $type)
376
    {
377
        $output = $this->_output;
378
        if (!in_array($type, array(self::TYPE_MIGRATION_STRUCTURE, self::TYPE_MIGRATION_DATA))) {
379
            throw new RuntimeException('Invalid Type [' . $type . ']: structure, data is valid');
380
        }
381
382
        if (!array_key_Exists($name, $needsUpdate)) {
383
            $output->writeln('<error>No updates to run for ' . $name . ', skipping </error>');
384
            return;
385
        }
386
387
        //remove all other setup resources from configuration
388
        //(in memory, do not persist this to cache)
389
        $realConfig = \Mage::getConfig();
390
        $resources  = $realConfig->getNode('global/resources');
391
        foreach ($resources->children() as $resource) {
392
            if (!$resource->setup) {
393
                continue;
394
            }
395
            unset($resource->setup);
396
        }
397
        //recreate our specific node in <global><resources></resource></global>
398
        //allows for theoretical multiple runs
399
        $setupResourceConfig = $this->_secondConfig->getNode('global/resources/' . $name);
400
        $moduleName          = $setupResourceConfig->setup->module;
401
        $className           = $setupResourceConfig->setup->class;
402
403
        $specificResource = $realConfig->getNode('global/resources/' . $name);
404
        $setup            = $specificResource->addChild('setup');
405
        if ($moduleName) {
406
            $setup->addChild('module', $moduleName);
407
        } else {
408
            $output->writeln('<error>No module node configured for ' . $name . ', possible configuration error </error>');
409
        }
410
411
        if ($className) {
412
            $setup->addChild('class', $className);
413
        }
414
415
        //and finally, RUN THE UPDATES
416
        try {
417
            ob_start();
418
            if ($type == self::TYPE_MIGRATION_STRUCTURE) {
419
                $this->_stashEventContext();
420
                \Mage_Core_Model_Resource_Setup::applyAllUpdates();
421
                $this->_restoreEventContext();
422
            } else if ($type == self::TYPE_MIGRATION_DATA) {
423
                \Mage_Core_Model_Resource_Setup::applyAllDataUpdates();
424
            }
425
            $exceptionOutput = ob_get_clean();
426
            $this->_output->writeln($exceptionOutput);
427
        } catch (Exception $e) {
428
            $exceptionOutput = ob_get_clean();
429
            $this->_processExceptionDuringUpdate($e, $name, $exceptionOutput);
430
            if ($this->_input->getOption('stop-on-error')) {
431
                throw new RuntimeException('Setup stopped with errors');
432
            }
433
        }
434
    }
435
436
    /**
437
     * @param Exception                      $e
438
     * @param string                          $name
439
     * @param string                          $magentoExceptionOutput
440
     */
441
    protected function _processExceptionDuringUpdate(
442
        Exception $e,
443
        $name,
444
        $magentoExceptionOutput
445
    )
446
    {
447
        $output = $this->_output;
448
        $output->writeln('<error>Magento encountered an error while running the following ' .
449
            'setup resource.</error>');
450
        $output->writeln("\n    $name \n");
451
452
        $output->writeln("<error>The Good News:</error> You know the error happened, and the database   \n" .
453
            "information below will  help you fix this error!");
454
        $output->writeln("");
455
456
        $output->writeln(
457
            "<error>The Bad News:</error> Because Magento/MySQL can't run setup resources \n" .
458
            "transactionallyyour database is now in an half upgraded, invalid\n" .
459
            "state.  Even if you fix the error, new errors may occur due to \n" .
460
            "this half upgraded, invalid state.");
461
        $output->writeln("");
462
463
        $output->writeln("What to Do: ");
464
        $output->writeln("1. Figure out why the error happened, and manually fix your \n   " .
465
            "database and/or system so it won't happen again.");
466
        $output->writeln("2. Restore your database from backup.");
467
        $output->writeln("3. Re-run the scripts.");
468
        $output->writeln("");
469
470
        $output->writeln("Exception Message:");
471
        $output->writeln($e->getMessage());
472
        $output->writeln("");
473
474
        if ($magentoExceptionOutput) {
475
            $this->getHelper('dialog')->askAndValidate(
476
                $output,
477
                '<question>Press Enter to view raw Magento error text:</question> '
478
            );
479
            $output->writeln("Magento Exception Error Text:");
480
            echo $magentoExceptionOutput, "\n"; //echoing (vs. writeln) to avoid seg fault
481
        }
482
    }
483
484
    /**
485
     * @return bool
486
     */
487
    protected function _checkCacheSettings()
488
    {
489
        $output   = $this->_output;
490
        $allTypes = \Mage::app()->useCache();
491
        if ($allTypes['config'] !== '1') {
492
            $output->writeln('<error>ERROR: Config Cache is Disabled</error>');
493
            $output->writeln('This command will not run with the configuration cache disabled.');
494
            $output->writeln('Please change your Magento settings at System -> Cache Management');
495
            $output->writeln('');
496
497
            return false;
498
        }
499
        return true;
500
    }
501
502
    /**
503
     * @param string $toUpdate
504
     * @param array  $needsUpdate
505
     * @param string $type
506
     */
507
    protected function _runStructureOrDataScripts($toUpdate, array $needsUpdate, $type)
508
    {
509
        $output = $this->_output;
510
        $output->writeln('The next ' . $type . ' update to run is <info>' . $toUpdate . '</info>');
511
        $this->getHelper('dialog')->askAndValidate($output,
512
            '<question>Press Enter to Run this update: </question>');
513
514
        $start = microtime(true);
515
        $this->_runNamedSetupResource($toUpdate, $needsUpdate, $type);
516
        $time_ran = microtime(true) - $start;
517
        $output->writeln('');
518
        $output->writeln(ucwords($type) . ' update <info>' . $toUpdate . '</info> complete.');
519
        $output->writeln('Ran in ' . floor($time_ran * 1000) . 'ms');
520
    }
521
522
    /**
523
     * @return array
524
     */
525
    protected function _getTestedVersions()
526
    {
527
        return $this->_config['tested-versions'];
528
    }
529
530
    protected function _restoreEventContext()
531
    {
532
        $app = \Mage::app();
533
        $this->_setProtectedPropertyFromObjectToValue('_events', $app, $this->_eventStash);
534
    }
535
536
    protected function _stashEventContext()
537
    {
538
        $app               = \Mage::app();
539
        $events            = $this->_getProtectedPropertyFromObject('_events', $app);
0 ignored issues
show
'_events' is of type string, but the function expects a object<ReflectionProperty>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
540
        $this->_eventStash = $events;
541
        $this->_setProtectedPropertyFromObjectToValue('_events', $app, array());
542
    }
543
544
    /**
545
     * @return bool
546
     */
547
    protected function _init()
548
    {
549
        //bootstrap magento
550
        $this->detectMagento($this->_output);
551
        if (!$this->initMagento()) {
552
            return false;
553
        }
554
555
        //don't run if cache is off.  If cache is off that means
556
        //setup resource will run automagically
557
        if (!$this->_checkCacheSettings()) {
558
            return false;
559
        }
560
561
        //load a second, not cached, config.xml tree
562
        $this->_loadSecondConfig();
563
        return true;
564
    }
565
566
    /**
567
     * @return array
568
     */
569
    protected function _analyzeSetupResourceClasses()
570
    {
571
        $output = $this->_output;
572
        $this->writeSection($output, 'Analyzing Setup Resource Classes');
573
        $setupResources = $this->_getAllSetupResourceObjects();
574
        $needsUpdate    = $this->_getAllSetupResourceObjectThatNeedUpdates($setupResources);
575
576
        $output->writeln('Found <info>' . count($setupResources) . '</info> configured setup resource(s)</info>');
577
        $output->writeln('Found <info>' . count($needsUpdate) . '</info> setup resource(s) which need an update</info>');
578
579
        return $needsUpdate;
580
    }
581
582
    /**
583
     * @param array $needsUpdate
584
     */
585
    protected function _listDetailedUpdateInformation(array $needsUpdate)
586
    {
587
        $output = $this->_output;
588
        $this->getHelper('dialog')->askAndValidate($output,
589
            '<question>Press Enter to View Update Information: </question>');
590
591
        $this->writeSection($output, 'Detailed Update Information');
592
        $this->_outputUpdateInformation($needsUpdate, $output);
593
    }
594
595
    /**
596
     * @param array $needsUpdate
597
     */
598
    protected function _runAllStructureUpdates(array $needsUpdate)
599
    {
600
        $output = $this->_output;
601
        $this->writeSection($output, "Run Structure Updates");
602
        $output->writeln('All structure updates run before data updates.');
603
        $output->writeln('');
604
605
        $c     = 1;
606
        $total = count($needsUpdate);
607 View Code Duplication
        foreach ($needsUpdate as $key => $value) {
608
            $toUpdate = $key;
609
            $this->_runStructureOrDataScripts($toUpdate, $needsUpdate, self::TYPE_MIGRATION_STRUCTURE);
610
            $output->writeln("($c of $total)");
611
            $output->writeln('');
612
            $c++;
613
        }
614
615
        $this->writeSection($output, "Run Data Updates");
616
        $c     = 1;
617
        $total = count($needsUpdate);
618 View Code Duplication
        foreach ($needsUpdate as $key => $value) {
619
            $toUpdate = $key;
620
            $this->_runStructureOrDataScripts($toUpdate, $needsUpdate, self::TYPE_MIGRATION_DATA);
621
            $output->writeln("($c of $total)");
622
            $output->writeln('');
623
            $c++;
624
        }
625
    }
626
}
627