Completed
Pull Request — master (#9)
by
unknown
08:34
created

MyTwitter::TwitterFeed()   D

Complexity

Conditions 18
Paths 26

Size

Total Lines 81

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
nc 26
nop 2
dl 0
loc 81
rs 4.8666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace SunnysideUp\ShareThis;
4
5
use SilverStripe\Core\Injector\Injectable;
6
use SilverStripe\Control\Session;
7
use SilverStripe\Core\Config\Config;
8
use SunnysideUp\ShareThis\MyTwitter;
9
use SunnysideUp\ShareThis\MyTwitterData;
10
use SilverStripe\Control\Director;
11
use SunnysideUp\ShareThis\TwitterOAuth;
12
use SilverStripe\ORM\ArrayList;
13
use SilverStripe\ORM\FieldType\DBDatetime;
14
use SilverStripe\View\ArrayData;
15
16
/**
17
 * @author romain [at] sunnys side up .co.nz + nicolaas [at] sunny side up . co .nz
18
 * @inspiration: https://github.com/tylerkidd/silverstripe-twitter-feed/
19
 * @funding: MSO Design (www.msodesign.com)
20
 *
21
 **/
22
class MyTwitter
23
{
24
    use Injectable;
25
26
    /**
27
     * @var boolean
28
     */
29
    private static $debug = false;
30
31
    /**
32
     * @var array
33
     */
34
    private static $singletons = [];
35
36
    /**
37
     * @var boolean
38
     */
39
    private static $favourites_only = false;
40
41
    /**
42
     * @var boolean
43
     */
44
    private static $non_replies_only = false;
45
46
    /**
47
     * @var string
48
     */
49
    private static $twitter_consumer_key = "";
50
51
    /**
52
     * @var string
53
     */
54
    private static $twitter_consumer_secret = "";
55
56
    /**
57
     * @var string
58
     */
59
    private static $titter_oauth_token = "";
60
61
    /**
62
     * @var string
63
     */
64
    private static $titter_oauth_token_secret = "";
65
66
    /**
67
     * @var array
68
     */
69
    private static $twitter_config = [
70
        'include_entities' => 'true',
71
        'include_rts' => 'true'
72
    ];
73
74
    /**
75
     * returns a DataObjetSet of the last $count tweets.
76
     * - saves twitter feed to dataobject
77
     *
78
     * @param String $username (e.g. mytwitterhandle)
79
     * @param Int $count - number of tweets to retrieve at any one time
80
     * @return DataObjectSet | Null
0 ignored issues
show
Documentation introduced by
Should the return type not be \SilverStripe\ORM\DataList?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
81
     */
82
    public static function last_statuses($username, $count = 1, $useHourlyCache = true)
83
    {
84
        if (!$username) {
85
            user_error("No username provided");
86
        }
87
88
        $sessionName = "MyTwitterFeeds$username".date("Ymdh");
89
90
        if (Session::get($sessionName) && $useHourlyCache && !Config::inst()->get(MyTwitter::class, "debug")) {
91
            //do nothing
92
        } else {
93
            if (empty(self::$singletons[$username])) {
94
                self::$singletons[$username] = new MyTwitter($username, $count);
0 ignored issues
show
Unused Code introduced by
The call to MyTwitter::__construct() has too many arguments starting with $username.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
95
            }
96
97
            $dataObjectSet = self::$singletons[$username]->TwitterFeed($username, $count);
98
99
            if ($dataObjectSet && $dataObjectSet->count()) {
100
                foreach ($dataObjectSet as $tweet) {
101
                    if (!MyTwitterData::get()->filter(["TwitterID" => $tweet->ID])->count()) {
102
                        $myTwitterData = new MyTwitterData();
103
                        $myTwitterData->TwitterID = $tweet->ID;
0 ignored issues
show
Documentation introduced by
The property TwitterID does not exist on object<SunnysideUp\ShareThis\MyTwitterData>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
104
                        $myTwitterData->Title = $tweet->Title;
105
                        $myTwitterData->Date = $tweet->Date;
0 ignored issues
show
Documentation introduced by
The property Date does not exist on object<SunnysideUp\ShareThis\MyTwitterData>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
106
                        $myTwitterData->write();
107
                    }
108
                }
109
            }
110
111
            Session::set($sessionName, 1);
112
        }
113
114
        Config::inst()->update(MyTwitterData::class, "username", $username);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SilverStripe\Config\Coll...nfigCollectionInterface as the method update() does only exist in the following implementations of said interface: SilverStripe\Config\Coll...s\DeltaConfigCollection, SilverStripe\Config\Coll...\MemoryConfigCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
115
        return MyTwitterData::get()->filter(["Hide" => 0])->limit($count);
116
    }
117
118
119
    /**
120
     * retries latest tweets from Twitter
121
     *
122
     * @param String $username (e.g. mytwitterhandle)
123
     * @param Int $count - number of tweets to retrieve at any one time
124
     * @return DataObjectSet | Null
0 ignored issues
show
Documentation introduced by
Should the return type not be null|ArrayList?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
125
     */
126
    public function TwitterFeed($username, $count = 5)
127
    {
128
        if (!$username) {
129
            user_error("No username provided");
130
        }
131
132
        Config::inst()->update(MyTwitterData::class, "username", $username);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SilverStripe\Config\Coll...nfigCollectionInterface as the method update() does only exist in the following implementations of said interface: SilverStripe\Config\Coll...s\DeltaConfigCollection, SilverStripe\Config\Coll...\MemoryConfigCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
133
134
        //check settings are available
135
        $requiredSettings = [
136
            "twitter_consumer_key",
137
            "twitter_consumer_secret",
138
            "titter_oauth_token",
139
            "titter_oauth_token"
140
        ];
141
142
        foreach ($requiredSettings as $setting) {
143
            if (!Config::inst()->get(MyTwitter::class, $setting)) {
144
                user_error(" you must set MyTwitter::$setting", E_USER_NOTICE);
145
                return null;
146
            }
147
        }
148
149
        require_once(Director::baseFolder().'/'.SS_SHARETHIS_DIR.'/third_party/twitter_oauth/TwitterOAuthConsumer.php');
150
151
        $connection = new TwitterOAuth(
152
            Config::inst()->get(MyTwitter::class, "twitter_consumer_key"),
153
            Config::inst()->get(MyTwitter::class, "twitter_consumer_secret"),
154
            Config::inst()->get(MyTwitter::class, "titter_oauth_token"),
155
            Config::inst()->get(MyTwitter::class, "titter_oauth_token_secret")
156
        );
157
158
        $config = Config::inst()->get(MyTwitter::class, "twitter_config");
159
        $config['screen_name'] = $username;
160
        $tweets = $connection->get('statuses/user_timeline', $config);
161
        $tweetList = new ArrayList();
162
163
        if (count($tweets) > 0 && !isset($tweets->error)) {
164
            $i = 0;
165
166
            foreach ($tweets as $tweet) {
167
                if (Config::inst()->get(MyTwitter::class, "favourites_only") && $tweet->favorite_count == 0) {
168
                    break;
169
                }
170
171
                if (Config::inst()->get(MyTwitter::class, "non_replies_only") && $tweet->in_reply_to_status_id) {
172
                    break;
173
                }
174
175
                if (Config::inst()->get(MyTwitter::class, "debug")) {
176
                    print_r($tweet);
177
                }
178
179
                if (++$i > $count) {
180
                    break;
181
                }
182
183
                $date = new DBDatetime();
184
                $date->setValue(strtotime($tweet->created_at));
185
                $text = htmlentities($tweet->text, ENT_NOQUOTES, $encoding = "UTF-8", $doubleEncode = false);
186
187
                if (!empty($tweet->entities) && !empty($tweet->entities->urls)) {
188
                    foreach ($tweet->entities->urls as $url) {
189
                        if (!empty($url->url) && !empty($url->display_url)) {
190
                            $text = str_replace($url->url, '<a href="'.$url->url.'" class="external">'.$url->display_url.'</a>', $text);
191
                        }
192
                    }
193
                }
194
195
                $tweetList->push(
196
                    new ArrayData([
197
                        'ID' => $tweet->id_str,
198
                        'Title' => $text,
199
                        'Date' => $date
200
                    ])
201
                );
202
            }
203
        }
204
205
        return $tweetList;
206
    }
207
}
208