symfony/http-foundation · error · InvalidArgumentException

A Closure must be passed to

Error message

A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.

What it means

InputBag::filter() wraps PHP's filter_var(). When the filter is FILTER_CALLBACK, PHP requires the 'options' key to hold a Closure; the library detects a non-Closure (or missing) options value and throws this InvalidArgumentException up front with a clearer message than filter_var's silent failure. This is a pre-flight validation of the filter options array.

Solutions

  1. Wrap the callable in a Closure: ['options' => fn ($v) => strtoupper($v)].
  2. Use first-class callable syntax: ['options' => strtoupper(...)] which produces a Closure.
  3. If the callable is [$obj, 'method'], use Closure::fromCallable([$obj, 'method']) or $obj->method(...).
  4. Verify the filter constant actually needs FILTER_CALLBACK; a built-in filter like FILTER_VALIDATE_INT does not need options.
  5. Ensure the options array is not reused/overwritten so the 'options' key still holds the Closure at call time.

Example fix

// before
$bag->filter('name', \FILTER_CALLBACK, ['options' => 'trim']); // string callable: throws

// after
$bag->filter('name', \FILTER_CALLBACK, ['options' => fn ($v) => trim($v)]);
Defensive patterns

Strategy: validation

Validate before calling

// before calling filter with FILTER_CALLBACK
$options = $options ?? [];
if (\FILTER_CALLBACK & $filter && !($options['options'] ?? null) instanceof \Closure) {
    $options['options'] = $options['options'](...); // wrap callable in a Closure
}

Type guard

function isClosure(mixed $c): bool {
    return $c instanceof \Closure;
}

Try / catch

try {
    $value = $bag->filter($key, \FILTER_CALLBACK, ['options' => $callback]);
} catch (\InvalidArgumentException $e) {
    $value = null; // or fall back to an unfiltered default
}

Prevention

When it happens

Trigger: Calling $inputBag->filter('key', FILTER_CALLBACK) without passing ['options' => fn(...) => ...]; passing a callable string like 'strtoupper' or a first-class callable syntax array [$obj, 'method'] as options instead of a Closure; passing ['options' => FILTER_VALIDATE_INT] by mistake.

Common situations: Developers assuming any PHP callable works with FILTER_CALLBACK (filter_var only accepts Closures for it); forgetting the 'options' key entirely; migrating code where a function name string was used with filter_var directly.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of symfony/http-foundation@5aea19cd67 (2026-09-13). Data as JSON: /api/errors/80a033cfdbbdea11. Report an issue: GitHub.

Appendix: source

Thrown at InputBag.php:157

    /**
     * @throws BadRequestException if the input value is an array and \FILTER_REQUIRE_ARRAY or \FILTER_FORCE_ARRAY is not set
     * @throws BadRequestException if the input value is invalid and \FILTER_NULL_ON_FAILURE is not set
     */
    public function filter(string $key, mixed $default = null, int $filter = \FILTER_DEFAULT, mixed $options = []): mixed
    {
        $value = $this->has($key) ? $this->all()[$key] : $default;

        // Always turn $options into an array - this allows filter_var option shortcuts.
        if (!\is_array($options) && $options) {
            $options = ['flags' => $options];
        }

        if (\is_array($value) && !(($options['flags'] ?? 0) & (\FILTER_REQUIRE_ARRAY | \FILTER_FORCE_ARRAY))) {
            throw new BadRequestException(\sprintf('Input value "%s" contains an array, but "FILTER_REQUIRE_ARRAY" or "FILTER_FORCE_ARRAY" flags were not set.', $key));
        }

        if ((\FILTER_CALLBACK & $filter) && !(($options['options'] ?? null) instanceof \Closure)) {
            throw new \InvalidArgumentException(\sprintf('A Closure must be passed to "%s()" when FILTER_CALLBACK is used, "%s" given.', __METHOD__, get_debug_type($options['options'] ?? null)));
        }

        $options['flags'] ??= 0;
        $nullOnFailure = $options['flags'] & \FILTER_NULL_ON_FAILURE;
        $options['flags'] |= \FILTER_NULL_ON_FAILURE;

        $value = filter_var($value, $filter, $options);

        if (null !== $value || $nullOnFailure) {
            return $value;
        }

        throw new BadRequestException(\sprintf('Input value "%s" is invalid and flag "FILTER_NULL_ON_FAILURE" was not set.', $key));
    }
}

View on GitHub (pinned to 5aea19cd67)