Passed
Push — master ( b2168e...f1daef )
by Paweł
03:26
created

components/updaters/AccountUpdater.php (6 issues)

1
<?php
2
/**
3
 * Created for IG Monitoring.
4
 * User: jakim <[email protected]>
5
 * Date: 12.06.2018
6
 */
7
8
namespace app\components\updaters;
9
10
11
use app\components\instagram\models\Account;
12
use app\components\traits\NextUpdateCalculator;
13
use app\components\traits\SaveModelTrait;
14
use app\components\traits\SetAccount;
15
use app\models\AccountStats;
16
use DateTime;
17
use Throwable;
18
use yii\base\Component;
19
use yii\db\Expression;
20
use yii\helpers\ArrayHelper;
21
use function count;
22
23
class AccountUpdater extends Component
24
{
25
    use SaveModelTrait, SetAccount, NextUpdateCalculator;
0 ignored issues
show
The trait app\components\traits\SaveModelTrait requires the property $errors which is not provided by app\components\updaters\AccountUpdater.
Loading history...
26
27
    private $postStats;
28
29
    public function init()
30
    {
31
        parent::init();
32
        $this->throwExceptionIfAccountIsNotSet();
33
    }
34
35
    /**
36
     * Update username and instagram_id.
37
     *
38
     * @param \app\components\instagram\models\Account $account
39
     * @return $this
40
     */
41
    public function setIdents(Account $account)
42
    {
43
        $this->account->username = $account->username;
44
        $this->account->instagram_id = (string)$account->id;
45
46
        return $this;
47
    }
48
49
    /**
50
     * @param \app\components\instagram\models\Account $account
51
     * @return $this
52
     */
53
    public function setDetails(Account $account)
54
    {
55
        $this->setIdents($account);
56
        $this->account->profile_pic_url = $account->profilePicUrl;
57
        $this->account->full_name = $account->fullName;
58
        $this->account->biography = $account->biography;
59
        $this->account->external_url = $account->externalUrl;
60
        $this->account->is_verified = $account->isVerified;
61
        $this->account->is_business = $account->isBusiness;
62
        $this->account->business_category = $account->businessCategory;
63
64
        return $this;
65
    }
66
67
    public function setMonitoring($proxyId = null)
68
    {
69
        $this->account->monitoring = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $monitoring was declared of type boolean, but 1 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
70
        $this->account->proxy_id = $proxyId;
71
72
        return $this;
73
    }
74
75
    public function setIsValid()
76
    {
77
        $this->account->is_valid = 1;
0 ignored issues
show
Documentation Bug introduced by
The property $is_valid was declared of type boolean, but 1 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
78
        $this->account->invalidation_count = 0;
79
        $this->account->invalidation_type_id = null;
80
81
        return $this;
82
    }
83
84
    public function setIsInvalid(?int $invalidationType = null)
85
    {
86
        $this->account->is_valid = 0;
0 ignored issues
show
Documentation Bug introduced by
The property $is_valid was declared of type boolean, but 0 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
87
        $this->account->invalidation_count = (int)$this->account->invalidation_count + 1;
88
        $this->account->invalidation_type_id = $invalidationType;
89
90
        return $this;
91
    }
92
93
    /**
94
     * If true, then will be automatically calculate from invalidation_count
95
     *
96
     * @param true|int|null $interval
97
     * @return $this
98
     */
99
    public function setNextStatsUpdate($interval = 24)
100
    {
101
        $this->account->update_stats_after = $this->getNextUpdateDate($this->account, $interval);
0 ignored issues
show
It seems like $interval can also be of type true; however, parameter $interval of app\components\updaters\...er::getNextUpdateDate() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

101
        $this->account->update_stats_after = $this->getNextUpdateDate($this->account, /** @scrutinizer ignore-type */ $interval);
Loading history...
102
103
        return $this;
104
    }
105
106
    /**
107
     * Direct account statistics.
108
     *
109
     * @param \app\components\instagram\models\Account $account
110
     * @param array|\app\components\instagram\models\Post[] $posts
111
     * @param bool $createHistory
112
     * @return $this
113
     */
114
    public function setStats(Account $account, array $posts, bool $createHistory = true)
115
    {
116
        $this->account->touch('stats_updated_at');
117
        if ($this->account->media === null || $this->statsNeedUpdate($account, $posts)) {
118
            $postsStats = $this->postsStats($account, $posts);
119
            $this->account->media = $account->media;
120
            $this->account->follows = $account->follows;
121
            $this->account->followed_by = $account->followedBy;
122
            $this->account->er = $postsStats['er'];
123
            $this->account->avg_likes = $postsStats['avg_likes'];
124
            $this->account->avg_comments = $postsStats['avg_comments'];
125
126
            if ($lastPost = ArrayHelper::getValue($posts, '0')) {
127
                try {
128
                    $takenAt = (new DateTime('@' . ArrayHelper::getValue($lastPost, 'takenAt')))->format('Y-m-d H:i:s');
129
                    $this->account->last_post_taken_at = $takenAt;
130
                } catch (Throwable $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
131
                }
132
133
            }
134
135
            if ($createHistory) {
136
                $this->createHistory();
137
            }
138
        }
139
140
        return $this;
141
    }
142
143
    /**
144
     * @throws \yii\web\ServerErrorHttpException
145
     */
146
    public function save()
147
    {
148
        $this->saveModel($this->account);
149
    }
150
151
    protected function createHistory()
152
    {
153
        // dirty fix - avoiding duplicates
154
        // the possibility of occurrence if the account is monitored and appears as a tagged/mentioned in the post
155
        AccountStats::deleteAll(['AND',
156
            ['account_id' => $this->account->id],
157
            new Expression('DATE(created_at)=DATE(NOW())'),
158
        ]);
159
        $accountStats = new AccountStats([
160
            'account_id' => $this->account->id,
161
            'media' => $this->account->media,
162
            'follows' => $this->account->follows,
163
            'followed_by' => $this->account->followed_by,
164
            'er' => $this->account->er,
165
            'avg_likes' => $this->account->avg_likes,
166
            'avg_comments' => $this->account->avg_comments,
167
        ]);
168
        $this->saveModel($accountStats);
169
170
        return $accountStats;
171
    }
172
173
    private function statsNeedUpdate(Account $account, array $posts): bool
174
    {
175
        $res = $this->account->media != $account->media ||
176
            $this->account->follows != $account->follows ||
177
            $this->account->followed_by != $account->followedBy;
178
        if ($res === false) {
179
            $postStats = $this->postsStats($account, $posts);
180
            $res = $this->account->er != $postStats['er'] ||
181
                $this->account->avg_likes != $postStats['avg_likes'] ||
182
                $this->account->avg_comments != $postStats['avg_comments'];
183
        }
184
185
        return $res;
186
    }
187
188
    /**
189
     * Statistics calculated from posts (engagement, avg_likes, avg_comments).
190
     *
191
     * @param \app\components\instagram\models\Account $account
192
     * @param array|\app\components\instagram\models\Post[] $posts
193
     * @return array
194
     */
195
    private function postsStats(Account $account, array $posts): array
196
    {
197
        if (!empty($this->postStats)) {
198
            return $this->postStats;
199
        }
200
201
        $er = [];
202
        if ($account->followedBy) {
203
            foreach ($posts as $post) {
204
                $er[] = ($post->likes + $post->comments) / $account->followedBy;
205
            }
206
        }
207
        $er = $er ? array_sum($er) / count($er) : 0;
208
209
        $avgLikes = [];
210
        $avgComments = [];
211
        foreach ($posts as $post) {
212
            $avgLikes[] = $post->likes;
213
            $avgComments[] = $post->comments;
214
        }
215
        $avgLikes = $avgLikes ? array_sum($avgLikes) / count($avgLikes) : 0;
216
        $avgComments = $avgComments ? array_sum($avgComments) / count($avgComments) : 0;
217
218
        return $this->postStats = [
219
            'er' => $er,
220
            'avg_likes' => $avgLikes,
221
            'avg_comments' => $avgComments,
222
        ];
223
    }
224
}