Skip to content

PHP deep integration: test coverage, security, imports, tree-sitter#178

Open
ikx94 wants to merge 4 commits intopeteromallet:mainfrom
ikx94:feat/php-deep-integration
Open

PHP deep integration: test coverage, security, imports, tree-sitter#178
ikx94 wants to merge 4 commits intopeteromallet:mainfrom
ikx94:feat/php-deep-integration

Conversation

@ikx94
Copy link

@ikx94 ikx94 commented Mar 2, 2026

Summary

WIP — coded with Claude Code.

Upgrades PHP from a minimal phpstan-only plugin to near-parity with Python/TypeScript for framework-aware analysis. Addresses ~20 distinct gaps across security detection, import resolution, test coverage mapping, and tree-sitter queries.

The prompt that started this:

Desloppify has a hook system for language-specific test coverage, but PHP registers zero hooks. Let me look at reference implementations and the PHP plugin.

Three bugs/gaps:

  1. No PHP test_coverage hooks — languages/php/init.py doesn't pass test_coverage_module, so no PHP-specific test-to-source mapping, no assertion patterns, nothing.
  2. _infer_lang_name doesn't know .php — even if hooks existed, the fallback lang inference in mapping.py:26-34 has no .php entry, so PHP files would never trigger the hooks.
  3. No entry_patterns — generic_lang hardcodes entry_patterns=[] at line 250, so all Laravel controllers/commands/middleware/providers are flagged as orphaned (0 importers).

[... full 22-issue audit followed, covering critical/high/medium/low gaps ...]

New files

  • languages/php/test_coverage.py — PHPUnit/Pest assertion patterns, use-statement parsing (incl group use A\{B,C}), has_testable_logic (excludes interfaces/config/migration stubs), is_runtime_entrypoint (controllers/commands/jobs/routes), map_test_to_source, strip_test_markers, strip_comments
  • languages/php/review_data/dimensions.override.json — PHP/Laravel abstraction fitness review guidance

Fixes (12 files, ~20 issues)

Issue File Fix
#14 SECRET_NAME_RE misses $var = security.py Add \$? to char class
#11 ENV_LOOKUPS missing PHP security.py Add env(, getenv(, $_ENV[, config(
#13 LOG_CALLS missing PHP security.py Add Log::info/error/..., error_log()
#12 RANDOM_CALLS missing PHP security.py Add rand(, mt_rand(
#5 Import query misses group use _specs.py, _imports.py New query pattern + prefix prepending
#4 resolve_php_import ignores composer.json _imports.py Read PSR-4 mappings from composer.json
#6 Aliased use Foo as Bar FP _unused_imports.py Extract alias name, search for alias
#8 class_query misses enums _specs.py Add enum_declaration
#9 Closure node type wrong _complexity.py Add anonymous_function_creation_expression
#10 Match arms not counted _complexity.py Add match_conditional_expression
#22 Signature allowlist signature.py Add __construct, boot, register, render, toArray, rules, authorize
#2 _infer_lang_name mapping.py Add .phpphp
#3 entry_patterns hardcoded empty generic.py New entry_patterns param
#1,#16,#19,#20,#24 PHP plugin php/__init__.py Wire test_coverage, entry_patterns, zone_rules, excludes
#15 Remediation text rules.py Add random_bytes() for PHP
#21 Review override dimensions.override.json PHP/Laravel-specific guidance

Deferred

Test plan

  • All 3305 tests pass (3305 passed, 5 skipped)
  • Smoke-tested: import parsing, entrypoint detection, secret matching, test mapping
  • Test against a real Laravel codebase
  • Verify tree-sitter queries against edge cases (nested group use, multi-alias)

🤖 Generated with Claude Code

ikx94 and others added 3 commits March 2, 2026 00:34
…fixes

WIP — coded with Claude Code.

New:
- languages/php/test_coverage.py — full test coverage hooks
  (PHPUnit/Pest assertions, use-statement parsing incl group use,
  has_testable_logic, is_runtime_entrypoint, map_test_to_source)
- languages/php/review_data/dimensions.override.json

