Skip to content
lphellerPublic

About

Finds circular dependencies between the modules of a PHP codebase and tells you which file and line closes each cycle

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

phpcycles

phpcycles finds places where the modules of your PHP project depend on each other in a circle, and shows you the file and line where each circle closes.

$ vendor/bin/phpcycles
4 modules, 4 files from ./composer.json (psr-4 autoload)

✗ 1 cycle in 1 tangle (3 of 4 modules)

Cut first
  Orders -> Shipping   on 1 of 1 cycle · 1 reference in 1 file
    src/Orders/Order.php:8  Parcel
  Shipping -> Billing  on 1 of 1 cycle · 1 reference in 1 file
    src/Shipping/Parcel.php:8  Invoice
  Billing -> Orders    on 1 of 1 cycle · 2 references in 1 file
    src/Billing/Invoice.php:8  Order

Shortest cycles
  1. Billing -> Orders -> Shipping -> Billing
     Billing -> Orders    src/Billing/Invoice.php:8
     Orders -> Shipping   src/Orders/Order.php:8
     Shipping -> Billing  src/Shipping/Parcel.php:8

Every reference: phpcycles deps Orders --why (with the same -c/-d/-x options)

Here Billing uses Orders, Orders uses Shipping, and Shipping uses Billing again. Modules caught in such a loop form a tangle. To break it, you only have to remove one of these dependencies. "Cut first" suggests which one: the one that breaks the most cycles and touches the fewest files.

Install

composer require --dev heller/phpcycles

Usage

vendor/bin/phpcycles                          # checks the project and fails if it finds a cycle
vendor/bin/phpcycles -x Tests/                # leaves out test code
vendor/bin/phpcycles -f json                  # JSON output, or -f github for GitHub annotations
vendor/bin/phpcycles deps Billing --why       # everything Billing uses, with file and line

vendor/bin/phpcycles --help shows all commands and options.

Configuration

Without any config, phpcycles takes the modules from composer.json: every folder directly below a psr-4 path becomes a module, like app/Http or app/Models. If you want to decide yourself, add a phpcycles.php next to composer.json. Paths in it are relative to that file.

<?php

return [
    'exclude' => ['Tests/', 'Fixtures/'],
    'detectors' => [
        ['namespace' => 'App', 'path' => 'src'],   // src/Billing -> App\Billing, ...
    ],
    'modules' => [
        ['name' => 'Kernel', 'namespace' => 'App\Kernel', 'path' => 'src/Kernel.php'],
    ],
];

Baseline

Already have cycles you can't fix right now? Record them once, and from then on only new ones make the check fail.

vendor/bin/phpcycles --generate-baseline phpcycles-baseline.txt
vendor/bin/phpcycles --baseline phpcycles-baseline.txt

What counts as a dependency

A module is a namespace. phpcycles reads every PHP file in the module folders, and each file belongs to the module whose namespace it declares, even if it sits in another module's folder.

Every class, function or constant your code uses counts, for example in new, extends or a type hint. A use statement alone doesn't count, only the code that actually uses the imported name. Comments, docblocks and strings are ignored, and so are files in vendor/ or listed in .gitignore. Names that only exist at runtime, like new $class, can't be seen.

Related tools

phpcycles was inspired by Koriit/PHPDeps and can read its phpdeps.xml. If you want to enforce rules between layers, like "Domain must not use Http", have a look at deptrac or mago guard. phpcycles needs no rules, it only looks for cycles.

License

MIT

About

Finds circular dependencies between the modules of a PHP codebase and tells you which file and line closes each cycle

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages