PHPackages                             denzyl/phanalist - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Testing &amp; Quality](/categories/testing)
4. /
5. denzyl/phanalist

ActiveLibrary[Testing &amp; Quality](/categories/testing)

denzyl/phanalist
================

Performant static analyzer for PHP, which is extremely easy to use. It helps you catch common mistakes in your PHP code.

v1.1.4(5d ago)16046.6k↓66.3%8[1 PRs](https://github.com/denzyldick/phanalist/pulls)MITRustCI passing

Since Oct 3Pushed 2d ago3 watchersCompare

[ Source](https://github.com/denzyldick/phanalist)[ Packagist](https://packagist.org/packages/denzyl/phanalist)[ RSS](/packages/denzyl-phanalist/feed)WikiDiscussions main Synced 2d ago

READMEChangelog (10)DependenciesVersions (44)Used By (0)

[![](https://raw.githubusercontent.com/denzyldick/phanalist/main/docs/branding/banner-cropped.png)](https://raw.githubusercontent.com/denzyldick/phanalist/main/docs/branding/banner-cropped.png)

[![Crates.io](https://camo.githubusercontent.com/29408512d7273f68bb2658388f6fe0a662e01adecbb50e8d1058e1bc8c96a972/68747470733a2f2f696d672e736869656c64732e696f2f6372617465732f762f7068616e616c6973742e737667)](https://crates.io/crates/phanalist)[![License: MIT](https://camo.githubusercontent.com/7013272bd27ece47364536a221edb554cd69683b68a46fc0ee96881174c4214c/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d626c75652e737667)](./LICENSE)[![CI](https://github.com/denzyldick/phanalist/actions/workflows/rust.yml/badge.svg)](https://github.com/denzyldick/phanalist/actions)

> Performant static analyzer for PHP, written in Rust. Catches common mistakes and enforces best practices with zero configuration required.

---

### 🤔 Why Phanalist?

[](#-why-phanalist)

PHP codebases grow. As they grow, they accumulate technical debt — god classes that do everything, methods no one can follow, hidden complexity that breaks with every change. Traditional linters catch syntax errors and style issues, but they don't tell you if your code is **maintainable**.

Phanalist focuses on **structural health**. It measures what matters for long-term maintainability:

- **Complexity metrics** — cyclomatic complexity, cognitive complexity, LOC per method, nested paths
- **Coupling &amp; cohesion** — Law of Demeter violations, god classes, data classes, fan-in/fan-out
- **Object-oriented design** — depth of inheritance, weighted methods per class, response for a class
- **Readability** — comment ratios, error suppression, method parameter counts

Think of it as a health checkup for your PHP code. It doesn't just tell you *that* something is wrong — each rule explains *why* it matters and *how* to fix it.

---

### ✨ Features

[](#-features)

- 🚀 **Fast** — built in Rust, analyzes large codebases in seconds
- 🔍 **31 built-in rules** — covering complexity, style, design patterns, and more
- ⚙️ **Zero config to start** — works out of the box, configure only what you need
- 📄 **Multiple output formats** — `text`, `json`, `sarif` (for CI pipelines with inline PR annotations via [GitHub Action](https://github.com/marketplace/actions/phanalist)), and `codeclimate` (for Code Quality platforms)
- 🔌 **Extensible** — adding a custom rule takes minutes

---

### Installation

[](#installation)

The simplest way to install Phanalist is to use the installation script:

```
curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/denzyldick/phanalist/main/bin/init.sh | sh
```

It will automatically download the executable for your platform:

```
$ ~/phanalist -V
phanalist 1.0.0
```

There are also [multiple other installation options](./docs/installation.md).

### GitHub Action

[](#github-action)

Add inline SARIF annotations to your PRs using the [Phanalist GitHub Action](https://github.com/marketplace/actions/phanalist):

```
name: Phanalist
on: [pull_request]
permissions:
  security-events: write
jobs:
  phanalist:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: denzyldick/phanalist-action@v1
```

---

### Usage

[](#usage)

To analyze your project sources, run:

```
~/phanalist
```

#### Example

[](#example)

[![Example](docs/branding/example.gif)](docs/branding/example.gif)

On the first run `phanalist.yaml` will be created with the default configuration and reused on all subsequent runs.

**Additional CLI flags:**

FlagDescriptionDefault`--config`, `-c`Path to configuration file`./phanalist.yaml``--src`, `-s`Path(s) to project sources (repeatable, e.g. `-s src -s tests`)`./src``--rules`, `-r`Only run these rules (overrides config)from config`--output-format`, `-o`Output format: `text`, `json`, `sarif`, `codeclimate``text``--summary-only`Show only violation counts per rule—`--quiet`, `-q`Suppress all output—`--verbose`, `-v`Increase verbosity; repeat for more (`-v` main pass, `-vv` parsing, `-vvv` indexing)—`--debug-rule-timing`Print per-rule per-file timing (min/max/avg/p90/p95/p99 + slowest files)—`--debug-rule-stats`Print per-rule cost/coverage stats (time, %, violations, files, statements)—`--use-baseline`Filter results against a baseline file, reporting only new violations—`--update-baseline`Regenerate the baseline from the current scan (requires `--use-baseline`)—`--blame`Attribute violations to engineers via git blame and show a quality report—`--since`Only count violations from commits after this date (e.g. `"30 days"`, `"1 year"`, `"2025-01-01"`)—`--until`Only count violations from commits before this date (e.g. `"2025-06-01"`)—`--export-chart`Export engineer chart as PNG/SVG image (requires `--blame`)—`--exclude-author`Exclude authors from the report (repeatable, e.g. `--exclude-author dependabot`)—`--min-violations`Minimum total violations to include an engineer in the report`0``--lsp`Start as a Language Server (LSP) for editor integrations—---

### Engineer Quality Report

[](#engineer-quality-report)

Use the `--blame` flag to see who is introducing and fixing violations in your codebase:

```
# Who has violations in their code currently
~/phanalist --blame

# See what changed in the last 30 days (Net column shows who is improving vs degrading)
~/phanalist --blame --since "30 days"

# See changes over the last year
~/phanalist --blame --since "1 year"

# Exclude bots
~/phanalist --blame --since "30 days" --exclude-author dependabot

# Sort by net improvements (who is adding the most value)
~/phanalist --blame --since "30 days" --sort net
```

The report shows a table with:

ColumnMeaning**Engineer**Git author name**Fixed (✓)**Violations that disappeared compared to the `--since` snapshot**Introduced (✗)**Violations that appeared compared to the `--since` snapshot**Net**`Fixed - Introduced` — positive means improving, negative means degrading**Sorting:**

Use `--sort` to change the order: `total` (default, by volume), `net` (by net improvements), `name`, `fixed`, `introduced`.

**How it works:**

- **Without `--since`:** Looks at the current violations in your code and uses git blame to figure out who last touched each affected line. Engineers are credited with the violations in code they most recently worked on.
- **With `--since `:** Takes a snapshot of your code as it was at that date, runs the same analysis on the old version, and compares the results. Violations that disappeared were "fixed" — violations that appeared were "introduced". Each change is attributed to the engineer who made it.

**Output:**

The report includes a summary table and a per-rule breakdown with colored counts (green for fixed, red for introduced).

The `--blame` flag works with `--output-format json` — the engineer data is included as an `"engineer_report"` field in the JSON output for use in pipelines or dashboards.

Requires a `.git` directory (discovered from the current working directory). Only files within `--src` paths are attributed.

---

### Baseline

[](#baseline)

A baseline lets you adopt phanalist on an existing codebase without fixing every finding at once. It freezes the current violations; later runs report only new ones, so CI stays green on known debt but fails on regressions.

Generate (or regenerate) the baseline:

```
~/phanalist --use-baseline phanalist-baseline.json --update-baseline
```

Then run against it (in CI, or locally):

```
~/phanalist --use-baseline phanalist-baseline.json
```

The baseline is a pretty-printed, stably sorted JSON file, so it produces clean diffs and merges. Each entry is keyed on the file, rule, and a stable message id with a count, so unrelated edits that shift line numbers do not invalidate it, and reworded message text does not either. When you fix violations, regenerate the baseline to shrink it.

---

### Configuration

[](#configuration)

```
enabled_rules: []   # empty = all rules active
disable_rules: []
exclude_paths: []   # paths skipped before any rule runs (see below)
rules:
  E0007:
    check_constructor: true
    max_parameters: 5
  E0009:
    max_complexity: 10
  E0010:
    max_paths: 200
  E0012:
    include_namespaces:
      - "App\\Service\\"
      - "App\\Controller\\"
    exclude_namespaces: []
    reset_interfaces:
      - "ResetInterface"
  E0015:
    threshold: 1
  E0016:
    max_complexity: 15
  E0024:
    max_loc: 30
  E0025:
    max_loc: 500
  E0026:
    min_ratio: 0.1
    max_ratio: 0.5
  E0027:
    max_methods: 15
    max_fields: 10
  E0028:
    max_getter_setter_ratio: 0.7
    min_methods: 3
  E0029:
    max_fan_out: 10
    max_fan_in: 20
  E0030:
    max_density: 0.3
```

- **`enabled_rules`** — whitelist of rules to run (empty = all)
- **`disable_rules`** — rules to skip
- **`rules`** — per-rule configuration options
- **`exclude_paths`** — files skipped before any rule runs, as directory prefixes (`var/cache`, `bootstrap/cache`) or globs (`**/*.generated.php`). Handy for framework caches and frozen code like migrations that would only add noise. Literal (non-glob) patterns that don't exist on disk trigger a warning at `-v` verbosity — a helpful catch for typos. Globs that match nothing are silently accepted.

---

### Rules

[](#rules)

CodeNameOptionsE0000Example rule[E0001](/src/rules/examples/e1/e1.md)Opening tag position[E0002](/src/rules/examples/e2/e2.md)Empty catch[E0003](/src/rules/examples/e3/e3.md)Method modifiers[E0004](/src/rules/examples/e4/e4.md)Uppercase constants[E0005](/src/rules/examples/e5/e5.md)Capitalized class name[E0006](/src/rules/examples/e6/e6.md)Property modifiers[E0007](/src/rules/examples/e7/e7.md)Method parameters count`check_constructor: true`, `max_parameters: 5`[E0008](/src/rules/examples/e8/e8.md)Return type signature[E0009](/src/rules/examples/e9/e9.md)Cyclomatic complexity`max_complexity: 10`[E0010](/src/rules/examples/e10/e10.md)Npath complexity`max_paths: 200`[E0011](/src/rules/examples/e11/e11.md)Detect error suppression symbol (`@`)[E0012](/src/rules/examples/e12/e12.md)Service compatibility with Shared Memory Model`include_namespaces`, `exclude_namespaces`, `reset_interfaces`[E0013](/src/rules/examples/e13/e13.md)Private method not being used[E0014](/src/rules/examples/e14/e14.md)Law of Demeter[E0015](/src/rules/examples/e15/e15.md)Lack of Cohesion of Methods (LCOM4)`threshold: 1`[E0016](/src/rules/examples/e16/e16.md)Cognitive complexity`max_complexity: 15`[E0017](/src/rules/examples/e17/e17.md)Coupling Between Objects (CBO)`max_coupling: 10`[E0018](/src/rules/examples/e18/e18.md)Weighted Methods per Class (WMC)`max_wmc: 50`[E0019](/src/rules/examples/e19/e19.md)Response For a Class (RFC)`max_rfc: 50`[E0020](/src/rules/examples/e20/e20.md)Depth of Inheritance Tree (DIT)`max_depth: 4`[E0021](/src/rules/examples/e21/e21.md)Number of Children (NOC)`max_children: 15`[E0022](/src/rules/examples/e22/e22.md)Afferent and Efferent Coupling (Ca/Ce)`max_ca: 20`, `max_ce: 20`[E0023](/src/rules/examples/e23/e23.md)Instability, Abstractness, Distance (I/A/D)`max_instability: 0.8`, `max_abstractness: 0.8`, `max_distance: 0.5`[E0024](/src/rules/examples/e24/e24.md)Lines of Code per Method`max_loc: 30`[E0025](/src/rules/examples/e25/e25.md)Lines of Code per File`max_loc: 500`[E0026](/src/rules/examples/e26/e26.md)Comment Ratio`min_ratio: 0.1`, `max_ratio: 0.5`[E0027](/src/rules/examples/e27/e27.md)God Class (Brain Class)`max_methods: 15`, `max_fields: 10`[E0028](/src/rules/examples/e28/e28.md)Data Class`max_getter_setter_ratio: 0.7`, `min_methods: 3`[E0029](/src/rules/examples/e29/e29.md)Fan-in / Fan-out`max_fan_out: 10`, `max_fan_in: 20`[E0030](/src/rules/examples/e30/e30.md)Cyclomatic Complexity Density`max_density: 0.3`Adding a new rule is straightforward — [this tutorial](./docs/adding_new_rule.md) explains how.

---

### 🔌 Language Server Protocol (LSP)

[](#-language-server-protocol-lsp)

Phanalist includes built-in LSP support to provide real-time, as-you-type diagnostics directly in your editor.

To start the language server, run:

```
phanalist --lsp
```

#### Editor Configuration Examples

[](#editor-configuration-examples)

##### Neovim (using `vim.lsp`)

[](#neovim-using-vimlsp)

Add the following to your `init.lua` or filetype plugin (`ftplugin/php.lua`):

```
vim.api.nvim_create_autocmd("FileType", {
  pattern = "php",
  callback = function()
    local root_dir = vim.fs.dirname(vim.fs.find({'phanalist.yaml', '.git'}, { upward = true })[1])
    if root_dir then
      vim.lsp.start({
        name = "phanalist-lsp",
        cmd = { "phanalist", "--lsp" },
        root_dir = root_dir,
      })
    end
  end,
})
```

##### VS Code

[](#vs-code)

You can use any generic LSP extension or configure your settings to run `phanalist --lsp` as a language client for PHP files.

---

### Articles

[](#articles)

Read a series of chapters on  to understand the project's internals — a great, easy-to-read introduction.

1. [Write your own static analyzer for PHP.](https://dev.to/denzyldick/the-beginning-of-my-php-static-analyzer-in-rust-5bp8)
2. [How I made it impossible to write spaghetti code.](https://dev.to/denzyldick/how-i-made-it-impossible-to-write-spaghetti-code-dg4)
3. [Detecting spaghetti code in AST of a PHP source code.](https://dev.to/denzyldick/traversing-an-ast-of-php-source-code-2kee)
4. [Getting Symfony app ready for Swoole, RoadRunner, and FrankenPHP (no AI involved).](https://dev.to/sergiid/getting-symfony-app-ready-for-swoole-roadrunner-and-frankenphp-no-ai-involved-2d0g)
5. [Improve your CI output](https://dev.to/denzyldick/improve-your-ci-output-2eg)
6. [Why using unserialize in PHP is a bad idea](https://dev.to/denzyldick/why-is-unserializing-an-object-in-php-a-bad-idea-3odl)

###  Health Score

58

—

FairBetter than 98% of packages

Maintenance99

Actively maintained with recent releases

Popularity45

Moderate usage in the ecosystem

Community19

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 82% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~52 days

Recently: every ~2 days

Total

20

Last Release

5d ago

Major Versions

v0.1.32 → v1.0.02026-06-14

### Community

Maintainers

![](https://www.gravatar.com/avatar/1d0a8bd745f09678b85bde73cef8411e3f06cd38c0cb2de36da7bc153e67177a?d=identicon)[denzyl](/maintainers/denzyl)

---

Top Contributors

[![denzyldick](https://avatars.githubusercontent.com/u/2477646?v=4)](https://github.com/denzyldick "denzyldick (437 commits)")[![SerheyDolgushev](https://avatars.githubusercontent.com/u/166894?v=4)](https://github.com/SerheyDolgushev "SerheyDolgushev (46 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (27 commits)")[![Fabccc](https://avatars.githubusercontent.com/u/33267330?v=4)](https://github.com/Fabccc "Fabccc (9 commits)")[![GrzegorzDrozd](https://avatars.githubusercontent.com/u/1885137?v=4)](https://github.com/GrzegorzDrozd "GrzegorzDrozd (7 commits)")[![mleczakm](https://avatars.githubusercontent.com/u/3474636?v=4)](https://github.com/mleczakm "mleczakm (3 commits)")[![KevinMartinsDevUbi](https://avatars.githubusercontent.com/u/100281657?v=4)](https://github.com/KevinMartinsDevUbi "KevinMartinsDevUbi (2 commits)")[![supun-io](https://avatars.githubusercontent.com/u/44988673?v=4)](https://github.com/supun-io "supun-io (1 commits)")[![azjezz](https://avatars.githubusercontent.com/u/29315886?v=4)](https://github.com/azjezz "azjezz (1 commits)")

---

Tags

phprefactoringstatic-analysisstatic-code-analysisphpstatic analysisrefactoringcode qualitystatic analyzerCleancodelintingmaintainabilityphanalist

### Embed Badge

![Health badge](/badges/denzyl-phanalist/health.svg)

```
[![Health](https://phpackages.com/badges/denzyl-phanalist/health.svg)](https://phpackages.com/packages/denzyl-phanalist)
```

###  Alternatives

[vimeo/psalm

A static analysis tool for finding errors in PHP applications

5.9k82.2M7.7k](/packages/vimeo-psalm)[larastan/larastan

Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel

6.5k55.4M8.4k](/packages/larastan-larastan)[phan/phan

A static analyzer for PHP

5.6k11.9M1.2k](/packages/phan-phan)[php-code-archeology/php-code-archeology

Static analyzer for PHP project archeology. Calculates various metrics for your codebase.

825.2k](/packages/php-code-archeology-php-code-archeology)[dave-liddament/sarb

Provides tools for baselining static analysis results and comparing against that baseline

1651.6M](/packages/dave-liddament-sarb)[exakat/exakat

The smart static analyzer for PHP

437.8k](/packages/exakat-exakat)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
