Readalizer Documentation

Readable PHP, enforced.

Readalizer is a PHP static analysis tool that enforces readability standards via AST analysis. Configure your rules, run the CLI, and get actionable violations with consistent formatting.

Current release: 1.0.2 (2026-02-23)
PHP: 8.1+
Config file: readalizer.php
composer require readalizer/readalizer
            vendor/bin/readalizer --init
            vendor/bin/readalizer src/
CLI uses readalizer.php when no paths are provided.

Getting Started

Readalizer scans PHP source files, parses them into an AST, and runs configured readability rules. The CLI exits with 1 when violations are found, so it works cleanly in CI or git hooks.

Installation

composer require readalizer/readalizer

Readalizer is distributed as a Composer package. The CLI entrypoint is vendor/bin/readalizer.

Quick Start

vendor/bin/readalizer --init
              vendor/bin/readalizer
              vendor/bin/readalizer src/ lib/

When no paths are provided, Readalizer uses the paths array from readalizer.php. Use --init to generate the config scaffold.

vendor/bin/readalizer --init

CLI

Primary options wired into runtime behavior:

vendor/bin/readalizer --config=path/to/readalizer.php
              vendor/bin/readalizer --jobs=4 --memory-limit=2G
              vendor/bin/readalizer --progress
              vendor/bin/readalizer --debug

Exit codes:

0 — No violations
1 — Violations detected

The CLI help lists additional options (--format, --cache, --baseline, --max-violations) that are parsed but not yet enforced in runtime. If you plan to implement them, start in src/Command/AnalyseCommand.php, src/Config/ConfigurationLoader.php, and src/Analysis/Analyser.php.

Configuration

Readalizer loads configuration from a PHP file that returns an array. Use --config to point to a different file.

Generate a starter config with vendor/bin/readalizer --init. The default config file location is readalizer.php in the current working directory.

<?php

use Readalizer\Readalizer\Rulesets\ClassDesignRuleset;
use Readalizer\Readalizer\Rulesets\FileStructureRuleset;
use Readalizer\Readalizer\Rulesets\MethodDesignRuleset;
use Readalizer\Readalizer\Rulesets\NamingRuleset;
use Readalizer\Readalizer\Rulesets\TypeSafetyRuleset;
use Readalizer\Readalizer\Rules\NoAssignmentInConditionRule;
use Readalizer\Readalizer\Rules\NoBreakInFinallyRule;
use Readalizer\Readalizer\Rules\NoChainMethodCallsRule;
use Readalizer\Readalizer\Rules\NoComplexConditionRule;
use Readalizer\Readalizer\Rules\NoDeepBooleanExpressionRule;
use Readalizer\Readalizer\Rules\NoEchoRule;
use Readalizer\Readalizer\Rules\NoElseAfterReturnRule;
use Readalizer\Readalizer\Rules\NoEmptyCatchRule;
use Readalizer\Readalizer\Rules\NoExitRule;
use Readalizer\Readalizer\Rules\NoImplicitTernaryRule;
use Readalizer\Readalizer\Rules\NoMagicStringRule;
use Readalizer\Readalizer\Rules\NoNestedTernaryRule;
use Readalizer\Readalizer\Rules\NoSwitchFallthroughRule;
use Readalizer\Readalizer\Rules\NoYodaConditionsRule;

return [
    'paths' => ['.'],
    'memory_limit' => '2G',
    'cache' => [
        'enabled' => true,
        'path' => '.readalizer-cache.json',
    ],
    'baseline' => '.readalizer-baseline.json',
    'ignore' => [
        'vendor',
        '.php-cs-fixer.dist.php',
        'phpstan-stubs',
        'rector.php',
    ],
    'ruleset' => [
        new FileStructureRuleset(),
        new TypeSafetyRuleset(),
        new ClassDesignRuleset(),
        new MethodDesignRuleset(),
        new NamingRuleset(),
    ],
    'rules' => [
        // ExpressionRuleset expanded inline (example of explicit rule config)
        new NoNestedTernaryRule(),
        new NoDeepBooleanExpressionRule(maxConditions: 3),
        new NoElseAfterReturnRule(),
        new NoEmptyCatchRule(),
        new NoSwitchFallthroughRule(),
        new NoBreakInFinallyRule(),
        new NoEchoRule(),
        new NoExitRule(),
        new NoYodaConditionsRule(),
        new NoImplicitTernaryRule(),
        new NoComplexConditionRule(),
        new NoChainMethodCallsRule(maxChain: 5),
        new NoAssignmentInConditionRule(),
        new NoMagicStringRule(),
    ],
];
paths — Default paths when CLI paths are omitted.
ignore — Path prefixes or globs to skip.
rules — Rule instances applied directly.
ruleset — Ruleset bundles expanded into rules.
memory_limit — Default memory limit, overridden by --memory-limit.
cache, baseline, max_violations — Optional keys parsed by config; cache/baseline/max_violations are not yet enforced in runtime.
Precedence — CLI paths override paths in config.
Precedence--memory-limit overrides memory_limit.
Precedence--config overrides the default config file location.

Rulesets

The DefaultRuleset merges six focused rulesets. You can enable the default set or compose your own.

File Structure

Strict file layout, namespaces, and line hygiene.

