Completed
Pull Request — master (#108)
by Brent
12:23 queued 11:25
created

State::resolveStateClass()   B

Complexity

Conditions 7
Paths 8

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 26
rs 8.5706
c 0
b 0
f 0
cc 7
nc 8
nop 1
1
<?php
2
3
namespace Spatie\ModelStates;
4
5
use Illuminate\Contracts\Database\Eloquent\Castable;
6
use Illuminate\Database\Eloquent\Model;
7
use JsonSerializable;
8
use ReflectionClass;
9
use Spatie\ModelStates\Exceptions\CouldNotPerformTransition;
10
11
abstract class State implements Castable, JsonSerializable
12
{
13
    private static array $stateMapping = [];
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_ARRAY, expecting T_FUNCTION or T_CONST
Loading history...
14
15
    private Model $model;
16
17
    private StateConfig $stateConfig;
18
19
    public static function castUsing(array $arguments)
20
    {
21
        return new StateCaster(static::class);
22
    }
23
24
    public static function getMorphClass(): string
25
    {
26
        return static::$name ?? static::class;
27
    }
28
29
    public static function getStateMapping(): array
30
    {
31
        if (! isset(self::$stateMapping[static::class])) {
32
            self::$stateMapping[static::class] = static::resolveStateMapping();
33
        }
34
35
        return self::$stateMapping[static::class];
36
    }
37
38
    public static function resolveStateClass($state): ?string
39
    {
40
        if ($state === null) {
41
            return null;
42
        }
43
44
        if ($state instanceof State) {
45
            return get_class($state);
46
        }
47
48
        foreach (static::getStateMapping() as $stateClass) {
49
            if (! class_exists($stateClass)) {
50
                continue;
51
            }
52
53
            // Loose comparison is needed here in order to support non-string values,
54
            // Laravel casts their database value automatically to strings if we didn't specify the fields in `$casts`.
55
            $name = isset($stateClass::$name) ? (string) $stateClass::$name : null;
56
57
            if ($name == $state) {
58
                return $stateClass;
59
            }
60
        }
61
62
        return $state;
63
    }
64
65
    public function __construct(Model $model, StateConfig $stateConfig)
66
    {
67
        $this->model = $model;
68
        $this->stateConfig = $stateConfig;
69
    }
70
71
    public function transitionTo($newState): Model
72
    {
73
        $newState = $this->resolveStateObject($newState);
74
75
        $from = static::getMorphClass();
76
77
        $to = $newState::getMorphClass();
78
79
        if (! $this->stateConfig->isTransitionAllowed($from, $to)) {
80
            throw CouldNotPerformTransition::notFound($from, $to, $this->model);
81
        }
82
83
        $transition = new DefaultTransition(
84
            $this->model,
85
            $this->stateConfig->fieldName,
86
            $newState
87
        );
88
89
        $model = app()->call([$transition, 'handle']);
90
91
        return $model;
92
    }
93
94
    public function transitionableStates(): array
95
    {
96
        return $this->stateConfig->transitionableStates(self::getMorphClass());
97
    }
98
99
    public function canTransitionTo($newState): bool
100
    {
101
        $newState = $this->resolveStateObject($newState);
102
103
        $from = static::getMorphClass();
104
105
        $to = $newState::getMorphClass();
106
107
        return $this->stateConfig->isTransitionAllowed($from, $to);
108
    }
109
110
    public function getValue(): string
111
    {
112
        return static::getMorphClass();
113
    }
114
115
    public function equals(State ...$otherStates): bool
116
    {
117
        foreach ($otherStates as $otherState) {
118
            if ($this->stateConfig->baseStateClass === $otherState->stateConfig->baseStateClass
119
                && $this->getValue() === $otherState->getValue()) {
120
                return true;
121
            }
122
        }
123
124
        return false;
125
    }
126
127
    public function jsonSerialize()
128
    {
129
        return $this->getValue();
130
    }
131
132
    public function __toString(): string
133
    {
134
        return $this->getValue();
135
    }
136
137
    private function resolveStateObject($state): self
138
    {
139
        if (is_object($state) && is_subclass_of($state, $this->stateConfig->baseStateClass)) {
140
            return $state;
141
        }
142
143
        $stateClassName = $this->stateConfig->baseStateClass::resolveStateClass($state);
144
145
        return new $stateClassName($this->model, $this->stateConfig);
146
    }
147
148
    private static function resolveStateMapping(): array
149
    {
150
        $reflection = new ReflectionClass(static::class);
151
152
        ['dirname' => $directory] = pathinfo($reflection->getFileName());
153
154
        $files = scandir($directory);
155
156
        unset($files[0], $files[1]);
157
158
        $namespace = $reflection->getNamespaceName();
159
160
        $resolvedStates = [];
161
162
        foreach ($files as $file) {
163
            ['filename' => $className] = pathinfo($file);
164
165
            /** @var \Spatie\ModelStates\State|mixed $stateClass */
166
            $stateClass = $namespace . '\\' . $className;
167
168
            if (! is_subclass_of($stateClass, static::class)) {
169
                continue;
170
            }
171
172
            $resolvedStates[$stateClass::getMorphClass()] = $stateClass;
173
        }
174
175
        return $resolvedStates;
176
    }
177
}
178