通知是一个应用程序的基本功能,因为它们提供了一个渠道,让客户了解与他们账户有关的最新活动。短信通知,特别是,由于流通中的手机数量庞大,提供了一个很大的覆盖面。它们还有一个额外的优势,那就是可及性,因为即使是使用功能手机的客户也能够收到通知。
因此,问题就变成了:你如何向大量的手机有效地发送短信通知?这是因为按顺序发送短信通知很可能会产生一个可扩展性瓶颈。
所以在这篇文章中,我将向你展示如何使用Twilio和PHP的Yii2框架批量发送短信通知,Twilio使得管理/调度可编程的短信变得轻而易举。我们将建立一个应用程序,通过Twilio的短信API向客户批量发送定制的短信通知。
教程要求
要学习本教程,你需要以下组件:
- PHP 7.4。最好是8版本。
- 全局安装的Composer。
- 一个免费或付费的Twilio账户。如果你是Twilio的新手,请 点击这里,现在就可以 创建一个免费账户,当你升级到付费账户时,会收到10美元的积分。
- 一个拥有有效电话号码的智能手机。
开始使用
要开始使用,请创建一个名为bulk_sms_app 的新应用程序,并使用以下命令切换到项目目录:
composer create-project --prefer-dist yiisoft/yii2-app-basic bulk_sms_app
cd bulk_sms_app
安装所需的依赖性
该应用程序需要一个外部依赖,即Twilio PHP Helper库,以便它能与Twilio通信。使用下面的命令安装它:
composer require twilio/sdk
测试应用程序是否工作
然后用下面的命令启动该应用程序:
php yii serve
默认情况下,该应用程序将在http://localhost:8080/。在你喜欢的网络浏览器中打开该网址,你应该看到默认页面,如下图所示。

返回到终端,按Control + C ,退出应用程序。
更新应用程序的配置
为了让应用程序工作,它需要设置几个配置选项。这些是你的Twilio账户SID、 Auth Token和电话号码,以及一个单独的消息传递ID。
在这些被检索之前,更新config/params.php以匹配以下内容,这样设置就可以用占位符的值:
<?php
return [
'TWILIO_ACCOUNT_SID' => "your_twilio_account_sid",
'TWILIO_AUTH_TOKEN' => "your_twilio_auth_token",
'TWILIO_MESSAGING_SID' => "your_twilio_messaging_sid",
'TWILIO_PHONE_NUMBER' => "your_twilio_phone_number",
];
接下来,从Twilio仪表板检索你的Twilio电话号码,"ACCOUNT SID",和 "AUTH TOKEN"。用这三个值替换config/params.php中各自的占位符值。
作为一项安全预防措施,你的AUTH TOKEN不会显示在屏幕上。点击 "复制 "图标来复制它。
然后,你需要设置一个消息服务,以便你可以检索其消息ID。要做到这一点,打开Twilio控制台,导航到*"所有产品和服务 > 可编程消息服务 >* 消息服务"。一旦到达那里,点击蓝色的*"创建消息服务*"按钮。

设置 "Messaging Service friendly name "为 "yii_bulk_sms",因为我们的用例是通知用户,然后点击 "Create Messaging Service"。
之后,我们需要为我们的服务添加一个发件人。具体来说,我们需要一个电话号码。点击 "添加发件人",选择 "电话号码",然后点击 "继续"。
如果你没有(或需要一个新的),你可以购买更多的号码。

然后,通过点击 "第3步:设置集成 "完成,在接下来的页面点击 "第4步:合规信息",最后在之后的页面点击 "完成消息服务设置"。
现在服务已经创建,复制消息服务的SID,用它来替换config/params.php中TWILIO_MESSAGING_SID'的占位符数值。

