← Back to Blog

Announcing MeliorStan v1

Published on May 2, 2026 by Kevin Ullyott

MeliorStan v1 is out! It is a PHPStan extension that ships over 30 custom rules you can plug into your existing PHPStan setup. There is no separate runner, no separate report, and no separate config file to maintain. You include it in your phpstan.neon, list the rules you want, and the next vendor/bin/phpstan analyse run picks them up.

How It Got Here

For years I reached for PHPMD on PHP projects whenever I wanted opinions about naming conventions, complexity, and general code smells. It has been a staple of the PHP ecosystem for a long time and shaped a lot of how I think about these kinds of checks. The catch was that running it always meant a second tool alongside PHPStan, with its own config, its own report, and its own place to add suppressions.

MeliorStan started as an attempt to take the rules I actually used from PHPMD, rewrite them as PHPStan rules, and have them work with new PHP features and syntax. As well as speaking the same PHPStan vocabulary as the rest of my analysis. Once the core rules were in place I kept going, adding rules for other checks I wanted enforced:

A rule that flags Pest tests with a stray ->only() left in them.

A rule checking for Cognitive Complexity from SonarSource as a separate metric alongside the more traditional cyclomatic and NPath versions.

The list will keep growing. As the community suggests rules, and as I run into things I want enforced on my own projects.

What You Get in V1

The rules fall mostly into a few buckets:

  • Naming conventions. PascalCase classes, camelCase methods and variables, UPPERCASE constants, minimum and maximum name lengths, no getFoo() methods that return a boolean, and similar checks.
  • Code quality. Unused local variables, unused parameters, missing closure type hints, boolean flag arguments that hint at a method doing two things, banned superglobals, and the Pest ->only() rule.
  • Control flow. Discouraging else branches and assignments inside if conditions.
  • Design metrics. Cyclomatic complexity, NPath complexity, Cognitive Complexity, coupling between objects, depth of inheritance, excessive class or method length, too many fields, too many methods, and a handful of similar shape and size checks.

Every rule has its own documentation page in the repo with the available parameters and what they default to.

One important note on installation: MeliorStan does not auto-register any rules. Every rule has to be listed explicitly in your PHPStan config, which is intentional.

The full set is broad, different projects want different things out of it, and turning everything on at once against an existing codebase would generate more findings than anyone is going to triage. Picking rules that makes sense for your project and team keeps the signal high.

Static Analysis, Linting, or Code Smells?

You could be thinking: Are these static analysis rules, are they linting rules, or are they code smell detectors? Some of the rules sound like one, some like another, and a few sit in all three at once.

The lines between those three terms have always been blurry in my opinion, and they have only blurred more over time:

  • Static analysis is the umbrella. Any examination of source code without running it qualifies: type checking, data flow analysis, security scanning, and a long list of other techniques.
  • Linting started in 1978 at Bell Labs as a specific tool called lint that, despite the name, was doing real static analysis on C code. The word has drifted since then and now covers almost any source level checker, from pure formatters up through deep semantic rules.
  • Code smells come from Kent Beck and Martin Fowler. They are heuristics that flag a possible design problem rather than a bug. Long methods, classes with too many fields, feature envy, and so on. The code still runs correctly, the smell just hints that something underneath is off.

Look at how real tools position themselves and the boundaries fall apart fast. ESLint calls itself a linter but builds an AST and runs semantic rules. PHPMD calls itself a mess detector but its checks are not meaningfully different from what a static analyzer does. PHPStan never calls itself a linter, but plenty of teams treat it as one. SonarSource collapses the whole thing into a single taxonomy of "bugs, vulnerabilities, and code smells" and runs them all out of one analyzer.

PHPStan is an excellent home for all of it. It already has the AST, the type information, the baseline support, the per-line ignore comments, and the CI integration. Adding more rules to that pipeline is cheaper than standing up a second tool with its own config, its own report, and its own place to add suppressions. That is a big part of why MeliorStan exists. One tool, one report, one place to look.

