Skip to content

feat(symfony): Symfony routes, linked to their controller actions - #3088

Open
deniskern wants to merge 3 commits into
colbymchenry:mainfrom
deniskern:feat/symfony-resolver
Open

deniskern wants to merge 3 commits into
colbymchenry:mainfrom
deniskern:feat/symfony-resolver

Conversation

@deniskern

@deniskern deniskern commented Oct 30, 2026 •

Copy link
Copy Markdown

Supersedes #611. A fresh take against today's main, as @colbymchenry suggested when closing #611 (close comment). It carries @jorgerobles's Symfony resolver (credited with Co-authored-by on the commits that port his code) plus the AST-based route work from jorgerobles#1, and resolves every point raised when #611 was closed.

What it adds

  • Route nodes from three sources, each linked to the controller action that serves it:
    • PHP 8 #[Route] attributes, read from the PHP AST: class-level prefixes, stacked routes on one action, multi-line attributes, methods:, name:, defaults:, requirements:, host:, schemes:, and Request::METHOD_GET-style constants.
    • config/routes.yaml (path, controller, methods, prefix; a resource: import is not a route).
    • The compiled router in the kernel's build dir (var/cache/<env>/…).
  • Service definitions: a services.yaml / compiled-container service links to its class.
  • PHP attributes as decorates edges on classes, methods and functions (#[AsCommand], #[ORM\Entity], #[AsEventListener], …) for every PHP project, so attribute classes show who uses them.
  • Detection: symfony/framework-bundle, symfony/symfony, shopware/core (Shopware 6 shops and plugins are Symfony apps without bin/console), or bin/console + config/.

The close comment, point by point

  1. (?:\s*\n\s*)* before class backtracks exponentially. The regex is gone; routes are read from the attribute_list → attribute_group → attribute nodes of each class and method declaration. On the old pattern, #[Route('/x')] followed by 16 whitespace-only lines took 3.3 s and 18 lines took 17.5 s. symfony-resolver.test.ts > pathological input feeds 20,000 such lines plus 2,000 unclosed nested [ and requires the extraction to finish in under 1 s.
  2. Controller resolution scanned every class per reference. resolve() now looks the class up through the name index (getNodesByName(shortName)), takes only the class in the namespace the FQCN names (another namespace's class of the same short name stays unlinked), and asks only for the method by name in that file (getNodesInFileNamed), limited to the class's line range. The unit test's context throws if getNodesByKind is called; an integration test with two BlogControllers in different namespaces pins the FQCN choice. claimsReference is narrowed to names containing \, and resolve() answers only route and service references.
  3. PHP attributes don't reach the native kernel. The PHP walker in codegraph-kernel/src/php.rs (the default PHP path) dropped attributes by design, and the wasm extractor never descended into attribute_list. Both now emit the same decorates refs; kernel-php-parity.test.ts checks kernel/wasm equality and the expected refs, and the end-to-end test passes on both paths (CODEGRAPH_KERNEL=0 and default).
  4. The #[ change in strip-comments.ts affects every PHP project. Dropped. Route extraction reads the AST, so commented-out attributes are skipped without touching the comment stripper.

Also fixed from the review of #611

  • A path-less class-level #[Route(defaults: …)] (Shopware's _routeScope) no longer swallows the first method route.
  • Every stacked #[Route] on one action becomes a route.
  • No duplicate class nodes for entities, repositories, subscribers and commands (those extractors were removed; the attribute edges cover them).
  • symfony/polyfill-* is excluded by prefix.
  • Only var/cache/… and .symfony/… count as the compiled container, not any src/Cache/…Container.php.
  • A compiled route's HTTP methods are read from the seventh Route argument (the sixth is the schemes), and an empty list means ANY.
  • #[Route('')] under a class prefix is that prefix.
  • The extra PHP syntax trees are deleted after reading.
  • defaults: / requirements: arrays keep their own pairs when both are present, and unquoted scalars ('page' => 1, => true) are kept. Fixtures that used YAML-style { key: value } (not valid PHP) now use PHP arrays.

Notes for review

  • Route extraction does a second PHP parse in the framework extractor, only for files containing both #[ and Route. The kernel's tables don't carry attribute arguments, so reading them from the kernel would mean widening the kernel ABI. I kept that out of this PR.
  • EXTRACTION_VERSION bumped to 29 (new framework extractor and new PHP edges), so existing indexes get the re-index hint.
  • XML service definitions (services.xml, common in Shopware) are not parsed.

Testing

  • __tests__/symfony-resolver.test.ts (unit, 48 tests) and __tests__/symfony-integration.test.ts (end to end, 10 tests).
  • __tests__/kernel-php-parity.test.ts, with the kernel built (npm run build:kernel).
  • npm test with the kernel built: 9,815 passed, 46 skipped. The only failure, ui-server-api.test.ts > the engine's own busiest symbol (a 100 ms timing bar, 114–128 ms here), runs only on a machine with a local .codegraph/ index of this repository and does not touch PHP or Symfony code.
  • npx tsc --noEmit: clean.

Credits

🤖 Generated with Claude Code and onedot-devkit

deniskern and others added 3 commits October 30, 2026 23:45
The PHP grammar nests `#[A, B]` as attribute_list > attribute_group >
attribute, which the decorator walk never descended, so no PHP attribute
usage became a `decorates` reference. Both extraction paths now emit them:
the wasm extractor and the native kernel's PHP walker, which is the
default for PHP and previously dropped attributes by design. Namespaced
names (`ORM\Entity`, `\Symfony\...\Route`) resolve by their last segment.

SIBLINGS: decorator container never descended -- C# attribute_list is
skipped the same way on both paths (csharp.rs:709); left as is, out of
scope and parity-pinned.
GUARD: kernel-php-parity.test.ts asserts the decorates refs and
kernel/wasm equality.

Co-authored-by: jorge robles <jorge.diego.robles@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Routes come from PHP 8 #[Route] attributes, config/routes.yaml and the
compiled router in var/cache, each a route node with a references edge to
its action; service definitions link to their class. Detection covers
symfony/framework-bundle, symfony/symfony and Shopware 6 (shopware/core).

Changes against the closed colbymchenry#611:
- #[Route] is read from the PHP AST (attribute_list > attribute_group >
  attribute), not a regex over the source: the `(?:\s*\n\s*)*` pattern
  before `class` backtracked exponentially (3.3 s at 16 blank lines).
  A path-less class-level #[Route] no longer swallows the first method
  route, stacked routes are all kept, multi-line attributes keep their
  defaults, and commented-out attributes stay out without changing the
  PHP comment stripper for every project.
- Controller resolution looks the class up by name through the index and
  prefers the written namespace, instead of scanning every class per
  reference; claimsReference only claims names with a `\`.
- No duplicate class nodes for entities, repositories, subscribers and
  commands; the attribute decorates edges cover those classes.
- YAML: a resource import is not a route; polyfills are excluded by
  prefix; only var/cache and .symfony count as the compiled container.
- defaults/requirements arrays keep their own pairs and unquoted scalars.

EXTRACTION_VERSION 29: a new framework extractor changes what an index holds.

Co-authored-by: jorge robles <jorge.diego.robles@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From a review of the branch:
- A compiled Route's HTTP methods are its seventh argument; the sixth is
  the schemes, so `['https']` became an `HTTPS` route and a route with
  methods but no schemes was dropped. An empty list means ANY.
- `#[Route('')]` under a class prefix is that prefix, not a missing path.
- A written FQCN resolves only to the class in that namespace; another
  namespace's class of the same short name is left unlinked.
- The extra PHP syntax trees are deleted after reading, so repeated syncs
  do not hold wasm memory.

Fixtures with methods in the schemes slot now use Symfony's argument order.

SIBLINGS: unfreed getParser() trees -- src/graph/branch-guards.ts and
src/extraction/syntax-tokens.ts, the other callers outside the
extractor, already delete theirs.
GUARD: symfony-resolver.test.ts covers each case and was red before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant