Completed
Push — master ( d83f32...d4821e )
by Rémi
04:29
created

TwitterApiClient   C

Complexity

Total Complexity 29

Size/Duplication

Total Lines 368
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 25

Test Coverage

Coverage 60.51%

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 25
dl 0
loc 368
c 0
b 0
f 0
ccs 95
cts 157
cp 0.6051
rs 5

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 1
A getUser() 0 15 1
B getMentionsTweets() 0 26 5
B getDirectMessages() 0 26 5
A getSentTweets() 0 22 2
A getSentDirectMessages() 0 17 2
B getFollowedUsers() 0 28 2
A sendTweet() 0 13 2
A sendDirectMessage() 0 13 1
A deleteTweet() 0 10 1
A deleteDirectMessage() 0 10 1
A follow() 0 10 1
A unfollow() 0 10 1
A checkRate() 0 11 3
A handleResponse() 0 4 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 36
    public function __construct(
61
        TwitterApiGateway $adapter,
62
        TwitterUserSerializer $userSerializer,
63
        TweetSerializer $tweetSerializer,
64
        TwitterDirectMessageSerializer  $directMessageSerializer
65
    ) {
66 36
        $this->adapter = $adapter;
67
68 36
        $this->tweetSerializer = $tweetSerializer;
69 36
        $this->directMessageSerializer = $directMessageSerializer;
70 36
        $this->userSerializer = $userSerializer;
71
72 36
        $this->rateLimits = [];
73
74 36
        $this->logger = new NullLogger();
75 36
    }
76
77
    /**
78
     * @param string $userName
79
     *
80
     * @return TwitterUser
81
     *
82
     * @throws TwitterException
83
     */
84 3
    public function getUser($userName)
85
    {
86 3
        $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 = $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 = $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
    public function getSentTweets($userName)
182
    {
183
        $this->checkRate(self::GET_SENT_TWEETS);
184
185
        $response = $this->adapter->statusesUserTimeLine(
186
            new UserTimelineQuery(
187
                UserIdentifier::fromScreenName($userName),
188
                200
189
            )
190
        );
191
192
        $this->handleResponse(self::GET_SENT_TWEETS, $response);
193
194
        $tweets = [];
195
        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
            $tweet = $this->tweetSerializer->unserialize($obj);
197
            $id = (string) $tweet->getId();
198
            $tweets[(int) $id] = $tweet;
199
        }
200
        ksort($tweets);
201
        return $tweets;
202
    }
203
204
    /**
205
     * @return TwitterDirectMessage[]
206
     *
207
     * @throws TwitterException
208
     */
209
    public function getSentDirectMessages()
210
    {
211
        $this->checkRate(self::GET_SENT_DIRECT_MESSAGES);
212
213
        $response = $this->adapter->sentDirectMessages(new SentDirectMessageQuery(200));
214
215
        $this->handleResponse(self::GET_SENT_DIRECT_MESSAGES, $response);
216
217
        $dms = [];
218
        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
            $dm = $this->directMessageSerializer->unserialize($obj);
220
            $id = (string) $dm->getId();
221
            $dms[(int) $id] = $dm;
222
        }
223
        ksort($dms);
224
        return $dms;
225
    }
226
227
    /**
228
     * @param TwitterUser $user
229
     *
230
     * @return TwitterUser[]
231
     *
232
     * @throws TwitterException
233
     */
234
    public function getFollowedUsers(TwitterUser $user)
235
    {
236
        $cursor = -1;
237
        $serializedUsers = [];
238
239
        while ($cursor !== 0) {
240
            $this->checkRate(self::GET_FOLLOWED_USERS);
241
242
            $response = $this->adapter->friends(
243
                new FriendsListQuery(
244
                    UserIdentifier::fromId($user->getId()),
245
                    200,
246
                    $cursor
247
                )
248
            );
249
250
            $this->handleResponse(self::GET_FOLLOWED_USERS, $response);
251
252
            $friendsResponse = $response->getContent();
253
            $serializedUsers = array_merge($serializedUsers, $friendsResponse->users);
254
255
            $cursor = $friendsResponse->next_cursor;
256
        }
257
258
        return array_map(function ($serializedUser) {
259
            return $this->userSerializer->unserialize($serializedUser);
260
        }, $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
    public function deleteTweet(Tweet $tweet)
313
    {
314
        $this->checkRate(self::DELETE_TWEET);
315
316
        $params = new DeleteTweetParameters((string) $tweet->getId());
317
318
        $response = $this->adapter->deleteStatus($params);
319
320
        $this->handleResponse(self::DELETE_TWEET, $response);
321
    }
322
323
    /**
324
     * @param TwitterDirectMessage $directMessage
325
     *
326
     * @throws TwitterException
327
     */
328
    public function deleteDirectMessage(TwitterDirectMessage $directMessage)
329
    {
330
        $this->checkRate(self::DELETE_DIRECT_MESSAGE);
331
332
        $params = new DeleteDirectMessageParameters((string) $directMessage->getId());
333
334
        $response = $this->adapter->deleteDirectMessage($params);
335
336
        $this->handleResponse(self::DELETE_DIRECT_MESSAGE, $response);
337
    }
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->checkRate(self::FOLLOW);
349
350 3
        $params =  new FollowParameters(UserIdentifier::fromId($user->getId()));
351
352 3
        $response = $this->adapter->createFriendship($params);
353
354 3
        $this->handleResponse(self::FOLLOW, $response);
355 3
    }
356
357
    /**
358
     * Unfollow a $user
359
     *
360
     * @param  TwitterUser $user
361
     *
362
     * @throws TwitterException
363
     */
364 3
    public function unfollow(TwitterUser $user)
365
    {
366 3
        $this->checkRate(self::UNFOLLOW);
367
368 3
        $params = UserIdentifier::fromId($user->getId());
369
370 3
        $response = $this->adapter->destroyFriendship($params);
371
372 3
        $this->handleResponse(self::UNFOLLOW, $response);
373 3
    }
374
375
    /**
376
     * @param string $category
377
     *
378
     * @throws TwitterRateLimitException
379
     */
380 36
    private function checkRate($category)
381
    {
382 36
        if (!array_key_exists($category, $this->rateLimits)) {
383 36
            return;
384
        }
385
386 12
        $rate = $this->rateLimits[$category];
387 12
        if (!$rate->canMakeAnotherCall()) {
388
            throw TwitterRateLimitException::create($category, $rate);
389
        }
390 12
    }
391
392
    /**
393
     * @param string      $category
394
     * @param ApiResponse $response
395
     */
396 36
    private function handleResponse($category, ApiResponse $response)
397
    {
398 36
        $this->rateLimits[$category] = $response->getRate();
399 36
    }
400
}
401