Completed
Pull Request — master (#32)
by Brent
08:17
created

HasStates::transitionableStates()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 4
nc 3
nop 2
1
<?php
2
3
namespace Spatie\ModelStates;
4
5
use Illuminate\Database\Eloquent\Builder;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Support\Collection;
8
use Spatie\ModelStates\Exceptions\CouldNotPerformTransition;
9
use Spatie\ModelStates\Exceptions\InvalidConfig;
10
11
/**
12
 * @mixin \Illuminate\Database\Eloquent\Model
13
 */
14
trait HasStates
15
{
16
    protected static ?array $stateFields = null;
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 '?', expecting T_FUNCTION or T_CONST
Loading history...
17
18
    abstract protected function registerStates(): void;
19
20
    public function __set($name, $value): void
21
    {
22
        if ($value instanceof State) {
23
            $value->setField($name);
24
        }
25
26
        parent::__set($name, $value);
27
    }
28
29
    public static function bootHasStates(): void
30
    {
31
        $serializeState = function (StateConfig $stateConfig) {
32
            return function (Model $model) use ($stateConfig) {
33
                $value = $model->getAttribute($stateConfig->field);
34
35
                if ($value === null) {
36
                    $value = $stateConfig->defaultStateClass;
37
                }
38
39
                if ($value === null) {
40
                    return;
41
                }
42
43
                $stateClass = $stateConfig->stateClass::resolveStateClass($value);
44
45
                if (! is_subclass_of($stateClass, $stateConfig->stateClass)) {
46
                    throw InvalidConfig::fieldDoesNotExtendState(
47
                        $stateConfig->field,
48
                        $stateConfig->stateClass,
49
                        $stateClass
50
                    );
51
                }
52
53
                $model->setAttribute(
54
                    $stateConfig->field,
55
                    State::resolveStateName($value)
56
                );
57
            };
58
        };
59
60
        $unserializeState = function (StateConfig $stateConfig) {
61
            return function (Model $model) use ($stateConfig) {
62
                $stateClass = $stateConfig->stateClass::resolveStateClass($model->getAttribute($stateConfig->field));
63
64
                $defaultState = $stateConfig->defaultStateClass
65
                    ? new $stateConfig->defaultStateClass($model)
66
                    : null;
67
68
                /** @var null|\Spatie\ModelStates\State $state */
69
                $state = $defaultState;
70
71
                if (class_exists($stateClass)) {
72
                    $state = new $stateClass($model);
73
74
                    $state->setField($stateConfig->field);
75
                }
76
77
                $model->setAttribute(
78
                    $stateConfig->field,
79
                    $state
80
                );
81
            };
82
        };
83
84
        foreach (self::getStateConfig() as $stateConfig) {
85
            static::retrieved($unserializeState($stateConfig));
86
            static::created($unserializeState($stateConfig));
87
            static::saved($unserializeState($stateConfig));
88
89
            static::updating($serializeState($stateConfig));
90
            static::creating($serializeState($stateConfig));
91
            static::saving($serializeState($stateConfig));
92
        }
93
    }
94
95
    public function initializeHasStates(): void
96
    {
97
        foreach (self::getStateConfig() as $stateConfig) {
98
            if (! $stateConfig->defaultStateClass) {
99
                continue;
100
            }
101
102
            $this->{$stateConfig->field} = new $stateConfig->defaultStateClass($this);
103
        }
104
    }
105
106
    public function scopeWhereState(Builder $builder, string $field, $states): Builder
107
    {
108
        self::getStateConfig();
109
110
        /** @var \Spatie\ModelStates\StateConfig|null $stateConfig */
111
        $stateConfig = self::getStateConfig()[$field] ?? null;
112
113
        if (! $stateConfig) {
114
            throw InvalidConfig::unknownState($field, $this);
115
        }
116
117
        $abstractStateClass = $stateConfig->stateClass;
118
119
        $stateNames = collect((array) $states)->map(
120
            fn($state) => $abstractStateClass::resolveStateName($state)
121
        );
122
123
        return $builder->whereIn($field, $stateNames);
124
    }
125
126
    public function scopeWhereNotState(Builder $builder, string $field, $states): Builder
127
    {
128
        /** @var \Spatie\ModelStates\StateConfig|null $stateConfig */
129
        $stateConfig = self::getStateConfig()[$field] ?? null;
130
131
        if (! $stateConfig) {
132
            throw InvalidConfig::unknownState($field, $this);
133
        }
134
135
        $stateNames = collect((array) $states)->map(
136
            fn($state) => $stateConfig->stateClass::resolveStateName($state)
137
        );
138
139
        return $builder->whereNotIn($field, $stateNames);
140
    }
141
142
    /**
143
     * @param \Spatie\ModelStates\State|string $state
144
     * @param string|null $field
145
     *
146
     * @return \Illuminate\Database\Eloquent\Model
147
     */
148
    public function transitionTo($state, string $field = null)
149
    {
150
        $stateConfig = self::getStateConfig();
151
152
        if ($field === null && count($stateConfig) > 1) {
153
            throw CouldNotPerformTransition::couldNotResolveTransitionField($this);
154
        }
155
156
        $field = $field ?? reset($stateConfig)->field;
157
158
        return $this->{$field}->transitionTo($state);
159
    }
160
161
    /**
162
     * @param string $fromClass
163
     * @param string $toClass
164
     *
165
     * @return \Spatie\ModelStates\Transition|string|null
166
     */
167
    public function resolveTransitionClass(string $fromClass, string $toClass)
168
    {
169
        foreach (static::getStateConfig() as $stateConfig) {
170
            $transitionClass = $stateConfig->resolveTransition($this, $fromClass, $toClass);
171
172
            if ($transitionClass) {
173
                return $transitionClass;
174
            }
175
        }
176
177
        throw CouldNotPerformTransition::notFound($fromClass, $toClass, $this);
178
    }
179
180
    protected function addState(string $field, string $stateClass): StateConfig
181
    {
182
        $stateConfig = new StateConfig($field, $stateClass);
183
184
        static::$stateFields[$stateConfig->field] = $stateConfig;
185
186
        return $stateConfig;
187
    }
188
189
    /**
190
     * @return \Spatie\ModelStates\StateConfig[]
191
     */
192
    public static function getStateConfig(): array
193
    {
194
        if (static::$stateFields === null) {
195
            static::$stateFields = [];
196
197
            (new static)->registerStates();
198
        }
199
200
        return static::$stateFields ?? [];
201
    }
202
203
    public static function getStates(): Collection
204
    {
205
        return collect(static::getStateConfig())
206
            ->map(
207
                fn($state) => $state->stateClass::all()->map(
208
                    fn($stateClass) => $stateClass::getMorphClass()
209
                )
210
            );
211
    }
212
213
    public static function getStatesFor(string $column): Collection
214
    {
215
        return static::getStates()->get($column, new Collection);
216
    }
217
218
    public static function getDefaultStates(): Collection
219
    {
220
        return collect(static::getStateConfig())->map(
221
            fn($state) => $state->defaultStateClass
222
        );
223
    }
224
225
    public static function getDefaultStateFor(string $column): string
226
    {
227
        return static::getDefaultStates()->get($column);
228
    }
229
}
230