本文目录导读:

在 PHP 中,“订阅管理”通常指以下几种场景,我分别给出解决方案:
事件订阅(Event Subscription)
使用内置的观察者模式
<?php
// 定义事件接口
interface Subject {
public function attach(Observer $observer);
public function detach(Observer $observer);
public function notify();
}
interface Observer {
public function update(Subject $subject);
}
// 具体实现
class Order implements Subject {
private $observers = [];
private $status;
public function attach(Observer $observer) {
$this->observers[] = $observer;
}
public function detach(Observer $observer) {
foreach ($this->observers as $key => $obs) {
if ($obs === $observer) {
unset($this->observers[$key]);
}
}
}
public function notify() {
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function setStatus($status) {
$this->status = $status;
$this->notify();
}
public function getStatus() {
return $this->status;
}
}
class EmailNotifier implements Observer {
public function update(Subject $subject) {
echo "发送邮件通知:订单状态变为 {$subject->getStatus()}\n";
}
}
class SMSNotifier implements Observer {
public function update(Subject $subject) {
echo "发送短信通知:订单状态变为 {$subject->getStatus()}\n";
}
}
// 使用
$order = new Order();
$order->attach(new EmailNotifier());
$order->attach(new SMSNotifier());
$order->setStatus('已发货');
Redis 发布/订阅(消息队列场景)
<?php
// 订阅端
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
// 订阅频道(阻塞方式)
$redis->subscribe(['channel1', 'channel2'], function($redis, $channel, $message) {
echo "收到来自 {$channel} 的消息: {$message}\n";
});
// 或者使用 psubscribe 支持模式匹配
$redis->psubscribe(['news.*'], function($redis, $pattern, $channel, $message) {
echo "模式 {$pattern} 匹配到 {$channel}: {$message}\n";
});
// 发布端
$redis->publish('channel1', 'Hello World');
$redis->publish('news.sports', '体育新闻');
数据库驱动的订阅管理(用户订阅功能)
<?php
class SubscriptionManager {
private $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
// 创建订阅
public function subscribe($userId, $planId, $duration) {
$sql = "INSERT INTO subscriptions
(user_id, plan_id, start_date, end_date, status)
VALUES (?, ?, NOW(), DATE_ADD(NOW(), INTERVAL ? DAY), 'active')";
$stmt = $this->pdo->prepare($sql);
return $stmt->execute([$userId, $planId, $duration]);
}
// 取消订阅
public function unsubscribe($userId) {
$sql = "UPDATE subscriptions
SET status = 'cancelled', cancelled_at = NOW()
WHERE user_id = ? AND status = 'active'";
$stmt = $this->pdo->prepare($sql);
return $stmt->execute([$userId]);
}
// 检查订阅状态
public function checkSubscription($userId) {
$sql = "SELECT * FROM subscriptions
WHERE user_id = ? AND status = 'active'
AND end_date > NOW()
ORDER BY end_date DESC LIMIT 1";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([$userId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
// 续订
public function renew($userId, $days) {
$current = $this->checkSubscription($userId);
if ($current) {
$sql = "UPDATE subscriptions SET end_date = DATE_ADD(end_date, INTERVAL ? DAY) WHERE id = ?";
return $this->pdo->prepare($sql)->execute([$days, $current['id']]);
}
return $this->subscribe($userId, 1, $days);
}
}
WebSocket 订阅(实时推送)
<?php
// 使用 Ratchet 库
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class WebSocketSubscriber implements MessageComponentInterface {
protected $clients;
protected $subscriptions;
public function __construct() {
$this->clients = new \SplObjectStorage;
$this->subscriptions = [];
}
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
echo "新连接: {$conn->resourceId}\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
$data = json_decode($msg, true);
if (isset($data['action'])) {
switch ($data['action']) {
case 'subscribe':
$this->addSubscription($from, $data['channel']);
break;
case 'unsubscribe':
$this->removeSubscription($from, $data['channel']);
break;
}
}
}
private function addSubscription($conn, $channel) {
if (!isset($this->subscriptions[$channel])) {
$this->subscriptions[$channel] = [];
}
$this->subscriptions[$channel][$conn->resourceId] = $conn;
}
private function removeSubscription($conn, $channel) {
if (isset($this->subscriptions[$channel])) {
unset($this->subscriptions[$channel][$conn->resourceId]);
}
}
public function publish($channel, $message) {
if (!isset($this->subscriptions[$channel])) return;
foreach ($this->subscriptions[$channel] as $client) {
$client->send(json_encode([
'channel' => $channel,
'message' => $message
]));
}
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
$this->cleanupSubscriptions($conn);
}
public function onError(ConnectionInterface $conn, \Exception $e) {
echo "错误: {$e->getMessage()}\n";
$conn->close();
}
}
使用成熟库(推荐)
使用 RabbitMQ(php-amqplib)
<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
// 发布订阅模式
function createPublisher() {
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
// 声明交换器(fanout 广播模式)
$channel->exchange_declare('notifications', 'fanout', false, false, false);
$message = new AMQPMessage('Hello World!');
$channel->basic_publish($message, 'notifications');
$channel->close();
$connection->close();
}
function createSubscriber() {
$connection = new AMQPStreamConnection('localhost', 5672, 'guest', 'guest');
$channel = $connection->channel();
$channel->exchange_declare('notifications', 'fanout', false, false, false);
// 声明临时队列
list($queue_name, ,) = $channel->queue_declare("", false, false, true, false);
// 绑定队列到交换器
$channel->queue_bind($queue_name, 'notifications');
echo "等待消息,退出请按 CTRL+C\n";
$callback = function ($msg) {
echo '收到消息: ', $msg->body, "\n";
};
$channel->basic_consume($queue_name, '', false, true, false, false, $callback);
while ($channel->is_consuming()) {
$channel->wait();
}
}
最佳实践建议
订阅管理器类(综合方案)
<?php
class SubscriptionHandler {
private $subscribers = [];
private $persistence;
public function __construct(PersistenceInterface $persistence) {
$this->persistence = $persistence;
}
public function registerSubscriber($eventType, callable $callback, $id = null) {
$subscriberId = $id ?? uniqid('sub_', true);
$this->subscribers[$eventType][$subscriberId] = $callback;
// 持久化存储
$this->persistence->save('subscription', [
'id' => $subscriberId,
'event_type' => $eventType,
'callback' => $callback
]);
return $subscriberId;
}
public function unregisterSubscriber($eventType, $subscriberId) {
if (isset($this->subscribers[$eventType][$subscriberId])) {
unset($this->subscribers[$eventType][$subscriberId]);
$this->persistence->delete('subscription', $subscriberId);
return true;
}
return false;
}
public function triggerEvent($eventType, $payload = null) {
if (!isset($this->subscribers[$eventType])) return;
foreach ($this->subscribers[$eventType] as $callback) {
call_user_func($callback, $payload);
}
}
public function getSubscribers($eventType = null) {
if ($eventType) {
return $this->subscribers[$eventType] ?? [];
}
return $this->subscribers;
}
}
选择建议
| 使用场景 | 推荐方案 |
|---|---|
| 简单事件通知 | 内置观察者模式 |
| 跨进程/跨服务通信 | Redis Pub/Sub 或 RabbitMQ |
| 网站订阅功能 | 数据库实现 + 定时任务 |
| 实时推送 | WebSocket + 消息队列 |
| 复杂事件驱动架构 | Symfony EventDispatcher 或 Laravel Events |
根据你的具体需求(是事件订阅、消息订阅、还是用户订阅管理),选择最合适的方式实现即可。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播