Type Safety

Explicit types, typed properties, and iterable value annotations.

Class Design

Encapsulation, immutability, and class size constraints.

Method Design

Small methods, clean parameters, and readable control flow.

Naming

Predictable naming conventions and disallowed suffixes.

Expressions

Readable boolean logic, conditionals, and error handling.

You can combine rulesets and individual rules in the config file. Rulesets live in src/Rulesets/ and rules in src/Rules/.

Rules

Each rule includes enforcement, rationale, examples, and configuration details. Use the search box to filter by name or description.

File Structure Ruleset

File-level conventions that keep files predictable, declarative, and consistent.

StrictTypesDeclarationRule File Rule Default: None

Every PHP file must include declare(strict_types=1).

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Strict typing makes type errors fail fast and improves static analysis.

Bad Example

<?php

namespace App;

final class User {}

Good Example

<?php

declare(strict_types=1);

namespace App;

final class User {}

Configuration

None.

RequireNamespaceRule File Rule Default: None

Files declaring symbols must live in a namespace.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Namespaces prevent name collisions and make autoloading predictable.

Bad Example

<?php

declare(strict_types=1);

final class User {}

Good Example

<?php

declare(strict_types=1);

namespace App;

final class User {}

Configuration

None.

RequireNamespaceDeclarationFirstRule File Rule Default: None

Namespace declaration must be the first statement after declare.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Keeping namespace first avoids confusion and keeps file structure consistent.

Bad Example

<?php

declare(strict_types=1);

use App\Foo;

namespace App;

Good Example

<?php

declare(strict_types=1);

namespace App;

use App\Foo;

Configuration

None.

SingleNamespacePerFileRule File Rule Default: None

Only one namespace declaration is allowed per file.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Single namespaces keep file intent focused and autoloading clear.

Bad Example

<?php

namespace App;

class A {}

namespace App\Support;

class B {}

Good Example

<?php

namespace App;

class A {}

Configuration

None.

SingleClassPerFileRule File Rule Default: None

Only one class/interface/trait is allowed per file.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

One class per file makes discovery and autoloading predictable.

Bad Example

<?php

namespace App;

class A {}

class B {}

Good Example

<?php

namespace App;

class A {}

Configuration

None.

NoMultipleClassesWithSameNameRule File Rule Default: None

Disallows multiple types with the same name in one file.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Duplicate names in a file create ambiguity and fragile behavior.

Bad Example

<?php

namespace App;

class User {}

class User {}

Good Example

<?php

namespace App;

class User {}

Configuration

None.

NoExecutableCodeInFilesRule File Rule Default: None

Disallows executable top-level code; declarations only.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Top-level execution makes files harder to reason about and test.

Bad Example

<?php

namespace App;

$booted = true;

class User {}

Good Example

<?php

namespace App;

class User {}

Configuration

None.

FileLengthRule File Rule Default: maxLines: 400

Files must not exceed a maximum line count.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Smaller files are easier to review and maintain.

Bad Example

// file length exceeds the configured max
// (example omitted for brevity)

Good Example

// split into smaller, focused files

Configuration

maxLines (default 400 in the default ruleset).

LineLengthRule File Rule Default: maxLength: 120

Lines must not exceed the configured length.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Shorter lines make diffs and reviews easier.

Bad Example

// a single line longer than 120 characters

Good Example

// wrap long lines to stay within 120 characters

Configuration

maxLength (default 120 in the default ruleset).

NoTrailingWhitespaceRule File Rule Default: None

Disallows trailing whitespace on any line.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Trailing whitespace adds noise to diffs and reviews.

Bad Example

<?php

$foo = 'bar'; 

Good Example

<?php

$foo = 'bar';

Configuration

None.

NoTrailingBlankLinesRule File Rule Default: None

Disallows blank lines at the end of a file.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Trailing blank lines create diff noise and inconsistent formatting.

Bad Example

<?php

final class User {}


Good Example

<?php

final class User {}

Configuration

None.

NoMixedLineEndingsRule File Rule Default: None

Disallows mixed CRLF and LF line endings in a file.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Mixed line endings cause noisy diffs and tooling issues.

Bad Example

// file contains both CRLF and LF line endings

Good Example

// file uses a single line ending style

Configuration

None.

NoBOMRule File Rule Default: None

Disallows UTF-8 BOMs in PHP files.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

BOMs can break tooling and introduce hidden characters.

Bad Example

// UTF-8 BOM present at file start

Good Example

// file starts with <?php without a BOM

Configuration

None.

NoPhpCloseTagRule File Rule Default: None

Disallows closing PHP tags in PHP-only files.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Closing tags can introduce accidental output and whitespace bugs.

Bad Example

<?php

final class User {}
?>

Good Example

<?php

final class User {}

Configuration

None.

NoMultipleDeclareStrictTypesRule File Rule Default: None

Disallows multiple strict_types declarations in a file.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Multiple declarations are redundant and confusing.

Bad Example

<?php

declare(strict_types=1);

declare(strict_types=1);

Good Example

<?php

declare(strict_types=1);

Configuration

None.

NoInlineDeclareRule File Rule Default: None

declare statements must appear before any other code.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

declare should appear before other code for clarity.

Bad Example

<?php

$foo = 'bar';

