Completed
Push — master ( 76a557...820b1c )
by lan tian
14s
created

Sms::validator()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 0
loc 8
ccs 3
cts 4
cp 0.75
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 2.0625
1
<?php
2
3
namespace Toplan\PhpSms;
4
5
use SuperClosure\Serializer;
6
use Toplan\TaskBalance\Balancer;
7
8
/**
9
 * Class Sms
10
 */
11
class Sms
12
{
13
    /**
14
     * sms send task name
15
     */
16
    const TASK = 'PhpSms';
17
18
    /**
19
     * agents instance
20
     */
21
    protected static $agents = [];
22
23
    /**
24
     * enable agents` name
25
     *
26
     * @var
27
     */
28
    protected static $agentsName = [];
29
30
    /**
31
     * agents` config
32
     *
33
     * @var
34
     */
35
    protected static $agentsConfig = [];
36
37
    /**
38
     * whether to enable queue
39
     *
40
     * @var bool
41
     */
42
    protected static $enableQueue = false;
43
44
    /**
45
     * queue work
46
     *
47
     * @var \Closure
48
     */
49
    protected static $howToUseQueue = null;
50
51
    /**
52
     * sms already pushed to queue
53
     *
54
     * @var bool
55
     */
56
    protected $pushedToQueue = false;
57
58
    /**
59
     * hook handlers
60
     *
61
     * @var array
62
     */
63
    protected static $enableHooks = [
64
        'beforeRun',
65
        'beforeDriverRun',
66
        'afterDriverRun',
67
        'afterRun',
68
    ];
69
70
    /**
71
     * sms data
72
     *
73
     * @var array
74
     */
75
    protected $smsData = [
76
        'to'           => null,
77
        'templates'    => [],
78
        'content'      => '',
79
        'templateData' => [],
80
        'voiceCode'    => null,
81
    ];
82
83
    /**
84
     * first agent for send sms/voice verify
85
     *
86
     * @var string
87
     */
88
    protected $firstAgent = null;
89
90
    /**
91
     * a instance of class 'SuperClosure\Serializer'
92
     *
93
     * @var Serializer
94
     */
95
    protected static $serializer = null;
96
97
    /**
98
     * store the static properties of Sms class when serialize a instance
99
     *
100
     * @var array
101
     */
102
    protected $_status_before_enqueue_ = [];
103
104
    /**
105
     * construct
106
     *
107
     * @param bool $autoBoot
108
     */
109 6
    public function __construct($autoBoot = true)
110
    {
111 6
        if ($autoBoot) {
112 6
            self::bootstrap();
113 6
        }
114 6
    }
115
116
    /**
117
     * create sms instance and set templates
118
     *
119
     * @param mixed $agentName
120
     * @param mixed $tempId
121
     *
122
     * @return Sms
123
     */
124
    public static function make($agentName = null, $tempId = null)
125
    {
126
        $sms = new self();
127
        if (is_array($agentName)) {
128
            $sms->template($agentName);
129
        } elseif ($agentName && is_string($agentName)) {
130
            if ($tempId === null) {
131
                $sms->content($agentName);
132
            } elseif (is_string("$tempId")) {
133
                $sms->template($agentName, $tempId);
134
            }
135
        }
136
137
        return $sms;
138
    }
139
140
    /**
141
     * send voice verify
142
     *
143
     * @param $code
144
     *
145
     * @return Sms
146
     */
147 3
    public static function voice($code)
148
    {
149 3
        $sms = new self();
150 3
        $sms->smsData['voiceCode'] = $code;
151
152 3
        return $sms;
153
    }
154
155
    /**
156
     * set how to use queue.
157
     *
158
     * @param $enable
159
     * @param $handler
160
     *
161
     * @return bool
162
     */
163 3
    public static function queue($enable = null, $handler = null)
164
    {
165 3
        if ($enable === null && $handler === null) {
166 3
            return self::$enableQueue;
167
        }
168 3
        if (is_callable($enable)) {
169 3
            $handler = $enable;
170 3
            $enable = true;
171 3
        }
172 3
        self::$enableQueue = (bool) $enable;
173 3
        if (is_callable($handler)) {
174 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...
175 3
        }
176
177 3
        return self::$enableQueue;
178
    }
179
180
    /**
181
     * set the mobile number
182
     *
183
     * @param $mobile
184
     *
185
     * @return $this
186
     */
187 3
    public function to($mobile)
188
    {
189 3
        $this->smsData['to'] = $mobile;
190
191 3
        return $this;
192
    }
193
194
    /**
195
     * set content for content sms
196
     *
197
     * @param $content
198
     *
199
     * @return $this
200
     */
201 3
    public function content($content)
202
    {
203 3
        $this->smsData['content'] = trim((string) $content);
204
205 3
        return $this;
206
    }
207
208
    /**
209
     * set template id for template sms
210
     *
211
     * @param $agentName
212
     * @param $tempId
213
     *
214
     * @return $this
215
     */
216 3
    public function template($agentName, $tempId = null)
217
    {
218 3
        if (is_array($agentName)) {
219 3
            foreach ($agentName as $k => $v) {
220 3
                $this->template($k, $v);
221 3
            }
222 3
        } elseif ($agentName && $tempId) {
223 3
            if (!isset($this->smsData['templates']) || !is_array($this->smsData['templates'])) {
224
                $this->smsData['templates'] = [];
225
            }
226 3
            $this->smsData['templates']["$agentName"] = $tempId;
227 3
        }
228
229 3
        return $this;
230
    }
231
232
    /**
233
     * set data for template sms
234
     *
235
     * @param array $data
236
     *
237
     * @return $this
238
     */
239 3
    public function data(array $data)
240
    {
241 3
        $this->smsData['templateData'] = $data;
242
243 3
        return $this;
244
    }
245
246
    /**
247
     * set the first agent
248
     *
249
     * @param $name
250
     *
251
     * @return $this
252
     */
253 3
    public function agent($name)
254
    {
255 3
        $this->firstAgent = (string) $name;
256
257 3
        return $this;
258
    }
259
260
    /**
261
     * start send
262
     *
263
     * @param bool $immediately
264
     *
265
     * @return mixed
266
     */
267 18
    public function send($immediately = false)
268
    {
269
        // if disable push to queue,
270
        // send the sms immediately.
271 18
        if (!self::$enableQueue) {
272 15
            $immediately = true;
273 15
        }
274
275
        // whatever 'PhpSms' whether to enable or disable push to queue,
276
        // if you are already pushed sms instance to queue,
277
        // you can recall the method `send()` in queue job without `true` parameter.
278
        //
279
        // So this mechanism in order to make you convenient use the method `send()` in queue system.
280 18
        if ($this->pushedToQueue) {
281 3
            $immediately = true;
282 3
        }
283
284
        // whether to send sms immediately,
285
        // or push it to queue.
286 18
        if ($immediately) {
287 18
            $result = Balancer::run(self::TASK, [
288 18
                'data'   => $this->getData(),
289 18
                'driver' => $this->firstAgent,
290 18
            ]);
291 18
        } else {
292 3
            $result = $this->push();
293
        }
294
295 18
        return $result;
296
    }
297
298
    /**
299
     * push sms send task to queue
300
     *
301
     * @throws \Exception | PhpSmsException
302
     *
303
     * @return mixed
304
     */
305 3
    public function push()
306
    {
307 3
        if (is_callable(self::$howToUseQueue)) {
308
            try {
309 3
                $this->pushedToQueue = true;
310
311 3
                return call_user_func_array(self::$howToUseQueue, [$this, $this->smsData]);
312
            } catch (\Exception $e) {
313
                $this->pushedToQueue = false;
314
                throw $e;
315
            }
316
        } else {
317
            throw new PhpSmsException('Please define how to use queue by method `queue($enable, $handler)`');
318
        }
319
    }
320
321
    /**
322
     * get sms data
323
     *
324
     * @return array
325
     */
326 36
    public function getData()
327
    {
328 36
        return $this->smsData;
329
    }
330
331
    /**
332
     * bootstrap
333
     */
334 9
    public static function bootstrap()
335
    {
336 9
        $task = self::generatorTask();
337 9
        if (!count($task->drivers)) {
338 6
            self::configuration();
339 6
            self::createDrivers($task);
340 6
        }
341 9
    }
342
343
    /**
344
     * generator a sms send task
345
     *
346
     * @return object
347
     */
348 18
    public static function generatorTask()
349
    {
350 18
        if (!Balancer::hasTask(self::TASK)) {
351 6
            Balancer::task(self::TASK);
352 6
        }
353
354 18
        return Balancer::getTask(self::TASK);
355
    }
356
357
    /**
358
     * configuration
359
     */
360 6
    protected static function configuration()
361
    {
362 6
        $config = [];
363 6
        self::generatorAgentsName($config);
364 6
        self::generatorAgentsConfig($config);
365 6
        self::configValidator();
366 6
    }
367
368
    /**
369
     * generate enabled agents name
370
     *
371
     * @param array $config
372
     */
373 6
    protected static function generatorAgentsName(&$config)
374
    {
375 6
        if (empty(self::$agentsName)) {
376 3
            $config = $config ?: include __DIR__ . '/../config/phpsms.php';
377 3
            $enableAgents = isset($config['enable']) ? $config['enable'] : null;
378 3
            self::enable($enableAgents);
379 3
        }
380 6
    }
381
382
    /**
383
     * generator agents config
384
     *
385
     * @param array $config
386
     */
387 6
    protected static function generatorAgentsConfig(&$config)
388
    {
389 6
        $diff = array_diff_key(self::$agentsName, self::$agentsConfig);
390 6
        $diff = array_keys($diff);
391 6
        if (count($diff)) {
392 6
            $config = $config ?: include __DIR__ . '/../config/phpsms.php';
393 6
            $agentsConfig = isset($config['agents']) ? $config['agents'] : [];
394 6
            foreach ($diff as $name) {
395 6
                $agentConfig = isset($agentsConfig[$name]) ? $agentsConfig[$name] : [];
396 6
                self::agents($name, $agentConfig);
397 6
            }
398 6
        }
399 6
    }
400
401
    /**
402
     * config value validator
403
     *
404
     * @throws PhpSmsException
405
     */
406 6
    protected static function configValidator()
407
    {
408 6
        if (!count(self::$agentsName)) {
409
            throw new PhpSmsException('Please set at least one enable agent in config file(config/phpsms.php) or use method enable()');
410
        }
411 6
    }
412
413
    /**
414
     * create drivers for sms send task
415
     *
416
     * @param $task
417
     */
418 18
    protected static function createDrivers($task)
419
    {
420 6
        foreach (self::$agentsName as $name => $options) {
421
            //获取代理器配置
422 6
            $configData = self::getAgentConfigData($name);
423
            //解析代理器数组模式的调度配置
424 6
            if (is_array($options)) {
425 3
                $data = self::parseAgentArrayOptions($options);
426 3
                $configData = array_merge($configData, $data);
427 3
                $options = $data['driverOpts'];
428 3
            }
429
            //创建任务驱动器
430 6
            $task->driver("$name $options")->data($configData)
431 18
                 ->work(function ($driver) {
432 18
                     $configData = $driver->getDriverData();
433 18
                     $agent = self::getSmsAgent($driver->name, $configData);
434 18
                     $smsData = $driver->getTaskData();
435 18
                     extract($smsData);
436 18
                     if (isset($smsData['voiceCode']) && $smsData['voiceCode']) {
437
                         $agent->voiceVerify($to, $voiceCode);
438
                     } else {
439 18
                         $template = isset($templates[$driver->name]) ? $templates[$driver->name] : 0;
440 18
                         $agent->sendSms($template, $to, $templateData, $content);
441
                     }
442 18
                     $result = $agent->result();
443 18
                     if ($result['success']) {
444 18
                         $driver->success();
445 18
                     }
446 18
                     unset($result['success']);
447
448 18
                     return $result;
449 6
                 });
450 6
        }
451 6
    }
452
453
    /**
454
     * 解析可用代理器的数组模式的调度配置
455
     *
456
     * @param array $options
457
     *
458
     * @return array
459
     */
460 3
    protected static function parseAgentArrayOptions(array $options)
461
    {
462 3
        $agentClass = self::pullAgentOptionByName($options, 'agentClass');
463 3
        $sendSms = self::pullAgentOptionByName($options, 'sendSms');
464 3
        $voiceVerify = self::pullAgentOptionByName($options, 'voiceVerify');
465 3
        $backup = self::pullAgentOptionByName($options, 'backup');
466 3
        $driverOpts = implode(' ', array_values($options)) . " $backup";
467
468 3
        return compact('agentClass', 'sendSms', 'voiceVerify', 'driverOpts');
469
    }
470
471
    /**
472
     * 从调度配置中拉取指定数据
473
     *
474
     * @param array  $options
475
     * @param string $name
476
     *
477
     * @return null|string
478
     */
479 3
    protected static function pullAgentOptionByName(array &$options, $name)
480
    {
481 3
        $value = isset($options[$name]) ? $options[$name] : null;
482 3
        if ($name === 'backup') {
483 3
            $value = isset($options[$name]) ? ($options[$name] ? 'backup' : '') : '';
484 3
        }
485 3
        unset($options[$name]);
486
487 3
        return $value;
488
    }
489
490
    /**
491
     * get agent config data by name
492
     *
493
     * @param $name
494
     *
495
     * @return array
496
     */
497 6
    protected static function getAgentConfigData($name)
498
    {
499 6
        return isset(self::$agentsConfig[$name]) ?
500 6
               (array) self::$agentsConfig[$name] : [];
501
    }
502
503
    /**
504
     * get a sms agent instance,
505
     * if null, will create a new agent instance
506
     *
507
     * @param       $name
508
     * @param array $configData
509
     *
510
     * @throws PhpSmsException
511
     *
512
     * @return mixed
513
     */
514 21
    public static function getSmsAgent($name, array $configData)
515
    {
516 21
        if (!isset(self::$agents[$name])) {
517 6
            $configData['name'] = $name;
518 6
            $className = isset($configData['agentClass']) ? $configData['agentClass'] : ('Toplan\\PhpSms\\' . $name . 'Agent');
519 6
            if ((isset($configData['sendSms']) && is_callable($configData['sendSms'])) ||
520 6
                (isset($configData['voiceVerify']) && is_callable($configData['voiceVerify']))) {
521
                //创建寄生代理器
522 3
                $configData['agentClass'] = '';
523 3
                self::$agents[$name] = new ParasiticAgent($configData);
524 6
            } elseif (class_exists($className)) {
525
                //创建新代理器
526 3
                self::$agents[$name] = new $className($configData);
527 3
            } else {
528
                //无代理器可用
529
                throw new PhpSmsException("Do not support [$name] agent.");
530
            }
531 6
        }
532
533 21
        return self::$agents[$name];
534
    }
535
536
    /**
537
     * set enable agents
538
     *
539
     * @param      $agentName
540
     * @param null $options
541
     */
542 6
    public static function enable($agentName, $options = null)
543
    {
544 6
        if (is_array($agentName)) {
545 6
            foreach ($agentName as $name => $opt) {
546 6
                self::enable($name, $opt);
547 6
            }
548 6
        } elseif ($agentName && is_string($agentName) && $options !== null) {
549 3
            self::$agentsName["$agentName"] = is_array($options) ? $options : "$options";
550 6
        } elseif (is_int($agentName) && !is_array($options) && "$options") {
551 3
            self::$agentsName["$options"] = '1';
552 6
        } elseif ($agentName && $options === null) {
553 3
            self::$agentsName["$agentName"] = '1';
554 3
        }
555 6
    }
556
557
    /**
558
     * set config for available agents
559
     *
560
     * @param       $agentName
561
     * @param array $config
562
     *
563
     * @throws PhpSmsException
564
     */
565 9
    public static function agents($agentName, array $config = [])
566
    {
567 9
        if (is_array($agentName)) {
568 3
            foreach ($agentName as $name => $conf) {
569 3
                self::agents($name, $conf);
570 3
            }
571 9
        } elseif ($agentName && is_array($config)) {
572 9
            if (preg_match('/^[0-9]+$/', $agentName)) {
573
                throw new PhpSmsException("Agent name [$agentName] must be string, could not be a pure digital");
574
            }
575 9
            self::$agentsConfig["$agentName"] = $config;
576 9
        }
577 9
    }
578
579
    /**
580
     * get enable agents
581
     *
582
     * @return array
583
     */
584 12
    public static function getEnableAgents()
585
    {
586 12
        return self::$agentsName;
587
    }
588
589
    /**
590
     * get agents config info
591
     *
592
     * @return array
593
     */
594 12
    public static function getAgentsConfig()
595
    {
596 12
        return self::$agentsConfig;
597
    }
598
599
    /**
600
     * tear down enable agents
601
     */
602 6
    public static function cleanEnableAgents()
603
    {
604 6
        self::$agentsName = [];
605 6
    }
606
607
    /**
608
     * tear down agents config
609
     */
610 3
    public static function cleanAgentsConfig()
611
    {
612 3
        self::$agentsConfig = [];
613 3
    }
614
615
    /**
616
     * overload static method
617
     *
618
     * @param $name
619
     * @param $args
620
     *
621
     * @throws PhpSmsException
622
     */
623 9
    public static function __callStatic($name, $args)
624
    {
625 9
        $name = $name === 'beforeSend' ? 'beforeRun' : $name;
626 9
        $name = $name === 'afterSend' ? 'afterRun' : $name;
627 9
        $name = $name === 'beforeAgentSend' ? 'beforeDriverRun' : $name;
628 9
        $name = $name === 'afterAgentSend' ? 'afterDriverRun' : $name;
629 9
        if (in_array($name, self::$enableHooks)) {
630 9
            $handler = $args[0];
631 9
            $override = isset($args[1]) ? (bool) $args[1] : false;
632 9
            if (is_callable($handler)) {
633 9
                $task = self::generatorTask();
634 9
                $task->hook($name, $handler, $override);
635 9
            } else {
636
                throw new PhpSmsException("Please give method static $name() a callable parameter");
637
            }
638 9
        } else {
639
            throw new PhpSmsException("Do not find static method $name()");
640
        }
641 9
    }
642
643
    /**
644
     * overload method
645
     *
646
     * @param $name
647
     * @param $args
648
     *
649
     * @throws PhpSmsException
650
     * @throws \Exception
651
     */
652 3
    public function __call($name, $args)
653
    {
654
        try {
655 3
            $this->__callStatic($name, $args);
656 3
        } catch (\Exception $e) {
657
            throw $e;
658
        }
659 3
    }
660
661
    /**
662
     * serialize magic method
663
     * store current sms instance status
664
     *
665
     * @return array
666
     */
667 3
    public function __sleep()
668
    {
669
        try {
670 3
            $this->_status_before_enqueue_['enableAgents'] = self::serializeEnableAgents();
671 3
            $this->_status_before_enqueue_['agentsConfig'] = self::getAgentsConfig();
672 3
            $this->_status_before_enqueue_['handlers'] = self::serializeHandlers();
673 3
        } catch (\Exception $e) {
674
            //swallow exception
675
        }
676
677 3
        return ['pushedToQueue', 'smsData', 'firstAgent', '_status_before_enqueue_'];
678
    }
679
680
    /**
681
     * unserialize magic method
682
     * note: the force bootstrap must before reinstall handlers!
683
     */
684 3
    public function __wakeup()
685
    {
686 3
        if (empty($this->_status_before_enqueue_)) {
687
            return;
688
        }
689 3
        $status = $this->_status_before_enqueue_;
690 3
        self::$agentsName = self::unserializeEnableAgents($status['enableAgents']);
691 3
        self::$agentsConfig = $status['agentsConfig'];
692 3
        Balancer::destroy(self::TASK);
693 3
        self::bootstrap();
694 3
        self::reinstallHandlers($status['handlers']);
695 3
    }
696
697
    /**
698
     * get a serializer
699
     *
700
     * @return Serializer
701
     */
702 3
    public static function getSerializer()
703
    {
704 3
        if (!self::$serializer) {
705 3
            self::$serializer = new Serializer();
706 3
        }
707
708 3
        return self::$serializer;
709
    }
710
711
    /**
712
     * serialize enable agents
713
     *
714
     * @return array
715
     */
716 3
    protected static function serializeEnableAgents()
717
    {
718 3
        $enableAgents = self::getEnableAgents();
719 3
        foreach ($enableAgents as $name => &$options) {
720 3
            if (is_array($options)) {
721 3
                self::serializeClosureAndReplace($options, 'sendSms');
722 3
                self::serializeClosureAndReplace($options, 'voiceVerify');
723 3
            }
724 3
        }
725
726 3
        return $enableAgents;
727
    }
728
729
    /**
730
     * unserialize enable agents
731
     *
732
     * @param array $serialized
733
     *
734
     * @return mixed
735
     */
736 3
    protected static function unserializeEnableAgents(array $serialized)
737
    {
738 3
        foreach ($serialized as $name => &$options) {
739 3
            if (is_array($options)) {
740 3
                self::unserializeToClosureAndReplace($options, 'sendSms');
741 3
                self::unserializeToClosureAndReplace($options, 'voiceVerify');
742 3
            }
743 3
        }
744
745 3
        return $serialized;
746
    }
747
748
    /**
749
     * serialize character closure value of a array and replace origin value
750
     *
751
     * @param array $options
752
     * @param       $key
753
     */
754 3
    protected static function serializeClosureAndReplace(array &$options, $key)
755
    {
756 3
        if (isset($options["$key"]) && is_callable($options["$key"])) {
757 3
            $serializer = self::getSerializer();
758 3
            $options["$key"] = (string) $serializer->serialize($options["$key"]);
759 3
        }
760 3
    }
761
762
    /**
763
     * unserialize character string of a array to closure and replace origin value
764
     *
765
     * @param array $options
766
     * @param       $key
767
     */
768 3
    protected static function unserializeToClosureAndReplace(array &$options, $key)
769
    {
770 3
        if (isset($options["$key"])) {
771 3
            $serializer = self::getSerializer();
772 3
            $options["$key"] = $serializer->unserialize($options["$key"]);
773 3
        }
774 3
    }
775
776
    /**
777
     * serialize these hooks` handlers:
778
     * 'beforeRun','beforeDriverRun','afterDriverRun','afterRun'
779
     *
780
     * @return array
781
     */
782 3
    protected static function serializeHandlers()
783
    {
784 3
        $hooks = [];
785 3
        $serializer = self::getSerializer();
786 3
        $task = self::generatorTask();
787 3
        foreach ($task->handlers as $hookName => $handlers) {
788 3
            foreach ($handlers as $handler) {
789 3
                $serialized = $serializer->serialize($handler);
790 3
                if (!isset($hooks[$hookName])) {
791 3
                    $hooks[$hookName] = [];
792 3
                }
793 3
                array_push($hooks[$hookName], $serialized);
794 3
            }
795 3
        }
796
797 3
        return $hooks;
798
    }
799
800
    /**
801
     * reinstall hooks` handlers by serialized handlers
802
     *
803
     * @param array $handlers
804
     */
805 3
    protected static function reinstallHandlers(array $handlers)
806
    {
807 3
        $serializer = self::getSerializer();
808 3
        foreach ($handlers as $hookName => $serializedHandlers) {
809 3
            foreach ($serializedHandlers as $index => $handler) {
810 3
                if (is_string($handler)) {
811 3
                    $handler = $serializer->unserialize($handler);
812 3
                }
813 3
                $override = $index === 0;
814 3
                self::$hookName($handler, $override);
815 3
            }
816 3
        }
817 3
    }
818
}
819