A PhpStorm/IntelliJ plugin that enhances PHP development workflow with advanced navigation and code analysis features, particularly focused on Symfony Messenger pattern and peer navigation capabilities.
- Smart Navigation: Navigate from message dispatch calls directly to their corresponding handlers
- Find Usages: Discover all dispatch calls for a specific message class or handler method
- Message Detection: Automatically identify message classes based on naming patterns and interfaces
- Handler Detection: Recognize message handlers through interfaces, attributes, or naming conventions
- Multi-dispatch Support: Support for various dispatch method names (
dispatch
,query
,command
,handle
)
- Pattern-based Navigation: Navigate between related classes using regex patterns
- Flexible Mapping: Define custom source-to-target class relationships
- Go to Declaration: Jump between peer classes with a single keystroke
- Associate Navigation: Navigate between associated classes based on custom relationships
- Multi-direction Support: Navigate bidirectionally between associated classes
- Attribute-based Navigation: Open a intellij search with a formatted value of an attribute property
- Flexible Mapping: Define custom attribute property and a formatter to generate a custom intellij search pattern
- Click and search: Jump between peer classes with a single keystroke
- Markdown Export: Export source code to markdown format optimized for LLMs
- Code Context Preservation: Maintain code structure and relationships in exported format, could also add all related classes found in specific namespaces.
- Custom Formatting: Configure export format settings
- Selective Export: Choose specific files or directories to export
- .aiignore: .aiignore file is used to filter files to export
- Freely inspired from https://github.com/keyboardsamurai/source-clipboard-export-intellij-plugin which was dedicated to Java project
- Command Output Analysis: Parse and analyze CLI dumper output
- Structured Data Extraction: Convert raw CLI output into structured format (JSON ou short syntac PHP arrays)
- Integration Support: Seamless integration with development workflow
- Supports filtering and searching
- Double-click to navigate to command implementation
- Provides direct navigation to route definitions
- Commands Panel: Shows command descriptions and usage information
- Routes Panel: Displays route path, name, and methods
- Doctrine Entities Panel: Displays name, tableName and schema
- Hot Reload: Configuration changes are automatically detected and applied
- Multiple Formats: Support for JSON and YAML configuration files
- Hierarchical Config: Local configuration files can override global settings
- Real-time Notifications: Get notified when configuration is loaded or encounters errors
The plugin uses configuration files placed in your project root. The plugin will automatically detect and load configuration from any of these files (in order of precedence):
.php-companion.yaml
.php-companion.json
.php-companion.local.yaml
.php-companion.local.json
attributeNavigation:
rules:
- actionType: find_in_file
attributeFQCN: \Symfony\Component\Routing\Attribute\Route
fileMask: '*.yaml,*.yml,*.php'
formatterScript: |
return (value.replaceAll("(\\{.*?\\})", "[^/]*")+ ":");
isDefault: true
propertyName: path
commandsConfiguration:
attributeFQCN: \Symfony\Component\Console\Attribute\AsCommand
enabled: true
namespaces:
- \App
- \Application
consoleCleaner:
patterns:
- ''
doctrineEntitiesConfiguration:
attributeFQCN: \Doctrine\ORM\Mapping\Table
enabled: true
namespaces:
- \Domain
- \Entity
exportSourceToMarkdown:
contextualNamespaces:
- App\Core\Models
template: |
[# th:each="file : ${files}"]
## [(${file.path})]
````[(${file.extension})]
[(${file.content})]
````
[/]
useContextualNamespaces: true
useIgnoreFile: true
openAPIConfiguration:
enabled: true
specificationRoots:
- ''
peerNavigation:
associates:
- classA: \\App\\Tests\\Func\\(?<type>.*)\\Web\\(?<path>.*)\\ControllerTest
classB: \\App\\(?<type>.*)\\Web\\(?<path>.*)\\Controller
peers:
- source: ''
target: ''
routesConfiguration:
attributeFQCN: \Symfony\Component\Routing\Attribute\Route
enabled: true
namespaces:
- \App
- \Application
symfonyMessenger:
asMessageHandlerAttribute: Symfony\Component\Messenger\Attribute\AsMessageHandler
dispatchMethods:
- dispatch
- query
- command
- handle
handlerMethods:
- __invoke
- handle
messageClassNamePatterns: .*(Message|Command|Query|Event|Input)$
messageHandlerInterfaces:
- Symfony\Component\Messenger\Handler\MessageHandlerInterface
messageInterfaces:
- ''
projectRootNamespace: \App
useNativeGoToDeclaration: false
Property | Description |
---|---|
useNativeGoToDeclaration | Disable ctrl+click to go to handler service |
projectRootNamespace | Root namespace for scanning classes |
messageClassNamePatterns | Regex pattern to identify message classes |
messageInterfaces[] | Interfaces that message classes implement |
messageHandlerInterfaces[] | Interfaces that handler classes implement |
dispatchMethods[] | Method names used to dispatch messages |
handlerMethods[] | Method names in handler classes |
asMessageHandlerAttribute |
- useNativeGoToDeclaration
- Disable ctrl+click to go to handler service
- Default Value:
false
- projectRootNamespace
- Root namespace for scanning classes
- Default Value:
\App
- messageClassNamePatterns
- Regex pattern to identify message classes
- Default Value:
.*(Message|Command|Query|Event|Input)$
- messageInterfaces[]
- Interfaces that message classes implement
- messageHandlerInterfaces[]
- Interfaces that handler classes implement
- dispatchMethods[]
- Method names used to dispatch messages
- handlerMethods[]
- Method names in handler classes
- asMessageHandlerAttribute
- Default Value:
Symfony\Component\Messenger\Attribute\AsMessageHandler
- Default Value:
symfonyMessenger:
asMessageHandlerAttribute: Symfony\Component\Messenger\Attribute\AsMessageHandler
dispatchMethods:
- dispatch
- query
- command
- handle
handlerMethods:
- __invoke
- handle
messageClassNamePatterns: .*(Message|Command|Query|Event|Input)$
messageHandlerInterfaces:
- Symfony\Component\Messenger\Handler\MessageHandlerInterface
messageInterfaces:
- ''
projectRootNamespace: \App
useNativeGoToDeclaration: false
Property | Description |
---|---|
peers[] | Array of one-way navigation rules |
peers[].source | Regex pattern with named groups matching source class FQN |
peers[].target | Target class FQN pattern using (?<groupName>.+) substitution from named groups |
associates[] | Array of bidirectional navigation rules |
associates[].classA | Regex pattern with named groups matching first class FQN |
associates[].classB | Pattern for second class FQN using (?<groupName>.+) substitution from named groups |
- peers[]
- Array of one-way navigation rules
- peers[].source
- Regex pattern with named groups matching source class FQN
- peers[].target
- Target class FQN pattern using
(?<groupName>.+)
substitution from named groups
- Target class FQN pattern using
- associates[]
- Array of bidirectional navigation rules
- associates[].classA
- Regex pattern with named groups matching first class FQN
- Example:
\\App\\Tests\\Func\\(?<type>.*)\\Web\\(?<path>.*)\\ControllerTest
- associates[].classB
- Pattern for second class FQN using
(?<groupName>.+)
substitution from named groups - Example:
\\App\\(?<type>.*)\\Web\\(?<path>.*)\\Controller
- Pattern for second class FQN using
peerNavigation:
associates:
- classA: \\App\\Tests\\Func\\(?<type>.*)\\Web\\(?<path>.*)\\ControllerTest
classB: \\App\\(?<type>.*)\\Web\\(?<path>.*)\\Controller
peers:
- source: ''
target: ''
Note: Associates provide bidirectional navigation - you can navigate from classA to classB and vice versa.
Bidirectional Navigation between Entities and Repositories:
peerNavigation:
associates:
- classA: \\App\\Domain\\Entity\\(?<entity>.+)
classB: \\App\\Domain\\Repository\\(?<entity>.+)Repository
Bidirectional Navigation between Controllers and Services:
peerNavigation:
associates:
- classA: \\App\\Controller\\(?<controller>.+)Controller
classB: \\App\\Service\\(?<controller>.+)Service
One-way Navigation from Commands to CommandHandlers using Named Groups:
peerNavigation:
peers:
- source: \\App\\Application\\(?<domain>.+)\\Command\\(?<command>.+)Command
target: \\App\\Application\\(?<domain>.+)\\CommandHandler\\(?<command>.+)CommandHandler
One-way Navigation from Queries to QueryHandlers:
peerNavigation:
peers:
- source: \\App\\Application\\(?<domain>.+)\\Query\\(?<query>.+)Query
target: \\App\\Application\\(?<domain>.+)\\QueryHandler\\(?<query>.+)QueryHandler
Complex Example with Multiple Named Groups:
peerNavigation:
peers:
- source: \\App\\(?<layer>Application|Domain)\\(?<module>.+)\\Entity\\(?<entity>.+)
target: \\App\\(?<layer>Application|Domain)\\(?<module>.+)\\Repository\\(?<entity>.+)Repository
associates:
- classA: \\App\\Domain\\(?<module>.+)\\Entity\\(?<entity>.+)
classB: \\App\\Domain\\(?<module>.+)\\Factory\\(?<entity>.+)Factory
Property | Description |
---|---|
rules[] | |
rules[].attributeFQCN | |
rules[].propertyName | |
rules[].isDefault | |
rules[].fileMask | |
rules[].actionType | How search is triggered |
rules[].formatterScript | A groovy script to reformat raw attribute value |
- rules[]
- rules[].attributeFQCN
- Default Value:
\Symfony\Component\Routing\Attribute\Route
- Default Value:
- rules[].propertyName
- Default Value:
path
- Default Value:
- rules[].isDefault
- Default Value:
true
- Default Value:
- rules[].fileMask
- Default Value:
*.yaml,*.yml,*.php
- Default Value:
- rules[].actionType
- How search is triggered
- Default Value:
find_in_file
- rules[].formatterScript
- A groovy script to reformat raw attribute value
- Example: ``` return (value.replaceAll("(\{.?\})", "[^/]")+ ":");
<!-- generateDocumentationEnd -->
#### Example
<!-- generateDocumentationExample("org.micoli.php.attributeNavigation.configuration.AttributeNavigationConfiguration","attributeNavigation") -->
```yaml
attributeNavigation:
rules:
- attributeFQCN: \Symfony\Component\Routing\Attribute\Route
propertyName: path
isDefault: true
fileMask: '*.yaml,*.yml,*.php'
actionType: find_in_file
formatterScript: |
return (value.replaceAll("(\\{.*?\\})", "[^/]*")+ ":");
Property | Description |
---|---|
useContextualNamespaces | |
useIgnoreFile | |
contextualNamespaces[] | List of namespaces, if an import detected in an exported classes belong to one of those namespace, than the class is added in the context |
template | Template Thymeleaf used to generate markdown export. Accès aux variables : files (FileData properties path , content , et extension ) |
- useContextualNamespaces
- Default Value:
true
- Default Value:
- useIgnoreFile
- Default Value:
true
- Default Value:
- contextualNamespaces[]
- List of namespaces, if an import detected in an exported classes belong to one of those namespace, than the class is added in the context
- template
- Template Thymeleaf used to generate markdown export. Accès aux variables :
files
(FileData propertiespath
,content
, etextension
) - Default Value: ``` [# th:each="file : ${files}"]
- Template Thymeleaf used to generate markdown export. Accès aux variables :
[(${file.content})]
[/]
<!-- generateDocumentationEnd -->
#### Example
<!-- generateDocumentationExample("org.micoli.php.exportSourceToMarkdown.configuration.ExportSourceToMarkdownConfiguration","exportSourceToMarkdown") -->
```yaml
exportSourceToMarkdown:
contextualNamespaces:
- App\Core\Models
template: |
[# th:each="file : ${files}"]
## [(${file.path})]
````[(${file.extension})]
[(${file.content})]
````
[/]
useContextualNamespaces: true
useIgnoreFile: true
Property | Description |
---|---|
patterns[] | Regular expression pattern for parsing output (if pattern start with ^and finished with $, then the whole line is stripped out) |
- patterns[]
- Regular expression pattern for parsing output (if pattern start with ^and finished with $, then the whole line is stripped out)
consoleCleaner:
patterns:
- ''
Property | Description |
---|---|
enabled | Enabler for panel of routes |
namespaces[] | List of namespaces where routes are searched |
attributeFQCN | Attribute used to detect routes |
- enabled
- Enabler for panel of routes
- Default Value:
true
- namespaces[]
- List of namespaces where routes are searched
- attributeFQCN
- Attribute used to detect routes
- Default Value:
\Symfony\Component\Routing\Attribute\Route
routesConfiguration:
attributeFQCN: \Symfony\Component\Routing\Attribute\Route
enabled: true
namespaces:
- \App
- \Application
Property | Description |
---|---|
enabled | Enabler for panel of console commands |
namespaces[] | List of namespaces where console commands are searched |
attributeFQCN | Attribute used to detect console commands |
- enabled
- Enabler for panel of console commands
- Default Value:
true
- namespaces[]
- List of namespaces where console commands are searched
- attributeFQCN
- Attribute used to detect console commands
- Default Value:
\Symfony\Component\Console\Attribute\AsCommand
commandsConfiguration:
attributeFQCN: \Symfony\Component\Console\Attribute\AsCommand
enabled: true
namespaces:
- \App
- \Application
Property | Description |
---|---|
enabled | Enabler for panel of doctrine entities |
namespaces[] | List of namespaces where doctrine entities are searched |
attributeFQCN | Attribute used to detect Entities |
- enabled
- Enabler for panel of doctrine entities
- Default Value:
true
- namespaces[]
- List of namespaces where doctrine entities are searched
- attributeFQCN
- Attribute used to detect Entities
- Default Value:
\Doctrine\ORM\Mapping\Table
doctrineEntitiesConfiguration:
attributeFQCN: \Doctrine\ORM\Mapping\Table
enabled: true
namespaces:
- \Domain
- \Entity
Property | Description |
---|---|
enabled | Enabler for panel of OAS routes |
specificationRoots[] | List of root files of swagger/openapi yaml/json files |
- enabled
- Enabler for panel of OAS routes
- Default Value:
true
- specificationRoots[]
- List of root files of swagger/openapi yaml/json files
openAPIConfiguration:
enabled: true
specificationRoots:
- public/openapi.yaml
- private/openapi.yaml
Property | Description |
---|---|
enabled | Enabler for panel of Task and actions |
tasks[] | Array of runnable task configurations available in the system. Each task must have a unique identifier to be referenced by tree or toolbar |
tasks[].actionId | Builtin actionId to execute |
tasks[].icon | Path to the icon to display for this builtin task. Uses standard IntelliJ Platform icons |
tasks[].id | Unique task identifier used for references in tree and toolbar. Must be unique among all tasks in the configuration |
tasks[].label | Label displayed to user in the interface. User-friendly name describing the task function |
tree[] | Hierarchical tree structure of tasks and folders for organization in the user interface. Can contain Task objects (referencing tasks by ID) and Path objects (folders containing other nodes) |
tree[].label | Label displayed for this folder in the hierarchical tree. User-friendly name for organizing tasks into logical groups |
tree[].tasks[] | Array of child nodes contained in this folder. Can contain other folders (Path) or task references (Task) |
toolbar[] | Array of tasks to display in the toolbar for quick access. Each element must reference an existing task via its taskId |
watchers[] | File watchers configuration that automatically trigger tasks when specified files are modified |
watchers[].taskId | Identifier of the task to execute when watched files are modified. Must match the ID of an existing task in the configuration |
watchers[].debounce | Delay in milliseconds before task triggering after change detection. Prevents multiple executions during rapid successive modifications |
watchers[].notify | Indicates if a notification should be displayed to the user upon triggering. False by default to avoid too frequent notifications |
watchers[].watches[] | Array of file patterns to watch. Supports wildcards and regular expressions to match file paths |
- enabled
- Enabler for panel of Task and actions
- Example:
true
- Default Value:
false
- tasks[]
- Array of runnable task configurations available in the system. Each task must have a unique identifier to be referenced by tree or toolbar
- tasks[].actionId
- Builtin actionId to execute
- Example:
$Copy
- tasks[].icon
- Path to the icon to display for this builtin task. Uses standard IntelliJ Platform icons
- Default Value:
debugger/threadRunning.svg
- tasks[].id
- Unique task identifier used for references in tree and toolbar. Must be unique among all tasks in the configuration
- Example:
aTaskId
- tasks[].label
- Label displayed to user in the interface. User-friendly name describing the task function
- Example:
First task
- tree[]
- Hierarchical tree structure of tasks and folders for organization in the user interface. Can contain Task objects (referencing tasks by ID) and Path objects (folders containing other nodes)
- tree[].label
- Label displayed for this folder in the hierarchical tree. User-friendly name for organizing tasks into logical groups
- tree[].tasks[]
- Array of child nodes contained in this folder. Can contain other folders (Path) or task references (Task)
- toolbar[]
- Array of tasks to display in the toolbar for quick access. Each element must reference an existing task via its taskId
- watchers[]
- File watchers configuration that automatically trigger tasks when specified files are modified
- watchers[].taskId
- Identifier of the task to execute when watched files are modified. Must match the ID of an existing task in the configuration
- watchers[].debounce
- Delay in milliseconds before task triggering after change detection. Prevents multiple executions during rapid successive modifications
- Default Value:
1000
- watchers[].notify
- Indicates if a notification should be displayed to the user upon triggering. False by default to avoid too frequent notifications
- Default Value:
false
- watchers[].watches[]
- Array of file patterns to watch. Supports wildcards and regular expressions to match file paths
tasksConfiguration:
enabled: false
tasks:
- type: builtin
id: null
label: null
actionId: $Copy
icon: debugger/threadRunning.svg
- type: shell
id: null
label: null
command: make clear-cache
cwd: ''
icon: debugger/threadRunning.svg
- type: script
id: null
label: null
source: ''
extension: groovy
icon: debugger/threadRunning.svg
- type: observedFile
id: null
label: null
commentPrefix: '#'
filePath: ''
variableName: ''
activeIcon: actions/inlayRenameInComments.svg
inactiveIcon: actions/inlayRenameInCommentsActive.svg
unknownIcon: expui/fileTypes/unknown.svg
postToggle:
type: builtin
id: null
label: null
actionId: null
icon: debugger/threadRunning.svg
icon: actions/inlayRenameInComments.svg
toolbar:
- null
tree:
- type: path
label: ''
tasks:
- null
- type: task
taskId: aTaskId
label: aLabel
- null
watchers:
- taskId: ''
debounce: 1000
notify: false
watches:
- ''
Property | Description |
---|---|
enabled | Enabler for panel of Code style synchronization |
styles[] | |
styles[].styleAttribute | Code style field property as in com.intellij.psi.codeStyle.CommonCodeStyleSettings |
styles[].value | a boolean value true/false or an int value |
- enabled
- Enabler for panel of Code style synchronization
- Example:
true
- Default Value:
false
- styles[]
- styles[].styleAttribute
- Code style field property as in com.intellij.psi.codeStyle.CommonCodeStyleSettings
- Example:
ALIGN_MULTILINE_PARAMETERS_IN_CALLS
- styles[].value
- a boolean value true/false or an int value
- Example:
false
codeStyleSynchronization:
enabled: false
styles:
- styleAttribute: ALIGN_MULTILINE_PARAMETERS_IN_CALLS
value: 'false'
known as ui
in scripting engine
-
void alert(String message)
Displays a closable popupmessage
: the message to display.
-
void alert(String message, int delayInMs)
Displays a closable popup and automatically close it after a given delaymessage
: the message to display.delayInMs
: the delay in milliseconds before the popup is closed.
known as fs
in scripting engine
-
void clearPath(String path)
Removes a path and it's sub content. Path must be ignored by GIT.path
: the relative filepath
-
void clearPath(String path, boolean mustBeGitIgnored)
Removes a path and it's sub content.path
: the relative filepathmustBeGitIgnored
: if false, the path will be removed even if it's not ignored by GIT.
known as core
in scripting engine
-
void runAction(String actionId)
Runs a registered action.actionId
: the ID of the action to run
-
void runActionInEditor(String actionId)
Activates the currently opened editor and runs a registered action.actionId
: the ID of the action to run
- Named Groups: Use regex named groups
(?<name>...)
in patterns and reference them with(?<name>...) (same expression)
in targets for better readability and maintainability - Regex Escaping: In YAML configuration, use double backslashes (
\\
) for namespace separators in regex patterns - Local Overrides: Use
.php-companion.local.*
files for project-specific settings that shouldn't be committed - Hot Reload: The plugin checks for configuration changes every 2 seconds
- Error Handling: Configuration errors will be displayed as notifications in the IDE
- Peers vs Associates:
- Use
peers
for one-way navigation (source → target) - Use
associates
for bidirectional navigation (classA ↔ classB)
- Use
- Pattern Matching: Both
peers
andassociates
support complex regex patterns with multiple named groups
- Java 11+
- IntelliJ IDEA with Plugin DevKit
- Gradle
# Build the plugin
./gradlew buildPlugin
# Run in development mode
./gradlew runIde
# Run tests
./gradlew test
- ConfigurationFactory: Loads and merges configuration files
- GsonTools: Handles JSON object merging with conflict resolution
- Hot Reload: Automatic configuration reloading every 2 seconds
- MessengerService: Core service for message/handler detection and navigation
- MessengerGotoDeclarationHandler: Handles navigation from dispatch calls to handlers
- MessengerFindUsagesHandler: Finds all dispatch calls for messages/handlers
- PHPHelper: Utility methods for PHP class analysis
- PeerNavigationGotoDeclarationHandler: Handles navigation between peer classes
- Regex-based Matching: Flexible pattern matching for class relationships
- Fork the Repository: Create your own fork of the project
- Create Feature Branch:
git checkout -b feature/your-feature-name
- Follow Code Style: Use existing code formatting and conventions
- Add Tests: Include unit tests for new functionality
- Update Documentation: Update README and inline documentation
- Submit Pull Request: Create a PR with clear description of changes
- Use standard Java naming conventions
- Add JavaDoc comments for public methods
- Keep methods focused and single-purpose
- Use meaningful variable and method names
- Handle exceptions appropriately with user-friendly error messages
The plugin includes unit tests for core functionality. When adding new features:
- Add corresponding unit tests
- Test with different PHP project structures
- Verify configuration loading and hot reload
- Test error handling scenarios
To debug the plugin:
- Use
./gradlew runIde
to launch a development instance - Set breakpoints in your IDE
- Use IntelliJ's internal logging:
Help → Show Log in Files
- Enable debug notifications in
Notification.java
- Extension Points: Uses IntelliJ's extension point system for handlers
- Project Components: Manages lifecycle through ProjectComponent interface
- PSI Integration: Leverages PhpStorm's PSI (Program Structure Interface) for code analysis
- Background Processing: Configuration loading runs on background threads
- Event-Driven: Uses IntelliJ's event system for real-time updates
- Support for PHP 8 attributes parsing
- Enhanced caching for better performance
- Additional Symfony component integrations
- Visual configuration editor
- Code generation templates