All these options under «Languages & Frameworks» → «PHP» → «Composer» are disabled:
- «Add packages as libraries»
- «Synchronize IDE Settings with composer.json»
- «Check for available package updates»

All these options under «Languages & Frameworks» → «PHP» → «Composer» are disabled:

Delete all entries from «Languages & Frameworks» → «PHP» → «Composer» → «Composer Files»:

Exit IntelliJ IDEA / PHPStorm completely.
How to remove «Include Path» entries from PHPStorm when the minus button is disabled?
PHPStorm will likely make multiple attempts to restore the contents of the .idea/php.xml file that we modified in the previous step.
So, make a copy of the file after editing it.
Start IntelliJ IDEA / PHPStorm again and open your project.
After that, PHPStorm re-enabled (for an unknown reason) the 2 options under «Languages & Frameworks» → «PHP» → «Composer»:

Disable the 2 options again:
Then, repeat Step 2 and Step 3.
You may need to repeat this procedure multiple times.
That’s why we made a copy of the .idea/php.xml file in Step 4.
The JetBrains PHP plugin (com.jetbrains.php) is a proprietary, closed-source component bundled with PhpStorm and IntelliJ IDEA Ultimate inside plugins/php-impl/lib/php.jar (e.g., %APPDATA%\JetBrains\IntelliJIdea<version>\plugins\php-impl\lib\php.jar). Related bug reports are tracked on JetBrains YouTrack:
While the base IntelliJ Platform is open-source on GitHub (github.com/JetBrains/intellij-community/tree/idea/262.10968.63), the PHP plugin bytecode decompiled directly from php.jar using Fernflower reveals how project opening and Composer detection operate.
When a project is opened in the IDE, JetBrains runs background startup activities implementing com.intellij.openapi.startup.ProjectActivity:
Specifically, com.jetbrains.php.roots.PhpDetectPsrRootsOnProjectOpen triggers ComposerDetectionManager.detectComposer(project).
Decompiled from com/jetbrains/php/composer/ComposerDetectionManager.class:
package com.jetbrains.php.composer;
public final class ComposerDetectionManager {
public static @Nullable ComposerConfigs detectComposer(Project project) {
if (project != null && !project.isDefault()) {
if (ComposerConfigManager.getInstance(project).isConfigured()) {
return null;
} else {
VirtualFile mainComposerJson = findMainConfig(project);
if (mainComposerJson == null) {
return null;
} else {
Collection<VirtualFile> subProjectConfigs = findSubProjectConfigs(project, mainComposerJson);
return new ComposerConfigs(mainComposerJson, subProjectConfigs);
}
}
} else {
return null;
}
}
}
Notice the safeguard at line 34:
if (ComposerConfigManager.getInstance(project).isConfigured()) {
return null;
}
If isConfigured() returns true, the detection routine exits immediately. No files are scanned, no notifications are displayed, and no settings are altered.
isConfigured() is implementedDecompiled from com/jetbrains/php/composer/configData/ComposerConfigManager.class:
package com.jetbrains.php.composer.configData;
@Service({Level.PROJECT})
@State(
name = "ComposerConfigs",
storages = {@Storage("$WORKSPACE_FILE$")}
)
public final class ComposerConfigManager extends SimplePersistentStateComponent<ComposerConfigState> {
private final Set<VirtualFile> configs;
public final boolean isConfigured() {
return !((ComposerConfigState)this.getState()).getNotifyAboutSettingsSync() || !((Collection)this.configs).isEmpty();
}
}
And in the inner state class ComposerConfigState:
public static final class ComposerConfigState extends BaseState {
public ComposerConfigState() {
this.configs$delegate = this.list().provideDelegate(this, $$delegatedProperties[0]);
this.notifyAboutSettingsSync$delegate = this.property(true).provideDelegate(this, $$delegatedProperties[1]);
}
public final boolean getNotifyAboutSettingsSync() {
return (Boolean)this.notifyAboutSettingsSync$delegate.getValue(this, $$delegatedProperties[1]);
}
}
Key observations:
notifyAboutSettingsSync is initialized to true by default. Thus !getNotifyAboutSettingsSync() evaluates to false.isConfigured() evaluates to:isConfigured() = false || !this.configs.isEmpty() = !this.configs.isEmpty()
configs is NOT empty!When all entries are deleted from «Composer Files»:
<component name='ComposerConfigs'> becomes empty in .idea/workspace.xml.this.configs.isEmpty() is true.isConfigured() returns false.detectComposer(project) runs and scans the repository using FilenameIndex.getVirtualFilesByName():composer.json plus all sub-project composer.json files outside /vendor/.isConfigured() is false, detectComposer() returns a non-null ComposerConfigs object.ComposerDetectionManager.updateProjectSettings(project, composerConfigs) is invoked:public static void updateProjectSettings(@NotNull Project project, @Nullable ComposerConfigs composerConfigs) {
...
dataService.setConfigPathAndLibraryUpdateStatus(composerConfigs.main().getPath(), true);
for (VirtualFile config : composerConfigs.subProjects) {
configManager.addConfig(config);
ComposerLibraryService libraryManager = ComposerLibraryServiceFactory.getInstance(project, config);
libraryManager.reloadLibraries(true, Collections.emptySet(), false, true);
}
if (isDefaultAllSettings) {
dataService.setSynchronizationState(ComposerDataService.SynchronizationState.SYNCHRONIZE);
}
showNotification(project, composerConfigs, syncState, updateLibrary, isMainConfigUpdated);
}
libraryManager.reloadLibraries(true, ...) executes:
include_path.<excludeFolder> lines to .iml.synchronizationState to SYNCHRONIZE.Deleting entries from «Composer Files» only appears to work after multiple manual attempts when repeated saving eventually causes the IDE to persist discovered sub-configs back into ComposerConfigs while keeping DONT_SYNCHRONIZE set.
To prevent IntelliJ IDEA / PhpStorm from ever running detectComposer() and re-populating include_path:
Do NOT empty <component name='ComposerConfigs'>!
Instead:
<component name='ComposerConfigs'> in .idea/workspace.xml with the paths to all sub-project composer.json files in the repository. Because configs is not empty, isConfigured() immediately returns true on project startup, and detectComposer() aborts on line 34 without modifying anything.<component name='ComposerSettings'> using synchronizationState='DONT_SYNCHRONIZE' and updatePackages='false'.include_path entries from .idea/workspace.xml and .idea/php.xml..iml so it only contains project-level exclusions, with no vendor package exclusions.Make these modifications while IntelliJ IDEA / PhpStorm is closed.
.idea/workspace.xmlEnsure <component name='ComposerConfigs'> contains all sub-project composer.json files, <component name='ComposerSettings'> has synchronizationState='DONT_SYNCHRONIZE', and <component name='PhpWorkspaceProjectConfiguration'> is self-closing without <include_path>:
<component name='ComposerConfigs'>
<option name='configs'>
<option value='$PROJECT_DIR$/packages/module-a/composer.json' />
<option value='$PROJECT_DIR$/packages/module-b/composer.json' />
<option value='$PROJECT_DIR$/tests/integration/composer.json' />
</option>
</component>
<component
loadUpdateAvailability='false'
name='ComposerSettings'
notifyAboutMissingVendor='false'
showQuickActionsPanel='false'
synchronizationState='DONT_SYNCHRONIZE'
updatePackages='false'
>
<pharConfigPath>$PROJECT_DIR$/composer.json</pharConfigPath>
<execution>
<executable path='composer' />
</execution>
</component>
<component interpreter_name='PHP' name='PhpWorkspaceProjectConfiguration' />
(Note: In <component name='ComposerConfigs'>, list every sub-project composer.json file in your repository located outside the vendor directory).
.idea/php.xmlEnsure that <component name='PhpIncludePathManager'> is completely absent:
<?xml version='1.0' encoding='UTF-8'?>
<project version='4'>
<component name='PhpProjectSharedConfiguration' php_language_level='8.3'>
<option name='suggestChangeDefaultLanguageLevel' value='false' />
</component>
</project>
<module_name>.imlEnsure the module configuration only excludes your intended project directories, with no <excludeFolder> entries for vendor packages:
<?xml version='1.0' encoding='UTF-8'?>
<module type='JAVA_MODULE' version='4'>
<component inherit-compiler-output='true' name='NewModuleRootManager'>
<exclude-output />
<content url='file://$MODULE_DIR$'>
<excludeFolder url='file://$MODULE_DIR$/var' />
<excludeFolder url='file://$MODULE_DIR$/cache' />
</content>
<orderEntry type='inheritedJdk' />
<orderEntry forTests='false' type='sourceFolder' />
</component>
</module>
ComposerConfigManager.getInstance(project).isConfigured() checks configs. Because configs contains the pre-populated paths of all sub-project composer.json files, isConfigured() immediately evaluates to true.ComposerDetectionManager.detectComposer(project) returns null at line 34, completely bypassing the scan and update logic..iml file stays clean without vendor package pollution.Ctrl+Alt+Shift+C) reliably produces the correct, full relative path from the project root.