Title: PHP SDK Locale: zh URL: https://sensorswave.com/docs/data-integration/server-sdks/php/ Description: PHP SDK 集成指南和 API 参考 Sensors Wave PHP SDK 是一个面向 PHP/FPM 环境的轻量级事件追踪和 A/B 测试工具,支持用户事件追踪、用户属性管理和完整的 A/B 测试功能。SDK 采用离线运行时架构,在请求路径上消除远程 I/O,通过独立的 Worker 进程完成网络通信,确保不影响 Web 请求的响应性能。 ## 核心功能 PHP SDK 提供以下核心能力: - **事件追踪**:追踪用户事件和自定义属性 - **用户属性管理**:支持设置、增量更新、追加等完整的用户属性操作 - **A/B 测试集成**:支持功能开关、实验变量和动态配置 - **自动曝光日志**:自动记录 A/B 测试的曝光事件 - **离线运行时**:请求路径无远程 I/O,事件写入本地队列,A/B 从本地快照读取 - **可插拔适配器**:默认使用文件存储,支持 Redis 等自定义适配器 ## 环境要求 - PHP >= 8.2 - 无外部生产依赖 ## 安装 使用 Composer 安装 SDK: ```bash composer require sensorswave/sdk-php ``` ## 运行时架构 > [!NOTE] > > PHP SDK 的架构与其他语言 SDK 不同。由于 PHP/FPM 的进程模型特点,SDK 将同步的请求逻辑与异步的网络 I/O 分离,确保 Web 请求不被阻塞。 SDK 由三部分组成: 1. **客户端(请求路径)**:在 Web 请求中运行,事件写入 `EventQueue`,A/B 评估从本地 `ABSpecStore` 读取快照,**不发起任何远程调用** 2. **sensorswave-sync Worker**:独立进程,负责从远程拉取 A/B 元数据并保存到本地快照 3. **sensorswave-send Worker**:独立进程,负责从事件队列中取出事件并发送到数据收集端点 默认适配器使用 `sys_get_temp_dir()` 下的本地文件存储。SDK 同时提供了 Redis 适配器作为参考实现,生产环境中您应根据自身基础设施需求评估或自行实现 `ABSpecStoreInterface` / `EventQueueInterface`。 ```mermaid flowchart LR subgraph request["👤 Request Path"] Client([Client]) end subgraph sync["⏰ sensorswave-sync · Cron"] SyncCmd([SyncCommand]) end subgraph send["⏰ sensorswave-send · Cron"] SendCmd([SendCommand]) end ABStore[(💾 ABSpecStore)] Queue[(📨 EventQueue)] Meta["🌐 Meta EndpointPOST /ab/all4eval"] Collector["🌐 Collector EndpointPOST /in/track"] SyncCmd -- "fetch specs" --> Meta Meta -. "response" .-> SyncCmd SyncCmd -- "save()" --> ABStore ABStore -- "load()" --> Client Client -- "enqueue()" --> Queue Queue -- "dequeue()" --> SendCmd SendCmd -- "deliver" --> Collector Collector -. "ack / nack" .-> SendCmd classDef green fill:#d4edda,stroke:#28a745,stroke-width:2px,color:#155724 classDef blue fill:#cce5ff,stroke:#004085,stroke-width:2px,color:#004085 classDef purple fill:#e2d9f3,stroke:#6f42c1,stroke-width:2px,color:#4a2d8a classDef orange fill:#fff3cd,stroke:#d68a00,stroke-width:2px,color:#856404 class Client green class SyncCmd,SendCmd blue class ABStore,Queue purple class Meta,Collector orange style request fill:none,stroke:#28a745,stroke-width:2px,stroke-dasharray:5 5 style sync fill:none,stroke:#004085,stroke-width:2px,stroke-dasharray:5 5 style send fill:none,stroke:#004085,stroke-width:2px,stroke-dasharray:5 5 ``` ## 快速开始 ### 基础事件追踪 ```php trackEvent($user, 'PageView', [ 'page' => '/home', ]); // 关闭客户端 $client->close(); ``` ### 启用 A/B 测试 要启用 A/B 测试,需要提供 `ABConfig`: ```php getExperiment($user, 'my_experiment'); // 从实验结果中获取参数 $btnColor = $result->getString('button_color', 'blue'); $showBanner = $result->getBool('show_banner', false); $discount = $result->getNumber('discount_percent', 0); echo "按钮颜色: {$btnColor}, 显示横幅: " . ($showBanner ? '是' : '否') . ", 折扣: {$discount}%\n"; ``` ### 启动 Worker 进程 SDK 需要在后台运行 Worker 进程来处理网络 I/O。通过 cron、Supervisor 或 systemd 等方式调度运行: ```bash # 同步 A/B 元数据(需要启用 A/B 测试时运行) ./vendor/bin/sensorswave-sync # 发送事件数据 ./vendor/bin/sensorswave-send ``` > [!WARNING] > > Worker 进程是 PHP SDK 正常工作的必要组件。如果不启动 `sensorswave-send`,事件数据将积压在本地队列中无法上报;如果不启动 `sensorswave-sync`,A/B 测试将无法获取最新的实验配置。 --- ## User 类型 > [!WARNING] > > ### 🔑 用户身份要求(必读) > > **除了 `identify` 方法外的所有方法:** > > - ✅ `anonId` 或 `loginId` 至少有一个非空 > - ⚡ **如果两者都提供了,`loginId` 优先用于用户身份识别** > > **仅对于 `identify` 方法:** > > - ✅ **`anonId` 和 `loginId` 都必须非空** > - 🔗 这会创建一个 `$Identify` 事件,链接匿名和认证身份 ### 使用示例 **创建不同 ID 组合的用户:** ```php use SensorsWave\Model\User; // ✅ 有效:仅 loginId(用于已登录用户) $user = new User(loginId: 'user-123'); // ✅ 有效:仅 anonId(用于匿名用户) $user = new User(anonId: 'device-456'); // ✅ 有效:两者都有(loginId 优先用于识别) $user = new User(anonId: 'device-456', loginId: 'user-123'); // ❌ 无效:两者都未提供 - 这会失败 $user = new User(); ``` **对于 identify 方法 - 两个 ID 都必需:** ```php // ✅ 正确:两个 ID 都提供了 $client->identify(new User( anonId: 'device-456', // ✅ 必需 loginId: 'user-123', // ✅ 必需 )); // ❌ 无效:只有一个 ID - identify 会失败 $client->identify(new User( loginId: 'user-123', // ❌ 缺少 anonId )); ``` **添加 A/B 测试定向属性:** ```php // 创建用户 $user = new User(anonId: 'device-456', loginId: 'user-123'); // 添加 A/B 定向属性(不可变模式,返回新实例) $user = $user->withAbUserProperty('$app_version', '11.0'); $user = $user->withAbUserProperty('is_premium', true); // 或者一次添加多个属性 $user = $user->withAbUserProperties([ '$app_version' => '11.0', 'is_premium' => true, ]); ``` --- ## 事件追踪 ### identify - 关联用户身份 将匿名 ID 与登录 ID 关联(注册事件)。 ```php $user = new User(anonId: 'anon-123', loginId: 'user-456'); $client->identify($user); ``` > **注意**:如果 `loginId` 包含隐私信息(如手机号、邮箱、身份证号等),务必在加密后再进行传输,避免敏感信息明文上报。 ### trackEvent - 追踪自定义事件 ```php $user = new User(anonId: 'anon-123', loginId: 'user-456'); $client->trackEvent($user, 'Purchase', [ 'product_id' => 'SKU-001', 'price' => 99.99, 'quantity' => 2, ]); ``` ### track - 追踪完整事件结构 ```php use SensorsWave\Model\Event; use SensorsWave\Model\Properties; $event = Event::create('anon-123', 'user-456', 'PageView') ->withProperties( Properties::create() ->set('page_name', '/home') ->set('referrer', 'google.com') ); $client->track($event); ``` --- ## 用户属性管理 用户属性用于描述用户的特征,如会员等级、注册时间、偏好设置等。与事件属性不同,用户属性会持久化保存并与用户关联。 ### profileSet - 设置用户属性 设置用户属性属性,如果属性已存在则覆盖。 ```php $user = new User(anonId: 'anon-123', loginId: 'user-456'); $client->profileSet($user, [ 'name' => '张三', 'level' => 5, ]); ``` ### profileSetOnce - 仅首次设置 设置用户属性属性,仅在属性不存在时设置。 ```php $client->profileSetOnce($user, [ 'first_login_date' => '2026-01-20', ]); ``` ### profileIncrement - 增加数值属性 ```php $client->profileIncrement($user, [ 'login_count' => 1, 'points' => 100, ]); ``` ### profileAppend - 追加到列表属性 ```php use SensorsWave\Model\ListProperties; $client->profileAppend($user, ListProperties::create()->set('tags', ['premium'])); ``` ### profileUnion - 合并列表属性 ```php $client->profileUnion($user, ListProperties::create()->set('categories', ['sports'])); ``` ### profileUnset - 删除属性 ```php $client->profileUnset($user, 'temp_field', 'old_field'); ``` ### profileDelete - 删除用户属性 ```php $client->profileDelete($user); ``` > **注意**:`profileDelete` 会删除所有用户属性,此操作不可逆。 --- ## A/B 测试 PHP SDK 内置了完整的 A/B 测试支持,可以评估功能开关、实验变量和动态配置。A/B 评估仅从本地快照完成,不发起远程调用。如果快照缺失或过期,功能开关检查将返回 `false`(fail closed)。 ### checkFeatureGate - 检查功能开关 检查功能开关(Boolean Toggle)是否启用。 ```php $pass = $client->checkFeatureGate($user, 'new_feature_gate'); if ($pass) { // 功能已启用 enableNewFeature(); } else { // 功能未启用 useOldBehavior(); } ``` ### getFeatureConfig - 获取功能配置 从功能配置中获取动态配置值。 ```php $result = $client->getFeatureConfig($user, 'button_color_config'); // 获取字符串值(带默认值) $color = $result->getString('color', 'blue'); // 获取数值(带默认值) $size = $result->getNumber('size', 14.0); // 获取布尔值(带默认值) $enabled = $result->getBool('enabled', false); // 获取数组(带默认值) $items = $result->getSlice('items', []); // 获取关联数组(带默认值) $settings = $result->getMap('settings', []); ``` ### getExperiment - 评估实验 评估实验并获取实验变体参数。 ```php $result = $client->getExperiment($user, 'pricing_experiment'); // 获取实验变体参数 $pricingStrategy = $result->getString('strategy', 'original'); // 根据实验变体执行不同逻辑 switch ($pricingStrategy) { case 'original': showOriginalPricing(); break; case 'discount': $discount = (int) $result->getNumber('discount_percent', 0); showDiscountPricing($discount); break; case 'bundle': $bundleSize = (int) $result->getNumber('bundle_size', 1.0); showBundlePricing($bundleSize); break; default: showOriginalPricing(); } ``` ### evaluateAll - 评估所有实验 一次性评估所有已加载的 A/B 规格,并自动记录曝光事件。 ```php $results = $client->evaluateAll($user); ``` ### 完整的 A/B 测试示例 ```php withAbUserProperties([ '$app_version' => '11.0', 'is_premium' => true, ]); // 评估功能开关 if ($client->checkFeatureGate($user, 'new_ui')) { echo "新 UI 已启用\n"; } // 评估实验 $result = $client->getExperiment($user, 'pricing_test'); $variant = $result->getString('variant', 'control'); echo "实验变体: {$variant}\n"; // 获取实验参数 $discount = (int) $result->getNumber('discount', 0); echo "折扣: {$discount}%\n"; $client->close(); ``` --- ## 配置选项 ### 客户端配置(Config) | 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `trackUriPath` | `string` | `/in/track` | 事件上报接口路径 | | `flushIntervalMs` | `int` | `10000` | 内存缓冲区刷新间隔(毫秒) | | `httpConcurrency` | `int` | `1` | Worker 端最大并发 HTTP 请求数 | | `httpTimeoutMs` | `int` | `3000` | Worker 端 HTTP 请求超时(毫秒) | | `httpRetry` | `int` | `2` | Worker 端 HTTP 请求重试次数 | | `eventQueue` | `EventQueueInterface` | 本地文件队列 | 请求路径使用的事件队列 | | `onTrackFailHandler` | `?callable` | `null` | 事件写入队列失败时的回调 | | `ab` | `?ABConfig` | `null` | A/B 测试配置(默认不启用) | | `transport` | `?TransportInterface` | `null` | Worker 端自定义 HTTP Transport | | `logger` | `?LoggerInterface` | 默认 Logger | 自定义 Logger 实现 | > [!NOTE] > > `eventQueue`、`logger` 等字段的内置默认实现均为**参考实现**,仅供本地开发和快速验证使用。生产环境中应根据自身基础设施需求,自行实现对应接口。详见[自定义适配器](#自定义适配器)章节。 ### A/B 测试配置(ABConfig) | 字段 | 类型 | 默认值 | 说明 | |------|------|--------|------| | `projectSecret` | `string` | `''` | A/B 项目密钥,供 sync Worker 使用(必填) | | `metaEndpoint` | `string` | 主 Endpoint | 元数据服务器地址(可选覆盖) | | `metaUriPath` | `string` | `/ab/all4eval` | 元数据接口路径 | | `metaLoadIntervalMs` | `int` | `60000` | 快照新鲜度阈值(最小 `30000` 毫秒) | | `stickyHandler` | `?StickyHandlerInterface` | `null` | 自定义流量粘性处理器 | | `loadABSpecs` | `string` | `''` | 从 `getABSpecs()` 缓存的启动快照 | | `abSpecStore` | `ABSpecStoreInterface` | 本地文件存储 | 请求路径使用的快照存储 | > [!NOTE] > > `abSpecStore` 的内置默认实现为**参考实现**,生产环境中应自行实现 `ABSpecStoreInterface`。详见[自定义适配器](#自定义适配器)章节。 --- ## 高级用法:缓存 A/B 规格数据 为了提高启动性能,您可以缓存 A/B 规格数据并在客户端初始化时加载。 ```php // 1. 从已初始化的客户端获取规格 $specs = $client->getABSpecs(); // 2. 将规格保存到持久化存储(如文件、数据库、Redis) // saveToStorage($specs); // 3. 在创建新客户端时加载规格 $savedSpecs = loadFromStorage(); $config = new Config( ab: new ABConfig( projectSecret: 'your-project-secret', loadABSpecs: $savedSpecs, // 注入缓存的规格 ), ); // 客户端将立即准备好使用缓存规格进行 A/B 评估 $client = Client::create('https://your-endpoint.com', 'your-source-token', $config); ``` --- ## 内置适配器(参考实现) SDK 内置了以下适配器作为**参考实现**,供本地开发和快速验证使用: | 适配器 | 说明 | |--------|------| | `LocalFileABSpecStore` | 基于文件的 A/B 快照存储(默认) | | `LocalFileEventQueue` | 基于文件的事件队列(默认) | | `RedisABSpecStore` | 基于 Redis 的 A/B 快照存储 | | `RedisEventQueue` | 基于 Redis 的事件队列 | Redis 适配器依赖 `RedisClientInterface`,可接入您偏好的 Redis 扩展或客户端库,无需引入硬依赖。 > **注意**:以上所有内置适配器(包括 `LocalFile*` 和 `Redis*`)均为**参考实现**,不保证满足所有生产场景的需求。您应根据项目实际情况评估其适用性,并在必要时自行实现 `ABSpecStoreInterface` / `EventQueueInterface`。 --- ## 自定义适配器 生产环境中,您需要根据自身基础设施实现 `ABSpecStoreInterface` 和 `EventQueueInterface`。内置适配器的源码可作为实现参考。 ### ABSpecStoreInterface 管理 A/B 快照的持久化存储。sync Worker 调用 `save()` 写入,请求路径客户端调用 `load()` 读取。 ```php use SensorsWave\Contract\ABSpecStoreInterface; interface ABSpecStoreInterface { /** * 加载最近保存的快照 JSON 字符串。 * 无数据时返回 null — SDK 将跳过 A/B 评估。 */ public function load(): ?string; /** * 持久化快照 JSON 字符串(由 sync Worker 调用,不在请求路径上执行)。 */ public function save(string $snapshot): void; } ``` **实现要点**: - `load()` 必须返回传递给 `save()` 的原始字符串 — 不要重新编码或转换 - 实现必须是进程安全的 — 请求路径的 FPM 进程和 sync Worker 可能并发读写 ### EventQueueInterface 管理请求路径和 send Worker 之间的事件缓冲,采用基于 claim 的交付模型: ```mermaid flowchart LR enqueue["📥 enqueue()"] --> pending["📦 pending"] pending --> dequeue["📤 dequeue()"] --> claimed["⚙️ claimed(in-flight)"] claimed -- "✅ ok" --> ack["✅ ack()"] claimed -- "❌ fail" --> nack["♻️ nack()"] --> pending classDef method fill:#87CEEB,stroke:#333,stroke-width:2px,color:darkblue classDef state fill:#E6E6FA,stroke:#333,stroke-width:2px,color:darkblue classDef success fill:#90EE90,stroke:#2E7D2E,stroke-width:2px,color:darkgreen classDef retry fill:#FFD700,stroke:#333,stroke-width:2px,color:black class enqueue,dequeue method class pending,claimed state class ack success class nack retry ``` ```php use SensorsWave\Contract\EventQueueInterface; use SensorsWave\Storage\QueueMessage; interface EventQueueInterface { /** * 将原始 JSON 负载写入队列。 * 在请求路径上调用 — 应尽快返回。 * * @param list $payloads */ public function enqueue(array $payloads): void; /** * 取出最多 $limit 条负载,返回 QueueMessage 列表。 * 队列为空时返回空数组。 * * @return list */ public function dequeue(int $limit): array; /** * 确认消息已成功投递。 * * @param list $messages */ public function ack(array $messages): void; /** * 投递失败 — 将消息退回队列等待重试。 * * @param list $messages */ public function nack(array $messages): void; } ``` **实现要点**: - `enqueue()` 在 PHP-FPM 请求处理中运行 — 避免昂贵的 I/O 操作(网络往返、同步磁盘刷新等) - `dequeue()` 返回 `list`,每个 `QueueMessage` 包含一个不透明的 `receipt`(实现自定义的确认令牌)和 `payload`(原始 JSON 字符串) - 已认领(claimed)的消息应有过期机制(如 Redis 的 TTL、数据库的定时清理),确保 Worker 崩溃时消息能自动恢复 ### 使用自定义适配器 将自定义实现通过 `Config` 和 `ABConfig` 构造函数传入: ```php use SensorsWave\Client\Client; use SensorsWave\Config\Config; use SensorsWave\Config\ABConfig; $client = Client::create( 'https://your-endpoint.com', 'your-source-token', new Config( eventQueue: new YourEventQueue(/* ... */), // 自定义事件队列 ab: new ABConfig( projectSecret: 'your-project-secret', abSpecStore: new YourABSpecStore(/* ... */), // 自定义快照存储 ), ), ); ``` > [!WARNING] > > 请求路径客户端和对应的 Worker 必须共享相同的存储后端: > - `ABSpecStoreInterface` — 由 `sensorswave-sync` 写入,由请求路径客户端读取 > - `EventQueueInterface` — 由请求路径客户端写入,由 `sensorswave-send` 读取 --- ## 预定义属性 SDK 为事件追踪和用户属性提供了预定义的属性常量: ```php // 设备和系统属性 '$app_version' // 应用版本 '$browser' // 浏览器名称 '$browser_version' // 浏览器版本 '$model' // 设备型号 '$ip' // IP 地址 '$os' // 操作系统:ios/android/harmony '$os_version' // 操作系统版本 // 地理位置属性 '$country' // 国家 '$province' // 省/州 '$city' // 城市 ``` 在事件中使用: ```php $client->trackEvent($user, 'Purchase', [ '$app_version' => '2.1.0', '$country' => 'US', 'product_id' => 'SKU-001', ]); ``` 在 A/B 测试中使用: ```php $user = $user->withAbUserProperty('$app_version', '2.1.0'); $user = $user->withAbUserProperty('$country', 'US'); ``` --- ## 完整的 API 方法参考表 ### 生命周期管理 | 方法 | 签名 | 说明 | |------|------|------| | **create** | `Client::create(string $endpoint, string $sourceToken, ?Config $config = null): Client` | 创建客户端实例 | | **close** | `close(): void` | 将内存中的事件刷入本地队列并关闭客户端 | | **flush** | `flush(): void` | 将当前缓冲的批次刷入本地队列,但不关闭客户端 | ### 用户身份 | 方法 | 签名 | 参数 | 返回值 | 说明 | |---|---|---|---|---| | **identify** | `identify(User $user): void` | `$user`:包含 `anonId` 和 `loginId` 的用户 | `void` | 创建 `$Identify` 事件,链接匿名和认证身份 | ### 事件追踪 | 方法 | 签名 | 参数 | 返回值 | 说明 | |---|---|---|---|---| | **trackEvent** | `trackEvent(User $user, string $eventName, array\|Properties $properties = []): void` | `$user`:用户身份;`$eventName`:事件名称;`$properties`:事件属性 | `void` | 使用自定义属性追踪用户行为的主要方法 | | **track** | `track(Event $event): void` | `$event`:完整填充的事件结构 | `void` | 用于高级场景的低级 API。正常使用建议使用 `trackEvent` | ### 用户属性操作 | 方法 | 签名 | 说明 | 使用场景 | |---|---|---|---| | **profileSet** | `profileSet(User $user, array\|Properties $properties): void` | 设置或覆盖用户属性 | 更新用户名、邮箱、设置 | | **profileSetOnce** | `profileSetOnce(User $user, array\|Properties $properties): void` | 仅在属性不存在时设置 | 记录注册日期、首次来源 | | **profileIncrement** | `profileIncrement(User $user, array\|Properties $properties): void` | 增加数值型用户属性 | 登录次数、积分、分数 | | **profileAppend** | `profileAppend(User $user, array\|ListProperties $properties): void` | 追加到列表用户属性(允许重复) | 添加购买历史、活动日志 | | **profileUnion** | `profileUnion(User $user, array\|ListProperties $properties): void` | 添加唯一值到列表用户属性 | 添加兴趣、标签、分类 | | **profileUnset** | `profileUnset(User $user, string ...$propertyKeys): void` | 移除指定用户属性 | 清除临时或已废弃的字段 | | **profileDelete** | `profileDelete(User $user): void` | 删除整个用户属性(不可逆) | GDPR 数据删除请求 | ### A/B 测试 | 方法 | 签名 | 参数 | 返回值 | 说明 | |---|---|---|---|---| | **checkFeatureGate** | `checkFeatureGate(User $user, string $key): bool` | `$user`:用户,`$key`:开关键 | `bool` | 评估功能开关。如果键不存在或类型错误,返回 `false` | | **getFeatureConfig** | `getFeatureConfig(User $user, string $key): ABResult` | `$user`:用户,`$key`:配置键 | `ABResult` | 评估功能配置。如果键不存在或类型错误,返回空结果 | | **getExperiment** | `getExperiment(User $user, string $key): ABResult` | `$user`:用户,`$key`:实验键 | `ABResult` | 评估实验。如果键不存在或类型错误,返回空结果 | | **evaluateAll** | `evaluateAll(User $user): array` | `$user`:用户 | `array` | 评估所有已加载的规格并记录曝光事件 | | **getABSpecs** | `getABSpecs(): string` | 无 | `string` | 将当前 A/B 元数据快照导出为 JSON,用于缓存和快速启动 | --- ## 最佳实践 ### 1. 生产环境部署清单 上线前应完成以下配置: ```php use SensorsWave\Client\Client; use SensorsWave\Config\Config; use SensorsWave\Config\ABConfig; $client = Client::create( 'https://your-endpoint.com', 'your-source-token', new Config( // ✅ 实现 EventQueueInterface,接入您的存储后端 eventQueue: new YourEventQueue(/* ... */), // ✅ 注册失败回调,便于排查问题 onTrackFailHandler: function (array $payloads, \Throwable $e) { error_log("事件入队失败: {$e->getMessage()}, 丢失 " . count($payloads) . " 条事件"); }, // ✅ 接入您的日志框架 logger: new YourLoggerAdapter(/* ... */), ab: new ABConfig( projectSecret: 'your-project-secret', // ✅ 实现 ABSpecStoreInterface,接入您的存储后端 abSpecStore: new YourABSpecStore(/* ... */), ), ), ); ``` ### 2. 资源管理 在请求结束时关闭客户端,释放资源: ```php $client = Client::create(...); try { // 业务逻辑... $client->trackEvent($user, 'PageView', ['page' => '/home']); } finally { $client->close(); // 确保客户端正确关闭 } ``` ### 3. 错误处理 在生产环境中,建议对所有 SDK 调用进行错误处理,但不影响主业务流程: ```php function trackUserEvent(Client $client, User $user, string $eventName, array $props): void { try { $client->trackEvent($user, $eventName, $props); } catch (\Exception $e) { // 记录错误,但不影响主业务流程 error_log("追踪事件失败: {$e->getMessage()}"); } } ``` ### 4. 事件名称命名规范 统一采用**大写驼峰格式(PascalCase)**命名事件: ```php // ✅ 推荐做法:大写驼峰格式 $client->trackEvent($user, 'PageView', $props); $client->trackEvent($user, 'Purchase', $props); $client->trackEvent($user, 'AddToCart', $props); $client->trackEvent($user, 'UserRegistered', $props); // ❌ 避免使用:小写下划线(除非有历史数据) $client->trackEvent($user, 'page_view', $props); $client->trackEvent($user, 'purchase', $props); ``` **命名规范说明**: - **推荐**:采用大写驼峰格式(如 `PageView`、`AddToCart`、`UserRegistered`) - **历史兼容**:如果已存在历史埋点数据采用小写下划线风格,可继续保持该风格,但要**保持统一** - **关键点**:无论采用哪种风格,**尽量保证同一个项目下的事件命名规范必须保持一致** ### 5. 事件属性命名 遵循一致的命名规范,使用 `snake_case`: ```php // 推荐做法 [ 'product_id' => 'SKU-001', 'order_amount' => 99.99, 'user_tier' => 'premium', ] // 避免使用 [ 'ProductID' => 'SKU-001', // 避免大写 'orderAmount' => 99.99, // 避免驼峰命名 ] ``` ### 6. 批量操作 对于需要频繁更新用户属性的场景,使用增量更新而非多次设置: ```php $user = new User(loginId: $userId); // 不推荐:多次调用 $client->profileSet($user, ['login_count' => 1]); $client->profileSet($user, ['points' => 100]); // 推荐:一次调用 $client->profileIncrement($user, [ 'login_count' => 1, 'points' => 100, ]); ``` --- ## 常见问题 ### 为什么事件没有上报成功? 检查以下几点: 1. **Worker 进程是否运行**:确认 `sensorswave-send` Worker 已启动并正常运行 2. **Endpoint 和 Token 是否正确**:确认创建客户端时的 `endpoint` 和 `sourceToken` 配置正确 3. **事件队列是否正常**:检查默认队列目录(`sys_get_temp_dir()`)的读写权限 4. **失败回调**:检查是否配置了 `onTrackFailHandler`,查看是否有队列写入失败的日志 5. **错误日志**:查看 Worker 进程的日志输出 ### Worker 进程需要一直运行吗? 是的。`sensorswave-send` 负责将事件队列中的数据发送到服务端,如果不运行,事件将积压在本地。`sensorswave-sync` 负责同步 A/B 元数据,如果不运行,A/B 评估将使用过期的快照数据。 建议使用 Supervisor 或 systemd 管理 Worker 进程,确保异常退出后自动重启。 ### 为什么 PHP SDK 的架构与其他语言 SDK 不同? PHP/FPM 的进程模型决定了每个请求是一个独立的短生命周期进程,不适合在请求路径中发起远程 HTTP 调用。SDK 通过将网络 I/O 分离到独立的 Worker 进程,确保 Web 请求路径上没有远程调用,从而不影响响应性能。 ### A/B 测试评估结果不一致? 确保: 1. **用户属性一致**:每次评估时使用的 A/B 定向属性保持一致 2. **快照已更新**:确认 `sensorswave-sync` Worker 正在运行,快照数据已更新 3. **适配器一致**:确保所有 Web 服务器和 Worker 进程使用相同的适配器配置,共享同一份快照数据 ### 如何在分布式环境中使用? 当应用部署在多台 Web 服务器上时,需要自行实现 `EventQueueInterface` 和 `ABSpecStoreInterface`,接入共享存储后端,确保所有服务器和 Worker 进程共享相同的 A/B 快照和事件队列。详见[自定义适配器](#自定义适配器)章节。 --- ## 相关文档 - [埋点方案选择](../tracking-strategy.mdx):了解服务端埋点和客户端埋点的优劣 - [如何正确的标识用户](../user-identification.mdx):用户身份识别的最佳实践 - [数据模型](../data-model.mdx):理解 Sensors Wave 的数据结构 - [事件和属性](../events-and-properties.mdx):事件设计规范和最佳实践 --- **最后更新时间**:2026 年 4 月 16 日 **SDK 仓库**:[github.com/sensorswave/sdk-php](https://github.com/sensorswave/sdk-php) **许可证**:查看 LICENSE 文件