Marking entrypoints in libraries using @api
master@api PHPDoc tag. You can mark individual methods with @api, or mark an entire class or interface with @api to treat all its members as entrypoints.repository·master·Indexed 19 days ago
https://github.com/shipmonk-rnd/dead-code-detectorA PHPStan extension for detecting unused PHP code, including dead cycles, transitive dead members, and code used only in tests. It supports automatic dead code removal, custom MemberUsageProvider and MemberUsageExcluder implementations, and integration with libraries like Symfony, Doctrine, and PHPUnit.
@api PHPDoc tag. You can mark individual methods with @api, or mark an entire class or interface with @api to treat all its members as entrypoints.Once installed and configured, run the detector using the standard PHPStan binary.
Important: Ensure you analyze your entire codebase (e.g., both src and tests) so that all usages are correctly identified.
$ vendor/bin/phpstanIf you are not using the official extension-installer, add the following to your phpstan.neon.dist file to load the rules:
includes:
- vendor/shipmonk/dead-code-detector/rules.neon# phpstan.neon.dist
includes:
- vendor/shipmonk/dead-code-detector/rules.neonInstall the extension as a development dependency using Composer:
composer require --dev shipmonk/dead-code-detectorTo activate the rules in PHPStan, you can either use the official PHPStan extension-installer or manually include the rules file in your phpstan.neon.dist configuration.
By default, all dead class member types are detected. You can customize which types are checked using the shipmonkDeadCode.detect configuration key in your phpstan.neon.dist.
parameters:
shipmonkDeadCode:
detect:
deadMethods: true
deadConstants: true
deadEnumCases: true
deadProperties:
neverRead: true
neverWritten: trueBy default, any usage within your scanned paths marks a member as used. If you want to identify code that is only used within your test suite (and thus potentially dead in production), enable the tests usage excluder.
When enabled, members used only in tests will be reported with a message like: Unused AddressValidator::isValidPostalCode (all usages excluded by tests excluder).
Recommendation: It is recommended to enable this excluder for all projects.
parameters:
shipmonkDeadCode:
usageExcluders:
tests:
enabled: true
devPaths: # optional, autodetects from autoload-dev sections of composer.json when omitted
- %currentWorkingDirectory%/testsTo prevent false positives, the library marks all methods/constants named after a call over an unknown type (e.g., $unknown->method()) as used.
If your codebase is strictly typed and you want to disable this behavior to avoid marking all constructors or constants as used, you can enable the usageOverMixed excluder in your phpstan.neon.dist.
parameters:
shipmonkDeadCode:
usageExcluders:
usageOverMixed:
enabled: trueThe detector automatically enables support for popular libraries (Symfony, Doctrine, PHPUnit, etc.) when they are found in your composer dependencies. You can manually force-enable or disable a specific provider using the shipmonkDeadCode.usageProviders.{provider}.enabled key.
parameters:
shipmonkDeadCode:
usageProviders:
phpunit:
enabled: trueBy default, the library reports only the first dead method in a subtree and lists subsequent transitively dead methods as tips (💡). If you prefer to have every transitively dead method reported as its own individual error, enable reportTransitivelyDeadMethodAsSeparateError in your phpstan.neon.dist configuration.
parameters:
shipmonkDeadCode:
reportTransitivelyDeadMethodAsSeparateError: trueDead code detection requires a full codebase analysis and is automatically disabled during partial analysis (when only specific files are passed to PHPStan). This causes inline ignores (e.g., // @phpstan-ignore shipmonk.deadMethod) to report as unmatched errors.
To fix this, use the filterOutUnmatchedInlineIgnoresDuringPartialAnalysis error format in your configuration.
parameters:
errorFormat: filterOutUnmatchedInlineIgnoresDuringPartialAnalysis
# optionally:
shipmonkDeadCode:
filterOutUnmatchedInlineIgnoresDuringPartialAnalysis:
wrappedErrorFormatter: tableTo understand why a specific member is marked as alive or dead, you can provide a list of members to debug in your phpstan.neon.dist. After configuring this, run PHPStan with the -vvv flag to see the trace of how the member was evaluated (e.g., which provider marked it as alive and the call chain leading to it).
parameters:
shipmonkDeadCode:
debug:
usagesOf:
- App\User\Entity\Address::__constructYou can extend the detection logic by implementing a custom ReflectionBasedMemberUsageProvider. This is useful for handling cases like serialization or interface methods that should be considered 'used' even if no direct call is found.
If your API output objects implement a specific interface, you can use shouldMarkPropertyAsRead to prevent them from being reported as dead.
use ReflectionProperty;
use ShipMonk\PHPStan\DeadCode\Provider\VirtualUsageData;
use ShipMonk\PHPStan\DeadCode\Provider\ReflectionBasedMemberUsageProvider;
class ApiOutputPropertyUsageProvider extends ReflectionBasedMemberUsageProvider
{
protected function shouldMarkPropertyAsRead(ReflectionProperty $property): ?VirtualUsageData
{
if ($property->getDeclaringClass()->implementsInterface(ApiOutput::class)) {
return VirtualUsageData::withNote('Used upon JSON serialization');
}
return null;
}
}