Completed
Push — develop ( 64265e...12ceca )
by Mohamed
10:07
created

SendMessages::processDirectMessages()   C

Complexity

Conditions 11
Paths 10

Size

Total Lines 65
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 65
rs 5.9999
cc 11
eloc 28
nc 10
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/*
4
 * This file is part of the Tinyissue package.
5
 *
6
 * (c) Mohamed Alsharaf <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Tinyissue\Model\Project\Issue;
13
14
use Illuminate\Database\Eloquent\Collection;
15
use Tinyissue\Model\Message;
16
use Tinyissue\Model\Message\Queue;
17
use Tinyissue\Model\Project;
18
use Tinyissue\Model\Project\Issue;
19
use Tinyissue\Model\Tag;
20
use Tinyissue\Model\User;
21
use Tinyissue\Services\SendMessagesAbstract;
22
23
/**
24
 * SendMessages is a class to manage & process of sending messages about issue changes.
25
 *
26
 * @author Mohamed Alsharaf <[email protected]>
27
 */
28
class SendMessages extends SendMessagesAbstract
29
{
30
    protected $template = 'update_issue';
31
32
    /**
33
     * Collection of tags
34
     *
35
     * @var Collection
36
     */
37
    protected $tags;
38
39
    /**
40
     * Returns an instance of Issue.
41
     *
42
     * @return bool
43
     */
44
    protected function getIssue()
45
    {
46
        if (null === $this->issue) {
47
            $this->issue = $this->getModel();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->getModel() can also be of type object<Tinyissue\Model\Project\Issue\Comment> or object<Tinyissue\Http\Requests\FormRequest\Note>. However, the property $issue is declared as type object<Tinyissue\Model\Project\Issue>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
48
        }
49
50
        return $this->issue;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->issue; (Tinyissue\Model\Project\...quests\FormRequest\Note) is incompatible with the return type declared by the abstract method Tinyissue\Services\SendMessagesAbstract::getIssue of type Tinyissue\Model\Project\Issue.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
51
    }
52
53
    /**
54
     * Returns an instance of Project.
55
     *
56
     * @return Project
57
     */
58
    protected function getProject()
59
    {
60
        return $this->getIssue()->project;
61
    }
62
63
    /**
64
     * Returns the project id.
65
     *
66
     * @return int
67
     */
68
    protected function getProjectId()
69
    {
70
        return $this->getIssue()->project_id;
71
    }
72
73
    /**
74
     * Returns message data belongs to adding an issue.
75
     *
76
     * @param Queue $queue
77
     *
78
     * @return array
79
     */
80
    protected function getMessageDataForAddIssue(Queue $queue)
81
    {
82
        $messageData = [];
83
        $messageData['changeByHeading'] = $queue->changeBy->fullname . ' created a new issue';
0 ignored issues
show
Documentation introduced by
The property fullname does not exist on object<Tinyissue\Model\Project\User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
84
        if ($this->issue->assigned->exists()) {
0 ignored issues
show
Bug introduced by
The property assigned does not seem to exist. Did you mean assigned_to?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
85
            $messageData['changes']['assignee'] = $this->issue->assigned->fullname;
0 ignored issues
show
Bug introduced by
The property assigned does not seem to exist. Did you mean assigned_to?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
86
        }
87
88
        $messageData['changes'] += $this->issue->tags()->with('parent')->get()
89
            ->lists('fullname', 'parent.name')->toArray();
90
91
        if ($this->issue->time_quote) {
92
            $messageData['changes']['time_quote'] = \Html::duration($this->issue->time_quote);
93
        }
94
95
        return $messageData;
96
    }
97
98
    /**
99
     * Returns message data belongs to updating an issue.
100
     *
101
     * @param Queue $queue
102
     *
103
     * @return array
104
     */
105
    protected function getMessageDataForUpdateIssue(Queue $queue)
106
    {
107
        $messageData = [];
108
        $messageData['changeByHeading'] = $queue->changeBy->fullname . ' updated an issue';
0 ignored issues
show
Documentation introduced by
The property fullname does not exist on object<Tinyissue\Model\Project\User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
109
110
        foreach ($queue->payload['dirty'] as $field => $value) {
0 ignored issues
show
Bug introduced by
The expression $queue->payload['dirty'] of type string is not traversable.
Loading history...
111
            // Skip fields that not part of the white list
112
            if (!in_array($field,
113
                ['change_by', 'title', 'body', 'assignee', 'status', 'type', 'resolution', 'time_quote'])
114
            ) {
115
                continue;
116
            }
117
            // Format quote to readable time
118
            $value = $field === 'time_quote' ? \Html::duration($value) : $value;
119
            $value = $field === 'body' ? \Html::format($value) : $value;
120
            $messageData['changes'][$field] = [
121
                'now' => $value,
122
                'was' => $queue->getDataFromPayload('origin.' . $field),
123
            ];
124
        }
125
126
        return $messageData;
127
    }
128
129
    /**
130
     * Returns message data belongs to reopening an issue.
131
     *
132
     * @param Queue $queue
133
     *
134
     * @return array
135
     */
136 View Code Duplication
    protected function getMessageDataForReopenIssue(Queue $queue)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
137
    {
138
        $messageData = [];
139
        $messageData['changeByHeading'] = $queue->changeBy->fullname . ' reopened an issue';
0 ignored issues
show
Documentation introduced by
The property fullname does not exist on object<Tinyissue\Model\Project\User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
140
        $statusNow = $this->issue
141
            ->tags()->with('parent')->get()->where('parent.name', Tag::GROUP_STATUS)->last();
142
        $messageData['changes']['status'] = [
143
            'was' => trans('tinyissue.closed'),
144
            'now' => ($statusNow ? $statusNow->fullname : ''),
145
        ];
146
147
        return $messageData;
148
    }
149
150
    /**
151
     * Returns message data belongs to closing an issue.
152
     *
153
     * @param Queue $queue
154
     *
155
     * @return array
156
     */
157 View Code Duplication
    protected function getMessageDataForCloseIssue(Queue $queue)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
158
    {
159
        $messageData = [];
160
        $messageData['changeByHeading'] = $queue->changeBy->fullname . ' closed an issue';
0 ignored issues
show
Documentation introduced by
The property fullname does not exist on object<Tinyissue\Model\Project\User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
161
        $statusWas = $this->issue
162
            ->tags()->with('parent')->get()->where('parent.name', Tag::GROUP_STATUS)->last();
163
        $messageData['changes']['status'] = [
164
            'was' => ($statusWas ? $statusWas->fullname : ''),
165
            'now' => trans('tinyissue.closed'),
166
        ];
167
168
        return $messageData;
169
    }
170
171
    /**
172
     * Returns message data belongs to assigning an issue to a user.
173
     *
174
     * @param Queue $queue
175
     * @param array $extraData
176
     *
177
     * @return array
178
     */
179
    protected function getMessageDataForAssignIssue(Queue $queue, array $extraData)
180
    {
181
        $messageData = [];
182
        if (!array_key_exists('now', $extraData)) {
183
            $assignTo = $this->getUserById($queue->getDataFromPayload('dirty.assigned_to'));
184
            $extraData = ['now' => $assignTo->fullname];
185
        }
186
187
        $messageData['changes']['assignee'] = $extraData;
188
        $messageData['changeByHeading'] = $queue->changeBy->fullname . ' assigned an issue to ' . $extraData['now'];
0 ignored issues
show
Documentation introduced by
The property fullname does not exist on object<Tinyissue\Model\Project\User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
189
190
        return $messageData;
191
    }
192
193
    /**
194
     * Returns message data belongs to changing an issue tags.
195
     *
196
     * @param Queue $queue
197
     *
198
     * @return array
199
     */
200
    protected function getMessageDataForChangeTagIssue(Queue $queue)
201
    {
202
        $messageData = [];
203
        $messageData['changeByHeading'] = $queue->changeBy->fullname . ' changed an issue tag';
0 ignored issues
show
Documentation introduced by
The property fullname does not exist on object<Tinyissue\Model\Project\User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
204
205
        foreach ($queue->payload['added'] as $tag) {
0 ignored issues
show
Bug introduced by
The expression $queue->payload['added'] of type string is not traversable.
Loading history...
206
            $group = strtolower($tag['group']);
207
            $messageData['changes'][$group] = [
208
                'now'           => $tag['name'],
209
                'id'            => $tag['id'],
210
                'message_limit' => $tag['message_limit'],
211
            ];
212
        }
213
214
        foreach ($queue->payload['removed'] as $tag) {
0 ignored issues
show
Bug introduced by
The expression $queue->payload['removed'] of type string is not traversable.
Loading history...
215
            $group = strtolower($tag['group']);
216
            $messageData['changes'][$group]['was'] = $tag['name'];
217
        }
218
219
        return $messageData;
220
    }
221
222
    /**
223
     * Check that the issue is load.
224
     *
225
     * @return bool
226
     */
227
    protected function validateData()
228
    {
229
        // if issue closed and last issue not closed, then something is wrong skip
230
        if (!$this->issue->isOpen() && $this->latestMessage->event !== Queue::CLOSE_ISSUE) {
231
            return false;
232
        }
233
234
        return $this->issue;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->issue; (Tinyissue\Model\Project\Issue) is incompatible with the return type declared by the abstract method Tinyissue\Services\SendM...sAbstract::validateData of type boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
235
    }
236
237
    /**
238
     * Populate assigned relation in the current issue.
239
     *
240
     * @return void
241
     */
242 View Code Duplication
    protected function populateData()
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
243
    {
244
        $this->issue->setRelation('assigned', $this->getUserById($this->issue->assigned_to));
245
        $creator = $this->getUserById($this->issue->created_by);
246
        if ($creator) {
247
            $this->issue->setRelation('user', $creator);
248
        }
249
        $this->loadIssueCreatorToProjectUsers();
250
    }
251
252
    /**
253
     * Check if the latest message is about closing or reopening an issue.
254
     *
255
     * @return bool
256
     */
257
    public function isStatusMessage()
258
    {
259
        return ((!$this->issue->isOpen() && $this->latestMessage->event === Queue::CLOSE_ISSUE)
260
            || ($this->issue->isOpen() && $this->latestMessage->event === Queue::REOPEN_ISSUE));
261
    }
262
263
    /**
264
     * Process assign to user message. Send direct message to new and previous users and full subscribers.
265
     *
266
     * @return void
267
     */
268
    protected function processDirectMessages()
269
    {
270
        // Stop if issue is closed
271
        if (!$this->getIssue()->isOpen()) {
0 ignored issues
show
Bug introduced by
The method isOpen does only exist in Tinyissue\Model\Project\Issue, but not in Tinyissue\Http\Requests\...l\Project\Issue\Comment.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
272
            return;
273
        }
274
275
        // Fetch all of the assign issue changes
276
        $assignMessages = $this->allMessages->where('event', Queue::ASSIGN_ISSUE);
277
278
        // Skip if no changes
279
        if ($assignMessages->isEmpty()) {
280
            return;
281
        }
282
283
        // Fetch the latest assignee
284
        /** @var Queue $assignMessage */
285
        $assignMessage = $assignMessages->first();
286
287
        // Fetch the user details of the new assignee & previous assignee if this isn't new issue
288
        $assigns = [];
289
        $assigns['new'] = (int)$assignMessage->getDataFromPayload('dirty.assigned_to');
290
        if (!$this->addMessage) {
291
            $previousAssignMessage = $assignMessages->last();
292
            $assigns['old'] = (int)$previousAssignMessage->getDataFromPayload('origin.assigned_to');
293
        }
294
295
        // Fetch users objects for old and new assignee
296
        /** @var \Illuminate\Database\Eloquent\Collection $assignObjects */
297
        $assignObjects = (new User())->whereIn('id', $assigns)->get();
0 ignored issues
show
Documentation Bug introduced by
The method whereIn does not exist on object<Tinyissue\Model\User>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
298
299
        // If for what ever reason the user does not exists, skip this change
300
        // or if there is only one user and is not matching the new assignee.
301
        // then skip this message
302
        if ($assignObjects->count() === 0
303
            || ($assignObjects->count() === 1 && (int)$assignObjects->first()->id !== $assigns['new'])
304
        ) {
305
            return;
306
        }
307
308
        // Get the object of the new assignee
309
        $assignTo = $assignObjects->where('id', $assigns['new'], false)->first();
310
        $users = collect([$this->createProjectUserObject($assignTo)]);
311
312
        // Exclude the user from any other message for this issue
313
        $this->addToExcludeUsers($assignTo);
314
315
        // Data about new and previous assignee
316
        $extraMessageData = [
317
            'now' => $assignTo->fullname,
318
        ];
319
320
        // Make sure that the previous assignee was not the same as the new user
321
        if (array_key_exists('old', $assigns) && $assigns['old'] > 0 && $assigns['new'] !== $assigns['old']) {
322
            $previousAssign = $assignObjects->where('id', $assigns['old'], false)->first();
323
            if ($previousAssign) {
324
                $extraMessageData['was'] = $previousAssign->fullname;
325
                $users->push($this->createProjectUserObject($previousAssign));
326
            }
327
        }
328
329
        // Get message data needed for the message & send
330
        $messageData = $this->getMessageData($assignMessage, $extraMessageData);
331
        $this->sendMessages($users, $messageData);
332
    }
333
334
    /**
335
     * Create user project object for a user
336
     *
337
     * @param User $user
338
     *
339
     * @return Project\User
340
     */
341
    protected function createProjectUserObject(User $user)
342
    {
343
        $userProject = new Project\User([
344
            'user_id'    => $user->id,
345
            'project_id' => $this->getProjectId(),
346
        ]);
347
        $userProject->setRelation('user', $user);
348
        $userProject->setRelation('project', $this->getProject());
349
350
        return $userProject;
351
    }
352
353
    /**
354
     * Get collection of tags or one by ID.
355
     *
356
     * @param null|int $tagId
357
     *
358
     * @return \Illuminate\Support\Collection|Tag
359
     */
360
    protected function getTags($tagId = null)
361
    {
362
        if (null === $this->tags) {
363
            $this->tags = collect([]);
364
        }
365
366
        // Load & extract tag by ID
367
        if (null !== $tagId) {
368
            $tag = $this->tags->where('id', $tagId, false)->first();
369
            if (!$tag) {
370
                $tag = (new Tag())->find($tagId);
0 ignored issues
show
Documentation Bug introduced by
The method find does not exist on object<Tinyissue\Model\Tag>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
371
                $this->tags->push($tag);
372
            }
373
374
            return $tag;
375
        }
376
377
        return $this->tags;
378
    }
379
380
    /**
381
     * Whether or not the user wants to receive the message.
382
     *
383
     * @param Project\User $user
384
     * @param array        $data
385
     *
386
     * @return bool
387
     */
388
    protected function wantToReceiveMessage(Project\User $user, array $data)
389
    {
390
        $status = parent::wantToReceiveMessage($user, $data);
391
392
        if (!$status) {
393
            return false;
394
        }
395
396
        // Search for tag changes and verify if the user can receive messages
397
        foreach ($data['changes'] as $label => $change) {
398
            // Skip other changes that are not related to tag
399
            if (!in_array($label, Tag::getCoreGroups())) {
400
                continue;
401
            }
402
403
            // Fetch tag details
404
            $tag = $this->getTags($change['id']);
405
406
            // Check if user allowed to receive the message
407
            if (!$tag->allowMessagesToUser($user->user)) {
0 ignored issues
show
Documentation introduced by
The property user does not exist on object<Tinyissue\Model\Project\User>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Bug introduced by
The method allowMessagesToUser does only exist in Tinyissue\Model\Tag, but not in Illuminate\Support\Collection.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
408
                return false;
409
            }
410
        }
411
412
        return true;
413
    }
414
}
415