declare(strict_types=1);

Good Example

<?php

declare(strict_types=1);

$foo = 'bar';

Configuration

None.

NoMixedPhpHtmlRule File Rule Default: None

Disallows mixing HTML and PHP in source files.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Mixing PHP and HTML obscures responsibilities in libraries.

Bad Example

<?php

echo '<div>' . $name . '</div>';
?>
<div>extra html</div>

Good Example

<?php

final class Renderer {}

Configuration

None.

RequireFileDocblockRule File Rule Default: None

Files that declare symbols must include a file-level docblock.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

File docblocks make intent explicit and help tooling.

Bad Example

<?php

namespace App;

final class User {}

Good Example

<?php

/**
 * User domain model.
 */

namespace App;

final class User {}

Configuration

None.

NoTodoWithoutTicketRule File Rule Default: None

TODO comments must include a ticket reference.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Ticketed TODOs are trackable and actionable.

Bad Example

// TODO: refactor this

Good Example

// TODO: refactor this (JIRA-123)

Configuration

None.

NoSuppressAllRule File Rule Default: None

Disallows suppression comments that omit rule names.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Blanket suppressions hide real issues.

Bad Example

// @readalizer-suppress
$foo = 'bar';

Good Example

// @readalizer-suppress NoLongMethodsRule
function legacy(): void {}

Configuration

None.

NoGlobalFunctionsRule File Rule Default: None

Disallows functions in the global namespace.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Global functions are harder to discover and test.

Bad Example

<?php

function helper() {}

Good Example

<?php

namespace App;

final class Helper {}

Configuration

None.

NoGlobalConstantsRule File Rule Default: None

Disallows constants in the global namespace.

Applies to: File
Ruleset: File Structure Ruleset

Why This Matters

Global constants pollute the global namespace.

Bad Example

<?php

const VERSION = '1.0.0';

Good Example

<?php

namespace App;

final class Version
{
    public const VALUE = '1.0.0';
}

Configuration

None.

Type Safety Ruleset

Rules that enforce explicit types, avoid mixed, and require precise iterable annotations.

ReturnTypeRequiredRule Node Rule Default: None

All functions and methods must declare a return type.

Applies to: ClassMethod, Function_
Ruleset: Type Safety Ruleset

Why This Matters

Return types make APIs explicit and safer to consume.

Bad Example

function total($items) { return count($items); }

Good Example

function total(array $items): int { return count($items); }

Configuration

None.

NoArrayReturnRule Node Rule Default: None

Disallows returning raw arrays.

Applies to: ClassMethod, Function_
Ruleset: Type Safety Ruleset

Why This Matters

Typed objects are clearer and safer than raw arrays.

Bad Example

function stats(): array { return ['count' => 10]; }

Good Example

function stats(): Stats { return new Stats(count: 10); }

Configuration

None.

ParameterTypeRequiredRule Node Rule Default: None

Every parameter must declare an explicit type.

Applies to: ClassMethod, Function_
Ruleset: Type Safety Ruleset

Why This Matters

Parameter types prevent ambiguity and improve tooling.

Bad Example

function total($items): int { return count($items); }

Good Example

function total(array $items): int { return count($items); }

Configuration

None.

NoMixedTypeRule Node Rule Default: None

Disallows mixed types on parameters, returns, and properties.

Applies to: ClassMethod, Function_, Property
Ruleset: Type Safety Ruleset

Why This Matters

Mixed erodes type safety and makes APIs vague.

Bad Example

function parse(mixed $value): mixed { return $value; }

Good Example

function parse(string $value): ParsedValue { return ParsedValue::from($value); }

Configuration

None.

NoUntypedPropertyRule Node Rule Default: None

All properties must declare a type.

Applies to: Property
Ruleset: Type Safety Ruleset

Why This Matters

Typed properties make object state explicit.

Bad Example

final class User { public $id; }

Good Example

final class User { public int $id; }

Configuration

None.

NoMixedDocblockRule File Rule Default: None

Disallows @var/@param/@return annotations with mixed.

Applies to: File
Ruleset: Type Safety Ruleset

Why This Matters

Docblocks should be as specific as code.

Bad Example

/** @var mixed */
private $value;

Good Example

/** @var string */
private string $value;

Configuration

None.

RequireIterableValueTypeRule Node Rule Default: None

Iterable types must include value type annotations.

Applies to: ClassMethod, Function_, Property
Ruleset: Type Safety Ruleset

Why This Matters

Iterable value types are critical for static analysis.

Bad Example

/** @return array */
function ids(): array { return [1, 2]; }

Good Example

/** @return array<int> */
function ids(): array { return [1, 2]; }

Configuration

None.

NoNullableMixedRule Node Rule Default: None

Disallows ?mixed or mixed|null.

Applies to: ClassMethod, Function_, Property
Ruleset: Type Safety Ruleset

Why This Matters

Nullable mixed provides no actionable type information.

Bad Example

function parse(mixed|null $value): void {}

Good Example

function parse(?string $value): void {}

Configuration

None.

NoUnionWithMixedRule Node Rule Default: None

Disallows union types that include mixed.

Applies to: ClassMethod, Function_, Property
Ruleset: Type Safety Ruleset

Why This Matters

