Passed
Push — master ( bd5142...9b9e59 )
by y
01:36
created

Task   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 484
Duplicated Lines 0 %

Importance

Changes 19
Bugs 1 Features 2
Metric Value
eloc 121
c 19
b 1
f 2
dl 0
loc 484
rs 8.4
wmc 50

38 Methods

Rating   Name   Duplication   Size   Complexity  
A setParent() 0 9 3
A setRenderedAsSeparator() 0 2 1
A addTag() 0 9 2
A getWebhooks() 0 4 1
A getAttachments() 0 2 1
A getStories() 0 2 1
A removeFromProject() 0 11 3
A newSubTask() 0 4 1
A addFollower() 0 2 1
A removeDependents() 0 3 1
A removeFollower() 0 2 1
A addDependencies() 0 3 1
A removeFollowers() 0 6 2
A removeDependent() 0 2 1
A removeDependencies() 0 3 1
A _getMap() 0 11 1
A __toString() 0 2 1
A getComments() 0 3 1
A getUrl() 0 2 1
A addDependents() 0 3 1
A addAttachment() 0 4 1
A getDependents() 0 2 1
A removeDependency() 0 2 1
A newComment() 0 6 1
A duplicate() 0 6 1
A getSubTasks() 0 2 1
A _getDir() 0 2 1
A getDependencies() 0 2 1
A removeTag() 0 6 2
A addToProject() 0 25 4
A addFollowers() 0 9 2
A getEvents() 0 2 1
A addDependency() 0 2 1
A isRenderedAsSeparator() 0 2 1
A addDependent() 0 2 1
A update() 0 6 2
A addWebhook() 0 4 1
A addComment() 0 2 1

How to fix   Complexity   

Complex Class

Complex classes like Task 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 Task, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Helix\Asana;
4
5
use Helix\Asana\Base\AbstractEntity;
6
use Helix\Asana\Base\AbstractEntity\CrudTrait;
7
use Helix\Asana\Base\AbstractEntity\WorkspaceTrait;
8
use Helix\Asana\Event\StoryEvent;
9
use Helix\Asana\Event\TaskEvent;
10
use Helix\Asana\Project\Section;
11
use Helix\Asana\Task\Attachment;
12
use Helix\Asana\Task\CustomValues;
13
use Helix\Asana\Task\Like;
14
use Helix\Asana\Task\Membership;
15
use Helix\Asana\Task\Story;
16
use Helix\Asana\Webhook\TaskWebhook;
17
18
/**
19
 * A task.
20
 *
21
 * @see https://developers.asana.com/docs/#asana-tasks
22
 * @see https://developers.asana.com/docs/#tocS_Task
23
 *
24
 * @method null|User    getAssignee         ()
25
 * @method $this        setAssignee         (null|User $user)
26
 * @method string       getAssigneeStatus   ()
27
 * @method $this        setAssigneeStatus   (string $status)
28
 * @method bool         isCompleted         ()
29
 * @method $this        setCompleted        (bool $completed)
30
 * @method string       getCompletedAt      ()
31
 * @method string       getCreatedAt        ()
32
 * @method null|CustomValues getCustomFields()
33
 * @method bool         hasCustomFields     ()
34
 * @method string       getDueOn            ()
35
 * @method $this        setDueOn            (string $date)
36
 * @method User[]       getFollowers        ()
37
 * @method string       getHtmlNotes        ()
38
 * @method $this        setHtmlNotes        (string $notes)
39
 * @method bool         isLiked             ()
40
 * @method $this        setLiked            (bool $liked)
41
 * @method Like[]       getLikes            ()
42
 * @method Membership[] getMemberships      ()
43
 * @method string       getModifiedAt       ()
44
 * @method string       getName             ()
45
 * @method $this        setName             (string $name)
46
 * @method string       getNotes            ()
47
 * @method $this        setNotes            (string $notes)
48
 * @method int          getNumLikes         ()
49
 * @method int          getNumSubtasks      ()
50
 * @method null|Task    getParent           ()
51
 * @method bool         hasParent           ()
52
 * @method Project[]    getProjects         ()
53
 * @method string       getResourceSubtype  ()
54
 * @method $this        setResourceSubtype  (string $type)
55
 * @method string       getStartOn          ()
56
 * @method $this        setStartOn          (string $date)
57
 * @method Tag[]        getTags             ()
58
 */