设置数据库
对于这个应用程序,我们将使用SQLite作为数据库。在应用程序的根目录下创建一个名为db的新目录。在该目录中,创建一个名为app.db的文件*。然后,更新config/db.php*以匹配以下代码:
<?php
return [
'class' => 'yii\db\Connection',
'dsn' => 'sqlite:' . dirname(__DIR__) . '/db/app.db',
'charset' => 'utf8',
];
确保你用来运行代码的用户在db目录上有写权限。
完成这些后,我们现在需要创建几个数据库迁移,以简化数据库的支架。第一个将创建一个名为 "client "的用户表。这个表将有两列;一列是姓名,一列是电话号码。
使用yii migrate/create 命令来创建它,提供要创建的迁移的名称,如下图所示:
php yii migrate/create create_client_table
当系统提示确认时,输入 "是 "并按回车键。
默认情况下,迁移文件位于项目根目录下的 migrations 目录中。迁移文件名的前缀是字母m和创建时的UTC日期时间。例如m210809_113722_create_client_table.php。
打开刚刚创建的migrations/m<YYMMDD_HHMMSS>_create_user_table.php,修改safeUp 和safeDown 函数,使其与下面的代码一致:
public function safeUp()
{
$this->createTable('client', [
'id' => $this->primaryKey(),
'name' => $this->string()->notNull(),
'phoneNumber' => $this->string(12)->notNull(),
]);
}
public function safeDown()
{
$this->dropTable('client');
}
第二个迁移将为客户表注入一组假数据,这样我们的应用程序就有一些数据可以使用了。使用下面的命令来创建它:
php yii migrate/create seed_client_table
和以前一样,当被要求确认时,输入 "yes "并按回车键。然后,打开migrations/m<YYMMDD_HHMMSS>_seed_client_table.php,修改safeUp 函数,使之与下面的代码一致:
public function safeUp()
{
$this->insertFakeMembers();
}
private function insertFakeMembers()
{
$faker = Faker\Factory::create();
for ($i = 0; $i < 1000; $i++) {
$this->insert(
'client',
[
'name' => $faker->name(),
'phoneNumber' => $faker->e164PhoneNumber()
]
);
}
}
这将向客户表插入1000条记录。
创建好这两个迁移程序后,使用下面的命令运行它们:
php yii migrate
当要求确认时,输入 "yes "并按回车键。运行完迁移后,你可以使用sqlite3命令行工具来验证数据库是否已经创建并适当地播种,就像下面的命令示例那样:
sqlite3 db/app.db "select * from client limit 100;"
数据库中的前100个客户将被打印到命令行中,与下面的截图类似:

创建客户模型
我们将创建一个ActiveRecord模型,它将为我们管理查询的创建和执行,而不是编写原始的SQL查询来与数据库互动。更重要的是,通过这样做,我们将有一个面向对象的方法来访问和存储数据库中的数据。使用下面的命令为Client 实体创建一个模型。当出现提示时,按回车键。
php yii gii/model --tableName=client --modelClass=Client
这个新的类将被创建在一个名为models的新目录下,位于项目的根目录下。
创建客户端控制器
有了数据库和模型,我们现在可以创建控制器来处理客户的显示和短信通知的发送。使用下面的命令创建一个控制器来处理与客户有关的请求;在提示时输入 "yes "并按回车:
php yii gii/controller --controllerClass="app\controllers\ClientController"
完成后,将创建两个新的文件:controllers/ClientController.php(它扩展了yii/web/Controller),以及views/client/index.php。
为了让API能够处理POST请求,CSRF验证将在ClientController.php中被禁用*。*要做到这一点,请在该类的开头添加以下内容:
public $enableCsrfValidation = false;
在没有额外检查的情况下,禁用CSRF可能会使你的应用程序暴露在安全威胁之下。
在我们创建处理请求的动作之前,我们需要为新的API端点添加路由规则。要做到这一点,在config/web.php中取消对urlManager 组件的注释,并在其rules元素中添加以下代码:
'GET clients' => 'client/index',
'POST clients/notify' => 'client/notify',
使用Yii提供的速记符号,我们可以为一个特定的URL指定路由。URL是作为一个键提供的,而路由是作为一个值提供给相应的行动。通过在URL前加上一个HTTP动词,应用程序能够处理具有相同模式或不同动作的URL。你可以在这里阅读更多关于这方面的内容。
该应用程序将有两个路由:/clients 和/clients/notify 。第一个路由(*/clients*)将被用来显示保存在数据库中的客户列表。这将在ClientController.php中由一个名为actionIndex的函数处理。
第二条路线(*/clients/notify*)将被用来向所提供的客户批量发送短信通知。它也将在ClientController.php中由一个名为actionNotify的函数来处理。
为了让我们的控制器能够解析JSON请求,我们需要给我们的应用组件添加一个JSON解析器。config/web.php中的components 数组包含了一个请求数组,它保存了请求应用组件的配置。
为了添加JSON解析器,在request 数组中,在cookieValidationKey 元素之后添加以下内容:
'parsers' => [
'application/json' => 'yii\\web\\JsonParser',
]
然后,更新controllers/ClientController.php以符合下面的例子:
<?php
namespace app\controllers;
use app\models\Client;
use Yii;
use yii\data\Pagination;
use yii\web\Controller;
class ClientController extends Controller
{
public $enableCsrfValidation = false;
public function actionIndex()
{
$query = Client::find();
$count = $query->count();
$pagination = new Pagination(['totalCount' => $count]);
$clients = $query->offset($pagination->offset)
->limit($pagination->limit)
->all();
$this->view->title = 'Clients';
return $this->render(
'index',
[
'clients' => $clients,
'pagination' => $pagination
]
);
}
public function actionNotify()
{
$request = Yii::$app->request;
$clients = $request->post('clients');
$smsContent = $request->post('smsContent');
return $this->asJson(
[
'message' => 'Notifications sent successfully'
]
);
}
}
在actionIndex 函数中,我们使用分页法从数据库中分批检索客户。这些客户,连同分页对象,被传递给位于views/layouts/main.php的视图*。*
actionNotify函数做了三件事它:
- 接受请求并检索客户端和smsContent键。
- 返回一个带有成功信息的JSON响应。向选定的客户发送消息。
创建客户视图
客户列表将显示在一个表格中。在列表中的每个客户旁边,将有一个复选框来表示哪个客户被选中,在表格的底部将有一个按钮来发送短信给所有选中的客户。你可以在下面的图片中看到一个模拟图。

