TwitterApiClient::follow()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 12
ccs 7
cts 7
cp 1
rs 9.4285
cc 1
eloc 6
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Twitter\API\REST\Client;
4
5
use Psr\Log\LoggerAwareInterface;
6
use Psr\Log\LoggerAwareTrait;
7
use Psr\Log\NullLogger;
8
use Twitter\API\REST\TwitterApiGateway;
9
use Twitter\API\REST\TwitterClient;
10
use Twitter\Object\Tweet;
11
use Twitter\Object\TwitterDirectMessage;
12
use Twitter\Object\TwitterUser;
13
use Twitter\Serializer\TweetSerializer;
14
use Twitter\Serializer\TwitterDirectMessageSerializer;
15
use Twitter\Serializer\TwitterUserSerializer;
16
use Twitter\API\Exception\TwitterException;
17
use Twitter\API\Exception\TwitterRateLimitException;
18
use Twitter\API\REST\DTO\DeleteDirectMessageParameters;
19
use Twitter\API\REST\DTO\DeleteTweetParameters;
20
use Twitter\API\REST\DTO\DirectMessageParameters;
21
use Twitter\API\REST\DTO\FollowParameters;
22
use Twitter\API\REST\DTO\TweetParameters;
23
use Twitter\API\REST\DTO\UserIdentifier;
24
use Twitter\API\REST\Query\DirectMessage\DirectMessageQuery;
25
use Twitter\API\REST\Query\DirectMessage\SentDirectMessageQuery;
26
use Twitter\API\REST\Query\Friends\FriendsListQuery;
27
use Twitter\API\REST\Query\Tweet\MentionsTimelineQuery;
28
use Twitter\API\REST\Query\Tweet\UserTimelineQuery;
29
use Twitter\API\REST\Query\User\UserInformationQuery;
30
use Twitter\API\REST\Response\ApiRate;
31
use Twitter\API\REST\Response\ApiResponse;
32
33
class TwitterApiClient implements TwitterClient, LoggerAwareInterface
34
{
35
    use LoggerAwareTrait;
36
37
    /** @var TwitterApiGateway */
38
    private $adapter;
39
40
    /** @var TwitterUserSerializer */
41
    private $userSerializer;
42
43
    /** @var TweetSerializer */
44
    private $tweetSerializer;
45
46
    /** @var TwitterDirectMessageSerializer */
47
    private $directMessageSerializer;
48
49
    /** @var ApiRate[] */
50
    private $rateLimits;
51
52
    /**
53
     * Constructor
54
     *
55
     * @param TwitterApiGateway              $adapter
56
     * @param TwitterUserSerializer          $userSerializer
57
     * @param TweetSerializer                $tweetSerializer
58
     * @param TwitterDirectMessageSerializer $directMessageSerializer
59
     */
60 54
    public function __construct(
61
        TwitterApiGateway $adapter,
62
        TwitterUserSerializer $userSerializer,
63
        TweetSerializer $tweetSerializer,
64
        TwitterDirectMessageSerializer  $directMessageSerializer
65
    ) {
66 54
        $this->adapter = $adapter;
67
68 54
        $this->tweetSerializer = $tweetSerializer;
69 54
        $this->directMessageSerializer = $directMessageSerializer;
70 54
        $this->userSerializer = $userSerializer;
71
72 54
        $this->rateLimits = [];
73
74 54
        $this->logger = new NullLogger();
75 54
    }
76
77
    /**
78
     * @param string $userName
79
     *
80
     * @return TwitterUser
81
     *
82
     * @throws TwitterException
83
     */
84 6
    public function getUser($userName)
85
    {
86 6
        $this->checkRate(self::GET_USER);
87
88 3
        $response = $this->adapter->getUserInformation(
89 3
            new UserInformationQuery(
90 3
                UserIdentifier::fromScreenName($userName),
91 1
                true
92 2
            )
93 2
        );
94
95 3
        $this->handleResponse(self::GET_USER, $response);
96
97 3
        return $this->userSerializer->unserialize($response->getContent());
0 ignored issues
show
Documentation introduced by
$response->getContent() is of type array|object|null, but the function expects a object<stdClass>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
98
    }
99
100
    /**
101
     * Gets the mention tweets with id between $from and $to
102
     *
103
     * @param  string $from
104
     * @param  string $to
105
     *
106
     * @return Tweet[]
107
     *
108
     * @throws TwitterException
109
     */
110 9
    public function getMentionsTweets($from = null, $to = null)
111
    {
112 9
        $this->logger->info('Retrieving tweets messages from ' . $from . ' to ' . $to);
113
114 9
        $this->checkRate(self::GET_MENTIONS);
115
116 9
        $tweets = [];
117 9
        $response = $this->adapter->statusesMentionsTimeLine(new MentionsTimelineQuery(200, $from, $to, true));
118
119 9
        $this->handleResponse(self::GET_MENTIONS, $response);
120
121 9
        foreach ($response->getContent() as $index => $obj) {
0 ignored issues
show
Bug introduced by
The expression $response->getContent() of type array|object|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
122 6
            $tweet = $this->tweetSerializer->unserialize($obj);
123 6
            $id = (string) $tweet->getId();
124 6
            $tweets[(int) $id] = $tweet;
125 6
        }
126
127 9
        if (!empty($tweets) && $from !== null) {
128 6
            $moreTweets = $this->getMentionsTweets($from, min(array_keys($tweets))-1);
129 6
            if (!empty($moreTweets)) {
130 3
                $tweets += $moreTweets;
131 2
            }
132 4
        }
133 9
        ksort($tweets);
134 9
        return $tweets;
135
    }
136
137
    /**
138
     * Gets the direct messages with id between $from and $to
139
     *
140
     * @param  string $from
141
     * @param  string $to
142
     *
143
     * @return TwitterDirectMessage[]
144
     *
145
     * @throws TwitterException
146
     */
147 9
    public function getDirectMessages($from = null, $to = null)
148
    {
149 9
        $this->logger->info('Retrieving direct messages from ' . $from . ' to ' . $to);
150
151 9
        $this->checkRate(self::GET_DIRECT_MESSAGES);
152
153 9
        $dms = [];
154 9
        $response = $this->adapter->directMessages(new DirectMessageQuery(200, $from, $to, true));
155
156 9
        $this->handleResponse(self::GET_DIRECT_MESSAGES, $response);
157
158 9
        foreach ($response->getContent() as $index => $obj) {
0 ignored issues
show
Bug introduced by
The expression $response->getContent() of type array|object|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
159 6
            $dm = $this->directMessageSerializer->unserialize($obj);
160 6
            $id = (string) $dm->getId();
161 6
            $dms[(int) $id] = $dm;
162 6
        }
163
164 9
        if (!empty($dms) && $from !== null) {
165 6
            $moreDms = $this->getDirectMessages($from, min(array_keys($dms))-1);
166 6
            if (!empty($moreDms)) {
167 3
                $dms += $moreDms;
168 2
            }
169 4
        }
170 9
        ksort($dms);
171 9
        return $dms;
172
    }
173
174
    /**
175
     * @param string $userName
176
     *
177
     * @return Tweet[]
178
     *
179
     * @throws TwitterException
180
     */
181 3
    public function getSentTweets($userName)
182
    {
183 3
        $this->checkRate(self::GET_SENT_TWEETS);
184
185 3
        $response = $this->adapter->statusesUserTimeLine(
186 3
            new UserTimelineQuery(
187 3
                UserIdentifier::fromScreenName($userName),
188 1
                200
189 2
            )
190 2
        );
191
192 3
        $this->handleResponse(self::GET_SENT_TWEETS, $response);
193
194 3
        $tweets = [];
195 3
        foreach ($response->getContent() as $index => $obj) {
0 ignored issues
show
Bug introduced by
The expression $response->getContent() of type array|object|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
196 3
            $tweet = $this->tweetSerializer->unserialize($obj);
197 3
            $id = (string) $tweet->getId();
198 3
            $tweets[(int) $id] = $tweet;
199 2
        }
200 3
        ksort($tweets);
201 3
        return $tweets;
202
    }
203
204
    /**
205
     * @return TwitterDirectMessage[]
206
     *
207
     * @throws TwitterException
208
     */
209 3
    public function getSentDirectMessages()
210
    {
211 3
        $this->checkRate(self::GET_SENT_DIRECT_MESSAGES);
212
213 3
        $response = $this->adapter->sentDirectMessages(new SentDirectMessageQuery(200));
214
215 3
        $this->handleResponse(self::GET_SENT_DIRECT_MESSAGES, $response);
216
217 3
        $dms = [];
218 3
        foreach ($response->getContent() as $index => $obj) {
0 ignored issues
show
Bug introduced by
The expression $response->getContent() of type array|object|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
219 3
            $dm = $this->directMessageSerializer->unserialize($obj);
220 3
            $id = (string) $dm->getId();
221 3
            $dms[(int) $id] = $dm;
222 2
        }
223 3
        ksort($dms);
224 3
        return $dms;
225
    }
226
227
    /**
228
     * @param TwitterUser $user
229
     *
230
     * @return TwitterUser[]
231
     *
232
     * @throws TwitterException
233
     */
234 3
    public function getFollowedUsers(TwitterUser $user)
235
    {
236 3
        $cursor = -1;
237 3
        $serializedUsers = [];
238
239 3
        while ($cursor !== 0) {
240 3
            $this->checkRate(self::GET_FOLLOWED_USERS);
241
242 3
            $response = $this->adapter->friends(
243 3
                new FriendsListQuery(
244 3
                    UserIdentifier::fromId($user->getId()),
245 3
                    200,
246 1
                    $cursor
247 2
                )
248 2
            );
249
250 3
            $this->handleResponse(self::GET_FOLLOWED_USERS, $response);
251
252 3
            $friendsResponse = $response->getContent();
253 3
            $serializedUsers = array_merge($serializedUsers, $friendsResponse->users);
254
255 3
            $cursor = $friendsResponse->next_cursor;
256 2
        }
257
258 3
        return array_map(function ($serializedUser) {
259 3
            return $this->userSerializer->unserialize($serializedUser);
260 3
        }, $serializedUsers);
261
    }
262
263
    /**
264
     * Sends a tweet
265
     *
266
     * @param  string $message
267
     * @param  Tweet $replyTo
268
     *
269
     * @throws TwitterException
270
     */
271 6
    public function sendTweet($message, Tweet $replyTo = null)
272
    {
273 6
        $this->checkRate(self::SEND_TWEET);
274
275 6
        $params =  new TweetParameters(
276 6
            $message,
277 6
            $replyTo !== null ? (int) ((string) $replyTo->getId()) : null
278 4
        );
279
280 6
        $response = $this->adapter->updateStatus($params);
281
282 6
        $this->handleResponse(self::SEND_TWEET, $response);
283 6
    }
284
285
    /**
286
     * Sends a direct message to $user
287
     *
288
     * @param  TwitterUser $user
289
     * @param  string      $message
290
     *
291
     * @throws TwitterException
292
     */
293 3
    public function sendDirectMessage(TwitterUser $user, $message)
294
    {
295 3
        $this->checkRate(self::SEND_DIRECT_MESSAGE);
296
297 3
        $params =  new DirectMessageParameters(
298 3
            UserIdentifier::fromId($user->getId()),
299 1
            $message
300 2
        );
301
302 3
        $response = $this->adapter->newDirectMessage($params);
303
304 3
        $this->handleResponse(self::SEND_DIRECT_MESSAGE, $response);
305 3
    }
306
307
    /**
308
     * @param Tweet $tweet
309
     *
310
     * @throws TwitterException
311
     */
312 3
    public function deleteTweet(Tweet $tweet)
313
    {
314 3
        $this->checkRate(self::DELETE_TWEET);
315
316 3
        $params = new DeleteTweetParameters((string) $tweet->getId());
317
318 3
        $response = $this->adapter->deleteStatus($params);
319
320 3
        $this->handleResponse(self::DELETE_TWEET, $response);
321 3
    }
322
323
    /**
324
     * @param TwitterDirectMessage $directMessage
325
     *
326
     * @throws TwitterException
327
     */
328 3
    public function deleteDirectMessage(TwitterDirectMessage $directMessage)
329
    {
330 3
        $this->checkRate(self::DELETE_DIRECT_MESSAGE);
331
332 3
        $params = new DeleteDirectMessageParameters((string) $directMessage->getId());
333
334 3
        $response = $this->adapter->deleteDirectMessage($params);
335
336 3
        $this->handleResponse(self::DELETE_DIRECT_MESSAGE, $response);
337 3
    }
338
339
    /**
340
     * Follow a $user
341
     *
342
     * @param  TwitterUser $user
343
     *
344
     * @throws TwitterException
345
     */
346 3
    public function follow(TwitterUser $user)
347
    {
348 3
        $this->logger->info('Follow @' . $user->getScreenName());
349
350 3
        $this->checkRate(self::FOLLOW);
351
352 3
        $params =  new FollowParameters(UserIdentifier::fromId($user->getId()));
353
354 3
        $response = $this->adapter->createFriendship($params);
355
356 3
        $this->handleResponse(self::FOLLOW, $response);
357 3
    }
358
359
    /**
360
     * Unfollow a $user
361
     *
362
     * @param  TwitterUser $user
363
     *
364
     * @throws TwitterException
365
     */
366 3
    public function unfollow(TwitterUser $user)
367
    {
368 3
        $this->checkRate(self::UNFOLLOW);
369
370 3
        $params = UserIdentifier::fromId($user->getId());
371
372 3
        $response = $this->adapter->destroyFriendship($params);
373
374 3
        $this->handleResponse(self::UNFOLLOW, $response);
375 3
    }
376
377
    /**
378
     * @param string  $category
379
     * @param ApiRate $rate
380
     */
381 54
    public function setRateLimit($category, ApiRate $rate)
382
    {
383 54
        $this->rateLimits[$category] = $rate;
384 54
    }
385
386
    /**
387
     * @param string $category
388
     *
389
     * @throws TwitterRateLimitException
390
     */
391 54
    private function checkRate($category)
392
    {
393 54
        if (!array_key_exists($category, $this->rateLimits)) {
394 51
            return;
395
        }
396
397 15
        $rate = $this->rateLimits[$category];
398 15
        if (!$rate->canMakeAnotherCall()) {
399 3
            throw TwitterRateLimitException::create($category, $rate);
400
        }
401 12
    }
402
403
    /**
404
     * @param string      $category
405
     * @param ApiResponse $response
406
     */
407 51
    private function handleResponse($category, ApiResponse $response)
408
    {
409 51
        $this->setRateLimit($category, $response->getRate());
410 51
    }
411
}
412