PHP项目Symfony SMS与Twilio

wen PHP项目 2

本文目录导读:

PHP项目Symfony SMS与Twilio

  1. 安装依赖
  2. 配置Twilio参数
  3. 创建SMS服务类
  4. 创建Form类型
  5. 创建Controller
  6. 创建模板
  7. 创建验证码生成器
  8. 创建异步发送(可选)
  9. 使用示例
  10. 注意事项

我将为您详细介绍如何在Symfony项目中集成Twilio实现短信发送功能。

安装依赖

composer require twilio/sdk

配置Twilio参数

.env 文件中添加配置:

TWILIO_ACCOUNT_SID=your_account_sid
TWILIO_AUTH_TOKEN=your_auth_token
TWILIO_FROM_NUMBER=+1234567890

创建Twilio配置服务

// config/services.yaml
services:
    App\Service\TwilioService:
        arguments:
            $accountSid: '%env(TWILIO_ACCOUNT_SID)%'
            $authToken: '%env(TWILIO_AUTH_TOKEN)%'
            $fromNumber: '%env(TWILIO_FROM_NUMBER)%'

创建SMS服务类

// src/Service/TwilioService.php
namespace App\Service;
use Twilio\Rest\Client;
use Psr\Log\LoggerInterface;
class TwilioService
{
    private Client $client;
    private string $fromNumber;
    private LoggerInterface $logger;
    public function __construct(
        string $accountSid,
        string $authToken,
        string $fromNumber,
        LoggerInterface $logger
    ) {
        $this->client = new Client($accountSid, $authToken);
        $this->fromNumber = $fromNumber;
        $this->logger = $logger;
    }
    /**
     * 发送短信
     */
    public function sendSms(string $to, string $message, array $options = []): array
    {
        try {
            $sms = $this->client->messages->create(
                $to,
                array_merge([
                    'from' => $this->fromNumber,
                    'body' => $message,
                ], $options)
            );
            $this->logger->info('SMS sent successfully', [
                'to' => $to,
                'sid' => $sms->sid,
                'status' => $sms->status
            ]);
            return [
                'success' => true,
                'sid' => $sms->sid,
                'status' => $sms->status,
                'message' => 'SMS sent successfully'
            ];
        } catch (\Exception $e) {
            $this->logger->error('Failed to send SMS', [
                'to' => $to,
                'error' => $e->getMessage()
            ]);
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
    /**
     * 发送验证码
     */
    public function sendVerificationCode(string $to, string $code): array
    {
        $message = sprintf(
            '您的验证码是: %s,有效期为5分钟。',
            $code
        );
        return $this->sendSms($to, $message);
    }
    /**
     * 批量发送短信
     */
    public function sendBulkSms(array $recipients, string $message): array
    {
        $results = [];
        foreach ($recipients as $recipient) {
            $results[] = $this->sendSms($recipient, $message);
        }
        return $results;
    }
    /**
     * 获取短信状态
     */
    public function getMessageStatus(string $sid): ?string
    {
        try {
            $message = $this->client->messages($sid)->fetch();
            return $message->status;
        } catch (\Exception $e) {
            $this->logger->error('Failed to get message status', [
                'sid' => $sid,
                'error' => $e->getMessage()
            ]);
            return null;
        }
    }
}

创建Form类型

// src/Form/SmsType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TelType;
use Symfony\Component\Validator\Constraints as Assert;
class SmsType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('phoneNumber', TelType::class, [
                'label' => '手机号码',
                'constraints' => [
                    new Assert\NotBlank(['message' => '请填写手机号码']),
                    new Assert\Regex([
                        'pattern' => '/^\+?[1-9]\d{1,14}$/',
                        'message' => '请输入有效的手机号码(包含国家代码)'
                    ])
                ]
            ])
            ->add('message', TextareaType::class, [
                'label' => '短信内容',
                'constraints' => [
                    new Assert\NotBlank(['message' => '请填写短信内容']),
                    new Assert\Length([
                        'max' => 160,
                        'maxMessage' => '短信内容不能超过{{ limit }}个字符'
                    ])
                ]
            ])
            ->add('verificationCode', TextType::class, [
                'label' => '验证码(可选)',
                'required' => false,
                'constraints' => [
                    new Assert\Length([
                        'min' => 6,
                        'max' => 6,
                        'exactMessage' => '验证码必须为6位数字'
                    ])
                ]
            ]);
    }
}

创建Controller

// src/Controller/SmsController.php
namespace App\Controller;
use App\Service\TwilioService;
use App\Form\SmsType;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;
#[Route('/sms')]
#[IsGranted('ROLE_USER')]
class SmsController extends AbstractController
{
    public function __construct(
        private TwilioService $twilioService
    ) {}
    #[Route('/send', name: 'sms_send')]
    public function send(Request $request): Response
    {
        $result = null;
        $form = $this->createForm(SmsType::class);
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();
            if ($data['verificationCode']) {
                $result = $this->twilioService->sendVerificationCode(
                    $data['phoneNumber'],
                    $data['verificationCode']
                );
            } else {
                $result = $this->twilioService->sendSms(
                    $data['phoneNumber'],
                    $data['message']
                );
            }
            if ($result['success']) {
                $this->addFlash('success', '短信发送成功!SID: ' . $result['sid']);
            } else {
                $this->addFlash('error', '短信发送失败:' . $result['error']);
            }
            return $this->redirectToRoute('sms_send');
        }
        return $this->render('sms/send.html.twig', [
            'form' => $form->createView(),
            'result' => $result
        ]);
    }
    #[Route('/status/{sid}', name: 'sms_status')]
    public function status(string $sid): Response
    {
        $status = $this->twilioService->getMessageStatus($sid);
        return $this->json([
            'sid' => $sid,
            'status' => $status
        ]);
    }
    #[Route('/send-bulk', name: 'sms_send_bulk')]
    public function sendBulk(Request $request): Response
    {
        if ($request->isMethod('POST')) {
            $recipients = $request->request->all('recipients');
            $message = $request->request->get('message');
            $results = $this->twilioService->sendBulkSms($recipients, $message);
            $successCount = count(array_filter($results, fn($r) => $r['success']));
            $this->addFlash('success', "成功发送 {$successCount}/" . count($results) . " 条短信");
            return $this->redirectToRoute('sms_send_bulk');
        }
        return $this->render('sms/send_bulk.html.twig');
    }
}