Mixed in unions cancels out the rest of the type.

Bad Example

function parse(string|mixed $value): void {}

Good Example

function parse(string|int $value): void {}

Configuration

None.

PreferNullableTypeSyntaxRule Node Rule Default: None

Prefer ?Type over Type|null.

Applies to: ClassMethod, Function_, Property
Ruleset: Type Safety Ruleset

Why This Matters

Nullable syntax is the standard, most readable form.

Bad Example

function name(): string|null { return null; }

Good Example

function name(): ?string { return null; }

Configuration

None.

NoVariadicScalarRule Node Rule Default: None

Disallows variadic scalar parameters.

Applies to: ClassMethod, Function_
Ruleset: Type Safety Ruleset

Why This Matters

Variadic scalars are harder to validate and evolve.

Bad Example

function add(int ...$ids): void {}

Good Example

function add(array $ids): void {}

Configuration

None.

RequireVoidReturnRule Node Rule Default: None

If there is no return value, declare void.

Applies to: ClassMethod, Function_
Ruleset: Type Safety Ruleset

Why This Matters

Void clarifies that a function has no return value.

Bad Example

function log(string $msg) { echo $msg; }

Good Example

function log(string $msg): void { echo $msg; }

Configuration

None.

NoBoolStringComparisonRule Node Rule Default: None

Disallows comparisons between booleans and strings/numbers.

Applies to: Equal, NotEqual, Identical, NotIdentical
Ruleset: Type Safety Ruleset

Why This Matters

Mixed comparisons are error-prone and unclear.

Bad Example

if (true == '1') { /* ... */ }

Good Example

if ($flag === true) { /* ... */ }

Configuration

None.

NoImplicitBoolReturnRule Node Rule Default: None

Boolean-returning methods must return a value (no bare return).

Applies to: ClassMethod, Function_
Ruleset: Type Safety Ruleset

Why This Matters

Bare returns in boolean methods are ambiguous and hide intent.

Bad Example

function isReady(): bool { return; }

Good Example

function isReady(): bool { return true; }

Configuration

None.

NoImplicitStringCastRule Node Rule Default: None

Disallows explicit string casts.

Applies to: String_
Ruleset: Type Safety Ruleset

Why This Matters

Implicit casts hide intent and formatting rules.

Bad Example

return (string) $value;

Good Example

return $formatter->format($value);

Configuration

None.

Class Design Ruleset

Rules that enforce focused, immutable, and explicit class design.

ClassNamePascalCaseRule Node Rule Default: None

Class, interface, and trait names must be PascalCase.

Applies to: Class_, Interface_, Trait_
Ruleset: Class Design Ruleset

Why This Matters

PascalCase is the expected class naming convention in PHP.

Bad Example

final class user_profile {}

Good Example

final class UserProfile {}

Configuration

None.

FinalClassRule Node Rule Default: None

Concrete classes must be declared final.

Applies to: Class_
Ruleset: Class Design Ruleset

Why This Matters

Final classes reduce accidental inheritance and encourage composition.

Bad Example

class UserService {}

Good Example

final class UserService {}

Configuration

None.

NoPublicPropertiesRule Node Rule Default: None

Disallows public properties.

Applies to: Property
Ruleset: Class Design Ruleset

Why This Matters

Public properties make class contracts implicit and mutable.

Bad Example

final class User { public string $name; }

Good Example

final class User { private string $name; }

Configuration

None.

NoMutablePublicPropertiesRule Node Rule Default: None

Disallows public mutable properties.

Applies to: Property
Ruleset: Class Design Ruleset

Why This Matters

Mutable public state makes behavior hard to reason about.

Bad Example

final class User { public string $name; }

Good Example

final class User { public readonly string $name; }

Configuration

None.

NoStaticPropertyRule Node Rule Default: None

Disallows static properties.

Applies to: Property
Ruleset: Class Design Ruleset

Why This Matters

Static state couples code and complicates testing.

Bad Example

final class Cache { public static array $items = []; }

Good Example

final class Cache { private array $items = []; }

Configuration

None.

NoProtectedPropertiesRule Node Rule Default: None

Disallows protected properties.

Applies to: Property
Ruleset: Class Design Ruleset

Why This Matters

Protected properties blur class boundaries.

Bad Example

class Base { protected string $name; }

Good Example

final class User { private string $name; }

Configuration

None.

NoPublicConstructorRule Node Rule Default: None

Disallows public constructors (except internal rules).

Applies to: ClassMethod
Ruleset: Class Design Ruleset

Why This Matters

Named constructors make creation intent explicit.

Bad Example

final class User { public function __construct() {} }

Good Example

final class User { private function __construct() {} }

Configuration

None.

PreferPropertyPromotionRule Node Rule Default: None

Prefer constructor property promotion for simple assignments.

Applies to: ClassMethod
Ruleset: Class Design Ruleset

Why This Matters

Property promotion reduces boilerplate and improves readability.

Bad Example

final class User { private string $name; public function __construct(string $name) { $this->name = $name; } }

Good Example

final class User { public function __construct(private string $name) {} }

Configuration

None.

NoStaticMethodsRule Node Rule Default: None

Disallows static methods except public factories (from/create/make).

Applies to: ClassMethod
Ruleset: Class Design Ruleset

