public function migrate($targets, $plan, $fake)
    {
        if (empty($plan)) {
            $plan = $this->getMigrationPlan($targets);
        }

        $migrationsToRun = $this->getMigrationsFromPlan($plan);

        $targets = [];
        $leaves = $this->loader->graph->getLeafNodes();
        foreach ($leaves as $app => $appLeaves) {
            $targets[$app] = $appLeaves[0];
        }

        // the full plan that would be executed if we to run on a new database
        $fullPlan = $this->getMigrationsFromPlan(
            $this->getMigrationPlan($targets, true)
        );

        // Holds all states right before a migration is applied
        // if the migration is being run.
        /** @var $states ProjectState[][] */
        $states = [];
        $state = ProjectState::createObject();

        //Phase 1 -- create all project states before a migration is (un)applied
        /* @var $migration Migration */
        foreach ($fullPlan as $appName => $appMigrations) {
            foreach ($appMigrations as $migName => $migration) {
                // we use the migration to mutate state
                // after we mutate we remove the migration from the
                // $migrationsToRun list.
                // so if we get to a point where we don't have any more
                // $migrationsToRun break
                // this is to avoid any further mutations by other migrations
                // not in the list.
                if (empty($migrationsToRun)) {
                    break;
                }

                $run = !empty($migrationsToRun[$appName][$migName]);
                if ($run) {
                    $states[$appName][$migName] = $state->deepClone();
                    unset($migrationsToRun[$appName][$migName]);
                }

                // $run will be false if the migration is not in the
                // $migrationsToRun list
                // so there is no need to preserve state else if its in the list
                // we will get a new state object
                // that has been altered by the migration.
                // we do this because we need the object stored in the states
                // array in the condition it was right before
                // the migration was applied.
                // remember in PHP objects are passed by reference.
                $state = $migration->updateState($state, $run);
            }
        }

        // Phase 2 -- Run the migrations
        foreach ($plan as $appName => $actionPlan) {
            foreach ($actionPlan as $mName => $migrationMeta) {
                if ($migrationMeta['unapply']) {
                    $this->unApplyMigration(
                        $states[$appName][$mName],
                        $migrationMeta['migration'],
                        $fake
                    );
                } elseif (false === $migrationMeta['unapply']) {
                    $this->applyMigration(
                        $states[$appName][$mName],
                        $migrationMeta['migration'],
                        $fake
                    );
                }
            }
        }
    }