Code Monkey home page Code Monkey logo

modulite-phpstan's Introduction

Modulite plugin for PHPStan

Modulite is a conception that brings modules into the PHP language. PHP does not have native modules (internal classes, private namespaces, explicit exports), and Modulite tries to eliminate this drawback.

Actually, all "modularity" is represented as .modulite.yaml files, which declare exports and requires. There is a PHPStorm plugin that deeply integrates into an IDE, visualizes yaml config and provides actions to modify it.

This PHPStan plugin reads .modulite.yaml files and fires errors if your code breaks modularity. That allows you to check a whole project in Git hooks, in CI, etc.

Tip. If you are unfamiliar with Modulite, consider a PHPStorm plugin and a landing page (in Russian).

Installation and configuration

To install, just use Composer:

composer require --dev vkcom/modulite-phpstan

After installing, include the plugin in your phpstan.neon and pass 2 parameters:

parameters:
    modulite:
        projectRoot: path    # project root is where composer.json/composer.lock/vendor exist
        srcRoot: path        # typically, projectRoot/src ; .modulite.yaml files will be searched recursively

includes:
    - vendor/vkcom/modulite-phpstan/extension.neon

Example project

Clone modulite-example-project, run composer install, run vendor/bin/phpstan analyze, and see some errors.

The example intentionally contains some errors :) Also, it describes several steps of making its code perfect.

screen-err-phpstan

You can use Modulite while developing Composer packages, too. Moreover, you can control which symbols are exported from your package. That ability is covered in documentation.

How the plugin works

As expected, modulite-phpstan analyzes function calls, class usages, PHPDocs, etc., and ensures that your code fits export, require, and other Modulite rules.

We advise you to use PHPStorm while development, because PHPStorm's plugin smoothly integrates into an IDE, producing .modulite.yaml files, which are analyzed by modulite-phpstan.

Typically, a project structure is the following:

my_project/     # projectRoot
    src/        # srcRoot
    tests/
    vendor/
    composer.json

When PHPStan starts analyzing your code, this plugin

  • locates all .modulite.yaml inside {srcRoot}
  • locates all .modulite.yaml and composer.json files inside {projectRoot}/vendor

The plugin has to scan vendor, because Composer packages are also checked to be required, etc. Moreover, a package can also be developed using Modulite and contain private symbols, so when embedded into your project, the plugin would check against their usages also.

Modulite files are parsed, resolved and validated once PHPStan runs. In case they contain no errors, all modularity checks will be performed. Otherwise, yaml errors are dumped and no checks are performed.

Composer packages outside vendor dir

In rare situations, you may be developing Composer packages locally, in the same repo. Being installed to vendor, they are just symlinked:

