Completed
Push — master ( 62f537...55f32a )
by Alberto
02:16
created

Chirp::prepareWriteOptions()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 15
rs 9.4285
cc 3
eloc 10
nc 3
nop 1
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 $twitterAuthConf
0 ignored issues
show
Bug introduced by
There is no parameter named $twitterAuthConf. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
117
     * @param array $mongoDbConf
118
     * @return $this
119
     */
120
    public function setupMongoDb(array $mongoDbConf)
121
    {
122
        $mongoDbConf += [
123
            'uri' => 'mongodb://localhost:27017',
124
            'uriOptions' => [],
125
            'driverOptions' => [],
126
            'db' => ''
127
        ];
128
        $client = new Client($mongoDbConf['uri'], $mongoDbConf['uriOptions'], $mongoDbConf['driverOptions']);
129
        $this->db = $client->selectDatabase($mongoDbConf['db']);
130
        return $this;
131
    }
132
133
    /**
134
     * Return TwitterOAuth instance
135
     *
136
     * @return \Abraham\TwitterOAuth\TwitterOAuth
137
     */
138
    public function getTwitter()
139
    {
140
        if (empty($this->twitter)) {
141
            throw new \RuntimeException(
142
                'You have to setup twitter before use it. See \Bato\Chirp\Chirp::setupTwitter()'
143
            );
144
        }
145
        return $this->twitter;
146
    }
147
148
    /**
149
     * Return the instance of MongoDB database
150
     *
151
     * @return \MongoDB\Database
152
     */
153
    public function getDb()
154
    {
155
        if (empty($this->db)) {
156
            throw new \RuntimeException(
157
                'You have to setup MongoDB connection before use it. See \Bato\Chirp\Chirp::setupMongoDb()'
158
            );
159
        }
160
        return $this->db;
161
    }
162
163
    /**
164
     * Starting from twitter endpoint return the related collection
165
     * It replaces "/" with "-" after removing trailing "/", for example:
166
     *
167
     * endpoint "statuses/user_timeline" corresponds to "statuses-user_timeline" collection
168
     *
169
     * @param string $endpoint [description]
170
     * @return \MongoDB\Collection
171
     */
172
    public function getCollection($endpoint)
173
    {
174
        $name = Parser::normalize($endpoint);
175
        return $this->getDb()
176
            ->selectCollection($name);
177
    }
178
179
    /**
180
     * Perform a Twitter request
181
     *
182
     * @param string $endpoint the endpoint for example 'statuses/user_timeline'
183
     * @param array $query an array that specify query string to use with the endpoint
184
     * @param string $requestMethod the http request method
185
     * @return array
186
     */
187
    public function request($endpoint, $query = [], $requestMethod = 'get')
188
    {
189
        $validMethods = ['get', 'post', 'put', 'delete'];
190
        $requestMethod = strtolower($requestMethod);
191
        if (!in_array($requestMethod, $validMethods)) {
192
            throw new \UnexpectedValueException('Unsupported http request method ' . $requestMethod);
193
        }
194
        $response = $this->getTwitter()
195
            ->{$requestMethod}($endpoint, $query);
196
197
        return $response;
198
    }
199
200
    /**
201
     * Read results from a collection (default self::collection)
202
     * using $filter to filter results
203
     *
204
     * If $options['limit'] = 1 return a \MongoDB\BSONDocument object
205
     * else return an array or a cursor depending from $options['returnType']
206
     *
207
     * @param string $endpoint
208
     * @param array $filter
209
     * @param array $options
210
     * @return object|array
211
     */
212
    public function read($endpoint, array $filter = [], array $options = [])
213
    {
214
        $options += ['returnType' => 'array'];
215
        $collection = $this->getCollection($endpoint);
216
        // delegate to MongoDB\Collection::findOne()
217
        if (isset($options['limit']) && $options['limit'] === 1) {
218
            return $collection->findOne($filter, $options);
219
        }
220
        $cursor = $collection->find($filter, $options);
221
        return ($options['returnType'] == 'array') ? iterator_to_array($cursor) : $cursor;
222
    }
223
224
    /**
225
     * Perform twitter request and save results in db.
226
     *
227
     * Possible $options are:
228
     * - query: an array used for compose query string
229
     * - grep: an array used for look up. Results matching the grep criteria will be saved.
230
     *         Example
231
     *         ```
232
     *         [
233
     *             'key_to_look_up' => ['match1', 'match2'],
234
     *             'other_key_to_look_up' => ['match3']
235
     *         ]
236
     *         ```
237
     * - require: an array of string of keys required.
238
     *            Only results with those fields will be saved.
239
     *            Use point separtated string to go deep in results, for example
240
     *            `'key1.key2.key3'` checks against`[ 'key1' => ['key2' => ['key3' => 'some_value']]] `
241
     *
242
     * @param string $endpoint the twitter endpoint for example 'statuses/user_timeline'
243
     * @param array $options
244
     * @return array
245
     */
246
    public function write($endpoint, array $options = [])
247
    {
248
        $options = $this->prepareWriteOptions($options);
249
        $response = $this->request($endpoint, $options['query']);
250
        if (empty($response)) {
251
            return ['saved' => [], 'read'=> []];
252
        }
253
254
        if (array_key_exists('errors', $response)) {
255
            return $response;
256
        }
257
258
        $collection = $this->getCollection($endpoint);
259
        $tweets = $this->filterToSave($response, $collection, $options);
260
        if (!empty($tweets)) {
261
            $collection->insertMany($tweets);
262
        }
263
264
        return [
265
            'saved' => $tweets,
266
            'read'=> $response
267
        ];
268
    }
269
270
    /**
271
     * Check and return $options to be used in self::write()
272
     *
273
     * @param string $endpoint the twitter endpoint for example 'statuses/user_timeline'
0 ignored issues
show
Bug introduced by
There is no parameter named $endpoint. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
274
     * @param array $options
275
     * @return array
276
     */
277
    private function prepareWriteOptions($options)
278
    {
279
        $options += [
280
            'query' => [],
281
            'grep' => [],
282
            'require' => []
283
        ];
284
        foreach (['query', 'grep', 'require'] as $test) {
285
            if (!is_array($options[$test])) {
286
                throw new \UnexpectedValueException('"' . $test . '" option must be an array');
287
            }
288
        }
289
        $options['query'] = array_filter($options['query']);
290
        return $options;
291
    }
292
293
    /**
294
     * Given an array of tweets and a collection
295
     * return the tweets that missing from the collection
296
     *
297
     * @see self::write() for $options
298
     * @param array $tweets
299
     * @param \MongoDB\Collection $collection
300
     * @param array $options
301
     * @return array
302
     */
303
    private function filterToSave(array $tweets, \MongoDB\Collection $collection, array $options)
304
    {
305
        $toSave = [];
306
        foreach ($tweets as $key => $tweet) {
307
            if (!Parser::match($tweet, $options)) {
308
                continue;
309
            }
310
311
            $countTweets = $collection->count(['id_str' => $tweet['id_str']]);
312
            if ($countTweets === 0) {
313
                $toSave[] = $tweet;
314
            }
315
        }
316
        return $toSave;
317
    }
318
}
319