همهی راهنماها
راهنمایی با این جستوجو پیدا نشد.
مدیریت خطا
استثناهای خودتان را از تصمیمهای Reqtide تشخیص دهید. کد دلیل و گزارش مشکل را بخوانید و ببینید حالت تفصیلی همان اطلاعات را چگونه به شکل داده برمیگرداند.
در این صفحه
همهٔ بلوکهای کد این صفحه از examples/07-error-handling.php (opens in a new tab) آمدهاند. ابتدا مفهومها را میگوییم، بعد کد را نشان میدهیم.
دو نوع شکست
| نوع | چه چیزی دریافت میکنید | مثال |
|---|---|---|
| شکست خودتان | استثنای خودتان، بدون تغییر و با همان کلاس و پیام. Reqtide هیچوقت آن را در استثنای دیگری نمیپیچد. | مهلت upstream تمام شد |
| تصمیم Reqtide | استثنایی با نوع مشخص که ProblemCarrier و ReqtideException را پیاده میکند | محدودیت نرخ فراخوانی را رد کرد، مدار باز است یا مهلت کل گذشته است |
گروه سوم اشتباه پیکربندی است. این اشتباه InvalidConfigurationException را پرتاب میکند، که آن هم یک ProblemCarrier است، پیش از آنکه چیزی ارسال شود.
گام ۱: پایپلاینی بسازید که رد میکند
این پایپلاین در هر ساعت یک فراخوانی را میپذیرد. فراخوانی اول انجام میشود و فراخوانی دوم رد میشود.
$base = static fn(): PipelineBuilder => PipelineBuilder::profile('orders', 'v1')
->partition('shop', 'prod', 'orders-api')
->attemptTimeout(Duration::seconds(2), AttemptTimeoutRequirement::Cooperative);
// A pipeline that admits exactly one call per hour, so the second call is refused.
$pipeline = $base()
->rateLimit(PipelineBuilder::tokenBucket(1, 1, Duration::seconds(3600)))
->compileInMemory(ExceptionClassifier::create());
$fetchOrder = static fn(): string => 'order 42';
echo $pipeline->execute($fetchOrder, $pipeline->newContext(Duration::seconds(30), operationId: 'orders-1')), "\n";خروجی تا اینجا:
order 42گام ۲: ردشدن را بگیرید و دلیلش را بخوانید
// 1. A refusal is a typed exception. Every policy exception implements ProblemCarrier.
try {
$pipeline->execute($fetchOrder, $pipeline->newContext(Duration::seconds(30), operationId: 'orders-2'));
} catch (ReqtideException $e) {
if (!$e instanceof ProblemCarrier) {
throw $e;
}
echo get_class($e), "\n";
echo 'reason: ', $e->reason()->value, "\n";
echo json_encode($e->problem(), JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR), "\n";
// 2. Decide what to do from the machine-readable fields, not from the message.
echo match ($e->problem()->retryability()) {
Retryability::AfterWait => "action: ask the caller to try again later\n",
Retryability::Transient => "action: retry now\n",
Retryability::Never => "action: fix the configuration or the code\n",
Retryability::Unknown => "action: reconcile before repeating\n",
};
}خروجی:
Enaxon\Reqtide\Exception\AdmissionRejectedException
reason: rate.exhausted
{
"type": "tag:enaloo.com,2026:reqtide:problem:rate.exhausted",
"title": "Rate limit exhausted",
"category": "admission",
"code": "rate.exhausted",
"stage": "admission",
"severity": "info",
"retryability": "after_wait",
"detail": "Rate limit exhausted. Category: admission; retryability: after_wait; stage: admission.",
"operationId": "orders-2",
"profileVersion": "v1",
"attemptId": null,
"correlationId": "orders-2",
"causes": []
}
action: ask the caller to try again later- متد
reason()یکReasonCodeبرمیگرداند. شرطهای برنامه را روی آن بگذارید، نه رویgetMessage(). - متد
problem()یکProblemبرمیگرداند. این گزارشی پاکسازیشده و قابل تبدیل به JSON است. شکل آن از RFC 9457 گرفته شده (type،title،detail) و عضوهای مخصوص Reqtide را هم دارد (category،code،stage،severity،retryabilityو شناسهها). این استثنا ازexecute()بیرون آمده است، پس عملیات را باoperationId،profileVersionوcorrelationIdمشخص میکند. - برای ساختن
Problemفقط فرادادههای کد به کار میرود. پیام استثناها، دادههای درخواست و اطلاعات محرمانه هیچوقت وارد آن نمیشوند. علت زیرین به شکل نام کلاس و کد میآید. - نام عضوها و ترتیب آنها بخشی از API است. متن
titleوdetailبخشی از API نیست، پس آن را تجزیه نکنید. عضوهای ناشناخته را نادیده بگیرید.
قابلیت تلاش مجدد
| مقدار | معنی |
|---|---|
مقدار after_wait | یک سیاست فراخوانی را رد کرده است. پس از کمی انتظار دوباره امتحان کنید. |
مقدار transient | تلاش مجدد بعداً ممکن است موفق شود و ابهامی دربارهٔ اثر جانبی وجود ندارد. |
مقدار never | پیکربندی یا کد را درست کنید. تکرار همان فراخوانی کمکی نمیکند. |
مقدار unknown | ممکن است یک تغییر اعمال شده باشد. بهجای تکرار کورکورانه، وضعیت را بررسی و تطبیق دهید. |
این مقدارها تصمیم Reqtide را توصیف میکنند. دربارهٔ نتیجهٔ کسبوکار شما چیزی نمیگویند.
دستهها و شدت
مقدار category() یکی از اینهاست: policy، infrastructure، configuration، classification، fallback، admission، retry، accounting یا preparation.
مقدار severity() فقط جنبهٔ راهنما دارد. نام سطحهای PSR-3 (debug، info، notice، warning، error، critical) را به شکل رشتهٔ ساده به کار میبرد، چون Core به PSR-3 وابسته نیست. امروز هیچ کدی از فهرست کدها (registry) از critical استفاده نمیکند. ردشدنهای این صفحه info هستند، چون ردشدن یعنی محافظت کار میکند.
گام ۳: استثناهای خودتان عبور میکنند
// 3. Your own exceptions are never wrapped. They come out exactly as your callable threw them.
$failing = $base()->compileInMemory(ExceptionClassifier::create());
try {
$failing->execute(static fn(): string => throw new UpstreamTimeout('upstream timed out'), $failing->newContext(Duration::seconds(30)));
} catch (UpstreamTimeout $e) {
echo 'business exception: ', $e::class, ' (', $e->getMessage(), ")\n";
}خروجی:
business exception: Reqtide\Examples\ErrorHandling\UpstreamTimeout (upstream timed out)گام ۴: حالت تفصیلی همین اطلاعات را به شکل داده برمیگرداند
متد executeDetailed() ردشدن را پرتاب نمیکند و آن را به شکل داده در یک ExecutionCompletion برمیگرداند.
// 4. Detailed mode returns the same facts as data instead of throwing.
$completion = $pipeline->executeDetailed($fetchOrder, $pipeline->newContext(Duration::seconds(30), operationId: 'orders-3'));
echo 'disposition: ', $completion->disposition->value, "\n";
echo 'dispatched: ', $completion->summary->dispatched, "\n";
echo 'problem code: ', $completion->problem()?->code->value, "\n";خروجی:
disposition: rejected
dispatched: 0
problem code: rate.exhaustedمقدارهای ممکن disposition اینها هستند: succeeded، failed، rejected، deferred، cancelled، deadline_exceeded، fallback_succeeded و fallback_failed.
شیء completion رکورد هر تلاش، تشخیصهای محدودشده و خلاصهٔ شمارشها را هم دارد (dispatched، retries، admittedNotDispatched، fallbackExecutions و بیشتر). وقتی Reqtide چیزی برای گزارش ندارد، مثلاً بعد از موفقیت یا یک شکست ساده در منطق کسبوکار، problem() برابر null است.
گام ۵: وقتی سیاست تلاش مجدد دست میکشد
وقتی تلاشهای مجدد به دلیل خود سیاست تلاش مجدد متوقف شوند، آخرین استثنای شما بهعنوان نتیجه میماند. Reqtide دلیل را به شکل یک مشکل در مرحلهٔ retry ثبت میکند.
اگر در تلاشی بعدی محافظت دیگری فراخوانی را رد کند، رفتار فرق دارد. این محافظتها مدارشکن، سقف اجرای همزمان (Bulkhead)، محدودیت نرخ و دورهٔ انتظار هستند. ردشدن همان نتیجه میشود و آخرین استثنای شما با getPrevious() به آن متصل میشود.
// 5. When the retry policy gives up, the last business failure is kept, and the reason is recorded.
$retrying = $base()
->retry(2, BackoffMode::Constant, Duration::milliseconds(5), Duration::milliseconds(5), JitterMode::None)
->retryBudget(10, 10, Duration::seconds(60), Duration::seconds(10))
->compileInMemory(ExceptionClassifier::create()->retryOn(UpstreamTimeout::class));
$safety = OperationSafety::replayableRead(ReplaySafetyContract::forReadOnlyCallable('get-order', Duration::seconds(3600)));
$completion = $retrying->executeDetailed(
static fn(): string => throw new UpstreamTimeout('upstream timed out'),
$retrying->newContext(Duration::seconds(30), $safety, operationId: 'orders-4'),
);
echo 'disposition: ', $completion->disposition->value, "\n";
echo 'dispatched: ', $completion->summary->dispatched, "\n";
echo 'problem code: ', $completion->problem()?->code->value, "\n";
echo 'last business failure: ', $completion->terminalOutcome?->exception()::class, "\n";خروجی:
disposition: failed
dispatched: 2
problem code: attempts_exhausted
last business failure: Reqtide\Examples\ErrorHandling\UpstreamTimeoutفراخوانی ساده با execute() در این مثال همان UpstreamTimeout شما را پرتاب میکرد. حالت تفصیلی دلیل توقف تلاشها را هم میگوید: attempts_exhausted.
گام ۶: اشتباه پیکربندی نام گزینه را میگوید
// 6. A configuration mistake is an InvalidConfigurationException that names the option. It is raised before
// anything is dispatched: while building the pipeline, or when a call lacks what a retry requires.
try {
$base()->retry(3, BackoffMode::Constant, Duration::milliseconds(5), Duration::milliseconds(5), JitterMode::None)
->compileInMemory(ExceptionClassifier::create());
} catch (InvalidConfigurationException $e) {
echo $e->getMessage(), "\n";
}
try {
$retrying->execute(static fn(): string => 'order 42', $retrying->newContext(Duration::seconds(30)));
} catch (InvalidConfigurationException $e) {
echo $e->getMessage(), "\n";
}خروجی:
retry(maxAttempts=3) requires retryBudget(): retries must be paid for so a failing dependency is not hammered. Every call also needs an explicit replay-safety assertion, for example ReplaySafetyContract::forReadOnlyCallable(); Reqtide never infers that repeating a callable is safe.
maxAttempts greater than one requires an explicit replay-safety contract; the operation is Unsafe.کار با فهرست کدها (registry)
- شرطها را روی
$exception->reason()،$problem->code،reason()یک رویداد یاreason()یک تشخیص بگذارید. وقتی رویReasonCodeازmatchاستفاده میکنید، شاخهٔdefaultرا نگه دارید. در نسخههای minor مورد تازه اضافه میشود، پسmatchکامل روی آن پشتیبانی نمیشود. مثالmatchدر گام ۲ رویRetryabilityنوشته شده است. - این فهرست فقط اضافه میشود و چیزی از آن کم نمیشود. مقداری که منتشر شده هیچوقت معنیاش عوض نمیشود و دوباره به کار نمیرود. تغییر دسته یا قابلیت تلاش مجدد یک کد، تغییر ناسازگار است. شدت و متن پیام فقط جنبهٔ راهنما دارند. کدی که کنار گذاشته شود دستکم تا یک نسخهٔ minor دیگر هنوز تولید میشود.
- کدهای اجزای بیرونی عضو فهرست نیستند. کدهای نتیجهٔ طبقهبند خودتان و دلیلهای backend شخص ثالث نمونهاند. برای این کدها
ReasonCode::tryFrom()مقدارnullبرمیگرداند. - فهرست کامل همان enum با نام
ReasonCodeدرsrc/Error/ReasonCode.php(opens in a new tab) است.
اشتباههای رایج
- گرفتن
\Throwableو برخورد با هر استثنا مثل شکست upstream. ردشدن به این معنی نیست که callable شما شکست خورده است. وقتی پایپلاین رد میکند، callable شما اصلاً صدا زده نشده است. - تلاش فوری و حلقهای پس از ردشدن
rate.exhausted. قابلیت تلاش مجدد آنafter_waitاست. - ثبت
$e->getMessage()استثنای Reqtide در لاگ بهعنوان شناسهٔ پایدار. بهجای آنreason()->valueوproblem()->type()را ثبت کنید.
این صفحه مفید بود؟