What It Is Best For

A few situations where it has paid off for me:

  1. Onboarding a new codebase. Turning on a small handful of naming rules and the unused variable checks usually surfaces a useful list of low risk cleanups. It is a quick way to get a feel for how disciplined the codebase is before you start changing real behavior.
  2. Holding the line on a large project. If you already have a clean baseline, the complexity and length rules are good guardrails. They will not stop someone from writing a 400 line method, but they will fail the build when they try.
  3. Replacing your PHPMD setup. MeliorStan covers the core of every rule that PHPMD ships, rebuilt with first class support for modern PHP features and syntax. If you have been running PHPMD as a second pass on your codebase, you can move those checks into your PHPStan run and retire the extra tool from your CI.

A Real Example

The clearest example of a real configuration is the one we use on Advising App, the student success platform I lead the development on at Canyon GBS. The relevant section of phpstan.neon.dist looks roughly like this:

includes:
    - ./vendor/larastan/larastan/extension.neon
    - ./vendor/pestphp/pest/extension.neon
    - ./vendor/orrison/meliorstan/config/extension.neon

rules:
    - Orrison\MeliorStan\Rules\BooleanGetMethodName\BooleanGetMethodNameRule
    - Orrison\MeliorStan\Rules\CamelCaseMethodName\CamelCaseMethodNameRule
    - Orrison\MeliorStan\Rules\CamelCaseParameterName\CamelCaseParameterNameRule
    - Orrison\MeliorStan\Rules\CamelCasePropertyName\CamelCasePropertyNameRule
    - Orrison\MeliorStan\Rules\CamelCaseVariableName\CamelCaseVariableNameRule
    - Orrison\MeliorStan\Rules\ConstantNamingConventions\ConstantNamingConventionsRule
    - Orrison\MeliorStan\Rules\MissingClosureParameterTypehint\MissingClosureParameterTypehintRule
    - Orrison\MeliorStan\Rules\PascalCaseClassName\PascalCaseClassNameRule
    - Orrison\MeliorStan\Rules\ShortMethodName\ShortMethodNameRule
    - Orrison\MeliorStan\Rules\ShortVariable\ShortVariableRule
    - Orrison\MeliorStan\Rules\Superglobals\SuperglobalsRule
    - Orrison\MeliorStan\Rules\TraitConstantNamingConventions\TraitConstantNamingConventionsRule
    - Orrison\MeliorStan\Rules\ForbidPestPhpOnly\ForbidPestPhpOnlyRule

parameters:
    meliorstan:
        boolean_get_method_name:
            check_parameterized_methods: true
        short_variable:
            exceptions: [id, at, to, as]
        short_method_name:
            minimum_length: 2
        camel_case_property_name:
            ignored_when_in_classes_descendant_of: [Spatie\LaravelSettings\Settings]

A few things worth pointing out:

  • The rules list is explicit. We started with the naming convention rules because those are the safest to enable on an existing codebase, then added the small quality rules over time.
  • The parameters.meliorstan block shows how rules are configured. short_variable.exceptions lets us keep $id, $at, $to, and $as even though they fall under the minimum length. camel_case_property_name.ignored_when_in_classes_descendant_of exempts Spatie settings classes, where the property names are tied to a config key shape we do not control.
  • ForbidPestPhpOnlyRule is the kind of rule that pays for itself the first time someone almost merges a Pest test with ->only() left on it. Imagine not noticing an ->only() was merged in to main a few weeks ago... CI would run real fast for that couple of weeks... hypothetically...

That is more or less the workflow. Pick your rules, configure the ones that need it, let PHPStan run!

Where to Go Next

If you try it out and run into any problems, please open an issue. If there is a rule or configuration you would like to see added, open an issue for that as well. The v1 release is a milestone, not the end of the road by far!