Chirp::request()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 2
nc 2
nop 3
1
<?php
2
/**
3
 * This file is part of Chirp package
4
 *
5
 * Copyright (c) 2015 Alberto Pagliarini
6
 *
7
 * Licensed under the MIT license
8
 * https://github.com/batopa/chirp/blob/master/LICENSE
9
 */
10
namespace Bato\Chirp;
11
12
use Bato\Chirp\Utility\Parser;
13
use MongoDB\Client;
14
use Abraham\TwitterOAuth\TwitterOAuth;
15
16
/**
17
 * Chirp Class
18
 *
19
 * Help to create a cache for Twitter using MongoDB
20
 *
21
 */
22
class Chirp
23
{
24
25
    /**
26
     * The Database object
27
     *
28
     * @var \MongoDB\Database
29
     */
30
    private $db = null;
31
32
    /**
33
     * The instance of TwitterOAuth
34
     *
35
     * @var \Abraham\TwitterOAuth\TwitterOAuth
36
     */
37
    private $twitter;
38
39
    /**
40
     * Constructor
41
     *
42
     * Passing $twitterAuthConf and/or $mongoConf  it tries to setup them
43
     *
44
     * $twitterAuthConf must contain
45
     * - consumer_key
46
     * - consumer_secret
47
     * - oauth_access_token
48
     * - oauth_access_token_secret
49
     *
50
     * $mongoConf must contain
51
     *  - db: the name of mongo database to use
52
     *
53
     *  and can contain
54
     * - uri
55
     * - uriOptions
56
     * - driverOptions
57
     *
58
     * used for MongoDB connection
59
     *
60
     * @see \MongoDB\Client for $mongoConf
61
     * @param array $twitterAuthConf
62
     * @param array $mongoDbConf
63
     */
64
    public function __construct(array $twitterAuthConf = array(), array $mongoDbConf = array())
65
    {
66
        if (!empty($twitterAuthConf)) {
67
            $this->setupTwitter($twitterAuthConf);
68
        }
69
        if (!empty($mongoDbConf)) {
70
            $this->setupMongoDb($mongoDbConf);
71
        }
72
    }
73
74
    /**
75
     * Setup TwitterOAuth
76
     *
77
     * $twitterAuthConf must contain
78
     * - consumer_key
79
     * - consumer_secret
80
     * - oauth_access_token
81
     * - oauth_access_token_secret
82
     *
83
     * @param array $twitterAuthConf
84
     * @return $this
85
     */
86
    public function setupTwitter(array $twitterAuthConf)
87
    {
88
        $twitterAuthConf += [
89
            'consumer_key' => null,
90
            'consumer_secret' => null,
91
            'oauth_access_token' => null,
92
            'oauth_access_token_secret' => null
93
        ];
94
        $this->twitter = new TwitterOAuth(
95
            $twitterAuthConf['consumer_key'],
96
            $twitterAuthConf['consumer_secret'],
97
            $twitterAuthConf['oauth_access_token'],
98
            $twitterAuthConf['oauth_access_token_secret']
99
        );
100
        $this->twitter->setDecodeJsonAsArray(true);
101
        return $this;
102
    }
103
104
    /**
105
     * Setup MongoDB connection
106
     *
107
     * $mongoDbConf must contain
108
     *  - db: the name of mongo database to use
109
     *
110
     *  and can contain
111
     * - uri
112
     * - uriOptions
113
     * - driverOptions
114
     *
115
     * @see \MongoDB\Client for $mongoConf
116
     * @param array $mongoDbConf
117
     * @return $this
118
     */
119
    public function setupMongoDb(array $mongoDbConf)
120
    {
121
        $mongoDbConf += [
122
            'uri' => 'mongodb://localhost:27017',
123
            'uriOptions' => [],
124
            'driverOptions' => [],
125
            'db' => ''
126
        ];
127
        $client = new Client($mongoDbConf['uri'], $mongoDbConf['uriOptions'], $mongoDbConf['driverOptions']);
128
        $this->db = $client->selectDatabase($mongoDbConf['db']);
129
        return $this;
130
    }
131
132
    /**
133
     * Return TwitterOAuth instance
134
     *
135
     * @return \Abraham\TwitterOAuth\TwitterOAuth
136
     */
137
    public function getTwitter()
138
    {
139
        if (empty($this->twitter)) {
140
            throw new \RuntimeException(
141
                'You have to setup twitter before use it. See \Bato\Chirp\Chirp::setupTwitter()'
142
            );
143
        }
144
        return $this->twitter;
145
    }
146
147
    /**
148
     * Return the instance of MongoDB database
149
     *
150
     * @return \MongoDB\Database
151
     */
152
    public function getDb()
153
    {
154
        if (empty($this->db)) {
155
            throw new \RuntimeException(
156
                'You have to setup MongoDB connection before use it. See \Bato\Chirp\Chirp::setupMongoDb()'
157
            );
158
        }
159
        return $this->db;
160
    }
161
162
    /**
163
     * Starting from twitter endpoint return the related collection
164
     * It replaces "/" with "-" after removing trailing "/", for example:
165
     *
166
     * endpoint "statuses/user_timeline" corresponds to "statuses-user_timeline" collection
167
     *
168
     * @param string $endpoint [description]
169
     * @return \MongoDB\Collection
170
     */
171
    public function getCollection($endpoint)
172
    {
173
        $name = Parser::normalize($endpoint);
174
        return $this->getDb()
175
            ->selectCollection($name);
176
    }
177
178
    /**
179
     * Perform a Twitter request
180
     *
181
     * @param string $endpoint the endpoint for example 'statuses/user_timeline'
182
     * @param array $query an array that specify query string to use with the endpoint
183
     * @param string $requestMethod the http request method
184
     * @return array
185
     */
186
    public function request($endpoint, $query = [], $requestMethod = 'get')
187
    {
188
        $validMethods = ['get', 'post', 'put', 'delete'];
189
        $requestMethod = strtolower($requestMethod);
190
        if (!in_array($requestMethod, $validMethods)) {
191
            throw new \UnexpectedValueException('Unsupported http request method ' . $requestMethod);
192
        }
193
        $response = $this->getTwitter()
194
            ->{$requestMethod}($endpoint, $query);
195
196
        return $response;
197
    }
198
199
    /**
200
     * Read results from a collection (default self::collection)
201
     * using $filter to filter results
202
     *
203
     * If $options['limit'] = 1 return a \MongoDB\BSONDocument object
204
     * else return an array or a cursor depending from $options['returnType']
205
     *
206
     * @param string $endpoint
207
     * @param array $filter
208
     * @param array $options
209
     * @return object|array
210
     */
211
    public function read($endpoint, array $filter = [], array $options = [])
212
    {
213
        $options += ['returnType' => 'array'];
214
        $collection = $this->getCollection($endpoint);
215
        // delegate to MongoDB\Collection::findOne()
216
        if (isset($options['limit']) && $options['limit'] === 1) {
217
            return $collection->findOne($filter, $options);
218
        }
219
        $cursor = $collection->find($filter, $options);
220
        return ($options['returnType'] == 'array') ? iterator_to_array($cursor) : $cursor;
221
    }
222
223
    /**
224
     * Perform twitter request and save results in db.
225
     *
226
     * Possible $options are:
227
     * - query: an array used for compose query string
228
     * - grep: an array used for look up. Results matching the grep criteria will be saved.
229
     *         Example
230
     *         ```
231
     *         [
232
     *             'key_to_look_up' => ['match1', 'match2'],
233
     *             'other_key_to_look_up' => ['match3']
234
     *         ]
235
     *         ```
236
     * - require: an array of string of keys required.
237
     *            Only results with those fields will be saved.
238
     *            Use point separtated string to go deep in results, for example
239
     *            `'key1.key2.key3'` checks against`[ 'key1' => ['key2' => ['key3' => 'some_value']]] `
240
     *
241
     * @param string $endpoint the twitter endpoint for example 'statuses/user_timeline'
242
     * @param array $options
243
     * @return array
244
     */
245
    public function write($endpoint, array $options = [])
246
    {
247
        $options = $this->prepareWriteOptions($options);
248
        $response = $this->request($endpoint, $options['query']);
249
        if (empty($response)) {
250
            return ['saved' => [], 'read'=> []];
251
        }
252
253
        if (array_key_exists('errors', $response)) {
254
            return $response;
255
        }
256
257
        $collection = $this->getCollection($endpoint);
258
        $tweets = $this->filterToSave($response, $collection, $options);
259
        if (!empty($tweets)) {
260
            $collection->insertMany($tweets);
261
        }
262
263
        return [
264
            'saved' => $tweets,
265
            'read'=> $response
266
        ];
267
    }
268
269
    /**
270
     * Check and return $options to be used in self::write()
271
     *
272
     * @param array $options
273
     * @return array
274
     */
275
    private function prepareWriteOptions($options)
276
    {
277
        $options += [
278
            'query' => [],
279
            'grep' => [],
280
            'require' => []
281
        ];
282
        foreach (['query', 'grep', 'require'] as $test) {
283
            if (!is_array($options[$test])) {
284
                throw new \UnexpectedValueException('"' . $test . '" option must be an array');
285
            }
286
        }
287
        $options['query'] = array_filter($options['query']);
288
        return $options;
289
    }
290
291
    /**
292
     * Given an array of tweets and a collection
293
     * return the tweets that missing from the collection
294
     *
295
     * @see self::write() for $options
296
     * @param array $tweets
297
     * @param \MongoDB\Collection $collection
298
     * @param array $options
299
     * @return array
300
     */
301
    private function filterToSave(array $tweets, \MongoDB\Collection $collection, array $options)
302
    {
303
        $toSave = [];
304
        foreach ($tweets as $key => $tweet) {
305
            if (!Parser::match($tweet, $options)) {
306
                continue;
307
            }
308
309
            $countTweets = $collection->count(['id_str' => $tweet['id_str']]);
0 ignored issues
show
Deprecated Code introduced by
The method MongoDB\Collection::count() has been deprecated with message: 1.4

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
310
            if ($countTweets === 0) {
311
                $toSave[] = $tweet;
312
            }
313
        }
314
        return $toSave;
315
    }
316
}
317