{"id":15107828,"url":"https://github.com/vkcom/modulite","last_synced_at":"2025-04-12T15:33:32.079Z","repository":{"id":138760340,"uuid":"568834307","full_name":"VKCOM/modulite","owner":"VKCOM","description":"A plugin for PHPStorm that brings modules to the PHP language","archived":false,"fork":false,"pushed_at":"2024-10-01T19:48:54.000Z","size":5490,"stargazers_count":68,"open_issues_count":18,"forks_count":4,"subscribers_count":10,"default_branch":"master","last_synced_at":"2024-10-29T15:50:58.870Z","etag":null,"topics":["intellij-plugin","kphp","modules","modulite","php","phpstorm-plugin"],"latest_commit_sha":null,"homepage":"https://vkcom.github.io/modulite/","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/VKCOM.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-21T14:06:03.000Z","updated_at":"2024-10-14T14:43:41.000Z","dependencies_parsed_at":null,"dependency_job_id":"6dc1b80f-b029-4221-9d39-c72100207b91","html_url":"https://github.com/VKCOM/modulite","commit_stats":{"total_commits":26,"total_committers":6,"mean_commits":4.333333333333333,"dds":0.6153846153846154,"last_synced_commit":"0688256abc650d1091a4573d391ce51f0c7a7cd6"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VKCOM%2Fmodulite","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VKCOM%2Fmodulite/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VKCOM%2Fmodulite/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/VKCOM%2Fmodulite/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/VKCOM","download_url":"https://codeload.github.com/VKCOM/modulite/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248589879,"owners_count":21129697,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["intellij-plugin","kphp","modules","modulite","php","phpstorm-plugin"],"created_at":"2024-09-25T21:41:48.313Z","updated_at":"2025-04-12T15:33:32.045Z","avatar_url":"https://github.com/VKCOM.png","language":"Kotlin","readme":"# Modulite\n\nModulite is a conception that **brings modules into the PHP language**.\nPHP does not have native modules (internal classes, private namespaces, explicit exports),\nand Modulite tries to eliminate this drawback.\n\nThis repo is a **Modulite plugin for PHPStorm**. It deeply integrates into an IDE,\nallowing you to create modules from folders, declare private classes with hotkeys, and see errors right in the editor.\nActually, all \"modularity\" is represented as `.modulite.yaml` files,\nand the plugin is a great UI that visualizes yaml config and provides actions to modify it.\n\n**[Visit a landing page with details and examples (in Russian)](https://vkcom.github.io/modulite/)**\n\nHere we'll briefly cover most important aspects in English.\n\n\u003cimg alt=\"screen\" src=\"docs/img/screen-main-noshadow.png\" width=\"1287\"\u003e\n\n\n## What is \"a modulite\"\n\nModulite adds modules to PHP without modifying PHP syntax, but declaring scopes in yaml files.\n\n**A modulite** is a regular folder with a `.modulite.yaml` file inside. \nIt declares name, exports, requires, and some other properties.\nModulite names start with \"@\", e.g. `@api` or `@db/pdo`.\n\n**Export**. All classes / functions / constants within a modulite are either exported or internal.\nIf a symbol is internal, it's prohibited to be used outside the modulite. The plugin shows errors of such usages,\nas well as hints in a file tree.\n\n\u003cimg src=\"docs/img/screen-adaminka-2.png\" alt=\"screen-adaminka-2\" width=\"800\"\u003e\n\n**Require**. All used symbols that are defined out of modulite scope, must be explicitly imported (required).\nUsing a non-required symbol shows an error with a quick fix.\n\n\u003cimg src=\"docs/img/current-user-add.png\" alt=\"current-user-add\" width=\"800\"\u003e\n\n\n## The goal of Modulite\n\nThe goal in two words: **prevent uncontrolled entropy growth** inside a monolith.\nIt's a way to isolate folders **in existing code**.\n\nHuge projects (like VK.com, where Modulite was created) is a bunch of code with extreme connectivity,\nand time makes things worse. Some developers want to extract pieces of code into Composer packages,\nbut until it's not autonomous, it's impossible (in 100% of cases, actually).\nEvery sensible namespace is connected with all other code — both forwards (uses) and backwards (is used).\n\nExtracting to a package should be an iterative process, gradually decreasing folder's dependencies from external code.\nA problem is, that while you are doing this, other developers may still use your functions supposed to be internal.\nIt's not a malicious intent, it's just a coincidence: there was no tool to control it. Until now.\n\nModulite allows to **isolate specific parts of code, that obey the rules specified by code owners**.\nThis allows to refactor code, little by little decreasing its dependencies — having a guarantee no new ones occur.\nIdeally, a modulite tends to be fully autonomous, and then it may be exposed as a Composer package.\n\nIf to speak about existing solutions, you may remember `@psalm-internal`. That annotation allows to specify\na namespace where a function/class may be used. Probably, that annotation could be a solution for a \"public interface\",\nbut once you forget to write it over a new class, it occasionally becomes public.\nIn Modulite's conception, all new symbols are internal, an explicit `export` is needed.\nMoreover, specifying `require` is even more important for a long term refactoring.\n\n**By the way, a slight notice**. It's true, that when a modulite becomes independent,\nit may be exposed as a Composer package. But do you really need this? If it's not supposed to be a shared piece of code,\nyou just leave a folder inside a monolith. An idea to \"extract a package\" exists just because of an association\n\"a package is good, it means isolation\". And if isolation is provided by modularity,\n**you don't need Composer** for these purposes.\n\n\n## Features of the PHPStorm plugin\n\n**Create a modulite from existing folder**. A wizard scans a folder and lets you uncheck symbols to declare internal.\nAll requires are automatically generated.\n\n\u003cimg src=\"docs/img/new-modulite-window-kernel.png\" alt=\"new-modulite-window-kernel\" width=\"586\"\u003e\n\n**Mark a symbol exported/internal**. Every symbol inside a modulite has a hint `exported from @name`\n(or `internal in @name`). Visibility is changed either by *Alt+Enter* or via a context menu on a hint. \nThese hints are interactive: *@name* is clickable (navigates you to a *yaml* file).\n\nVisibility rules are the following:\n* all new symbols are internal unless manually exported\n* if a class is exported, its fields / constants / methods are also exported by default\n* but even in an exported class a concrete member may be forced to be internal\n\n\u003cimg src=\"docs/img/ui-make-symbol-internal.png\" alt=\"ui-make-symbol-internal\" width=\"800\"\u003e\n\n**Making an already used class internal**. If you just make an already used symbol internal,\nthat usages will arise errors. Here's what you can do: make a symbol internal, but allow all\ncurrent usages \"as an exception\". It makes your current code still work, but new code is \ndenied to use an internal symbol. So, we just stabilize current state and don't let it become worse.\nA plugin's action *\"Make internal in @name\"* makes this automatically.\nIt's stored as a special section `allow-internal-access` in `.modulite.yaml`.\n\n\u003cimg src=\"docs/img/code-made-internal-used-class.png\" alt=\"code-made-internal-used-class\" width=\"800\"\u003e\n\n**Hide members of an exported class**. In rare situations, you may want to deny a method from an external code,\nbut a class should be exported, and a method should have *public* modifier (since it's called from other classes).\nThis doesn't remind a good architecture, but remember, that we are dealing with *existing*, non-perfect code,\nand the goal is to restrict its usages. The plugin gives you all necessary UI, \nmodifying `force-internal` in yaml.\n\n\u003cimg src=\"docs/img/code-make-internal-method.png\" alt=\"code-make-internal-method\" width=\"800\"\u003e\n\n**New code and `require`**. You might remember, that all external symbols need to be manually listed:\n* other modulites `@another-modulite`\n* composer packages `#some/package`\n* extern classes `\\Some\\External\\Class`\n* static method of classes `\\Some\\External\\Class::itsMethod()`\n* extern global functions `\\externalGlobalFunction()`\n* extern defines and constants `\\SOME_DEFINE`\n* all global variables `$global_var`\n\n\u003cimg src=\"docs/img/code-requires-unknown-global.png\" alt=\"code-requires-unknown-global\" width=\"800\"\u003e\n\nA plugin checks that every symbol you use is listed here and allows you a quick fix.\nIt will lead to a change in a yaml file — an explicit newly-created dependency, visible in code review and Git. \n\nIf another modulite is required, all its exported symbols are available. The same for Composer packages.\nIf a class is required, all its constants, fields and instance methods are available.\nEvery static method is needed to be required separately, on purpose. \n\nAs a result, whenever we look into a yaml file, **we always see how our modulite depends on external code**.\n\n\u003cimg src=\"docs/img/code-requires-list-yaml.png\" alt=\"code-requires-list-yaml\" width=\"800\"\u003e\n\n**Submodulites**. Modulites may be nested, a nested modulite's name starts from a parent. \nE.g., if `@messi` is a parent, `@messi/folders` is a child. A submodulite may be exported from a parent.\n\n\u003cimg src=\"docs/img/window-create-messi-outer.png\" alt=\"window-create-messi-outer\" width=\"586\"\u003e\n\n**Find usages inside a modulite**. Every class/function now has additional items in a context menu.\nFor example, you read a modulite's code and see a call `currentUser()`. You may have interested:\nare there many such calls? (if there is only a couple, it seems easy to get rid of this dependency).\n\n\u003cimg src=\"docs/img/ui-find-usages-in-api.png\" alt=\"ui-find-usages-in-api\" width=\"800\"\u003e\n\n**Connectivity between modulites**. Similar to above, but more complex. Say, you've required `@messi/kernel` \nto use some kernel class. After, when writing a code and start using other symbols from kernel,\nyou don't have to update requires. And once, you ask yourself a question: how many symbols from kernel do I use?\nYou just click *\"Find usages in @my\"* in a context menu in `my/.modulite.yaml`.\n\n\u003cimg src=\"docs/img/window-find-usages-of-another-m.png\" alt=\"window-find-usages-of-another-m\" width=\"736\"\u003e\n\n**Tip**. A [landing page](https://vkcom.github.io/modulite/) contains more details and screenshots.\n\n\n## A .modulite.yaml file structure\n\n```yaml\nname: \"@modulite-name\"\ndescription: \"...\"\nnamespace: \"Some\\\\Namespace\\\\\"\n\nexport:\n  - \"ClassInNamespace\"\n  - \"OrConcrete::CLASS_MEMBER\"\n  # and others\n\nforce-internal:\n  - \"ClassInNamespace::staticMethod()\"\n  # and others\n\nrequire:\n  - \"@another-modulite\"\n  - \"#composer/package\"\n  - \"\\\\GlobalClass\"\n  # and others\n\nallow-internal-access:\n  \"@rpc\":\n    - \"ClassAllowedForRpcModule\"\n    - \"OrConcrete::method()\"\n    # the same format as in 'export', actually\n  \"\\\\globalFunction()\":\n    # more exceptions\n  \"\\\\Some\\\\GlobalClass\":\n    # and others\n```\n\nThe config is the reason why Modulite works in Git hooks and CI, not only within an IDE. \n\n\n## Modulite in Git hooks and CI\n\nFor real usage, it must be possible to validate your code as a whole.\n\n**Modulite + PHPStan**\n\nThere is [a PHPStan plugin](https://github.com/VKCOM/modulite-phpstan) for regular PHP projects.\nA plugin reads yaml configs emitted by PHPStorm and has all necessary checks implemented. \nErrors are printed as expected.\n\n\u003cimg src=\"docs/img/screen-err-phpstan.png\" alt=\"screen-err-phpstan\" width=\"800\"\u003e\n\n**Modulite + KPHP**\n\nModulite is integrated into [KPHP](https://github.com/VKCOM/kphp) (a PHP compiler from VK.com) out of the box.\n\n\u003cimg src=\"docs/img/screen-err-kphp.png\" alt=\"screen-err-kphp\" width=\"800\"\u003e\n\n\n## Modulite + Composer\n\nNothing prevents you to develop Composer packages using Modulite: to create subfolders with internal symbols, etc.\nIt just helps you organize code.\n\nWhen somebody uses your package in his project from any modulite, \nhe must add `#your/package` to requires. Actually, every embedded Composer package becomes an implicit modulite,\nand all modularity checks work automatically. \n\nModulites nested to a package are prefixed when embedded to a monolith.\nSay, you have `@impl` in your package. When embedded as vendor, it will be treated as `#your/package/@impl`.\nSince you export concrete symbols from `@impl`, a monolith can't use its internals.\n\n\u003cimg src=\"docs/img/screen-use-vk-voicetext-kphp-error.png\" alt=\"screen-use-vk-voicetext-kphp-error\" width=\"800\"\u003e\n\n\n## Export classes from a Composer package\n\nLet's talk about Composer alone. What is a package? Just a set of classes/functions and a way to autoload them.\nBut actually, no one prevents a monolith to use various helpers from a package, pretended to be non-public.\n\nModulite extends the functionality of Composer packages: it allows you to declare exports.\n\nIt's done in the following way. At a root folder, near `composer.json`, place a `.modulite.yaml` file:\n\n* **name** = \"\u003ccomposer_root\u003e\"\n* **namespace** = \"The\\\\\\\\Same\\\\\\\\\\As\\\\\\\\PSR4\\\\\\\\\"\n* **export** list as usual\n* **force-internal** also works\n* **require** leave blank, it's filled from \"requires\" of *composer.json*\n\n**Important**: You should write **name** = \"\u003ccomposer_root\u003e\" as is. You should not replace \"\u003ccomposer_root\u003e\" with your package name from composer.\n\u003cimg src=\"docs/img/code-modulite-at-composer-root.png\" alt=\"code-modulite-at-composer-root\" width=\"800\"\u003e\n\n\n## \"A module\" vs \"a modulite\"\n\nNot to be messed with JS modules, CSS modules, etc., we use the word \"modulite\". Treat this word in two ways: \n* \"lite module\" = \"module\" + \"lite\"\n* \"module in a monolith\" = \"modulith(e)\".\n\n\n## How to I start using Modulite?\n\nProceed to **[modulite-example-project](https://github.com/VKCOM/modulite-example-project)** and follow instructions.\n\nAlso, a reminder about [a landing page](https://vkcom.github.io/modulite/).\n\nAnd don't forget to install a PHPStorm plugin.\n\n\n## Contributing notes\n\nKeep in mind, that all logic of Modulite behavior must be equal in 3 places:\n* Modulite in PHPStorm (this repo); checks are represented as IDEA inspections, symbols resolving also rely on PHPStorm internals.\n* Modulite in [KPHP](https://github.com/VKCOM/kphp); 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.\n* Modulite in [PHPStan](https://github.com/VKCOM/modulite-phpstan); 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.\n\nIf you find a bug, it's either a specific bug related to PHPStorm 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.\n\n\n## The License\n\nModulite is distributed under the MIT License, on behalf of VK.com (V Kontakte LLC).\n\nMade by [KPHP Team](https://github.com/VKCOM/kphp) from VK.\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvkcom%2Fmodulite","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvkcom%2Fmodulite","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvkcom%2Fmodulite/lists"}