Test Setup Failed
Push — master ( e22c69...3f50b7 )
by Ben
07:43 queued 23s
created

AbstractManager::withoutPagination()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 2
c 0
b 0
f 0
dl 0
loc 5
ccs 2
cts 2
cp 1
rs 10
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Thinktomorrow\Chief\Management;
6
7
use Illuminate\Support\Facades\Schema;
8
use Illuminate\Contracts\Pagination\Paginator;
9
use Illuminate\Database\Eloquent\Builder;
10
use Illuminate\Http\Request;
11
use Thinktomorrow\Chief\Fields\Types\NumberField;
12
use Thinktomorrow\Chief\Concerns\Translatable\TranslatableCommand;
13
use Thinktomorrow\Chief\Fields\FieldReference;
14
use Thinktomorrow\Chief\Fields\Fields;
15
use Thinktomorrow\Chief\Fields\SavingFields;
16
use Thinktomorrow\Chief\Fields\Types\Field;
17
use Thinktomorrow\Chief\Filters\Filters;
18
use Thinktomorrow\Chief\Fragments\ManagesFragments;
19
use Thinktomorrow\Chief\Management\Assistants\ManagesAssistants;
20
use Thinktomorrow\Chief\Management\Details\HasDetails;
21
use Thinktomorrow\Chief\Management\Details\HasDetailSections;
22
use Thinktomorrow\Chief\Management\Exceptions\NonExistingRecord;
23
use Thinktomorrow\Chief\Management\Exceptions\NotAllowedManagerRoute;
24
use Thinktomorrow\Chief\Relations\ActsAsChild;
25
use Thinktomorrow\Chief\Relations\ActsAsParent;
26
27
abstract class AbstractManager
28
{
29
    use SavingFields;
30
    use HasDetails;
0 ignored issues
show
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...
31
    use HasDetailSections;
32
    use ManagesMedia;
33
    use ManagesPagebuilder;
0 ignored issues
show
introduced by
The trait Thinktomorrow\Chief\Management\ManagesPagebuilder requires some properties which are not provided by Thinktomorrow\Chief\Management\AbstractManager: $trans, $relation, $owner_id, $slug, $id
Loading history...
34
    use TranslatableCommand;
35
    use ManagesAssistants;
36
    use ManagesFragments;
37
38
    protected $translation_columns = [];
39
40
    protected $model;
41
42
    /** @var Register */
43
    protected $registration;
44 169
45
    protected $pageCount = 20;
46 169
    protected $paginated = true;
47
    protected static $bootedTraitMethods = [];
48
49 169
    final public function __construct(Registration $registration)
50
    {
51
        $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...
52 169
53
        // Upon instantiation, a general model is set that doesn't point to a persisted record.
54 169
        $this->manage(app($this->registration->model()));
55 169
56
        // Check if key and model are present since the model should be set by the manager itself
57 169
        $this->validateConstraints();
58
59 169
        static::bootTraitMethods();
60
    }
61 169
62
    public function managerKey(): string
63
    {
64 98
        return $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

64
        return $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...
65
    }
66 98
67
    public function manage($model): Manager
68 98
    {
69
        $this->model = $model;
70 98
71
        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...
72
    }
73 6
74
    public function isManualSortable(): bool
75 6
    {
76
        return (isset($this->useManualSorting) && $this->useManualSorting && method_exists($this->modelInstance(), 'scopeSortedManually'));
77 6
    }
78
79 6
    public function saveCreateFields(Request $request): void
80
    {
81 6
        if($this->isManualSortable() && !$request->has('order')) {
82
            $this->model->order = $this->modelInstance()::orderBy('order', 'desc')->first()->order + 1;
0 ignored issues
show
Bug introduced by
The method orderBy() does not exist on Thinktomorrow\Chief\Management\ManagedModel. Since it exists in all sub-types, consider adding an abstract or default implementation to Thinktomorrow\Chief\Management\ManagedModel. ( Ignorable by Annotation )

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

82
            $this->model->order = $this->modelInstance()::/** @scrutinizer ignore-call */ orderBy('order', 'desc')->first()->order + 1;
Loading history...
83 6
        }
84
85 6
        $this->saveFields($request, $this->createFields());
86 6
    }
87
88
    public function findManaged($id): Manager
89
    {
90
        $modelInstance = $this->modelInstance()::where('id', $id)->withoutGlobalScopes()->first();
0 ignored issues
show
Bug introduced by
The method where() does not exist on Thinktomorrow\Chief\Management\ManagedModel. Since it exists in all sub-types, consider adding an abstract or default implementation to Thinktomorrow\Chief\Management\ManagedModel. ( Ignorable by Annotation )

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

90
        $modelInstance = $this->modelInstance()::/** @scrutinizer ignore-call */ where('id', $id)->withoutGlobalScopes()->first();
Loading history...
91
92
        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

92
        return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($modelInstance);
Loading history...
93
    }
94 6
95
    public function indexCollection()
96 6
    {
97
        $builder = ($this->modelInstance())->query();
0 ignored issues
show
Bug introduced by
The method query() does not exist on Thinktomorrow\Chief\Management\ManagedModel. Since it exists in all sub-types, consider adding an abstract or default implementation to Thinktomorrow\Chief\Management\ManagedModel. ( Ignorable by Annotation )

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

97
        $builder = ($this->modelInstance())->/** @scrutinizer ignore-call */ query();
Loading history...
98
99 6
        $this->filters()->apply($builder);
100
101 6
        $builder = $this->indexBuilder($builder);
102 3
103
        $builder = $this->indexSorting($builder);
104
105
        if ($this->paginated) {
106 6
            return $this->indexPagination($builder);
107 6
        }
108
109
        return $builder->get()->map(function ($model) {
110 6
            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

110
            return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($model);
Loading history...
111
        });
112
    }
113 6
114
    public function withoutPagination(): Manager
115 6
    {
116
        $this->paginated = false;
117
118 4
        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...
119 6
    }
120
121 6
    protected function indexBuilder(Builder $builder): Builder
122
    {
123
        return $builder;
124 106
    }
125
126 106
    protected function indexSorting(Builder $builder): Builder
127
    {
128
        if($this->isManualSortable()) {
129 119
            $builder->sortedManually();
130
        }
131 119
132
        if ($this->isAssistedBy('publish') && Schema::hasColumn($this->modelInstance()->getTable(), 'published_at')) {
0 ignored issues
show
Bug introduced by
The method getTable() does not exist on Thinktomorrow\Chief\Management\ManagedModel. Since it exists in all sub-types, consider adding an abstract or default implementation to Thinktomorrow\Chief\Management\ManagedModel. ( Ignorable by Annotation )

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

132
        if ($this->isAssistedBy('publish') && Schema::hasColumn($this->modelInstance()->/** @scrutinizer ignore-call */ getTable(), 'published_at')) {
Loading history...
133
            $builder->orderBy('published_at', 'DESC');
134
        }
135
136
        // if model has no timestamps, updated_at doesn't exist
137
        if ($this->modelInstance()->timestamps) {
0 ignored issues
show
Bug introduced by
Accessing timestamps on the interface Thinktomorrow\Chief\Management\ManagedModel suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
138
            $builder->orderBy('updated_at', 'DESC');
139
        }
140 119
141
        return $builder;
142 119
    }
143 1
144
    protected function indexPagination($builder): Paginator
145
    {
146 118
        $paginator = $builder->paginate($this->pageCount);
147
148
        $modifiedCollection = $builder->paginate($this->pageCount)->getCollection()->transform(function ($model) {
149
            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

149
            return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($model);
Loading history...
150
        });
151
152
        return $paginator->setCollection($modifiedCollection);
153
    }
154
155
    public function indexView(): string
156
    {
157 130
        return 'chief::back.managers._partials._rowitems';
158
    }
159
160 130
    public function indexViewData(): array
161 130
    {
162 130
        return [];
163
    }
164
165 130
    public function modelInstance(): ManagedModel
166 76
    {
167
        $class = $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

167
        /** @scrutinizer ignore-call */ 
168
        $class = $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...
168
169
        return new $class();
170
    }
171 107
172 106
    public function existingModel(): ManagedModel
173 106
    {
174 106
        if (!$this->hasExistingModel()) {
175
            throw new NonExistingRecord('Model does not exist yet but is expected.');
176
        }
177 106
178
        return $this->model;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->model could return the type Illuminate\Contracts\Foundation\Application which is incompatible with the type-hinted return Thinktomorrow\Chief\Management\ManagedModel. Consider adding an additional type-check to rule them out.
Loading history...
179
    }
180 140
181
    public function hasExistingModel(): bool
182 140
    {
183 8
        return ($this->model && $this->model->exists);
184
    }
185
186 8
    /**
187
     * Determine which actions should be available for this
188
     * manager and their respective routed urls.
189 136
     *
190
     * @param $verb
191
     * @return null|string
192 126
     * @throws NonExistingRecord
193
     */
194 126
    public function route($verb): ?string
195 5
    {
196
        $routes = [
197
            'index'      => route('chief.back.managers.index', [$this->registration->key()]),
198 121
            'create'     => route('chief.back.managers.create', [$this->registration->key()]),
199
            'store'      => route('chief.back.managers.store', [$this->registration->key()]),
200
            'sort-index' => route('chief.back.managers.sort-index', [$this->registration->key()]),
201 87
        ];
202
203 87
        if (array_key_exists($verb, $routes)) {
204
            return $routes[$verb] ?? null;
205
        }
206
207
        //These routes expect the model to be persisted in the database
208
        $modelRoutes = [
209
            'edit'   => route('chief.back.managers.edit', [$this->registration->key(), $this->existingModel()->id]),
0 ignored issues
show
Bug introduced by
Accessing id on the interface Thinktomorrow\Chief\Management\ManagedModel suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
210
            'update' => route('chief.back.managers.update', [$this->registration->key(), $this->existingModel()->id]),
211
            'delete' => route('chief.back.managers.delete', [$this->registration->key(), $this->existingModel()->id]),
212 111
            'upload' => route('chief.back.managers.media.upload', [
213
                $this->registration->key(),
214 111
                $this->existingModel()->id,
215
            ]),
216 111
        ];
217 85
218 83
        return $modelRoutes[$verb] ?? null;
219
    }
220
221 85
    public function can($verb): bool
222
    {
223
        foreach (static::$bootedTraitMethods['can'] as $method) {
224 111
            if (!method_exists($this, $method)) {
225
                continue;
226
            }
227
            $this->$method($verb);
228
        }
229
230
        return !is_null($this->route($verb));
231
    }
232
233
    public function guard($verb): Manager
234
    {
235
        if (!$this->can($verb)) {
236 3
            NotAllowedManagerRoute::notAllowedVerb($verb, $this);
237
        }
238 3
239
        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...
240
    }
241 2
242
    public function fields(): Fields
243 2
    {
244 2
        return new Fields();
245
    }
246
247 2
    /**
248 2
     * Enrich the manager fields with any of the assistant specified fields
249
     *
250
     * @return Fields
251 2
     * @throws \Exception
252 2
     */
253
    public function fieldsWithAssistantFields(): Fields
254 3
    {
255
        $fields = $this->fields();
256 3
257
        foreach ($this->assistantsAsClassNames() as $assistantClass) {
258
            if (!method_exists($assistantClass, 'fields')) {
259
                continue;
260
            }
261
262
            $fields = $fields->merge($this->assistant($assistantClass)->fields());
0 ignored issues
show
Bug introduced by
$assistantClass of type object is incompatible with the type string expected by parameter $assistantKey of Thinktomorrow\Chief\Mana...actManager::assistant(). ( Ignorable by Annotation )

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

262
            $fields = $fields->merge($this->assistant(/** @scrutinizer ignore-type */ $assistantClass)->fields());
Loading history...
Bug introduced by
The method fields() does not exist on Thinktomorrow\Chief\Mana...nt\Assistants\Assistant. It seems like you code against a sub-type of Thinktomorrow\Chief\Mana...nt\Assistants\Assistant such as Thinktomorrow\Chief\Mana...Assistants\UrlAssistant or Thinktomorrow\Chief\Test...FakeAssistantWithFields. ( Ignorable by Annotation )

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

262
            $fields = $fields->merge($this->assistant($assistantClass)->/** @scrutinizer ignore-call */ fields());
Loading history...
263
        }
264
265
        return $fields;
266 26
    }
267
268 26
    public function createFields(): Fields
269
    {
270
        $fields = $this->fieldsWithAssistantFields();
271
272
        if($this->isManualSortable() && !$fields->offsetExists('order')) {
273
            $fields = $fields->add(NumberField::make('order'));
274
        }
275
276
        return $fields;
277
    }
278 24
279
    public function editFields(): Fields
280 24
    {
281
        return $this->fieldsWithAssistantFields()->map(function (Field $field) {
282
            return $field->model($this->model);
0 ignored issues
show
Bug introduced by
The method model() does not exist on Thinktomorrow\Chief\Fields\Types\Field. Since it exists in all sub-types, consider adding an abstract or default implementation to Thinktomorrow\Chief\Fields\Types\Field. ( Ignorable by Annotation )

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

282
            return $field->/** @scrutinizer ignore-call */ model($this->model);
Loading history...
283 83
        });
284
    }
285 83
286
    public function createView(): string
287
    {
288 169
        return 'chief::back.managers._partials._form';
289
    }
290 169
291
    public function createViewData(): array
292
    {
293 169
        return [];
294
    }
295 169
296
    public function editView(): string
297 169
    {
298
        return 'chief::back.managers._partials._form';
299
    }
300 169
301
    public function editViewData(): array
302
    {
303 169
        return [];
304 169
    }
305
306 169
    public function delete()
307 169
    {
308
        $this->guard('delete');
309 169
310 169
        if ($this->model instanceof ActsAsChild) {
311
            $this->model->detachAllParentRelations();
312
        }
313
314 169
        if ($this->model instanceof ActsAsParent) {
315
            $this->model->detachAllChildRelations();
316
        }
317
318
        $this->model->delete();
0 ignored issues
show
Bug introduced by
The method delete() does not exist on Illuminate\Contracts\Foundation\Application. ( Ignorable by Annotation )

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

318
        $this->model->/** @scrutinizer ignore-call */ 
319
                      delete();

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...
319
    }
320
321
    public static function filters(): Filters
322
    {
323
        return new Filters();
324
    }
325
326
    /**
327
     * This method can be used to manipulate the store request payload
328
     * before being passed to the storing / updating the models.
329
     *
330
     * @param Request $request
331
     * @return Request
332
     */
333
    public function storeRequest(Request $request): Request
334
    {
335
        return $request;
336
    }
337
338
    /**
339
     * This method can be used to manipulate the update request payload
340
     * before being passed to the storing / updating the models.
341
     *
342
     * @param Request $request
343
     * @return Request
344
     */
345
    public function updateRequest(Request $request): Request
346
    {
347
        return $request;
348
    }
349
350
    protected function requestContainsTranslations(Request $request): bool
351
    {
352
        return $request->has('trans');
353
    }
354
355
    protected function validateConstraints()
356
    {
357
        if (!$this->model) {
358
            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.');
359
        }
360
    }
361
362
    public static function bootTraitMethods()
363
    {
364
        $class = static::class;
365
366
        $methods = [
367
            'can',
368
        ];
369
370
        foreach ($methods as $baseMethod) {
371
            static::$bootedTraitMethods[$baseMethod] = [];
372
373
            foreach (class_uses_recursive($class) as $trait) {
374
                $method = class_basename($trait) . ucfirst($baseMethod);
375
376
                if (method_exists($class, $method) && !in_array($method, static::$bootedTraitMethods[$baseMethod])) {
377
                    static::$bootedTraitMethods[$baseMethod][] = lcfirst($method);
378
                }
379
            }
380
        }
381
    }
382
}
383