Skip to content

API Reference ​

Hunter Class ​

Constructor Methods ​

Hunter::for(string $modelClass): self ​

Creates a new Hunter instance for the specified Eloquent model.

Parameters:

  • $modelClass: The fully qualified class name of the Eloquent model

Throws:

  • InvalidArgumentException: If the class doesn't exist or doesn't extend Model

Example:

php
$hunter = Hunter::for(User::class);

Query Building Methods ​

find(string $column, mixed $operator = null, mixed $value = null): self ​

Sets the primary search criteria.

Parameters:

  • $column: The column name to search
  • $operator: The comparison operator (optional, defaults to '=')
  • $value: The value to compare against

Example:

php
$hunter->find('status', 'active');
$hunter->find('created_at', '>', now()->subDays(30));

modifyQueryUsing(Closure $callback): self ​

Adds a custom query modifier.

Parameters:

  • $callback: Function that receives the query builder and returns the modified query

Example:

php
$hunter->modifyQueryUsing(function ($query) {
    return $query->whereHas('posts');
});

Convenience Methods ​

whereIn(string $column, array $values): self ​

Adds a WHERE IN clause.

whereNull(string $column): self ​

Adds a WHERE NULL clause.

whereNotNull(string $column): self ​

Adds a WHERE NOT NULL clause.

whereBetween(string $column, array $values): self ​

Adds a WHERE BETWEEN clause.

orderBy(string $column, string $direction = 'asc'): self ​

Adds an ORDER BY clause.

latest(string $column = 'created_at'): self ​

Orders by the specified column in descending order.

oldest(string $column = 'created_at'): self ​

Orders by the specified column in ascending order.

limit(int $limit): self ​

Limits the number of records.

offset(int $offset): self ​

Sets the query offset.

with(array|string $relations): self ​

Eager loads the specified relations.

Action Methods ​

then(Closure $callback): self ​

Adds an action to be executed for each record.

Parameters:

  • $callback: Function that receives the record, hunter instance, and model

Example:

php
$hunter->then(function ($user, $hunter) {
    $user->update(['processed' => true]);
});

thenIf(Closure|bool $condition, Closure $callback, array $context = []): self ​

Conditionally executes an action.

Parameters:

  • $condition: Boolean or closure that returns boolean
  • $callback: Action to execute if condition is true
  • $context: Additional context for the condition closure

Example:

php
$hunter->thenIf(
    fn($user) => $user->posts()->count() === 0,
    fn($user) => $user->delete()
);

Callback Methods ​

onBeforeThen(Closure $callback): self ​

Registers a callback to be executed before the main actions.

onAfterThen(Closure $callback): self ​

Registers a callback to be executed after the main actions.

onSuccess(Closure $callback): self ​

Registers a callback to be executed when processing succeeds.

onError(Closure $callback): self ​

Registers a callback to be executed when an error occurs.

onProgress(Closure $callback): self ​

Registers a callback to track processing progress.

Callback Parameters:

  • $processed: Number of records processed
  • $total: Total number of records
  • $percentage: Completion percentage
  • $successful: Number of successful records
  • $failed: Number of failed records
  • $skipped: Number of skipped records
  • $result: The current HunterResult instance
  • $hunter: The Hunter instance

Example:

php
$hunter->onProgress(function ($processed, $total, $percentage) {
    echo "Progress: {$percentage}% ({$processed}/{$total})\n";
});

Configuration Methods ​

chunk(int $size): self ​

Sets the chunk size for batch processing.

Parameters:

  • $size: Number of records to process per chunk (must be > 0)

Default: 250

withLogging(string $context = 'hunter'): self ​

Enables error logging with the specified context.

withoutLogging(): self ​

Disables error logging.

dryRun(bool $enabled = true): self ​

Enables or disables dry run mode.

Note: In dry run mode, the main actions are not executed, but callbacks and validations still run.

Flow Control Methods ​

skip(?Model $record = null, ?string $reason = null): self ​

Marks the current or specified record to be skipped.

stop(?string $reason = null): self ​

Stops the entire processing operation.