my_project/     # projectRoot
    src/        # srcRoot
    packages/   # additionalPackagesRoot
        utils-common/
            src/
            composer.json
    tests/
    vendor/
        doctrine/
        phpstan/
        utils/
            common/   (symlink to packages/utils-common)
    composer.json     (contains "repositories" type "path" into packages/*)

In such cases, PHPStan's internal reflection discovers some classes in packages/, which are expected to be in vendor/ from the Modulite's point of view. Without additional hints, Modulite will produce errors, thinking vendor/ uses files out of scope.

To overcome this problem, pass additionalPackagesRoot parameter besides srcRoot. Then modulite-phpstan will also scan that directory and create necessary path mappings.

A sad note about PHPStan cache

If a PHP file is unchanged, PHPStan doesn't run analysis within it. It caches files state and errors, and just dumps them out, it analyzes only diff and changed dependencies.

As appears from the above,

  • when PHP code is unchanged, but .modulite.yaml files are changed via PHPStorm plugin, no checks will be run
  • when bits of PHP code are changed and .modulite.yaml files are changed also, PHPStan will run checks only for changed files, and dump previous (cached) errors for unchanged files, though unchanged files may contain no errors after yaml modification

For now, a 100% working advice is to clear cache from time to time:

vendor/bin/phpstan clear-result-cache

That will make PHPStan analyze a whole project, and the plugin will work as expected.

Probably, this may be fixed using some deep knowledge of PHPStan cache internals, how to embed into it and how to tell PHPStan about PHP<->yaml interconnection. If you are experienced in writing plugins, give a suggestion in this issue.

Limitations

  • If you have unexpected behavior, try clearing PHPStan cache (a section above).
  • vendor/ dir is scanned recursively every run, which may take noticeable time; this can be improved, vote for this issue.
  • projectRoot and srcRoot parameters are required, but probably, they may be calculated automatically; if you are experienced in writing plugins, add a comment for this issue.
  • Only static method calls are analyzed, $obj->method() are not, that's why instance methods can't be added to force-internal. It's intentional, because static calls are resolved unambiguously, whereas instance calls rely on type inferring, which would certainly differ between IDE and PHPStan; static calls and class usages are more than enough, actually.

Contributing notes

Keep in mind, that all logic of Modulite behavior must be equal in 3 places:

  • Modulite in PHPStorm; checks are represented as IDEA inspections, symbols resolving also rely on PHPStorm internals.
  • Modulite in PHPStan (this repo).
  • Modulite in KPHP; KPHP is a PHP compiler invented in VK; moreover, initial implementation of modules was made for KPHP+PHPStorm combination, and later the same logic was exposed as a PHPStan plugin to be used in regular PHP projects; lots of code related to yaml validation and rules checking are ported from the C++ implementation and must be kept in sync with KPHP, to remain sustainable.

If you find a bug, it's either a specific bug related to PHPStan peculiarities, or it may be a bug that also exists in other implementations and should be fixed simultaneously. If you have a feature request, it must be implemented in three repos at the same time, covered with the same tests, also. So, feel free to file issues, we'll find a way to manage them.

If you are experienced in PHPStan internals, and you have some advices to make this plugin more canonical, we'd be glad to see your comments and PRs.

Ask questions and provide feedback

This plugin was developed by the KPHP Team at VK.com.

To communicate with our community, use GitHub issues or a Telegram chat.

modulite-phpstan's People

Contributors

danil42russia avatar hidanio avatar unserialize avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

modulite-phpstan's Issues

Make projectDir/srcDir optional (auto-calculated)?

Currenly, projectRoot and srcRoot parameters are required, but probably, they may be calculated automatically.

Maybe, parameters > paths is something we might base on for calculating srcRoot. There may be multiple paths, may there be multiple src roots? Also, there is no way to exclude directories from .modulite.yaml searching.

But even if we use parameters > paths, how projectRoot should be calculated to locate composer.json and vendor/?

Fatal error after installation

Tried the latest version of modulite-phpstan.
When run analyze command the following error happens.

 PHPStan analyze 
PHP Warning:  Undefined array key "message" in phar:///var/www/example/vendor/phpstan/phpstan/phpstan.phar/src/DependencyInjection/ValidateIgnoredErrorsExtension.php on line 95
Warning: Undefined array key "message" in phar:///var/www/example/vendor/phpstan/phpstan/phpstan.phar/src/DependencyInjection/ValidateIgnoredErrorsExtension.php on line 95
PHP Fatal error:  Uncaught TypeError: PHPStan\DependencyInjection\ValidateIgnoredErrorsExtension::validateMessage(): Argument #2 ($ignoreMessage) must be of type string, null given, called in phar:///var/www/example/vendor/phpstan/phpstan/phpstan.phar/src/DependencyInjection/ValidateIgnoredErrorsExtension.php on line 101 and defined in phar:///var/www/example/vendor/phpstan/phpstan/phpstan.phar/src/DependencyInjection/ValidateIgnoredErrorsExtension.php:113
Stack trace:

Don't scan vendor dir every time

Currently, {projectRoot}/vendor/ dir is scanned recursively every run, which may take noticeable time.

What can be done?
Result of vendor scanning may be cached (in \PHPStan\Cache\Cache prodived by DI, for example).
Invalidation could be based on composer.lock (we store SHA of a file, and if changed, rescan vendor).
Also, there should be a way to disable this cache in case vendor has symlinks to local folders (a typical way of developing Composer packages before publishing them to packagist).

Allow symfony/yaml:^6

Hello!
Can you allow Symfony Yaml package version as ^5.4 || ^6 please?

  Problem 1
    - Root composer.json requires vkcom/modulite-phpstan * -> satisfiable by vkcom/modulite-phpstan[v1.0.0].
    - vkcom/modulite-phpstan v1.0.0 requires symfony/yaml ^5.4 -> found symfony/yaml[v5.4.0, ..., v5.4.17] but the package is fixed to v6.2.2 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command.

Error after install

I run vendor/bin/phpstan analyse and get error:

Error                                                                                                                                                                                       
 -- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
     Child process error (exit code 255): Fatal error: Class ModulitePHPStan\ModulitePHPStanError contains 1 abstract method and must therefore be declared abstract or implement the remaining  
     methods (PHPStan\Rules\FileRuleError::getFileDescription) in /app/vendor/vkcom/modulite-phpstan/src/ModulitePHPStanError.php on line 9 

I use

        "phpstan/phpstan": "1.10.41",
        "vkcom/modulite-phpstan": "1.0.1"

A way to overcome PHPStan caches?

PHPStan has internal caches. They help it not to scan a whole project every time, but to scan only changed files and their dependents.

But PHPStan knows nothing about Modulite configs (about .modulite.yaml files), that they also refer to PHP symbols. This lack faces us with problems: once yaml files are changed, PHPStan doesn't but checks for PHP files thay may be affected. Due to caching, we may see previous errors, or don't see errors where they are expected โ€” for unchanged files.

Dropping caches (with phpstan clear-result-cache) or launching with --debug always works.

What can be done?
An acceptable solution. Once any yaml file has changed, all cache drops off automatically.
An ideal solution. Symbols in yaml files are treated as references from PHP code (classes, methods, etc.). Once a yaml file has changed, PHPStan should invalidate symbols referenced from it (both from previous and current versions).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.