Completed
Push — master ( 1de2e3...928b36 )
by Brent
02:04 queued 40s
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\Support\Collection;
6
use Illuminate\Database\Eloquent\Model;
7
use Illuminate\Database\Eloquent\Builder;
8
use Spatie\ModelStates\Exceptions\InvalidConfig;
9
use Spatie\ModelStates\Exceptions\CouldNotPerformTransition;
10
11
/**
12
 * @mixin \Illuminate\Database\Eloquent\Model
13
 */
14
trait HasStates
15
{
16
    /** @var \Spatie\ModelStates\StateConfig[]|null */
17
    protected static $stateFields = null;
18
19
    abstract protected function registerStates(): void;
20
21
    public static function bootHasStates(): void
22
    {
23
        $serialiseState = function (StateConfig $stateConfig) {
24
            return function (Model $model) use ($stateConfig) {
25
                $value = $model->getAttribute($stateConfig->field);
26
27
                if ($value === null) {
28
                    $value = $stateConfig->defaultStateClass;
29
                }
30
31
                if ($value === null) {
32
                    return;
33
                }
34
35
                $stateClass = $stateConfig->stateClass::resolveStateClass($value);
36
37
                if (! is_subclass_of($stateClass, $stateConfig->stateClass)) {
38
                    throw InvalidConfig::fieldDoesNotExtendState(
39
                        $stateConfig->field,
40
                        $stateConfig->stateClass,
41
                        $stateClass
42
                    );
43
                }
44
45
                $model->setAttribute(
46
                    $stateConfig->field,
47
                    State::resolveStateName($value)
48
                );
49
            };
50
        };
51
52
        $unserialiseState = function (StateConfig $stateConfig) {
53
            return function (Model $model) use ($stateConfig) {
54
                $stateClass = $stateConfig->stateClass::resolveStateClass($model->getAttribute($stateConfig->field));
55
56
                $defaultState = $stateConfig->defaultStateClass
57
                    ? new $stateConfig->defaultStateClass($model)
58
                    : null;
59
60
                $model->setAttribute(
61
                    $stateConfig->field,
62
                    class_exists($stateClass)
63
                        ? new $stateClass($model)
64
                        : $defaultState
65
                );
66
            };
67
        };
68
69
        foreach (self::getStateConfig() as $stateConfig) {
70
            static::retrieved($unserialiseState($stateConfig));
71
            static::created($unserialiseState($stateConfig));
72
            static::saved($unserialiseState($stateConfig));
73
74
            static::updating($serialiseState($stateConfig));
75
            static::creating($serialiseState($stateConfig));
76
            static::saving($serialiseState($stateConfig));
77
        }
78
    }
79
80
    public function initializeHasStates(): void
81
    {
82
        foreach (self::getStateConfig() as $stateConfig) {
83
            if (! $stateConfig->defaultStateClass) {
84
                continue;
85
            }
86
87
            $this->{$stateConfig->field} = new $stateConfig->defaultStateClass($this);
88
        }
89
    }
90
91
    public function scopeWhereState(Builder $builder, string $field, $states): Builder
92
    {
93
        self::getStateConfig();
94
95
        /** @var \Spatie\ModelStates\StateConfig|null $stateConfig */
96
        $stateConfig = self::getStateConfig()[$field] ?? null;
97
98
        if (! $stateConfig) {
99
            throw InvalidConfig::unknownState($field, $this);
100
        }
101
102
        $abstractStateClass = $stateConfig->stateClass;
103
104
        $stateNames = collect((array) $states)->map(function ($state) use ($abstractStateClass) {
105
            return $abstractStateClass::resolveStateName($state);
106
        });
107
108
        return $builder->whereIn($field, $stateNames);
109
    }
110
111
    public function scopeWhereNotState(Builder $builder, string $field, $states): Builder
112
    {
113
        /** @var \Spatie\ModelStates\StateConfig|null $stateConfig */
114
        $stateConfig = self::getStateConfig()[$field] ?? null;
115
116
        if (! $stateConfig) {
117
            throw InvalidConfig::unknownState($field, $this);
118
        }
119
120
        $stateNames = collect((array) $states)->map(function ($state) use ($stateConfig) {
121
            return $stateConfig->stateClass::resolveStateName($state);
122
        });
123
124
        return $builder->whereNotIn($field, $stateNames);
125
    }
126
127
    /**
128
     * @param \Spatie\ModelStates\State|string $state
129
     * @param string|null $field
130
     */
131
    public function transitionTo($state, string $field = null)
132
    {
133
        $stateConfig = self::getStateConfig();
134
135
        if ($field === null && count($stateConfig) > 1) {
136
            throw CouldNotPerformTransition::couldNotResolveTransitionField($this);
137
        }
138
139
        $field = $field ?? reset($stateConfig)->field;
140
141
        $this->{$field}->transitionTo($state);
142
    }
143
144
    public function transitionableStates(string $fromClass, ?string $field = null): array
145
    {
146
        $stateConfig = self::getStateConfig();
147
148
        if ($field === null && count($stateConfig) > 1) {
149
            throw InvalidConfig::fieldNotFound($fromClass, $this);
150
        }
151
152
        $field = $field ?? reset($stateConfig)->field;
153
154
        if (! array_key_exists($field, $stateConfig)) {
155
            throw InvalidConfig::unknownState($field, $this);
156
        }
157
158
        return $stateConfig[$field]->transitionableStates($fromClass);
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $transitionClass of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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
    private 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(function ($state) {
207
                return $state->stateClass::all();
208
            });
209
    }
210
211
    public static function getStatesFor(string $column): Collection
212
    {
213
        return static::getStates()->get($column, new Collection);
214
    }
215
216
    public static function getDefaultStates(): Collection
217
    {
218
        return collect(static::getStateConfig())
219
            ->map(function ($state) {
220
                return $state->defaultStateClass;
221
            });
222
    }
223
224
    public static function getDefaultStateFor(string $column): string
225
    {
226
        return static::getDefaultStates()->get($column);
227
    }
228
}
229