InterceptorTrait::__call()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 8
cts 8
cp 1
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
nc 3
nop 2
crap 3
1
<?php
2
3
namespace mpyw\HardBotter\Traits;
4
5
use mpyw\Co\Co;
6
7
trait InterceptorTrait
8
{
9
    abstract public function getClient();
10
    abstract public function getGetErrorMode();
11
    abstract public function getPostErrorMode();
12
    abstract public function getMarkedStatusIds();
13
    abstract public function getBackLimitSeconds();
14
    abstract protected static function expired($past, $interval);
15
16
    /**
17
     * 間接的に mpyw\Cowitter\Client のメソッドをコールするメソッド
18
     */
19 43
    public function __call($method, array $args)
20
    {
21 43
        $client = $this->getClient();
22 43
        $callback = [$client, $method];
23 43
        if (!is_callable($callback)) {
24 1
            throw new \BadMethodCallException("Call to undefined method mpyw\Cowitter\Client::$method()");
25
        }
26 42
        return ((new \ReflectionMethod($client, $method))->isGenerator())
27 15
            ? $this->callAsync($method, $callback, $args)
28 42
            : $this->call($method, $callback, $args);
29
    }
30
31
    /**
32
     * 同期処理を __call() から呼ぶ場合
33
     */
34 27
    protected function call($method, $callback, $args)
35
    {
36
        try {
37 27
            return $this->filter($method, call_user_func_array($callback, $args));
38 10
        } catch (\RuntimeException $e) {
39 10
            return $this->handleException($method, $e);
40
        }
41
    }
42
43
    /**
44
     * 非同期処理を __call() から呼ぶ場合
45
     */
46 15
    protected function callAsync($method, $callback, $args)
47
    {
48
        try {
49 15
            yield Co::RETURN_WITH => $this->filter($method, (yield call_user_func_array($callback, $args)));
50 6
        } catch (\RuntimeException $e) {
51 6
            yield Co::RETURN_WITH => $this->handleException($method, $e);
52
        }
53
        // @codeCoverageIgnoreStart
54
    }
55
    // @codeCoverageIgnoreEnd
56
57
    /**
58
     * 結果をフィルタリングするメソッド
59
     */
60 32
    protected function filter($method, $variable)
61
    {
62
        // get, post, postMultipart 以外のメソッドは処理しない
63 32
        if (!preg_match('/^(?:get2?|post|postMultipart)(?:Async)?$/i', $method)) {
64 1
            return $variable;
65
        }
66
67
        // 一度連想配列に変換した後再帰的にフィルタリング処理をかけ,もとに戻す
68 31
        $variable = (array)json_decode(json_encode($variable), true);
69 31
        array_walk_recursive($variable, [get_class(), 'recursiveFilterCallback']);
70 31
        $variable = json_decode(json_encode($variable));
71
72
        // get 以外のメソッドはこれ以上処理しない
73 31
        if (!preg_match('/^get2?(?:Async)?$/i', $method)) {
74 11
            return $variable;
75
        }
76
77
        // 取得してきたものがステータス配列あるいはステータス配列を含むオブジェクトの場合,
78
        // その配列を処理対象にする
79 22
        if (is_array($variable)) {
80 12
            $list = &$variable;
81 10
        } elseif (isset($variable->statuses) && is_array($variable->statuses)) {
82 4
            $list = &$variable->statuses;
83
        } else {
84 6
            return $variable;
85
        }
86
87 16
        $marked = $this->getMarkedStatusIds();
88 16
        $limit = $this->getBackLimitSeconds();
89 16
        foreach ($list as $i => $status) {
90
            // GET users/lookup などは無視
91 16
            if (!isset($status->text, $status->id_str, $status->created_at)) {
92 1
                continue;
93
            }
94
            // マークされているツイートと期限切れのツイートを除外
95 15
            if (isset($marked[$status->id_str]) || static::expired($status->created_at, $limit)) {
96 15
                unset($list[$i]);
97
            }
98
        }
99
        // キーを振り直す
100 16
        $list = array_values($list);
101
102 16
        return $variable;
103
    }
104
105
    /**
106
     * 例外ハンドラ
107
     */
108 16
    private function handleException($method, \RuntimeException $e)
109
    {
110 16
        $get_error_mode = $this->getGetErrorMode();
111 16
        $post_error_mode = $this->getPostErrorMode();
112
113 16
        if (preg_match('/^get2?(?:Out)?(?:Async)?$/i', $method)) {
114
            // getのとき
115 12
            if ($get_error_mode & self::ERRMODE_WARNING) {
116 4
                trigger_error($e->getMessage(), E_USER_WARNING);
117
            }
118 12
            if ($get_error_mode & self::ERRMODE_EXCEPTION) {
119 4
                throw $e;
120
            }
121 8
            return false;
122
        }
123
124 4
        if (preg_match('/^(?:(?:post|postMultipart)(?:Out)?|(?:upload(?:|Image|AnimeGIF|Video)))(?:Async)?$/i', $method)) {
125
            // postのとき
126 3
            if ($post_error_mode & self::ERRMODE_WARNING) {
127 1
                trigger_error($e->getMessage(), E_USER_WARNING);
128
            }
129 3
            if ($post_error_mode & self::ERRMODE_EXCEPTION) {
130 1
                throw $e;
131
            }
132 2
            return false;
133
        }
134
135
        // それ以外
136 1
        throw $e;
137
    }
138
139
    /**
140
     * array_walk_recursive 専用
141
     */
142 31
    protected static function recursiveFilterCallback(&$value, $key)
143
    {
144 31
        if ($key === 'text') {
145
            // ツイート本文のHTML特殊文字をデコードする
146 24
            $value = htmlspecialchars_decode($value, ENT_NOQUOTES);
147 31
        } elseif ($key === 'name' || $key === 'description') {
148
            // プロフィールの名前および詳細に含まれるスクリーンネームの「@」を「(at)」に置換する
149 17
            $value = preg_replace('/@(?=\w{1,15}+(?!\w))/', '(at)', $value);
150
        }
151 31
    }
152
}
153