Custom PHPStan rules that enforce PHPNomad framework conventions.
These rules catch architectural anti-patterns at static analysis time, including models with inline serialization, service locator usage, raw SQL, singleton abuse, missing interface implementations, database scope violations, cache misuse, and more.
composer require --dev phpnomad/phpstan-rules
If you use phpstan/extension-installer, the rules activate automatically.
Otherwise, add to your phpstan.neon:
includes:
- vendor/phpnomad/phpstan-rules/extension.neon
| Identifier |
Description |
phpnomad.model.notFinal |
Models implementing DataModel must be declared final. |
phpnomad.model.setter |
Models must not have setter methods (set*). Models are immutable. |
phpnomad.model.arrayMethod |
Models must not have toArray() or fromArray(). Use a separate adapter class. |
| Identifier |
Description |
phpnomad.adapter.notImplementing |
Classes named *Adapter or in Adapters namespaces must implement ModelAdapter. Exempts MutationAdapter implementations. |
| Identifier |
Description |
phpnomad.event.notImplementing |
Classes in Events namespaces or named *Event must implement PHPNomad\Events\Interfaces\Event. |
phpnomad.event.notFinal |
Event classes should be declared final. |
phpnomad.event.notReadonly |
Event properties and promoted constructor parameters should be readonly. |
phpnomad.listener.notImplementing |
Classes in Listeners namespaces or named *Listener must implement CanHandle. |
| Identifier |
Description |
phpnomad.di.serviceLocator |
InstanceProvider::get() must not be called inside business classes. Use constructor injection. Calls inside initializers and bootstrappers are allowed. |
phpnomad.di.missingTrait |
Classes implementing CanSetContainer must use the HasSettableContainer trait. |
| Identifier |
Description |
phpnomad.initializer.useInterface |
Initializers should use Has* interfaces (e.g., HasListeners, HasTaskHandlers) instead of manually calling registration methods like EventStrategy::attach(). |
| Identifier |
Description |
phpnomad.facade.notExtending |
Classes in Facades namespaces or named *Facade must extend PHPNomad\Facade\Abstracts\Facade. |
phpnomad.facade.noAbstractInstance |
Concrete Facade subclasses must implement abstractInstance(). |
| Identifier |
Description |
phpnomad.database.rawSql |
Raw SQL strings (SELECT ... FROM, INSERT INTO, etc.) must not appear in code. Use PHPNomad datastore patterns. |
phpnomad.database.scope |
QueryBuilder, ClauseBuilder, and QueryStrategy must only be used inside Datastore classes. |
phpnomad.database.concreteTableHint |
Constructor parameters should type-hint the Datastore interface, not concrete Table subclasses. |
| Identifier |
Description |
phpnomad.controller.noStatus |
Controller getResponse() must explicitly set an HTTP status code via setStatus() or setError(). |
| Identifier |
Description |
phpnomad.command.notImplementing |
Classes in Commands namespaces or named *Command must implement PHPNomad\Console\Interfaces\Command. |
phpnomad.command.handleReturnType |
Command handle() methods must declare an int return type. |
| Identifier |
Description |
phpnomad.task.notImplementing |
Classes in Tasks namespaces or named *Task must implement PHPNomad\Tasks\Interfaces\Task. |
phpnomad.taskHandler.notImplementing |
Task handler classes must implement PHPNomad\Tasks\Interfaces\CanHandleTask. |
phpnomad.task.directHandle |
Task handlers must be dispatched via TaskStrategy::dispatch(), not by calling handle() directly. |
| Identifier |
Description |
phpnomad.cache.directStrategy |
Business classes should not inject CacheStrategy directly. Use CacheableService or a Facade. |
phpnomad.cache.uncaughtException |
CacheStrategy::get() throws CachedItemNotFoundException. Ensure the call is wrapped in a try/catch. |
phpnomad.cache.hardcodedTtl |
Do not hardcode TTL values as integer literals. Use a CachePolicy or configuration constant. |
phpnomad.cache.stringConcatKey |
Use the HasCacheKey interface for cache key generation instead of string concatenation. |
| Identifier |
Description |
phpnomad.general.singletonInBusiness |
::instance() singleton calls must not be used outside of Facade classes. Use dependency injection. |
phpnomad.general.globalKeyword |
The global keyword must not be used. Use dependency injection. |
Use PHPStan's built-in ignore syntax:
// @phpstan-ignore phpnomad.model.notFinal
class User implements DataModel
{
// ...
}
Or suppress in phpstan.neon via baseline:
vendor/bin/phpstan --generate-baseline
MIT