Completed
Pull Request — master (#20)
by
unknown
01:26
created

Page::isMultiple()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace SocialLinks;
4
5
use Doctrine\Common\Cache\Cache;
6
use Doctrine\Common\Cache\PhpFileCache;
7
8
/**
9
 * @method html()
10
 * @method openGraph()
11
 * @method schema()
12
 * @method twitterCard()
13
 */
14
class Page
15
{
16
    private $cache;
17
    protected $config = array(
18
        'useCache' => TRUE,
19
        'cacheDuration' => 3600,
20
    );
21
    protected $providers = array();
22
    protected $metas = array();
23
    protected $info = array(
24
        'url' => null,
25
        'urls' => array(),
26
        'title' => null,
27
        'text' => null,
28
        'image' => null,
29
        'icon' => null,
30
        'twitterUser' => null,
31
    );
32
33
    /**
34
     * Constructor.
35
     *
36
     * @param array $info   The page info. Only url, title, text, image, icon and twitterUser fields are available
37
     * @param array $config Configuration options
38
     * @param Cache $cache Doctrine Cache instance, defaults to a new PhpFileCache
39
     */
40
    public function __construct(array $info, array $config = array(), Cache $cache = NULL)
41
    {
42
        $cache = $cache ?: new PhpFileCache(sys_get_temp_dir());
43
44
        if (array_diff_key($info, $this->info)) {
45
            throw new \Exception('Only the following fields are available:'.implode(',', array_keys($this->info)));
46
        }
47
48
        $this->info = array_map('static::normalize', $info + $this->info);
49
50
        $this->config = array_map('static::normalize', $config + $this->config);
51
        $this->cache = $cache;
52
    }
53
54
    /**
55
     * Normalize value before save it:
56
     * - remove html tags
57
     * - remove line-ending and multiple spaces
58
     * - remove spaces around
59
     * - decode escaped html entities.
60
     *
61
     * @param string
62
     *
63
     * @return string
64
     */
65
    protected static function normalize($value)
66
    {
67
        if (is_array($value)) {
68
            return array_map(function ($v) {
69
                return trim(strip_tags(htmlspecialchars_decode(preg_replace('/\s+/', ' ', $v))));
70
            }, $value);
71
        }
72
        else {
73
            return trim(strip_tags(htmlspecialchars_decode(preg_replace('/\s+/', ' ', $value))));
74
        }
75
    }
76
77
    /**
78
     * Magic method to check if a provider exists.
79
     *
80
     * @param string $key
81
     *
82
     * @return bool
83
     */
84
    public function __isset($key)
85
    {
86
        $key = strtolower($key);
87
88
        if (isset($this->providers[$key])) {
89
            return true;
90
        }
91
92
        $class = 'SocialLinks\\Providers\\'.ucfirst($key);
93
94
        return class_exists($class);
95
    }
96
97
    /**
98
     * Magic method to instantiate and return providers in lazy mode.
99
     *
100
     * @param string $key The provider name
101
     *
102
     * @throws \Exception if the provider does not exists
103
     *
104
     * @return Providers\ProviderInterface
105
     */
106 View Code Duplication
    public function __get($key)
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...
107
    {
108
        if ($key == 'cache') {
109
            return $this->cache;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->cache; (Doctrine\Common\Cache\Cache) is incompatible with the return type documented by SocialLinks\Page::__get of type SocialLinks\Providers\ProviderInterface.

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...
110
        }
111
112
        $key = strtolower($key);
113
114
        if (isset($this->providers[$key])) {
115
            return $this->providers[$key];
116
        }
117
118
        $class = 'SocialLinks\\Providers\\'.ucfirst($key);
119
120
        if (class_exists($class)) {
121
            return $this->providers[$key] = new $class($this);
122
        }
123
124
        throw new \Exception("The provider $key does not exists");
125
    }
126
127
    /**
128
     * Magic method to instantiate and return metas in lazy mode.
129
     *
130
     * @param string $key       The meta collection name
131
     * @param array  $arguments The arguments passed to the method
132
     *
133
     * @throws \Exception if the meta does not exists
134
     *
135
     * @return Metas\MetaInterface
136
     */
137 View Code Duplication
    public function __call($key, $arguments)
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...
138
    {
139
        $key = strtolower($key);
140
141
        if (isset($this->metas[$key])) {
142
            return $this->metas[$key];
143
        }
144
145
        $class = 'SocialLinks\\Metas\\'.ucfirst($key);
146
147
        if (class_exists($class)) {
148
            return $this->metas[$key] = new $class($this);
149
        }
150
151
        throw new \Exception("The meta $key does not exists");
152
    }
153
154
    /**
155
     * Preload the counter.
156
     *
157
     * @param array $providers Array of providers - defaults to all.
158
     */
159
    public function shareCount(array $providers)
160
    {
161
        $providers = $providers ?: array_keys($this->providers);
162
163
        $connections = array();
164
        $curl = curl_multi_init();
165
        $now = time();
166
167
        foreach ($providers as $provider) {
168
169
            // Check cache, if option is set.
170
            if ($this->getConfig('useCache')) {
171
                $id = $this->getId($provider);
172
                if ($cachedData = $this->cache->fetch($id)) {
173
                    $expired = empty($cachedData[1]) || (
174
                        $cachedData[1] + $this->getConfig('cacheDuration') < $now
175
                    );
176
177
                    // If not expired, set shareCount and return.
178
                    if (!$expired) {
179
                        $this->$provider->shareCount = $cachedData[0];
180
                        continue;
181
                    }
182
                }
183
            }
184
185
            if ($this->isMultiple()) {
186
                $request = $this->$provider->shareCountRequestMultiple();
187
            }
188
            else {
189
                $request = $this->$provider->shareCountRequest();
190
            }
191
192
            if ($request !== null) {
193
                $connections[$provider] = $request;
194
                curl_multi_add_handle($curl, $request);
195
            } else {
196
                $this->$provider->shareCount = null;
197
            }
198
        }
199
200
        do {
201
            $return = curl_multi_exec($curl, $active);
202
        } while ($return === CURLM_CALL_MULTI_PERFORM);
203
204
        while ($active && $return === CURLM_OK) {
205
            if (curl_multi_select($curl) === -1) {
206
                usleep(100);
207
            }
208
209
            do {
210
                $return = curl_multi_exec($curl, $active);
211
            } while ($return === CURLM_CALL_MULTI_PERFORM);
212
        }
213
214
        foreach ($connections as $provider => $request) {
215
            if ($this->isMultiple()) {
216
                $this->$provider->shareCount = $this->$provider->shareCountMultiple(curl_multi_getcontent($request));
217
            }
218
            else {
219
                $this->$provider->shareCount = $this->$provider->shareCount(curl_multi_getcontent($request));
220
            }
221
222
            curl_multi_remove_handle($curl, $request);
223
224
            // Cache count.
225
            $id = $this->getId($provider);
226
            $this->cache->save($id, array($this->$provider->shareCount, $now));
227
        }
228
229
        curl_multi_close($curl);
230
    }
231
232
    /**
233
     * Gets the total number of shares for a given URL across given providers.
234
     *
235
     * @param array $providers
236
     *
237
     * @throws \RuntimeException
238
     *
239
     * @return int
240
     */
241
    public function getShareCountTotal(array $providers = array())
242
    {
243
        $providers = $providers ?: array_keys($this->providers);
244
        $this->shareCount($providers);
245
246
        $shareCountTotal = 0;
247
        foreach ($providers as $provider) {
248
            if ($this->isMultiple()) {
249
                $shareCountTotal += array_sum($this->$provider->shareCount);
250
            }
251
            else {
252
                $shareCountTotal += $this->$provider->shareCount;
253
            }
254
        }
255
        return $shareCountTotal;
256
    }
257
258
    /**
259
     * Gets the page url.
260
     *
261
     * @return string|null
262
     */
263
    public function getUrl()
264
    {
265
        return $this->info['url'];
266
    }
267
268
    /**
269
     * Gets the page url.
270
     *
271
     * @return array|null
272
     */
273
    public function getUrls()
274
    {
275
        return $this->info['urls'];
276
    }
277
278
    /**
279
     * Gets the page title.
280
     *
281
     * @return string|null
282
     */
283
    public function getTitle()
284
    {
285
        return $this->info['title'];
286
    }
287
288
    /**
289
     * Gets the page text description.
290
     *
291
     * @return string|null
292
     */
293
    public function getText()
294
    {
295
        return $this->info['text'];
296
    }
297
298
    /**
299
     * Gets the page image.
300
     *
301
     * @return string|null
302
     */
303
    public function getImage()
304
    {
305
        return $this->info['image'];
306
    }
307
308
    /**
309
     * Gets the page icon.
310
     *
311
     * @return array|null
312
     */
313
    public function getIcon()
314
    {
315
        return $this->info['icon'];
316
    }
317
318
    /**
319
     * Gets the page twitterUser.
320
     *
321
     * @return string|null
322
     */
323
    public function getTwitterUser()
324
    {
325
        return $this->info['twitterUser'];
326
    }
327
328
    /**
329
     * Gets some page info.
330
     *
331
     * @param array|null Array with the page fields to return as $name => $rename. Set null to return all info
332
     *
333
     * @return array
334
     */
335
    public function get(array $info = null)
336
    {
337
        if ($info === null) {
338
            return $this->info;
339
        }
340
341
        $data = array();
342
343
        foreach ($info as $name => $rename) {
344
            if (is_int($name)) {
345
                $name = $rename;
346
            }
347
348
            if (!isset($this->info[$name])) {
349
                continue;
350
            }
351
352
            $data[$rename] = $this->info[$name];
353
        }
354
355
        return $data;
356
    }
357
358
    /**
359
     * Gets one or all configuration option.
360
     *
361
     * @param string $name
362
     * @param null   $default
363
     *
364
     * @return mixed
365
     */
366
    public function getConfig($name, $default = null)
367
    {
368
        return isset($this->config[$name]) ? $this->config[$name] : $default;
369
    }
370
371
    /**
372
     * Gets cache.
373
     *
374
     * @return object
375
     */
376
    public function getCache()
377
    {
378
        return $this->cache;
379
    }
380
381
    /**
382
     * Gets the ID for this provider and URL.
383
     *
384
     * @param string $provider
385
     *
386
     * @return string
387
     */
388
    public function getId($provider)
389
    {
390
        return sprintf('%s_%s', $provider, $this->info['url']);
391
    }
392
393
    /**
394
     * Checks if there are multiple URLs.
395
     *
396
     * @return bool
397
     */
398
    public function isMultiple()
399
    {
400
        return !empty($this->getUrls());
401
    }
402
}
403