Why This Matters

Static methods impede dependency injection and testing.

Bad Example

final class User { public static function save(): void {} }

Good Example

final class User { public function save(): void {} }

Configuration

None.

PropertyNameCamelCaseRule Node Rule Default: None

Property names must be camelCase.

Applies to: Property
Ruleset: Class Design Ruleset

Why This Matters

camelCase is the expected property naming convention.

Bad Example

final class User { private string $user_name; }

Good Example

final class User { private string $userName; }

Configuration

None.

ConstantUpperCaseRule Node Rule Default: None

Constants must be uppercase with underscores.

Applies to: ClassConst, Const_
Ruleset: Class Design Ruleset

Why This Matters

Uppercase constants are standard and obvious.

Bad Example

final class User { public const max_count = 10; }

Good Example

final class User { public const MAX_COUNT = 10; }

Configuration

None.

ExceptionSuffixRule Node Rule Default: None

Exception classes must end with Exception.

Applies to: Class_
Ruleset: Class Design Ruleset

Why This Matters

Exception suffix makes intent clear in stack traces.

Bad Example

final class MissingConfig extends \Exception {}

Good Example

final class MissingConfigException extends \Exception {}

Configuration

None.

MaxClassLengthRule Node Rule Default: maxLines=170

Classes must not exceed a maximum line count.

Applies to: Class_
Ruleset: Class Design Ruleset

Why This Matters

Smaller classes are easier to maintain.

Bad Example

// class exceeds max line count

Good Example

// split class into smaller collaborators

Configuration

maxLines (default 170).

NoGodClassRule Node Rule Default: maxMethods: 10, maxProperties: 10

Limits number of methods and properties on a class.

Applies to: Class_
Ruleset: Class Design Ruleset

Why This Matters

God classes are difficult to test and evolve.

Bad Example

final class OrderService { /* 20 methods and 12 properties */ }

Good Example

final class OrderService { /* focused methods and properties */ }

Configuration

maxMethods (default 10), maxProperties (default 10).

NoEmptyClassRule Node Rule Default: None

Disallows empty classes.

Applies to: Class_
Ruleset: Class Design Ruleset

Why This Matters

Empty classes add indirection without value.

Bad Example

final class Marker {}

Good Example

final class Marker { public function describe(): string { return '...'; } }

Configuration

None.

NoEmptyTraitRule Node Rule Default: None

Disallows empty traits.

Applies to: Trait_
Ruleset: Class Design Ruleset

Why This Matters

Empty traits add indirection without value.

Bad Example

trait HasSomething {}

Good Example

trait HasSomething { protected function something(): void {} }

Configuration

None.

SingleResponsibilityClassRule Node Rule Default: maxPublicMethods: 8

Limits number of public methods on a class.

Applies to: Class_
Ruleset: Class Design Ruleset

Why This Matters

Limiting public methods keeps classes focused.

Bad Example

final class UserService { /* 12 public methods */ }

Good Example

final class UserService { /* <= 8 public methods */ }

Configuration

maxPublicMethods (default 8).

RequireImmutableValueObjectRule Node Rule Default: None

Classes that look like value objects must be readonly.

Applies to: Class_
Ruleset: Class Design Ruleset

Why This Matters

Value objects should be immutable to be safe and predictable.

Bad Example

final class Money { public int $amount; }

Good Example

final readonly class Money { public function __construct(public int $amount) {} }

Configuration

None.

NoInheritanceRule Node Rule Default: None

Disallows class inheritance.

Applies to: Class_
Ruleset: Class Design Ruleset

Why This Matters

Composition is usually clearer and more flexible than inheritance.

Bad Example

class BaseService {}
class UserService extends BaseService {}

Good Example

final class UserService { private BaseService $base; }

Configuration

None.

NoInterfacesOnFinalClassRule Node Rule Default: maxInterfaces: 1

Final classes may implement only a limited number of interfaces.

Applies to: Class_
Ruleset: Class Design Ruleset

Why This Matters

Too many interfaces on a final class can signal overreach.

Bad Example

final class UserService implements A, B {}

Good Example

final class UserService implements A {}

Configuration

maxInterfaces (default 1).

Method Design Ruleset

Rules that keep methods small, explicit, and easy to reason about.

FunctionVerbNameRule Node Rule Default: None

Function and method names must start with an action verb.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Verb names communicate actions clearly.

Bad Example

function user() {}

Good Example

function loadUser() {}

Configuration

None.

BooleanMethodPrefixRule Node Rule Default: None

Bool methods must start with allowed predicate prefixes.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Predicate prefixes make boolean methods obvious.

Bad Example

function ready(): bool { return true; }

Good Example

function isReady(): bool { return true; }

Configuration

None.

GetterMustReturnValueRule Node Rule Default: None

Getter methods must return a value.

Applies to: ClassMethod
Ruleset: Method Design Ruleset

Why This Matters

Getters that do not return values are misleading.

Bad Example

function getName(): void { $this->name = 'x'; }

Good Example

function getName(): string { return $this->name; }

Configuration

None.

ParameterNameNotSingleLetterRule Node Rule Default: None

Parameters must not be single-letter names.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Descriptive names improve readability.

Bad Example

function add(int $x): int { return $x + 1; }

Good Example

