symfony/translation · error · InvalidArgumentException

The writer class " " does not implement the "getFormats()"…

Error message

The writer class "%s" does not implement the "getFormats()" method.

What it means

TranslationExtractCommand requires the injected writer object to expose getFormats() so it can list supported formats for command options. The constructor checks method_exists($writer, 'getFormats') and throws InvalidArgumentException if the check fails.

Solutions

  1. Add a public static getFormats(): array method to your custom writer class
  2. Inject the correct TranslationWriter service instead of another object
  3. Update the custom writer to match the expected interface

Example fix

// before
class MyWriter { public function write(MessageCatalogue $c, string $f, array $o = []): void {} }
// after
class MyWriter {
    public function write(MessageCatalogue $c, string $f, array $o = []): void {}
    public static function getFormats(): array { return ['custom']; }
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (!method_exists($writer, 'getFormats')) {
    throw new \InvalidArgumentException('Writer must implement getFormats()');
}

Type guard

function isCompatibleWriter(object $writer): bool {
    return method_exists($writer, 'getFormats');
}

Try / catch

try {
    $command = new TranslationExtractCommand($writer, $loader, $enabledLocales);
} catch (\InvalidArgumentException $e) {
    // inspect injected writer class
    throw new \LogicException('Invalid writer injected: '.get_debug_type($writer));
}

Prevention

When it happens

Trigger: Constructing TranslationExtractCommand with an object (e.g. a custom TranslationWriterInterface implementation or a mock) that lacks a public getFormats() method; passing the wrong variable to the constructor.

Common situations: Custom writer implementations missing the method; wiring errors in Symfony DI where the wrong service is injected; outdated custom writers after upgrading symfony/translation.

Related errors


AI-assisted analysis of symfony/translation@ae9e8a51bc (2026-09-15). Data as JSON: /api/errors/e2f105e3b3ac88e7. Report an issue: GitHub.

Appendix: source

Thrown at Command/TranslationExtractCommand.php:71

     * @param \Closure():KernelInterface|KernelInterface $kernel
     */
    public function __construct(
        private \Closure|KernelInterface $kernel,
        private TranslationWriterInterface $writer,
        private TranslationReaderInterface $reader,
        private ExtractorInterface $extractor,
        private string $defaultLocale,
        private ?string $defaultTransPath = null,
        private ?string $defaultViewsPath = null,
        private array $transPaths = [],
        private array $codePaths = [],
        private array $enabledLocales = [],
    ) {
        $this->enabledLocales = array_filter($enabledLocales);
        parent::__construct();

        if (!method_exists($writer, 'getFormats')) {
            throw new \InvalidArgumentException(\sprintf('The writer class "%s" does not implement the "getFormats()" method.', $writer::class));
        }
    }

    protected function configure(): void
    {
        $this
            ->setDefinition([
                new InputArgument('locale', InputArgument::REQUIRED, 'The locale'),
                new InputArgument('bundle', InputArgument::OPTIONAL, 'The bundle name or directory where to load the messages'),
                new InputOption('prefix', null, InputOption::VALUE_REQUIRED, 'Override the default prefix', '__'),
                new InputOption('no-fill', null, InputOption::VALUE_NONE, 'Extract translation keys without filling in values'),
                new InputOption('format', null, InputOption::VALUE_REQUIRED, 'Override the default output format', 'xlf12'),
                new InputOption('dump-messages', null, InputOption::VALUE_NONE, 'Should the messages be dumped in the console'),
                new InputOption('force', null, InputOption::VALUE_NONE, 'Should the extract be done'),
                new InputOption('clean', null, InputOption::VALUE_NONE, 'Should clean not found messages'),
                new InputOption('domain', null, InputOption::VALUE_REQUIRED, 'Specify the domain to extract'),
                new InputOption('sort', null, InputOption::VALUE_REQUIRED, 'Return list of messages sorted alphabetically'),
                new InputOption('as-tree', null, InputOption::VALUE_REQUIRED, 'Dump the messages as a tree-like structure: The given value defines the level where to switch to inline YAML'),

View on GitHub (pinned to ae9e8a51bc)