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