点击这个按钮将触发一个弹出窗口,在里面输入短信内容。提交该表格将产生一个POST请求,其中包含短信的内容和要通知的客户。一旦请求成功完成,将显示一个警报。
在编辑视图之前,让我们编辑主布局,删除默认的Yii2头和脚,并通过CDN导入SweetAlert。SweetAlert将被用于显示弹出式表单和通知。
打开views/layouts/main.php并更新它,使之与以下内容相匹配:
<?php
/* @var $this \yii\web\View */
/* @var $content string */
use app\assets\AppAsset;
use yii\helpers\Html;
AppAsset::register($this);
?>
<?php
$this->beginPage() ?>
<!DOCTYPE html>
<html lang="<?= Yii::$app->language ?>">
<head>
<meta charset="<?= Yii::$app->charset ?>">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<?php $this->registerCsrfMetaTags() ?>
<title><?= Html::encode($this->title) ?></title>
<?php $this->head() ?>
</head>
<body>
<?php $this->beginBody() ?>
<div class="container">
<?= $content ?>
</div>
<script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
<?php $this->endBody() ?>
</body>
</html>
<?php $this->endPage() ?>
接下来,更新views/client/index.php以匹配以下内容:
<?php
/* @var $this yii\web\View */
use yii\widgets\LinkPager;
?>
<h1>Clients</h1>
<table class="table" id="clientsTable">
<thead>
<tr>
<th> </th>
<th>#</th>
<th>Name</th>
<th>Phone number</th>
</tr>
</thead>
<?php
foreach ($clients as $i => $client): ?>
<tr>
<td><input type="checkbox"/></td>
<td style="display: none"><?= $client->id ?></td>
<td><?= $i + 1 ?></td>
<td><?= $client->name ?></td>
<td><?= $client->phoneNumber ?></td>
</tr>
<?php
endforeach; ?>
</table>
<button
class='btn btn-primary'
style="display: block; margin: auto"
onclick="sendSMS()"
>Send SMS</button>
<div style="width: 50%; margin: auto">
<?php
echo LinkPager::widget(
[
'pagination' => $pagination,
]
); ?>
</div>
<script>
const getSelectedClients = () => {
const table = document.getElementById('clientsTable');
const checkboxes = Array.from(table.getElementsByTagName('input'));
const selectedClients = [];
checkboxes
.filter(checkbox => checkbox.checked)
.forEach(checkbox => {
let row = checkbox.parentNode.parentNode;
selectedClients.push(row.cells[1].innerHTML)
}
)
return selectedClients;
}
const sendSMS = () => {
Swal.fire({
title: 'Enter the content of the SMS',
input: 'textarea',
inputAttributes: {
autocapitalize: 'off'
},
showCancelButton: true,
confirmButtonText: 'Send',
showLoaderOnConfirm: true,
preConfirm: (smsContent) => {
const data = {
clients: getSelectedClients(),
smsContent
};
return fetch('clients/notify', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
}).then(response => response.json()).then(data => {
Swal.fire({
title: data.message,
})
}).catch(error => {
Swal.showValidationMessage(
`Request failed: ${error}`
)
})
},
backdrop: true,
allowOutsideClick: () => !Swal.isLoading()
})
}
</script>
除了渲染一个包含内容的表格,我们还添加了一个脚本,包含两个函数:sendSMS 和getSelectedClients 。
sendSMS 是在点击发送短信按钮时被调用。这个函数触发了弹出的表单,在那里要输入短信内容。一旦提交表格,就会使用 ,检索所选择的客户。getSelectedClients
与短信内容一起,通过fetch API向clients/notify 端点发出一个POST请求。响应信息会显示在一个警报中,用户可以关闭。
创建一个帮助类来批量发送短信
在项目的根部,创建一个名为helpers的新文件夹。然后,在该目录下,使用你喜欢的IDE或文本编辑器,创建一个名为TwilioSMSHelper.php的新文件,在其中加入以下代码:
<?php
namespace app\helpers;
use Twilio\Rest\Client;
use Yii;
class TwilioSMSHelper
{
private string $phoneNumber;
private string $messagingSID;
private Client $twilio;
public function __construct()
{
$params = Yii::$app->params;
$accountSID = $params['TWILIO_ACCOUNT_SID'];
$authToken = $params['TWILIO_AUTH_TOKEN'];
$this->phoneNumber = $params['TWILIO_PHONE_NUMBER'];
$this->messagingSID = $params['TWILIO_MESSAGING_SID'];
$this->twilio = new Client($accountSID, $authToken);
}
public function sendBulkNotifications(array $clients, string $message)
{
foreach ($clients as $client) {
$this->twilio->messages->create(
$client->phoneNumber,
[
'body' => "Dear {$client->name} \n\n$message",
'from' => $this->phoneNumber,
'messagingServiceSid' => $this->messagingSID
]
);
}
}
}
由于我们使用FakerPHP来生成电话号码,你可以在create 函数中硬编码一个有效的电话号码用于测试。
在构造函数中,我们使用TwilioClient 对象的 twilio_account_sid和 twilio_auth_token我们在config/params.php中设置了一个Twilio对象。我们还提取了电话号码和消息服务ID,作为消息配置的一部分。这些都被保存为类中的私有字段。在sendBulkNotifications 函数中,我们循环浏览客户,为每个客户创建一个新的消息。
没有必要在你的逻辑中添加延迟。你可以随心所欲地发送消息,因为Twilio会按照你规定的速率限制排队发送。
添加批量通知功能
在controllers/ClientController.php中*,*更新actionNotify 函数,使其与以下内容相匹配:
public function actionNotify()
{
$request = Yii::$app->request;
$clientIds = $request->post('clients');
$smsContent = $request->post('smsContent');
$clients = [];
foreach ($clientIds as $clientId) {
$clients[] = Client::findOne($clientId);
}
$smsHelper = new TwilioSMSHelper();
$smsHelper->sendBulkNotifications($clients, $smsContent);
return $this->asJson(
[
'message' => 'Notifications sent successfully'
]
);
}
不要忘记下面的TwilioSMSHelper类的导入语句:
use app\helpers\TwilioSMSHelper;
有了这些,我们就可以向客户发送自定义的短信通知了。
还记得我们用FakerPHP为数据库中的每个客户生成随机和无效的电话号码吗?如果被选中,Twilio的API将无法连接这些号码,并在此过程中抛出一个错误。
为了测试,你可以通过使用下面的脚本改变一个或两个数字,并改成有效数字:
sqlite3 db/app.db "UPDATE client SET phoneNumber = '+2349057042039' WHERE id = 2;"
这将把数据库中的第二个电话号码的值改为上面指定的号码。
现在,再次启动应用程序,在项目根目录的终端运行php yii serve 。然后,在你选择的浏览器中打开http:://localhost:8080/client,选择一些客户端。

之后,点击 "发送短信 "按钮,弹出表单,输入信息,并点击发送。你将会得到成功通知,以及发送到所提供电话号码的短信。


总结
在这篇文章中,我们研究了如何使用Twilio发送批量短信通知。集成Twilio SDK简化了创建和发送通知的过程。
此外,你不必担心排队和发送过程,因为Twilio基础设施能够为你的用户实时处理通知。
本教程的整个代码库可在GitHub上找到。请自由地进一步探索。编码愉快!