function add(int $count): int { return $count + 1; }

Configuration

None.

NoHungarianNotationRule Node Rule Default: None

Disallows Hungarian notation in method, function, and property names.

Applies to: ClassMethod, Function_, Property
Ruleset: Method Design Ruleset

Why This Matters

Hungarian notation adds noise and ages poorly.

Bad Example

function save(string $str_name): void {}

Good Example

function save(string $name): void {}

Configuration

None.

NoLongMethodsRule Node Rule Default: maxLines: 30

Methods must not exceed a maximum line count.

Applies to: ClassMethod
Ruleset: Method Design Ruleset

Why This Matters

Shorter methods are easier to test and understand.

Bad Example

// method body exceeds 30 lines

Good Example

// method broken into smaller helpers

Configuration

maxLines (default 30).

NoEmptyMethodRule Node Rule Default: None

Disallows empty methods.

Applies to: ClassMethod
Ruleset: Method Design Ruleset

Why This Matters

Empty methods hide missing behavior.

Bad Example

function handle(): void {}

Good Example

function handle(): void { $this->process(); }

Configuration

None.

NoLongParameterListRule Node Rule Default: maxParams: 4

Methods/functions must not exceed a parameter limit.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Long parameter lists are error-prone and hard to read.

Bad Example

function create(string $a, string $b, string $c, string $d, string $e): void {}

Good Example

function create(CreateUserInput $input): void {}

Configuration

maxParams (default 4).

NoConstructorWorkRule Node Rule Default: maxLines: 10

Constructor bodies must not exceed a maximum line count.

Applies to: ClassMethod
Ruleset: Method Design Ruleset

Why This Matters

Constructors should be lightweight and predictable.

Bad Example

public function __construct() { $this->loadFromDb(); }

Good Example

public function __construct(private Repository $repo) {}

Configuration

maxLines (default 10).

MaxNestingDepthRule Node Rule Default: maxDepth: 3

Limits the maximum nesting depth of control flow.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Deep nesting is hard to read and refactor.

Bad Example

if ($a) { if ($b) { if ($c) { doWork(); } } }

Good Example

if (!$a || !$b || !$c) { return; }

doWork();

Configuration

maxDepth (default 3).

NoNestedLoopsRule Node Rule Default: maxDepth: 2

Limits loop nesting depth.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Nested loops hide complexity and make performance unclear.

Bad Example

foreach ($a as $x) { foreach ($b as $y) { /* ... */ } }

Good Example

foreach ($a as $x) { process($x); }

Configuration

maxDepth (default 2).

MaxMethodStatementsRule Node Rule Default: maxStatements: 12

Limits the number of statements in a method.

Applies to: ClassMethod
Ruleset: Method Design Ruleset

Why This Matters

Many statements usually indicate multiple responsibilities.

Bad Example

// method contains too many statements

Good Example

// extract blocks into smaller methods

Configuration

maxStatements (default 12).

NoBooleanParameterRule Node Rule Default: None

Disallows boolean parameters or boolean defaults.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Boolean flags make APIs ambiguous.

Bad Example

function save(bool $force): void {}

Good Example

function save(): void {}
function forceSave(): void {}

Configuration

None.

NoOptionalParameterAfterRequiredRule Node Rule Default: None

Disallows required parameters after optional parameters.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Required parameters after optional ones are confusing.

Bad Example

function add(string $name = 'x', int $count): void {}

Good Example

function add(string $name, int $count = 0): void {}

Configuration

None.

NoDefaultArrayParameterRule Node Rule Default: None

Disallows default array parameters.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Default arrays can hide missing arguments.

Bad Example

function add(array $items = []): void {}

Good Example

function add(?array $items = null): void {}

Configuration

None.

NoReferenceParameterRule Node Rule Default: None

Disallows by-reference parameters.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Reference parameters obscure data flow.

Bad Example

function add(int &$count): void { $count++; }

Good Example

function add(int $count): int { return $count + 1; }

Configuration

None.

NoReturnNullRule Node Rule Default: None

Disallows returning null unless return type allows it.

Applies to: ClassMethod, Function_
Ruleset: Method Design Ruleset

Why This Matters

Returning null without nullable types is misleading.

Bad Example

function find(): User { return null; }

Good Example

function find(): ?User { return null; }

Configuration

None.

NoThrowGenericExceptionRule Node Rule Default: None

Disallows throwing generic Exception/Throwable.

Applies to: Throw_
Ruleset: Method Design Ruleset

Why This Matters

Specific exceptions are easier to handle and reason about.

Bad Example

throw new \Exception('fail');

Good Example

throw new DomainException('fail');

Configuration

None.

NoCatchGenericExceptionRule Node Rule Default: None

Disallows catching generic Exception/Throwable.

Applies to: Catch_
Ruleset: Method Design Ruleset

Why This Matters

Catching generic exceptions hides the real error shape.

Bad Example

catch (\Exception $e) { /* ... */ }

Good Example

catch (DomainException $e) { /* ... */ }

Configuration

None.

NoNestedTryRule Node Rule Default: None

Disallows nested try/catch blocks.

Applies to: TryCatch
Ruleset: Method Design Ruleset

Why This Matters

Nested try/catch blocks obscure error handling.

Bad Example

