Passed
Push — 0.5 ( 147f97...b42156 )
by Ben
07:38
created

AbstractManager::indexView()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
c 0
b 0
f 0
dl 0
loc 3
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\Concerns\Translatable\TranslatableCommand;
12
use Thinktomorrow\Chief\Fields\FieldReference;
13
use Thinktomorrow\Chief\Fields\Fields;
14
use Thinktomorrow\Chief\Fields\SavingFields;
15
use Thinktomorrow\Chief\Fields\Types\Field;
16
use Thinktomorrow\Chief\Filters\Filters;
17
use Thinktomorrow\Chief\Fragments\ManagesFragments;
18
use Thinktomorrow\Chief\Management\Assistants\ManagesAssistants;
19
use Thinktomorrow\Chief\Management\Details\HasDetails;
20
use Thinktomorrow\Chief\Management\Details\HasDetailSections;
21
use Thinktomorrow\Chief\Management\Exceptions\NonExistingRecord;
22
use Thinktomorrow\Chief\Management\Exceptions\NotAllowedManagerRoute;
23
use Thinktomorrow\Chief\Relations\ActsAsChild;
24
use Thinktomorrow\Chief\Relations\ActsAsParent;
25
26
abstract class AbstractManager
27
{
28
    use SavingFields;
29
    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...
30
    use HasDetailSections;
31
    use ManagesMedia;
32
    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, $slug, $page_id, $id
Loading history...
33
    use TranslatableCommand;
34
    use ManagesAssistants;
35
    use ManagesFragments;
36
37
    protected $translation_columns = [];
38
39
    protected $model;
40
41
    /** @var Register */
42
    protected $registration;
43
44
    protected $pageCount = 20;
45
    protected $paginated = true;
46
    protected static $bootedTraitMethods = [];
47
48 210
    final public function __construct(Registration $registration)
49
    {
50 210
        $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...
51
52
        // Upon instantiation, a general model is set that doesn't point to a persisted record.
53 210
        $this->manage(app($this->registration->model()));
54
55
        // Check if key and model are present since the model should be set by the manager itself
56 210
        $this->validateConstraints();
57
58 210
        static::bootTraitMethods();
59 210
    }
60
61 16
    public function managerKey(): string
62
    {
63 16
        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

63
        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...
64
    }
65
66 210
    public function manage($model): Manager
67
    {
68 210
        $this->model = $model;
69
70 210
        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...
71
    }
72
73 140
    public function findManaged($id): Manager
74
    {
75 140
        $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

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

77
        return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($modelInstance);
Loading history...
78
    }
79
80 6
    public function indexCollection()
81
    {
82 6
        $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

82
        $builder = ($this->modelInstance())->/** @scrutinizer ignore-call */ query();
Loading history...
83
84 6
        $this->filters()->apply($builder);
85
86 6
        $builder = $this->indexBuilder($builder);
87
88 6
        $builder = $this->indexSorting($builder);
89
90 6
        if ($this->paginated) {
91 6
            return $this->indexPagination($builder);
92
        }
93
94
        return $builder->get()->map(function ($model) {
95
            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

95
            return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($model);
Loading history...
96
        });
97
    }
98
99 6
    protected function indexBuilder(Builder $builder): Builder
100
    {
101 6
        return $builder;
102
    }
103
104 6
    protected function indexSorting(Builder $builder): Builder
105
    {
106 6
        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

106
        if ($this->isAssistedBy('publish') && Schema::hasColumn($this->modelInstance()->/** @scrutinizer ignore-call */ getTable(), 'published_at')) {
Loading history...
107
            $builder->orderBy('published_at', 'DESC');
108
        }
109
110
        // if model has no timestamps, updated_at doesn't exist
111 6
        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...
112 6
            $builder->orderBy('updated_at', 'DESC');
113
        }
114
115 6
        return $builder;
116
    }
117
118 6
    protected function indexPagination($builder): Paginator
119
    {
120 6
        $paginator = $builder->paginate($this->pageCount);
121
122
        $modifiedCollection = $builder->paginate($this->pageCount)->getCollection()->transform(function ($model) {
123 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

123
            return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($model);
Loading history...
124 6
        });
125
126 6
        return $paginator->setCollection($modifiedCollection);
127
    }
128
129 193
    public function indexView(): string
130
    {
131 193
        return 'chief::back.managers._partials._rowitems';
132
    }
133 193
134
    public function indexViewData(): array
135
    {
136 174
        return [];
137
    }
138 174
139 2
    public function modelInstance(): ManagedModel
140
    {
141
        $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

141
        /** @scrutinizer ignore-call */ 
142
        $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...
142 172
143
        return new $class();
144
    }
145 194
146
    public function existingModel(): ManagedModel
147 194
    {
148
        if (!$this->hasExistingModel()) {
149
            throw new NonExistingRecord('Model does not exist yet but is expected.');
150
        }
151
152
        return $this->model;
153
    }
154
155
    public function hasExistingModel(): bool
156
    {
157
        return ($this->model && $this->model->exists);
158 169
    }
159
160
    /**
161 169
     * Determine which actions should be available for this
162 169
     * manager and their respective routed urls.
163 169
     *
164
     * @param $verb
165
     * @return null|string
166 169
     * @throws NonExistingRecord
167 83
     */
168
    public function route($verb): ?string