59
class Task extends AbstractEntity {
60
61
    use CrudTrait {
62
        update as private _update;
63
    }
64
65
    use WorkspaceTrait;
66
67
    const TYPE = 'task';
68
    const TYPE_DEFAULT = 'default_task';
69
    const TYPE_MILESTONE = 'milestone';
70
71
    const ASSIGN_INBOX = 'inbox';
72
    const ASSIGN_LATER = 'later';
73
    const ASSIGN_NEW = 'new';
74
    const ASSIGN_TODAY = 'today';
75
    const ASSIGN_UPCOMING = 'upcoming';
76
77
    final public function __toString (): string {
78
        return "tasks/{$this->getGid()}";
79
    }
80
81
    final protected function _getDir (): string {
82
        return 'tasks';
83
    }
84
85
    protected function _getMap (): array {
86
        return [
87
            'assignee' => User::class,
88
            'custom_fields' => CustomValues::class,
89
            'followers' => [User::class],
90
            'likes' => [Like::class],
91
            'memberships' => [Membership::class],
92
            'parent' => self::class,
93
            'projects' => [Project::class],
94
            'tags' => [Tag::class],
95
            'workspace' => Workspace::class
96
        ];
97
    }
98
99
    /**
100
     * Uploads a file attachment.
101
     *
102
     * @depends after-create
103
     * @param string $file
104
     * @return Attachment
105
     */
106
    public function addAttachment (string $file) {
107
        /** @var Attachment $attachment */
108
        $attachment = $this->factory(Attachment::class, ['parent' => $this]);
109
        return $attachment->upload($file);
110
    }
111
112
    /**
113
     * Posts a comment.
114
     *
115
     * @depends after-create
116
     * @param string $text
117
     * @return Story
118
     */
119
    public function addComment (string $text) {
120
        return $this->newComment()->setText($text)->create();
1 ignored issue
show
Unused Code introduced by
The call to Helix\Asana\Task\Story::setText() has too many arguments starting with $text. ( Ignorable by Annotation )

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

120
        return $this->newComment()->/** @scrutinizer ignore-call */ setText($text)->create();

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
121
    }
122
123
    /**
124
     * Premium feature.
125
     *
126
     * @depends after-create
127
     * @param Task[] $tasks
128
     * @return $this
129
     */
130
    public function addDependencies (array $tasks) {
131
        $this->api->post("{$this}/addDependencies", ['dependents' => static::_getGids($tasks)]);
132
        return $this;
133
    }
134
135
    /**
136
     * Premium feature.
137
     *
138
     * @depends after-create
139
     * @param Task $task
140
     * @return $this
141
     */
142
    public function addDependency (Task $task) {
143
        return $this->addDependencies([$task]);
144
    }
145
146
    /**
147
     * Premium feature.
148
     *
149
     * @depends after-create
150
     * @param Task $task
151
     * @return $this
152
     */
153
    public function addDependent (Task $task) {
154
        return $this->addDependents([$task]);
155
    }
156
157
    /**
158
     * Premium feature.
159
     *
160
     * @depends after-create
161
     * @param Task[] $tasks
162
     * @return $this
163
     */
164
    public function addDependents (array $tasks) {
165
        $this->api->post("{$this}/addDependents", ['dependents' => static::_getGids($tasks)]);
166
        return $this;
167
    }
168
169
    /**
170
     * Adds a follower.
171
     *
172
     * @param User $user
173
     * @return $this
174
     */
175
    public function addFollower (User $user) {
176
        return $this->addFollowers([$user]);
177
    }
178
179
    /**
180
     * Adds followers.
181
     *
182
     * @param User[] $users
183
     * @return $this
184
     */
185
    public function addFollowers (array $users) {
186
        if ($this->hasGid()) {
187
            $this->api->post("{$this}/addFollowers", ['followers' => static::_getGids($users)]);
188
            $this->_merge('followers', $users);
189
        }
190
        else {
191
            $this->_merge('followers', $users, true);
192
        }
193
        return $this;
194
    }
195
196
    /**
197
     * Adds a tag.
198
     *
199
     * @param Tag $tag
200
     * @return $this
201
     */
202
    public function addTag (Tag $tag) {
203
        if ($this->hasGid()) {
204
            $this->api->post("{$this}/addTag", ['tag' => $tag->getGid()]);
205
            $this->_merge('tags', [$tag]);
206
        }
207
        else {
208
            $this->_merge('tags', [$tag], true);
209
        }
210
        return $this;
211
    }
212
213
    /**
214
     * Adds the task to a project section.
215
     *
216
     * @param Section $section
217
     * @return $this
218
     */
219
    public function addToProject (Section $section) {
220
        $project = $section->getProject();
221
        if ($this->hasGid()) {
222
            $this->api->post("{$this}/addProject", [
223
                'project' => $project->getGid(),
224
                'section' => $section->getGid()
225
            ]);
226
            if (isset($this->data['memberships'])) {
227
                $this->data['memberships'][] = $this->factory(Membership::class)->_setData([
228
                    'project' => $project,
229
                    'section' => $section
230
                ]);
231
            }
232
        }
233
        else {
234
            if (!$this->hasWorkspace()) {
235
                $this->setWorkspace($project->getWorkspace());
1 ignored issue
show
Bug introduced by
It seems like $project->getWorkspace() can also be of type null; however, parameter $workspace of Helix\Asana\Task::setWorkspace() does only seem to accept Helix\Asana\Workspace, maybe add an additional type check? ( Ignorable by Annotation )

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

235
                $this->setWorkspace(/** @scrutinizer ignore-type */ $project->getWorkspace());
Loading history...
236
            }
237
            $membership = $this->factory(Membership::class);
238
            $membership->_set('project', $section->getProject());
239
            $membership->_set('section', $section);
240
            $this->data['memberships'][] = $membership;
241
            $this->diff['memberships'] = true;
242
        }
243
        return $this;
244
    }
245
246
    /**
247
     * Creates and returns a webhook.
248
     *
249
     * @depends after-create
250
     * @param string $target
251
     * @return TaskWebhook
252
     */
253
    public function addWebhook (string $target) {
254
        /** @var TaskWebhook $webhook */
255
        $webhook = $this->factory(TaskWebhook::class);
256
        return $webhook->create($this, $target);
257
    }
258
259
    /**
260
     * Creates and returns job to duplicate the task.
261
     *
262
     * @see https://developers.asana.com/docs/#duplicate-a-task
263
     *
264
     * @depends after-create
265
     * @param string $name
266
     * @param string[] $include
267
     * @return Job
268
     */
269
    public function duplicate (string $name, array $include) {
270
        $remote = $this->api->post("{$this}/duplicate", [
271
            'name' => $name,
272
            'include' => array_values($include)
273
        ]);
274
        return $this->factory(Job::class, $remote);
1 ignored issue
show
Bug introduced by
It seems like $remote can also be of type null; however, parameter $data of Helix\Asana\Base\Data::factory() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

274
        return $this->factory(Job::class, /** @scrutinizer ignore-type */ $remote);
Loading history...
275
    }
276
277
    /**
278
     * Returns the task's files.
279
     *
280
     * @depends after-create
281
     * @return Attachment[]
282
     */
283
    public function getAttachments () {
284
        return $this->loadAll(Attachment::class, "{$this}/attachments");
285
    }
286
287
    /**
288
     * Returns the task's comments.
289
     *
290
     * @depends after-create
291
     * @return Story[]
292
     */
293
    public function getComments () {
294
        return array_values(array_filter($this->getStories(), function(Story $story) {
295
            return $story->isComment();
296
        }));
297
    }
298
299
    /**
300
     * Premium feature.
301
     *
302
     * @depends after-create
303
     * @return Task[]
304
     */
305
    public function getDependencies () {
306
        return $this->loadAll(self::class, "{$this}/dependencies");
307
    }
308
309
    /**
310
     * Premium feature.
311
     *
312
     * @depends after-create
313
     * @return Task[]
314
     */
315
    public function getDependents () {
316
        return $this->loadAll(self::class, "{$this}/dependents");
317
    }
318
319
    /**
320
     * Returns events since the last sync.
321
     *
322
     * @depends after-create
323
     * @param null|string $token
324
     * @return TaskEvent[]|StoryEvent[]
325
     */
326
    public function getEvents (&$token) {
327
        return $this->api->sync($this, $token);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->api->sync($this, $token) returns an array which contains values of type Helix\Asana\Event\ProjectEvent which are incompatible with the documented value type Helix\Asana\Event\StoryE...x\Asana\Event\TaskEvent.
Loading history...
328
    }
329
330
    /**
331
     * Returns the task's activity.
332
     *
333
     * @depends after-create
334
     * @return Story[]
335
     */
336
    public function getStories () {
337
        return $this->loadAll(Story::class, "{$this}/stories");
338
    }
339
340
    /**
341
     * Returns the task's subtasks.
342
     *
343
     * @depends after-create
344
     * @return Task[]
345
     */
346
    public function getSubTasks () {
347
        return $this->loadAll(self::class, "{$this}/subtasks");
348
    }
349
350
    /**
351
     * Returns the task's URL.
352
     *
353
     * @depends after-create
354
     * @return string
355
     */
356
    public function getUrl (): string {
357
        return "https://app.asana.com/0/0/{$this->getGid()}";
358
    }
359
360
    /**
361
     * Returns the task's webhooks.
362
     *
363
     * @depends after-create
364
     * @return TaskWebhook[]
365
     */
366
    public function getWebhooks () {
367
        return $this->loadAll(TaskWebhook::class, 'webhooks', [
368
            'workspace' => $this->getWorkspace()->getGid(),
369
            'resource' => $this->getGid()
370
        ]);
371
    }
372
373
    /**
374
     * @return bool
375
     */
376
    public function isRenderedAsSeparator (): bool {
377
        return $this->_is('is_rendered_as_separator');
378
    }
379
380
    /**
381
     * Instantiates and returns a new comment.
382
     *
383
     * @depends after-create
384
     * @return Story
385
     */
386
    public function newComment () {
387
        /** @var Story $comment */
388
        $comment = $this->factory(Story::class, [
389
            'resource_subtype' => Story::TYPE_COMMENT_ADDED
390
        ]);
391
        return $comment->_set('task', $this);
392
    }
393
394
    /**
395
     * Instantiates and returns a new subtask.
396
     *
397
     * @depends after-create
398
     * @return Task
399
     */
400
    public function newSubTask () {
401
        /** @var Task $sub */
402
        $sub = $this->factory(self::class);
403
        return $sub->setParent($this);
404
    }
405
406
    /**
407
     * Premium feature.
408
     *
409
     * @depends after-create
410
     * @param Task[] $tasks
411
     * @return $this
412
     */
413
    public function removeDependencies (array $tasks) {
414
        $this->api->post("{$this}/removeDependencies", ['dependencies' => static::_getGids($tasks)]);
415
        return $this;
416
    }
417
418
    /**
419
     * Premium feature.
420
     *
421
     * @depends after-create
422
     * @param Task $task
423
     * @return $this
424
     */
425
    public function removeDependency (Task $task) {
426
        return $this->removeDependencies([$task]);
427
    }
428
429
    /**
430
     * Premium feature.
431
     *
432
     * @depends after-create
433
     * @param Task $task
434
     * @return $this
435
     */
436
    public function removeDependent (Task $task) {
437
        return $this->removeDependents([$task]);
438
    }
439
440
    /**
441
     * Premium feature.
442
     *
443
     * @depends after-create
444
     * @param Task[] $tasks
445
     * @return $this
446
     */
447
    public function removeDependents (array $tasks) {
448
        $this->api->post("{$this}/removeDependents", ['dependents' => static::_getGids($tasks)]);
449
        return $this;
450
    }
451
452
    /**
453
     * Removes a follower.
454
     *
455
     * @param User $user
456
     * @return $this
457
     */
458
    public function removeFollower (User $user) {
459
        return $this->removeFollowers([$user]);
460
    }
461
462
    /**
463
     * Removes followers.
464
     *
465
     * @param User[] $users
466
     * @return $this
467
     */
468
    public function removeFollowers (array $users) {
469
        if ($this->hasGid()) {
470
            $this->api->post("{$this}/removeFollowers", ['followers' => static::_getGids($users)]);
471
        }
472
        $this->_remove('followers', $users);
473
        return $this;
474
    }
475
476
    /**
477
     * Removes the task from a project.
478
     *
479
     * @param Project $project
480
     * @return $this
481
     */
482
    public function removeFromProject (Project $project) {
483
        $gid = $project->getGid();
484
        if ($this->hasGid()) {
485
            $this->api->post("{$this}/removeProject", ['project' => $gid]);
486
        }
487
        if (isset($this->data['memberships'])) {
488
            $this->data['memberships'] = array_values(array_filter($this->data['memberships'], function(Membership $membership) use ($gid) {
489
                return $membership->getProject()->getGid() !== $gid;
490
            }));
491
        }
492
        return $this;
493
    }
494
495
    /**
496
     * Removes a tag.
497
     *
498
     * @param Tag $tag
499
     * @return $this
500
     */
501
    public function removeTag (Tag $tag) {
502
        if ($this->hasGid()) {
503
            $this->api->post("{$this}/removeTag", ['tag' => $tag->getGid()]);
504
        }
505
        $this->_remove('tags', [$tag]);
506
        return $this;
507
    }
508
509
    /**
510
     * Makes the task a subtask of another.
511
     *
512
     * @param null|Task $parent
513
     * @return $this
514
     */
515
    public function setParent (?self $parent) {
516
        if ($this->hasGid()) {
517
            $this->api->post("{$this}/setParent", ['parent' => $parent ? $parent->getGid() : null]);
518
            $this->data['parent'] = $parent;
519
        }
520
        else {
521
            $this->_set('parent', $parent);
522
        }
523
        return $this;
524
    }
525
526
    /**
527
     * @param bool $flag
528
     * @return $this
529
     */
530
    public function setRenderedAsSeparator (bool $flag) {
531
        return $this->_set('is_rendered_as_separator', $flag);
532
    }
533
534
    /**
535
     * @return $this
536
     */
537
    public function update () {
538
        $this->_update();
539
        if ($this->hasCustomFields()) {
540
            $this->getCustomFields()->diff = [];
541
        }
542
        return $this;
543
    }
544
545
}