Shared formatting and static analysis configuration.
This package provides:
- dPrint formatting via a shared
dprint.json - PHPStan at level
9, with custom rules for native PHPDoc member contracts (@method,@property,@const),@abstract,@static,@singleton, and sealed trait methods
The conventions here prioritize ergonomics over PSR alignment.
- PHP 8.4+
- Composer
- dPrint CLI (optional, for formatting)
- PHPStan
2.2+
composer require --dev northrook/php-csAdd the package, then run the setup script from your project root:
composer require --dev northrook/php-cs
vendor/bin/php-cs-config
composer updateThe script copies the shared dprint.json, generates a project phpstan.neon, and updates composer.json:
require-devphpstan/phpstanscripts.phpstanvendor/bin/phpstan analysescripts.php-cs-configvendor/bin/php-cs-configscripts.collisionvendor/bin/collision-check
After setup, these run as Composer scripts from the project root:
composer php-cs-config
composer collision
composer phpstanPass --force to overwrite existing config files or refresh values that were already set.
The custom rules and the enforced level 9 live in the package's canonical extension.neon.
The setup script generates a thin project phpstan.neon that includes that extension.neon and declares the analysed paths:
includes:
- vendor/northrook/php-cs/extension.neon
parameters:
paths:
- src
- tests- the source directory (
src, falling back tophp) tests, when present
Add any project-specific overrides (paths, excludePaths, ignoreErrors, a different level) to that generated phpstan.neon.
Run PHPStan from the project root:
composer phpstanInstall the dPrint CLI.
The setup script copies the shared config into the project.
Format PHP files:
dprint fmtDeclare members that implementing or extending types must provide, using standard PHPDoc tags.
Checked on concrete classes, and on interfaces themselves.
| Tag | Example |
|---|---|
@const |
@const STATUS_CODE or @const string STATUS_CODE |
@property |
@property string $name |
@method |
@method string run() or @method static static register() |
@property-read and @property-write are treated like @property.
@method can require static. Types are checked for @method, @property, and @const.
Visibility is not part of standard @method / @property syntax and is not validated.
On concrete classes, mismatches are reported with stable identifiers (e.g. requiresMember.method.TypeMissing).
Unexpected-but-compatible modifiers/types produce ignorable warnings.
Requirements are collected from the class itself, its parents, interfaces, and traits — including nested traits and traits used by parents.
/**
* @method static static register()
*/
abstract class ContractSingleton
{
final protected static function getInstance(): static
{
return self::$instance ??= self::register();
}
}Mark members on abstract classes or traits that every descendant must redeclare — including intermediate abstract classes.
abstract class Base
{
/** @abstract */
public const string LABEL = 'base';
/** @abstract */
protected string $name = 'base';
/** @abstract */
public function label(): string
{
return self::LABEL;
}
}Each class in the hierarchy must declare its own versions of these members; inheritance alone is not enough.
Mark a class (or trait) as a static utility type: it must have a non-public constructor (private or protected). final is not required.
/**
* @static
*/
class Hash
{
private function __construct() {}
public static function checksum(string $value): string { /* ... */ }
}Subclasses must follow the same constructor rule. A @static trait imposes the rule on every class that uses it — including via nested traits or parents that use the trait.
Reported with the staticClass.publicConstructor identifier.
Mark a class as a singleton façade. It must implement \Northrook\Contracts\Interfaces\SingletonInterface. This is intentionally only an interface check — extending Northrook\Contracts\Singleton is the usual way to satisfy the pattern, but is not required by the rule.
/**
* @singleton
*/
final class Debug extends Singleton
{
// ...
}Reported with the singleton.missingInterface identifier.
Errors when a class, trait, or enum body redeclares a final method sealed by a trait — including traits used by parents and nested traits.
PHP silently lets the using type override a trait's final method, defeating the intended seal (PHP only fatals when a subclass overrides an inherited final trait method).
trait Sealed
{
final public function run(): string
{
return 'sealed';
}
}
final class Broken
{
use Sealed;
// finalTraitMethod.overridden
public function run(): string
{
return 'overridden';
}
}Reported with the finalTraitMethod.overridden identifier.
Overrides in test directories are allowed by default. Configure path segments via finalTraitMethod.testDirectories (defaults to tests):
parameters:
finalTraitMethod:
testDirectories:
- tests
- fixturesSet testDirectories to an empty list to enforce the seal everywhere.
The package ships .phpstorm.meta.php.
PhpStorm recognizes @const, @abstract, @static, and @singleton in docblocks (in addition to the built-in @method and @property support).
In this repository:
composer check # phpstan + phpunit + collision
composer phpstan
composer test
composer collision