Completed
Pull Request — master (#17)
by lan tian
02:37
created

Sms   D

Complexity

Total Complexity 107

Size/Duplication

Total Lines 648
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Test Coverage

Coverage 82.98%

Importance

Changes 28
Bugs 5 Features 6
Metric Value
wmc 107
c 28
b 5
f 6
lcom 2
cbo 3
dl 0
loc 648
ccs 195
cts 235
cp 0.8298
rs 4.6752

32 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
B make() 0 15 6
A voice() 0 7 1
B queue() 0 16 5
A to() 0 6 1
A content() 0 6 1
B template() 0 15 7
A data() 0 6 1
A agent() 0 6 1
B send() 0 29 4
A push() 0 15 3
A getData() 0 4 1
A bootstrap() 0 8 2
A generatorTask() 0 8 2
A configuration() 0 7 1
A generatorAgentsName() 0 8 4
B generatorAgentsConfig() 0 13 6
A configValidator() 0 6 2
C createDrivers() 0 34 7
A parseAgentArrayOptions() 0 10 1
B pullAgentOptionByName() 0 10 5
A getAgentConfigData() 0 5 2
B getSmsAgent() 0 20 8
A validator() 0 8 2
B enable() 0 14 12
B agents() 0 13 6
A getEnableAgents() 0 4 1
A getAgentsConfig() 0 4 1
A cleanEnableAgents() 0 4 1
A cleanAgentsConfig() 0 4 1
B __callStatic() 0 19 8
A __call() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like Sms often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Sms, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Toplan\PhpSms;
4
5
use Toplan\TaskBalance\Balancer;
6
7
/**
8
 * Class Sms
9
 */
10
class Sms
11
{
12
    /**
13
     * sms send task name
14
     */
15
    const TASK = 'PhpSms';
16
17
    /**
18
     * agents instance
19
     */
20
    protected static $agents;
21
22
    /**
23
     * agents`s name
24
     *
25
     * @var
26
     */
27
    protected static $agentsName = [];
28
29
    /**
30
     * agents`s config
31
     *
32
     * @var
33
     */
34
    protected static $agentsConfig = [];
35
36
    /**
37
     * whether to enable queue
38
     *
39
     * @var bool
40
     */
41
    protected static $enableQueue = false;
42
43
    /**
44
     * queue work
45
     *
46
     * @var null
47
     */
48
    protected static $howToUseQueue = null;
49
50
    /**
51
     * sms already pushed to queue
52
     *
53
     * @var bool
54
     */
55
    protected $pushedToQueue = false;
56
57
    /**
58
     * hook handlers
59
     *
60
     * @var array
61
     */
62
    protected static $enableHooks = [
63
        'beforeRun',
64
        'beforeDriverRun',
65
        'afterDriverRun',
66
        'afterRun',
67
    ];
68
69
    /**
70
     * sms data
71
     *
72
     * @var array
73
     */
74
    protected $smsData = [
75
        'to'           => null,
76
        'templates'    => [],
77
        'content'      => '',
78
        'templateData' => [],
79
        'voiceCode'    => null,
80
    ];
81
82
    /**
83
     * first agent for send sms/voice verify
84
     *
85
     * @var string
86
     */
87
    protected $firstAgent = null;
88
89
    /**
90
     * construct
91
     *
92 9
     * @param bool $autoBoot
93
     */
94 9
    public function __construct($autoBoot = true)
95 9
    {
96
        if ($autoBoot) {
97
            self::bootstrap();
98
        }
99
    }
100
101
    /**
102
     * create sms instance and set templates
103
     *
104
     * @param null $agentName
105 3
     * @param null $tempId
106
     *
107 3
     * @return Sms
108 3
     */
109
    public static function make($agentName = null, $tempId = null)
110 3
    {
111
        $sms = new self();
112
        if (is_array($agentName)) {
113
            $sms->template($agentName);
114
        } elseif ($agentName && is_string($agentName)) {
115
            if ($tempId === null) {
116
                $sms->content($agentName);
117
            } elseif (is_string("$tempId")) {
118 3
                $sms->template($agentName, $tempId);
119
            }
120
        }
121
122
        return $sms;
123
    }
124
125
    /**
126
     * send voice verify
127
     *
128 3
     * @param $code
129
     *
130 3
     * @return Sms
131 3
     */
132
    public static function voice($code)
133 3
    {
134
        $sms = new self();
135
        $sms->smsData['voiceCode'] = $code;
136
137
        return $sms;
138
    }
139
140
    /**
141
     * set how to use queue.
142
     *
143
     * @param $enable
144 3
     * @param $handler
145
     *
146 3
     * @return bool
147 3
     */
148
    public static function queue($enable = null, $handler = null)
149 3
    {
150 3
        if ($enable === null && $handler === null) {
151 3
            return self::$enableQueue;
152 2
        }
153 3
        if (is_callable($enable)) {
154 3
            $handler = $enable;
155 3
            $enable = true;
156 2
        }
157
        self::$enableQueue = (bool) $enable;
158 3
        if (is_callable($handler)) {
159
            self::$howToUseQueue = $handler;
0 ignored issues
show
Documentation Bug introduced by
It seems like $handler of type callable is incompatible with the declared type null of property $howToUseQueue.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
160
        }
161
162
        return self::$enableQueue;
163
    }
164
165
    /**
166
     * set the mobile number
167
     *
168 6
     * @param $mobile
169
     *
170 6
     * @return $this
171
     */
172 6
    public function to($mobile)
173
    {
174
        $this->smsData['to'] = $mobile;
175
176
        return $this;
177
    }
178
179
    /**
180
     * set content for content sms
181
     *
182 3
     * @param $content
183
     *
184 3
     * @return $this
185
     */
186 3
    public function content($content)
187
    {
188
        $this->smsData['content'] = trim((String) $content);
189
190
        return $this;
191
    }
192
193
    /**
194
     * set template id for template sms
195
     *
196
     * @param $agentName
197 3
     * @param $tempId
198
     *
199 3
     * @return $this
200 3
     */
201 3
    public function template($agentName, $tempId = null)
202 2
    {
203 3
        if (is_array($agentName)) {
204 3
            foreach ($agentName as $k => $v) {
205
                $this->template($k, $v);
206
            }
207 3
        } elseif ($agentName && $tempId) {
208 2
            if (!isset($this->smsData['templates']) || !is_array($this->smsData['templates'])) {
209
                $this->smsData['templates'] = [];
210 3
            }
211
            $this->smsData['templates']["$agentName"] = $tempId;
212
        }
213
214
        return $this;
215
    }
216
217
    /**
218
     * set data for template sms
219
     *
220 11
     * @param array $data
221
     *
222 3
     * @return $this
223
     */
224 3
    public function data(array $data)
225 10
    {
226
        $this->smsData['templateData'] = $data;
227
228
        return $this;
229
    }
230
231
    /**
232
     * set the first agent
233
     *
234 3
     * @param $name
235
     *
236 3
     * @return $this
237
     */
238 3
    public function agent($name)
239
    {
240
        $this->firstAgent = (String) $name;
241
242
        return $this;
243
    }
244
245
    /**
246
     * start send
247
     *
248 15
     * @param bool $immediately
249
     *
250 15
     * @return mixed
251
     */
252
    public function send($immediately = false)
253
    {
254 15
        $this->validator();
255 12
256 8
        // if disable push to queue,
257
        // send the sms immediately.
258
        if (!self::$enableQueue) {
259
            $immediately = true;
260
        }
261
262
        // whatever 'PhpSms' whether to enable or disable push to queue,
263 15
        // if you are already pushed sms instance to queue,
264 3
        // you can recall the method `send()` in queue job without `true` parameter.
265 2
        //
266
        // So this mechanism in order to make you convenient use the method `send()` in queue system.
267
        if ($this->pushedToQueue) {
268
            $immediately = true;
269 15
        }
270 15
271 10
        // whether to send sms immediately,
272 3
        // or push it to queue.
273
        if ($immediately) {
274
            $result = Balancer::run(self::TASK, $this->getData(), $this->firstAgent);
0 ignored issues
show
Documentation introduced by
$this->getData() is of type array, but the function expects a string|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
275 15
        } else {
276
            $result = $this->push();
277
        }
278
279
        return $result;
280
    }
281
282
    /**
283
     * push sms send task to queue
284
     *
285 3
     * @throws \Exception | PhpSmsException
286
     *
287 3
     * @return mixed
288
     */
289 3
    protected function push()
290
    {
291 3
        if (is_callable(self::$howToUseQueue)) {
292
            try {
293
                $this->pushedToQueue = true;
294
295
                return call_user_func_array(self::$howToUseQueue, [$this, $this->smsData]);
296
            } catch (\Exception $e) {
297
                $this->pushedToQueue = false;
298
                throw $e;
299
            }
300
        } else {
301
            throw new PhpSmsException('Please define how to use queue by method `queue($enable, $handler)`');
302
        }
303
    }
304
305
    /**
306 33
     * get sms data
307
     *
308 33
     * @return array
309
     */
310
    public function getData()
311
    {
312
        return $this->smsData;
313
    }
314 9
315
    /**
316 9
     * bootstrap
317 9
     */
318 9
    public static function bootstrap()
319 9
    {
320
        self::configuration();
321
        $task = self::generatorTask();
322
        if (!count($task->drivers)) {
323
            self::createDrivers($task);
324
        }
325
    }
326 18
327
    /**
328 18
     * generator a sms send task
329 3
     *
330 2
     * @return object
331
     */
332 18
    public static function generatorTask()
333
    {
334
        if (!Balancer::hasTask(self::TASK)) {
335
            Balancer::task(self::TASK, null);
336
        }
337
338 9
        return Balancer::getTask(self::TASK);
339
    }
340 9
341 9
    /**
342 9
     * configuration
343 9
     */
344 9
    protected static function configuration()
345
    {
346
        $config = [];
347
        self::generatorAgentsName($config);
348
        self::generatorAgentsConfig($config);
349
        self::configValidator();
350
    }
351 9
352
    /**
353 9
     * generate enabled agents name
354 3
     *
355 3
     * @param array $config
356 3
     */
357 2
    protected static function generatorAgentsName(&$config)
358 9
    {
359
        if (empty(self::$agentsName)) {
360
            $config = $config ?: include __DIR__ . '/../config/phpsms.php';
361
            $enableAgents = isset($config['enable']) ? $config['enable'] : null;
362
            self::enable($enableAgents);
363
        }
364
    }
365 9
366
    /**
367 9
     * generator agents config
368 9
     *
369 9
     * @param array $config
370 3
     */
371 3
    protected static function generatorAgentsConfig(&$config)
372 3
    {
373 3
        $diff = array_diff_key(self::$agentsName, self::$agentsConfig);
374 3
        $diff = array_keys($diff);
375 2
        if (count($diff)) {
376 2
            $config = $config ?: include __DIR__ . '/../config/phpsms.php';
377 9
            $agentsConfig = isset($config['agents']) ? $config['agents'] : [];
378
            foreach ($diff as $name) {
379
                $agentConfig = isset($agentsConfig[$name]) ? $agentsConfig[$name] : [];
380
                self::agents($name, $agentConfig);
381
            }
382
        }
383
    }
384 9
385
    /**
386 9
     * config value validator
387
     *
388
     * @throws PhpSmsException
389 9
     */
390
    protected static function configValidator()
391
    {
392
        if (!count(self::$agentsName)) {
393
            throw new PhpSmsException('Please set at least one enable agent in config file(config/phpsms.php) or use method enable()');
394
        }
395
    }
396 15
397
    /**
398 9
     * create drivers for sms send task
399
     *
400 9
     * @param $task
401
     */
402 9
    protected static function createDrivers($task)
403
    {
404
        foreach (self::$agentsName as $name => $options) {
405
            //获取代理器配置
406
            $configData = self::getAgentConfigData($name);
407
            //解析代理器数组模式的调度配置
408 9
            if (is_array($options)) {
409 15
                $data = self::parseAgentArrayOptions($options);
410 15
                $configData = array_merge($configData, $data);
411 15
                $options = $data['driverOpts'];
412 15
            }
413 15
            //创建任务驱动器
414 15
            $task->driver("$name $options")->data($configData)
415
                 ->work(function ($driver) {
416
                     $configData = $driver->getDriverData();
417 15
                     $agent = self::getSmsAgent($driver->name, $configData);
418 15
                     $smsData = $driver->getTaskData();
419
                     extract($smsData);
420 15
                     if (isset($smsData['voiceCode']) && $smsData['voiceCode']) {
421 15
                         $agent->voiceVerify($to, $voiceCode);
422 15
                     } else {
423 10
                         $template = isset($templates[$driver->name]) ? $templates[$driver->name] : 0;
424 15
                         $agent->sendSms($template, $to, $templateData, $content);
425
                     }
426 15
                     $result = $agent->getResult();
427 9
                     if ($result['success']) {
428 6
                         $driver->success();
429 9
                     }
430
                     unset($result['success']);
431
432
                     return $result;
433
                 });
434
        }
435
    }
436
437
    /**
438
     * 解析可用代理器的数组模式的调度配置
439
     *
440
     * @param array $options
441
     *
442
     * @return array
443
     */
444
    protected static function parseAgentArrayOptions(array $options)
445
    {
446
        $agentClass = self::pullAgentOptionByName($options, 'agentClass');
447
        $sendSms = self::pullAgentOptionByName($options, 'sendSms');
448
        $voiceVerify = self::pullAgentOptionByName($options, 'voiceVerify');
449
        $backup = self::pullAgentOptionByName($options, 'backup');
450
        $driverOpts = implode(' ', array_values($options)) . " $backup";
451
452
        return compact('agentClass', 'sendSms', 'voiceVerify', 'driverOpts');
453
    }
454
455
    /**
456
     * 从调度配置中拉取指定数据
457
     *
458
     * @param array  $options
459
     * @param string $name
460
     *
461
     * @return null|string
462
     */
463
    protected static function pullAgentOptionByName(array &$options, $name)
464
    {
465
        $value = isset($options[$name]) ? $options[$name] : null;
466
        if ($name === 'backup') {
467
            $value = isset($options[$name]) ? ($options[$name] ? 'backup' : '') : '';
468
        }
469
        unset($options[$name]);
470
471
        return $value;
472
    }
473
474
    /**
475 9
     * get agent config data by name
476
     *
477 9
     * @param $name
478 9
     *
479
     * @return array
480
     */
481
    protected static function getAgentConfigData($name)
482
    {
483
        return isset(self::$agentsConfig[$name]) ?
484
               (Array) self::$agentsConfig[$name] : [];
485
    }
486
487
    /**
488
     * get a sms agent instance,
489
     * if null, will create a new agent instance
490
     *
491
     * @param       $name
492 18
     * @param array $configData
493
     *
494 18
     * @throws PhpSmsException
495 3
     *
496 3
     * @return mixed
497 3
     */
498 3
    public static function getSmsAgent($name, array $configData)
499
    {
500
        if (!isset(self::$agents[$name])) {
501 3
            $configData['name'] = $name;
502
            $className = isset($configData['agentClass']) ? $configData['agentClass'] : ('Toplan\\PhpSms\\' . $name . 'Agent');
503 3
            if ((isset($configData['sendSms']) && is_callable($configData['sendSms'])) ||
504 2
                (isset($configData['voiceVerify']) && is_callable($configData['voiceVerify']))) {
505
                //将临时代理器寄生到LogAgent
506
                self::$agents[$name] = new LogAgent($configData);
507
            } elseif (class_exists($className)) {
508 2
                //创建新代理器
509
                self::$agents[$name] = new $className($configData);
510 18
            } else {
511
                //无代理器可用
512
                throw new PhpSmsException("Agent [$name] not support. If you are want to use parasitic agent, please set callable arguments: [sendSms] and [voiceVerify]");
513
            }
514
        }
515
516
        return self::$agents[$name];
517
    }
518 18
519
    /**
520 18
     * validate
521
     *
522
     * @throws PhpSmsException
523
     */
524 18
    protected function validator()
525
    {
526
        if (!$this->smsData['to']) {
527
            throw new PhpSmsException('Please set send sms(or voice verify) to who use `to()` method.');
528
        }
529
530
        return true;
531
    }
532
533 6
    /**
534
     * set enable agents
535 6
     *
536 6
     * @param      $agentName
537 6
     * @param null $options
538 4
     */
539 6
    public static function enable($agentName, $options = null)
540 3
    {
541 6
        if (is_array($agentName)) {
542 3
            foreach ($agentName as $name => $opt) {
543 5
                self::enable($name, $opt);
544 3
            }
545 2
        } elseif ($agentName && is_string($agentName) && $options !== null) {
546 6
            self::$agentsName["$agentName"] = is_array($options) ? $options : "$options";
547
        } elseif (is_int($agentName) && !is_array($options) && "$options") {
548
            self::$agentsName["$options"] = '1';
549
        } elseif ($agentName && $options === null) {
550
            self::$agentsName["$agentName"] = '1';
551
        }
552
    }
553
554
    /**
555
     * set config for available agents
556 6
     *
557
     * @param       $agentName
558 6
     * @param array $config
559 3
     *
560 3
     * @throws PhpSmsException
561 2
     */
562 6
    public static function agents($agentName, array $config = [])
563 6
    {
564
        if (is_array($agentName)) {
565
            foreach ($agentName as $name => $conf) {
566 6
                self::agents($name, $conf);
567 4
            }
568 6
        } elseif ($agentName && is_array($config)) {
569
            if (preg_match('/^[0-9]+$/', $agentName)) {
570
                throw new PhpSmsException("Agent name [$agentName] must be string, could not be a pure digital");
571
            }
572
            self::$agentsConfig["$agentName"] = $config;
573
        }
574
    }
575 9
576
    /**
577 9
     * get enable agents
578
     *
579
     * @return array
580
     */
581
    public static function getEnableAgents()
582
    {
583
        return self::$agentsName;
584
    }
585 9
586
    /**
587 9
     * get agents config info
588
     *
589
     * @return array
590
     */
591
    public static function getAgentsConfig()
592
    {
593 3
        return self::$agentsConfig;
594
    }
595 3
596 3
    /**
597
     * tear down enable agents
598
     */
599
    public static function cleanEnableAgents()
600
    {
601 3
        self::$agentsName = [];
602
    }
603 3
604 3
    /**
605
     * tear down agents config
606
     */
607
    public static function cleanAgentsConfig()
608
    {
609
        self::$agentsConfig = [];
610
    }
611
612
    /**
613
     * overload static method
614 6
     *
615
     * @param $name
616 6
     * @param $args
617 6
     *
618 6
     * @throws PhpSmsException
619 6
     */
620 6
    public static function __callStatic($name, $args)
621 6
    {
622 6
        $name = $name === 'beforeSend' ? 'beforeRun' : $name;
623 6
        $name = $name === 'afterSend' ? 'afterRun' : $name;
624 6
        $name = $name === 'beforeAgentSend' ? 'beforeDriverRun' : $name;
625 6
        $name = $name === 'afterAgentSend' ? 'afterDriverRun' : $name;
626 4
        if (in_array($name, self::$enableHooks)) {
627 2
            $handler = $args[0];
628
            $override = isset($args[1]) ? (bool) $args[1] : false;
629 4
            if (is_callable($handler)) {
630
                $task = self::generatorTask();
631
                $task->hook($name, $handler, $override);
632 6
            } else {
633
                throw new PhpSmsException("Please give method static $name() a callable parameter");
634
            }
635
        } else {
636
            throw new PhpSmsException("Do not find static method $name()");
637
        }
638
    }
639
640
    /**
641
     * overload method
642
     *
643 3
     * @param $name
644
     * @param $args
645
     *
646 3
     * @throws PhpSmsException
647 2
     * @throws \Exception
648
     */
649
    public function __call($name, $args)
650 3
    {
651
        try {
652
            $this->__callStatic($name, $args);
653
        } catch (\Exception $e) {
654
            throw $e;
655
        }
656
    }
657
}
658