Passed
Push — master ( 9436c7...881d31 )
by Philippe
02:59 queued 12s
created

AbstractManager   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 289
Duplicated Lines 0 %

Test Coverage

Coverage 96.15%

Importance

Changes 6
Bugs 2 Features 0
Metric Value
wmc 41
eloc 96
c 6
b 2
f 0
dl 0
loc 289
ccs 100
cts 104
cp 0.9615
rs 9.1199

23 Methods

Rating   Name   Duplication   Size   Complexity  
A route() 0 21 2
A __construct() 0 11 1
A guard() 0 7 2
A hasExistingModel() 0 3 2
A fieldsWithAssistantFields() 0 13 3
A storeRequest() 0 3 1
A validateConstraints() 0 4 2
A indexSorting() 0 12 3
A can() 0 10 3
A delete() 0 11 3
A model() 0 3 1
A updateRequest() 0 3 1
A indexPagination() 0 9 1
A filters() 0 3 1
A findManaged() 0 7 1
A bootTraitMethods() 0 16 5
A manage() 0 5 1
A indexCollection() 0 18 2
A fieldArrangement() 0 3 1
A fields() 0 3 1
A existingModel() 0 7 2
A indexBuilder() 0 3 1
A requestContainsTranslations() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like AbstractManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use AbstractManager, and based on these observations, apply Extract Interface, too.

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
use Thinktomorrow\Chief\Relations\Relation;
22
23
abstract class AbstractManager
24
{
25
    use RenderingFields,
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...
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\ManagesPagebuilder requires some properties which are not provided by Thinktomorrow\Chief\Management\AbstractManager: $trans, $slug, $page_id, $id
Loading history...
26
        SavingFields,
27
        HasDetails,
28
        HasSections,
29
        ManagesMedia,
30
        ManagesPagebuilder,
31
        TranslatableCommand,
32
        AssistedManager;
33
34
    protected $translation_columns = [];
35
36
    protected $model;
37
38
    /** @var Register */
39
    protected $registration;
40
41
    protected $pageCount                 = 20;
42
    protected $paginated                 = true;
43
    protected static $bootedTraitMethods = [];
44
45 168
    public function __construct(Registration $registration)
46
    {
47 168
        $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...
48
49
        // Upon instantiation, a general model is set that doesn't point to a persisted record.
50 168
        $this->manage(app($this->registration->model()));
51
52
        // Check if key and model are present since the model should be set by the manager itself
53 168
        $this->validateConstraints();
54
55 168
        static::bootTraitMethods();
56 168
    }
57
58 168
    public function manage($model): Manager
59
    {
60 168
        $this->model = $model;
61
62 168
        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...
63
    }
64
65 97
    public function findManaged($id): Manager
66
    {
67 97
        $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

67
        /** @scrutinizer ignore-call */ 
68
        $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...
68
69 97
        $modelInstance = $model::where('id', $id)->withoutGlobalScopes()->first();
70
71 97
        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

71
        return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($modelInstance);
Loading history...
72
    }
73
74 6
    public function indexCollection()
75
    {
76 6
        $model = $this->registration->model();
77
78 6
        $builder = (new $model)->query();
79
80 6
        $this->filters()->apply($builder);
81
82 6
        $builder = $this->indexBuilder($builder);
83
84 6
        $builder = $this->indexSorting($builder);
85
86 6
        if ($this->paginated) {
87 6
            return $this->indexPagination($builder);
88
        }
89
90
        return $builder->get()->map(function ($model) {
91
            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

91
            return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($model);
Loading history...
92
        });
93
    }
94
95 6
    protected function indexBuilder(Builder $builder): Builder
96
    {
97 6
        return $builder;
98
    }
99
100 6
    protected function indexSorting(Builder $builder): Builder
101
    {
102 6
        if ($this->isAssistedBy('publish')) {
103 3
            $builder->orderBy('published', 'DESC');
104
        }
105
106
        // if model has no timestamps, updated_at doesn't exist
107 6
        if ($this->model()->timestamps) {
108 6
            $builder->orderBy('updated_at', 'DESC');
109
        }
110
111 6
        return $builder;
112
    }
113
114 6
    protected function indexPagination($builder): Paginator
115
    {
116 6
        $paginator = $builder->paginate($this->pageCount);
117
118
        $modifiedCollection = $builder->paginate($this->pageCount)->getCollection()->transform(function ($model) {
119 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

119
            return (new static(/** @scrutinizer ignore-type */ $this->registration))->manage($model);
Loading history...
120 6
        });
121
122 6
        return $paginator->setCollection($modifiedCollection);
123
    }
124
125 105
    public function model()
126
    {
127 105
        return $this->model;
128
    }
129
130 106
    public function hasExistingModel(): bool
131
    {
132 106
        return ($this->model && $this->model->exists);
133
    }
134
135
    /**
136
     * If the model exists return it otherwise
137
     * throws a nonExistingRecord exception;
138
     *
139
     * @throws NonExistingRecord
140
     */
141 106
    protected function existingModel()
142
    {
143 106
        if (!$this->hasExistingModel()) {
144 1
            throw new NonExistingRecord('Model does not exist yet but is expected.');
145
        }
146
147 105
        return $this->model;
148
    }
149
150
    /**
151
     * Determine which actions should be available for this
152
     * manager and their respective routed urls.
153
     *
154
     * @param $verb
155
     * @return null|string
156
     * @throws NonExistingRecord
157
     */
158 129
    public function route($verb): ?string
159
    {
160
        $routes = [
161 129
            '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

161
            '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...
162 129
            'create'  => route('chief.back.managers.create', [$this->registration->key()]),
163 129
            'store'   => route('chief.back.managers.store', [$this->registration->key()]),
164
        ];
165
166 129
        if (array_key_exists($verb, $routes)) {
167 75
            return $routes[$verb] ?? null;
168
        }
169
170
        //These routes expect the model to be persisted in the database
171
        $modelRoutes = [
172 106
            'edit'    => route('chief.back.managers.edit', [$this->registration->key(), $this->existingModel()->id]),
173 105
            'update'  => route('chief.back.managers.update', [$this->registration->key(), $this->existingModel()->id]),
174 105
            'delete'  => route('chief.back.managers.delete', [$this->registration->key(), $this->existingModel()->id]),
175 105
            'upload'  => route('chief.back.managers.media.upload', [$this->registration->key(), $this->existingModel()->id]),
176
        ];
177
178 105
        return $modelRoutes[$verb] ?? null;
179
    }
180
181 139
    public function can($verb): bool
182
    {
183 139
        foreach (static::$bootedTraitMethods['can'] as $method) {
184 8
            if (!method_exists($this, $method)) {
185
                continue;
186
            }
187 8
            $this->$method($verb);
188
        }
189
190 135
        return !is_null($this->route($verb));
191
    }
192
193 125
    public function guard($verb): Manager
194
    {
195 125
        if (! $this->can($verb)) {
196 5
            NotAllowedManagerRoute::notAllowedVerb($verb, $this);
197
        }
198
199 120
        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...
200
    }
201
202 87
    public function fields(): Fields
203
    {
204 87
        return new Fields();
205
    }
206
207
    /**
208
     * Enrich the manager fields with any of the assistant specified fields
209
     *
210
     * @return Fields
211
     * @throws \Exception
212
     */
213 110
    public function fieldsWithAssistantFields(): Fields
214
    {
215 110
        $fields = $this->fields();
216
217 110
        foreach ($this->assistants() as $assistant) {
218 84
            if (! method_exists($assistant, 'fields')) {
219 82
                continue;
220
            }
221
222 84
            $fields = $fields->merge($assistant->fields());
223
        }
224
225 110
        return $fields;
226
    }
227
228
    /**
229
     * This determines the arrangement of the manageable fields
230
     * on the create and edit forms. By default, all fields
231
     * are presented in their order of appearance
232
     *
233
     * @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...
234
     * @return FieldArrangement
235
     * @throws \Exception
236
     */
237 3
    public function fieldArrangement($key = null): FieldArrangement
238
    {
239 3
        return new FieldArrangement($this->fieldsWithAssistantFields());
240
    }
241
242 2
    public function delete()
243
    {
244 2
        if($this->model instanceof ActsAsChild) {
245 2
            $this->model->detachAllParentRelations();
246
        }
247
248 2
        if($this->model instanceof ActsAsParent) {
249 2
            $this->model->detachAllChildRelations();
250
        }
251
252 2
        $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

252
        $this->model->/** @scrutinizer ignore-call */ 
253
                      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...
253 2
    }
254
255 3
    public static function filters(): Filters
256
    {
257 3
        return new Filters();
258
    }
259
260
    /**
261
     * This method can be used to manipulate the store request payload
262
     * before being passed to the storing / updating the models.
263
     *
264
     * @param Request $request
265
     * @return Request
266
     */
267 26
    public function storeRequest(Request $request): Request
268
    {
269 26
        return $request;
270
    }
271
272
    /**
273
     * This method can be used to manipulate the update request payload
274
     * before being passed to the storing / updating the models.
275
     *
276
     * @param Request $request
277
     * @return Request
278
     */
279 24
    public function updateRequest(Request $request): Request
280
    {
281 24
        return $request;
282
    }
283
284 83
    protected function requestContainsTranslations(Request $request): bool
285
    {
286 83
        return $request->has('trans');
287
    }
288
289 168
    protected function validateConstraints()
290
    {
291 168
        if (!$this->model) {
292
            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.');
293
        }
294 168
    }
295
296 168
    public static function bootTraitMethods()
297
    {
298 168
        $class = static::class;
299
300
        $methods = [
301 168
            'can'
302
        ];
303
304 168
        foreach ($methods as $baseMethod) {
305 168
            static::$bootedTraitMethods[$baseMethod] = [];
306
307 168
            foreach (class_uses_recursive($class) as $trait) {
308 168
                $method = class_basename($trait) . ucfirst($baseMethod);
309
310 168
                if (method_exists($class, $method) && ! in_array($method, static::$bootedTraitMethods[$baseMethod])) {
311 168
                    static::$bootedTraitMethods[$baseMethod][] = lcfirst($method);
312
                }
313
            }
314
        }
315 168
    }
316
}
317