If you write Symfony in PHPStorm, you have a plugin that knows about your services, routes, Twig templates and translations. It quietly powers most of what feels good about that editor. If you write Symfony in Neovim, you have… well, until recently you had grep.

Vimfony is the missing piece. It’s an LSP server written in Go that teaches your editor what a Symfony project actually looks like, so you can gd into a service, autocomplete a route name, jump to a Twig template, and stop pretending Telescope fuzzy-search is a substitute for understanding.

What it does

Two categories of features, both of which I rely on every working day.

Go to definition (gd):

  • Twig templates (with @Bundle support)
  • Twig functions
  • Classes referenced from YAML or XML
  • Service definitions, including @service_container-style references
  • Route names
  • Translations (YAML)
  • Doctrine-mapped fields inside query builders

Autocomplete:

  • Service names in YAML, XML, and inside #[Autoconfigure] PHP attributes
  • Twig functions
  • Twig variables
  • Route names and route parameters (in Twig and in PHP)
  • Twig file paths: works in PHP, in Twig, and in YAML when the key is template
  • Translations (YAML)
  • Doctrine-mapped fields inside query builders

It also reads Composer’s autoload_classmap for more complete class resolution, and supports multiple container_xml_path values — which matters if you’re working on a Sulu project with separate website and admin kernels, like I do most days.

Why it exists

I work on Symfony and Sulu projects for a living. I also refuse to leave Neovim. For a while those two things were in tension: every time I sat in a non-PHPStorm setup I lost enough Symfony intelligence that productivity took a real hit. The official answer was “use PHPStorm.” That wasn’t going to happen.

So I wrote my own LSP. It’s a single Go binary. You download it (or build it), point your editor at it, and it works the same way any other language server works. No plugin manager. No vendor lock-in. No 700 MB Java process. Just a binary that knows about Symfony.

The hard part: PHP, statically

Most of the features above only feel useful because Vimfony actually understands the code underneath them. Building a static analyser for PHP in Go was, predictably, the part of this project that ate the most hours.

Vimfony parses PHP with tree-sitter and walks the AST itself. It tracks use aliases and resolves short names back to fully-qualified class names. It follows extends chains so inherited methods and properties are visible to autocomplete and gd. It indexes the type of every property, including the constructor-promoted ones (public function __construct(public readonly Foo $foo)).

The thing I’m quietly proudest of: it tracks property type occurrences per line. So as the analyser walks through a file, it knows what type a given property had at each point in the source. Which means autocomplete inside a method body uses the type that was actually declared, not a rough guess based on the property’s name. Most LSPs in this space either don’t bother or rely on a heavyweight external analyser to do it. Vimfony does it with tree-sitter and a few hundred lines of Go.

It’s the part I’m most pleased with.

Status & roadmap

I already use it in my day-to-day work on real production codebases. Honestly, that’s the bar I’m setting — not just how many LSP features it implements.

I do have some things I want to implement at some point:

  • Autocomplete for form options
  • gd for Twig components
  • A built-in vimfony update for self-updating the binary

(Yes, I really need to continue working on this)

If you find a bug or want a feature, open an issue or a PR. And if you use it and it makes your day better, star it on GitHub . That’s still the best signal that a project is worth keeping alive.

Want to see what I'm building?

Most of my work lives on GitHub. Drop by, browse the repos, or open an issue.

GitHub