try { try { doWork(); } catch (\Throwable $e) {} } catch (\Throwable $e) {}

Good Example

try { doWork(); } catch (\Throwable $e) {}

Configuration

None.

RequireNamedConstructorRule Node Rule Default: None

Classes with public constructors must include a named constructor.

Applies to: Class_
Ruleset: Method Design Ruleset

Why This Matters

Named constructors clarify intent and validation.

Bad Example

final class User { public function __construct() {} }

Good Example

final class User { private function __construct() {} public static function fromEmail(string $email): self { return new self(); } }

Configuration

None.

Naming Ruleset

Rules that keep names clear, consistent, and intention-revealing.

InterfaceNamingRule Node Rule Default: None

Interfaces must use the Contract suffix.

Applies to: Interface_
Ruleset: Naming Ruleset

Why This Matters

Contract suffix makes interfaces explicit.

Bad Example

interface Renderer {}

Good Example

interface RendererContract {}

Configuration

None.

TraitNamingRule Node Rule Default: None

Traits must use the Has prefix.

Applies to: Trait_
Ruleset: Naming Ruleset

Why This Matters

Has prefix signals capabilities clearly.

Bad Example

trait Timestamped {}

Good Example

trait HasTimestamps {}

Configuration

None.

NoAbbreviationRule Node Rule Default: None

Disallows common abbreviations in names.

Applies to: Class_, Property, ClassMethod, Function_
Ruleset: Naming Ruleset

Why This Matters

Abbreviations reduce clarity and hurt searchability.

Bad Example

final class UserSvc {}

Good Example

final class UserService {}

Configuration

None.

NoNegativeBooleanNameRule Node Rule Default: None

Disallows negative boolean method names.

Applies to: ClassMethod, Function_
Ruleset: Naming Ruleset

Why This Matters

Negative boolean names are hard to reason about.

Bad Example

function isNotReady(): bool { return true; }

Good Example

function isReady(): bool { return true; }

Configuration

None.

NoPrefixHungarianRule Node Rule Default: None

Disallows Hungarian-style prefixes.

Applies to: ClassMethod, Function_, Property
Ruleset: Naming Ruleset

Why This Matters

Hungarian prefixes are noisy and redundant.

Bad Example

function save(string $str_name): void {}

Good Example

function save(string $name): void {}

Configuration

None.

NoSuffixImplRule Node Rule Default: None

Disallows Impl suffix on class names.

Applies to: Class_
Ruleset: Naming Ruleset

Why This Matters

Impl suffix adds noise and hides the real intent.

Bad Example

final class CacheImpl {}

Good Example

final class Cache {}

Configuration

None.

NoManagerSuffixRule Node Rule Default: None

Disallows Manager/Helper/Util suffixes.

Applies to: Class_
Ruleset: Naming Ruleset

Why This Matters

Manager/Helper/Util are vague and unhelpful.

Bad Example

final class UserManager {}

Good Example

final class UserService {}

Configuration

None.

NoPluralClassNameRule Node Rule Default: None

Disallows plural class names.

Applies to: Class_
Ruleset: Naming Ruleset

Why This Matters

Singular class names read more clearly.

Bad Example

final class Users {}

Good Example

final class User {}

Configuration

None.

Expression Ruleset

Rules that simplify control flow and keep expressions readable.

NoNestedTernaryRule Node Rule Default: None

Disallows nested ternary expressions.

Applies to: Ternary
Ruleset: Expression Ruleset

Why This Matters

Nested ternaries are hard to parse and error-prone.

Bad Example

$value = $a ? ($b ? 1 : 2) : 3;

Good Example

if ($a) { $value = $b ? 1 : 2; } else { $value = 3; }

Configuration

None.

NoDeepBooleanExpressionRule Node Rule Default: maxConditions: 3

Limits the number of boolean conditions in a single expression.

Applies to: If_, While_, For_
Ruleset: Expression Ruleset

Why This Matters

Deep boolean expressions are hard to read.

Bad Example

if ($a && $b && $c && $d) { doWork(); }

Good Example

if (!$a || !$b || !$c || !$d) { return; }

doWork();

Configuration

maxConditions (default 3).

NoElseAfterReturnRule Node Rule Default: None

Disallows else blocks following a return/throw.

Applies to: If_
Ruleset: Expression Ruleset

Why This Matters

Early returns reduce nesting.

Bad Example

if ($ok) { return 1; } else { return 0; }

Good Example

if ($ok) { return 1; }
return 0;

Configuration

None.

NoEmptyCatchRule Node Rule Default: None

Disallows empty catch blocks.

Applies to: Catch_
Ruleset: Expression Ruleset

Why This Matters

Empty catches hide errors and make failures silent.

Bad Example

try { doWork(); } catch (\Throwable $e) {}

Good Example

try { doWork(); } catch (\Throwable $e) { report($e); }

Configuration

None.

NoSwitchFallthroughRule Node Rule Default: None

Switch cases must end with break/return/throw/continue.

Applies to: Switch_
Ruleset: Expression Ruleset

Why This Matters

Fallthrough is often accidental and hard to spot.

Bad Example

switch ($x) { case 1: doWork(); case 2: doMore(); }

Good Example

switch ($x) { case 1: doWork(); break; case 2: doMore(); break; }

