Completed
Push — ft/states ( d9d02a...d4f7ca )
by Ben
45:58 queued 35:27
created

AbstractManager::route()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 2
nop 1
dl 0
loc 21
ccs 11
cts 11
cp 1
crap 2
rs 9.8666
c 0
b 0
f 0
1
<?php
2
3
namespace Thinktomorrow\Chief\Management;
4
5
use Illuminate\Contracts\Pagination\Paginator;
6
use Illuminate\Database\Eloquent\Builder;
7
use Illuminate\Http\Request;
8
use Thinktomorrow\Chief\Concerns\Translatable\TranslatableCommand;
9
use Thinktomorrow\Chief\Fields\FieldArrangement;
10
use Thinktomorrow\Chief\Fields\Fields;
11
use Thinktomorrow\Chief\Fields\RenderingFields;
12
use Thinktomorrow\Chief\Fields\SavingFields;
13
use Thinktomorrow\Chief\Filters\Filters;
14
use Thinktomorrow\Chief\Management\Assistants\AssistedManager;
15
use Thinktomorrow\Chief\Management\Details\HasDetails;
16
use Thinktomorrow\Chief\Management\Details\HasSections;
17
use Thinktomorrow\Chief\Management\Exceptions\NonExistingRecord;
18
use Thinktomorrow\Chief\Management\Exceptions\NotAllowedManagerRoute;
19
use Thinktomorrow\Chief\Relations\ActsAsChild;
20
use Thinktomorrow\Chief\Relations\ActsAsParent;
21
22
abstract class AbstractManager
23
{
24
    use RenderingFields,
0 ignored issues
show
Bug introduced by
The trait Thinktomorrow\Chief\Fields\SavingFields requires the property $key which is not provided by Thinktomorrow\Chief\Management\AbstractManager.
Loading history...
introduced by
The trait Thinktomorrow\Chief\Management\Details\HasDetails requires some properties which are not provided by Thinktomorrow\Chief\Management\AbstractManager: $labelPlural, $id, $labelSingular, $title
Loading history...
introduced by
The trait Thinktomorrow\Chief\Management\ManagesPagebuilder requires some properties which are not provided by Thinktomorrow\Chief\Management\AbstractManager: $trans, $slug, $page_id, $id
Loading history...
25
        SavingFields,
26
        HasDetails,
27
        HasSections,
28
        ManagesMedia,
29
        ManagesPagebuilder,
30
        TranslatableCommand,
31
        AssistedManager;
32
33
    protected $translation_columns = [];
34
35
    protected $model;
36
37
    /** @var Register */
38
    protected $registration;
39
40
    protected $pageCount = 20;
41
    protected $paginated = true;
42
    protected static $bootedTraitMethods = [];
43
44 175
    public function __construct(Registration $registration)
45
    {
46 175
        $this->registration = $registration;
0 ignored issues
show
Documentation Bug introduced by
It seems like $registration of type Thinktomorrow\Chief\Management\Registration is incompatible with the declared type Thinktomorrow\Chief\Management\Register of property $registration.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
47
48
        // Upon instantiation, a general model is set that doesn't point to a persisted record.
49 175
        $this->manage(app($this->registration->model()));
50
51
        // Check if key and model are present since the model should be set by the manager itself
52 175
        $this->validateConstraints();
53
54 175
        static::bootTraitMethods();
55 175
    }
56
57 175
    public function manage($model): Manager
58
    {
59 175
        $this->model = $model;
60
61 175
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Thinktomorrow\Chief\Management\AbstractManager which is incompatible with the type-hinted return Thinktomorrow\Chief\Management\Manager.
Loading history...
62
    }
63
64 104
    public function findManaged($id): Manager
65
    {
66 104
        $model = $this->registration->model();
0 ignored issues
show
Bug introduced by
The method model() does not exist on Thinktomorrow\Chief\Management\Register. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

66
        /** @scrutinizer ignore-call */ 
67
        $model = $this->registration->model();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
67
68 104
        $modelInstance = $model::where('id', $id)->withoutGlobalScopes()->first();
69
70 104
        return (new static($this->registration))->manage($modelInstance);
0 ignored issues
show
Bug introduced by
$this->registration of type Thinktomorrow\Chief\Management\Register is incompatible with the type Thinktomorrow\Chief\Management\Registration expected by parameter $registration of Thinktomorrow\Chief\Mana...tManager::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

70
        return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($modelInstance);
Loading history...
71
    }
72
73 6
    public function indexCollection()
74
    {
75 6
        $model = $this->registration->model();
76
77 6
        $builder = (new $model)->query();
78
79 6
        $this->filters()->apply($builder);
80
81 6
        $builder = $this->indexBuilder($builder);
82
83 6
        $builder = $this->indexSorting($builder);
84
85 6
        if ($this->paginated) {
86 6
            return $this->indexPagination($builder);
87
        }
88
89
        return $builder->get()->map(function ($model) {
90
            return (new static($this->registration))->manage($model);
0 ignored issues
show
Bug introduced by
$this->registration of type Thinktomorrow\Chief\Management\Register is incompatible with the type Thinktomorrow\Chief\Management\Registration expected by parameter $registration of Thinktomorrow\Chief\Mana...tManager::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

90
            return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($model);
Loading history...
91
        });
92
    }
93
94 6
    protected function indexBuilder(Builder $builder): Builder
95
    {
96 6
        return $builder;
97
    }
98
99 6
    protected function indexSorting(Builder $builder): Builder
100
    {
101 6
        if ($this->isAssistedBy('publish')) {
102 3
            $builder->orderBy('published_at', 'DESC');
103
        }
104
105
        // if model has no timestamps, updated_at doesn't exist
106 6
        if ($this->model()->timestamps) {
107 6
            $builder->orderBy('updated_at', 'DESC');
108
        }
109
110 6
        return $builder;
111
    }
112
113 6
    protected function indexPagination($builder): Paginator
114
    {
115 6
        $paginator = $builder->paginate($this->pageCount);
116
117
        $modifiedCollection = $builder->paginate($this->pageCount)->getCollection()->transform(function ($model) {
118 4
            return (new static($this->registration))->manage($model);
0 ignored issues
show
Bug introduced by
$this->registration of type Thinktomorrow\Chief\Management\Register is incompatible with the type Thinktomorrow\Chief\Management\Registration expected by parameter $registration of Thinktomorrow\Chief\Mana...tManager::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

118
            return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($model);
Loading history...
119 6
        });
120
121 6
        return $paginator->setCollection($modifiedCollection);
122
    }
123
124 104
    public function model()
125
    {
126 104
        return $this->model;
127
    }
128
129 120
    public function hasExistingModel(): bool
130
    {
131 120
        return ($this->model && $this->model->exists);
132
    }
133
134
    /**
135
     * If the model exists return it otherwise
136
     * throws a nonExistingRecord exception;
137
     *
138
     * @throws NonExistingRecord
139
     */
140 120
    protected function existingModel()
141
    {
142 120
        if (!$this->hasExistingModel()) {
143 1
            throw new NonExistingRecord('Model does not exist yet but is expected.');
144
        }
145
146 119
        return $this->model;
147
    }
148
149
    /**
150
     * Determine which actions should be available for this
151
     * manager and their respective routed urls.
152
     *
153
     * @param $verb
154
     * @return null|string
155
     * @throws NonExistingRecord
156
     */
157 130
    public function route($verb): ?string
158
    {
159
        $routes = [
160 130
            'index'   => route('chief.back.managers.index', [$this->registration->key()]),
0 ignored issues
show
Bug introduced by
The method key() does not exist on Thinktomorrow\Chief\Management\Register. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

160
            'index'   => route('chief.back.managers.index', [$this->registration->/** @scrutinizer ignore-call */ key()]),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
161 130
            'create'  => route('chief.back.managers.create', [$this->registration->key()]),
162 130
            'store'   => route('chief.back.managers.store', [$this->registration->key()]),
163
        ];
164
165 130
        if (array_key_exists($verb, $routes)) {
166 75
            return $routes[$verb] ?? null;
167
        }
168
169
        //These routes expect the model to be persisted in the database
170
        $modelRoutes = [
171 108
            'edit'    => route('chief.back.managers.edit', [$this->registration->key(), $this->existingModel()->id]),
172 107
            'update'  => route('chief.back.managers.update', [$this->registration->key(), $this->existingModel()->id]),
173 107
            'delete'  => route('chief.back.managers.delete', [$this->registration->key(), $this->existingModel()->id]),
174 107
            'upload'  => route('chief.back.managers.media.upload', [$this->registration->key(), $this->existingModel()->id]),
175
        ];
176
177 107
        return $modelRoutes[$verb] ?? null;
178
    }
179
180 141
    public function can($verb): bool
181
    {
182 141
        foreach (static::$bootedTraitMethods['can'] as $method) {
183 8
            if (!method_exists($this, $method)) {
184
                continue;
185
            }
186 8
            $this->$method($verb);
187
        }
188
189 137
        return !is_null($this->route($verb));
190
    }
191
192 127
    public function guard($verb): Manager
193
    {
194 127
        if (! $this->can($verb)) {
195 5
            NotAllowedManagerRoute::notAllowedVerb($verb, $this);
196
        }
197
198 122
        return $this;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this returns the type Thinktomorrow\Chief\Management\AbstractManager which is incompatible with the type-hinted return Thinktomorrow\Chief\Management\Manager.
Loading history...
199
    }
200
201 87
    public function fields(): Fields
202
    {
203 87
        return new Fields();
204
    }
205
206
    /**
207
     * Enrich the manager fields with any of the assistant specified fields
208
     *
209
     * @return Fields
210
     * @throws \Exception
211
     */
212 110
    public function fieldsWithAssistantFields(): Fields
213
    {
214 110
        $fields = $this->fields();
215
216 110
        foreach ($this->assistants() as $assistant) {
217 84
            if (! method_exists($assistant, 'fields')) {
218 82
                continue;
219
            }
220
221 84
            $fields = $fields->merge($assistant->fields());
222
        }
223
224 110
        return $fields;
225
    }
226
227
    /**
228
     * This determines the arrangement of the manageable fields
229
     * on the create and edit forms. By default, all fields
230
     * are presented in their order of appearance
231
     *
232
     * @param null $key pinpoint to a specific field arrangement e.g. for create page.
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $key is correct as it would always require null to be passed?
Loading history...
233
     * @return FieldArrangement
234
     * @throws \Exception
235
     */
236 3
    public function fieldArrangement($key = null): FieldArrangement
237
    {
238 3
        return new FieldArrangement($this->fieldsWithAssistantFields());
239
    }
240
241 3
    public function delete()
242
    {
243 3
        $this->guard('delete');
244
245 2
        if ($this->model instanceof ActsAsChild) {
246 2
            $this->model->detachAllParentRelations();
247
        }
248
249 2
        if ($this->model instanceof ActsAsParent) {
250 2
            $this->model->detachAllChildRelations();
251
        }
252
253 2
        $this->model->delete();
254 2
    }
255
256 3
    public static function filters(): Filters
257
    {
258 3
        return new Filters();
259
    }
260
261
    /**
262
     * This method can be used to manipulate the store request payload
263
     * before being passed to the storing / updating the models.
264
     *
265
     * @param Request $request
266
     * @return Request
267
     */
268 26
    public function storeRequest(Request $request): Request
269
    {
270 26
        return $request;
271
    }
272
273
    /**
274
     * This method can be used to manipulate the update request payload
275
     * before being passed to the storing / updating the models.
276
     *
277
     * @param Request $request
278
     * @return Request
279
     */
280 24
    public function updateRequest(Request $request): Request
281
    {
282 24
        return $request;
283
    }
284
285 83
    protected function requestContainsTranslations(Request $request): bool
286
    {
287 83
        return $request->has('trans');
288
    }
289
290 175
    protected function validateConstraints()
291
    {
292 175
        if (!$this->model) {
293
            throw new \DomainException('Model class should be set for this manager. Please set the model property default via the constructor or by extending the setupDefaults method.');
294
        }
295 175
    }
296
297 175
    public static function bootTraitMethods()
298
    {
299 175
        $class = static::class;
300
301
        $methods = [
302 175
            'can'
303
        ];
304
305 175
        foreach ($methods as $baseMethod) {
306 175
            static::$bootedTraitMethods[$baseMethod] = [];
307
308 175
            foreach (class_uses_recursive($class) as $trait) {
309 175
                $method = class_basename($trait) . ucfirst($baseMethod);
310
311 175
                if (method_exists($class, $method) && ! in_array($method, static::$bootedTraitMethods[$baseMethod])) {
312 175
                    static::$bootedTraitMethods[$baseMethod][] = lcfirst($method);
313
                }
314
            }
315
        }
316 175
    }
317
}
318