PHPackages                             tivins/php-solid - 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. [Parsing &amp; Serialization](/categories/parsing)
4. /
5. tivins/php-solid

ActiveLibrary[Parsing &amp; Serialization](/categories/parsing)

tivins/php-solid
================

PHP Solid Principles checker

0.20.4(4mo ago)0566MITPHPPHP ^8.2CI passing

Since Feb 13Pushed 4mo agoCompare

[ Source](https://github.com/tivins/php-solid)[ Packagist](https://packagist.org/packages/tivins/php-solid)[ RSS](/packages/tivins-php-solid/feed)WikiDiscussions main Synced today

READMEChangelog (8)Dependencies (3)Versions (9)Used By (0)

php-solid - SOLID principles checker
====================================

[](#php-solid---solid-principles-checker)

A PHP tool that checks **SOLID** principles in your codebase. It detects **Liskov Substitution Principle (LSP)** and **Interface Segregation Principle (ISP)** violations.

[![CI](https://github.com/tivins/php-solid/actions/workflows/ci.yml/badge.svg)](https://github.com/tivins/php-solid/actions/workflows/ci.yml)

---

Principles covered
------------------

[](#principles-covered)

- **LSP (Liskov Substitution Principle)** — Exception contracts, return type covariance, and parameter type contravariance between classes and their contracts (interfaces and parent classes).
- **ISP (Interface Segregation Principle)** — Dead or empty methods, "not implemented" stubs, and fat interfaces (configurable threshold).

---

LSP — What it checks
--------------------

[](#lsp--what-it-checks)

A subclass or implementation must not weaken the contract of its parent or interface. The checker verifies:

- A method must not **declare** (in docblocks) or **throw** (in code) exception types that are not allowed by the contract (interface or parent class).
- If the contract says nothing about exceptions, the implementation must not throw (or declare) any.
- If the contract documents `@throws SomeException`, the implementation may throw that type or any **subclass** (e.g. contract `@throws RuntimeException` allows throwing `UnexpectedValueException`).
- A method return type must be **covariant** with the contract return type (same type or more specific subtype).
- A method parameter type must be **contravariant** with the contract parameter type (same type or wider supertype). Narrowing a parameter type strengthens the precondition and is a violation.

Violations are reported as:

1. **Docblock violations** — `@throws` in the implementation that are not in the contract.
2. **Code violations** — actual `throw` statements (detected via AST) for exception types not allowed by the contract.

### LSP example

[](#lsp-example)

```
interface MyInterface1
{
    /**
     * This method does not mention throwing an exception. Subclasses must not throw any exceptions.
     */
    public function doSomething(): void;
}

/**
 * This class violates the Liskov Substitution Principle.
 */
class MyClass1 implements MyInterface1
{
    /**
     * This method throws an exception, which violates the Liskov Substitution Principle.
     */
    public function doSomething(): void
    {
        throw new RuntimeException("exception is thrown");
    }
}
```

### LSP features

[](#lsp-features)

- **Docblock analysis** — parses `@throws` from PHPDoc (supports piped types, FQCN, descriptions).
- **AST analysis** — uses [nikic/php-parser](https://github.com/nikic/PHP-Parser) to detect real `throw` statements:
    - Direct throws, conditional throws, re-throws in catch.
    - **Transitive throws** — follows `$this->method()` calls within the same class.
    - **Cross-class static/instance calls** — follows `ClassName::method()` and `(new ClassName())->method()`.
    - **Dynamic method calls on variables** — follows `$variable->method()` when the variable type is known (parameter type hints, local assignments). Union types on parameters are supported.
- **Contract comparison** — checks against all implemented interfaces and the parent class.
- **Return type covariance** and **parameter type contravariance** validation.
- **Cached parsing** — each file is parsed once; results are reused for multiple methods.

---

ISP — What it checks
--------------------

[](#isp--what-it-checks)

Clients should not be forced to depend on methods they do not use. The checker detects:

- **Dead or empty methods** — methods with an empty body (or comments only), suggesting the interface is too broad for this class.
- **"Not implemented" stubs** — methods whose body is a single `throw new \BadMethodCallException(...)`, the canonical PHP way to signal an unsupported operation.
- **Return-null/void stubs** — methods that only `return;` or `return null;`, another sign of a forced contract.
- **Fat interfaces** — interfaces with more methods than a configurable threshold (default: 5). Reported once per interface.

### ISP example

[](#isp-example)

```
interface WorkerInterface
{
    public function work(): void;
    public function eat(): void;
    public function sleep(): void;
}

// Robot doesn't need to eat or sleep → empty methods = ISP violation
class RobotWorker implements WorkerInterface
{
    public function work(): void { echo "Working...\n"; }
    public function eat(): void { /* empty — ISP violation */ }
    public function sleep(): void { /* empty — ISP violation */ }
}
```

### ISP features

[](#isp-features)

- **AST-based body analysis** — uses [nikic/php-parser](https://github.com/nikic/PHP-Parser) to inspect method bodies. Comment-only methods are treated as empty.
- **Configurable threshold** — set the fat interface method threshold with `--isp-threshold ` (default: 5).
- **Strategy pattern** — pluggable rule checkers via `IspRuleCheckerInterface`, same architecture as LSP.

---

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

[](#requirements)

- PHP 8.2+
- Composer

Installation
------------

[](#installation)

```
composer require tivins/php-solid
```

Usage
-----

[](#usage)

You can run the checker in two ways: by passing a directory, or by using a configuration file.

### Scan a directory

[](#scan-a-directory)

Pass a directory path as the first argument. The path is relative to the current working directory. The checker builds a config with that directory and recursively finds all PHP classes to check:

```
vendor/bin/php-solid src/
```

The classes (and their contracts — interfaces, parent classes) must be loadable. If a `vendor/autoload.php` is found in or near the target directory, it is included automatically.

### Configuration file

[](#configuration-file)

Use `--config ` to load a PHP file that **returns** a `Tivins\Solid\Config` instance. The config defines which directories and files to scan, optional exclusions, and optional ISP threshold.

```
vendor/bin/php-solid --config config.php
```

**Config file:** Copy the bundled example to your project and adapt paths:

- **Example file:** `config-example.php` (in the package root after install, or in this repo).
- **Config class:** `Tivins\Solid\Config`.

Example (e.g. copy `config-example.php` to `config.php`):

```
