Completed
Push — master ( 820b1c...4cdf42 )
by lan tian
7s
created

Sms::pullAgentOptionByName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

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