
# المهام

المهام هي وحدة العمل الأساسية في Queuety. أرسل مهمة باسم معالج وحمولة، فيلتقطها عامل وينفذها في الخلفية.

## إرسال المهام

```php
use Queuety\Queuety;

Queuety::dispatch( 'send_email', [ 'to' => 'user@example.com' ] );
```

يعيد `dispatch()` أداة إنشاء من النوع `PendingJob`. يمكنك تسلسل الخيارات قبل الإرسال التلقائي:

```php
use Queuety\Enums\Priority;

Queuety::dispatch( 'send_email', [ 'to' => 'user@example.com' ] )
    ->on_queue( 'emails' )
    ->with_priority( Priority::High )
    ->delay( 300 )
    ->max_attempts( 5 );
```

للحصول على معرّف المهمة فورًا، استدعِ `->id()`:

```php
$job_id = Queuety::dispatch( 'send_email', $payload )->id();
```

## كتابة المعالجات

المعالج هو أي فئة تنفّذ الواجهة `Queuety\Handler`:

```php
use Queuety\Handler;

class SendEmailHandler implements Handler {
    public function handle( array $payload ): void {
        wp_mail( $payload['to'], $payload['subject'], $payload['body'] );
    }

    public function config(): array {
        return [
            'queue'           => 'emails',
            'max_attempts'    => 5,
            'backoff'         => 'exponential',
        ];
    }
}
```

تستقبل الطريقة `handle()` حمولة المهمة وتنفذ العمل. وتعيد الطريقة `config()` القيم الافتراضية الاختيارية:

| المفتاح | النوع | الوصف |
|---|---|---|
| `queue` | `string` | اسم صف الانتظار الافتراضي |
| `max_attempts` | `int` | الحد الأقصى لعدد مرات التشغيل، بما فيها المرة الأولى، قبل نقل المهمة إلى الأرشيف |
| `backoff` | `string` | استراتيجية التراجع لإعادة المحاولة: `exponential` أو `linear` أو `fixed` |
| `rate_limit` | `array` | حد المعدل بصيغة `[max_executions, window_seconds]` |
| `concurrency_group` | `string` | مجموعة قبول مشتركة للعمال مخصصة للمعالجات المكلفة |
| `concurrency_limit` | `int` | الحد الأقصى لعدد مهام المعالجة المتزامنة داخل تلك المجموعة |
| `cost_units` | `int` | تكلفة التنفيذ النسبية المستخدمة في ميزانيات صفوف الانتظار أو المزوّد، وميزانيات سير العمل، وإرشادات القبول |

