
本文详解如何在 Laravel 包中通过 Builder 模式实现 Shiprocket::withCredential()->order()->details() 这类可链式调用、高内聚低耦合的面向对象 API 封装,兼顾复用性、可测试性与扩展性。
本文详解如何在 laravel 包中通过 builder 模式实现 `shiprocket::withcredential()->order()->details()` 这类可链式调用、高内聚低耦合的面向对象 api 封装,兼顾复用性、可测试性与扩展性。
在构建 Laravel 第三方包(如 Shiprocket API 封装)时,追求“方法可链式调用 + 资源职责分离 + 父上下文共享”是典型的面向对象进阶需求。直接返回新实例(如 new OrderResource())会导致子类无法访问主类的 $token、get()、post() 等核心能力——这正是原始设计的核心瓶颈。
✅ 正确解法:采用轻量级 Builder 模式变体(非传统复杂构建器,而是“上下文委托型链式构造”),让主类(Shiprocket)自身作为协调中心,持有并代理子资源,同时将关键依赖(认证凭据、Token、HTTP 工具方法)以不可变方式注入子类。
✅ 推荐架构:上下文透传 + 延迟绑定
// src/Shiprocket.php
class Shiprocket
{
protected array $credentials;
protected string $token;
protected ?OrderResource $order = null;
protected ?ShipmentResource $shipment = null;
public function __construct()
{
$this->initDefaultCredentials();
}
protected function initDefaultCredentials(): void
{
$defaultId = config('shiprocket.default_credentials');
$this->credentials = config("shiprocket.credentials.{$defaultId}");
$this->token = $this->getToken();
}
public function withCredential(string $credentialId): self
{
$this->credentials = config("shiprocket.credentials.{$credentialId}");
$this->token = $this->getToken();
// 重置已缓存的子资源,确保上下文一致
$this->order = null;
$this->shipment = null;
return $this;
}
public function order(int|string|null $orderId = null): self
{
$this->order = new OrderResource($orderId, $this);
return $this;
}
public function shipment(int|string|null $shipmentId = null): self
{
$this->shipment = new ShipmentResource($shipmentId, $this);
return $this;
}
// 链式终点方法:触发实际请求
public function details(): array
{
if ($this->order) {
return $this->order->details();
}
throw new RuntimeException('No order specified. Call ->order() first.');
}
// 公共 HTTP 方法(供子类复用)
public function get(string $url, array $query = []): array
{
return Http::withToken($this->token)->get($url, $query)->json();
}
public function post(string $url, array $data = []): array
{
return Http::withToken($this->token)->post($url, $data)->json();
}
protected function getToken(): string
{
$cacheKey = "shiprocket-{$this->credentials['email']}";
$duration = config('shiprocket.token_cache')
? config('shiprocket.token_cache_duration', 3600)
: 0;
return cache()->remember($cacheKey, $duration, function () {
$response = Http::post('https://apiv2.shiprocket.in/v1/external/auth/login', [
'email' => $this->credentials['email'],
'password' => $this->credentials['password'],
]);
return $response->json()['token'] ?? throw new RuntimeException('Failed to fetch Shiprocket token');
});
}
}
// src/Resources/OrderResource.php
class OrderResource
{
private ?int|string $orderId;
private Shiprocket $client; // 关键:持父类引用,复用其 token 与 HTTP 方法
public function __construct(int|string|null $orderId, Shiprocket $client)
{
$this->orderId = $orderId;
$this->client = $client;
}
public function details(): array
{
if (!$this->orderId) {
throw new InvalidArgumentException('Order ID is required for ->details()');
}
return $this->client->get("https://apiv2.shiprocket.in/v1/external/orders/{$this->orderId}");
}
public function status(): array
{
return $this->client->get("https://apiv2.shiprocket.in/v1/external/orders/status/{$this->orderId}");
}
}
⚠️ 关键注意事项
- 禁止子类直接 new 主类:OrderResource 不应重新初始化凭证或 Token,必须通过构造函数接收 Shiprocket 实例完成依赖注入;
- 链式终点需显式触发:.details() .status() 等是“终结方法”,不返回 $this,避免误连;
- 状态隔离:每次调用 withCredential() 必须重置 order/shipment 属性,防止跨凭据污染;
- Facade 注册:在服务提供者中绑定单例,确保 Facade 调用 Shiprocket::xxx() 时始终操作同一实例上下文;
- 类型安全增强(推荐):为 order() 和 shipment() 方法添加 PHP 8.0+ 返回类型提示 : static,配合 IDE 提升开发体验。
✅ 最终调用效果(完全符合预期)
// 使用默认凭据
$data = Shiprocket::order(203504661)->details();
// 切换凭据后调用
$data = Shiprocket::withCredential('my-credential')
->order(203504661)
->status();
// 复用同一实例处理多个资源(高效)
$sr = Shiprocket::withCredential('prod');
$order = $sr->order(123)->details();
$shipment = $sr->shipment(456)->status();
这种设计既保持了 Laravel 的流畅链式风格,又严格遵循 OOP 的单一职责与依赖倒置原则,为后续扩展 returns()、couriers() 等新资源模块提供了清晰、可预测的范式。










