
# Dinamik For-Each

İş akışı oluşturulurken dal sayısı bilinmiyorsa `->for_each()` kullanın. Queuety, iş akışı durumundan bir dizi okur, her öğe için bir dal işi dağıtır ve yalnızca yapılandırılmış tamamlanma koşulu sağlandığında ilerler.

## `->for_each()` yöntemi

```php
use Queuety\Contracts\ForEachHandler;
use Queuety\Contracts\ForEachReducer;
use Queuety\Enums\ForEachMode;
use Queuety\Queuety;

Queuety::workflow( 'index_site' )
    ->then( PlanPagesStep::class )
    ->for_each(
        items_key: 'pages',
        handler_class: IndexPageStep::class,
        result_key: 'page_results',
        mode: ForEachMode::Quorum,
        quorum: 2,
        reducer_class: IndexSummaryReducer::class,
        name: 'index_pages',
    )
    ->then( PublishIndexStep::class )
    ->dispatch( [ 'site_id' => 42 ] );
```

Planlayıcı adımı, `pages` değerini iş akışı durumuna yazar. For-each adımı bu diziyi okur ve her öğe için bir dal işi dağıtır.

## Dal işleyicileri

For-each dal işleyicileri `Contracts\ForEachHandler` uygular:

```php
use Queuety\Contracts\ForEachHandler;

final class IndexPageStep implements ForEachHandler {
    public function handle_item( array $state, mixed $item, int $index ): array {
        $page = is_array( $item ) ? $item : [ 'url' => (string) $item ];

        return [
            'url' => $page['url'],
            'title' => fetch_title( $page['url'] ),
            'index' => $index,
        ];
    }

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

`config()` içinde `backoff` bir strateji adı alır: `exponential`, `linear` veya `fixed`. Buradaki deneme başına gecikme listesi şu anda atılır ve anında yeniden denemelere dönüşür ([#16](https://github.com/inline0/queuety/issues/16)); deneme başına gecikmeleri bunun yerine adım tanımındaki `backoff` içine ekleyin.

`$state` iş akışının tam durumunu, `$item` tekil for-each öğesini, `$index` ise sıfır tabanlı konumunu içerir.

## Tamamlanma kipleri

`ForEachMode::All`

Adım, tüm dallar başarıyla tamamlanana kadar sonuçlanmaz. Herhangi bir kalıcı dal hatası, for-each adımının başarısız olmasına neden olur.

`ForEachMode::FirstSuccess`

Adım, ilk dal başarıya ulaşır ulaşmaz sonuçlanır. Etkin kalan dallar, yerini yenilerine bırakmış olarak arşivlenir.

`ForEachMode::Quorum`

Adım, en az `quorum` dal başarıya ulaştığında sonuçlanır. Kalan dallar yeter sayı koşulunu artık karşılayamıyorsa iş akışı başarısız olur.

## İndirgemeciler

Varsayılan olarak Queuety, yapılandırılmış bir toplamı `result_key` altında saklar:

- `mode`
- `quorum`
- `total`
- `succeeded`
- `failed`
- `settled`
- `winner`
- `results`
- `failures`

Özel bir özet istiyorsanız `Contracts\ForEachReducer` uygulayan bir indirgemeci sınıfı iletin. Toplam yine `result_key` altında saklanır ve indirgemecinin döndürdüğü anahtarlar, iş akışı durumuna yanına eklenerek birleştirilir:

```php
use Queuety\Contracts\ForEachReducer;

final class IndexSummaryReducer implements ForEachReducer {
    public function reduce( array $state, array $for_each ): array {
        return [
            'indexed_count' => count( $for_each['results'] ),
            'winning_url' => $for_each['winner']['output']['url'] ?? null,
        ];
    }
}
```

İndirgemeci çıktısı, diğer tüm adım çıktıları gibi iş akışı durumuyla birleştirilir. For-each tamamlandıktan sonra dallanmak için `_next_step` da döndürebilir.

## Yeniden deneme davranışı

For-each dalları, dal işleyicisinin `config()` ayarına göre birbirinden bağımsız olarak yeniden denenir. For-each adımı başarısız olur ve `retry_workflow()` çağrılırsa Queuety for-each adımını yeniden genişletir ve yalnızca hâlâ eksik veya başarısız olan dalları yeniden kuyruğa alır. Başarılı dal sonuçları iş akışı durumunda kalır.

## `parallel()` ile `for_each()` arasında seçim

Dal listesi kodda sabitse `parallel()` kullanın.

Dal listesi çalışma zamanında iş akışı durumundan keşfediliyorsa ve ilk başarı veya yeter sayı gibi tamamlanma kiplerine ihtiyacınız varsa `for_each()` kullanın.