创建模板

{# templates/sms/send.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}发送短信{% endblock %}
{% block body %}
<div class="container mt-4">
    <h1>发送短信</h1>
    {% for message in app.flashes('success') %}
        <div class="alert alert-success">{{ message }}</div>
    {% endfor %}
    {% for message in app.flashes('error') %}
        <div class="alert alert-danger">{{ message }}</div>
    {% endfor %}
    {{ form_start(form, {'attr': {'class': 'needs-validation'}}) }}
        <div class="mb-3">
            {{ form_label(form.phoneNumber, null, {'label_attr': {'class': 'form-label'}}) }}
            {{ form_widget(form.phoneNumber, {'attr': {'class': 'form-control'}}) }}
            {{ form_errors(form.phoneNumber) }}
        </div>
        <div class="mb-3">
            {{ form_label(form.message, null, {'label_attr': {'class': 'form-label'}}) }}
            {{ form_widget(form.message, {'attr': {'class': 'form-control', 'rows': 3}}) }}
            {{ form_errors(form.message) }}
        </div>
        <div class="mb-3">
            {{ form_label(form.verificationCode, null, {'label_attr': {'class': 'form-label'}}) }}
            {{ form_widget(form.verificationCode, {'attr': {'class': 'form-control'}}) }}
            <small class="form-text text-muted">如果需要发送验证码,请填写6位数字</small>
            {{ form_errors(form.verificationCode) }}
        </div>
        <button type="submit" class="btn btn-primary">发送短信</button>
    {{ form_end(form) }}
</div>
{% endblock %}

创建验证码生成器

// src/Service/VerificationCodeGenerator.php
namespace App\Service;
class VerificationCodeGenerator
{
    public function generate(int $length = 6): string
    {
        $code = '';
        for ($i = 0; $i < $length; $i++) {
            $code .= random_int(0, 9);
        }
        return $code;
    }
    public function store(string $phoneNumber, string $code): void
    {
        // 存储验证码到缓存或数据库
        // 例如使用Redis或Symfony Cache组件
        // 示例:使用文件缓存(生产环境应使用Redis等)
        $cacheFile = sprintf('/tmp/sms_verification_%s.json', md5($phoneNumber));
        file_put_contents($cacheFile, json_encode([
            'code' => $code,
            'expires_at' => time() + 300 // 5分钟有效期
        ]));
    }
    public function verify(string $phoneNumber, string $code): bool
    {
        $cacheFile = sprintf('/tmp/sms_verification_%s.json', md5($phoneNumber));
        if (!file_exists($cacheFile)) {
            return false;
        }
        $data = json_decode(file_get_contents($cacheFile), true);
        if ($data['expires_at'] < time()) {
            unlink($cacheFile);
            return false;
        }
        return $data['code'] === $code;
    }
}

创建异步发送(可选)

// src/Message/SmsNotification.php
namespace App\Message;
class SmsNotification
{
    public function __construct(
        private string $phoneNumber,
        private string $message
    ) {}
    public function getPhoneNumber(): string
    {
        return $this->phoneNumber;
    }
    public function getMessage(): string
    {
        return $this->message;
    }
}
// src/MessageHandler/SmsNotificationHandler.php
namespace App\MessageHandler;
use App\Message\SmsNotification;
use App\Service\TwilioService;
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
#[AsMessageHandler]
class SmsNotificationHandler
{
    public function __construct(
        private TwilioService $twilioService
    ) {}
    public function __invoke(SmsNotification $message): void
    {
        $this->twilioService->sendSms(
            $message->getPhoneNumber(),
            $message->getMessage()
        );
    }
}

使用示例

// 在Controller中使用
class SomeController extends AbstractController
{
    public function __construct(
        private TwilioService $twilioService,
        private VerificationCodeGenerator $codeGenerator
    ) {}
    public function sendVerification(Request $request): Response
    {
        $phoneNumber = $request->get('phone');
        $code = $this->codeGenerator->generate();
        // 存储验证码
        $this->codeGenerator->store($phoneNumber, $code);
        // 发送短信
        $result = $this->twilioService->sendVerificationCode($phoneNumber, $code);
        if ($result['success']) {
            return $this->json(['message' => '验证码已发送']);
        }
        return $this->json(['error' => '发送失败'], 500);
    }
}

注意事项

  1. 安全: 妥善保管Twilio凭证,不要提交到版本控制
  2. 验证: 始终验证电话号码格式
  3. 限制: 设置发送频率限制,防止滥用
  4. 日志: 记录所有短信发送操作
  5. 错误处理: 妥善处理所有异常情况
  6. 测试: 在开发环境使用Twilio测试凭证

这样就完成了Symfony项目中的Twilio SMS集成,记得根据实际需求调整配置和功能。

抱歉,评论功能暂时关闭!