Completed
Push — master ( 740609...cb74ad )
by Grégoire
02:47
created

getPlanForVersions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2.0023

Importance

Changes 0
Metric Value
cc 2
eloc 13
nc 2
nop 2
dl 0
loc 21
ccs 11
cts 12
cp 0.9167
crap 2.0023
rs 9.8333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Doctrine\Migrations\Version;
6
7
use Doctrine\Migrations\Exception\MigrationClassNotFound;
8
use Doctrine\Migrations\Exception\NoMigrationsFoundWithCriteria;
9
use Doctrine\Migrations\Exception\NoMigrationsToExecute;
10
use Doctrine\Migrations\Metadata;
11
use Doctrine\Migrations\Metadata\AvailableMigration;
12
use Doctrine\Migrations\Metadata\MigrationPlan;
13
use Doctrine\Migrations\Metadata\MigrationPlanList;
14
use Doctrine\Migrations\Metadata\Storage\MetadataStorage;
15
use Doctrine\Migrations\MigrationRepository;
16
use function array_diff;
17
use function array_filter;
18
use function array_map;
19
use function array_reverse;
20
use function count;
21
use function in_array;
22
use function reset;
23
24
/**
25
 * The MigrationPlanCalculator is responsible for calculating the plan for migrating from the current
26
 * version to another version.
27
 *
28
 * @internal
29
 */
30
final class SortedMigrationPlanCalculator implements MigrationPlanCalculator
31
{
32
    /** @var MigrationRepository */
33
    private $migrationRepository;
34
35
    /** @var MetadataStorage */
36
    private $metadataStorage;
37
38 26
    public function __construct(MigrationRepository $migrationRepository, MetadataStorage $metadataStorage)
39
    {
40 26
        $this->migrationRepository = $migrationRepository;
41 26
        $this->metadataStorage     = $metadataStorage;
42 26
    }
43
44
    /**
45
     * @param Version[] $versions
46
     */
47 5
    public function getPlanForVersions(array $versions, string $direction) : MigrationPlanList
48
    {
49 5
        $migrationsToCheck   = $this->arrangeMigrationsForDirection($direction, $this->migrationRepository->getMigrations());
50
        $availableMigrations = array_filter($migrationsToCheck, static function (AvailableMigration $availableMigration) use ($versions) : bool {
51
            // in_array third parameter is intentionally false to force object to string casting
52 4
            return in_array($availableMigration->getVersion(), $versions, false);
53 5
        });
54
55
        $planItems = array_map(static function (AvailableMigration $availableMigration) use ($direction) : MigrationPlan {
56 3
            return new MigrationPlan($availableMigration->getVersion(), $availableMigration->getMigration(), $direction);
57 5
        }, $availableMigrations);
58
59 5
        if (count($planItems) !== count($versions)) {
60
            $plannedVersions = array_map(static function (MigrationPlan $migrationPlan) : Version {
61
                return $migrationPlan->getVersion();
62 1
            }, $planItems);
63 1
            $diff            = array_diff($versions, $plannedVersions);
64 1
            throw MigrationClassNotFound::new((string) reset($diff));
65
        }
66
67 4
        return new MigrationPlanList($planItems, $direction);
68
    }
69
70 20
    public function getPlanUntilVersion(?Version $to = null) : MigrationPlanList
71
    {
72 20
        $availableMigrations = $this->migrationRepository->getMigrations();
73 20
        $executedMigrations  = $this->metadataStorage->getExecutedMigrations();
74
75
        try {
76 20
            $to = $to ?: $availableMigrations->getLast()->getVersion();
77 1
        } catch (NoMigrationsFoundWithCriteria $e) {
78 1
            throw NoMigrationsToExecute::new($e);
79
        }
80
81 19
        $direction = $this->findDirection($to, $executedMigrations);
82
83 19
        $migrationsToCheck = $this->arrangeMigrationsForDirection($direction, $availableMigrations);
84
85 19
        $toExecute = $this->findMigrationsToExecute($to, $migrationsToCheck, $direction, $executedMigrations);
86
87
        return new MigrationPlanList(array_map(static function (AvailableMigration $migration) use ($direction) {
88 15
            return new MigrationPlan($migration->getVersion(), $migration->getMigration(), $direction);
89 19
        }, $toExecute), $direction);
90
    }
91
92 19
    private function findDirection(Version $to, Metadata\ExecutedMigrationsSet $executedMigrations) : string
93
    {
94 19
        if ((string) $to === '0' || ($executedMigrations->hasMigration($to) && ! $executedMigrations->getLast()->getVersion()->equals($to))) {
95 2
            return Direction::DOWN;
96
        }
97
98 17
        return Direction::UP;
99
    }
100
101
    /**
102
     * @return  AvailableMigration[]
103
     */
104 24
    private function arrangeMigrationsForDirection(string $direction, Metadata\AvailableMigrationsList $availableMigrations) : array
105
    {
106 24
        return $direction === Direction::UP ? $availableMigrations->getItems() : array_reverse($availableMigrations->getItems());
107
    }
108
109
    /**
110
     * @param AvailableMigration[] $migrationsToCheck
111
     *
112
     * @return AvailableMigration[]
113
     */
114 19
    private function findMigrationsToExecute(Version $to, array $migrationsToCheck, string $direction, Metadata\ExecutedMigrationsSet $executedMigrations) : array
115
    {
116 19
        $toExecute = [];
117 19
        foreach ($migrationsToCheck as $availableMigration) {
118 19
            if ($direction === Direction::DOWN && $availableMigration->getVersion()->equals($to)) {
119 1
                break;
120
            }
121
122 19
            if ($direction === Direction::UP && ! $executedMigrations->hasMigration($availableMigration->getVersion())) {
123 13
                $toExecute[] = $availableMigration;
124 8
            } elseif ($direction === Direction::DOWN && $executedMigrations->hasMigration($availableMigration->getVersion())) {
125 2
                $toExecute[] = $availableMigration;
126
            }
127
128 19
            if ($direction === Direction::UP && $availableMigration->getVersion()->equals($to)) {
129 17
                break;
130
            }
131
        }
132
133 19
        return $toExecute;
134
    }
135
}
136