Completed
Pull Request — master (#17)
by lan tian
03:35
created

Sms::generatorAgentsConfig()   B

Complexity

Conditions 6
Paths 13

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 6

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 13
ccs 12
cts 12
cp 1
rs 8.8571
cc 6
eloc 9
nc 13
nop 1
crap 6
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 \Closure
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
     * @param bool $autoBoot
93
     */
94 9
    public function __construct($autoBoot = true)
95
    {
96 9
        if ($autoBoot) {
97 9
            self::bootstrap();
98 6
        }
99 9
    }
100
101
    /**
102
     * create sms instance and set templates
103
     *
104
     * @param mixed $agentName
105
     * @param mixed $tempId
106
     *
107
     * @return Sms
108
     */
109 3
    public static function make($agentName = null, $tempId = null)
110
    {
111 3
        $sms = new self();
112 3
        if (is_array($agentName)) {
113
            $sms->template($agentName);
114 3
        } elseif ($agentName && is_string($agentName)) {
115
            if ($tempId === null) {
116
                $sms->content($agentName);
117
            } elseif (is_string("$tempId")) {
118
                $sms->template($agentName, $tempId);
119
            }
120
        }
121
122 3
        return $sms;
123
    }
124
125
    /**
126
     * send voice verify
127
     *
128
     * @param $code
129
     *
130
     * @return Sms
131
     */
132 3
    public static function voice($code)
133
    {
134 3
        $sms = new self();
135 3
        $sms->smsData['voiceCode'] = $code;
136
137 3
        return $sms;
138
    }
139
140
    /**
141
     * set how to use queue.
142
     *
143
     * @param $enable
144
     * @param $handler
145
     *
146
     * @return bool
147
     */
148 3
    public static function queue($enable = null, $handler = null)
149
    {
150 3
        if ($enable === null && $handler === null) {
151 3
            return self::$enableQueue;
152
        }
153 3
        if (is_callable($enable)) {
154 3
            $handler = $enable;
155 3
            $enable = true;
156 2
        }
157 3
        self::$enableQueue = (bool) $enable;
158 3
        if (is_callable($handler)) {
159 3
            self::$howToUseQueue = $handler;
0 ignored issues
show
Documentation Bug introduced by
It seems like $handler of type callable is incompatible with the declared type object<Closure> 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 2
        }
161
162 3
        return self::$enableQueue;
163
    }
164
165
    /**
166
     * set the mobile number
167
     *
168
     * @param $mobile
169
     *
170
     * @return $this
171
     */
172 6
    public function to($mobile)
173
    {
174 6
        $this->smsData['to'] = $mobile;
175
176 6
        return $this;
177
    }
178
179
    /**
180
     * set content for content sms
181
     *
182
     * @param $content
183
     *
184
     * @return $this
185
     */
186 3
    public function content($content)
187
    {
188 3
        $this->smsData['content'] = trim((String) $content);
189
190 3
        return $this;
191
    }
192
193
    /**
194
     * set template id for template sms
195
     *
196
     * @param $agentName
197
     * @param $tempId
198
     *
199
     * @return $this
200
     */
201 3
    public function template($agentName, $tempId = null)
202
    {
203 3
        if (is_array($agentName)) {
204 3
            foreach ($agentName as $k => $v) {
205 3
                $this->template($k, $v);
206 2
            }
207 3
        } elseif ($agentName && $tempId) {
208 3
            if (!isset($this->smsData['templates']) || !is_array($this->smsData['templates'])) {
209
                $this->smsData['templates'] = [];
210
            }
211 3
            $this->smsData['templates']["$agentName"] = $tempId;
212 2
        }
213
214 3
        return $this;
215
    }
216
217
    /**
218
     * set data for template sms
219
     *
220
     * @param array $data
221
     *
222
     * @return $this
223
     */
224 11
    public function data(array $data)
225 10
    {
226 3
        $this->smsData['templateData'] = $data;
227
228 3
        return $this;
229
    }
230
231
    /**
232
     * set the first agent
233
     *
234
     * @param $name
235
     *
236
     * @return $this
237
     */
238 3
    public function agent($name)
239
    {
240 3
        $this->firstAgent = (String) $name;
241
242 3
        return $this;
243
    }
244
245
    /**
246
     * start send
247
     *
248
     * @param bool $immediately
249
     *
250
     * @return mixed
251
     */
252 15
    public function send($immediately = false)
253
    {
254 15
        $this->validator();
255
256
        // if disable push to queue,
257
        // send the sms immediately.
258 15
        if (!self::$enableQueue) {
259 12
            $immediately = true;
260 8
        }
261
262
        // whatever 'PhpSms' whether to enable or disable push to queue,
263
        // if you are already pushed sms instance to queue,
264
        // you can recall the method `send()` in queue job without `true` parameter.
265
        //
266
        // So this mechanism in order to make you convenient use the method `send()` in queue system.
267 15
        if ($this->pushedToQueue) {
268 3
            $immediately = true;
269 2
        }
270
271
        // whether to send sms immediately,
272
        // or push it to queue.
273 15
        if ($immediately) {
274 15
            $result = Balancer::run(self::TASK, [
275 15
                'data' => $this->getData(),
276 15
                'agent' => $this->firstAgent,
277 10
            ]);
278 10
        } else {
279 3
            $result = $this->push();
280
        }
281
282 15
        return $result;
283
    }
284
285
    /**
286
     * push sms send task to queue
287
     *
288
     * @throws \Exception | PhpSmsException
289
     *
290
     * @return mixed
291
     */
292 3
    protected function push()
293
    {
294 3
        if (is_callable(self::$howToUseQueue)) {
295
            try {
296 3
                $this->pushedToQueue = true;
297
298 3
                return call_user_func_array(self::$howToUseQueue, [$this, $this->smsData]);
299
            } catch (\Exception $e) {
300
                $this->pushedToQueue = false;
301
                throw $e;
302
            }
303
        } else {
304
            throw new PhpSmsException('Please define how to use queue by method `queue($enable, $handler)`');
305
        }
306
    }
307
308
    /**
309
     * get sms data
310
     *
311
     * @return array
312
     */
313 33
    public function getData()
314
    {
315 33
        return $this->smsData;
316
    }
317
318
    /**
319
     * bootstrap
320
     */
321 9
    public static function bootstrap()
322
    {
323 9
        $task = self::generatorTask();
324 9
        if (!count($task->drivers)) {
325 3
            self::configuration();
326 3
            self::createDrivers($task);
327 2
        }
328 9
    }
329
330
    /**
331
     * generator a sms send task
332
     *
333
     * @return object
334
     */
335 18
    public static function generatorTask()
336
    {
337 18
        if (!Balancer::hasTask(self::TASK)) {
338 3
            Balancer::task(self::TASK);
339 2
        }
340
341 18
        return Balancer::getTask(self::TASK);
342
    }
343
344
    /**
345
     * configuration
346
     */
347 3
    protected static function configuration()
348
    {
349 3
        $config = [];
350 3
        self::generatorAgentsName($config);
351 3
        self::generatorAgentsConfig($config);
352 3
        self::configValidator();
353 3
    }
354
355
    /**
356
     * generate enabled agents name
357
     *
358
     * @param array $config
359
     */
360 3
    protected static function generatorAgentsName(&$config)
361
    {
362 3
        if (empty(self::$agentsName)) {
363 3
            $config = $config ?: include __DIR__ . '/../config/phpsms.php';
364 3
            $enableAgents = isset($config['enable']) ? $config['enable'] : null;
365 3
            self::enable($enableAgents);
366 2
        }
367 3
    }
368
369
    /**
370
     * generator agents config
371
     *
372
     * @param array $config
373
     */
374 3
    protected static function generatorAgentsConfig(&$config)
375
    {
376 3
        $diff = array_diff_key(self::$agentsName, self::$agentsConfig);
377 3
        $diff = array_keys($diff);
378 3
        if (count($diff)) {
379 3
            $config = $config ?: include __DIR__ . '/../config/phpsms.php';
380 3
            $agentsConfig = isset($config['agents']) ? $config['agents'] : [];
381 3
            foreach ($diff as $name) {
382 3
                $agentConfig = isset($agentsConfig[$name]) ? $agentsConfig[$name] : [];
383 3
                self::agents($name, $agentConfig);
384 2
            }
385 2
        }
386 3
    }
387
388
    /**
389
     * config value validator
390
     *
391
     * @throws PhpSmsException
392
     */
393 3
    protected static function configValidator()
394
    {
395 3
        if (!count(self::$agentsName)) {
396
            throw new PhpSmsException('Please set at least one enable agent in config file(config/phpsms.php) or use method enable()');
397
        }
398 3
    }
399
400
    /**
401
     * create drivers for sms send task
402
     *
403
     * @param $task
404
     */
405 15
    protected static function createDrivers($task)
406
    {
407 3
        foreach (self::$agentsName as $name => $options) {
408
            //获取代理器配置
409 3
            $configData = self::getAgentConfigData($name);
410
            //解析代理器数组模式的调度配置
411 3
            if (is_array($options)) {
412
                $data = self::parseAgentArrayOptions($options);
413
                $configData = array_merge($configData, $data);
414
                $options = $data['driverOpts'];
415
            }
416
            //创建任务驱动器
417 3
            $task->driver("$name $options")->data($configData)
418 15
                 ->work(function ($driver) {
419 15
                     $configData = $driver->getDriverData();
420 15
                     $agent = self::getSmsAgent($driver->name, $configData);
421 15
                     $smsData = $driver->getTaskData();
422 15
                     extract($smsData);
423 15
                     if (isset($smsData['voiceCode']) && $smsData['voiceCode']) {
424
                         $agent->voiceVerify($to, $voiceCode);
425
                     } else {
426 15
                         $template = isset($templates[$driver->name]) ? $templates[$driver->name] : 0;
427 15
                         $agent->sendSms($template, $to, $templateData, $content);
428
                     }
429 15
                     $result = $agent->getResult();
430 15
                     if ($result['success']) {
431 15
                         $driver->success();
432 10
                     }
433 15
                     unset($result['success']);
434
435 15
                     return $result;
436 3
                 });
437 2
        }
438 3
    }
439
440
    /**
441
     * 解析可用代理器的数组模式的调度配置
442
     *
443
     * @param array $options
444
     *
445
     * @return array
446
     */
447
    protected static function parseAgentArrayOptions(array $options)
448
    {
449
        $agentClass = self::pullAgentOptionByName($options, 'agentClass');
450
        $sendSms = self::pullAgentOptionByName($options, 'sendSms');
451
        $voiceVerify = self::pullAgentOptionByName($options, 'voiceVerify');
452
        $backup = self::pullAgentOptionByName($options, 'backup');
453
        $driverOpts = implode(' ', array_values($options)) . " $backup";
454
455
        return compact('agentClass', 'sendSms', 'voiceVerify', 'driverOpts');
456
    }
457
458
    /**
459
     * 从调度配置中拉取指定数据
460
     *
461
     * @param array  $options
462
     * @param string $name
463
     *
464
     * @return null|string
465
     */
466
    protected static function pullAgentOptionByName(array &$options, $name)
467
    {
468
        $value = isset($options[$name]) ? $options[$name] : null;
469
        if ($name === 'backup') {
470
            $value = isset($options[$name]) ? ($options[$name] ? 'backup' : '') : '';
471
        }
472
        unset($options[$name]);
473
474
        return $value;
475
    }
476
477
    /**
478
     * get agent config data by name
479
     *
480
     * @param $name
481
     *
482
     * @return array
483
     */
484 3
    protected static function getAgentConfigData($name)
485
    {
486 3
        return isset(self::$agentsConfig[$name]) ?
487 3
               (Array) self::$agentsConfig[$name] : [];
488
    }
489
490
    /**
491
     * get a sms agent instance,
492
     * if null, will create a new agent instance
493
     *
494
     * @param       $name
495
     * @param array $configData
496
     *
497
     * @throws PhpSmsException
498
     *
499
     * @return mixed
500
     */
501 18
    public static function getSmsAgent($name, array $configData)
502
    {
503 18
        if (!isset(self::$agents[$name])) {
504 3
            $configData['name'] = $name;
505 3
            $className = isset($configData['agentClass']) ? $configData['agentClass'] : ('Toplan\\PhpSms\\' . $name . 'Agent');
506 3
            if ((isset($configData['sendSms']) && is_callable($configData['sendSms'])) ||
507 3
                (isset($configData['voiceVerify']) && is_callable($configData['voiceVerify']))) {
508
                //创建寄生代理器
509
                $configData['agentClass'] = '';
510
                self::$agents[$name] = new ParasiticAgent($configData);
511 3
            } elseif (class_exists($className)) {
512
                //创建新代理器
513 3
                self::$agents[$name] = new $className($configData);
514 2
            } else {
515
                //无代理器可用
516
                throw new PhpSmsException("Dose not support [$name] agent.");
517
            }
518 2
        }
519
520 18
        return self::$agents[$name];
521
    }
522
523
    /**
524
     * validate
525
     *
526
     * @throws PhpSmsException
527
     */
528 18
    protected function validator()
529
    {
530 18
        if (!$this->smsData['to']) {
531
            throw new PhpSmsException('Please set send sms(or voice verify) to who use `to()` method.');
532
        }
533
534 18
        return true;
535
    }
536
537
    /**
538
     * set enable agents
539
     *
540
     * @param      $agentName
541
     * @param null $options
542
     */
543 6
    public static function enable($agentName, $options = null)
544
    {
545 6
        if (is_array($agentName)) {
546 6
            foreach ($agentName as $name => $opt) {
547 6
                self::enable($name, $opt);
548 4
            }
549 6
        } elseif ($agentName && is_string($agentName) && $options !== null) {
550 3
            self::$agentsName["$agentName"] = is_array($options) ? $options : "$options";
551 6
        } elseif (is_int($agentName) && !is_array($options) && "$options") {
552 3
            self::$agentsName["$options"] = '1';
553 5
        } elseif ($agentName && $options === null) {
554 3
            self::$agentsName["$agentName"] = '1';
555 2
        }
556 6
    }
557
558
    /**
559
     * set config for available agents
560
     *
561
     * @param       $agentName
562
     * @param array $config
563
     *
564
     * @throws PhpSmsException
565
     */
566 6
    public static function agents($agentName, array $config = [])
567
    {
568 6
        if (is_array($agentName)) {
569 3
            foreach ($agentName as $name => $conf) {
570 3
                self::agents($name, $conf);
571 2
            }
572 6
        } elseif ($agentName && is_array($config)) {
573 6
            if (preg_match('/^[0-9]+$/', $agentName)) {
574
                throw new PhpSmsException("Agent name [$agentName] must be string, could not be a pure digital");
575
            }
576 6
            self::$agentsConfig["$agentName"] = $config;
577 4
        }
578 6
    }
579
580
    /**
581
     * get enable agents
582
     *
583
     * @return array
584
     */
585 9
    public static function getEnableAgents()
586
    {
587 9
        return self::$agentsName;
588
    }
589
590
    /**
591
     * get agents config info
592
     *
593
     * @return array
594
     */
595 9
    public static function getAgentsConfig()
596
    {
597 9
        return self::$agentsConfig;
598
    }
599
600
    /**
601
     * tear down enable agents
602
     */
603 3
    public static function cleanEnableAgents()
604
    {
605 3
        self::$agentsName = [];
606 3
    }
607
608
    /**
609
     * tear down agents config
610
     */
611 3
    public static function cleanAgentsConfig()
612
    {
613 3
        self::$agentsConfig = [];
614 3
    }
615
616
    /**
617
     * overload static method
618
     *
619
     * @param $name
620
     * @param $args
621
     *
622
     * @throws PhpSmsException
623
     */
624 6
    public static function __callStatic($name, $args)
625
    {
626 6
        $name = $name === 'beforeSend' ? 'beforeRun' : $name;
627 6
        $name = $name === 'afterSend' ? 'afterRun' : $name;
628 6
        $name = $name === 'beforeAgentSend' ? 'beforeDriverRun' : $name;
629 6
        $name = $name === 'afterAgentSend' ? 'afterDriverRun' : $name;
630 6
        if (in_array($name, self::$enableHooks)) {
631 6
            $handler = $args[0];
632 6
            $override = isset($args[1]) ? (bool) $args[1] : false;
633 6
            if (is_callable($handler)) {
634 6
                $task = self::generatorTask();
635 6
                $task->hook($name, $handler, $override);
636 4
            } else {
637 2
                throw new PhpSmsException("Please give method static $name() a callable parameter");
638
            }
639 4
        } else {
640
            throw new PhpSmsException("Do not find static method $name()");
641
        }
642 6
    }
643
644
    /**
645
     * overload method
646
     *
647
     * @param $name
648
     * @param $args
649
     *
650
     * @throws PhpSmsException
651
     * @throws \Exception
652
     */
653 3
    public function __call($name, $args)
654
    {
655
        try {
656 3
            $this->__callStatic($name, $args);
657 2
        } catch (\Exception $e) {
658
            throw $e;
659
        }
660 3
    }
661
}
662