├── .github └── workflows │ └── workflow.yml ├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── config.php ├── infection.json.dist ├── phpunit.xml.dist ├── src ├── Contracts │ └── Provider.php ├── Exceptions │ └── SmsSendingFailedException.php ├── Notifications │ ├── NutnetSmsChannel.php │ └── NutnetSmsMessage.php ├── Providers │ ├── IqSmsRu.php │ ├── Log.php │ ├── Smpp.php │ ├── Smpp │ │ └── SmppSender.php │ ├── SmsRu.php │ └── SmscRu.php ├── ServiceProvider.php └── SmsSender.php └── tests ├── BaseTestCase.php ├── Providers ├── LogTest.php ├── SmsRuTest.php └── SmscRuTest.php └── SmsSenderTest.php /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | 13 | - name: Validate composer.json and composer.lock 14 | run: composer validate 15 | 16 | - name: Install dependencies 17 | run: composer install --prefer-dist --no-progress --no-suggest 18 | 19 | - name: Run test suite 20 | run: composer run-script test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.log 3 | vendor/ 4 | composer.lock -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Nutnet 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Пакет Laravel-Sms 2 | 3 | Этот пакет предоставляет класс для отправки смс и предустановленные реализации популярных провайдеров. 4 | 5 | ## Установка 6 | 7 | Подключите пакет командой: 8 | ```bash 9 | composer require nutnet/laravel-sms 10 | ``` 11 | 12 | После того как пакет был установлен добавьте его сервис-провайдер в config/app.php (для версий Laravel ниже 5.5): 13 | ```php 14 | // config/app.php 15 | 'providers' => [ 16 | ... 17 | Nutnet\LaravelSms\ServiceProvider::class, 18 | ]; 19 | ``` 20 | 21 | Теперь необходимо перенести конфигурацию пакета в Laravel: 22 | ``` bash 23 | php artisan vendor:publish --provider="Nutnet\LaravelSms\ServiceProvider" --tag="config" 24 | ``` 25 | 26 | ## Конфигурирование 27 | 28 | **ВНИМАНИЕ:** в обновлении 0.8.0 изменился формат конфигурационного файла. Если вы обновились с более ранних версий, не забудьте актуализировать конфигурацию. 29 | 30 | #### Log 31 | Используется для локальной разработки. Смс-сообщения записываются в файл лога. 32 | Не поддерживает передачу параметров сообщения. 33 | 34 | Для включения данного провайдера добавьте в `.env` файл 35 | 36 | ``` 37 | NUTNET_SMS_PROVIDER=log 38 | ``` 39 | 40 | Для отправки сообщений в определенный канал/стек (например, в консоль браузера), используйте настройку `channels`: 41 | 42 | ```php 43 | // config/nutnet-laravel-sms.php 44 | 'providers' => [ 45 | 'log' => [ 46 | /** 47 | * каналы, в которые публикуются сообщения 48 | * оставьте пустым, если хотите использовать общие настройки логирования 49 | * @see https://laravel.com/docs/5.8/logging#building-log-stacks 50 | */ 51 | 'channels' => ['slack', 'browser'], // для версий Laravel >=5.6 52 | ] 53 | ], 54 | ``` 55 | 56 | Пример настройки лог-канала для отправки сообщений в консоль браузера 57 | ```php 58 | // config/logging.php 59 | 'browser' => [ 60 | 'driver' => 'monolog', 61 | 'handler' => Monolog\Handler\BrowserConsoleHandler::class, 62 | ], 63 | ``` 64 | 65 | #### SMPP 66 | Отправка соообщений через протокол SMPP. Требует для работы пакет `franzose/laravel-smpp`. 67 | В данный момент не поддерживает передачу параметров сообщения. 68 | 69 | Для включения данного провайдера добавьте в `.env` файл 70 | 71 | ``` 72 | NUTNET_SMS_PROVIDER=smpp 73 | ``` 74 | 75 | Все прочие настройки находятся в конфигурационном файле пакета `franzose/laravel-smpp` 76 | 77 | #### Sms.ru 78 | Отправка сообщений через провайдера Sms.ru. Требует для работы пакет `zelenin/smsru`. 79 | 80 | Для включения данного провайдера добавьте в `.env` файл 81 | 82 | ``` 83 | NUTNET_SMS_PROVIDER=smsru 84 | ``` 85 | 86 | **Авторизация по паре логин-пароль:** 87 | ```php 88 | // config/nutnet-laravel-sms.php 89 | 'providers' => [ 90 | 'smsru' => [ 91 | 'auth_type' => 'standard', 92 | 'login' => '', 93 | 'password' => '', 94 | 'partner_id' => '', // оставьте null, если не нужен 95 | 'message_defaults' => [] 96 | ] 97 | ], 98 | ``` 99 | 100 | **Усиленная авторизация по паре логин-пароль и api_id:** 101 | ```php 102 | // config/nutnet-laravel-sms.php 103 | 'providers' => [ 104 | 'smsru' => [ 105 | 'auth_type' => 'secured', 106 | 'login' => '', 107 | 'password' => '', 108 | 'api_id' => '', 109 | 'partner_id' => '', // оставьте null, если не нужен 110 | 'message_defaults' => [] 111 | ] 112 | ], 113 | ``` 114 | 115 | **Авторизация с использованием api_id:** 116 | ```php 117 | // config/nutnet-laravel-sms.php 118 | 'providers' => [ 119 | 'smsru' => [ 120 | 'auth_type' => 'api_id', 121 | 'api_id' => '', 122 | 'partner_id' => '', // оставьте null, если не нужен 123 | 'message_defaults' => [] 124 | ] 125 | ], 126 | ``` 127 | 128 | **Параметры сообщения:** 129 | Поддерживается передача параметров сообщения (см. ниже в блоке "Отправка сообщений"). Полный список доступных параметров можно найти [здесь](https://sms.ru/api/send). 130 | 131 | ```php 132 | $sender->send('', '', [ 133 | 'translit' => 1, 134 | 'test' => 1 135 | ]); 136 | ``` 137 | 138 | **Использовать собственный http-клиент вместо стандартного Zelenin\SmsRu\Client\Client:** 139 | 140 | Просто зарегистрируйте свой http-клиент (например, `App\Services\SmsRuHttpClient`) в DI-контейнере следующим образом: 141 | 142 | ```php 143 | // app/Providers/AppServiceProvider.php 144 | public function register() 145 | { 146 | $this->app->bind(\Zelenin\SmsRu\Client\ClientInterface::class, \App\Services\SmsRuHttpClient::class); 147 | } 148 | ``` 149 | 150 | #### Smsc.ru 151 | Отправка сообщений через провайдера Smsc.ru. Требует для работы установленный `curl`. 152 | 153 | Для включения данного провайдера добавьте в `.env` файл 154 | 155 | ``` 156 | NUTNET_SMS_PROVIDER=smscru 157 | ``` 158 | 159 | В настройках провайдера требуется указать логин и пароль: 160 | ```php 161 | // config/nutnet-laravel-sms.php 162 | 'providers' => [ 163 | 'smscru' => [ 164 | 'login' => '', 165 | 'password' => '', 166 | 'message_defaults' => [], 167 | ] 168 | ], 169 | ``` 170 | 171 | Поддерживается передача параметров сообщения (см. ниже в блоке "Отправка сообщений"). 172 | 173 | #### IqSms.ru (Смс-Дисконт) 174 | Отправка сообщений через провайдера iqsms.ru. Требует для работы установленный `curl`. 175 | 176 | Для включения данного провайдера добавьте в `.env` файл 177 | 178 | ``` 179 | NUTNET_SMS_PROVIDER=iqsms 180 | ``` 181 | 182 | В настройках провайдера требуется указать логин и пароль: 183 | ```php 184 | // config/nutnet-laravel-sms.php 185 | 'providers' => [ 186 | 'iqsms' => [ 187 | 'login' => '', 188 | 'password' => '', 189 | 'message_defaults' => [ 190 | // to example, sender 191 | // 'sender' => 'Test', 192 | ] 193 | ] 194 | ], 195 | ``` 196 | 197 | Передача параметров сообщения поддерживается частично - разрешено передавать client_id (см. ниже в блоке "Отправка сообщений"). 198 | 199 | ## Отправка сообщений 200 | 201 | Для отправки сообщений используется класс `Nutnet\LaravelSms\SmsSender`. 202 | Пример отправки: 203 | 204 | ```php 205 | class IndexController extends Controller 206 | { 207 | public function sendSms(Nutnet\LaravelSms\SmsSender $smsSender) 208 | { 209 | // отправка сообщения на 1 номер 210 | $smsSender->send('89193216754', 'Здесь текст сообщений'); 211 | 212 | // отправка сообщения на несколько номеров 213 | $smsSender->sendBatch(['89193216754', '89228764523'], 'Здесь текст сообщений'); 214 | 215 | // отправка сообщений с параметрами 216 | $sender->send('', '', [ 217 | 'translit' => 1, 218 | 'test' => 1 219 | ]); 220 | // ... 221 | } 222 | } 223 | ``` 224 | 225 | **Задать параметры сообщения по умолчанию** можно в настройках провайдера, в опции `message_defaults`. 226 | 227 | ## Использование в связке с Laravel Notifications 228 | 229 | Пакет включает в себя канал для Laravel Notifications (`Nutnet\LaravelSms\Notification\NutnetSmsChannel`). 230 | 231 | #### Настройка Notifiable-модели 232 | 233 | Добавьте метод `routeNotificationForNutnetSms` в свою Notifiable-модель, например: 234 | 235 | ```php 236 | public function routeNotificationForNutnetSms() { 237 | return $this->phone; // Метод должен возвращать номер телефона, на который будет отправлено уведомление. 238 | } 239 | ``` 240 | 241 | #### Пример Notification 242 | 243 | ```php 244 | namespace App\Notifications; 245 | 246 | use Nutnet\LaravelSms\Notification\NutnetSmsChannel; 247 | use Nutnet\LaravelSms\Notification\NutnetSmsMessage; 248 | use Illuminate\Notifications\Notification; 249 | 250 | class ExampleNotification extends Notification 251 | { 252 | public function via($notifiable) 253 | { 254 | return [NutnetSmsChannel::class]; 255 | } 256 | 257 | public function toNutnetSms($notifiable) 258 | { 259 | return new NutnetSmsMessage('текст сообщения', ['параметр1' => 'значение1']); 260 | 261 | // или верните просто строку, равнозначно new NutnetSmsMessage('текст сообщения') 262 | // return 'текст сообщения'; 263 | } 264 | } 265 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nutnet/laravel-sms", 3 | "description": "Package for sending SMS form your Laravel app, includes pre-installed sms providers and your custom.", 4 | "require": { 5 | "illuminate/support": ">=5.0" 6 | }, 7 | "keywords": [ 8 | "sms", 9 | "laravel sms", 10 | "laravel smsc.ru", 11 | "laravel sms.ru", 12 | "laravel smpp", 13 | "laravel iqsms" 14 | ], 15 | "autoload": { 16 | "psr-4": { 17 | "Nutnet\\LaravelSms\\": "src/" 18 | } 19 | }, 20 | "autoload-dev": { 21 | "psr-4": { 22 | "Tests\\": "tests/" 23 | } 24 | }, 25 | "suggest": { 26 | "illuminate/log": "Required for Log provider", 27 | "franzose/laravel-smpp": "Required for SMPP provider", 28 | "zelenin/smsru": "Required for Sms.Ru provider" 29 | }, 30 | "conflict": { 31 | "zelenin/smsru": "<5.0.0" 32 | }, 33 | "extra": { 34 | "laravel": { 35 | "providers": [ 36 | "Nutnet\\LaravelSms\\ServiceProvider" 37 | ] 38 | } 39 | }, 40 | "require-dev": { 41 | "zelenin/smsru": "^5.0", 42 | "phpunit/phpunit": "^9.3", 43 | "illuminate/log": ">=5.0", 44 | "franzose/laravel-smpp": "^1.1", 45 | "infection/infection": "^0.26", 46 | "psr/log": "^1.0" 47 | }, 48 | "scripts": { 49 | "test": [ 50 | "phpunit" 51 | ], 52 | "test-coverage": [ 53 | "phpdbg -qrr ./vendor/bin/phpunit --coverage-text" 54 | ], 55 | "test-mutation": [ 56 | "PHP_BINARY=phpdbg phpdbg -qrr vendor/bin/infection --ansi" 57 | ] 58 | }, 59 | "config": { 60 | "allow-plugins": { 61 | "infection/extension-installer": true 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | 4 | * 17.07.17 5 | */ 6 | return [ 7 | /** 8 | * название класса-провайдера 9 | * Доступные провайдеры: 10 | * * \Nutnet\LaravelSms\Providers\Log (alias: log) 11 | * * \Nutnet\LaravelSms\Providers\Smpp (alias: smpp) 12 | * * \Nutnet\LaravelSms\Providers\SmscRu (alias: smscru) 13 | * * \Nutnet\LaravelSms\Providers\SmsRu (alias: smsru) 14 | * * \Nutnet\LaravelSms\Providers\IqSmsRu (alias: iqsms) 15 | * @see Nutnet\LaravelSms\Providers 16 | */ 17 | 'provider' => env('NUTNET_SMS_PROVIDER', 'log'), 18 | 19 | /** 20 | * настройки, специфичные для провайдера 21 | */ 22 | 'providers' => [ 23 | 'log' => [ 24 | /** 25 | * каналы, в которые публикуются сообщения 26 | * оставьте пустым, если хотите использовать общие настройки логирования 27 | * @see https://laravel.com/docs/5.8/logging#building-log-stacks 28 | */ 29 | 'channels' => [], // для версий Laravel >=5.6 30 | ], 31 | 'smpp' => [ 32 | // все настройки провадера находятся в конфиг. файле franzose/laravel-smpp 33 | ], 34 | 'smscru' => [ 35 | 'login' => env('SMSCRU_LOGIN'), 36 | 'password' => env('SMSCRU_PASSWORD'), 37 | 'message_defaults' => [], 38 | ], 39 | 'smsru' => [ 40 | 'login' => env('SMSRU_LOGIN'), 41 | 'password' => env('SMSRU_PASSWORD'), 42 | 'message_defaults' => [], 43 | ], 44 | 'iqsms' => [ 45 | 'login' => env('IQSMS_LOGIN'), 46 | 'password' => env('IQSMS_PASSWORD'), 47 | 'message_defaults' => [], 48 | ], 49 | ], 50 | ]; 51 | -------------------------------------------------------------------------------- /infection.json.dist: -------------------------------------------------------------------------------- 1 | { 2 | "timeout": 10, 3 | "source": { 4 | "directories": [ 5 | "src" 6 | ], 7 | "excludes": [ 8 | "Exceptions", 9 | "Contracts" 10 | ] 11 | }, 12 | "logs": { 13 | "text": "infection.log" 14 | }, 15 | "mutators": { 16 | "@default": true 17 | } 18 | } -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | 19 | ./src/ 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Contracts/Provider.php: -------------------------------------------------------------------------------- 1 | 4 | * 17.07.17 5 | */ 6 | 7 | namespace Nutnet\LaravelSms\Contracts; 8 | 9 | /** 10 | * Interface Provider 11 | * @package Nutnet\LaravelSms\Contracts 12 | */ 13 | interface Provider 14 | { 15 | /** 16 | * @param $phone 17 | * @param $message 18 | * @param array $options 19 | * @return bool 20 | */ 21 | public function send($phone, $message, array $options = []) : bool; 22 | 23 | /** 24 | * @param array $phones 25 | * @param $message 26 | * @param array $options 27 | * @return bool 28 | */ 29 | public function sendBatch(array $phones, $message, array $options = []) : bool; 30 | } 31 | -------------------------------------------------------------------------------- /src/Exceptions/SmsSendingFailedException.php: -------------------------------------------------------------------------------- 1 | 4 | * 17.04.18 5 | */ 6 | namespace Nutnet\LaravelSms\Exceptions; 7 | 8 | /** 9 | * Class SmsSendingFailedException 10 | * @package Nutnet\LaravelSms\Exceptions 11 | */ 12 | class SmsSendingFailedException extends \Exception 13 | { 14 | 15 | } -------------------------------------------------------------------------------- /src/Notifications/NutnetSmsChannel.php: -------------------------------------------------------------------------------- 1 | 4 | * 04.06.19 5 | */ 6 | 7 | namespace Nutnet\LaravelSms\Notifications; 8 | 9 | use Nutnet\LaravelSms\SmsSender; 10 | use Illuminate\Notifications\Notification; 11 | 12 | /** 13 | * Class NutnetSmsChannel 14 | * @package Nutnet\LaravelSms\Notifications 15 | */ 16 | class NutnetSmsChannel 17 | { 18 | /** 19 | * @var SmsSender 20 | */ 21 | private $sender; 22 | 23 | /** 24 | * NutnetSmsChannel constructor. 25 | * @param SmsSender $sender 26 | */ 27 | public function __construct(SmsSender $sender) 28 | { 29 | $this->sender = $sender; 30 | } 31 | 32 | /** 33 | * @param $notifiable 34 | * @param Notification $notification 35 | */ 36 | public function send($notifiable, Notification $notification) 37 | { 38 | $phone = $notifiable->routeNotificationFor('nutnet_sms'); 39 | 40 | if (!$phone) { 41 | return; 42 | } 43 | 44 | /** @var NutnetSmsMessage $message */ 45 | $message = $notification->toNutnetSms($notifiable); 46 | 47 | if (!($message instanceof NutnetSmsMessage)) { 48 | $message = new NutnetSmsMessage($message); 49 | } 50 | 51 | $this->sender->send($phone, $message->getContent(), $message->getOptions()); 52 | } 53 | } -------------------------------------------------------------------------------- /src/Notifications/NutnetSmsMessage.php: -------------------------------------------------------------------------------- 1 | 4 | * 04.06.19 5 | */ 6 | 7 | namespace Nutnet\LaravelSms\Notifications; 8 | 9 | /** 10 | * Class NutnetSmsMessage 11 | * @package Nutnet\LaravelSms\Notifications 12 | */ 13 | class NutnetSmsMessage 14 | { 15 | /** 16 | * @var string 17 | */ 18 | private $content; 19 | 20 | /** 21 | * @var array 22 | */ 23 | private $options = []; 24 | 25 | /** 26 | * NutnetSmsMessage constructor. 27 | * @param $text 28 | * @param array $options 29 | */ 30 | public function __construct($text, array $options = []) 31 | { 32 | $this 33 | ->content($text) 34 | ->options($options); 35 | } 36 | 37 | /** 38 | * @param $text 39 | * @return $this 40 | */ 41 | public function content($text) 42 | { 43 | $this->content = $text; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * @param array $options 50 | * @return $this 51 | */ 52 | public function options(array $options) 53 | { 54 | $this->options = $options; 55 | 56 | return $this; 57 | } 58 | 59 | /** 60 | * @return array 61 | */ 62 | public function getOptions() 63 | { 64 | return $this->options; 65 | } 66 | 67 | /** 68 | * @return string 69 | */ 70 | public function getContent() 71 | { 72 | return $this->content; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Providers/IqSmsRu.php: -------------------------------------------------------------------------------- 1 | 4 | * 17.04.18 5 | */ 6 | 7 | namespace Nutnet\LaravelSms\Providers; 8 | 9 | use Nutnet\LaravelSms\Contracts\Provider; 10 | use Nutnet\LaravelSms\Exceptions\SmsSendingFailedException; 11 | use Illuminate\Support\Arr; 12 | 13 | /** 14 | * @link https://iqsms.ru 15 | * Class IqSmsRu 16 | * @package Nutnet\LaravelSms\Providers 17 | */ 18 | class IqSmsRu implements Provider 19 | { 20 | const STATUS_OK = 'ok'; 21 | const STATUS_ACCEPTED = 'accepted'; 22 | 23 | const API_URL = 'http://json.gate.iqsms.ru'; 24 | const PACKET_SIZE = 20; 25 | 26 | /** 27 | * @var string 28 | */ 29 | private $apiLogin; 30 | 31 | /** 32 | * @var string 33 | */ 34 | private $apiPassword; 35 | 36 | /** 37 | * IqSmsRu constructor. 38 | * @param array $options 39 | */ 40 | public function __construct(array $options) 41 | { 42 | $this->validateOptions($options); 43 | 44 | $this->apiLogin = Arr::get($options, 'login'); 45 | $this->apiPassword = Arr::get($options, 'password'); 46 | } 47 | 48 | /** 49 | * @param $phone 50 | * @param $message 51 | * @param array $options 52 | * @return bool 53 | * @throws SmsSendingFailedException 54 | */ 55 | public function send($phone, $message, array $options = []): bool 56 | { 57 | $result = $this->sendRequest('send', [ 58 | 'messages' => array_merge( 59 | Arr::except($options, 'client_id'), 60 | [ 61 | 'clientId' => Arr::get($options, 'client_id', "1"), 62 | 'phone' => $phone, 63 | 'text' => $message, 64 | ] 65 | ), 66 | ]); 67 | 68 | if ($result['status'] != self::STATUS_OK) { 69 | throw new SmsSendingFailedException("Failed to send request"); 70 | } 71 | 72 | foreach ($result['messages'] as $message) { 73 | if ($message['status'] != self::STATUS_ACCEPTED) { 74 | throw new SmsSendingFailedException("Failed to send sms with status: " . $message['status']); 75 | } 76 | } 77 | 78 | return true; 79 | } 80 | 81 | /** 82 | * @param array $phones 83 | * @param $message 84 | * @param array $options 85 | * @return bool 86 | * @throws SmsSendingFailedException 87 | */ 88 | public function sendBatch(array $phones, $message, array $options = []): bool 89 | { 90 | $params = [ 91 | 'messages' => [] 92 | ]; 93 | $clientId = Arr::get($options, 'client_id', '1'); 94 | 95 | foreach (array_chunk($phones, self::PACKET_SIZE) as $phonesPacket) { 96 | foreach ($phonesPacket as $phone) { 97 | $params['messages'][] = array_merge( 98 | Arr::except($options, 'client_id'), 99 | [ 100 | 'clientId' => $clientId, 101 | 'phone' => $phone, 102 | 'text' => $message, 103 | ] 104 | ); 105 | } 106 | } 107 | 108 | $response = $this->sendRequest('send', $params); 109 | 110 | return $response['status'] == self::STATUS_OK; 111 | } 112 | 113 | /** 114 | * @param $uri 115 | * @param null $params 116 | * @return mixed 117 | * @throws SmsSendingFailedException 118 | */ 119 | private function sendRequest($uri, $params = null) 120 | { 121 | $client = curl_init($this->getUrl($uri)); 122 | curl_setopt_array($client, array( 123 | CURLOPT_RETURNTRANSFER => true, 124 | CURLOPT_POST => true, 125 | CURLOPT_HEADER => false, 126 | CURLOPT_HTTPHEADER => array('Host: ' . parse_url(self::API_URL, PHP_URL_HOST)), 127 | CURLOPT_POSTFIELDS => $this->makePacket($params), 128 | )); 129 | 130 | $body = curl_exec($client); 131 | curl_close($client); 132 | 133 | if (empty($body)) { 134 | throw new SmsSendingFailedException('IQSms sends empty response.'); 135 | } 136 | 137 | $decodedBody = json_decode($body, true); 138 | 139 | if (is_null($decodedBody)) { 140 | throw new SmsSendingFailedException('Response body is not valid json.'); 141 | } 142 | 143 | return $decodedBody; 144 | } 145 | 146 | /** 147 | * @param $uri 148 | * @return string 149 | */ 150 | private function getUrl($uri) 151 | { 152 | return self::API_URL . '/' . $uri . '/'; 153 | } 154 | 155 | /** 156 | * @param null $params 157 | * @return false|string 158 | */ 159 | private function makePacket($params = null) 160 | { 161 | $params = $params ?: []; 162 | $params['login'] = $this->apiLogin; 163 | $params['password'] = $this->apiPassword; 164 | 165 | return json_encode(array_filter($params)); 166 | } 167 | 168 | /** 169 | * @param array $options 170 | */ 171 | private function validateOptions(array $options) 172 | { 173 | if (empty($options['login']) || empty($options['password'])) { 174 | throw new \InvalidArgumentException('Login and password is required.'); 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /src/Providers/Log.php: -------------------------------------------------------------------------------- 1 | 4 | * 18.05.17 5 | */ 6 | 7 | namespace Nutnet\LaravelSms\Providers; 8 | 9 | use Psr\Log\LoggerInterface as Writer; 10 | use Nutnet\LaravelSms\Contracts\Provider; 11 | 12 | /** 13 | * Class Log 14 | * @package Nutnet\LaravelSms\Providers 15 | */ 16 | class Log implements Provider 17 | { 18 | /** 19 | * @var Writer 20 | */ 21 | private $logWriter; 22 | 23 | /** 24 | * Log constructor. 25 | * @param Writer $logWriter 26 | * @param array $options 27 | */ 28 | public function __construct(Writer $logWriter, array $options = []) 29 | { 30 | $channels = isset($options['channels']) ? $options['channels'] : []; 31 | if (!is_array($channels)) { 32 | $channels = [$channels]; 33 | } 34 | 35 | // support for logging messages into custom channels 36 | if (!empty($channels)) { 37 | if (!$this->isSupportsChannels($logWriter)) { 38 | throw new \DomainException(sprintf( 39 | 'Writer of type %s doesnt support channels.', 40 | get_class($logWriter) 41 | )); 42 | } 43 | 44 | $logWriter = $this->makeStackedLogger($logWriter, $channels); 45 | } 46 | 47 | $this->logWriter = $logWriter; 48 | } 49 | 50 | /** 51 | * Send single sms 52 | * @param $phone 53 | * @param $text 54 | * @param $options 55 | * @return mixed 56 | */ 57 | public function send($phone, $text, array $options = []) : bool 58 | { 59 | $this->getWriter()->debug(sprintf( 60 | 'Sms is sent to %s: "%s"', 61 | $phone, 62 | $text 63 | )); 64 | 65 | return true; 66 | } 67 | 68 | /** 69 | * @param array $phones 70 | * @param $message 71 | * @param $options 72 | * @return bool 73 | */ 74 | public function sendBatch(array $phones, $message, array $options = []) : bool 75 | { 76 | foreach ($phones as $phone) { 77 | $this->send($phone, $message); 78 | } 79 | 80 | return true; 81 | } 82 | 83 | /** 84 | * @return Writer 85 | */ 86 | public function getWriter() 87 | { 88 | return $this->logWriter; 89 | } 90 | 91 | /** 92 | * @param Writer $logger 93 | * @return bool 94 | */ 95 | private function isSupportsChannels(Writer $logger) 96 | { 97 | return method_exists($logger, 'channel') && method_exists($logger, 'stack'); 98 | } 99 | 100 | /** 101 | * @param Writer $logWriter 102 | * @param array $channels 103 | * @return Writer 104 | */ 105 | private function makeStackedLogger(Writer $logWriter, array $channels) 106 | { 107 | if (count($channels) > 1) { 108 | $logWriter = $logWriter->stack($channels); 109 | } else { 110 | $logWriter = $logWriter->channel(reset($channels)); 111 | } 112 | 113 | return $logWriter; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Providers/Smpp.php: -------------------------------------------------------------------------------- 1 | 4 | * 17.07.17 5 | */ 6 | 7 | namespace Nutnet\LaravelSms\Providers; 8 | 9 | use Nutnet\LaravelSms\Contracts\Provider; 10 | use Nutnet\LaravelSms\Providers\Smpp\SmppSender; 11 | 12 | /** 13 | * Class Smpp 14 | * @package App\Services\Sms\Bridges\Smpp 15 | */ 16 | class Smpp implements Provider 17 | { 18 | /** 19 | * @var SmppSender 20 | */ 21 | private $smpp; 22 | 23 | /** 24 | * @var array 25 | */ 26 | private $options; 27 | 28 | /** 29 | * SmppBridge constructor. 30 | * @param SmppSender $smppService 31 | * @param array $options 32 | */ 33 | public function __construct(SmppSender $smppService, array $options = []) 34 | { 35 | $this->smpp = $smppService; 36 | $this->options = $options; 37 | } 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | public function send($phone, $text, array $options = []) : bool 43 | { 44 | return $this->smpp->sendOne($phone, $text); 45 | } 46 | 47 | /** 48 | * @inheritdoc 49 | */ 50 | public function sendBatch(array $phones, $message, array $options = []) : bool 51 | { 52 | $this->smpp->sendBulk($phones, $message); 53 | 54 | return true; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Providers/Smpp/SmppSender.php: -------------------------------------------------------------------------------- 1 | 4 | * 17.07.17 5 | */ 6 | 7 | namespace Nutnet\LaravelSms\Providers\Smpp; 8 | 9 | use SMPP; 10 | use SmppAddress; 11 | use LaravelSmpp\SmppService; 12 | 13 | /** 14 | * Class SmppSender 15 | * @package Nutnet\LaravelSms\Providers\Smpp 16 | */ 17 | class SmppSender extends SmppService 18 | { 19 | /** 20 | * @inheritdoc 21 | */ 22 | protected function sendSms(SmppAddress $sender, $recipient, $message) 23 | { 24 | $message = mb_convert_encoding($message, 'UCS-2', 'UTF-8'); 25 | 26 | return $this->smpp->sendSMS( 27 | $sender, 28 | $this->getRecipient($recipient), 29 | $message, 30 | null, 31 | SMPP::DATA_CODING_UCS2 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Providers/SmsRu.php: -------------------------------------------------------------------------------- 1 | 4 | * 18.05.17 5 | */ 6 | 7 | namespace Nutnet\LaravelSms\Providers; 8 | 9 | use Illuminate\Support\Arr; 10 | use Nutnet\LaravelSms\Contracts\Provider; 11 | use Zelenin\SmsRu as SmsRuApi; 12 | 13 | /** 14 | * Class SmsRu 15 | * @package Nutnet\LaravelSms\Providers 16 | */ 17 | class SmsRu implements Provider 18 | { 19 | const CODE_OK = 100; 20 | 21 | const AUTH_STANDARD = 'standard'; 22 | const AUTH_SECURED = 'secured'; 23 | const AUTH_API_ID = 'api_id'; 24 | 25 | /** 26 | * @var SmsRuApi\Api 27 | */ 28 | private $api; 29 | 30 | /** 31 | * @var SmsRuApi\Client\ClientInterface 32 | */ 33 | private $httpClient; 34 | 35 | /** 36 | * @var array 37 | */ 38 | private $options; 39 | 40 | /** 41 | * @var array 42 | */ 43 | private $authTypes = [ 44 | self::AUTH_STANDARD => 'makeAuthStandard', 45 | self::AUTH_SECURED => 'makeAuthSecured', 46 | self::AUTH_API_ID => 'makeAuthApiId' 47 | ]; 48 | 49 | public function __construct(array $options, SmsRuApi\Client\ClientInterface $httpClient = null) 50 | { 51 | $this->options = $options; 52 | $this->httpClient = $httpClient ?? new SmsRuApi\Client\Client(); 53 | } 54 | 55 | /** 56 | * @param $phone 57 | * @param $text 58 | * @param array $options 59 | * @return bool 60 | * @throws SmsRuApi\Exception\Exception 61 | */ 62 | public function send($phone, $text, array $options = []) : bool 63 | { 64 | $response = $this->getApi()->smsSend( 65 | $this->makeMessage($phone, $text, $options) 66 | ); 67 | 68 | return $response->code == self::CODE_OK; 69 | } 70 | 71 | /** 72 | * @param array $phones 73 | * @param $message 74 | * @param array $options 75 | * @return bool 76 | * @throws SmsRuApi\Exception\Exception 77 | */ 78 | public function sendBatch(array $phones, $message, array $options = []) : bool 79 | { 80 | $smsList = array_map(function ($phone) use ($message, $options) { 81 | return $this->makeMessage($phone, $message, $options); 82 | }, $phones); 83 | $response = $this->getApi()->smsSend(new SmsRuApi\Entity\SmsPool($smsList)); 84 | 85 | return $response->code == self::CODE_OK; 86 | } 87 | 88 | /** 89 | * @return SmsRuApi\Api 90 | */ 91 | public function getApi() 92 | { 93 | if (!$this->api) { 94 | return $this->api = new SmsRuApi\Api($this->getAuth(), $this->httpClient); 95 | } 96 | 97 | return $this->api; 98 | } 99 | 100 | /** 101 | * @return SmsRuApi\Auth\AuthInterface 102 | */ 103 | private function getAuth() 104 | { 105 | $authType = Arr::get($this->options, 'auth_type', self::AUTH_STANDARD); 106 | 107 | if (!array_key_exists($authType, $this->authTypes)) { 108 | throw new \InvalidArgumentException(sprintf('Unsupported auth type: %s', $authType)); 109 | } 110 | 111 | $authBuilder = $this->authTypes[$authType]; 112 | 113 | return $this->$authBuilder(); 114 | } 115 | 116 | /** 117 | * @param $phone 118 | * @param $text 119 | * @param array $options 120 | * @return SmsRuApi\Entity\Sms 121 | */ 122 | private function makeMessage($phone, $text, array $options = []) 123 | { 124 | $message = new SmsRuApi\Entity\Sms($phone, $text); 125 | 126 | // set message options, @see available on https://sms.ru/api/send 127 | foreach ($options as $optionName => $optionValue) { 128 | if (property_exists($message, $optionName)) { 129 | $message->$optionName = $optionValue; 130 | } 131 | } 132 | 133 | return $message; 134 | } 135 | 136 | /** 137 | * @return SmsRuApi\Auth\LoginPasswordAuth 138 | */ 139 | private function makeAuthStandard() 140 | { 141 | return new SmsRuApi\Auth\LoginPasswordAuth( 142 | Arr::get($this->options, 'login'), 143 | Arr::get($this->options, 'password'), 144 | Arr::get($this->options, 'partner_id') 145 | ); 146 | } 147 | 148 | /** 149 | * @return SmsRuApi\Auth\LoginPasswordSecureAuth 150 | */ 151 | private function makeAuthSecured() 152 | { 153 | return new SmsRuApi\Auth\LoginPasswordSecureAuth( 154 | Arr::get($this->options, 'login'), 155 | Arr::get($this->options, 'password'), 156 | Arr::get($this->options, 'api_id'), 157 | null, 158 | Arr::get($this->options, 'partner_id') 159 | ); 160 | } 161 | 162 | /** 163 | * @return SmsRuApi\Auth\ApiIdAuth 164 | */ 165 | private function makeAuthApiId() 166 | { 167 | return new SmsRuApi\Auth\ApiIdAuth( 168 | Arr::get($this->options, 'api_id'), 169 | Arr::get($this->options, 'partner_id') 170 | ); 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/Providers/SmscRu.php: -------------------------------------------------------------------------------- 1 | 4 | * 17.07.17 5 | */ 6 | 7 | namespace Nutnet\LaravelSms\Providers; 8 | 9 | use Nutnet\LaravelSms\Contracts\Provider; 10 | 11 | /** 12 | * Class SmscRu 13 | * @package Nutnet\LaravelSms\Providers 14 | */ 15 | class SmscRu implements Provider 16 | { 17 | const BASE_URL = 'https://smsc.ru/sys/send.php'; 18 | const PHONE_DELIMITER = ';'; 19 | 20 | /** 21 | * @var mixed|null 22 | */ 23 | private $login; 24 | 25 | /** 26 | * @var mixed|null 27 | */ 28 | private $password; 29 | 30 | /** 31 | * SmscRu constructor. 32 | * @param array $options 33 | */ 34 | public function __construct(array $options) 35 | { 36 | $this->login = $options['login'] ?? null; 37 | $this->password = $options['password'] ?? null; 38 | } 39 | 40 | /** 41 | * @inheritdoc 42 | */ 43 | public function send($phone, $text, array $options = []) : bool 44 | { 45 | return $this->sendBatch([$phone], $text, $options); 46 | } 47 | 48 | /** 49 | * @inheritdoc 50 | */ 51 | public function sendBatch(array $phones, $message, array $options = []) : bool 52 | { 53 | $response = $this->doRequest(array_merge( 54 | [ 55 | 'login' => $this->login, 56 | 'psw' => $this->password, 57 | 'phones' => implode(self::PHONE_DELIMITER, $phones), 58 | 'mes' => mb_convert_encoding($message, 'Windows-1251'), 59 | 'fmt' => 3 60 | ], 61 | $options 62 | )); 63 | 64 | if (!is_array($response)) { 65 | return true == $response; 66 | } 67 | 68 | return !isset($response['error']); 69 | } 70 | 71 | /** 72 | * @param $httpQuery 73 | * @return mixed 74 | */ 75 | protected function doRequest($httpQuery) 76 | { 77 | $url = self::BASE_URL.'?'.http_build_query($httpQuery); 78 | 79 | $ch = curl_init($url); 80 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 81 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 82 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); 83 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); 84 | $response = json_decode(curl_exec($ch), true); 85 | curl_close($ch); 86 | 87 | return $response; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | 4 | * 13.07.17 5 | */ 6 | 7 | namespace Nutnet\LaravelSms; 8 | 9 | use Illuminate\Support\Arr; 10 | use Nutnet\LaravelSms\Providers; 11 | 12 | /** 13 | * Class ServiceProvider 14 | * @package Nutnet\LaravelSms 15 | */ 16 | class ServiceProvider extends \Illuminate\Support\ServiceProvider 17 | { 18 | /** 19 | * @var array 20 | */ 21 | private $providerAliases = [ 22 | 'log' => Providers\Log::class, 23 | 'iqsms' => Providers\IqSmsRu::class, 24 | 'smpp' => Providers\Smpp::class, 25 | 'smscru' => Providers\SmscRu::class, 26 | 'smsru' => Providers\SmsRu::class, 27 | ]; 28 | 29 | public function boot() 30 | { 31 | $this->publishes([ 32 | __DIR__ . '/../config.php' => config_path('nutnet-laravel-sms.php') 33 | ], 'config'); 34 | } 35 | 36 | public function register() 37 | { 38 | $this->app->singleton(SmsSender::class, function ($app) { 39 | $providerClass = config('nutnet-laravel-sms.provider'); 40 | $options = config('nutnet-laravel-sms.providers.'.$providerClass, []); 41 | 42 | if (array_key_exists($providerClass, $this->providerAliases)) { 43 | $providerClass = $this->providerAliases[$providerClass]; 44 | } 45 | 46 | return new SmsSender( 47 | $app->make($providerClass, [ 48 | 'options' => Arr::except($options, 'message_defaults') 49 | ]), 50 | (array)Arr::get($options, 'message_defaults', []) 51 | ); 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/SmsSender.php: -------------------------------------------------------------------------------- 1 | bridge = $bridge; 30 | $this->defaultOptions = $defaultOptions; 31 | } 32 | 33 | /** 34 | * @param $phone 35 | * @param $message 36 | * @param array $options 37 | * @return bool 38 | */ 39 | public function send($phone, $message, array $options = []) 40 | { 41 | return $this->bridge->send( 42 | $this->preparePhone($phone), 43 | $message, 44 | array_merge($this->defaultOptions, $options) 45 | ); 46 | } 47 | 48 | /** 49 | * @param array $phones 50 | * @param $message 51 | * @param array $options 52 | * @return bool 53 | */ 54 | public function sendBatch(array $phones, $message, array $options = []) 55 | { 56 | return $this->bridge->sendBatch( 57 | array_map([$this, 'preparePhone'], $phones), 58 | $message, 59 | array_merge($this->defaultOptions, $options) 60 | ); 61 | } 62 | 63 | private function preparePhone($phone) 64 | { 65 | return preg_replace('/[^\d]+/', '', $phone); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/BaseTestCase.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Tests; 8 | 9 | use PHPUnit\Framework\TestCase; 10 | 11 | class BaseTestCase extends TestCase 12 | { 13 | /** 14 | * @param $class 15 | * @param $method 16 | * @return \ReflectionMethod 17 | * @throws \ReflectionException 18 | */ 19 | protected function makeMethodAccessible($class, $method) 20 | { 21 | $class = new \ReflectionClass($class); 22 | $method = $class->getMethod($method); 23 | $method->setAccessible(true); 24 | 25 | return $method; 26 | } 27 | } -------------------------------------------------------------------------------- /tests/Providers/LogTest.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Tests\Providers; 8 | 9 | use Illuminate\Log\Logger; 10 | use Illuminate\Log\LogManager; 11 | use Nutnet\LaravelSms\Providers\Log; 12 | use Psr\Log\NullLogger; 13 | use Psr\Log\Test\TestLogger; 14 | use Tests\BaseTestCase; 15 | 16 | class LogTest extends BaseTestCase 17 | { 18 | public function testSendOneMessage() 19 | { 20 | $store = new TestLogger(); 21 | $provider = new Log($store); 22 | 23 | $to = '79991112233'; 24 | $msg = 'Test'; 25 | 26 | $this->assertTrue($provider->send($to, $msg)); 27 | $this->assertTrue($store->hasDebugThatContains($this->formatMsg($to, $msg))); 28 | } 29 | 30 | public function testSendBatch() 31 | { 32 | $store = new TestLogger(); 33 | $provider = new Log($store); 34 | 35 | $to = ['79112238844', '79991112233', '79129998877']; 36 | $msg = 'Test'; 37 | 38 | $this->assertTrue($provider->sendBatch($to, $msg)); 39 | foreach ($to as $phone) { 40 | $this->assertTrue($store->hasDebugThatContains($this->formatMsg($phone, $msg))); 41 | } 42 | } 43 | 44 | // check sending, when set single channel 45 | public function testIsUsedLogChannel() 46 | { 47 | $channel = 'browser'; 48 | $writer = new TestLogger(); 49 | 50 | $store = $this 51 | ->getMockBuilder(LogManager::class) 52 | ->disableOriginalConstructor() 53 | ->setMethods(['channel']) 54 | ->getMock(); 55 | 56 | $store->expects($this->once()) 57 | ->method('channel') 58 | ->with($channel) 59 | ->willReturn($writer); 60 | 61 | $provider = new Log($store, [ 62 | 'channels' => [$channel] 63 | ]); 64 | 65 | $this->assertEquals($writer, $provider->getWriter()); 66 | } 67 | 68 | // check sending, when set multiple channels 69 | public function testIsUsedLogStack() 70 | { 71 | $channels = ['browser', 'syslog']; 72 | $writer = new Logger(new TestLogger()); 73 | 74 | $store = $this 75 | ->getMockBuilder(LogManager::class) 76 | ->disableOriginalConstructor() 77 | ->setMethods(['stack']) 78 | ->getMock(); 79 | 80 | $store->expects($this->once()) 81 | ->method('stack') 82 | ->with($channels) 83 | ->willReturn($writer); 84 | 85 | $provider = new Log($store, [ 86 | 'channels' => $channels 87 | ]); 88 | 89 | $this->assertEquals($writer, $provider->getWriter()); 90 | } 91 | 92 | public function testUsingNonStackableLoggerWithStack() 93 | { 94 | $channels = ['browser', 'syslog']; 95 | 96 | $store = $this 97 | ->getMockBuilder(NullLogger::class) 98 | ->disableOriginalConstructor() 99 | ->setMethods(['channel']) 100 | ->getMock(); 101 | 102 | $this->expectException(\DomainException::class); 103 | 104 | new Log($store, [ 105 | 'channels' => $channels 106 | ]); 107 | } 108 | 109 | // check sending, when channels is not set 110 | public function testIsUsedDefaultLogger() 111 | { 112 | $channels = []; 113 | $defaultLogDriver = new TestLogger(); 114 | 115 | $store = $this 116 | ->getMockBuilder(LogManager::class) 117 | ->disableOriginalConstructor() 118 | ->setMethods(['stack', 'channel', 'driver']) 119 | ->getMock(); 120 | 121 | $store->expects($this->never())->method('stack'); 122 | $store->expects($this->never())->method('channel'); 123 | $store->method('driver')->willReturn($defaultLogDriver); 124 | 125 | $provider = new Log($store, [ 126 | 'channels' => $channels 127 | ]); 128 | 129 | $this->assertEquals($defaultLogDriver, $provider->getWriter()->driver()); 130 | } 131 | 132 | private function formatMsg($to, $msg) 133 | { 134 | return sprintf('Sms is sent to %s: "%s"', $to, $msg); 135 | } 136 | } -------------------------------------------------------------------------------- /tests/Providers/SmsRuTest.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Tests\Providers; 8 | 9 | use Nutnet\LaravelSms\Providers\SmsRu; 10 | use PHPUnit\Framework\MockObject\MockObject; 11 | use Tests\BaseTestCase; 12 | use Zelenin\SmsRu\Api; 13 | use Zelenin\SmsRu\Auth\ApiIdAuth; 14 | use Zelenin\SmsRu\Auth\AuthInterface; 15 | use Zelenin\SmsRu\Auth\LoginPasswordAuth; 16 | use Zelenin\SmsRu\Auth\LoginPasswordSecureAuth; 17 | use Zelenin\SmsRu\Entity\Sms; 18 | use Zelenin\SmsRu\Entity\SmsPool; 19 | use Zelenin\SmsRu\Response\SmsResponse; 20 | 21 | class SmsRuTest extends BaseTestCase 22 | { 23 | public function testSendOneMessage() 24 | { 25 | list($provider, $client) = $this->getSendingMocks(); 26 | 27 | $messageTo = '79112238844'; 28 | $message = 'Test message'; 29 | 30 | $client->expects($this->exactly(2)) 31 | ->method('smsSend') 32 | ->willReturn( 33 | new SmsResponse(120), 34 | new SmsResponse(SmsRu::CODE_OK) 35 | ) 36 | ->with($this->callback(function ($sms) use ($messageTo, $message) { 37 | if (!($sms instanceof Sms)) { 38 | return false; 39 | } 40 | 41 | return $sms->to == $messageTo && $sms->text == $message; 42 | })); 43 | 44 | $this->assertFalse($provider->send($messageTo, $message)); 45 | $this->assertTrue($provider->send($messageTo, $message)); 46 | } 47 | 48 | public function testSendBatch() 49 | { 50 | list($provider, $client) = $this->getSendingMocks(); 51 | 52 | $messageTo = ['79112238844', '79991112233', '79129998877']; 53 | $message = 'Test message'; 54 | 55 | $client->expects($this->exactly(2)) 56 | ->method('smsSend') 57 | ->willReturn( 58 | new SmsResponse(SmsRu::CODE_OK), 59 | new SmsResponse(300) 60 | ) 61 | ->with($this->callback(function ($smsPool) use ($messageTo, $message) { 62 | if (!($smsPool instanceof SmsPool)) { 63 | return false; 64 | } 65 | 66 | $recipients = array_map(function (Sms $sms) { 67 | return $sms->to; 68 | }, $smsPool->messages); 69 | $messages = array_unique(array_map(function (Sms $sms) { 70 | return $sms->text; 71 | }, $smsPool->messages)); 72 | 73 | if (count($messages) > 1 || reset($messages) != $message) { 74 | return false; 75 | } 76 | 77 | return count(array_intersect($recipients, $messageTo)) == count($messageTo); 78 | })); 79 | 80 | $this->assertTrue($provider->sendBatch($messageTo, $message)); 81 | $this->assertFalse($provider->sendBatch($messageTo, $message)); 82 | } 83 | 84 | public function testGettingClient() 85 | { 86 | $provider = new SmsRu([ 87 | 'login' => 'test', 88 | 'password' => 'test', 89 | ]); 90 | 91 | $this->assertInstanceOf(Api::class, $provider->getApi()); 92 | } 93 | 94 | /** 95 | * @throws \ReflectionException 96 | */ 97 | public function testCreatingClientWithStandardAuth() 98 | { 99 | $partnerId = 2; 100 | $login = 'test'; 101 | $password = 'test_password'; 102 | 103 | $auth = $this->callAuthCreator(new SmsRu([ 104 | 'login' => $login, 105 | 'password' => $password, 106 | 'partner_id' => $partnerId 107 | ])); 108 | 109 | $this->assertInstanceOf(LoginPasswordAuth::class, $auth); 110 | $this->assertEquals($partnerId, $auth->getPartnerId()); 111 | $this->assertEquals($login, $auth->getAuthParams()['login']); 112 | $this->assertEquals($password, $auth->getAuthParams()['password']); 113 | } 114 | 115 | /** 116 | * @throws \ReflectionException 117 | */ 118 | public function testCreatingClientWithSecureAuth() 119 | { 120 | $apiId = 5; 121 | $login = 'test'; 122 | $password = 'test_password'; 123 | 124 | $auth = $this->callAuthCreator(new SmsRu([ 125 | 'auth_type' => SmsRu::AUTH_SECURED, 126 | 'login' => $login, 127 | 'password' => $password, 128 | 'api_id' => $apiId, 129 | ])); 130 | 131 | $this->assertInstanceOf(LoginPasswordSecureAuth::class, $auth); 132 | $this->assertNull($auth->getPartnerId()); 133 | $this->assertEquals($apiId, $auth->getApiId()); 134 | } 135 | 136 | /** 137 | * @throws \ReflectionException 138 | */ 139 | public function testCreatingClientWithApiIdAuth() 140 | { 141 | $apiId = 5; 142 | $partnerId = 10; 143 | 144 | $auth = $this->callAuthCreator(new SmsRu([ 145 | 'auth_type' => SmsRu::AUTH_API_ID, 146 | 'api_id' => $apiId, 147 | 'partner_id' => $partnerId 148 | ])); 149 | 150 | $this->assertInstanceOf(ApiIdAuth::class, $auth); 151 | $this->assertEquals($partnerId, $auth->getPartnerId()); 152 | $this->assertEquals($apiId, $auth->getApiId()); 153 | } 154 | 155 | /** 156 | * @throws \ReflectionException 157 | */ 158 | public function testMakingMessage() 159 | { 160 | $provider = new SmsRu([]); 161 | $method = $this->makeMethodAccessible(SmsRu::class, 'makeMessage'); 162 | 163 | /** @var Sms $result */ 164 | $result = $method->invoke($provider, '098765', 'test_message'); 165 | $this->assertNull($result->test); 166 | 167 | $result = $method->invoke($provider, '098765', 'test_message', [ 168 | 'test' => 1, 169 | 'translit' => 1 170 | ]); 171 | 172 | $this->assertEquals($result->test, 1); 173 | $this->assertEquals($result->translit, 1); 174 | } 175 | 176 | /** 177 | * @param $provider 178 | * @return AuthInterface 179 | * @throws \ReflectionException 180 | */ 181 | private function callAuthCreator($provider) 182 | { 183 | return $this->makeMethodAccessible(SmsRu::class, 'getAuth')->invoke($provider); 184 | } 185 | 186 | /** 187 | * @return array 188 | */ 189 | private function getSendingMocks() 190 | { 191 | $client = $this->getMockBuilder(Api::class) 192 | ->disableOriginalConstructor() 193 | ->setMethods(['smsSend']) 194 | ->getMock(); 195 | 196 | /** @var SmsRu|MockObject $provider */ 197 | $provider = $this->getMockBuilder(SmsRu::class) 198 | ->setConstructorArgs([ 199 | [ 200 | 'login' => 'test', 201 | 'password' => 'test', 202 | ] 203 | ]) 204 | ->setMethods(['getApi']) 205 | ->getMock(); 206 | 207 | $provider->method('getApi')->willReturn($client); 208 | 209 | return [$provider, $client]; 210 | } 211 | } -------------------------------------------------------------------------------- /tests/Providers/SmscRuTest.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Tests\Providers; 8 | 9 | use Nutnet\LaravelSms\Providers\Log; 10 | use Nutnet\LaravelSms\Providers\SmscRu; 11 | use PHPUnit\Framework\MockObject\MockObject; 12 | use Psr\Log\Test\TestLogger; 13 | use Tests\BaseTestCase; 14 | 15 | class SmscRuTest extends BaseTestCase 16 | { 17 | public function testSendOneMessage() 18 | { 19 | /** @var MockObject|SmscRu $provider */ 20 | $provider = $this->getMockBuilder(SmscRu::class) 21 | ->disableOriginalConstructor() 22 | ->setMethods(['sendBatch']) 23 | ->getMock(); 24 | 25 | $to = '79991112233'; 26 | $msg = 'Test'; 27 | $options = ['test' => 1]; 28 | 29 | $provider->expects($this->once()) 30 | ->method('sendBatch') 31 | ->with( 32 | $this->equalTo([$to]), 33 | $this->equalTo($msg), 34 | $this->equalTo($options) 35 | ); 36 | 37 | $provider->send($to, $msg, $options); 38 | } 39 | 40 | /** 41 | * @param $sendResponse 42 | * @param $expectedReturnValue 43 | * @dataProvider dpSendBatch 44 | */ 45 | public function testSendBatch($sendResponse, $expectedReturnValue) 46 | { 47 | $login = $password = 'test'; 48 | 49 | /** @var MockObject|SmscRu $provider */ 50 | $provider = $this->getMockBuilder(SmscRu::class) 51 | ->setConstructorArgs([ 52 | compact('login', 'password') 53 | ]) 54 | ->setMethods(['doRequest']) 55 | ->getMock(); 56 | 57 | $to = ['79112238844', '79991112233', '79129998877']; 58 | $msg = 'Test'; 59 | $options = ['test_1' => 1]; 60 | 61 | $provider->expects($this->once()) 62 | ->method('doRequest') 63 | ->with($this->equalTo(array_merge( 64 | [ 65 | 'login' => $login, 66 | 'psw' => $password, 67 | 'phones' => implode(SmscRu::PHONE_DELIMITER, $to), 68 | 'mes' => mb_convert_encoding($msg, 'Windows-1251'), 69 | 'fmt' => 3 70 | ], 71 | $options 72 | ))) 73 | ->willReturn($sendResponse); 74 | 75 | $result = $provider->sendBatch($to, $msg, $options); 76 | 77 | $this->assertIsBool($result); 78 | $this->assertEquals($expectedReturnValue, $result); 79 | } 80 | 81 | /** 82 | * @return array 83 | */ 84 | public function dpSendBatch() 85 | { 86 | return [ 87 | [ 88 | ['success' => true], 89 | true, 90 | ], 91 | [ 92 | ['error' => 'Bad call'], 93 | false, 94 | ], 95 | [ 96 | false, 97 | false, 98 | ], 99 | [ 100 | 0, 101 | false, 102 | ] 103 | ]; 104 | } 105 | } -------------------------------------------------------------------------------- /tests/SmsSenderTest.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Tests; 8 | 9 | use Nutnet\LaravelSms\Contracts\Provider; 10 | use Nutnet\LaravelSms\SmsSender; 11 | 12 | class SmsSenderTest extends BaseTestCase 13 | { 14 | public function testPassingSendDefaultOptions() 15 | { 16 | $provider = $this->createMock(Provider::class); 17 | $provider 18 | ->expects($this->once()) 19 | ->method('send') 20 | ->with('999999', 'test msg', [ 21 | 'test_option' => 5, 22 | 'test_option_2' => 7 23 | ]); 24 | $provider 25 | ->expects($this->once()) 26 | ->method('sendBatch') 27 | ->with(['999999', '10101010'], 'test msg', [ 28 | 'test_option' => 5, 29 | 'test_option_2' => 3 30 | ]); 31 | 32 | $sender = new SmsSender( 33 | $provider, 34 | [ 35 | 'test_option' => 5, 36 | 'test_option_2' => 6 37 | ] 38 | ); 39 | 40 | $sender->send('+999999', 'test msg', [ 41 | 'test_option_2' => 7 42 | ]); 43 | 44 | $sender->sendBatch(['999999', '+10101010'], 'test msg', [ 45 | 'test_option_2' => 3 46 | ]); 47 | } 48 | } --------------------------------------------------------------------------------