├── .gitignore ├── .scrutinizer.yml ├── .styleci.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── config │ └── phpsms.php └── phpsms │ ├── PhpSmsException.php │ ├── PhpSmsServiceProvider.php │ ├── Sms.php │ ├── Util.php │ ├── agents │ ├── Agent.php │ ├── AlidayuAgent.php │ ├── AliyunAgent.php │ ├── JuHeAgent.php │ ├── LogAgent.php │ ├── LuosimaoAgent.php │ ├── ParasiticAgent.php │ ├── QcloudAgent.php │ ├── SendCloudAgent.php │ ├── SmsBaoAgent.php │ ├── SubMailAgent.php │ ├── UcpaasAgent.php │ ├── YunPianAgent.php │ └── YunTongXunAgent.php │ ├── facades │ └── Sms.php │ ├── interfaces │ ├── ContentSms.php │ ├── ContentVoice.php │ ├── FileVoice.php │ ├── TemplateSms.php │ ├── TemplateVoice.php │ └── VoiceCode.php │ └── lib │ ├── CCPRestSmsSDK.php │ └── Ucpaas.php └── tests ├── AgentTest.php ├── ConfigTest.php ├── ProtectedTest.php ├── SerializeTest.php └── SmsTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | composer.lock 3 | vendor/ 4 | demo.php 5 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [tests/*] 3 | 4 | checks: 5 | php: 6 | remove_extra_empty_lines: true 7 | remove_php_closing_tag: true 8 | remove_trailing_whitespace: true 9 | fix_use_statements: 10 | remove_unused: true 11 | preserve_multiple: false 12 | preserve_blanklines: true 13 | order_alphabetically: true 14 | fix_php_opening_tag: true 15 | fix_linefeed: true 16 | fix_line_ending: true 17 | fix_identation_4spaces: true 18 | fix_doc_comments: true 19 | 20 | tools: 21 | external_code_coverage: 22 | timeout: 1200 23 | runs: 3 24 | php_analyzer: true 25 | php_code_coverage: true 26 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: recommended 2 | 3 | enabled: 4 | - concat_with_spaces 5 | - strict 6 | 7 | disabled: 8 | - concat_without_spaces 9 | - phpdoc_short_description 10 | - short_array_syntax -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - "5.4" 5 | - "5.5" 6 | - "5.6" 7 | - "7.0" 8 | - "hhvm" 9 | 10 | matrix: 11 | fast_finish: true 12 | allow_failures: 13 | - php: hhvm 14 | 15 | sudo: false 16 | 17 | before_script: 18 | - travis_retry composer self-update 19 | - travis_retry composer install --no-interaction --prefer-source 20 | 21 | script: 22 | 23 | - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover 24 | 25 | after_script: 26 | - wget https://scrutinizer-ci.com/ocular.phar 27 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover 28 | 29 | notifications: 30 | email: false 31 | 32 | # - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then wget https://scrutinizer-ci.com/ocular.phar; fi 33 | # - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 lan tian peng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhpSms 2 | [![StyleCI](https://styleci.io/repos/44543599/shield)](https://styleci.io/repos/44543599) 3 | [![Build Status](https://travis-ci.org/toplan/phpsms.svg?branch=master)](https://travis-ci.org/toplan/phpsms) 4 | [![Code Coverage](https://scrutinizer-ci.com/g/toplan/phpsms/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/toplan/phpsms/?branch=master) 5 | [![Latest Stable Version](https://img.shields.io/packagist/v/toplan/phpsms.svg)](https://packagist.org/packages/toplan/phpsms) 6 | [![Total Downloads](https://img.shields.io/packagist/dt/toplan/phpsms.svg)](https://packagist.org/packages/toplan/phpsms) 7 | 8 | 可能是目前最聪明、优雅的 php 短信发送库了。 9 | 10 | > phpsms的任务均衡调度功能由[toplan/task-balancer](https://github.com/toplan/task-balancer)提供。 11 | 12 | 特别感谢以下赞助者: 13 | 14 | [![短信宝](http://toplan.github.io/img/smsbao-logo.png)](http://www.smsbao.com/) 15 | 16 | # 特点 17 | - 支持内容短信,模版短信,语音验证码,内容语音,模版语音,语音文件。 18 | - 支持发送均衡调度,可按代理器权重值均衡选择服务商发送。 19 | - 支持一个或多个备用代理器(服务商)。 20 | - 支持代理器调度方案热更新,可随时更新/删除/新加代理器。 21 | - 允许推入队列,并自定义队列实现逻辑(与队列系统松散耦合)。 22 | - 灵活的发送前后钩子。 23 | - 内置国内主流服务商的代理器。 24 | - [自定义代理器](#自定义代理器)和[寄生代理器](#寄生代理器)。 25 | 26 | # 服务商 27 | 28 | | 服务商 | 模板短信 | 内容短信 | 语音验证码 | 最低消费 | 最低消费单价 | 资费标准 29 | | ----- | :-----: | :-----: | :------: | :-------: | :-----: | :-----: 30 | | [Luosimao](http://luosimao.com) | × | √ | √ | ¥850(1万条) | ¥0.085/条 | [资费标准](https://luosimao.com/service/sms#sms-price) 31 | | [云片网络](http://www.yunpian.com) | × | √ | √ | ¥55(1千条) | ¥0.055/条 | [资费标准](http://www.yunpian.com/price.html) 32 | | [容联·云通讯](http://www.yuntongxun.com) | √ | × | √ | 充值¥500 | ¥0.055/条 | [资费标准](http://www.yuntongxun.com/price/price_sms.html) 33 | | [SUBMAIL](http://submail.cn) | √ | × | √ | ¥100(1千条) | ¥0.100/条 | [资费标准](https://www.mysubmail.com/chs/store#/message) 34 | | [云之讯](http://www.ucpaas.com/) | √ | × | √ | -- | ¥0.050/条 | [资费标准](http://www.ucpaas.com/service/sms.html) 35 | | [聚合数据](https://www.juhe.cn/) | √ | × | √ | -- | ¥0.035/条 | [资费标准](https://www.juhe.cn/docs/api/id/54) 36 | | [阿里大鱼](https://www.alidayu.com/) | √ | × | √ | -- | ¥0.045/条 | [资费标准](https://www.alidayu.com/service/price) 37 | | [SendCloud](https://sendcloud.sohu.com/) | √ | × | √ | -- | ¥0.048/条 | [资费标准](https://sendcloud.sohu.com/price.html) 38 | | [短信宝](http://www.smsbao.com/) | × | √ | √ | ¥5(50条) | ¥0.040/条(100万条) | [资费标准](http://www.smsbao.com/fee/) 39 | | [腾讯云](https://www.qcloud.com/product/sms) | √ | √ | √ | -- | ¥0.045/条 | [资费标准](https://www.qcloud.com/product/sms#price) 40 | | [阿里云](https://www.aliyun.com/product/sms) | √ | × | × | -- | ¥0.045/条 | [资费标准](https://cn.aliyun.com/price/product#/mns/detail) 41 | 42 | # 安装 43 | 44 | ```php 45 | composer require toplan/phpsms:~1.8 46 | ``` 47 | 48 | 开发中版本 49 | ```php 50 | composer require toplan/phpsms:dev-master 51 | ``` 52 | 53 | # 快速上手 54 | 55 | ### 1. 配置 56 | 57 | - 配置代理器所需参数 58 | 59 | 为你需要用到的短信服务商(即代理器)配置必要的参数。可以在`config\phpsms.php`中键为`agents`的数组中配置,也可以手动在程序中设置,示例如下: 60 | 61 | ```php 62 | //example: 63 | Sms::config([ 64 | 'Luosimao' => [ 65 | 'apikey' => 'your api key', 66 | 'voiceApikey' => 'your voice api key', 67 | ], 68 | 'YunPian' => [ 69 | 'apikey' => 'your api key', 70 | ], 71 | 'SmsBao' => [ 72 | 'username' => 'your username', 73 | 'password' => 'your password' 74 | ] 75 | ]); 76 | ``` 77 | 78 | - 配置代理器调度方案 79 | 80 | 可在`config\phpsms.php`中键为`scheme`的数组中配置。也可以手动在程序中设置,示例如下: 81 | 82 | ```php 83 | //example: 84 | Sms::scheme([ 85 | //被使用概率为2/3 86 | 'Luosimao' => '20', 87 | 88 | //被使用概率为1/3,且为备用代理器 89 | 'YunPian' => '10 backup', 90 | 91 | //仅为备用代理器 92 | 'SmsBao' => '0 backup', 93 | ]); 94 | ``` 95 | > **调度方案解析:** 96 | > 如果按照以上配置,那么系统首次会尝试使用`Luosimao`或`YunPian`发送短信,且它们被使用的概率分别为`2/3`和`1/3`。 97 | > 如果使用其中一个代理器发送失败,那么会启用备用代理器,按照配置可知备用代理器有`YunPian`和`SmsBao`,那么会依次调用直到发送成功或无备用代理器可用。 98 | > 值得注意的是,如果首次尝试的是`YunPian`,那么备用代理器将会只使用`SmsBao`,也就是会排除使用过的代理器。 99 | 100 | ### 2. Enjoy it! 101 | 102 | ```php 103 | require('path/to/vendor/autoload.php'); 104 | use Toplan\PhpSms\Sms; 105 | 106 | // 接收人手机号 107 | $to = '1828****349'; 108 | // 短信模版 109 | $templates = [ 110 | 'YunTongXun' => 'your_temp_id', 111 | 'SubMail' => 'your_temp_id' 112 | ]; 113 | // 模版数据 114 | $tempData = [ 115 | 'code' => '87392', 116 | 'minutes' => '5' 117 | ]; 118 | // 短信内容 119 | $content = '【签名】这是短信内容...'; 120 | 121 | // 只希望使用模板方式发送短信,可以不设置content(如:云通讯、Submail、Ucpaas) 122 | Sms::make()->to($to)->template($templates)->data($tempData)->send(); 123 | 124 | // 只希望使用内容方式发送,可以不设置模板id和模板data(如:短信宝、云片、luosimao) 125 | Sms::make()->to($to)->content($content)->send(); 126 | 127 | // 同时确保能通过模板和内容方式发送,这样做的好处是可以兼顾到各种类型服务商 128 | Sms::make()->to($to) 129 | ->template($templates) 130 | ->data($tempData) 131 | ->content($content) 132 | ->send(); 133 | 134 | // 语音验证码 135 | Sms::voice('02343')->to($to)->send(); 136 | 137 | // 语音验证码兼容模版语音(如阿里大鱼的文本转语音) 138 | Sms::voice('02343') 139 | ->template('Alidayu', 'your_tts_code') 140 | ->data(['code' => '02343']) 141 | ->to($to) 142 | ->send(); 143 | ``` 144 | 145 | ### 3. 在laravel和lumen中使用 146 | 147 | * 服务提供器 148 | 149 | ```php 150 | //服务提供器 151 | 'providers' => [ 152 | ... 153 | Toplan\PhpSms\PhpSmsServiceProvider::class, 154 | ] 155 | 156 | //别名 157 | 'aliases' => [ 158 | ... 159 | 'PhpSms' => Toplan\PhpSms\Facades\Sms::class, 160 | ] 161 | ``` 162 | 163 | * 生成配置文件 164 | 165 | ```php 166 | php artisan vendor:publish 167 | ``` 168 | 生成的配置文件为config/phpsms.php,然后在该文件中按提示配置。 169 | 170 | * 使用 171 | 172 | 详见API,示例: 173 | ```php 174 | PhpSms::make()->to($to)->content($content)->send(); 175 | ``` 176 | 177 | # API 178 | 179 | ## API - 全局配置 180 | 181 | ### Sms::scheme([$name[, $scheme]]) 182 | 183 | 设置/获取代理器的调度方案。 184 | 185 | > 调度配置支持热更新,即在应用系统的整个运行过程中都能随时修改。 186 | 187 | - 设置 188 | 189 | 手动设置代理器调度方案(优先级高于配置文件),如: 190 | ```php 191 | Sms::scheme([ 192 | 'SmsBao' => '80 backup' 193 | 'YunPian' => '100 backup' 194 | ]); 195 | //或 196 | Sms::scheme('SmsBao', '80 backup'); 197 | Sms::scheme('YunPian', '100 backup'); 198 | ``` 199 | - 获取 200 | 201 | 通过该方法还能获取所有或指定代理器的调度方案,如: 202 | ```php 203 | //获取所有的调度方案: 204 | $scheme = Sms::scheme(); 205 | 206 | //获取指定代理器的调度方案: 207 | $scheme['SmsBao'] = Sms::scheme('SmsBao'); 208 | ``` 209 | 210 | > `scheme`静态方法的更多使用方法见[高级调度配置](#高级调度配置) 211 | 212 | ### Sms::config([$name[, $config][, $override]]); 213 | 214 | 设置/获取代理器的配置数据。 215 | 216 | > 参数配置支持热更新,即在应用系统的整个运行过程中都能随时修改。 217 | 218 | - 设置 219 | 220 | 手动设置代理器的配置数据(优先级高于配置文件),如: 221 | ```php 222 | Sms::config([ 223 | 'SmsBao' => [ 224 | 'username' => ..., 225 | 'password' => ..., 226 | ] 227 | ]); 228 | //或 229 | Sms::config('SmsBao', [ 230 | 'username' => ..., 231 | 'password' => ..., 232 | ]); 233 | ``` 234 | - 获取 235 | 236 | 通过该方法还能获取所有或指定代理器的配置参数,如: 237 | ```php 238 | //获取所有的配置: 239 | $config = Sms::config(); 240 | 241 | //获取指定代理器的配置: 242 | $config['SmsBao'] = Sms::config('SmsBao'); 243 | ``` 244 | 245 | ### Sms::beforeSend($handler[, $override]); 246 | 247 | 发送前钩子,示例: 248 | ```php 249 | Sms::beforeSend(function($task, $index, $handlers, $prevReturn){ 250 | //获取短信数据 251 | $smsData = $task->data; 252 | ... 253 | //如果返回false会终止发送任务 254 | return true; 255 | }); 256 | ``` 257 | > 更多细节请查看 [task-balancer](https://github.com/toplan/task-balancer#2-task-lifecycle) 的 `beforeRun` 钩子 258 | 259 | ### Sms::beforeAgentSend($handler[, $override]); 260 | 261 | 代理器发送前钩子,示例: 262 | ```php 263 | Sms::beforeAgentSend(function($task, $driver, $index, $handlers, $prevReturn){ 264 | //短信数据: 265 | $smsData = $task->data; 266 | //当前使用的代理器名称: 267 | $agentName = $driver->name; 268 | //如果返回false会停止使用当前代理器 269 | return true; 270 | }); 271 | ``` 272 | > 更多细节请查看 [task-balancer](https://github.com/toplan/task-balancer#2-task-lifecycle) 的 `beforeDriverRun` 钩子 273 | 274 | ### Sms::afterAgentSend($handler[, $override]); 275 | 276 | 代理器发送后钩子,示例: 277 | ```php 278 | Sms::afterAgentSend(function($task, $agentResult, $index, $handlers, $prevReturn){ 279 | //$result为代理器的发送结果数据 280 | $agentName = $agentResult['driver']; 281 | ... 282 | }); 283 | ``` 284 | > 更多细节请查看 [task-balancer](https://github.com/toplan/task-balancer#2-task-lifecycle) 的 `afterDriverRun` 钩子 285 | 286 | ### Sms::afterSend($handler[, $override]); 287 | 288 | 发送后钩子,示例: 289 | ```php 290 | Sms::afterSend(function($task, $taskResult, $index, $handlers, $prevReturn){ 291 | //$result为发送后获得的结果数组 292 | $success = $taskResult['success']; 293 | ... 294 | }); 295 | ``` 296 | > 更多细节请查看 [task-balancer](https://github.com/toplan/task-balancer#2-task-lifecycle) 的 `afterRun` 钩子 297 | 298 | ### Sms::queue([$enable[, $handler]]) 299 | 300 | 该方法可以设置是否启用队列以及定义如何推送到队列。 301 | 302 | `$handler`匿名函数可使用的参数: 303 | + `$sms` : Sms实例 304 | + `$data` : Sms实例中的短信数据,等同于`$sms->all()` 305 | 306 | 定义如何推送到队列: 307 | ```php 308 | //自动启用队列 309 | Sms::queue(function($sms, $data){ 310 | //define how to push to queue. 311 | ... 312 | }); 313 | 314 | //第一个参数为true,启用队列 315 | Sms::queue(true, function($sms, $data){ 316 | //define how to push to queue. 317 | ... 318 | }); 319 | 320 | //第一个参数为false,暂时关闭队列 321 | Sms::queue(false, function($sms, $data){ 322 | //define how to push to queue. 323 | ... 324 | }); 325 | ``` 326 | 327 | 如果已经定义过如何推送到队列,还可以继续设置关闭/开启队列: 328 | ```php 329 | Sms::queue(true);//开启队列 330 | Sms::queue(false);//关闭队列 331 | ``` 332 | 333 | 获取队列启用情况: 334 | ```php 335 | $enable = Sms::queue(); 336 | //为true,表示当前启用了队列。 337 | //为false,表示当前关闭了队列。 338 | ``` 339 | 340 | ## API - 发送相关 341 | 342 | ### Sms::make() 343 | 344 | 生成发送短信的sms实例,并返回实例。 345 | ```php 346 | $sms = Sms::make(); 347 | 348 | //创建实例的同时设置短信内容: 349 | $sms = Sms::make('【签名】这是短信内容...'); 350 | 351 | //创建实例的同时设置短信模版: 352 | $sms = Sms::make('YunTongXun', 'your_temp_id'); 353 | //或 354 | $sms = Sms::make([ 355 | 'YunTongXun' => 'your_temp_id', 356 | 'SubMail' => 'your_temp_id', 357 | ... 358 | ]); 359 | ``` 360 | 361 | ### Sms::voice() 362 | 363 | 生成发送语音验证码的sms实例,并返回实例。 364 | ```php 365 | $sms = Sms::voice(); 366 | 367 | //创建实例的同时设置验证码 368 | $sms = Sms::voice($code); 369 | ``` 370 | 371 | > - 如果你使用`Luosimao`语音验证码,还需用在配置文件中`Luosimao`选项中设置`voiceApikey`。 372 | > - **语音文件ID**即是在服务商配置的语音文件的唯一编号,比如阿里大鱼[语音通知](http://open.taobao.com/doc2/apiDetail.htm?spm=a219a.7395905.0.0.oORhh9&apiId=25445)的`voice_code`。 373 | > - **模版语音**是另一种语音请求方式,它是通过模版ID和模版数据进行的语音请求,比如阿里大鱼的[文本转语音通知](http://open.taobao.com/doc2/apiDetail.htm?spm=a219a.7395905.0.0.f04PJ3&apiId=25444)。 374 | 375 | ### type($type) 376 | 377 | 设置实例类型,可选值有`Sms::TYPE_SMS`和`Sms::TYPE_VOICE`,返回实例对象。 378 | 379 | ### to($mobile) 380 | 381 | 设置发送给谁,并返回实例。 382 | ```php 383 | $sms->to('1828*******'); 384 | 385 | //兼容腾讯云 386 | $sms->to([86, '1828*******']) 387 | ``` 388 | 389 | ### template($agentName, $id) 390 | 391 | 指定代理器设置模版或批量设置,并返回实例。 392 | ```php 393 | //设置指定服务商的模板id 394 | $sms->template('YunTongXun', 'your_temp_id') 395 | ->template('SubMail', 'your_temp_id'); 396 | 397 | //一次性设置多个服务商的模板id 398 | $sms->template([ 399 | 'YunTongXun' => 'your_temp_id', 400 | 'SubMail' => 'your_temp_id', 401 | ... 402 | ]); 403 | ``` 404 | 405 | ### data($key, $value) 406 | 407 | 设置模板短信的模板数据,并返回实例对象。 408 | ```php 409 | //单个数据 410 | $sms->data('code', $code); 411 | 412 | //同时设置多个数据 413 | $sms->data([ 414 | 'code' => $code, 415 | 'minutes' => $minutes 416 | ]); 417 | ``` 418 | 419 | > 通过`template`和`data`方法的组合除了可以实现模版短信的数据填充,还可以实现模版语音的数据填充。 420 | 421 | ### content($text) 422 | 423 | 设置内容短信的内容,并返回实例对象。 424 | 425 | > 一些内置的代理器(如SmsBao、YunPian、Luosimao)使用的是内容短信(即直接发送短信内容),那么就需要为它们设置短信内容。 426 | 427 | ```php 428 | $sms->content('【签名】这是短信内容...'); 429 | ``` 430 | 431 | ### code($code) 432 | 433 | 设置语音验证码,并返回实例对象。 434 | 435 | ### file($agentName, $id) 436 | 437 | 设置语音文件,并返回实例对象。 438 | ```php 439 | $sms->file('Agent1', 'agent1_file_id') 440 | ->file('Agent2', 'agent2_file_id'); 441 | 442 | //或 443 | $sms->file([ 444 | 'Agent1' => 'agent1_file_id', 445 | 'Agent2' => 'agent2_fiile_id', 446 | ]); 447 | ``` 448 | 449 | ### params($agentName, $params) 450 | 451 | 直接设置参数到服务商提供的原生接口上,并返回实例对象。 452 | ```php 453 | $sms->params('Agent1', [ 454 | 'callbackUrl' => ..., 455 | 'userData' => ..., 456 | ]); 457 | 458 | //或 459 | $sms->params([ 460 | 'Agent1' => [ 461 | 'callbackUrl' => ..., 462 | 'userData' => ..., 463 | ], 464 | 'Agent2' => [ 465 | ... 466 | ], 467 | ]); 468 | ``` 469 | 470 | ### all([$key]) 471 | 472 | 获取Sms实例中的短信数据,不带参数时返回所有数据,其结构如下: 473 | ```php 474 | [ 475 | 'type' => ..., 476 | 'to' => ..., 477 | 'templates' => [...], 478 | 'data' => [...], // template data 479 | 'content' => ..., 480 | 'code' => ..., // voice code 481 | 'files' => [...], // voice files 482 | 'params' => [...], 483 | ] 484 | ``` 485 | 486 | ### agent($name) 487 | 488 | 临时设置发送时使用的代理器(不会影响备用代理器的正常使用),并返回实例,`$name`为代理器名称。 489 | ```php 490 | $sms->agent('SmsBao'); 491 | ``` 492 | > 通过该方法设置的代理器将获得绝对优先权,但只对当前短信实例有效。 493 | 494 | ### send() 495 | 496 | 请求发送短信/语音验证码。 497 | ```php 498 | //会遵循是否使用队列 499 | $result = $sms->send(); 500 | 501 | //忽略是否使用队列 502 | $result = $sms->send(true); 503 | ``` 504 | 505 | > `$result`数据结构请参看[task-balancer](https://github.com/toplan/task-balancer) 506 | 507 | # 自定义代理器 508 | 509 | - step 1 510 | 511 | 可将配置项(如果有用到)加入到`config/phpsms.php`中键为`agents`的数组里。 512 | 513 | ```php 514 | //example: 515 | 'Foo' => [ 516 | 'key' => 'your api key', 517 | ... 518 | ] 519 | ``` 520 | 521 | - step 2 522 | 523 | 新建一个继承`Toplan\PhpSms\Agent`抽象类的代理器类,建议代理器类名为`FooAgent`,建议命名空间为`Toplan\PhpSms`。 524 | 525 | > 如果类名不为`FooAgent`或者命名空间不为`Toplan\PhpSms`,在使用该代理器时则需要指定代理器类,详见[高级调度配置](#高级调度配置)。 526 | 527 | - step 3 528 | 529 | 实现相应的接口,可选的接口有: 530 | 531 | | 接口 | 说明 | 532 | | ------------- | :----------: | 533 | | ContentSms | 发送内容短信 | 534 | | TemplateSms | 发送模版短信 | 535 | | VoiceCode | 发送语音验证码 | 536 | | ContentVoice | 发送内容语音 | 537 | | TemplateVoice | 发送模版语音 | 538 | | FileVoice | 发送文件语音 | 539 | 540 | # 高级调度配置 541 | 542 | 代理器的高级调度配置可以通过配置文件(`config/phpsms.php`)中的`scheme`项目配置,也可以通过`scheme`静态方法设置。 543 | 值得注意的是,高级调度配置的值的数据结构是数组。 544 | 545 | ### 指定代理器类 546 | 547 | 如果你自定义了一个代理器,类名不为`FooAgent`或者命名空间不为`Toplan\PhpSms`, 548 | 那么你还可以在调度配置时指定你的代理器使用的类。 549 | 550 | * 配置方式: 551 | 552 | 通过配置值中`agentClass`键来指定类名。 553 | 554 | * 示例: 555 | ```php 556 | Sms::scheme('agentName', [ 557 | '10 backup', 558 | 'agentClass' => 'My\Namespace\MyAgentClass' 559 | ]); 560 | ``` 561 | 562 | ### 寄生代理器 563 | 564 | 如果你既不想使用内置的代理器,也不想创建文件写自定义代理器,那么寄生代理器或许是个好的选择, 565 | 无需定义代理器类,只需在调度配置时定义好发送短信和语音验证码的方式即可。 566 | 567 | * 配置方式: 568 | 569 | 可以配置的发送过程有: 570 | 571 | | 发送过程 | 参数列表 | 说明 | 572 | | ----------------- | :---------------------------: | :----------: | 573 | | sendContentSms | $agent, $to, $content | 发送内容短信 | 574 | | sendTemplateSms | $agent, $to, $tmpId, $tmpData | 发送模版短信 | 575 | | sendVoiceCode | $agent, $to, $code | 发送语音验证码 | 576 | | sendContentVoice | $agent, $to, $content | 发送内容语音 | 577 | | sendTemplateVoice | $agent, $to, $tmpId, $tmpData | 发送模版语音 | 578 | | sendFileVoice | $agent, $to, $fileId | 发送文件语音 | 579 | 580 | * 示例: 581 | ```php 582 | Sms::scheme([ 583 | 'agentName' => [ 584 | '20 backup', 585 | 'sendContentSms' => function($agent, $to, $content){ 586 | // 获取配置(如果设置了的话): 587 | $key = $agent->key; 588 | ... 589 | // 可使用的内置方法: 590 | $agent->curlGet($url, $params); //get 591 | $agent->curlPost($url, $params); //post 592 | ... 593 | // 更新发送结果: 594 | $agent->result(Agent::SUCCESS, true); 595 | $agent->result(Agent::INFO, 'some info'); 596 | $agent->result(Agent::CODE, 'your code'); 597 | }, 598 | 'sendVoiceCode' => function($agent, $to, $code){ 599 | // 发送语音验证码,同上 600 | } 601 | ] 602 | ]); 603 | ``` 604 | 605 | # Todo 606 | 607 | - [ ] 重新实现云通讯代理器,去掉`lib/CCPRestSmsSDK.php` 608 | - [ ] 重新实现云之讯代理器,去掉`lib/Ucpaas.php` 609 | - [ ] 升级云片接口到v2版本 610 | 611 | # License 612 | 613 | MIT 614 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "toplan/phpsms", 3 | "description": "Probably the most intelligent, elegant sms send library in php", 4 | "license": "MIT", 5 | "keywords": ["sms", "php", "phpsms", "sms library"], 6 | "authors": [ 7 | { 8 | "name": "toplan", 9 | "email": "toplan710@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.4.0", 14 | "toplan/task-balancer": "~0.5", 15 | "jeremeamia/SuperClosure": "~2.2" 16 | }, 17 | "require-dev": { 18 | "phpunit/phpunit": "~4.0" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Toplan\\PhpSms\\": [ 23 | "src/phpsms/", 24 | "src/phpsms/agents/", 25 | "src/phpsms/interfaces/" 26 | ], 27 | "Toplan\\PhpSms\\Facades\\": [ 28 | "src/phpsms/facades/" 29 | ] 30 | }, 31 | "classmap": [ 32 | "src/phpsms/lib/" 33 | ] 34 | }, 35 | "autoload-dev": { 36 | "classmap": [ 37 | "tests/" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/AgentTest.php 16 | 17 | 18 | ./tests/ProtectedTest.php 19 | 20 | 21 | ./tests/ConfigTest.php 22 | 23 | 24 | ./tests/SmsTest.php 25 | 26 | 27 | ./tests/SerializeTest.php 28 | 29 | 30 | 31 | 32 | ./src/phpsms/agents/Agent.php 33 | ./src/phpsms/agents/LogAgent.php 34 | ./src/phpsms/Sms.php 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/config/phpsms.php: -------------------------------------------------------------------------------- 1 | {value} 9 | * 10 | * Examples: 11 | * 'Log' => '10 backup' 12 | * 'SmsBao' => '100' 13 | * 'CustomAgent' => [ 14 | * '5 backup', 15 | * 'agentClass' => '/Namespace/ClassName' 16 | * ] 17 | * 18 | * Supported agents: 19 | * 'Log', 'YunPian', 'YunTongXun', 'SubMail', 'Luosimao', 20 | * 'Ucpaas', 'JuHe', 'Alidayu', 'SendCloud', 'SmsBao', 21 | * 'Qcloud', 'Aliyun' 22 | * 23 | */ 24 | 'scheme' => [ 25 | 'Log', 26 | ], 27 | 28 | /* 29 | * The configuration 30 | * ------------------------------------------------------------------- 31 | * 32 | * Expected the name of agent to be a string. 33 | * 34 | */ 35 | 'agents' => [ 36 | /* 37 | * ----------------------------------- 38 | * YunPian 39 | * 云片代理器 40 | * ----------------------------------- 41 | * website:http://www.yunpian.com 42 | * support content sms. 43 | */ 44 | 'YunPian' => [ 45 | //用户唯一标识,必须 46 | 'apikey' => 'your_api_key', 47 | ], 48 | 49 | /* 50 | * ----------------------------------- 51 | * YunTongXun 52 | * 云通讯代理器 53 | * ----------------------------------- 54 | * website:http://www.yuntongxun.com/ 55 | * support template sms. 56 | */ 57 | 'YunTongXun' => [ 58 | //主帐号 59 | 'accountSid' => 'your_account_sid', 60 | //主帐号令牌 61 | 'accountToken' => 'your_account_token', 62 | //应用Id 63 | 'appId' => 'your_app_id', 64 | //请求地址(不加协议前缀) 65 | 'serverIP' => 'app.cloopen.com', 66 | //请求端口 67 | 'serverPort' => '8883', 68 | //被叫号显 69 | 'displayNum' => null, 70 | //语音验证码播放次数 71 | 'playTimes' => 3, 72 | ], 73 | 74 | /* 75 | * ----------------------------------- 76 | * SubMail 77 | * ----------------------------------- 78 | * website:http://submail.cn/ 79 | * support template sms. 80 | */ 81 | 'SubMail' => [ 82 | 'appid' => 'your_app_id', 83 | 'signature' => 'your app key', 84 | ], 85 | 86 | /* 87 | * ----------------------------------- 88 | * luosimao 89 | * ----------------------------------- 90 | * website:http://luosimao.com 91 | * support content sms. 92 | */ 93 | 'Luosimao' => [ 94 | 'apikey' => 'your_api_key', 95 | 'voiceApikey' => 'your_voice_api_key', 96 | ], 97 | 98 | /* 99 | * ----------------------------------- 100 | * ucpaas 101 | * ----------------------------------- 102 | * website:http://ucpaas.com 103 | * support template sms. 104 | */ 105 | 'Ucpaas' => [ 106 | //主帐号,对应开官网发者主账号下的 ACCOUNT SID 107 | 'accountSid' => 'your_account_sid', 108 | //主帐号令牌,对应官网开发者主账号下的 AUTH TOKEN 109 | 'accountToken' => 'your_account_token', 110 | //应用Id,在官网应用列表中点击应用,对应应用详情中的APP ID 111 | //在开发调试的时候,可以使用官网自动为您分配的测试Demo的APP ID 112 | 'appId' => 'your_app_id', 113 | ], 114 | 115 | /* 116 | * ----------------------------------- 117 | * JuHe 118 | * 聚合数据 119 | * ----------------------------------- 120 | * website:https://www.juhe.cn 121 | * support template sms. 122 | */ 123 | 'JuHe' => [ 124 | //应用App Key 125 | 'key' => 'your_key', 126 | //语音验证码播放次数 127 | 'times' => 3, 128 | ], 129 | 130 | /* 131 | * ----------------------------------- 132 | * Alidayu 133 | * 阿里大鱼代理器 134 | * ----------------------------------- 135 | * website:http://www.alidayu.com 136 | * support template sms. 137 | */ 138 | 'Alidayu' => [ 139 | //请求地址 140 | 'sendUrl' => 'http://gw.api.taobao.com/router/rest', 141 | //淘宝开放平台中,对应阿里大鱼短信应用的App Key 142 | 'appKey' => 'your_app_key', 143 | //淘宝开放平台中,对应阿里大鱼短信应用的App Secret 144 | 'secretKey' => 'your_secret_key', 145 | //短信签名,传入的短信签名必须是在阿里大鱼“管理中心-短信签名管理”中的可用签名 146 | 'smsFreeSignName' => 'your_sms_free_sign_name', 147 | //被叫号显(用于语音通知),传入的显示号码必须是阿里大鱼“管理中心-号码管理”中申请或购买的号码 148 | 'calledShowNum' => null, 149 | ], 150 | 151 | /* 152 | * ----------------------------------- 153 | * SendCloud 154 | * ----------------------------------- 155 | * website: http://sendcloud.sohu.com/sms/ 156 | * support template sms. 157 | */ 158 | 'SendCloud' => [ 159 | 'smsUser' => 'your_SMS_USER', 160 | 'smsKey' => 'your_SMS_KEY', 161 | ], 162 | 163 | /* 164 | * ----------------------------------- 165 | * SmsBao 166 | * ----------------------------------- 167 | * website: http://www.smsbao.com 168 | * support content sms. 169 | */ 170 | 'SmsBao' => [ 171 | //注册账号 172 | 'username' => 'your_username', 173 | //账号密码(明文) 174 | 'password' => 'your_password', 175 | ], 176 | 177 | /* 178 | * ----------------------------------- 179 | * Qcloud 180 | * 腾讯云 181 | * ----------------------------------- 182 | * website:http://www.qcloud.com 183 | * support template sms. 184 | */ 185 | 'Qcloud' => [ 186 | 'appId' => 'your_app_id', 187 | 'appKey' => 'your_app_key', 188 | ], 189 | 190 | /* 191 | * ----------------------------------- 192 | * Aliyun 193 | * 阿里云 194 | * ----------------------------------- 195 | * website:https://www.aliyun.com/product/sms 196 | * support template sms. 197 | */ 198 | 'Aliyun' => [ 199 | 'accessKeyId' => 'your_access_key_id', 200 | 'accessKeySecret' => 'your_access_key_secret', 201 | 'signName' => 'your_sms_sign_name', 202 | 'regionId' => 'cn-shenzhen', 203 | ], 204 | ], 205 | ]; 206 | -------------------------------------------------------------------------------- /src/phpsms/PhpSmsException.php: -------------------------------------------------------------------------------- 1 | publishes([ 28 | __DIR__ . '/../config/phpsms.php' => $publishPath, 29 | ], 'config'); 30 | } 31 | 32 | /** 33 | * Register the service provider. 34 | */ 35 | public function register() 36 | { 37 | if ($this->app instanceof LumenApplication) { 38 | $this->app->configure('phpsms'); 39 | } 40 | $this->mergeConfigFrom(__DIR__ . '/../config/phpsms.php', 'phpsms'); 41 | 42 | $this->app->singleton('Toplan\\PhpSms\\Sms', function () { 43 | Sms::scheme(config('phpsms.scheme', [])); 44 | Sms::config(config('phpsms.agents', [])); 45 | 46 | return new Sms(false); 47 | }); 48 | } 49 | 50 | /** 51 | * Get the services provided by the provider. 52 | * 53 | * @return array 54 | */ 55 | public function provides() 56 | { 57 | return ['Toplan\\PhpSms\\Sms']; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/phpsms/Sms.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class Sms 14 | { 15 | const TYPE_SMS = 1; 16 | const TYPE_VOICE = 2; 17 | 18 | /** 19 | * Task instance. 20 | * 21 | * @var Task 22 | */ 23 | protected static $task = null; 24 | 25 | /** 26 | * Agent instances. 27 | * 28 | * @var Agent[] 29 | */ 30 | protected static $agents = []; 31 | 32 | /** 33 | * Dispatch scheme of agents. 34 | * 35 | * @var array 36 | */ 37 | protected static $scheme = []; 38 | 39 | /** 40 | * Configuration information of agents. 41 | * 42 | * @var array 43 | */ 44 | protected static $agentsConfig = []; 45 | 46 | /** 47 | * Whether to use the queue system. 48 | * 49 | * @var bool 50 | */ 51 | protected static $enableQueue = false; 52 | 53 | /** 54 | * How to use the queue system. 55 | * 56 | * @var \Closure 57 | */ 58 | protected static $howToUseQueue = null; 59 | 60 | /** 61 | * Available hooks. 62 | * 63 | * @var string[] 64 | */ 65 | protected static $availableHooks = [ 66 | 'beforeRun', 67 | 'beforeDriverRun', 68 | 'afterDriverRun', 69 | 'afterRun', 70 | ]; 71 | 72 | /** 73 | * Data container. 74 | * 75 | * @var array 76 | */ 77 | protected $smsData = [ 78 | 'type' => self::TYPE_SMS, 79 | 'to' => null, 80 | 'templates' => [], 81 | 'data' => [], 82 | 'content' => null, 83 | 'code' => null, 84 | 'files' => [], 85 | 'params' => [], 86 | ]; 87 | 88 | /** 89 | * The name of first agent. 90 | * 91 | * @var string 92 | */ 93 | protected $firstAgent = null; 94 | 95 | /** 96 | * Whether pushed to the queue system. 97 | * 98 | * @var bool 99 | */ 100 | protected $pushedToQueue = false; 101 | 102 | /** 103 | * State container. 104 | * 105 | * @var array 106 | */ 107 | protected $state = []; 108 | 109 | /** 110 | * Constructor 111 | * 112 | * @param bool $autoBoot 113 | */ 114 | public function __construct($autoBoot = true) 115 | { 116 | if ($autoBoot) { 117 | self::bootTask(); 118 | } 119 | } 120 | 121 | /** 122 | * Bootstrap the task. 123 | */ 124 | public static function bootTask() 125 | { 126 | if (!self::isTaskBooted()) { 127 | self::configure(); 128 | foreach (self::scheme() as $name => $scheme) { 129 | self::registerDriver($name, $scheme); 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * Is task has been booted. 136 | * 137 | * @return bool 138 | */ 139 | protected static function isTaskBooted() 140 | { 141 | return !empty(self::getTask()->drivers); 142 | } 143 | 144 | /** 145 | * Get the task instance. 146 | * 147 | * @return Task 148 | */ 149 | public static function getTask() 150 | { 151 | if (empty(self::$task)) { 152 | self::$task = new Task(); 153 | } 154 | 155 | return self::$task; 156 | } 157 | 158 | /** 159 | * Configure. 160 | * 161 | * @throws PhpSmsException 162 | */ 163 | protected static function configure() 164 | { 165 | $config = []; 166 | if (!count(self::scheme())) { 167 | self::initScheme($config); 168 | } 169 | $diff = array_diff_key(self::scheme(), self::$agentsConfig); 170 | self::initAgentsConfig(array_keys($diff), $config); 171 | if (!count(self::scheme())) { 172 | throw new PhpSmsException('Expected at least one agent in scheme.'); 173 | } 174 | } 175 | 176 | /** 177 | * Initialize the dispatch scheme. 178 | * 179 | * @param array $config 180 | */ 181 | protected static function initScheme(array &$config) 182 | { 183 | $config = empty($config) ? include __DIR__ . '/../config/phpsms.php' : $config; 184 | $scheme = isset($config['scheme']) ? $config['scheme'] : []; 185 | self::scheme($scheme); 186 | } 187 | 188 | /** 189 | * Initialize the configuration information. 190 | * 191 | * @param array $agents 192 | * @param array $config 193 | */ 194 | protected static function initAgentsConfig(array $agents, array &$config) 195 | { 196 | if (empty($agents)) { 197 | return; 198 | } 199 | $config = empty($config) ? include __DIR__ . '/../config/phpsms.php' : $config; 200 | $agentsConfig = isset($config['agents']) ? $config['agents'] : []; 201 | foreach ($agents as $name) { 202 | $agentConfig = isset($agentsConfig[$name]) ? $agentsConfig[$name] : []; 203 | self::config($name, $agentConfig); 204 | } 205 | } 206 | 207 | /** 208 | * register driver. 209 | * 210 | * @param string $name 211 | * @param string|array $scheme 212 | */ 213 | protected static function registerDriver($name, $scheme) 214 | { 215 | // parse higher-order scheme 216 | $settings = []; 217 | if (is_array($scheme)) { 218 | $settings = self::parseScheme($scheme); 219 | $scheme = $settings['scheme']; 220 | } 221 | // register 222 | self::getTask()->driver("$name $scheme")->work(function (Driver $driver) use ($settings) { 223 | $agent = self::getAgent($driver->name, $settings); 224 | extract($driver->getTaskData()); 225 | $template = isset($templates[$driver->name]) ? $templates[$driver->name] : null; 226 | $file = isset($files[$driver->name]) ? $files[$driver->name] : null; 227 | $params = isset($params[$driver->name]) ? $params[$driver->name] : []; 228 | if ($type === self::TYPE_VOICE) { 229 | $agent->sendVoice($to, $content, $template, $data, $code, $file, $params); 230 | } elseif ($type === self::TYPE_SMS) { 231 | $agent->sendSms($to, $content, $template, $data, $params); 232 | } 233 | $result = $agent->result(); 234 | if ($result['success']) { 235 | $driver->success(); 236 | } 237 | unset($result['success']); 238 | 239 | return $result; 240 | }); 241 | } 242 | 243 | /** 244 | * Parse the higher-order dispatch scheme. 245 | * 246 | * @param array $options 247 | * 248 | * @return array 249 | */ 250 | protected static function parseScheme(array $options) 251 | { 252 | $weight = Util::pullFromArray($options, 'weight'); 253 | $backup = Util::pullFromArray($options, 'backup') ? 'backup' : ''; 254 | $props = array_filter(array_values($options), function ($prop) { 255 | return is_numeric($prop) || is_string($prop); 256 | }); 257 | 258 | $options['scheme'] = implode(' ', $props) . " $weight $backup"; 259 | 260 | return $options; 261 | } 262 | 263 | /** 264 | * Get the agent instance by name. 265 | * 266 | * @param string $name 267 | * @param array $options 268 | * 269 | * @throws PhpSmsException 270 | * 271 | * @return Agent 272 | */ 273 | public static function getAgent($name, array $options = []) 274 | { 275 | if (!self::hasAgent($name)) { 276 | $scheme = self::scheme($name); 277 | $config = self::config($name); 278 | if (is_array($scheme) && empty($options)) { 279 | $options = self::parseScheme($scheme); 280 | } 281 | if (isset($options['scheme'])) { 282 | unset($options['scheme']); 283 | } 284 | $className = "Toplan\\PhpSms\\{$name}Agent"; 285 | if (isset($options['agentClass'])) { 286 | $className = $options['agentClass']; 287 | unset($options['agentClass']); 288 | } 289 | if (!empty($options)) { 290 | self::$agents[$name] = new ParasiticAgent($config, $options); 291 | } elseif (class_exists($className)) { 292 | self::$agents[$name] = new $className($config); 293 | } else { 294 | throw new PhpSmsException("Not support agent `$name`."); 295 | } 296 | } 297 | 298 | return self::$agents[$name]; 299 | } 300 | 301 | /** 302 | * Whether has the specified agent. 303 | * 304 | * @param string $name 305 | * 306 | * @return bool 307 | */ 308 | public static function hasAgent($name) 309 | { 310 | return isset(self::$agents[$name]); 311 | } 312 | 313 | /** 314 | * Set or get the dispatch scheme. 315 | * 316 | * @param string|array|null $name 317 | * @param string|array|bool|null $scheme 318 | * @param bool $override 319 | * 320 | * @return mixed 321 | */ 322 | public static function scheme($name = null, $scheme = null, $override = false) 323 | { 324 | if (is_array($name) && is_bool($scheme)) { 325 | $override = $scheme; 326 | } 327 | 328 | return Util::operateArray(self::$scheme, $name, $scheme, null, function ($key, $value) { 329 | if (is_string($key)) { 330 | self::modifyScheme($key, is_array($value) ? $value : "$value"); 331 | } elseif (is_int($key)) { 332 | self::modifyScheme($value, ''); 333 | } 334 | }, $override, function (array $origin) { 335 | if (self::isTaskBooted()) { 336 | foreach (array_keys($origin) as $name) { 337 | self::getTask()->removeDriver($name); 338 | } 339 | } 340 | }); 341 | } 342 | 343 | /** 344 | * Modify the dispatch scheme of agent. 345 | * 346 | * @param string $name 347 | * @param string|array $scheme 348 | * 349 | * @throws PhpSmsException 350 | */ 351 | protected static function modifyScheme($name, $scheme) 352 | { 353 | self::validateAgentName($name); 354 | self::$scheme[$name] = $scheme; 355 | if (self::isTaskBooted()) { 356 | $driver = self::getTask()->getDriver($name); 357 | if ($driver) { 358 | if (is_array($scheme)) { 359 | $higherOrderScheme = self::parseScheme($scheme); 360 | $scheme = $higherOrderScheme['scheme']; 361 | } 362 | $driver->reset($scheme); 363 | } else { 364 | self::registerDriver($name, $scheme); 365 | } 366 | } 367 | } 368 | 369 | /** 370 | * Set or get the configuration information. 371 | * 372 | * @param string|array|null $name 373 | * @param array|bool|null $config 374 | * @param bool $override 375 | * 376 | * @throws PhpSmsException 377 | * 378 | * @return array 379 | */ 380 | public static function config($name = null, $config = null, $override = false) 381 | { 382 | $overrideAll = (is_array($name) && is_bool($config)) ? $config : false; 383 | 384 | return Util::operateArray(self::$agentsConfig, $name, $config, [], function ($name, array $config) use ($override) { 385 | self::modifyConfig($name, $config, $override); 386 | }, $overrideAll, function (array $origin) { 387 | foreach (array_keys($origin) as $name) { 388 | if (self::hasAgent("$name")) { 389 | self::getAgent("$name")->config([], true); 390 | } 391 | } 392 | }); 393 | } 394 | 395 | /** 396 | * Modify the configuration information. 397 | * 398 | * @param string $name 399 | * @param array $config 400 | * @param bool $override 401 | * 402 | * @throws PhpSmsException 403 | */ 404 | protected static function modifyConfig($name, array $config, $override = false) 405 | { 406 | self::validateAgentName($name); 407 | if (!isset(self::$agentsConfig[$name])) { 408 | self::$agentsConfig[$name] = []; 409 | } 410 | $target = &self::$agentsConfig[$name]; 411 | if ($override) { 412 | $target = $config; 413 | } else { 414 | $target = array_merge($target, $config); 415 | } 416 | if (self::hasAgent($name)) { 417 | self::getAgent($name)->config($target); 418 | } 419 | } 420 | 421 | /** 422 | * Validate the name of agent. 423 | * 424 | * @param string $name 425 | * 426 | * @throws PhpSmsException 427 | */ 428 | protected static function validateAgentName($name) 429 | { 430 | if (empty($name) || !is_string($name) || preg_match('/^[0-9]+$/', $name)) { 431 | throw new PhpSmsException('Expected the name of agent to be a string which except the digital string.'); 432 | } 433 | } 434 | 435 | /** 436 | * Tear down scheme. 437 | */ 438 | public static function cleanScheme() 439 | { 440 | self::scheme([], true); 441 | } 442 | 443 | /** 444 | * Tear down config information. 445 | */ 446 | public static function cleanConfig() 447 | { 448 | self::config([], true); 449 | } 450 | 451 | /** 452 | * Create a instance for send sms. 453 | * 454 | * @param mixed $agentName 455 | * @param mixed $tempId 456 | * 457 | * @return Sms 458 | */ 459 | public static function make($agentName = null, $tempId = null) 460 | { 461 | $sms = new self(); 462 | $sms->type(self::TYPE_SMS); 463 | if (is_array($agentName)) { 464 | $sms->template($agentName); 465 | } elseif ($agentName && is_string($agentName)) { 466 | if ($tempId === null) { 467 | $sms->content($agentName); 468 | } elseif (is_string($tempId) || is_int($tempId)) { 469 | $sms->template($agentName, "$tempId"); 470 | } 471 | } 472 | 473 | return $sms; 474 | } 475 | 476 | /** 477 | * Create a instance for send voice. 478 | * 479 | * @param int|string|null $code 480 | * 481 | * @return Sms 482 | */ 483 | public static function voice($code = null) 484 | { 485 | $sms = new self(); 486 | $sms->type(self::TYPE_VOICE); 487 | $sms->code($code); 488 | 489 | return $sms; 490 | } 491 | 492 | /** 493 | * Set whether to use the queue system, 494 | * and define how to use it. 495 | * 496 | * @param bool|\Closure|null $enable 497 | * @param \Closure|null $handler 498 | * 499 | * @return bool 500 | */ 501 | public static function queue($enable = null, $handler = null) 502 | { 503 | if ($enable === null && $handler === null) { 504 | return self::$enableQueue; 505 | } 506 | if (is_callable($enable)) { 507 | $handler = $enable; 508 | $enable = true; 509 | } 510 | self::$enableQueue = (bool) $enable; 511 | if (is_callable($handler)) { 512 | self::$howToUseQueue = $handler; 513 | } 514 | 515 | return self::$enableQueue; 516 | } 517 | 518 | /** 519 | * Set the type of Sms instance. 520 | * 521 | * @param $type 522 | * 523 | * @throws PhpSmsException 524 | * 525 | * @return $this 526 | */ 527 | public function type($type) 528 | { 529 | if ($type !== self::TYPE_SMS && $type !== self::TYPE_VOICE) { 530 | throw new PhpSmsException('Expected the parameter equals to `Sms::TYPE_SMS` or `Sms::TYPE_VOICE`.'); 531 | } 532 | $this->smsData['type'] = $type; 533 | 534 | return $this; 535 | } 536 | 537 | /** 538 | * Set the recipient`s mobile number. 539 | * 540 | * @param string|array $mobile 541 | * 542 | * @return $this 543 | */ 544 | public function to($mobile) 545 | { 546 | if (is_string($mobile)) { 547 | $mobile = trim($mobile); 548 | } 549 | $this->smsData['to'] = $mobile; 550 | 551 | return $this; 552 | } 553 | 554 | /** 555 | * Set the sms content. 556 | * 557 | * @param string $content 558 | * 559 | * @return $this 560 | */ 561 | public function content($content) 562 | { 563 | $this->smsData['content'] = trim((string) $content); 564 | 565 | return $this; 566 | } 567 | 568 | /** 569 | * Set the template ids. 570 | * 571 | * @param mixed $name 572 | * @param mixed $tempId 573 | * 574 | * @return $this 575 | */ 576 | public function template($name, $tempId = null) 577 | { 578 | Util::operateArray($this->smsData['templates'], $name, $tempId); 579 | 580 | return $this; 581 | } 582 | 583 | /** 584 | * Set the template data. 585 | * 586 | * @param mixed $key 587 | * @param mixed $value 588 | * 589 | * @return $this 590 | */ 591 | public function data($key, $value = null) 592 | { 593 | Util::operateArray($this->smsData['data'], $key, $value); 594 | 595 | return $this; 596 | } 597 | 598 | /** 599 | * Set the voice code. 600 | * 601 | * @param string|int $code 602 | * 603 | * @return $this 604 | */ 605 | public function code($code) 606 | { 607 | $this->smsData['code'] = $code; 608 | 609 | return $this; 610 | } 611 | 612 | /** 613 | * Set voice file. 614 | * 615 | * @param string|array $name 616 | * @param string|int $id 617 | * 618 | * @return $this 619 | */ 620 | public function file($name, $id = null) 621 | { 622 | Util::operateArray($this->smsData['files'], $name, $id); 623 | 624 | return $this; 625 | } 626 | 627 | /** 628 | * Set params of agent. 629 | * 630 | * @param string|array $name 631 | * @param array|bool|null $params 632 | * @param bool $override 633 | * 634 | * @return $this 635 | */ 636 | public function params($name, $params = null, $override = false) 637 | { 638 | $overrideAll = (is_array($name) && is_bool($params)) ? $params : false; 639 | Util::operateArray($this->smsData['params'], $name, $params, [], function ($name, array $params) use ($override) { 640 | if (!isset($this->smsData['params'][$name])) { 641 | $this->smsData['params'][$name] = []; 642 | } 643 | $target = &$this->smsData['params'][$name]; 644 | if ($override) { 645 | $target = $params; 646 | } else { 647 | $target = array_merge($target, $params); 648 | } 649 | }, $overrideAll); 650 | 651 | return $this; 652 | } 653 | 654 | /** 655 | * Set the first agent. 656 | * 657 | * @param string $name 658 | * 659 | * @throws PhpSmsException 660 | * 661 | * @return $this 662 | */ 663 | public function agent($name) 664 | { 665 | if (!is_string($name) || empty($name)) { 666 | throw new PhpSmsException('Expected the parameter to be non-empty string.'); 667 | } 668 | $this->firstAgent = $name; 669 | 670 | return $this; 671 | } 672 | 673 | /** 674 | * Start send. 675 | * 676 | * If call with a `true` parameter, this system will immediately start request to send sms whatever whether to use the queue. 677 | * if the current instance has pushed to the queue, you can recall this method in queue system without any parameter, 678 | * so this mechanism in order to make you convenient to use this method in queue system. 679 | * 680 | * @param bool $immediately 681 | * 682 | * @return mixed 683 | */ 684 | public function send($immediately = false) 685 | { 686 | if (!self::$enableQueue || $this->pushedToQueue) { 687 | $immediately = true; 688 | } 689 | if ($immediately) { 690 | return self::$task->data($this->all())->run($this->firstAgent); 691 | } 692 | 693 | return $this->push(); 694 | } 695 | 696 | /** 697 | * Push to the queue system. 698 | * 699 | * @throws \Exception | PhpSmsException 700 | * 701 | * @return mixed 702 | */ 703 | public function push() 704 | { 705 | if (!is_callable(self::$howToUseQueue)) { 706 | throw new PhpSmsException('Expected define how to use the queue system by methods `queue`.'); 707 | } 708 | try { 709 | $this->pushedToQueue = true; 710 | 711 | return call_user_func_array(self::$howToUseQueue, [$this, $this->all()]); 712 | } catch (\Exception $e) { 713 | $this->pushedToQueue = false; 714 | throw $e; 715 | } 716 | } 717 | 718 | /** 719 | * Get all of the data. 720 | * 721 | * @param null|string $key 722 | * 723 | * @return mixed 724 | */ 725 | public function all($key = null) 726 | { 727 | if ($key !== null) { 728 | return isset($this->smsData[$key]) ? $this->smsData[$key] : null; 729 | } 730 | 731 | return $this->smsData; 732 | } 733 | 734 | /** 735 | * Define the static hook methods by overload static method. 736 | * 737 | * @param string $name 738 | * @param array $args 739 | * 740 | * @throws PhpSmsException 741 | */ 742 | public static function __callStatic($name, $args) 743 | { 744 | $name = $name === 'beforeSend' ? 'beforeRun' : $name; 745 | $name = $name === 'afterSend' ? 'afterRun' : $name; 746 | $name = $name === 'beforeAgentSend' ? 'beforeDriverRun' : $name; 747 | $name = $name === 'afterAgentSend' ? 'afterDriverRun' : $name; 748 | if (!in_array($name, self::$availableHooks)) { 749 | throw new PhpSmsException("Not found methods `$name`."); 750 | } 751 | $handler = $args[0]; 752 | $override = isset($args[1]) ? (bool) $args[1] : false; 753 | self::getTask()->hook($name, $handler, $override); 754 | } 755 | 756 | /** 757 | * Define the hook methods by overload method. 758 | * 759 | * @param string $name 760 | * @param array $args 761 | * 762 | * @throws PhpSmsException 763 | * @throws \Exception 764 | */ 765 | public function __call($name, $args) 766 | { 767 | try { 768 | $this->__callStatic($name, $args); 769 | } catch (\Exception $e) { 770 | throw $e; 771 | } 772 | } 773 | 774 | /** 775 | * Serialize magic method. 776 | * 777 | * @return array 778 | */ 779 | public function __sleep() 780 | { 781 | try { 782 | $this->state['scheme'] = self::toggleSerializeScheme(self::scheme()); 783 | $this->state['agentsConfig'] = self::config(); 784 | $this->state['handlers'] = self::serializeHandlers(); 785 | } catch (\Exception $e) { 786 | //swallow exception 787 | } 788 | 789 | return ['smsData', 'firstAgent', 'pushedToQueue', 'state']; 790 | } 791 | 792 | /** 793 | * Deserialize magic method. 794 | */ 795 | public function __wakeup() 796 | { 797 | if (empty($this->state)) { 798 | return; 799 | } 800 | self::$scheme = self::toggleSerializeScheme($this->state['scheme']); 801 | self::$agentsConfig = $this->state['agentsConfig']; 802 | self::reinstallHandlers($this->state['handlers']); 803 | } 804 | 805 | /** 806 | * Serialize or deserialize the scheme. 807 | * 808 | * @param array $scheme 809 | * 810 | * @return array 811 | */ 812 | protected static function toggleSerializeScheme(array $scheme) 813 | { 814 | foreach ($scheme as $name => &$options) { 815 | if (is_array($options)) { 816 | foreach (ParasiticAgent::methods() as $method) { 817 | self::toggleSerializeClosure($options, $method); 818 | } 819 | } 820 | } 821 | 822 | return $scheme; 823 | } 824 | 825 | /** 826 | * Serialize the hooks' handlers. 827 | * 828 | * @return array 829 | */ 830 | protected static function serializeHandlers() 831 | { 832 | $hooks = (array) self::getTask()->handlers; 833 | foreach ($hooks as &$handlers) { 834 | foreach (array_keys($handlers) as $key) { 835 | self::toggleSerializeClosure($handlers, $key); 836 | } 837 | } 838 | 839 | return $hooks; 840 | } 841 | 842 | /** 843 | * Reinstall hooks' handlers. 844 | * 845 | * @param array $handlers 846 | */ 847 | protected static function reinstallHandlers(array $handlers) 848 | { 849 | $serializer = Util::getClosureSerializer(); 850 | foreach ($handlers as $hookName => $serializedHandlers) { 851 | foreach ($serializedHandlers as $index => $handler) { 852 | if (is_string($handler)) { 853 | $handler = $serializer->unserialize($handler); 854 | } 855 | self::$hookName($handler, $index === 0); 856 | } 857 | } 858 | } 859 | 860 | /** 861 | * Serialize or deserialize the specified closure and then replace the original value. 862 | * 863 | * @param array $options 864 | * @param int|string $key 865 | */ 866 | protected static function toggleSerializeClosure(array &$options, $key) 867 | { 868 | if (!isset($options[$key])) { 869 | return; 870 | } 871 | $serializer = Util::getClosureSerializer(); 872 | if (is_callable($options[$key])) { 873 | $options[$key] = (string) $serializer->serialize($options[$key]); 874 | } elseif (is_string($options[$key])) { 875 | $options[$key] = $serializer->unserialize($options[$key]); 876 | } 877 | } 878 | } 879 | -------------------------------------------------------------------------------- /src/phpsms/Util.php: -------------------------------------------------------------------------------- 1 | $v) { 25 | self::operateArray($array, $k, $v, $default, $setter, false, null, true); 26 | } 27 | } elseif (is_callable($setter)) { 28 | call_user_func_array($setter, [$key, $value]); 29 | } else { 30 | $array[$key] = $value; 31 | } 32 | 33 | return $array; 34 | } 35 | 36 | public static function pullFromArray(array &$options, $key) 37 | { 38 | $value = null; 39 | if (!isset($options[$key])) { 40 | return $value; 41 | } 42 | $value = $options[$key]; 43 | unset($options[$key]); 44 | 45 | return $value; 46 | } 47 | 48 | public static function getClosureSerializer() 49 | { 50 | if (empty(self::$closureSerializer)) { 51 | self::$closureSerializer = new Serializer(); 52 | } 53 | 54 | return self::$closureSerializer; 55 | } 56 | 57 | public static function formatMobiles($target) 58 | { 59 | if (!is_array($target)) { 60 | return [$target]; 61 | } 62 | $list = []; 63 | $nation = $number = null; 64 | $count = count($target); 65 | if ($count === 2) { 66 | $firstItem = $target[0]; 67 | if (is_int($firstItem) && $firstItem > 0 && $firstItem <= 9999) { 68 | $nation = $firstItem; 69 | $number = $target[1]; 70 | } 71 | if (is_string($firstItem) && strlen($firstItem = trim($firstItem)) <= 4) { 72 | $nation = $firstItem; 73 | $number = $target[1]; 74 | } 75 | } 76 | if (!is_null($nation)) { 77 | return [compact('nation', 'number')]; 78 | } 79 | foreach ($target as $childTarget) { 80 | $childList = self::formatMobiles($childTarget); 81 | foreach ($childList as $childListItem) { 82 | array_push($list, $childListItem); 83 | } 84 | } 85 | 86 | return $list; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/phpsms/agents/Agent.php: -------------------------------------------------------------------------------- 1 | reset(); 40 | $this->config($config); 41 | } 42 | 43 | /** 44 | * Reset states. 45 | */ 46 | public function reset() 47 | { 48 | $this->result = [ 49 | self::SUCCESS => false, 50 | self::INFO => null, 51 | self::CODE => 0, 52 | ]; 53 | } 54 | 55 | /** 56 | * Get or set the configuration information. 57 | * 58 | * @param string|array $key 59 | * @param mixed $value 60 | * @param bool $override 61 | * 62 | * @return mixed 63 | */ 64 | public function config($key = null, $value = null, $override = false) 65 | { 66 | if (is_array($key) && is_bool($value)) { 67 | $override = $value; 68 | } 69 | 70 | return Util::operateArray($this->config, $key, $value, null, null, $override); 71 | } 72 | 73 | /** 74 | * Get or set the custom params. 75 | * 76 | * @param string|array $key 77 | * @param mixed $value 78 | * @param bool $override 79 | * 80 | * @return mixed 81 | */ 82 | public function params($key = null, $value = null, $override = false) 83 | { 84 | if (is_array($key) && is_bool($value)) { 85 | $override = $value; 86 | } 87 | 88 | return Util::operateArray($this->params, $key, $value, null, null, $override); 89 | } 90 | 91 | /** 92 | * SMS send process. 93 | * 94 | * @param $to 95 | * @param $content 96 | * @param $tempId 97 | * @param array $data 98 | * @param array $params 99 | */ 100 | public function sendSms($to, $content = null, $tempId = null, array $data = [], array $params = []) 101 | { 102 | $this->reset(); 103 | $this->params($params, true); 104 | $to = $this->formatMobile(Util::formatMobiles($to)); 105 | 106 | if ($tempId && $this instanceof TemplateSms) { 107 | $this->sendTemplateSms($to, $tempId, $data); 108 | } elseif ($content && $this instanceof ContentSms) { 109 | $this->sendContentSms($to, $content); 110 | } 111 | } 112 | 113 | /** 114 | * Voice send process. 115 | * 116 | * @param $to 117 | * @param $content 118 | * @param $tempId 119 | * @param array $data 120 | * @param $code 121 | * @param $fileId 122 | * @param array $params 123 | */ 124 | public function sendVoice($to, $content = null, $tempId = null, array $data = [], $code = null, $fileId = null, array $params = []) 125 | { 126 | $this->reset(); 127 | $this->params($params, true); 128 | $to = $this->formatMobile(Util::formatMobiles($to)); 129 | 130 | if ($tempId && $this instanceof TemplateVoice) { 131 | $this->sendTemplateVoice($to, $tempId, $data); 132 | } elseif ($fileId && $this instanceof FileVoice) { 133 | $this->sendFileVoice($to, $fileId); 134 | } elseif ($code && $this instanceof VoiceCode) { 135 | $this->sendVoiceCode($to, $code); 136 | } elseif ($content && $this instanceof ContentVoice) { 137 | $this->sendContentVoice($to, $content); 138 | } 139 | } 140 | 141 | /** 142 | * Formatting a mobile number from the list of mobile numbers. 143 | * 144 | * @param array $list 145 | * 146 | * @return string 147 | */ 148 | public function formatMobile(array $list) 149 | { 150 | return implode(',', array_unique(array_map(function ($value) { 151 | return is_array($value) ? "{$value['number']}" : $value; 152 | }, $list))); 153 | } 154 | 155 | /** 156 | * @codeCoverageIgnore 157 | * 158 | * @param $url 159 | * @param array $params 160 | * @param array $opts 161 | * 162 | * @return array 163 | */ 164 | public function curlPost($url, array $params = [], array $opts = []) 165 | { 166 | $options = [ 167 | CURLOPT_POST => true, 168 | CURLOPT_URL => $url, 169 | ]; 170 | foreach ($opts as $key => $value) { 171 | if ($key !== CURLOPT_POST && $key !== CURLOPT_URL) { 172 | $options[$key] = $value; 173 | } 174 | } 175 | if (!array_key_exists(CURLOPT_POSTFIELDS, $options)) { 176 | $options[CURLOPT_POSTFIELDS] = $this->params($params); 177 | } 178 | 179 | return self::curl($options); 180 | } 181 | 182 | /** 183 | * @codeCoverageIgnore 184 | * 185 | * @param $url 186 | * @param array $params 187 | * @param array $opts 188 | * 189 | * @return array 190 | */ 191 | public function curlGet($url, array $params = [], array $opts = []) 192 | { 193 | $params = $this->params($params); 194 | $queryStr = http_build_query($params); 195 | $opts[CURLOPT_POST] = false; 196 | $opts[CURLOPT_URL] = $queryStr ? "$url?$queryStr" : $url; 197 | 198 | return self::curl($opts); 199 | } 200 | 201 | /** 202 | * cURl 203 | * 204 | * @codeCoverageIgnore 205 | * 206 | * @param array $opts curl options 207 | * 208 | * @return array ['request', 'response'] 209 | * request: Whether request success. 210 | * response: Response data. 211 | */ 212 | public static function curl(array $opts = []) 213 | { 214 | $ch = curl_init(); 215 | curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 216 | curl_setopt($ch, CURLOPT_HEADER, false); 217 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); 218 | curl_setopt($ch, CURLOPT_TIMEOUT, 30); 219 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 220 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); 221 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 222 | foreach ($opts as $key => $value) { 223 | curl_setopt($ch, $key, $value); 224 | } 225 | 226 | $response = curl_exec($ch); 227 | $request = $response !== false; 228 | if (!$request) { 229 | $response = curl_getinfo($ch); 230 | } 231 | curl_close($ch); 232 | 233 | return compact('request', 'response'); 234 | } 235 | 236 | /** 237 | * Get or set the result data. 238 | * 239 | * @param $name 240 | * @param $value 241 | * 242 | * @return mixed 243 | */ 244 | public function result($name = null, $value = null) 245 | { 246 | if ($name === null) { 247 | return $this->result; 248 | } 249 | if (array_key_exists($name, $this->result)) { 250 | if ($value === null) { 251 | return $this->result[$name]; 252 | } 253 | $this->result[$name] = $value; 254 | } 255 | } 256 | 257 | /** 258 | * Overload object properties. 259 | * 260 | * @param $name 261 | * 262 | * @return mixed 263 | */ 264 | public function __get($name) 265 | { 266 | return $this->config($name); 267 | } 268 | 269 | /** 270 | * When using isset() or empty() on inaccessible object properties, 271 | * the __isset() overloading method will be called. 272 | * 273 | * @param $name 274 | * 275 | * @return bool 276 | */ 277 | public function __isset($name) 278 | { 279 | return isset($this->config[$name]); 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/phpsms/agents/AlidayuAgent.php: -------------------------------------------------------------------------------- 1 | 'alibaba.aliqin.fc.sms.num.send', 27 | 'sms_type' => 'normal', 28 | 'sms_free_sign_name' => $this->smsFreeSignName, 29 | 'sms_param' => $this->getTempDataString($tempData), 30 | 'rec_num' => $to, 31 | 'sms_template_code' => $tempId, 32 | ]; 33 | $this->request($params); 34 | } 35 | 36 | /** 37 | * Template voice send process. 38 | * 39 | * @param string|array $to 40 | * @param int|string $tempId 41 | * @param array $tempData 42 | */ 43 | public function sendTemplateVoice($to, $tempId, array $tempData) 44 | { 45 | $params = [ 46 | 'called_num' => $to, 47 | 'called_show_num' => $this->calledShowNum, 48 | 'method' => 'alibaba.aliqin.fc.tts.num.singlecall', 49 | 'tts_code' => $tempId, 50 | 'tts_param' => $this->getTempDataString($tempData), 51 | ]; 52 | $this->request($params); 53 | } 54 | 55 | /** 56 | * Voice code send process. 57 | * 58 | * @param string|array $to 59 | * @param int|string $code 60 | */ 61 | public function sendVoiceCode($to, $code) 62 | { 63 | $params = [ 64 | 'called_num' => $to, 65 | 'called_show_num' => $this->calledShowNum, 66 | 'method' => 'alibaba.aliqin.fc.voice.num.singlecall', 67 | 'voice_code' => $code, 68 | ]; 69 | $this->request($params); 70 | } 71 | 72 | protected function request(array $params) 73 | { 74 | $params = $this->createParams($params); 75 | $result = $this->curlPost($this->sendUrl, [], [ 76 | CURLOPT_POSTFIELDS => http_build_query($params), 77 | ]); 78 | $this->setResult($result, $this->genResponseName($params['method'])); 79 | } 80 | 81 | protected function createParams(array $params) 82 | { 83 | $params = array_merge([ 84 | 'app_key' => $this->appKey, 85 | 'v' => '2.0', 86 | 'format' => 'json', 87 | 'sign_method' => 'md5', 88 | 'timestamp' => date('Y-m-d H:i:s'), 89 | ], $params); 90 | $params['sign'] = $this->genSign($params); 91 | 92 | return $this->params($params); 93 | } 94 | 95 | protected function genSign($params) 96 | { 97 | ksort($params); 98 | $stringToBeSigned = $this->secretKey; 99 | foreach ($params as $k => $v) { 100 | if (is_string($v) && '@' !== substr($v, 0, 1)) { 101 | $stringToBeSigned .= "$k$v"; 102 | } 103 | } 104 | unset($k, $v); 105 | $stringToBeSigned .= $this->secretKey; 106 | 107 | return strtoupper(md5($stringToBeSigned)); 108 | } 109 | 110 | protected function setResult($result, $callbackName) 111 | { 112 | if ($result['request']) { 113 | $result = json_decode($result['response'], true); 114 | if (isset($result[$callbackName]['result'])) { 115 | $result = $result[$callbackName]['result']; 116 | $this->result(Agent::SUCCESS, (bool) $result['success']); 117 | $this->result(Agent::INFO, json_encode($result)); 118 | $this->result(Agent::CODE, $result['err_code']); 119 | } elseif (isset($result['error_response'])) { 120 | $error = $result['error_response']; 121 | $this->result(Agent::INFO, json_encode($error)); 122 | $this->result(Agent::CODE, $error['code']); 123 | } 124 | } else { 125 | $this->result(Agent::INFO, 'request failed'); 126 | } 127 | } 128 | 129 | protected function genResponseName($method) 130 | { 131 | return str_replace('.', '_', $method) . '_response'; 132 | } 133 | 134 | protected function getTempDataString(array $data) 135 | { 136 | $data = array_map(function ($value) { 137 | return (string) $value; 138 | }, $data); 139 | 140 | return json_encode($data); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/phpsms/agents/AliyunAgent.php: -------------------------------------------------------------------------------- 1 | 'SendSms', 21 | 'SignName' => $this->signName, 22 | 'TemplateParam' => $this->getTempDataString($data), 23 | 'PhoneNumbers' => $to, 24 | 'TemplateCode' => $tempId, 25 | ]; 26 | $this->request($params); 27 | } 28 | 29 | protected function request(array $params) 30 | { 31 | $params = $this->createParams($params); 32 | $result = $this->curlPost(self::$sendUrl, [], [ 33 | CURLOPT_POSTFIELDS => http_build_query($params), 34 | ]); 35 | $this->setResult($result); 36 | } 37 | 38 | protected function createParams(array $params) 39 | { 40 | $params = array_merge([ 41 | 'RegionId' => $this->regionId ?: 'cn-shenzhen', 42 | 'Format' => 'JSON', 43 | 'Version' => '2017-05-25', 44 | 'AccessKeyId' => $this->accessKeyId, 45 | 'SignatureMethod' => 'HMAC-SHA1', 46 | 'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'), 47 | 'SignatureVersion' => '1.0', 48 | 'SignatureNonce' => uniqid(), 49 | ], $params); 50 | $params['Signature'] = $this->computeSignature($params); 51 | 52 | return $this->params($params); 53 | } 54 | 55 | private function computeSignature($parameters) 56 | { 57 | ksort($parameters); 58 | $canonicalizedQueryString = ''; 59 | foreach ($parameters as $key => $value) { 60 | $canonicalizedQueryString .= '&' . $this->percentEncode($key) . '=' . $this->percentEncode($value); 61 | } 62 | $stringToSign = 'POST&%2F&' . $this->percentencode(substr($canonicalizedQueryString, 1)); 63 | 64 | return base64_encode(hash_hmac('sha1', $stringToSign, $this->accessKeySecret . '&', true)); 65 | } 66 | 67 | protected function percentEncode($str) 68 | { 69 | $res = urlencode($str); 70 | $res = preg_replace('/\+/', '%20', $res); 71 | $res = preg_replace('/\*/', '%2A', $res); 72 | $res = preg_replace('/%7E/', '~', $res); 73 | 74 | return $res; 75 | } 76 | 77 | protected function setResult($result) 78 | { 79 | if ($result['request']) { 80 | $this->result(Agent::INFO, $result['response']); 81 | $result = json_decode($result['response'], true); 82 | $this->result(Agent::CODE, $result['Code']); 83 | if ($result['Code'] === 'OK') { 84 | $this->result(Agent::SUCCESS, true); 85 | } 86 | } else { 87 | $this->result(Agent::INFO, 'request failed'); 88 | } 89 | } 90 | 91 | protected function getTempDataString(array $data) 92 | { 93 | $data = array_map(function ($value) { 94 | return (string) $value; 95 | }, $data); 96 | 97 | return json_encode($data); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/phpsms/agents/JuHeAgent.php: -------------------------------------------------------------------------------- 1 | $value) { 18 | if (preg_match('/[#&=]/', $value)) { 19 | $value = urlencode($value); 20 | } 21 | $split = !$tplValue ? '' : '&'; 22 | $tplValue .= "$split#$key#=$value"; 23 | } 24 | $params = [ 25 | 'key' => $this->key, 26 | 'mobile' => $to, 27 | 'tpl_id' => $tempId, 28 | 'tpl_value' => urlencode($tplValue), 29 | 'dtype' => 'json', 30 | ]; 31 | $result = $this->curlGet($sendUrl, $params); 32 | $this->setResult($result); 33 | } 34 | 35 | public function sendVoiceCode($to, $code) 36 | { 37 | $url = 'http://op.juhe.cn/yuntongxun/voice'; 38 | $params = [ 39 | 'valicode' => $code, 40 | 'to' => $to, 41 | 'playtimes' => $this->times ?: 3, 42 | 'key' => $this->key, 43 | 'dtype' => 'json', 44 | ]; 45 | $result = $this->curlGet($url, $params); 46 | $this->setResult($result); 47 | } 48 | 49 | protected function setResult($result) 50 | { 51 | if ($result['request']) { 52 | $this->result(Agent::INFO, $result['response']); 53 | $result = json_decode($result['response'], true); 54 | $this->result(Agent::SUCCESS, $result['error_code'] === 0); 55 | $this->result(Agent::CODE, $result['error_code']); 56 | } else { 57 | $this->result(Agent::INFO, 'request failed'); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/phpsms/agents/LogAgent.php: -------------------------------------------------------------------------------- 1 | result(Agent::SUCCESS, true); 13 | $this->result(Agent::INFO, 'send content sms success'); 14 | } 15 | 16 | public function sendTemplateSms($to, $tempId, array $data) 17 | { 18 | $this->result(Agent::SUCCESS, true); 19 | $this->result(Agent::INFO, 'send template sms success'); 20 | } 21 | 22 | public function sendVoiceCode($to, $code) 23 | { 24 | $this->result(Agent::SUCCESS, true); 25 | $this->result(Agent::INFO, 'send voice verify success'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/phpsms/agents/LuosimaoAgent.php: -------------------------------------------------------------------------------- 1 | curlPost(self::$smsUrl, [ 25 | 'mobile' => $to, 26 | 'message' => $content, 27 | ], [ 28 | CURLOPT_HTTPAUTH => CURLAUTH_BASIC, 29 | CURLOPT_USERPWD => "api:key-{$this->apikey}", 30 | ]); 31 | $this->setResult($result); 32 | } 33 | 34 | public function sendVoiceCode($to, $code) 35 | { 36 | $result = $this->curlPost(self::$voiceCodeUrl, [ 37 | 'mobile' => $to, 38 | 'code' => $code, 39 | ], [ 40 | CURLOPT_HTTPAUTH => CURLAUTH_BASIC, 41 | CURLOPT_USERPWD => "api:key-{$this->voiceApikey}", 42 | ]); 43 | $this->setResult($result); 44 | } 45 | 46 | protected function setResult($result) 47 | { 48 | if ($result['request']) { 49 | $this->result(Agent::INFO, $result['response']); 50 | $result = json_decode($result['response'], true); 51 | $this->result(Agent::SUCCESS, $result['error'] === 0); 52 | $this->result(Agent::CODE, $result['error']); 53 | } else { 54 | $this->result(Agent::INFO, 'request failed'); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/phpsms/agents/ParasiticAgent.php: -------------------------------------------------------------------------------- 1 | handlers = $handlers; 20 | } 21 | 22 | /** 23 | * Content SMS send process. 24 | * 25 | * @param string|array $to 26 | * @param string $content 27 | */ 28 | public function sendContentSms($to, $content) 29 | { 30 | $this->handle(__FUNCTION__, func_get_args()); 31 | } 32 | 33 | /** 34 | * Content voice send process. 35 | * 36 | * @param string|array $to 37 | * @param string $content 38 | */ 39 | public function sendContentVoice($to, $content) 40 | { 41 | $this->handle(__FUNCTION__, func_get_args()); 42 | } 43 | 44 | /** 45 | * File voice send process. 46 | * 47 | * @param string|array $to 48 | * @param int|string $fileId 49 | */ 50 | public function sendFileVoice($to, $fileId) 51 | { 52 | $this->handle(__FUNCTION__, func_get_args()); 53 | } 54 | 55 | /** 56 | * Template SMS send process. 57 | * 58 | * @param string|array $to 59 | * @param int|string $tempId 60 | * @param array $tempData 61 | */ 62 | public function sendTemplateSms($to, $tempId, array $tempData) 63 | { 64 | $this->handle(__FUNCTION__, func_get_args()); 65 | } 66 | 67 | /** 68 | * Template voice send process. 69 | * 70 | * @param string|array $to 71 | * @param int|string $tempId 72 | * @param array $tempData 73 | */ 74 | public function sendTemplateVoice($to, $tempId, array $tempData) 75 | { 76 | $this->handle(__FUNCTION__, func_get_args()); 77 | } 78 | 79 | /** 80 | * Voice code send process. 81 | * 82 | * @param string|array $to 83 | * @param int|string $code 84 | */ 85 | public function sendVoiceCode($to, $code) 86 | { 87 | $this->handle(__FUNCTION__, func_get_args()); 88 | } 89 | 90 | /** 91 | * Handle send process by closure. 92 | * 93 | * @param $name 94 | * @param array $args 95 | */ 96 | protected function handle($name, array $args = []) 97 | { 98 | if (isset($this->handlers[$name]) && is_callable($this->handlers[$name])) { 99 | array_unshift($args, $this); 100 | call_user_func_array($this->handlers[$name], $args); 101 | } 102 | } 103 | 104 | /** 105 | * Get methods name which inherit from interfaces. 106 | * 107 | * @return array 108 | */ 109 | public static function methods() 110 | { 111 | if (!is_array(self::$methods)) { 112 | self::$methods = []; 113 | $interfaces = class_implements('Toplan\\PhpSms\\ParasiticAgent'); 114 | foreach ($interfaces as $interface) { 115 | self::$methods = array_merge(self::$methods, get_class_methods($interface)); 116 | } 117 | } 118 | 119 | return self::$methods; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/phpsms/agents/QcloudAgent.php: -------------------------------------------------------------------------------- 1 | $value['nation'], 23 | 'mobile' => $value['number'], 24 | ]; 25 | }, array_filter($list, function ($value) { 26 | return is_array($value); 27 | })); 28 | 29 | return count($list) === 1 ? array_pop($list) : array_values($list); 30 | } 31 | 32 | public function sendContentSms($to, $content) 33 | { 34 | $params = [ 35 | 'type' => 0, // 0:普通短信 1:营销短信 36 | 'msg' => $content, 37 | 'tel' => $to, 38 | 'time' => time(), 39 | ]; 40 | $this->random = $this->getRandom(); 41 | $sendUrl = "{$this->sendSms}?sdkappid={$this->appId}&random={$this->random}"; 42 | $this->request($sendUrl, $params); 43 | } 44 | 45 | public function sendTemplateSms($to, $tempId, array $data) 46 | { 47 | $params = [ 48 | 'tel' => $to, 49 | 'tpl_id' => $tempId, 50 | 'params' => array_values($data), 51 | 'time' => time(), 52 | ]; 53 | $this->random = $this->getRandom(); 54 | $sendUrl = "{$this->sendSms}?sdkappid={$this->appId}&random={$this->random}"; 55 | $this->request($sendUrl, $params); 56 | } 57 | 58 | public function sendVoiceCode($to, $code) 59 | { 60 | $params = [ 61 | 'tel' => $to, 62 | 'msg' => $code, 63 | ]; 64 | $sendUrl = "{$this->sendVoiceCode}?sdkappid={$this->appId}&random={$this->random}"; 65 | $this->request($sendUrl, $params); 66 | } 67 | 68 | public function sendContentVoice($to, $content) 69 | { 70 | $params = [ 71 | 'tel' => $to, 72 | 'prompttype' => 2, 73 | 'promptfile' => $content, 74 | ]; 75 | $sendUrl = "{$this->sendVoicePrompt}?sdkappid={$this->appId}&random={$this->random}"; 76 | $this->request($sendUrl, $params); 77 | } 78 | 79 | protected function request($sendUrl, array $params) 80 | { 81 | $params['sig'] = $this->genSign($params); 82 | $params = $this->params($params); 83 | $result = $this->curlPost($sendUrl, [], [ 84 | CURLOPT_POSTFIELDS => json_encode($params), 85 | ]); 86 | $this->setResult($result); 87 | } 88 | 89 | protected function genSign($params) 90 | { 91 | $mobileStr = null; 92 | if (array_key_exists('mobile', $params['tel'])) { 93 | $mobileStr = $params['tel']['mobile']; 94 | } else { 95 | $mobileStr = implode(',', array_map(function ($value) { 96 | return $value['mobile']; 97 | }, $params['tel'])); 98 | } 99 | $signature = "appkey={$this->appKey}&random={$this->random}&time={$params['time']}&mobile={$mobileStr}"; 100 | 101 | return hash('sha256', $signature, false); 102 | } 103 | 104 | protected function setResult($result) 105 | { 106 | if ($result['request']) { 107 | $this->result(Agent::INFO, $result['response']); 108 | $result = json_decode($result['response'], true); 109 | if (isset($result['result'])) { 110 | $this->result(Agent::SUCCESS, $result['result'] === 0); 111 | $this->result(Agent::CODE, $result['result']); 112 | } elseif (isset($result['ErrorCode'])) { 113 | $this->result(Agent::CODE, $result['ErrorCode']); 114 | $this->result(Agent::INFO, $result['ErrorInfo']); 115 | } 116 | } else { 117 | $this->result(Agent::INFO, 'request failed'); 118 | } 119 | } 120 | 121 | protected function getRandom() 122 | { 123 | return rand(100000, 999999); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/phpsms/agents/SendCloudAgent.php: -------------------------------------------------------------------------------- 1 | 0, 17 | 'vars' => $this->getTempDataString($data), 18 | 'phone' => $to, 19 | 'templateId' => $tempId, 20 | ]; 21 | $this->request('http://sendcloud.sohu.com/smsapi/send', $params); 22 | } 23 | 24 | public function sendVoiceCode($to, $code) 25 | { 26 | $params = [ 27 | 'phone' => $to, 28 | 'code' => $code, 29 | ]; 30 | $this->request('http://sendcloud.sohu.com/smsapi/sendVoice', $params); 31 | } 32 | 33 | protected function request($sendUrl, array $params) 34 | { 35 | $params['smsUser'] = $this->smsUser; 36 | $params['signature'] = $this->genSign($params); 37 | $result = $this->curlPost($sendUrl, $params); 38 | $this->setResult($result); 39 | } 40 | 41 | protected function genSign($params) 42 | { 43 | ksort($params); 44 | $stringToBeSigned = ''; 45 | foreach ($params as $k => $v) { 46 | $stringToBeSigned .= $k . '=' . $v . '&'; 47 | } 48 | $stringToBeSigned = trim($stringToBeSigned, '&'); 49 | $stringToBeSigned = $this->smsKey . '&' . $stringToBeSigned . '&' . $this->smsKey; 50 | 51 | return md5($stringToBeSigned); 52 | } 53 | 54 | protected function setResult($result) 55 | { 56 | if ($result['request']) { 57 | $this->result(Agent::INFO, $result['response']); 58 | $result = json_decode($result['response'], true); 59 | if (isset($result['result'])) { 60 | $this->result(Agent::SUCCESS, (bool) $result['result']); 61 | $this->result(Agent::CODE, $result['statusCode']); 62 | } 63 | } else { 64 | $this->result(Agent::INFO, 'request failed'); 65 | } 66 | } 67 | 68 | protected function getTempDataString(array $data) 69 | { 70 | return json_encode(array_map('strval', $data)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/phpsms/agents/SmsBaoAgent.php: -------------------------------------------------------------------------------- 1 | '发送成功', 15 | '-1' => '参数不全', 16 | '30' => '密码错误', 17 | '40' => '账号不存在', 18 | '41' => '余额不足', 19 | '42' => '帐户已过期', 20 | '43' => 'IP地址限制', 21 | '50' => '内容含有敏感词', 22 | '51' => '手机号码不正确', 23 | ]; 24 | 25 | public function sendContentSms($to, $content) 26 | { 27 | $url = 'http://api.smsbao.com/sms'; 28 | $params = [ 29 | 'u' => $this->username, 30 | 'p' => md5($this->password), 31 | 'm' => $to, 32 | 'c' => $content, 33 | ]; 34 | $result = $this->curlGet($url, $params); 35 | $this->setResult($result); 36 | } 37 | 38 | public function sendVoiceCode($to, $code) 39 | { 40 | $url = 'http://api.smsbao.com/voice'; 41 | $params = [ 42 | 'u' => $this->username, 43 | 'p' => md5($this->password), 44 | 'm' => $to, 45 | 'c' => $code, 46 | ]; 47 | $result = $this->curlGet($url, $params); 48 | $this->setResult($result); 49 | } 50 | 51 | protected function setResult($result) 52 | { 53 | if ($result['request']) { 54 | $result = $result['response']; 55 | $msg = array_key_exists($result, $this->resultArr) ? $this->resultArr[$result] : 'unknown error'; 56 | $this->result(Agent::INFO, json_encode(['code' => $result, 'msg' => $msg])); 57 | $this->result(Agent::SUCCESS, $result === '0'); 58 | $this->result(Agent::CODE, $result); 59 | } else { 60 | $this->result(Agent::INFO, 'request failed'); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/phpsms/agents/SubMailAgent.php: -------------------------------------------------------------------------------- 1 | $this->appid, 18 | 'project' => $tempId, 19 | 'to' => $to, 20 | 'signature' => $this->signature, 21 | 'vars' => json_encode($data), 22 | ]; 23 | $result = $this->curlPost($url, $params); 24 | $this->setResult($result); 25 | } 26 | 27 | public function sendVoiceCode($to, $code) 28 | { 29 | $url = 'https://api.mysubmail.com/voice/verify.json'; 30 | $params = [ 31 | 'appid' => $this->appid, 32 | 'to' => $to, 33 | 'code' => $code, 34 | 'signature' => $this->signature, 35 | ]; 36 | $result = $this->curlPost($url, $params); 37 | $this->setResult($result); 38 | } 39 | 40 | protected function setResult($result) 41 | { 42 | if ($result['request']) { 43 | $this->result(Agent::INFO, $result['response']); 44 | $result = json_decode($result['response'], true); 45 | if ($result['status'] === 'success') { 46 | $this->result(Agent::SUCCESS, true); 47 | } else { 48 | $this->result(Agent::INFO, $result['msg']); 49 | $this->result(Agent::CODE, $result['code']); 50 | } 51 | } else { 52 | $this->result(Agent::INFO, 'request failed'); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/phpsms/agents/UcpaasAgent.php: -------------------------------------------------------------------------------- 1 | ucpass()->templateSMS($this->appId, $to, $tempId, implode(',', $data)); 17 | $this->setResult($response); 18 | } 19 | 20 | public function sendVoiceCode($to, $code) 21 | { 22 | $response = $this->ucpass()->voiceCode($this->appId, $code, $to); 23 | $this->result($response); 24 | } 25 | 26 | protected function ucpass() 27 | { 28 | return new \Ucpaas([ 29 | 'accountsid' => $this->accountSid, 30 | 'token' => $this->accountToken, 31 | ]); 32 | } 33 | 34 | protected function setResult($result) 35 | { 36 | $result = json_decode($result); 37 | if (!$result) { 38 | return $this->result(Agent::INFO, 'request failed'); 39 | } 40 | $this->result(Agent::SUCCESS, $result->resp->respCode === '000000'); 41 | $this->result(Agent::CODE, $result->resp->respCode); 42 | $this->result(Agent::INFO, json_encode($result->resp)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/phpsms/agents/YunPianAgent.php: -------------------------------------------------------------------------------- 1 | params([ 21 | 'apikey' => $this->apikey, 22 | 'mobile' => $to, 23 | 'text' => $content, 24 | ]); 25 | $result = $this->curlPost($url, [], [ 26 | CURLOPT_HTTPHEADER => $this->headers, 27 | CURLOPT_POSTFIELDS => http_build_query($params), 28 | ]); 29 | $this->setResult($result); 30 | } 31 | 32 | public function sendVoiceCode($to, $code) 33 | { 34 | $url = 'https://voice.yunpian.com/v1/voice/send.json'; 35 | $params = $this->params([ 36 | 'apikey' => $this->apikey, 37 | 'mobile' => $to, 38 | 'code' => $code, 39 | ]); 40 | $result = $this->curlPost($url, [], [ 41 | CURLOPT_HTTPHEADER => $this->headers, 42 | CURLOPT_POSTFIELDS => http_build_query($params), 43 | ]); 44 | $this->setResult($result); 45 | } 46 | 47 | protected function setResult($result) 48 | { 49 | if ($result['request']) { 50 | $this->result(Agent::INFO, $result['response']); 51 | $result = json_decode($result['response'], true); 52 | $this->result(Agent::SUCCESS, $result['code'] === 0); 53 | $this->result(Agent::CODE, $result['code']); 54 | } else { 55 | $this->result(Agent::INFO, 'request failed'); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/phpsms/agents/YunTongXunAgent.php: -------------------------------------------------------------------------------- 1 | rest()->sendTemplateSMS($to, $data, $tempId); 24 | $this->setResult($result); 25 | } 26 | 27 | public function sendVoiceCode($to, $code) 28 | { 29 | $playTimes = intval($this->playTimes ?: 3); 30 | $displayNum = $this->displayNum ?: null; 31 | $lang = $this->params('lang') ?: 'zh'; 32 | $respUrl = $this->params('respUrl'); 33 | $userData = $this->params('userData'); 34 | $welcomePrompt = $this->params('welcomePrompt'); 35 | $result = $this->rest()->voiceVerify($code, $playTimes, $to, $displayNum, $respUrl, $lang, $userData, $welcomePrompt); 36 | $this->setResult($result); 37 | } 38 | 39 | protected function rest() 40 | { 41 | $rest = new REST($this->serverIP, $this->serverPort, '2013-12-26', 'json'); 42 | $rest->setAccount($this->accountSid, $this->accountToken); 43 | $rest->setAppId($this->appId); 44 | 45 | return $rest; 46 | } 47 | 48 | protected function setResult($result) 49 | { 50 | if (!$result) { 51 | return; 52 | } 53 | $code = $info = (string) $result->statusCode; 54 | $success = $code === '000000'; 55 | if (isset($result->statusMsg)) { 56 | $info = (string) $result->statusMsg; 57 | } elseif (isset($result->TemplateSMS)) { 58 | $info = 'smsSid:' . $result->TemplateSMS->smsMessageSid; 59 | } elseif (isset($result->VoiceVerify)) { 60 | $info = 'callSid:' . $result->VoiceVerify->callSid; 61 | } 62 | $this->result(Agent::SUCCESS, $success); 63 | $this->result(Agent::CODE, $code); 64 | $this->result(Agent::INFO, $info); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/phpsms/facades/Sms.php: -------------------------------------------------------------------------------- 1 | Batch = date('YmdHis'); 30 | $this->ServerIP = $ServerIP; 31 | $this->ServerPort = $ServerPort; 32 | $this->SoftVersion = $SoftVersion; 33 | if (in_array($BodyType, ['xml', 'json'])) { 34 | $this->BodyType = $BodyType; 35 | } 36 | } 37 | 38 | /** 39 | * 设置主帐号 40 | * 41 | * @param string $AccountSid 主帐号 42 | * @param string $AccountToken 主帐号Token 43 | */ 44 | public function setAccount($AccountSid, $AccountToken) 45 | { 46 | $this->AccountSid = $AccountSid; 47 | $this->AccountToken = $AccountToken; 48 | } 49 | 50 | /** 51 | * 设置应用ID 52 | * 53 | * @param string $AppId 应用ID 54 | */ 55 | public function setAppId($AppId) 56 | { 57 | $this->AppId = $AppId; 58 | } 59 | 60 | /** 61 | * 发起HTTPS请求 62 | * 63 | * @param string $url 64 | * @param mixed $data 65 | * @param mixed $header 66 | * @param mixed $post 67 | * 68 | * @return mixed 69 | */ 70 | public function curl_post($url, $data, $header, $post = 1) 71 | { 72 | //初始化curl 73 | $ch = curl_init(); 74 | //参数设置 75 | $res = curl_setopt($ch, CURLOPT_URL, $url); 76 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 77 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 78 | curl_setopt($ch, CURLOPT_HEADER, 0); 79 | curl_setopt($ch, CURLOPT_POST, $post); 80 | if ($post) { 81 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 82 | } 83 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 84 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 85 | $result = curl_exec($ch); 86 | //连接失败 87 | if ($result === false) { 88 | if ($this->BodyType === 'json') { 89 | $result = '{"statusCode":"172001","statusMsg":"网络错误"}'; 90 | } else { 91 | $result = '172001网络错误'; 92 | } 93 | } 94 | curl_close($ch); 95 | 96 | return $result; 97 | } 98 | 99 | /** 100 | * 发送模板短信 101 | * 102 | * @param string $to 103 | * 短信接收彿手机号码集合,用英文逗号分开 104 | * @param array $datas 105 | * 内容数据 106 | * @param mixed $tempId 107 | * 模板Id 108 | * 109 | * @return mixed 110 | */ 111 | public function sendTemplateSMS($to, $datas, $tempId) 112 | { 113 | //主帐号鉴权信息验证,对必选参数进行判空。 114 | $auth = $this->accAuth(); 115 | if ($auth !== true) { 116 | return $auth; 117 | } 118 | // 拼接请求包体 119 | if ($this->BodyType === 'json') { 120 | $data = ''; 121 | for ($i = 0; $i < count($datas); $i++) { 122 | $data = $data . "'" . $datas[$i] . "',"; 123 | } 124 | $body = "{'to':'$to','templateId':'$tempId','appId':'$this->AppId','datas':[" . $data . ']}'; 125 | } else { 126 | $data = ''; 127 | for ($i = 0; $i < count($datas); $i++) { 128 | $data = $data . '' . $datas[$i] . ''; 129 | } 130 | $body = " 131 | $to 132 | $this->AppId 133 | $tempId 134 | " . $data . ' 135 | '; 136 | } 137 | // 大写的sig参数 138 | $sig = strtoupper(md5($this->AccountSid . $this->AccountToken . $this->Batch)); 139 | // 生成请求URL 140 | $url = "https://$this->ServerIP:$this->ServerPort/$this->SoftVersion/Accounts/$this->AccountSid/SMS/TemplateSMS?sig=$sig"; 141 | // 生成授权:主帐户Id + 英文冒号 + 时间戳。 142 | $authen = base64_encode($this->AccountSid . ':' . $this->Batch); 143 | // 生成包头 144 | $header = array("Accept:application/$this->BodyType", "Content-Type:application/$this->BodyType;charset=utf-8", "Authorization:$authen"); 145 | // 发送请求 146 | $result = $this->curl_post($url, $body, $header); 147 | if ($this->BodyType === 'json') {//JSON格式 148 | $datas = json_decode($result); 149 | } else { //xml格式 150 | $datas = simplexml_load_string(trim($result, " \t\n\r")); 151 | } 152 | // 重新装填数据 153 | if (isset($datas->templateSMS)) { 154 | $datas->TemplateSMS = $datas->templateSMS; 155 | } 156 | 157 | return $datas; 158 | } 159 | 160 | /** 161 | * 语音验证码 162 | * 163 | * @param mixed $verifyCode 验证码内容,为数字和英文字母,不区分大小写,长度4-8位 164 | * @param mixed $playTimes 播放次数,1-3次 165 | * @param mixed $to 接收号码 166 | * @param mixed $displayNum 显示的主叫号码 167 | * @param mixed $respUrl 语音验证码状态通知回调地址,云通讯平台将向该Url地址发送呼叫结果通知 168 | * @param mixed $lang 语言类型 169 | * @param mixed $userData 第三方私有数据 170 | * @param mixed $welcomePrompt 欢迎提示音,在播放验证码语音前播放此内容(语音文件格式为wav) 171 | * @param mixed $playVerifyCode 语音验证码的内容全部播放此节点下的全部语音文件 172 | * 173 | * @return mixed 174 | */ 175 | public function voiceVerify($verifyCode, $playTimes, $to, $displayNum = null, $respUrl = null, $lang = 'zh', $userData = null, $welcomePrompt = null, $playVerifyCode = null) 176 | { 177 | //主帐号鉴权信息验证,对必选参数进行判空。 178 | $auth = $this->accAuth(); 179 | if ($auth !== true) { 180 | return $auth; 181 | } 182 | // 拼接请求包体 183 | if ($this->BodyType === 'json') { 184 | $body = "{'appId':'$this->AppId','verifyCode':'$verifyCode','playTimes':'$playTimes','to':'$to','respUrl':'$respUrl','displayNum':'$displayNum', 185 | 'lang':'$lang','userData':'$userData','welcomePrompt':'$welcomePrompt','playVerifyCode':'$playVerifyCode'}"; 186 | } else { 187 | $body = " 188 | $this->AppId 189 | $verifyCode 190 | $playTimes 191 | $to 192 | $respUrl 193 | $displayNum 194 | $lang 195 | $userData 196 | $welcomePrompt 197 | $playVerifyCode 198 | "; 199 | } 200 | // 大写的sig参数 201 | $sig = strtoupper(md5($this->AccountSid . $this->AccountToken . $this->Batch)); 202 | // 生成请求URL 203 | $url = "https://$this->ServerIP:$this->ServerPort/$this->SoftVersion/Accounts/$this->AccountSid/Calls/VoiceVerify?sig=$sig"; 204 | // 生成授权:主帐户Id + 英文冒号 + 时间戳。 205 | $authen = base64_encode($this->AccountSid . ':' . $this->Batch); 206 | // 生成包头 207 | $header = array("Accept:application/$this->BodyType", "Content-Type:application/$this->BodyType;charset=utf-8", "Authorization:$authen"); 208 | // 发送请求 209 | $result = $this->curl_post($url, $body, $header); 210 | if ($this->BodyType === 'json') {//JSON格式 211 | $datas = json_decode($result); 212 | } else { //xml格式 213 | $datas = simplexml_load_string(trim($result, " \t\n\r")); 214 | } 215 | 216 | return $datas; 217 | } 218 | 219 | /** 220 | * 主帐号鉴权 221 | * 222 | * @return mixed 223 | */ 224 | public function accAuth() 225 | { 226 | if ($this->ServerIP === '') { 227 | $data = new stdClass(); 228 | $data->statusCode = '172004'; 229 | $data->statusMsg = 'IP为空'; 230 | 231 | return $data; 232 | } 233 | if ($this->ServerPort <= 0) { 234 | $data = new stdClass(); 235 | $data->statusCode = '172005'; 236 | $data->statusMsg = '端口错误(小于等于0)'; 237 | 238 | return $data; 239 | } 240 | if ($this->SoftVersion === '') { 241 | $data = new stdClass(); 242 | $data->statusCode = '172013'; 243 | $data->statusMsg = '版本号为空'; 244 | 245 | return $data; 246 | } 247 | if ($this->AccountSid === '') { 248 | $data = new stdClass(); 249 | $data->statusCode = '172006'; 250 | $data->statusMsg = '主帐号为空'; 251 | 252 | return $data; 253 | } 254 | if ($this->AccountToken === '') { 255 | $data = new stdClass(); 256 | $data->statusCode = '172007'; 257 | $data->statusMsg = '主帐号令牌为空'; 258 | 259 | return $data; 260 | } 261 | if ($this->AppId === '') { 262 | $data = new stdClass(); 263 | $data->statusCode = '172012'; 264 | $data->statusMsg = '应用ID为空'; 265 | 266 | return $data; 267 | } 268 | 269 | return true; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/phpsms/lib/Ucpaas.php: -------------------------------------------------------------------------------- 1 | accountSid = isset($options['accountsid']) ? $options['accountsid'] : ''; 49 | $this->token = isset($options['token']) ? $options['token'] : ''; 50 | $this->timestamp = date('YmdHis'); 51 | } else { 52 | throw new Exception('非法参数'); 53 | } 54 | } 55 | 56 | /** 57 | * @return string 58 | * 包头验证信息,使用Base64编码(账户Id:时间戳) 59 | */ 60 | private function getAuthorization() 61 | { 62 | $data = $this->accountSid . ':' . $this->timestamp; 63 | 64 | return trim(base64_encode($data)); 65 | } 66 | 67 | /** 68 | * @return string 69 | * 验证参数,URL后必须带有sig参数,sig= MD5(账户Id + 账户授权令牌 + 时间戳,共32位)(注:转成大写) 70 | */ 71 | private function getSigParameter() 72 | { 73 | $sig = $this->accountSid . $this->token . $this->timestamp; 74 | 75 | return strtoupper(md5($sig)); 76 | } 77 | 78 | /** 79 | * @param string $url 80 | * @param string|null $body 81 | * @param string $type 82 | * @param string $method 83 | * 84 | * @return mixed|string 85 | */ 86 | private function getResult($url, $body, $type, $method) 87 | { 88 | $data = $this->connection($url, $body, $type, $method); 89 | if (isset($data) && !empty($data)) { 90 | $result = $data; 91 | } else { 92 | $result = '没有返回数据'; 93 | } 94 | 95 | return $result; 96 | } 97 | 98 | /** 99 | * @param string $url 100 | * @param string $type 101 | * @param string|null $body 102 | * @param string $method 103 | * 104 | * @return mixed|string 105 | */ 106 | private function connection($url, $body, $type, $method) 107 | { 108 | if ($type === 'json') { 109 | $mine = 'application/json'; 110 | } else { 111 | $mine = 'application/xml'; 112 | } 113 | if (function_exists('curl_init')) { 114 | $header = array( 115 | 'Accept:' . $mine, 116 | 'Content-Type:' . $mine . ';charset=utf-8', 117 | 'Authorization:' . $this->getAuthorization(), 118 | ); 119 | $ch = curl_init($url); 120 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 121 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 122 | if ($method === 'post') { 123 | curl_setopt($ch, CURLOPT_POST, 1); 124 | curl_setopt($ch, CURLOPT_POSTFIELDS, $body); 125 | } 126 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 127 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 128 | $result = curl_exec($ch); 129 | curl_close($ch); 130 | } else { 131 | $opts = array(); 132 | $opts['http'] = array(); 133 | $headers = array( 134 | 'method' => strtoupper($method), 135 | ); 136 | $headers[] = 'Accept:' . $mine; 137 | $headers['header'] = array(); 138 | $headers['header'][] = 'Authorization: ' . $this->getAuthorization(); 139 | $headers['header'][] = 'Content-Type:' . $mine . ';charset=utf-8'; 140 | 141 | if (!empty($body)) { 142 | $headers['header'][] = 'Content-Length:' . strlen($body); 143 | $headers['content'] = $body; 144 | } 145 | 146 | $opts['http'] = $headers; 147 | $result = file_get_contents($url, false, stream_context_create($opts)); 148 | } 149 | 150 | return $result; 151 | } 152 | 153 | /** 154 | * @param $appId 155 | * @param $fromClient 156 | * @param string $to 157 | * @param null $fromSerNum 158 | * @param null $toSerNum 159 | * @param string $type 160 | * 161 | * @throws Exception 162 | * 163 | * @return mixed|string 164 | * @links http://www.ucpaas.com/page/doc/doc_rest3-1.jsp 165 | */ 166 | public function callBack($appId, $fromClient, $to, $fromSerNum = null, $toSerNum = null, $type = 'json') 167 | { 168 | $url = self::BaseUrl . self::SoftVersion . '/Accounts/' . $this->accountSid . '/Calls/callBack?sig=' . $this->getSigParameter(); 169 | if ($type === 'json') { 170 | $body_json = array('callback' => array( 171 | 'appId' => $appId, 172 | 'fromClient' => $fromClient, 173 | 'fromSerNum' => $fromSerNum, 174 | 'to' => $to, 175 | 'toSerNum' => $toSerNum, 176 | )); 177 | $body = json_encode($body_json); 178 | } elseif ($type === 'xml') { 179 | $body_xml = ' 180 | 181 | ' . $fromClient . ' 182 | ' . $fromSerNum . ' 183 | ' . $to . ' 184 | ' . $toSerNum . ' 185 | ' . $appId . ' 186 | '; 187 | $body = trim($body_xml); 188 | } else { 189 | throw new Exception('只能json或xml,默认为json'); 190 | } 191 | $data = $this->getResult($url, $body, $type, 'post'); 192 | 193 | return $data; 194 | } 195 | 196 | /** 197 | * @param $appId 198 | * @param $verifyCode 199 | * @param $to 200 | * @param string $type 201 | * 202 | * @throws Exception 203 | * 204 | * @return mixed|string 205 | * @links http://www.ucpaas.com/page/doc/doc_rest3-2.jsp 206 | */ 207 | public function voiceCode($appId, $verifyCode, $to, $type = 'json') 208 | { 209 | $url = self::BaseUrl . self::SoftVersion . '/Accounts/' . $this->accountSid . '/Calls/voiceCode?sig=' . $this->getSigParameter(); 210 | if ($type === 'json') { 211 | $body_json = array('voiceCode' => array( 212 | 'appId' => $appId, 213 | 'verifyCode' => $verifyCode, 214 | 'to' => $to, 215 | )); 216 | $body = json_encode($body_json); 217 | } elseif ($type === 'xml') { 218 | $body_xml = ' 219 | 220 | ' . $verifyCode . ' 221 | ' . $to . ' 222 | ' . $appId . ' 223 | '; 224 | $body = trim($body_xml); 225 | } else { 226 | throw new Exception('只能json或xml,默认为json'); 227 | } 228 | $data = $this->getResult($url, $body, $type, 'post'); 229 | 230 | return $data; 231 | } 232 | 233 | /** 234 | * @param $appId 235 | * @param $to 236 | * @param $templateId 237 | * @param null $param 238 | * @param string $type 239 | * 240 | * @throws Exception 241 | * 242 | * @return mixed|string 243 | * @links http://www.ucpaas.com/page/doc/doc_rest4-1.jsp 244 | */ 245 | public function templateSMS($appId, $to, $templateId, $param = null, $type = 'json') 246 | { 247 | $url = self::BaseUrl . self::SoftVersion . '/Accounts/' . $this->accountSid . '/Messages/templateSMS?sig=' . $this->getSigParameter(); 248 | if ($type === 'json') { 249 | $body_json = array('templateSMS' => array( 250 | 'appId' => $appId, 251 | 'templateId' => $templateId, 252 | 'to' => $to, 253 | 'param' => $param, 254 | )); 255 | $body = json_encode($body_json); 256 | } elseif ($type === 'xml') { 257 | $body_xml = ' 258 | 259 | ' . $templateId . ' 260 | ' . $to . ' 261 | ' . $param . ' 262 | ' . $appId . ' 263 | '; 264 | $body = trim($body_xml); 265 | } else { 266 | throw new Exception('只能json或xml,默认为json'); 267 | } 268 | $data = $this->getResult($url, $body, $type, 'post'); 269 | 270 | return $data; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /tests/AgentTest.php: -------------------------------------------------------------------------------- 1 | 'value', 15 | 'key2' => 'value2', 16 | ]; 17 | $this->agent = new LogAgent($config); 18 | } 19 | 20 | public function testResultOriginValue() 21 | { 22 | $r = $this->agent->result(); 23 | $this->assertArrayHasKey('success', $r); 24 | $this->assertArrayHasKey('info', $r); 25 | $this->assertArrayHasKey('code', $r); 26 | } 27 | 28 | public function testSetAndGetResult() 29 | { 30 | $this->agent->result('success', true); 31 | $this->agent->result('info', 'info'); 32 | $this->agent->result('code', 'code'); 33 | 34 | $r = $this->agent->result(); 35 | $code = $this->agent->result('code'); 36 | $null = $this->agent->result('undefined'); 37 | 38 | $this->assertTrue($r['success']); 39 | $this->assertEquals('info', $r['info']); 40 | $this->assertEquals('code', $r['code']); 41 | $this->assertEquals('code', $code); 42 | $this->assertNull($null); 43 | } 44 | 45 | public function testGetConfig() 46 | { 47 | //get config value 48 | $value = $this->agent->__get('key'); 49 | $value2 = $this->agent->key2; 50 | $this->assertEquals('value', $value); 51 | $this->assertEquals('value2', $value2); 52 | 53 | //get not define 54 | $value = $this->agent->notdefine; 55 | $this->assertNull($value); 56 | $this->assertFalse(isset($this->agent->notdefile)); 57 | $this->assertTrue(empty($this->agent->notdefile)); 58 | } 59 | 60 | public function testSendTemplateSms() 61 | { 62 | $this->agent->sendSms('18280111111', null, 'template_id', []); 63 | $r = $this->agent->result(); 64 | $this->assertTrue($r['success']); 65 | $this->assertEquals('send template sms success', $r['info']); 66 | } 67 | 68 | public function testSendContentSms() 69 | { 70 | $this->agent->sendSms('18280111111', 'content', 0, []); 71 | $r = $this->agent->result(); 72 | $this->assertTrue($r['success']); 73 | $this->assertEquals('send content sms success', $r['info']); 74 | } 75 | 76 | public function testSendVoice() 77 | { 78 | $this->agent->sendVoice('18280111111', null, 0, [], '1111'); 79 | $r = $this->agent->result(); 80 | $this->assertTrue($r['success']); 81 | } 82 | 83 | public function testParasitic() 84 | { 85 | $parasiticAgent = new ParasiticAgent([], [ 86 | 'sendContentSms' => function ($agent, $to, $content) { 87 | $agent->result(Agent::INFO, $content); 88 | $agent->result(Agent::CODE, $to); 89 | $agent->result(Agent::SUCCESS, true); 90 | }, 91 | 'sendVoiceCode' => function ($agent, $to, $code) { 92 | $agent->result(Agent::INFO, 'parasitic_voice_verify'); 93 | $agent->result(Agent::CODE, $code); 94 | }, 95 | ]); 96 | $parasiticAgent->sendSms('18280111111', 'parasitic_sms_content'); 97 | $this->assertEquals('parasitic_sms_content', $parasiticAgent->result('info')); 98 | $this->assertEquals('18280111111', $parasiticAgent->result('code')); 99 | 100 | $parasiticAgent->sendVoice('18280111111', null, null, [], '2222'); 101 | $this->assertEquals('parasitic_voice_verify', $parasiticAgent->result('info')); 102 | $this->assertEquals('2222', $parasiticAgent->result('code')); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /tests/ConfigTest.php: -------------------------------------------------------------------------------- 1 | assertCount(0, Sms::scheme()); 11 | Sms::cleanConfig(); 12 | $this->assertCount(0, Sms::config()); 13 | } 14 | 15 | public function testAddEnableAgent() 16 | { 17 | Sms::scheme(['Log']); 18 | $this->assertCount(1, Sms::scheme()); 19 | 20 | Sms::scheme('Log', '80 backup'); 21 | $this->assertCount(1, Sms::scheme()); 22 | $this->assertEquals('80 backup', Sms::scheme('Log')); 23 | 24 | Sms::scheme('Luosimao', 'backup'); 25 | $this->assertCount(2, Sms::scheme()); 26 | 27 | Sms::scheme([ 28 | 'Luosimao' => '0 backup', 29 | 'YunPian' => '0', 30 | ]); 31 | $this->assertCount(3, Sms::scheme()); 32 | $this->assertEquals('0', Sms::scheme('YunPian')); 33 | } 34 | 35 | public function testAddAgentConfig() 36 | { 37 | Sms::config('Log', []); 38 | $this->assertCount(1, Sms::config()); 39 | $this->assertCount(0, Sms::config('Log')); 40 | 41 | Sms::config('Luosimao', [ 42 | 'apikey' => '123', 43 | ]); 44 | $this->assertCount(2, Sms::config()); 45 | $this->assertArrayHasKey('apikey', Sms::config('Luosimao')); 46 | 47 | Sms::config([ 48 | 'Luosimao' => [ 49 | 'apikey' => '123', 50 | ], 51 | 'YunPian' => [ 52 | 'apikey' => '123', 53 | ], 54 | ]); 55 | $this->assertCount(3, Sms::config()); 56 | } 57 | 58 | public function testUpdateAgentConfig() 59 | { 60 | $agent = Sms::getAgent('Luosimao'); 61 | $this->assertEquals('123', $agent->apikey); 62 | 63 | Sms::config('Luosimao', [ 64 | 'apikey' => '12345', 65 | 'data' => 'hello world', 66 | ]); 67 | 68 | $this->assertEquals('12345', $agent->apikey); 69 | $this->assertEquals('hello world', $agent->data); 70 | 71 | Sms::cleanConfig(); 72 | $this->assertEquals(null, $agent->apikey); 73 | $this->assertEquals(null, $agent->data); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/ProtectedTest.php: -------------------------------------------------------------------------------- 1 | getMethod($name); 12 | $method->setAccessible(true); 13 | 14 | return $method; 15 | } 16 | 17 | public function testConfiguration() 18 | { 19 | $method = self::getPrivateMethod('configure'); 20 | $obj = new Sms(false); 21 | $method->invokeArgs($obj, []); 22 | $config = include __DIR__ . '/../src/config/phpsms.php'; 23 | $this->assertCount(count($config['scheme']), Sms::scheme()); 24 | $this->assertCount(count($config['scheme']), Sms::config()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/SerializeTest.php: -------------------------------------------------------------------------------- 1 | function ($agent) { 17 | $agent->result(Agent::SUCCESS, true); 18 | $agent->result(Agent::INFO, 'some_info'); 19 | }, 20 | ]); 21 | Sms::beforeSend(function () { 22 | print_r('[_before_send_]'); 23 | }, true); 24 | Sms::afterSend(function () { 25 | print_r('[_after_send_]'); 26 | }, true); 27 | self::$sms = Sms::make()->to('18280354...')->content('content...'); 28 | } 29 | 30 | public function testSerialize() 31 | { 32 | Sms::afterAgentSend(function ($task, $data) { 33 | $this->assertEquals('TestAgent', $data['driver']); 34 | $this->assertEquals('some_info', $data['result']['info']); 35 | }); 36 | 37 | $serialized = serialize(self::$sms); 38 | 39 | Sms::cleanScheme(); 40 | $this->assertEmpty(Sms::scheme()); 41 | 42 | $sms = unserialize($serialized); 43 | 44 | $this->assertArrayHasKey('TestAgent', Sms::scheme()); 45 | $this->expectOutputString('[_before_send_][_after_send_]'); 46 | $sms->send(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/SmsTest.php: -------------------------------------------------------------------------------- 1 | '10', 14 | 'Luosimao' => '0', 15 | ]); 16 | self::$sms = Sms::make(); 17 | } 18 | 19 | public function testMakeSms() 20 | { 21 | $this->assertInstanceOf('Toplan\PhpSms\Sms', self::$sms); 22 | } 23 | 24 | public function testHasAgent() 25 | { 26 | $this->assertFalse(Sms::hasAgent('Log')); 27 | $this->assertFalse(Sms::hasAgent('SomeAgent')); 28 | 29 | Sms::getAgent('Log'); 30 | $this->assertTrue(Sms::hasAgent('Log')); 31 | } 32 | 33 | public function testGetAgent() 34 | { 35 | $agent = Sms::getAgent('Log'); 36 | $this->assertInstanceOf('Toplan\PhpSms\LogAgent', $agent); 37 | $luosimao = Sms::getAgent('Luosimao'); 38 | $this->assertInstanceOf('Toplan\PhpSms\LuosimaoAgent', $luosimao); 39 | } 40 | 41 | public function testGetTask() 42 | { 43 | $task = Sms::getTask(); 44 | $this->assertInstanceOf('Toplan\TaskBalance\Task', $task); 45 | } 46 | 47 | public function testGetSmsData() 48 | { 49 | $data = self::$sms->all(); 50 | $this->assertArrayHasKey('to', $data); 51 | $this->assertArrayHasKey('templates', $data); 52 | $this->assertArrayHasKey('data', $data); 53 | $this->assertArrayHasKey('content', $data); 54 | $this->assertArrayHasKey('code', $data); 55 | $this->assertArrayHasKey('files', $data); 56 | $this->assertArrayHasKey('params', $data); 57 | self::$sms->to('...'); 58 | $this->assertEquals('...', self::$sms->all('to')); 59 | } 60 | 61 | public function testSetTo() 62 | { 63 | self::$sms->to('18280345...'); 64 | $this->assertEquals('18280345...', self::$sms->all('to')); 65 | } 66 | 67 | public function testSetTemplate() 68 | { 69 | self::$sms->template('Luosimao', '123'); 70 | $smsData = self::$sms->all(); 71 | $this->assertEquals([ 72 | 'Luosimao' => '123', 73 | ], $smsData['templates']); 74 | self::$sms->template([ 75 | 'Luosimao' => '1234', 76 | 'YunTongXun' => '6789', 77 | ]); 78 | $smsData = self::$sms->all(); 79 | $this->assertEquals([ 80 | 'Luosimao' => '1234', 81 | 'YunTongXun' => '6789', 82 | ], $smsData['templates']); 83 | } 84 | 85 | public function testSetData() 86 | { 87 | self::$sms->data([ 88 | 'code' => '1', 89 | 'msg' => 'msg', 90 | ]); 91 | $smsData = self::$sms->all(); 92 | $this->assertEquals([ 93 | 'code' => '1', 94 | 'msg' => 'msg', 95 | ], $smsData['data']); 96 | } 97 | 98 | public function testSetContent() 99 | { 100 | self::$sms->content('this is content'); 101 | $smsData = self::$sms->all(); 102 | $this->assertEquals('this is content', $smsData['content']); 103 | } 104 | 105 | public function testSendSms() 106 | { 107 | $result = self::$sms->send(); 108 | $this->assertArrayHasKey('success', $result); 109 | $this->assertArrayHasKey('time', $result); 110 | $this->assertArrayHasKey('logs', $result); 111 | } 112 | 113 | public function testBeforeSend() 114 | { 115 | Sms::beforeSend(function () { 116 | print_r('before_'); 117 | }); 118 | $this->expectOutputString('before_'); 119 | self::$sms->send(); 120 | } 121 | 122 | public function testAfterSend() 123 | { 124 | self::$sms->afterSend(function () { 125 | print_r('after'); 126 | }); 127 | $this->expectOutputString('before_after'); 128 | self::$sms->send(); 129 | } 130 | 131 | public function testSetAgent() 132 | { 133 | $result = self::$sms->agent('Log')->send(); 134 | $this->assertTrue($result['success']); 135 | $this->assertCount(1, $result['logs']); 136 | $this->assertEquals('Log', $result['logs'][0]['driver']); 137 | } 138 | 139 | public function testVoice() 140 | { 141 | $sms = Sms::voice('code'); 142 | $data = $sms->all(); 143 | $this->assertEquals('code', $data['code']); 144 | } 145 | 146 | public function testUseQueue() 147 | { 148 | $status = Sms::queue(); 149 | $this->assertFalse($status); 150 | 151 | //define how to use queue 152 | //way 1 153 | Sms::queue(function ($sms, $data) { 154 | return 'in_queue_1'; 155 | }); 156 | $this->assertTrue(Sms::queue()); 157 | 158 | //define how to use queue 159 | //way 2 160 | Sms::queue(false, function ($sms, $data) { 161 | return 'in_queue_2'; 162 | }); 163 | $this->assertFalse(Sms::queue()); 164 | 165 | //open queue 166 | Sms::queue(true); 167 | $this->assertTrue(Sms::queue()); 168 | 169 | //push sms to queue 170 | $result = self::$sms->send(); 171 | $this->assertEquals('in_queue_2', $result); 172 | 173 | //force send 174 | $result = self::$sms->send(true); 175 | $this->assertTrue($result['success']); 176 | $this->assertCount(1, $result['logs']); 177 | $this->assertEquals('Log', $result['logs'][0]['driver']); 178 | } 179 | } 180 | --------------------------------------------------------------------------------