
# Repetições

Queuety Os fluxos de trabalho são cadeias ordenadas de etapas. Use uma etapa de controle de repetição para voltar a uma etapa nomeada anterior, em vez de escrever a lógica `_next_step` manualmente.

Escolha a forma que corresponde ao fluxo de trabalho:

- um `state_key` público quando a condição é simples
- uma `condition_class` quando a repetição exige uma lógica mais elaborada
- `max_iterations` quando a própria repetição precisa de um limite rígido

Ambos os métodos apontam para uma etapa nomeada anterior, portanto a repetição continua serializável, reproduzível e protegida pelo mesmo ambiente de execução do fluxo de trabalho que todas as outras etapas.

A condição de repetição é avaliada a partir do estado público persistido do fluxo de trabalho após a conclusão da etapa anterior, mantendo cada iteração durável mesmo após reinicializações do worker.

## `repeat_until()` com uma chave de estado

```php
Queuety::workflow( 'approval_revision_loop' )
    ->max_transitions( 12 )
    ->then( DraftBriefStep::class, 'draft' )
    ->wait_for_decision( result_key: 'review' )
    ->then( NormalizeReviewOutcomeStep::class, 'review_outcome' )
    ->repeat_until( 'draft', 'review_approved', true, 'repeat_draft_until_approved' )
    ->then( PublishBriefStep::class )
    ->dispatch( [ 'brief_id' => 42 ] );
```

`NormalizeReviewOutcomeStep` pode simplificar os dados de decisão em uma chave pública simples:

```php
class NormalizeReviewOutcomeStep implements \Queuety\Step {
    public function handle( array $state ): array {
        return [
            'review_approved' => 'approved' === ( $state['review']['outcome'] ?? null ),
        ];
    }

    public function config(): array {
        return [];
    }
}
```

Se `review_approved` ainda não for `true`, a etapa de repetição volta para `draft`. Quando se tornar `true`, o fluxo de trabalho continua para `PublishBriefStep`.

Os sinais são armazenados, não consumidos. Na segunda passagem por `wait_for_decision()`, o sinal `rejected` anterior ainda corresponde e a espera é concluída imediatamente, fazendo o loop continuar indefinidamente até que `max_transitions()` o interrompa. Use uma chave diferente a cada rodada: faça `DraftBriefStep` retornar um `review_round` incremental, passe `correlation_key: 'review_round'` para `wait_for_decision()` e peça ao revisor que envie o mesmo `review_round` no conteúdo do sinal. Assim, somente um sinal cujo `review_round` corresponda ao valor atual do estado concluirá a espera.

## `repeat_until()` com uma classe de condição

```php
use App\Workflows\ReviewApprovedCondition;

Queuety::workflow( 'approval_revision_loop' )
    ->then( DraftBriefStep::class, 'draft' )
    ->wait_for_decision( result_key: 'review' )
    ->repeat_until(
        target_step: 'draft',
        condition_class: ReviewApprovedCondition::class,
        max_iterations: 5,
        name: 'repeat_draft_until_approved',
    )
    ->then( PublishBriefStep::class )
    ->dispatch( [ 'brief_id' => 42 ] );
```

```php
namespace App\Workflows;

use Queuety\Contracts\RepeatCondition;

final class ReviewApprovedCondition implements RepeatCondition {
    public function matches( array $state ): bool {
        return 'approved' === ( $state['review']['outcome'] ?? null );
    }
}
```

As classes de condição recebem o estado público do fluxo de trabalho, mantendo o predicado serializável e inspecionável sem depender de closures.

## `repeat_while()`

```php
Queuety::workflow( 'poll_remote_status' )
    ->max_transitions( 20 )
    ->then( PollRemoteStatusStep::class, 'poll' )
    ->delay( seconds: 30 )
    ->repeat_while( 'poll', 'should_poll_again', true, 'repeat_poll' )
    ->then( FinalizeImportStep::class )
    ->dispatch( [ 'import_id' => 99 ] );
```

Esse padrão é útil quando uma etapa grava um valor booleano simples, como `should_poll_again`, `has_more_pages` ou `needs_revision`.

## Por que as repetições são separadas de `_next_step`

Você ainda pode criar ramificações manualmente retornando `_next_step` de uma etapa normal. Os auxiliares de repetição existem para o caso mais específico em que:

- o destino é uma etapa anterior
- a condição vem do estado público do fluxo de trabalho
- você quer que a lógica de repetição permaneça visível na própria definição do fluxo de trabalho

Isso facilita a inspeção das repetições em exportações, registros de eventos e documentação, em vez de ocultar o retorno dentro de um código de etapa arbitrário.

## Como proteger as repetições

Use `max_iterations` quando uma repetição específica precisar de um limite rígido local:

```php
Queuety::workflow( 'poll_remote_status' )
    ->then( PollRemoteStatusStep::class, 'poll' )
    ->repeat_while( 'poll', 'should_poll_again', true, max_iterations: 8 )
    ->dispatch();
```

Use `max_transitions()` quando todo o fluxo de trabalho precisar de um mecanismo geral de interrupção:

```php
Queuety::workflow( 'safe_revision_loop' )
    ->max_transitions( 10 )
    ->then( DraftBriefStep::class, 'draft' )
    ->wait_for_decision( result_key: 'review' )
    ->then( NormalizeReviewOutcomeStep::class )
    ->repeat_until( 'draft', 'review_approved', true )
    ->dispatch();
```

Se uma repetição exceder `max_iterations` ou o fluxo de trabalho exceder `max_transitions()`, Queuety interromperá a execução em vez de permitir que ela continue indefinidamente.

## Documentação relacionada

- [Ramificação condicional](https://queuety.inline0.dev/pt/docs/workflows/conditional)
- [Sinais](https://queuety.inline0.dev/pt/docs/workflows/signals)
- [Proteções](https://queuety.inline0.dev/pt/docs/workflows/guardrails)
- [PHP API](https://queuety.inline0.dev/pt/docs/api)
