Skip to content

feat: php (Laravel + Symfony) structural support#97

Merged
boorad merged 3 commits into
mainfrom
feat/86-php-structural
May 29, 2026
Merged

feat: php (Laravel + Symfony) structural support#97
boorad merged 3 commits into
mainfrom
feat/86-php-structural

Conversation

@boorad
Copy link
Copy Markdown
Collaborator

@boorad boorad commented May 29, 2026

Summary

Adds first-class structural detection for PHP, covering Laravel (Gates, Policies, route middleware, $this->authorize / $user->can helpers) and Symfony (Voters, $this->isGranted / denyAccessUnlessGranted, #[IsGranted] attributes), plus idiomatic role / permission shapes that show up in hand-rolled authz.

Tree-sitter-php is wired through the existing discovery → parser → matcher pipeline, with PHP-specific import tracking and propagation edges (parameters via type, fields via member-access name, property defaults) — mirroring the patterns already established for Kotlin, Ruby, and C#.

Changes

  • Wire tree-sitter-php 0.24 through parser.rs and discovery.rs (.php / .phtml extensions, plus a Php entry in the deep-pass language map).
  • 11 new PHP rules under rules/php/ with both Rego and Cedar templates:
    • Laravel: gate-allows-denies, gate-define, authorize-helper, policy-class, route-middleware
    • Symfony: voter-class, is-granted, is-granted-attribute
    • Idiomatic: role-equals-check, in-array-role-check, has-role-call
  • find_php_policy_imports covering both use Foo\Bar [as B]; (simple) and use Foo\Bar\{Baz, Qux as Q}; (group) forms, including use function … / use const ….
  • PHP propagation visitor: simple_parameter (type → variable), assignment_expression ($this->field = $x), property_element default values, and the nullsafe member-call analogue.
  • tests/scanner_enforcement_points.rs::enforcement_points_increments_for_php_policy_di pins constructor-DI → field propagation end-to-end.
  • docs/corpus/php.md calibration report against Monica (Laravel) and the official Symfony demo: 41 findings on Monica, 4 on Symfony demo, 0 false positives across both runs.
  • Review follow-up: tightened php-laravel-authorize-helper to require a principal-shaped receiver ($this, user-named variable, or ->user() / ::user() chain) so $widget->can('render') and $client->cannot('disconnect') no longer fire as high-confidence RBAC. Includes nullsafe ($user?->can(...)) coverage.
  • README + docs/corpus/README.md updated: PHP moves from "planned" to "yes".

Test plan

  • cargo build
  • cargo fmt --check
  • cargo clippy --all-features --all-targets -- -D warnings
  • cargo test --all-features (matcher + imports + integration tests all green)
  • cargo run -- rules test (498 inline rule tests passing)
  • Manual end-to-end probe: synthetic PHP file exercising all 11 rules produces the expected findings per rule, with $widget->can(...) / $client->cannot(...) correctly suppressed.

Closes #86

Summary by CodeRabbit

  • New Features

    • Structural PHP scanning added with broad Laravel and Symfony authorization detections (gates, policies, authorize/can helpers, route middleware, voters, IsGranted)
    • New RBAC pattern detectors for role/permission checks (in_array, hasRole, equality checks)
  • Documentation

    • README and corpus docs updated to reflect PHP support and scan findings
  • Tests

    • Extensive PHP matcher and end-to-end regression tests added for the new detections

Review Change Stack

boorad added 2 commits May 29, 2026 14:14
Closes #86. Wires tree-sitter-php into the structural scanner and ships
11 rules covering Laravel Gates/Policies/route-middleware, Symfony Voters
and #[IsGranted], plus three idiomatic role-comparison shapes.

Policy-import propagation gains a PHP path: `use` declarations (simple,
aliased, function/const, grouped), `assignment_expression`, constructor
DI via `simple_parameter` typed args, and `property_element` defaults —
matching how PHP DI threads a policy service through a controller field
and out to call sites.

Calibration: Monica (Laravel) at e08e917 surfaces 41 findings across the
expected authz surface (AuthServiceProvider Gate::define vocabulary,
VaultPolicy CRUD methods, can:* route middleware); symfony/demo at
83d4ac1 surfaces 4 findings hitting every Symfony rule. Docs in
docs/corpus/php.md.
`->can()` and `->cannot()` are common method names well outside Laravel
authz — network clients, render gates, feature toggles, JWT tokens.
The rule previously fired on any receiver, producing high-confidence
false positives like `$networkClient->cannot('disconnect')` and
`$widget->can('render')`.

Constrain the receiver to principal-shaped names: `$this`, user-named
variables (`$user`, `$currentUser`, `$me`, …), or call chains ending in
`->user()` / `::user()` (the canonical `Auth::user()` / `auth()->user()`
/ `$request->user()` Laravel idioms). Also add `nullsafe_member_call_expression`
to the query alternation so PHP 8's `$user?->can(...)` matches the same
way as the bare `->` form.
@boorad boorad self-assigned this May 29, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2460d9b8-b939-4a15-946d-fa8c8bfac0d7

📥 Commits

Reviewing files that changed from the base of the PR and between 9740a89 and b341e14.

📒 Files selected for processing (3)
  • rules/php/laravel-authorize-helper.toml
  • rules/php/symfony-is-granted-attribute.toml
  • src/scanner/matcher.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • rules/php/symfony-is-granted-attribute.toml
  • rules/php/laravel-authorize-helper.toml
  • src/scanner/matcher.rs

📝 Walkthrough

Walkthrough

Adds PHP structural scanning: tree-sitter-php integration, ~10 PHP authorization rules (Laravel + Symfony + idiomatic role checks), import/propagation analysis for PHP DI patterns, embedded rule registration, extensive matcher and E2E tests, and documentation/corpus updates.

Changes

PHP Structural Scanning

Layer / File(s) Summary
Language parser and file-type detection
Cargo.toml, src/scanner/parser.rs, src/scanner/discovery.rs
Adds tree-sitter-php = "0.24"; wires get_language to LANGUAGE_PHP; recognition of .php/.phtml and updated discovery tests.
PHP authorization rule definitions
rules/php/*.toml, src/rules/embedded.rs
Adds TOML rules for Laravel Gates (allows/denies/define), authorize/can helpers, policy classes, route middleware, Symfony isGranted/#[IsGranted]/voter classes, and idiomatic role checks (==/===, in_array, hasRole); each includes Rego and Cedar templates and fixture tests.
Embedded registry update
src/rules/embedded.rs
Registers the new PHP rules into the embedded rule set via include_str!.
PHP policy import and propagation analysis
src/scanner/imports.rs
Adds PHP use parsing (simple/grouped/aliased/function/const), filters policy imports, and extracts propagation edges from assignments, constructor/method DI parameters, and property initializers with tests.
Rule matching and integration validation
src/scanner/matcher.rs, tests/scanner_enforcement_points.rs
Adds parse_and_match_php, dozens of matcher tests for positive/negative cases across frameworks and idioms, and an E2E enforcement-point regression for PHP DI routing.
Documentation and corpus calibration
README.md, docs/corpus/README.md, docs/corpus/php.md
Update README status and supported-languages table for PHP; add corpus README row and a detailed docs/corpus/php.md report for Monica and Symfony demo targets.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • EnforceAuth/zift#32: Adds Go structural support via shared language-wiring functions in parser, discovery, and embedded rule registry.
  • EnforceAuth/zift#96: Similar language-support wiring changes (parser/discovery/embedded rules) to add Ruby structural support.
  • EnforceAuth/zift#50: Modifies src/rules/embedded.rs to change embedded rule registrations across languages.

Poem

🐰 I hopped through gates both old and new,
sniffed Voters, attributes, and middleware too,
found roles in arrays and hasRole calls,
stitched PHP parsing into Zift's halls,
now scans leap higher — one rabbit, two!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: php (Laravel + Symfony) structural support' clearly and concisely summarizes the main change: adding structural scanner support for PHP with framework-specific pattern detection.
Linked Issues check ✅ Passed All acceptance criteria from issue #86 are met: tree-sitter-php integrated [src/scanner/parser.rs], Laravel Gates/Policies/middleware detection implemented [11 rules], Symfony Voter/isGranted support added, idiomatic role-check rules included, docs/corpus/php.md calibration committed, README updated.
Out of Scope Changes check ✅ Passed All changes are directly scoped to PHP structural support as defined in #86. No WordPress, CodeIgniter, Yii, or CakePHP patterns were introduced; changes are limited to Laravel, Symfony, and idiomatic role checks.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/86-php-structural

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@amazon-q-developer amazon-q-developer Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR successfully adds PHP language support to the scanner with comprehensive coverage of Laravel and Symfony authorization patterns. The implementation follows the established architecture and includes thorough test coverage across parser, imports, matcher, and enforcement point tracking. No blocking issues identified.


You can now have the agent implement changes and create commits directly on your pull request's source branch. Simply comment with /q followed by your request in natural language to ask the agent to make changes.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/scanner/matcher.rs (1)

2159-2174: ⚡ Quick win

Add a direct $this->isGranted(...) positive case for Symfony parity.

This suite asserts denyAccessUnlessGranted(...), but not a direct $this->isGranted(...) call. Adding one dedicated positive test would better lock the rule behavior you claim in PR scope.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/scanner/matcher.rs` around lines 2159 - 2174, Add a new positive test
case to assert that a direct $this->isGranted(...) call is matched: create or
extend the existing test function (e.g., php_symfony_is_granted_matches) to
include a snippet using $this->isGranted('EDIT', $post) and call
parse_and_match_php with the same rules file (php/symfony-is-granted.toml), then
assert !findings.is_empty() to ensure the rule matches the direct isGranted
usage; keep the test structure consistent with the existing
denyAccessUnlessGranted case.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@rules/php/laravel-authorize-helper.toml`:
- Around line 32-42: The receiver predicate in the rule for
nullsafe_member_call_expression only matches non-nullsafe chains like .+->user()
and should be widened to also accept nullsafe chains ?->user(); update the
predicate that targets the `@receiver/`@method_name pattern to allow both -> and
?-> (e.g., change the regex or pattern fragment to accept an optional ? before
->), ensure the rule continues to bind the same captures
(nullsafe_member_call_expression, `@receiver`, `@method_name`, `@ability`), and add a
test fixture demonstrating a nullsafe chain such as
$request?->user()?->can('view', $post) to validate the change.

In `@rules/php/symfony-is-granted-attribute.toml`:
- Around line 14-27: The attribute query misses fully-qualified names because
tree-sitter-php uses qualified_name for namespaced attributes; update the
pattern that captures the attribute name to accept both bare and qualified forms
(e.g., match either (name) `@attr_name` or (qualified_name (name) `@attr_name`) so
#[...\\IsGranted] is captured) and adjust the predicate for
rule.predicates.attr_name (currently eq = "IsGranted") to compare the captured
`@attr_name` text (the short name) rather than the whole qualified string so the
rule still matches when the attribute is namespaced.

---

Nitpick comments:
In `@src/scanner/matcher.rs`:
- Around line 2159-2174: Add a new positive test case to assert that a direct
$this->isGranted(...) call is matched: create or extend the existing test
function (e.g., php_symfony_is_granted_matches) to include a snippet using
$this->isGranted('EDIT', $post) and call parse_and_match_php with the same rules
file (php/symfony-is-granted.toml), then assert !findings.is_empty() to ensure
the rule matches the direct isGranted usage; keep the test structure consistent
with the existing denyAccessUnlessGranted case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bb75cf79-6ac7-431e-b9ff-24a7abaa33a2

📥 Commits

Reviewing files that changed from the base of the PR and between 2ac2292 and 9740a89.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (21)
  • Cargo.toml
  • README.md
  • docs/corpus/README.md
  • docs/corpus/php.md
  • rules/php/has-role-call.toml
  • rules/php/in-array-role-check.toml
  • rules/php/laravel-authorize-helper.toml
  • rules/php/laravel-gate-allows-denies.toml
  • rules/php/laravel-gate-define.toml
  • rules/php/laravel-policy-class.toml
  • rules/php/laravel-route-middleware.toml
  • rules/php/role-equals-check.toml
  • rules/php/symfony-is-granted-attribute.toml
  • rules/php/symfony-is-granted.toml
  • rules/php/symfony-voter-class.toml
  • src/rules/embedded.rs
  • src/scanner/discovery.rs
  • src/scanner/imports.rs
  • src/scanner/matcher.rs
  • src/scanner/parser.rs
  • tests/scanner_enforcement_points.rs

Comment thread rules/php/laravel-authorize-helper.toml
Comment thread rules/php/symfony-is-granted-attribute.toml
…hapes

- laravel-authorize-helper: receiver regex now accepts nullsafe chains
  like $request?->user()?->can(...) in addition to ->user().
- symfony-is-granted-attribute: query now matches qualified attribute
  names like #[\Symfony\...\IsGranted(...)] alongside bare #[IsGranted].
- matcher: add direct $this->isGranted(...) positive test.
@boorad
Copy link
Copy Markdown
Collaborator Author

boorad commented May 29, 2026

@coderabbitai Addressed nitpick in b341e14: added php_symfony_is_granted_direct_call_matches in src/scanner/matcher.rs covering a direct $this->isGranted('EDIT', $post) call, parallel to the existing denyAccessUnlessGranted test.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Tip

For best results, initiate chat on the files or code changes.

@boorad Thanks for the follow-up! The new php_symfony_is_granted_direct_call_matches test nicely rounds out the Symfony is-granted coverage — having both the denyAccessUnlessGranted and isGranted paths explicitly exercised in the matcher tests is the right symmetry.

@boorad boorad merged commit e6b9991 into main May 29, 2026
2 checks passed
@boorad boorad deleted the feat/86-php-structural branch May 29, 2026 18:54
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.

Structural scanner support for PHP

1 participant