Fixes across 10 files addressing ~20 PHP gaps:
- SECRET_NAME_RE: match $variable assignments (peteromallet#14)
- ENV_LOOKUPS: add env(), getenv(), $_ENV[], config() (peteromallet#11)
- LOG_CALLS: add Log::info/error/etc, error_log() (peteromallet#13)
- RANDOM_CALLS: add rand(), mt_rand() (peteromallet#12)
- Import query: group use App\Models\{User, Post} (peteromallet#5)
- resolve_php_import: actually read composer.json PSR-4 (peteromallet#4)
- Unused imports: handle aliased use Foo as Bar (peteromallet#6)
- class_query: add enum_declaration (peteromallet#8)
- _CLOSURE_NODE_TYPES: add anonymous_function_creation_expression (peteromallet#9)
- _BRANCHING_NODE_TYPES: add match_conditional_expression (peteromallet#10)
- Signature _ALLOWLIST: __construct, boot, register, render, etc (peteromallet#22)
- _infer_lang_name: add .php -> php (peteromallet#2)
- generic_lang: entry_patterns parameter (peteromallet#3)
- PHP __init__: wire test_coverage, entry_patterns, zone_rules,
  exclude storage/bootstrap/cache/IDE helpers (peteromallet#1,peteromallet#16,peteromallet#19,peteromallet#20,peteromallet#24)
- Remediation text: add random_bytes() for PHP (peteromallet#15)

Deferred: peteromallet#17 (barrel_names — N/A), peteromallet#18 (dynamic_import_finder)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Trait `use` inside class bodies (use_declaration) was invisible to the
dep graph — trait files only referenced this way appeared orphaned.

- Add use_declaration query patterns for both bare names and FQNs
- Strip leading backslash from FQN trait uses (\App\Traits\X)
- Resolve bare trait names (HasUuid) via filesystem search in app/src/lib
- Cache bare-name lookups to avoid repeated walks

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@peteromallet
Copy link
Owner

Hey, nice work on this! Looks like a solid implementation.

I don't have any PHP projects to test against on my end — would you be able to run it against a real Laravel codebase and verify those tree-sitter edge cases you mentioned in the test plan? That would go a long way toward getting this merged.

`orphaned.py` checks `any(p in r for p in entry_patterns)` where
`r = rel(filepath)` returns relative paths like `app/Http/Controllers/Foo.php`.
All 24 PHP entry patterns had leading `/` — substring check always failed,
zero patterns matched. Every Laravel file flagged orphaned.

Also added missing Laravel convention-loaded directories: Models, Enums,
Actions, Filament, Livewire, View/Components, Http/Requests, Exceptions,
bootstrap, public, lang. Extended test_coverage entrypoint patterns and
content-based detection (FormRequest, Resource).

Tested against two real Laravel codebases (sologaming, zalon).
Code quality: 51% → 97% on both projects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ikx94
Copy link
Author

ikx94 commented Mar 3, 2026

Tested against two real Laravel codebases — a large e-commerce app (~500 production files) and a SaaS booking platform (~760 files). Found and fixed a bug that was tanking PHP scores everywhere.

The bug

All PHP_ENTRY_PATTERNS had a leading / (e.g. /app/Http/Controllers/), but the orphaned detector does a raw substring check against rel() output which returns paths without a leading slash (app/Http/Controllers/Foo.php). The check "/app/Http/Controllers/" in "app/Http/Controllers/Foo.php" always fails — zero entry patterns were matching. Every Laravel file was flagged orphaned regardless of config.

This is why code quality was stuck at ~50% on every Laravel project.

The fix (just pushed)

Stripped leading / from all 24 patterns. Also added missing Laravel convention-loaded directories that legitimately have zero explicit importers:

  • app/Http/Requests/ (injected by Laravel DI)
  • app/Exceptions/
  • app/Models/ (Eloquent, route model binding, relationship references)
  • app/Enums/ (model casts, Filament forms)
  • app/Actions/ (Fortify/Jetstream/custom, bound in service providers)
  • app/Filament/ (auto-discovered by Filament)
  • app/Livewire/ (auto-discovered by Livewire)
  • app/View/Components/ (Blade component auto-discovery)
  • bootstrap/, public/, lang/

Extended test_coverage.py entrypoint detection similarly — added events/, observers/, filament/, livewire/, http/requests/ path patterns and FormRequest/Resource to content-based extends matching.

Real-world results

Project A (large e-commerce, ~500 production files):

  • Code quality: 51.2% → 97.7%
  • Orphaned files: 465 → ~20
  • Overall: 87.1 → 90.1

Project B (SaaS platform, ~760 files):

  • Code quality: 55.5% → 97.5%
  • Orphaned files: 103 → 4 (3 are _ide_helper generated files, 1 genuinely unused service)
  • Overall: 80.1 → 82.8

Note: same entry pattern bug exists in other languages

Dart, Go, TypeScript, C#, GDScript all have leading / in their entry patterns too. Zone rules aren't affected (they use _match_pattern which pads the path). Impact is lower on those languages since they have fewer convention-based entry points, but it's still dead code. Worth a follow-up PR.

Tree-sitter edge cases

The tree-sitter queries handled everything I threw at them — standard use statements, group use A\B\{C, D}, aliased imports, trait use declarations. PSR-4 resolution via composer.json worked correctly across both projects. No edge case failures found.

@peteromallet
Copy link
Owner

Really appreciate the thoroughness here — the real-world testing against two Laravel codebases is exactly what was needed, and the entry pattern slash bug is a great catch. The before/after numbers speak for themselves (51% → 98% code quality is huge).

I'm in the middle of pushing a big batch of changes right now so I don't want to merge this on top of that and create a mess. Going to add this in once things settle down on my end. Thanks again for the work on this — it's a solid contribution.

@iNem0o
Copy link

iNem0o commented Mar 4, 2026

@ikx94 Great work here, and very convincing Laravel validation!

Drive-by comment from a newcomer: I only discovered this project recently, so my understanding is still limited.

This PR introduces many Laravel-specific conventions into the PHP layer. My concern is that others framework projects could get noisier results (false positives/negatives on entrypoints and architecture signals) if these conventions are applied by default.

Would moving to a framework-agnostic PHP design in this PR require a larger refactor, or would you prefer keeping this Laravel-first now and adding framework support for PHP in a follow-up?

I’m not trying to block this PR, I’m trying to align on direction early so I can try to contribute Symfony support later :)

@ikx94
Copy link
Author

ikx94 commented Mar 4, 2026

@ikx94 Great work here, and very convincing Laravel validation!

Drive-by comment from a newcomer: I only discovered this project recently, so my understanding is still limited.

This PR introduces many Laravel-specific conventions into the PHP layer. My concern is that others framework projects could get noisier results (false positives/negatives on entrypoints and architecture signals) if these conventions are applied by default.

Would moving to a framework-agnostic PHP design in this PR require a larger refactor, or would you prefer keeping this Laravel-first now and adding framework support for PHP in a follow-up?

I’m not trying to block this PR, I’m trying to align on direction early so I can try to contribute Symfony support later :)

I would encorage you to try it on Symfony and to make it less Laravel specific, that’s definitely the way to go

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.

3 participants