Completed
Pull Request — master (#57)
by Anthony
01:47 queued 14s
created

HasStates::getDefaultTransitionClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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