fail(Model $record, string $reason): self ​

Marks a record as failed with a specific reason.

Getter Methods ​

getCurrentRecord(): ?Model ​

Returns the currently being processed record.

getResult(): ?HunterResult ​

Returns the current result instance.

getSkipReason(): ?string ​

Returns the reason for skipping the current record.

getStopReason(): ?string ​

Returns the reason for stopping processing.

Execution Method ​

hunt(): HunterResult ​

Executes the batch processing operation.

Returns: HunterResult instance with processing statistics and results.

HunterResult Class ​

Properties ​

int $total ​

Total number of records found.

int $successful ​

Number of successfully processed records.

int $failed ​

Number of records that failed processing.

int $skipped ​

Number of records that were skipped.

array $errors ​

Array of errors indexed by record identifier.

array $skippedRecords ​

Array of skipped record identifiers.

array $skipReasons ​

Array of skip reasons indexed by record identifier.

?string $stopReason ​

Reason for stopping processing (if applicable).

float $executionTime ​

Total execution time in seconds.

int $memoryUsage ​

Peak memory usage in bytes.

Methods ​

hasErrors(): bool ​

Returns true if any records failed processing.

hasSkipped(): bool ​

Returns true if any records were skipped.

wasStopped(): bool ​

Returns true if processing was stopped before completion.

getStopReason(): ?string ​

Returns the reason for stopping processing.

getSkipReason(string $recordId): ?string ​

Returns the skip reason for a specific record.

getSkipReasons(): array ​

Returns all skip reasons.

hasSkipReasons(): bool ​

Returns true if there are any skip reasons.

getSuccessRate(): float ​

Returns the success rate as a percentage.

getProcessedCount(): int ​

Returns the total number of processed records (successful + failed).

getExecutionTime(): float ​

Returns the execution time in seconds.

getMemoryUsage(): int ​

Returns the memory usage in bytes.

getFormattedMemoryUsage(): string ​

Returns the memory usage in a human-readable format (e.g., "64 MB").

summary(): string ​

Returns a brief summary string.

getDetailedSummary(): array ​

Returns a detailed summary array with all statistics.

Error Handling ​

Exception Types ​

The Hunter may throw the following exceptions:

  • InvalidArgumentException: When invalid parameters are provided
  • RuntimeException: When execution fails due to runtime conditions

Error Logging ​

When logging is enabled, errors are automatically logged with the following context:

php
[
    'model' => 'App\\Models\\User',
    'id' => 123,
    'error' => 'Error message',
    'trace' => 'Stack trace...'
]

Custom Error Handling ​

php
$hunter->onError(function ($record, $exception, $hunter) {
    // Custom error handling
    Log::channel('batch-processing')->error("Failed to process {$record->id}", [
        'exception' => $exception,
        'record' => $record->toArray(),
    ]);

    // Mark for retry
    $record->update(['retry_count' => $record->retry_count + 1]);
});

Best Practices ​

1. Use Appropriate Chunk Sizes ​

  • Small operations (simple updates): 1000-2000 records
  • Medium operations (multiple queries): 100-500 records
  • Heavy operations (file processing, API calls): 10-50 records

2. Always Test with Dry Run ​

php
// Test first
$dryResult = Hunter::for(User::class)
    ->find('status', 'inactive')
    ->dryRun()
    ->then(fn($user) => $user->delete())
    ->hunt();

echo "Would delete: {$dryResult->successful} records\n";

3. Monitor Progress for Long Operations ​

php
$hunter->onProgress(function ($processed, $total, $percentage) {
    if ($processed % 100 === 0) {
        echo "Progress: {$percentage}%\n";
    }
});

4. Handle Errors Gracefully ​

php
$hunter->then(function ($record, $hunter) {
    try {
        // Risky operation
        $record->complexOperation();
    } catch (RetryableException $e) {
        $hunter->skip($record, "Will retry: {$e->getMessage()}");
    } catch (FatalException $e) {
        $hunter->fail($record, "Fatal: {$e->getMessage()}");
    }
});
php
$hunter->with(['posts', 'profile'])
    ->then(function ($user) {
        // $user->posts and $user->profile are already loaded
    });