169
    {
170
        $routes = [
171
            'index'  => route('chief.back.managers.index', [$this->registration->key()]),
172 145
            'create' => route('chief.back.managers.create', [$this->registration->key()]),
173 144
            'store'  => route('chief.back.managers.store', [$this->registration->key()]),
174 144
        ];
175 144
176 144
        if (array_key_exists($verb, $routes)) {
177 144
            return $routes[$verb] ?? null;
178
        }
179
180
        //These routes expect the model to be persisted in the database
181 144
        $modelRoutes = [
182
            '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...
183
            'update' => route('chief.back.managers.update', [$this->registration->key(), $this->existingModel()->id]),
184 179
            'delete' => route('chief.back.managers.delete', [$this->registration->key(), $this->existingModel()->id]),
185
            'upload' => route('chief.back.managers.media.upload', [
186 179
                $this->registration->key(),
187 8
                $this->existingModel()->id,
188
            ]),
189
        ];
190 8
191
        return $modelRoutes[$verb] ?? null;
192
    }
193 175
194
    public function can($verb): bool
195
    {
196 165
        foreach (static::$bootedTraitMethods['can'] as $method) {
197
            if (!method_exists($this, $method)) {
198 165
                continue;
199 5
            }
200
            $this->$method($verb);
201
        }
202 160
203
        return !is_null($this->route($verb));
204
    }
205 92
206
    public function guard($verb): Manager
207 92
    {
208
        if (!$this->can($verb)) {
209
            NotAllowedManagerRoute::notAllowedVerb($verb, $this);
210
        }
211
212
        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...
213
    }
214
215
    public function fields(): Fields
216 144
    {
217
        return new Fields();
218 144
    }
219
220 144
    /**
221 111
     * Enrich the manager fields with any of the assistant specified fields
222 109
     *
223
     * @return Fields
224
     * @throws \Exception
225 111
     */
226
    public function fieldsWithAssistantFields(): Fields
227
    {
228 144
        $fields = $this->fields();
229
230
        foreach ($this->assistantsAsClassNames() as $assistantClass) {
231 33
            if (!method_exists($assistantClass, 'fields')) {
232
                continue;
233 33
            }
234
235
            $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

235
            $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

235
            $fields = $fields->merge($this->assistant($assistantClass)->/** @scrutinizer ignore-call */ fields());
Loading history...
236 111
        }
237
238
        return $fields;
239 111
    }
240 111
241
    public function createFields(): Fields
242
    {
243 1
        return $this->fieldsWithAssistantFields();
244
    }
245 1
246
    public function editFields(): Fields
247
    {
248 2
        return $this->fieldsWithAssistantFields()->map(function (Field $field) {
249
            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

249
            return $field->/** @scrutinizer ignore-call */ model($this->model);
Loading history...
250 2
        });
251
    }
252
253 2
    public function createView(): string
254
    {
255 2
        return 'chief::back.managers._partials._form';
256
    }
257
258 5
    public function createViewData(): array
259
    {
260 5
        return [];
261
    }
262
263 4
    public function editView(): string
264
    {
265 4
        return 'chief::back.managers._partials._form';
266
    }
267 3
268 3
    public function editViewData(): array
269
    {
270
        return [];
271 3
    }
272 3
273
    public function delete()
274
    {
275 3
        $this->guard('delete');
276 3
277
        if ($this->model instanceof ActsAsChild) {
278 3
            $this->model->detachAllParentRelations();
279
        }
280 3
281
        if ($this->model instanceof ActsAsParent) {
282
            $this->model->detachAllChildRelations();
283
        }
284
285
        $this->model->delete();
286
    }
287
288
    public static function filters(): Filters
289
    {
290 29
        return new Filters();
291
    }
292 29
293
    /**
294
     * This method can be used to manipulate the store request payload
295
     * before being passed to the storing / updating the models.
296
     *
297
     * @param Request $request
298
     * @return Request
299
     */
300
    public function storeRequest(Request $request): Request
301
    {
302 28
        return $request;
303
    }
304 28
305
    /**
306
     * This method can be used to manipulate the update request payload
307 88
     * before being passed to the storing / updating the models.
308
     *
309 88
     * @param Request $request
310
     * @return Request
311
     */
312 210
    public function updateRequest(Request $request): Request
313
    {
314 210
        return $request;
315
    }
316
317 210
    protected function requestContainsTranslations(Request $request): bool
318
    {
319 210
        return $request->has('trans');
320
    }
321 210
322
    protected function validateConstraints()
323
    {
324 210
        if (!$this->model) {
325
            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.');
326
        }
327 210
    }
328 210
329
    public static function bootTraitMethods()
330 210
    {
331 210
        $class = static::class;
332
333 210
        $methods = [
334 210
            'can',
335
        ];
336
337
        foreach ($methods as $baseMethod) {
338 210
            static::$bootedTraitMethods[$baseMethod] = [];
339
340
            foreach (class_uses_recursive($class) as $trait) {
341
                $method = class_basename($trait) . ucfirst($baseMethod);
342
343
                if (method_exists($class, $method) && !in_array($method, static::$bootedTraitMethods[$baseMethod])) {
344
                    static::$bootedTraitMethods[$baseMethod][] = lcfirst($method);
345
                }
346
            }
347
        }
348
    }
349
}
350