Configuration

None.

NoBreakInFinallyRule Node Rule Default: None

Disallows break/continue/return in finally blocks.

Applies to: TryCatch
Ruleset: Expression Ruleset

Why This Matters

Control flow exits in finally blocks are confusing.

Bad Example

try { doWork(); } finally { break; }

Good Example

try { doWork(); } finally { cleanup(); }

Configuration

None.

NoEchoRule Node Rule Default: None

Disallows echo statements.

Applies to: Echo_
Ruleset: Expression Ruleset

Why This Matters

Libraries should not write directly to stdout.

Bad Example

echo 'done';

Good Example

$logger->info('done');

Configuration

None.

NoExitRule Node Rule Default: None

Disallows exit/die calls.

Applies to: Exit_
Ruleset: Expression Ruleset

Why This Matters

Exiting bypasses calling code and tests.

Bad Example

exit(1);

Good Example

throw new RuntimeException('failed');

Configuration

None.

NoYodaConditionsRule Node Rule Default: None

Disallows Yoda conditions with literals on the left.

Applies to: Equal, NotEqual, Identical, NotIdentical, Smaller, SmallerOrEqual, Greater, GreaterOrEqual
Ruleset: Expression Ruleset

Why This Matters

Yoda conditions are harder to read.

Bad Example

if (10 === $count) { doWork(); }

Good Example

if ($count === 10) { doWork(); }

Configuration

None.

NoImplicitTernaryRule Node Rule Default: None

Disallows the shorthand ternary operator.

Applies to: Ternary
Ruleset: Expression Ruleset

Why This Matters

Implicit ternaries are ambiguous in intent.

Bad Example

$name = $user ?: 'guest';

Good Example

$name = $user !== null ? $user : 'guest';

Configuration

None.

NoComplexConditionRule Node Rule Default: None

Disallows mixing && and || in a single condition.

Applies to: If_, While_, For_
Ruleset: Expression Ruleset

Why This Matters

Mixed boolean operators are easy to misread.

Bad Example

if ($a && $b || $c) { doWork(); }

Good Example

$ready = $a && $b;
if ($ready || $c) { doWork(); }

Configuration

None.

NoChainMethodCallsRule Node Rule Default: maxChain: 5

Limits method call chain depth.

Applies to: MethodCall
Ruleset: Expression Ruleset

Why This Matters

Deep call chains are hard to debug.

Bad Example

$result = $a->b()->c()->d()->e()->f();

Good Example

$builder = $a->b()->c();
$result = $builder->d();

Configuration

maxChain (default 5).

NoAssignmentInConditionRule Node Rule Default: None

Disallows assignments in if/while/for conditions.

Applies to: If_, While_, For_
Ruleset: Expression Ruleset

Why This Matters

Assignments inside conditions are easy to miss.

Bad Example

if ($count = $repo->count()) { doWork(); }

Good Example

$count = $repo->count();
if ($count) { doWork(); }

Configuration

None.

NoMagicStringRule Node Rule Default: None

Disallows string literals in comparisons and switch/match arms.

Applies to: Equal, NotEqual, Identical, NotIdentical, Case_, MatchArm
Ruleset: Expression Ruleset

Why This Matters

Constants communicate intent and reduce typos.

Bad Example

if ($status === 'paid') { /* ... */ }

Good Example

if ($status === Status::PAID) { /* ... */ }

Configuration

None.

Suppression

Use the #[Suppress] attribute to suppress rules at class, method, or property scope. Inline suppression uses // @readalizer-suppress with either short names or fully qualified rule classes.

use Readalizer\Readalizer\Attributes\Suppress;
use Readalizer\Readalizer\Rules\NoLongMethodsRule;

#[Suppress(NoLongMethodsRule::class)]
final class LegacyService
{
    // ...
}

// @readalizer-suppress NoLongMethodsRule
function legacy(): void
{
    // ...
}

// @readalizer-suppress
// suppress all rules for the current line and following lines

Parallel Execution

Set --jobs above 1 to enable worker processes. Readalizer splits files into chunks, runs workers in --_worker mode, and merges violation output in the parent process.

Security — Worker mode requires READALIZER_INTERNAL=1 and a matching token.
Resource caps — CPU usage is capped at 50% of logical cores, and memory is split across workers.
Progress — Workers write newline ticks that power the progress bar.

Architecture

High-level flow:

Application reads CLI input and dispatches commands.
AnalyseCommand builds an AnalyseCommandContext with config, rules, and paths.
Analyser parses files to AST nodes and applies rule collections.
ParallelRunner spawns workers when --jobs is set.
Formatter renders output (TextFormatter today).

Troubleshooting

No progress output — Ensure --no-progress is not set and your terminal supports ANSI.
Workers hang — Increase --worker-timeout, reduce --jobs, and check system limits.
Memory exhaustion — Reduce --jobs, raise --memory-limit, or exclude large directories.
JSON output--format=json is parsed but not wired to JsonFormatter yet.

Development

Requirements: PHP 8.1+ and Composer. Install dependencies and create a local config file:

composer install
vendor/bin/readalizer --init
vendor/bin/readalizer src

There is no automated test suite. Validate changes by running Readalizer against this repo and a large external codebase.