تُعد مفاتيح الموارد هذه جزءًا من نموذج [إدارة الموارد](https://queuety.inline0.dev/ar/docs/features/resource-management) الأوسع في Queuety، حيث تعمل بيانات المهام الوصفية وميزانيات سير العمل وقبول العامل للمهام معًا.

سجّل المعالج:

```php
Queuety::register( 'send_email', SendEmailHandler::class );
```

أو استخدم [سمات PHP](https://queuety.inline0.dev/ar/docs/features/attributes) للتسجيل التلقائي.

## الأولوية

للمهام أربعة مستويات أولوية. يطالب العمال بالمهام ذات الأولوية الأعلى أولًا.

```php
use Queuety\Enums\Priority;

Queuety::dispatch( 'handler', $payload )->with_priority( Priority::Urgent );
```

| المستوى | القيمة | الوصف |
|---|---|---|
| `Priority::Low` | `0` | الافتراضي. عمل في الخلفية يمكنه الانتظار. |
| `Priority::Normal` | `1` | أولوية عادية. |
| `Priority::High` | `2` | يُعالج قبل المنخفض والعادي. |
| `Priority::Urgent` | `3` | يُعالج أولًا. استخدمه باعتدال. |

## التأخير

أخّر المهمة لتصبح متاحة بعد انقضاء عدد الثواني المحدد فقط:

```php
Queuety::dispatch( 'send_reminder', $payload )->delay( 3600 ); // 1 hour
```

تُضاف المهمة فورًا، لكن العمال لن يطالبوا بها حتى انقضاء مدة التأخير.

## المهام الفريدة

امنع إرسال نسخ مكررة من المعالج والحمولة نفسيهما. إذا كانت هناك مهمة معلّقة أو قيد المعالجة لها المعالج والحمولة نفسيهما، فسيُعاد معرّف المهمة الموجودة بدلًا من إنشاء مهمة جديدة.

```php
Queuety::dispatch( 'sync_inventory', [ 'sku' => 'ABC-123' ] )->unique();
```

## تبعيات المهام

يمكن أن تعتمد مهمة على مهمة أخرى. ولن يطالب بها عامل حتى تكتمل المهمة التابعة لها.

```php
$first_id = Queuety::dispatch( 'import_data', $payload )->id();

Queuety::dispatch( 'send_notification', [ 'import_id' => $first_id ] )
    ->after( $first_id );
```

## إرسال دفعة

أرسل عدة مهام ضمن عملية INSERT واحدة متعددة الصفوف لتحقيق أعلى إنتاجية:

```php
$job_ids = Queuety::batch( [
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'a@example.com' ] ],
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'b@example.com' ] ],
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'c@example.com' ] ],
] );
```

يمكن أن يتضمن كل عنصر مفاتيح اختيارية: `queue` و`priority` و`delay` و`max_attempts`.

## إعادة المحاولة والتراجع

عندما ترمي المهمة استثناءً، يعيد Queuety المهمة إلى `pending` مع تأخير `available_at` حتى تُشغّل `max_attempts` مرات إجمالًا (تُحتسب المرة الأولى، لذا تعني `max_attempts( 3 )` إجراء محاولتي إعادة كحد أقصى). تحدد استراتيجية التراجع مدة الانتظار قبل كل إعادة محاولة، حيث يشير `attempt` إلى رقم التشغيل الذي فشل للتو:

| الاستراتيجية | معادلة التأخير | مثال (بعد التشغيل 1 و2 و3) |
|---|---|---|
| `exponential` | `2^attempt * 30` ثانية، بحد أقصى 3600 | 60s، 120s، 240s |
| `linear` | `attempt * 60` ثانية | 60s، 120s، 180s |
| `fixed` | `60` ثانية | 60s، 60s، 60s |

عيّن القيمة الافتراضية العامة في `wp-config.php`:

```php
define( 'QUEUETY_RETRY_BACKOFF', 'linear' );
```

أو لكل معالج في `config()`:

```php
public function config(): array {
    return [ 'backoff' => 'exponential', 'max_attempts' => 5 ];
}
```

أو لكل عملية إرسال:

```php
Queuety::dispatch( 'handler', $payload )->max_attempts( 10 );
```

## المهام المدفونة

بعد استنفاد جميع محاولات إعادة التشغيل، تُعلَّم المهمة بأنها **مدفونة**. تبقى المهام المدفونة في قاعدة البيانات للفحص، لكن لا تُعالَج. يمكنك إعادة تشغيلها يدويًا. تجعل إعادة التشغيل اليدوية المهمة `pending` مجددًا من دون إعادة ضبط عدد محاولاتها، لذا تحصل المهمة المدفونة التي أُعيد تشغيلها على فرصة تنفيذ أخرى، ثم تُدفن مجددًا إذا فشلت تلك المحاولة:

```php
// Retry a specific buried job
Queuety::retry( $job_id );

// Retry all buried jobs
$count = Queuety::retry_buried();
```

أو عبر CLI:

```bash
wp queuety retry 42
wp queuety retry-buried
```

## المهلة الزمنية

يقوم Queuety بتفعيل `pcntl_alarm()` لمدة التنفيذ القصوى، ويُفترض أن يؤدي تجاوزها إلى فشل المحاولة مع `TimeoutException`. لكن هذا المهلة غير مطبقة حاليًا ([#10](https://github.com/inline0/queuety/issues/10)): لا يفعّل العامل الإشارات غير المتزامنة، لذا يكتمل المعالج طويل التشغيل، بينما يُوقَف المعالج العالق في `sleep()` عند إطلاق التنبيه ثم يُعلَّم كمكتمل. اضبط المهلة على مستوى عام:

```php
define( 'QUEUETY_MAX_EXECUTION_TIME', 300 ); // 5 minutes
```

تُستعاد تلقائيًا المهام التي تبقى في الحالة `processing` لأكثر من `QUEUETY_STALE_TIMEOUT` ثانية:

```php
define( 'QUEUETY_STALE_TIMEOUT', 600 ); // 10 minutes
```

استرداد المهام القديمة يدويًا:

```bash
wp queuety recover
```

## قوائم الانتظار

تُرسَل المهام إلى قوائم انتظار مسماة. يعالج كل عامل قائمة انتظار واحدة أو قائمة مرتبة من قوائم الانتظار (راجع [Workers](https://queuety.inline0.dev/ar/docs/features/workers#multi-queue-priorities)):

```php
Queuety::dispatch( 'handler', $payload )->on_queue( 'emails' );
```

```bash
wp queuety work --queue=emails
```

قائمة الانتظار الافتراضية هي `default`. يمكنك تشغيل عمليات عامل منفصلة لكل قائمة انتظار لعزل أحمال العمل.

## تحديد معدل الطلبات

حدّد عدد مرات تنفيذ المعالج خلال فترة زمنية:

```php
Queuety::dispatch( 'call_api', $payload )
    ->rate_limit( 10, 60 ); // max 10 executions per 60 seconds
```

راجع [Rate Limiting](https://queuety.inline0.dev/ar/docs/features/rate-limiting) للاطلاع على التفاصيل.

## فئات المهام القابلة للإرسال

يقدّم Queuety v0.6.0 مفهوم مهمة مبني على الفئات عبر API. فبدلًا من فصل اسم المعالج والحمولة وفئة المعالج، تعرّف فئة واحدة تضم كل شيء: الحمولة (على هيئة خصائص عامة ذات أنواع محددة)، ومنطق التنفيذ، والبرمجيات الوسيطة الاختيارية.

### الواجهة `Contracts\Job`

```php
use Queuety\Contracts\Job;

interface Job {
    public function handle(): void;
}
```

تحتفظ فئة `Job` بحمولتها في خصائص عامة، وتنفّذ منطقها الخاص في `handle()`. ولا يلزم وجود طريقة `config()` منفصلة أو تسجيل للمعالج.

### السمة `Dispatchable`

أضف السمة `Dispatchable` للحصول على طريقة ثابتة `dispatch()` في فئة المهمة:

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class SendEmailJob implements Job {
    use Dispatchable;

    public function __construct(
        public readonly string $to,
        public readonly string $subject,
        public readonly string $body,
    ) {}

    public function handle(): void {
        wp_mail( $this->to, $this->subject, $this->body );
    }
}
```

أرسلها بتمرير وسائط المُنشئ:

```php
SendEmailJob::dispatch( 'user@example.com', 'Welcome!', 'Hello there.' )
    ->on_queue( 'emails' )
    ->with_priority( Priority::High );
```

تستدعي السمة `Dispatchable` داخليًا `Queuety::dispatch()`، لذا تعمل جميع خيارات `PendingJob` (قائمة الانتظار، والأولوية، والتأخير، وmax_attempts، وunique، وafter) بالطريقة نفسها تمامًا.

### آلية التسلسل

عند إرسال نسخة من `Job`، تستخرج Queuety `JobSerializer` اسم الفئة الكامل بوصفه المعالج، وجميع الخصائص العامة بوصفها الحمولة. وعلى جانب العامل، يعيد المُسلسِل إنشاء النسخة بمطابقة مفاتيح الحمولة مع أسماء وسائط المُنشئ. تُسلسَل التعدادات ذات القيم الأساسية إلى قيمها الأصلية وتُستعاد تلقائيًا.

### خصائص المهمة

يمكن لفئات المهام تعريف خصائص عامة للتحكم في سلوك إعادة المحاولة والمهلة. يقرأها العامل عبر الانعكاس ويطبقها تلقائيًا.

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class ImportProductsJob implements Job {
    use Dispatchable;

    public int $tries = 5;
    public int $timeout = 120;
    public array $backoff = [ 10, 30, 60, 120 ];
    public string $concurrency_group = 'imports';
    public int $concurrency_limit = 2;
    public int $cost_units = 4;

    public function __construct(
        public readonly int $supplier_id,
    ) {}

    public function handle(): void {
        // Import products...
    }
}
```

| الخاصية | النوع | الوصف |
|---|---|---|
| `$tries` | `int` | الحد الأقصى لعدد المحاولات قبل دفن المهمة. يتجاوز قيمة `max_attempts` المحددة عند الإرسال. |
| `$timeout` | `int` | الحد الأقصى لمدة التنفيذ بالثواني. يتجاوز الثابت العام `QUEUETY_MAX_EXECUTION_TIME`. يتطلب `pcntl`، ولم يُطبَّق بعد (راجع [Timeout](https://queuety.inline0.dev/ar/docs/jobs#timeout)). |
| `$backoff` | `array` | مصفوفة قيم التأخير بالثواني لكل محاولة إعادة تشغيل. يُعاد استخدام القيمة الأخيرة للمحاولات اللاحقة. |
| `$concurrency_group` | `string` | مجموعة مشتركة لقبول المهام المكلفة لدى العامل. |
| `$concurrency_limit` | `int` | الحد الأقصى للمهام المتزامنة من نوع `processing` في تلك المجموعة. يُتجاهل دون `$concurrency_group`. |
| `$cost_units` | `int` | التكلفة النسبية للتنفيذ، وتُستخدم في ميزانيات سير العمل وقبول العامل. |

توفر مصفوفة `$backoff` فترات تأخير متزايدة لإعادة المحاولة. مثلًا، تعني `[10, 30, 60, 120]` أن الانتظار للمحاولة الأولى 10 ثوانٍ، وللثانية 30 ثانية، وللثالثة 60 ثانية، ولأي محاولات إضافية 120 ثانية.

تكون الخصائص المتعلقة بالموارد مفيدة عندما تمثل فئات المهام مستويات تكلفة مختلفة. فتحديث ذاكرة تخزين مؤقت بسرعة واستدعاء مزود كبير قد يُعدّ كل منهما «مهمة واحدة»، لكن لا ينبغي بالضرورة احتسابهما بالطريقة نفسها عندما تحاول ميزانية سير العمل أو قرارات قبول العامل إبقاء العملية ضمن نطاق آمن.

للاطلاع على النموذج كاملًا، بما يشمل ميزانيات سير العمل وقبول المهام لدى العامل، راجع [Resource Management](https://queuety.inline0.dev/ar/docs/features/resource-management).

### الخطاف عند الفشل

عندما تُدفن مهمة بعد استنفاد جميع محاولات إعادة التشغيل، يستدعي Queuety طريقة `failed()` في نسخة المهمة إن كانت معرّفة. استخدمها للتنظيف أو التنبيه أو التسجيل.

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class ProcessPaymentJob implements Job {
    use Dispatchable;

    public int $tries = 3;

    public function __construct(
        public readonly int $order_id,
    ) {}

    public function handle(): void {
        // Process the payment...
    }

    public function failed( \Throwable $exception ): void {
        // Notify the team that payment processing failed permanently.
        error_log( "Payment failed for order {$this->order_id}: {$exception->getMessage()}" );
    }
}
```

تتلقى الطريقة `failed()` الاستثناء الذي تسبب في الفشل النهائي. يُعاد إلغاء تسلسل نسخة المهمة بحمولتها الأصلية، فتكون جميع الخصائص العامة متاحة.

### الإرسال المشروط

توفر السمة `Dispatchable` الطريقتين `dispatch_if()` و`dispatch_unless()` للإرسال المشروط. تقبل كلتاهما شرطًا منطقيًا كوسيط أول، تليه وسائط مُنشئ المهمة.

```php
// Only dispatch if the user has opted in to notifications.
SendEmailJob::dispatch_if( $user->wants_notifications, $user->email, 'Update', $body );

// Dispatch unless the feature flag is disabled.
SyncInventoryJob::dispatch_unless( $maintenance_mode, $sku );
```

ترسل `dispatch_if()` المهمة عندما يكون الشرط `true`، وتعيد `PendingJob`. وتعيد `null` عندما يكون الشرط `false`.

ترسل `dispatch_unless()` المهمة عندما يكون الشرط `false`، وتعيد `PendingJob`. وتعيد `null` عندما يكون الشرط `true`.

تدعم الطريقتان إضافة خيارات `PendingJob` عند الإرسال:

```php
SendEmailJob::dispatch_if( $should_send, $to, $subject, $body )
    ?->on_queue( 'emails' )
    ?->with_priority( Priority::High );
```

### الإرسال المتزامن

استخدم `Queuety::dispatch_sync()` لتنفيذ مهمة فورًا في العملية الحالية دون إضافتها إلى قائمة الانتظار. يفيد ذلك في الاختبار وللمهام التي يجب تنفيذها مباشرةً.

```php
use Queuety\Queuety;

// With a Job instance
Queuety::dispatch_sync( new SendEmailJob( to: 'user@example.com', subject: 'Hello', body: 'Hi!' ) );

// With a handler name and payload
Queuety::dispatch_sync( 'send_email', [ 'to' => 'user@example.com' ] );
```

يُستدعى أسلوب `handle()` الخاص بالمهمة مباشرة. لا يُنشأ أي سجل في قاعدة البيانات، ولا يُستخدم أي عامل، ولا تُطبّق البرمجيات الوسيطة.

### تسلسل المهام

ينفذ تسلسل المهام مجموعة من المهام واحدة تلو الأخرى. تعتمد كل مهمة في التسلسل على إكمال المهمة السابقة بنجاح. إذا فشلت أي مهمة، تظل المهام اللاحقة في التسلسل معلقة.

#### استخدام `Queuety::chain()`

```php
use Queuety\Queuety;

$first_job_id = Queuety::chain( [
    new FetchDataJob( $url ),
    new ProcessDataJob(),
    new NotifyCompleteJob(),
] )
    ->on_queue( 'pipeline' )
    ->catch( ChainFailedHandler::class )
    ->dispatch();
```

تعيد `Queuety::chain()` كائن `ChainBuilder` يضم طرقًا متسلسلة لضبط قائمة الانتظار ومعالج الفشل. تنشئ الطريقة `dispatch()` جميع المهام بعلاقات `depends_on` متتابعة، وتعيد معرّف المهمة الأولى.

#### استخدام `with_chain()`

توفر السمة `Dispatchable` طريقة `with_chain()`. وهي حاليًا مكافئة لـ `Queuety::chain( $jobs )`: لا تُضاف الفئة التي استُدعيت عليها إلى التسلسل ([#13](https://github.com/inline0/queuety/issues/13))، لذا أدرج المهمة الأولى في المصفوفة أيضًا:

```php
FetchDataJob::with_chain( [
    new FetchDataJob(),
    new ProcessDataJob(),
    new NotifyCompleteJob(),
] )
    ->on_queue( 'pipeline' )
    ->dispatch();
```

#### معالجة فشل التسلسل

سجّل فئة لمعالجة الفشل باستخدام `catch()`. يجب أن تحتوي فئة المعالج على طريقة `handle()`. تُستدعى عند دفن أي مهمة في التسلسل نهائيًا. يدعم العامل `handle()` و`handle( Job $job )` و`handle( Job $job, \Throwable $exception )`.

```php
class ChainFailedHandler {
    public function handle(): void {
        // Alert, clean up, etc.
    }
}
```

### إضافة برمجيات وسيطة

يمكن لفئات المهام تعريف طريقة `middleware()` لإرجاع برمجيات وسيطة تحيط بالتنفيذ:

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;
use Queuety\Middleware\RateLimited;
use Queuety\Middleware\Timeout;

class CallOpenAIJob implements Job {
    use Dispatchable;

    public function __construct(
        public readonly string $prompt,
    ) {}

    public function handle(): void {
        // Call the API...
    }

    public function middleware(): array {
        return [
            new RateLimited( max: 60, window: 60 ),
            new Timeout( seconds: 120 ),
        ];
    }
}
```

راجع [Middleware](https://queuety.inline0.dev/ar/docs/features/middleware) للاطلاع على تفاصيل البرمجيات الوسيطة المضمنة وكتابة برمجيات وسيطة مخصصة.

### مقارنة بالمهام المعتمدة على المعالجات

| | المعتمدة على المعالج (v0.1+) | المعتمدة على الفئة (v0.6+) |
|---|---|---|
| الإرسال | `Queuety::dispatch( 'send_email', $payload )` | `SendEmailJob::dispatch( $to, $subject )` |
| المعالج | فئة منفصلة تطبق `Handler` | الفئة نفسها تطبق `Contracts\Job` |
| الحمولة | `array $payload` بلا أنواع محددة | خصائص عامة ذات أنواع محددة |
| التسجيل | `Queuety::register()` أو `#[QueuetyHandler]` | غير مطلوب (يستخدم FQCN) |
| البرمجيات الوسيطة | غير متاحة | طريقة اختيارية `middleware()` |
| الإعداد | طريقة `config()` | خصائص المهمة + سلسلة `PendingJob` |

كلا الأسلوبين مدعومان بالكامل. وتواصل المهام الحالية المعتمدة على المعالجات العمل دون تغييرات. ويمكنك الجمع بين الأسلوبين في المشروع نفسه.

## دورة حياة المهمة

```
dispatch() -> pending -> processing -> completed
                  |           |
                  |           +-> exception -> pending (retry after backoff)
                  |                       |
                  |                       +-> buried (max attempts reached)
                  |
                  +-> delayed (available_at in the future)
```

| الحالة | الوصف |
|---|---|
| `pending` | في انتظار استلامها من عامل |
| `processing` | قيد التنفيذ حاليًا |
| `completed` | اكتملت بنجاح |
| `failed` | محجوزة؛ لا يضبطها العامل حاليًا. تعود المحاولة الفاشلة التي ستُعاد إلى `pending` |
| `buried` | استُنفدت جميع محاولات إعادة التشغيل |
