PHPackages                             infocyph/phpprobe - 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. [Utility &amp; Helpers](/categories/utility)
4. /
5. infocyph/phpprobe

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

infocyph/phpprobe
=================

Standalone PHP syntax and duplicate code checker.

0.4.4(2mo ago)04.8k↓20.2%MITPHPPHP &gt;=8.2CI passing

Since May 2Pushed 2mo agoCompare

[ Source](https://github.com/infocyph/PHPProbe)[ Packagist](https://packagist.org/packages/infocyph/phpprobe)[ RSS](/packages/infocyph-phpprobe/feed)WikiDiscussions main Synced 1w ago

READMEChangelogDependencies (6)Versions (11)Used By (0)

PHPProbe
========

[](#phpprobe)

[![Security & Standards](https://github.com/infocyph/PHPProbe/actions/workflows/ci.yml/badge.svg)](https://github.com/infocyph/PHPProbe/actions/workflows/ci.yml)[![Packagist Downloads](https://camo.githubusercontent.com/8bb41313836aee37c0cd1b2bfcf1fc388c29cc44dc2bede9da8b85c1b3774cd6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f696e666f637970682f50485050726f62653f636f6c6f723d677265656e266c696e6b3d68747470732533412532462532467061636b61676973742e6f72672532467061636b61676573253246696e666f6379706825324650485050726f6265)](https://camo.githubusercontent.com/8bb41313836aee37c0cd1b2bfcf1fc388c29cc44dc2bede9da8b85c1b3774cd6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f696e666f637970682f50485050726f62653f636f6c6f723d677265656e266c696e6b3d68747470732533412532462532467061636b61676973742e6f72672532467061636b61676573253246696e666f6379706825324650485050726f6265)[![License: MIT](https://camo.githubusercontent.com/784362b26e4b3546254f1893e778ba64616e362bd6ac791991d2c9e880a3a64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d677265656e2e737667)](https://opensource.org/licenses/MIT)[![Packagist Version](https://camo.githubusercontent.com/dd79983c560883814ef66211cfd8e3bb20d5624dba8ea5b63ad65439af87ddfd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f696e666f637970682f50485050726f6265)](https://camo.githubusercontent.com/dd79983c560883814ef66211cfd8e3bb20d5624dba8ea5b63ad65439af87ddfd/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f696e666f637970682f50485050726f6265)[![Packagist PHP Version](https://camo.githubusercontent.com/fbdcb560d752f08f49d4f08bb4420efacee67967fde287728473e4edeca19398/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f696e666f637970682f50485050726f62652f706870)](https://camo.githubusercontent.com/fbdcb560d752f08f49d4f08bb4420efacee67967fde287728473e4edeca19398/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f696e666f637970682f50485050726f62652f706870)[![GitHub Code Size](https://camo.githubusercontent.com/424c2e4a865a8487bd91414ee3cefb0d53bd36d5da7fd1761343208e6157b6b9/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c616e6775616765732f636f64652d73697a652f696e666f637970682f50485050726f6265)](https://camo.githubusercontent.com/424c2e4a865a8487bd91414ee3cefb0d53bd36d5da7fd1761343208e6157b6b9/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c616e6775616765732f636f64652d73697a652f696e666f637970682f50485050726f6265)

Standalone PHP checker for syntax validation, duplicate-code detection, public API snapshot checks and comment policy checks.

PHPProbe is the checker runtime. It can be used directly as `phpprobe`, required by tool-combiner packages such as PHPForge, or called from PHP code through the public gateway classes.

Requirements
------------

[](#requirements)

- PHP `>=8.2`
- `nikic/php-parser` `>=5.0 run(['--config=phpprobe.json', 'src']);
$duplicateCode = (new DuplicateChecker())->run(['--preset=strict', '--json', 'src']);
$apiCode = (new ApiSnapshotChecker())->run(['--baseline=.phpprobe-api-baseline.json', 'src']);
$commentCode = (new CommentChecker())->run(['--strict', '--fail-on=warning', 'src']);
```

Everything else is internal implementation detail, grouped by role:

NamespacePurpose`Api`Public API snapshot extraction from parser ASTs.`Console`CLI dispatch for `bin/phpprobe`.`Config`Config lookup, preset lookup, JSON parsing, config merging and shared CLI option handling.`Detection`Duplicate-code token indexing, AST block indexing, scoring, grouping and pruning.`Filesystem`Git-aware PHP file discovery and path exclusion.`Process`Small `proc_open` runner wrappers.`Util`Narrow shared helpers.Config Lookup
-------------

[](#config-lookup)

The default config filename is `phpprobe.json`.

When a checker needs a config file and `--config` was not passed, PHPProbe resolves it in this order:

1. `phpprobe.json` in the current project root, meaning the current working directory.
2. `vendor/infocyph/phpprobe/resources/phpprobe.json` under the current project root.
3. `resources/phpprobe.json` only when the current project itself is `infocyph/phpprobe`.

If no config can be found, PHPProbe throws a runtime config error.

Preset files are bundled resources. They are resolved from:

1. `vendor/infocyph/phpprobe/resources/presets/.json`.
2. `resources/presets/.json` only while developing `infocyph/phpprobe` itself.

Project-root preset files are not looked up automatically.

When `--config=FILE` is passed explicitly and that file is missing, unreadable, empty, or invalid JSON, PHPProbe treats it as an empty config and continues with internal defaults plus any CLI options.

Config Format
-------------

[](#config-format)

The bundled `resources/phpprobe.json` is intentionally small:

```
{
  "preset": "standard"
}
```

A full project config may override any part of the selected preset:

```
{
  "preset": "standard",
  "output": {
    "colors": {
      "success": "green",
      "error": "red",
      "warning": "yellow",
      "info": "cyan",
      "muted": "gray",
      "file": "cyan",
      "severity": {
        "error": "red",
        "critical": "red",
        "high": "red",
        "warning": "yellow",
        "medium": "yellow",
        "low": "blue",
        "info": "gray"
      }
    }
  },
  "syntax": {
    "paths": ["src"],
    "exclude": ["src/generated"]
  },
  "duplicates": {
    "paths": ["src"],
    "exclude": ["src/generated"],
    "mode": "audit",
    "normalize": true,
    "fuzzy": true,
    "near_miss": true,
    "min_lines": 5,
    "min_tokens": 90,
    "min_statements": 4,
    "min_similarity": 0.85,
    "baseline": "",
    "write_baseline": "",
    "ignore_fingerprints": [],
    "output": {
      "style": "compact",
      "score_colors": {
        "high": { "min": 260, "color": "red" },
        "medium": { "min": 180, "color": "yellow" },
        "low": { "min": 120, "color": "cyan" },
        "base": { "color": "gray" }
      }
    },
    "json": false
  },
  "api": {
    "paths": ["src"],
    "exclude": ["src/generated"],
    "include_protected": true,
    "baseline": "",
    "write_baseline": "",
    "json": false
  },
  "comments": {
    "paths": ["src"],
    "exclude": ["src/generated"],
    "scan_markers": true,
    "doc_mode": "hybrid",
    "doc_signature_consistency": true,
    "doc_type_hygiene": true,
    "baseline": "",
    "write_baseline": "",
    "fail_on": "error",
    "fail_confidence": "low",
    "explain": false,
    "custom_rules": [],
    "doc_cache": {
      "enabled": true,
      "file": ".phpprobe-comments-doc-cache.json"
    },
    "rules": {
      "comment_marker": {
        "enabled": true
      },
      "commented_out_code_with_weak_reason": {
        "severity": "warning"
      }
    }
  }
}
```

Config keys accept snake case, kebab case and camel case. For example, `min_tokens`, `min-tokens` and `minTokens` are equivalent. Excludes can be configured as either `exclude` or `exclude_paths`. `duplicates.ignore_fingerprints` suppresses known clone fingerprints without a baseline file. `comments.rules` lets you toggle rule `enabled` and override per-rule `severity`.

Internal duplicate defaults, before any preset is applied, are `mode=gate`, `normalize=true`, `fuzzy=false`, `near_miss=false`, `min_lines=5`, `min_tokens=70`, `min_statements=4`, `min_similarity=0.85`, no baseline, no JSON output and no configured paths or excludes.

Internal API defaults are `include_protected=true`, no baseline, no JSON output and no configured paths or excludes.

Config merge order is:

1. Internal checker defaults.
2. Config-file `preset`, when present.
3. Explicit values in the config file.
4. CLI `--preset=NAME`, when present.
5. Explicit CLI flags and CLI paths.

Local config values override the config-file preset. CLI `--preset` is a run-level override and can override config-file values. Explicit CLI flags still win after that.

Presets
-------

[](#presets)

Preset templates live in `resources/presets/` and are loaded by `Infocyph\PHPProbe\Config\PresetRepository`.

Available presets:

PresetDuplicate policyAPI policyComment policy`default`Raw engine defaults. `gate` mode, normalized tokens, no fuzzy identifiers, no near-miss matching, `min_lines=5`, `min_tokens=70`, `min_statements=4`, `min_similarity=0.85`.Includes protected members.`policy=standard`, baseline thresholds (`min_reason_length=12`, `max_allowed_block_lines=10`, `require_issue_for_blocks_longer_than=3`).`standard`Recommended balanced preset. `audit` mode, normalized tokens, fuzzy identifiers, near-miss matching, `min_lines=5`, `min_tokens=90`, `min_statements=4`, `min_similarity=0.85`.Includes protected members.`policy=standard`, baseline thresholds (`12`, `10`, `3`).`ci`Quieter CI gate. `gate` mode, normalized tokens, fuzzy identifiers, no near-miss matching, `min_lines=6`, `min_tokens=100`, `min_statements=5`, `min_similarity=0.9`.Includes protected members.`policy=standard`, baseline thresholds (`12`, `10`, `3`).`strict`Sensitive audit. `audit` mode, normalized tokens, fuzzy identifiers, near-miss matching, `min_lines=4`, `min_tokens=70`, `min_statements=3`, `min_similarity=0.8`.Includes protected members.`policy=strict`, strict thresholds (`16`, `6`, `2`).The `standard`, `ci`, and `strict` presets include the same default syntax, duplicate, API and comment excludes:

```
tests, vendor, node_modules, .git, .idea, .vscode, coverage,
.phpunit.cache, .psalm-cache, build, dist, tmp, .tmp, storage,
bootstrap/cache, var/cache

```

Their duplicate sections also exclude `storage/framework/views`.

Preset commands:

```
php vendor/bin/phpprobe presets
php vendor/bin/phpprobe preset standard
```

`presets` prints one preset name per line. `preset ` prints the bundled JSON template. Unknown preset names print an error and exit `2`. Legacy alias `phpstorm` is still accepted and resolves to `standard`.

Combined Check Command
----------------------

[](#combined-check-command)

`check` runs `syntax`, `duplicates`, `api`, and `comments` in sequence and returns a combined exit code.

```
php vendor/bin/phpprobe check [options] [paths...]
```

Options:

OptionFormMeaning`--config``--config=FILE`Read checker settings from a specific config file.`--preset``--preset=NAME`Apply `default`, `standard`, `ci`, or `strict`.`--format``--format=textjson`--summary-json``--summary-json=FILE`Write combined run summary JSON.`--report-dir``--report-dir=DIR`Write per-checker `text/json/markdown/sarif` reports plus `summary.json`.`--changed-only`flagScan only changed PHP files from Git diff.`--changed-base``--changed-base=REF`Base ref used with `--changed-only`.`--fail-on``--fail-on=errorwarning`--fail-confidence``--fail-confidence=lowmedium`--doc-mode``--doc-mode=heuristicparser`--explain`flagPassed through to comments.`--help`flagPrint command help.Exit behavior:

- If any checker exits `2`, combined exit is `2`.
- Else if any checker exits non-zero, combined exit is `1`.
- Otherwise combined exit is `0`.

Config Validate Command
-----------------------

[](#config-validate-command)

```
php vendor/bin/phpprobe config validate [options]
```

Options:

- `--config=FILE` (default `./phpprobe.json`)
- `--json` (machine-readable result)
- `--help`

Returns `0` for valid config, `1` for schema/key/type validation errors, and `2` for missing/unreadable/invalid JSON files.

Init Command
------------

[](#init-command)

```
php vendor/bin/phpprobe init [options]
```

Options:

- `--preset=NAME` (`default`, `standard`, `ci`, `strict`; default `standard`)
- `--path=FILE` (default `./phpprobe.json`)
- `--with-ci` (also writes `.github/workflows/phpprobe.yml`)
- `--force` (overwrite existing files)
- `--help`

Doctor Command
--------------

[](#doctor-command)

```
php vendor/bin/phpprobe doctor [options]
```

Options:

- `--config=FILE` (default `./phpprobe.json`)
- `--json`
- `--help`

Checks include PHP version, required extensions, config validity, and CaptainHook presence.

Syntax Checker
--------------

[](#syntax-checker)

The syntax checker discovers PHP files, then runs PHP's native lint command against each file:

```
php -d display_errors=1 -l
```

Command:

```
php vendor/bin/phpprobe syntax [options] [paths...]
```

Options:

OptionFormMeaning`--config``--config=FILE` or `--config FILE`Read checker settings from a specific config file.`--preset``--preset=NAME` or `--preset NAME`Apply `default`, `standard`, `ci`, or `strict` as a run-level preset.`--exclude``--exclude=PATH` or `--exclude PATH`Exclude a path. Repeatable.`--format``--format=textjson`--json`flagAlias for `--format=json`.`--summary-json``--summary-json=FILE`Write a machine-readable run summary JSON.`--changed-only`flagScan only changed PHP files from Git diff.`--changed-base``--changed-base=REF`Base ref used with `--changed-only`.`--parallel``--parallel=N`Parallel lint worker count. Default is `1`.`--help`, `-h`flagPrint syntax checker help and exit `0`.Path behavior:

- CLI paths override `syntax.paths` from config.
- If CLI paths are empty, `syntax.paths` is used.
- If both are empty, discovery starts from `.`.
- Config excludes and CLI excludes are merged.

Output and exits:

ConditionStreamExitNo PHP files found`stdout`: `No PHP files found.` plus summary`0`All files pass`stdout`: `Syntax OK: N PHP files checked.` plus summary`0`One or more files fail`stderr`: failing file list plus lint output`1`Unknown option or runtime config error`stderr`: error`2`JSON note: comment findings include `confidence`, `subtype`, `explanation`, `suggestion`, and `autofix` (suggestion metadata) when available. | Unknown preset | `stderr`: preset error | `2` |

Comment Policy Checker
----------------------

[](#comment-policy-checker)

The comment checker scans PHP comments using `token_get_all()` and reports marker tags and commented-out code policy findings.

Command:

```
php vendor/bin/phpprobe comments [options] [paths...]
```

Options:

OptionFormMeaning`--config``--config=FILE` or `--config FILE`Read checker settings from a specific config file.`--preset``--preset=NAME` or `--preset NAME`Apply `default`, `standard`, `ci`, or `strict` as a run-level preset.`--exclude``--exclude=PATH` or `--exclude PATH`Exclude a path. Repeatable.`--format``--format=textjson`--json`flagAlias for `--format=json`.`--strict`flagEscalate commented-out-code policy severities.`--policy``--policy=relaxedstandard`--doc-mode``--doc-mode=heuristicparser`--baseline``--baseline=FILE`Suppress comment findings already present in a baseline.`--write-baseline``--write-baseline` or `--write-baseline=FILE`Write current comment findings to a baseline and exit `0`.`--fail-on``--fail-on=errorwarning`--fail-confidence``--fail-confidence=lowmedium`--explain`flagInclude why/suggestion details per finding in text/markdown output.`--summary-json``--summary-json=FILE`Write a machine-readable run summary JSON.`--changed-only`flagScan only changed PHP files from Git diff.`--changed-base``--changed-base=REF`Base ref used with `--changed-only`.`--tags``--tags=TODO,FIXME,...`Override marker tags for marker detection.`--help`, `-h`flagPrint comments checker help and exit `0`.Config-only options:

- `comments.rules` allows per-finding overrides such as `{ "comment_marker": { "enabled": false } }` or `{ "commented_out_code_with_weak_reason": { "severity": "info" } }`.
- `comments.doc_mode` accepts `heuristic`, `parser`, or `hybrid`.
- `comments.fail_confidence` accepts `low`, `medium`, or `high`.
- `comments.doc_signature_consistency` enables signature-vs-PHPDoc consistency checks.
- `comments.doc_type_hygiene` enables PHPDoc tag/type hygiene checks.
- `comments.explain` enables explanation and suggestion details.
- `comments.baseline` and `comments.write_baseline` map to comment baseline CLI flags.
- `comments.doc_cache` configures persistent PHPDoc parse cache with `enabled` and `file`.
- `comments.custom_rules` allows regex-based project-specific rules (`id`, `pattern`, `severity`, `message`, `enabled`, `scope`).

### Comment policy and PHPDoc checks

[](#comment-policy-and-phpdoc-checks)

1. Marker detection: tags like `TODO`, `FIXME`, `BUG`, `HACK`, `SECURITY`, `REVIEW`, `DEPRECATED`.
2. Commented-out code requires directly attached tagged reason.
3. Long commented-out blocks require an issue reference.
4. Oversized commented-out blocks are always reported.
5. Dead suppressions are surfaced for cleanup.
6. PHPDoc signatures/tag values are validated against native declarations.

Default thresholds:

- `min_reason_length = 12`
- `require_issue_for_blocks_longer_than = 3`
- `max_allowed_block_lines = 10`

Policy-to-finding mapping:

PolicyFinding typesMarker detection`comment_marker`Tagged reason required for commented-out code`commented_out_code_without_reason`, `commented_out_code_without_valid_tag`, `commented_out_code_without_valid_reason`, `commented_out_code_with_weak_reason`Issue reference required for long blocks`commented_out_code_requires_issue_reference`Oversized block disallowed`commented_out_code_block_too_large`PHPDoc code without clear example label`commented_out_code_in_phpdoc_without_example_label`Invalid suppression directive`invalid_suppression_rule`Expired suppression directive`expired_suppression_rule`Unused suppression directive`dead_suppression_rule`PHPDoc tag/type parse issue`phpdoc_invalid_tag_value`PHPDoc parameter missing from native signature`phpdoc_unknown_param`Native parameter missing in PHPDoc`phpdoc_missing_param`PHPDoc/native type mismatch`phpdoc_signature_mismatch`Explicitly valid tagged reason (informational)`commented_out_code_with_valid_reason`Inline suppression format:

- `@phpprobe-ignore RULE_ID`
- `@phpprobe-ignore RULE_A,RULE_B until=YYYY-MM-DD`
- `@phpprobe-ignore RULE_ID scope=symbol`
- `@phpprobe-ignore RULE_ID scope=symbol symbol=ClassName::method`

Comment baseline usage:

```
php vendor/bin/phpprobe comments --write-baseline=.phpprobe-comments-baseline.json src
php vendor/bin/phpprobe comments --baseline=.phpprobe-comments-baseline.json src
```

Output and exits:

ConditionStreamExitNo failing findings at threshold`stdout`: summary (or JSON/markdown/SARIF)`0`Findings at or above threshold`stderr`: text report (or JSON on `stdout`)`1`Unknown option or runtime config error`stderr`: error`2`Public API Snapshot Checker
---------------------------

[](#public-api-snapshot-checker)

The API checker parses PHP files with `nikic/php-parser`, extracts the package-visible surface and can compare it with a saved snapshot. It is intended for library BC drift checks, not type analysis.

Command:

```
php vendor/bin/phpprobe api [options] [paths...]
```

Options:

OptionFormMeaning`--config``--config=FILE` or `--config FILE`Read checker settings from a specific config file.`--preset``--preset=NAME` or `--preset NAME`Apply `default`, `standard`, `ci`, or `strict` as a run-level preset.`--exclude``--exclude=PATH` or `--exclude PATH`Exclude a path. Repeatable.`--public-only`flagIgnore protected class members.`--include-protected`flagInclude protected members. This is the default.`--baseline``--baseline=FILE`Compare the current API against a snapshot file.`--write-baseline``--write-baseline`, `--write-baseline=FILE`Write the current API snapshot and exit `0`. Bare flag writes `.phpprobe-api-baseline.json`.`--format``--format=textjson`--json`flagAlias for `--format=json`.`--fail-on``--fail-on=errorwarning`--summary-json``--summary-json=FILE`Write a machine-readable run summary JSON.`--changed-only`flagScan only changed PHP files from Git diff.`--changed-base``--changed-base=REF`Base ref used with `--changed-only`.`--help`, `-h`flagPrint API checker help and exit `0`.Path behavior:

- CLI paths override `api.paths` from config.
- If CLI paths are empty, `api.paths` is used.
- If both are empty, discovery starts from `.`.
- Config excludes and CLI excludes are merged.

Snapshot contents:

- named classes, interfaces, traits and enums
- top-level namespaced functions
- top-level namespaced constants
- public members always
- protected members unless `--public-only` is used
- class modifiers, inheritance, implemented interfaces, method signatures, property signatures, constants, enum cases, function signatures and stable fingerprints

Output and exits:

ConditionStreamExitNo baseline passed`stdout`: `Public API snapshot OK: N symbol(s) scanned.``0`Baseline matches`stdout`: `Public API unchanged: N symbol(s) scanned.``0`Baseline differs`stderr`: added/removed/changed symbol list`1` by default, `0` when `--fail-on=error``--format=jsonmarkdownsarif`--write-baseline``stdout`: baseline message or JSON result`0`Unknown option or runtime config/baseline error`stderr`: error`2`Unknown preset`stderr`: preset error`2`Duplicate Checker
-----------------

[](#duplicate-checker)

The duplicate checker combines token fingerprints, AST block structure, statement windows, near-miss similarity, grouping, pruning, ranking and optional baseline suppression.

Command:

```
php vendor/bin/phpprobe duplicates [options] [paths...]
```

Options:

OptionFormMeaning`--config``--config=FILE` or `--config FILE`Read checker settings from a specific config file.`--preset``--preset=NAME` or `--preset NAME`Apply `default`, `standard`, `ci`, or `strict` as a run-level preset.`--exclude``--exclude=PATH` or `--exclude PATH`Exclude a path. Repeatable.`--mode``--mode=gate` or `--mode=audit``gate` runs token matching; `audit` also enables statement matching and near-miss matching.`--min-lines``--min-lines=N`Minimum duplicated line span. Values below `1` become `1`.`--min-tokens``--min-tokens=N`Token fingerprint window size. Values below `1` become `1`.`--min-statements``--min-statements=N`Statement window size for audit matching. Values below `1` become `1`.`--min-similarity``--min-similarity=N`Near-miss threshold. Accepts `0.0..1.0` or `0..100`; values above `1` are treated as percentages.`--near-miss`flagEnable bounded statement/shape similarity matching.`--exact`flagDisable variable/literal normalization and disable fuzzy matching.`--fuzzy`flagNormalize identifiers/calls as `ID` for renamed-code scans.`--no-fuzzy`flagDisable fuzzy identifier/call normalization.`--baseline``--baseline=FILE`Suppress clone groups whose fingerprints are already in a baseline file.`--write-baseline``--write-baseline`, `--write-baseline=FILE`Write current clone fingerprints to a baseline and exit `0`. Bare flag writes `.phpprobe-duplicates-baseline.json`.`--format``--format=textjson`--json`flagAlias for `--format=json`.`--fail-on``--fail-on=errorwarning`--error-duplicate-percentage``--error-duplicate-percentage=N`Error threshold used when `--fail-on=error`. Default `20`.`--summary-json``--summary-json=FILE`Write a machine-readable run summary JSON.`--changed-only`flagScan only changed PHP files from Git diff.`--changed-base``--changed-base=REF`Base ref used with `--changed-only`.`--no-cache`flagDisable duplicate result cache.`--cache-file``--cache-file=FILE`Duplicate result cache path.`--help`, `-h`flagPrint duplicate checker help and exit `0`.Config-only options:

- `duplicates.ignore_fingerprints` accepts a list of clone fingerprints to suppress without using a baseline file.
- `duplicates.output.style` accepts `compact` or `classic`.
- `duplicates.output.score_colors` lets you tune score thresholds and ANSI colors per band.

Exact accepted forms matter: numeric options, `--mode`, `--baseline` and valued `--write-baseline=FILE` are parsed in equals form. `--config`, `--preset` and `--exclude` also accept split form. `--write-baseline` may also be passed as a bare flag.

Path behavior:

- CLI paths override `duplicates.paths` from config.
- If CLI paths are empty, `duplicates.paths` is used.
- If both are empty, discovery starts from `.`.
- Config excludes and CLI excludes are merged.

Mode behavior:

- `gate`: token-window duplicate detection only, unless `--near-miss` is explicitly passed.
- `audit`: token-window matching plus statement-window matching and near-miss matching is enabled automatically.

Output and exits:

ConditionStreamExitNo clone groups after baseline suppression`stdout`: `No new duplicated code found (...)` plus summary`0`Clone groups found`stderr`: text report plus summary`1` by default`--format=jsonmarkdownsarif`--write-baseline``stdout`: baseline message or JSON result`0`Unknown option or runtime config/baseline error`stderr`: error`2`Unknown preset`stderr`: preset error`2`Duplicate Detection Details
---------------------------

[](#duplicate-detection-details)

File discovery:

- PHPProbe first tries `git ls-files -z --cached --others --exclude-standard`.
- It filters discovered PHP files with `git check-ignore -z --stdin --no-index`.
- If Git discovery is unavailable, it recursively scans the selected paths.
- Recursive fallback skips common infrastructure directories such as `.git`, `.idea`, `.phpunit.cache`, `.psalm-cache`, `.vscode`, `coverage`, `node_modules` and `vendor`.

Token normalization:

- Whitespace, comments, doc comments, PHP open tags and close tags are ignored.
- With `normalize=true`, variables become `VAR`, numbers become `NUM`, strings become `STR`.
- With `fuzzy=true`, identifiers and names become `ID`.
- With `--exact`, token values include token names and original text.

Token clones:

- PHPProbe hashes every normalized token window of `min_tokens` tokens.
- Matching windows are candidate clones.
- Candidates are extended token-by-token to find the full matching region.
- Overlapping windows in the same file are ignored.
- Clone regions below `min_lines` are ignored.

AST and statement matching:

- PHPProbe uses `nikic/php-parser` to index structural blocks.
- Indexed blocks include functions, methods, closures, arrow functions, loops, branches, match arms and try/catch/finally blocks.
- Statement hashes are built from AST shape.
- In `audit` mode, matching statement windows of `min_statements` statements are reported as statement clones.

Near-miss matching:

- Near-miss matching compares blocks with the same block type.
- Similarity is weighted as `72%` statement-hash similarity and `28%` AST-shape similarity.
- Similarity is based on longest-common-subsequence ratio.
- Matches below `min_similarity` are ignored.

Grouping, pruning and scoring:

- Duplicate pairs are grouped into clone families.
- Contained/weaker clones are pruned.
- Results are ranked by score, line span and similarity.
- Scoring rewards larger clones, more occurrences, higher similarity, structural completeness and near-miss signal; small trivial clones are penalized.

Duplicate JSON Result Shape
---------------------------

[](#duplicate-json-result-shape)

`phpprobe duplicates --json` emits:

```
{
  "files": 2,
  "total_lines": 100,
  "duplicated_lines": 20,
  "duplicate_percentage": 20.0,
  "known_clones": 0,
  "new_clones": 1,
  "clones": [
    {
      "fingerprint": "...",
      "source": "tokens",
      "score": 120.5,
      "similarity": 1.0,
      "tokens": 90,
      "lines": 10,
      "statements": 0,
      "block_type": "function",
      "occurrences": [
        {
          "file": "src/Example.php",
          "start_line": 10,
          "end_line": 20,
          "lines": 11,
          "context": "function"
        }
      ]
    }
  ]
}
```

Clone `source` is one of:

- `tokens`
- `statements`
- `near_miss`

`known_clones` is populated when a duplicate baseline is read. `new_clones` is the number of clone groups remaining after baseline suppression.

API JSON Result Shape
---------------------

[](#api-json-result-shape)

`phpprobe api --json` emits:

```
{
  "snapshot": {
    "version": 1,
    "generated_at": "2026-05-02T00:00:00+00:00",
    "symbols": [
      {
        "id": "class App\\Service",
        "kind": "class",
        "name": "App\\Service",
        "file": "src/Service.php",
        "line": 5,
        "modifiers": ["final"],
        "extends": "",
        "implements": [],
        "members": [],
        "fingerprint": "..."
      }
    ]
  },
  "baseline": {
    "version": 1,
    "generated_at": "",
    "symbols": []
  },
  "changed": false,
  "changes": {
    "added": [],
    "removed": [],
    "changed": []
  },
  "classifications": {
    "added": [],
    "removed": [],
    "changed": [
      {
        "id": "class App\\Service",
        "impact": "breaking",
        "reason": "Member signature changed: method App\\Service::run()"
      }
    ]
  },
  "impact": {
    "breaking": 1,
    "additive": 0,
    "internal": 0
  }
}
```

Baselines
---------

[](#baselines)

Write a baseline:

```
php vendor/bin/phpprobe duplicates --write-baseline
php vendor/bin/phpprobe duplicates --write-baseline=.phpprobe-duplicates-baseline.json
php vendor/bin/phpprobe api --write-baseline
php vendor/bin/phpprobe api --write-baseline=.phpprobe-api-baseline.json
```

Use a baseline:

```
php vendor/bin/phpprobe duplicates --baseline=.phpprobe-duplicates-baseline.json
php vendor/bin/phpprobe api --baseline=.phpprobe-api-baseline.json
```

This repository does not use a duplicate baseline in CI; duplicate findings are expected to be resolved directly.

Duplicate baseline files contain:

```
{
  "version": 1,
  "generated_at": "2026-05-02T00:00:00+00:00",
  "clones": [
    {
      "fingerprint": "...",
      "source": "tokens",
      "score": 100.0
    }
  ]
}
```

API baseline files use the same top-level `version`, `generated_at` and `symbols` shape emitted under the `snapshot` JSON key. Missing, unreadable, or invalid baseline files now fail with exit code `2`. Duplicate baseline files follow the same strict behavior: missing, unreadable, or invalid baselines fail with exit code `2`.

Colored Output
--------------

[](#colored-output)

Checker text output is colorized on interactive terminals:

- green: successful summaries
- yellow: warning/medium severity lines
- red: error/high/critical summaries
- cyan: baseline write notifications

You can override these globally for all checker text output with root-level `output.colors`:

```
{
  "output": {
    "colors": {
      "success": "green",
      "error": "red",
      "warning": "yellow",
      "info": "cyan",
      "muted": "gray",
      "file": "cyan",
      "severity": {
        "error": "red",
        "critical": "red",
        "high": "red",
        "warning": "yellow",
        "medium": "yellow",
        "low": "blue",
        "info": "gray"
      }
    }
  }
}
```

Color output is automatically disabled for non-TTY streams and when `NO_COLOR` is set (or `TERM=dumb`), so CI logs and JSON output stay clean.

CI / Cloud
----------

[](#ci--cloud)

Workflow: [.github/workflows/ci.yml](.github/workflows/ci.yml)

CI runs:

1. PHPProbe matrix on PHP `8.2`, `8.3`, `8.4`, `8.5`:
    - `composer validate --strict`
    - `composer test`
    - `composer lint`
    - `composer duplicates`
    - `composer api`
    - `composer comments`
2. PHPForge integration:
    - checks out `infocyph/phpforge`
    - injects local `phpprobe` via Composer `path` repository
    - runs PHPForge tests

`workflow_dispatch` supports `phpforge_ref` to test a specific PHPForge branch/tag/SHA.

Development
-----------

[](#development)

Composer scripts:

ScriptCommand`composer test``vendor/bin/pest -c pest.xml``composer check``php bin/phpprobe check --preset=standard --config=resources/phpprobe.json src tests``composer lint``php bin/phpprobe syntax src tests``composer duplicates``php bin/phpprobe duplicates --preset=standard --config=resources/phpprobe.json src tests``composer api``php bin/phpprobe api --config=resources/phpprobe.json src tests``composer comments``php bin/phpprobe comments --config=resources/phpprobe.json src tests``composer doctor``php bin/phpprobe doctor --config=resources/phpprobe.json``composer bench:comments``php tools/bench-comments.php`Useful local checks:

```
composer validate --strict
composer test
composer check
composer lint
composer duplicates
composer api
composer comments
git diff --check
```

###  Health Score

43

—

FairBetter than 89% of packages

Maintenance86

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~2 days

Total

9

Last Release

69d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/33b750b84ab22f2038ef9b90ee48f2fcdaab6b5698e9f956a89a152466200b63?d=identicon)[abmmhasan](/maintainers/abmmhasan)

---

Top Contributors

[![abmmhasan](https://avatars.githubusercontent.com/u/7711128?v=4)](https://github.com/abmmhasan "abmmhasan (34 commits)")

---

Tags

phpsyntaxinfocyphduplicate-codecopy-paste-detection

###  Code Quality

TestsPest

Static AnalysisRector

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/infocyph-phpprobe/health.svg)

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

###  Alternatives

[zircote/swagger-php

Generate interactive documentation for your RESTful API using PHP attributes (preferred) or PHPDoc annotations

5.3k144.5M623](/packages/zircote-swagger-php)[dedoc/scramble

Automatic generation of API documentation for Laravel applications.

2.1k11.2M113](/packages/dedoc-scramble)[deptrac/deptrac

Deptrac is a static code analysis tool that helps to enforce rules for dependencies between software layers.

3.0k8.8M207](/packages/deptrac-deptrac)[psalm/plugin-laravel

Psalm plugin for Laravel

3345.3M347](/packages/psalm-plugin-laravel)[dereuromark/cakephp-ide-helper

CakePHP IdeHelper Plugin to improve auto-completion

1882.3M45](/packages/dereuromark-cakephp-ide-helper)[phparkitect/phparkitect

Enforce architectural constraints in your PHP applications

9224.3M28](/packages/phparkitect-phparkitect)

PHPackages © 2026

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