Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
26 / 26
95.45% covered (success)
95.45%
21 / 22
47.62% covered (danger)
47.62%
10 / 21
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
PhpLoader
100.00% covered (success)
100.00%
26 / 26
95.45% covered (success)
95.45%
21 / 22
47.62% covered (danger)
47.62%
10 / 21
100.00% covered (success)
100.00%
3 / 3
28.39
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 load
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
9 / 9
83.33% covered (warning)
83.33%
5 / 6
100.00% covered (success)
100.00%
1 / 1
5.12
 loadClass
100.00% covered (success)
100.00%
11 / 11
91.67% covered (success)
91.67%
11 / 12
28.57% covered (danger)
28.57%
4 / 14
100.00% covered (success)
100.00%
1 / 1
14.11
1<?php
2
3declare(strict_types=1);
4
5namespace Celemas\Quma\Migrations;
6
7use ArgumentCountError;
8use Celemas\Quma\Contract;
9use Celemas\Quma\Environment;
10use RuntimeException;
11
12final class PhpLoader
13{
14    /** @var array<string, class-string<Contract\Migration>> */
15    private static array $classes = [];
16
17    public function __construct(
18        private readonly Environment $env,
19        private readonly ?Contract\MigrationFactory $migrationFactory = null,
20    ) {}
21
22    public function load(string $migration): Contract\Migration
23    {
24        if (!is_file($migration)) {
25            throw new RuntimeException('Could not read migration file');
26        }
27
28        $class = self::$classes[$migration] ?? null;
29
30        if ($class === null) {
31            $class = $this->loadClass($migration);
32            self::$classes[$migration] = $class;
33        }
34
35        if ($this->migrationFactory !== null) {
36            return $this->migrationFactory->create($class, $this->env);
37        }
38
39        try {
40            return new $class();
41        } catch (ArgumentCountError $e) {
42            throw new RuntimeException(
43                "Migration {$class} requires constructor arguments, but no migration factory is configured.",
44                previous: $e,
45            );
46        }
47    }
48
49    /** @return class-string<Contract\Migration> */
50    private function loadClass(string $migration): string
51    {
52        /** @psalm-suppress UnresolvableInclude */
53        $class = require $migration;
54
55        if (!is_string($class) || $class === '') {
56            throw new RuntimeException('Invalid migration file. Expected migration class name');
57        }
58
59        if (!class_exists($class)) {
60            throw new RuntimeException("Invalid migration file. Migration class '{$class}' does not exist");
61        }
62
63        if (!is_subclass_of($class, Contract\Migration::class)) {
64            throw new RuntimeException(
65                "Invalid migration file. Migration class '{$class}' must implement "
66                . Contract\Migration::class,
67            );
68        }
69
70        return $class;
71    }
72}