Static Methods ​

for(string $modelClass): self ​

Creates a new Hunter instance for the specified model class.

php
Hunter::for(User::class)
Hunter::for(Order::class)
Hunter::for('App\Models\Product')

// Or using the helper
hunter(User::class)
hunter(Order::class)
hunter('App\Models\Product')

Instance Methods ​

find(string $column, mixed $operator = null, mixed $value = null): self ​

Defines the search criteria for the query. Supports multiple syntaxes:

Two-parameter syntax (column and value):

php
->find('status', 'active')
->find('created_at', now())

Three-parameter syntax (column, operator, and value):

php
->find('created_at', '>=', now()->subDays(7))
->find('price', '>', 100)
->find('status', 'in', ['pending', 'processing'])

modifyQueryUsing(Closure $callback): self ​

Adds additional query constraints using the Filament-style approach. The callback must return a Builder instance. Multiple modifiers can be chained and will all be applied:

php
->modifyQueryUsing(function (Builder $query): Builder {
    return $query->where('status', 'active');
})
->modifyQueryUsing(function ($builder): Builder {
    return $builder->whereHas('subscriptions');
})

then(Closure $callback): self ​

Defines the main action to execute for each found record. Multiple actions can be chained:

php
->then(function (Order $order) {
    $order->process();
})
->then(function ($order) {
    $order->sendConfirmation();
})

thenIf(Closure | bool $condition, Closure $callback, array $context = []): self ​

Conditionally executes an action based on a condition. The condition is evaluated during configuration, providing optimal performance:

php
// Execute only if condition is true
->thenIf(
    fn($order) => $order->total > 1000,
    fn($order) => $order->applyVipDiscount()
)

// With boolean condition
->thenIf(
    !now()->isWeekend(),
    fn($order) => $order->processImmediate()
)

// With additional context
->thenIf(
    fn($order, $config) => $order->priority >= $config['min_priority'],
    fn($order) => $order->processHighPriority(),
    ['min_priority' => 5]
)

// Always execute (condition defaults to true)
->thenIf(
    true,
    fn($order) => $order->sendConfirmation()
)

onBeforeThen(Closure $callback): self ​

Executes callbacks before the main actions. Useful for validation, preparation, or flow control:

php
->onBeforeThen(function (Order $order, Hunter $hunter) {
    if (!$order->isValid()) {
        $hunter->fail($order, 'Invalid order');
        return;
    }
})
->onBeforeThen(function ($order) {
    Log::info("Processing order: {$order->id}");
})

onAfterThen(Closure $callback): self ​

Executes callbacks after the main actions complete successfully:

php
->onAfterThen(function (Order $order) {
    $order->update(['processed_at' => now()]);
})
->onAfterThen(function ($order, $hunter) {
    NotificationService::sendProcessedNotification($order);
})

onSuccess(Closure $callback): self ​

Executes callbacks when all actions complete successfully:

php
->onSuccess(function (Order $order) {
    Log::info("Order processed successfully: {$order->id}");
})
->onSuccess(function ($order) {
    MetricsService::incrementProcessedOrders();
})

onError(Closure $callback): self ​

Executes callbacks when any action throws an exception:

php
->onError(function (Order $order, Throwable $e, Hunter $hunter) {
    $order->update(['status' => 'failed']);
})
->onError(function ($order, $exception) {
    NotificationService::sendErrorAlert($order, $exception);
})

withLogging(string $context = 'hunter'): self ​

Enables error logging with a custom context:

php
->withLogging('order_processing')
->withLogging('campaign_dispatch')

Flow Control Methods ​

skip(?Model $record = null, ?string $reason = null): self ​

Gracefully skips the current record without throwing an exception. Can be called from any callback:

php
->onBeforeThen(function ($campaign, $hunter) {
    if (!$campaign->hasContacts()) {
        $hunter->skip($campaign, 'No contacts available');
        return;
    }
})

fail(Model $record, string $reason): self ​

Marks the current record as failed with a specific reason and skips remaining processing:

php
->onBeforeThen(function ($campaign, $hunter) {
    if ($campaign->isExpired()) {
        $hunter->fail($campaign, 'Campaign has expired');
        return;
    }
})

stop(?string $reason = null): self ​

Stops processing all remaining records immediately:

php
->onBeforeThen(function ($campaign, $hunter) {
    if (SystemStatus::isDown()) {
        $hunter->stop('System maintenance in progress');
        return;
    }
})

getCurrentRecord(): ?Model ​

Returns the currently processing record:

php
$currentRecord = $hunter->getCurrentRecord();

getResult(): ?HunterResult ​

Returns the current result object during processing:

php
$result = $hunter->getResult();
echo "Processed so far: {$result->successful}";

hunt(): HunterResult ​

Executes the configured actions and returns a result object with statistics.

Flexible Parameter Resolution ​

Hunter uses Filament's EvaluatesClosures trait, providing flexible parameter resolution. You can use any combination of parameter names and types:

php
// All of these parameter variations work:
->onBeforeThen(function (Campaign $campaign, Hunter $hunter) { })
->onBeforeThen(function ($record, $hunter) { })
->onBeforeThen(function ($model, Hunter $hunter) { })
->onBeforeThen(function (Campaign $campaign) { })
->onBeforeThen(function ($hunter) { })
->onBeforeThen(function () { })

// Error callbacks support multiple parameter names:
->onError(function ($record, Throwable $exception, $hunter) { })
->onError(function ($model, $error, Hunter $hunter) { })
->onError(function (Campaign $campaign, $e) { })

// Query modifiers:
->modifyQueryUsing(function (Builder $query) { })
->modifyQueryUsing(function ($builder) { })

Available Parameter Names ​

For regular callbacks:

  • $record, $model - The current model instance
  • $hunter - The Hunter instance
  • ${modelName} - The model class name (e.g., $campaign for Campaign)

For error callbacks (additionally):

  • $exception, $error, $e - The thrown exception

For query modifiers:

  • $query, $builder - The query builder instance

Execution Flow ​

The Hunter class executes callbacks in the following order for each found record:

  1. All onBeforeThen() callbacks (in registration order)
  2. All then() callbacks (in registration order)
  3. All onAfterThen() callbacks (in registration order)
  4. All onSuccess() callbacks (in registration order)

If any exception occurs during steps 1-3:

  • All onError() callbacks are executed (in registration order)
  • The error is logged if logging is enabled

Flow Control: Any callback can call skip(), fail(), or stop() to control the execution flow without throwing exceptions.

HunterResult Object ​

The hunt() method returns a HunterResult object with the following properties:

php
$result = hunter(Order::class)->find('status', 'pending')->hunt();

$result->total;             // Total records found
$result->successful;        // Successfully processed records
$result->failed;            // Failed records
$result->skipped;           // Skipped records
$result->errors;            // Array of error messages keyed by record identifier
$result->skippedRecords;    // Array of skipped record identifiers
$result->skipReasons;       // Array of skip reasons keyed by record identifier
$result->stopReason;        // Reason for stopping (if applicable)

// Helper methods
$result->hasErrors();        // Returns true if any records failed
$result->hasSkipped();       // Returns true if any records were skipped
$result->wasStopped();       // Returns true if processing was stopped
$result->getSuccessRate();   // Returns success rate as percentage
$result->getProcessedCount(); // Returns successful + failed count
$result->summary();          // Returns summary string
$result->getDetailedSummary(); // Returns detailed array summary

Notes ​

  • All callbacks are executed in the order they were registered
  • Flow control methods (skip(), fail(), stop()) provide graceful alternatives to throwing exceptions
  • Parameter resolution is flexible - use any parameter names that make sense for your use case
  • If any callback in the chain throws an exception, the remaining callbacks in that chain are skipped and onError() callbacks are executed
  • Logging is enabled by default and uses the Laravel Log facade
  • Multiple query modifiers are applied cumulatively
  • The modifyQueryUsing() method requires returning a Builder instance
  • Skipped records are tracked separately from failed records for better reporting