craft-php-guidelines
Installation
SKILL.md
Craft CMS 5 PHP Guidelines
Complete PHP coding standards and conventions for Craft CMS 5 plugin and module development. These extend Craft's official coding guidelines with project-specific conventions.
Core principles: PHPDocs on everything — classes, methods, and properties — regardless of type hints. No declare(strict_types=1) in plugin source files (matching Craft core convention).
Companion Skills — Always Load Together
craftcms— Architecture patterns, element lifecycle, controllers, events, migrations. Required for any Craft plugin or module development.ddev— All commands run through DDEV. Required for running ECS, PHPStan, scaffolding, and tests.
Documentation
- Official coding guidelines: https://craftcms.com/docs/5.x/extend/coding-guidelines.html
- Class reference: https://docs.craftcms.com/api/v5/
- Generator reference: https://craftcms.com/docs/5.x/extend/generator.html
When unsure about a convention, WebFetch the coding guidelines page for the authoritative answer.
Common Pitfalls
addSelect()is the convention inbeforePrepare()— safely additive when multiple extensions contribute columns.$_instancesis not a Craft convention — private properties use underscore prefix but meaningful names like$_items,$_sections.- Records use the same class name as models (namespace distinguishes). Alias when importing both:
use ...\records\MyEntity as MyEntityRecord;. - Queue jobs have no "Job" suffix —
ResaveElements, notResaveElementsJob. declare(strict_types=1)is NOT used in plugin source files. Only in standalone config files likeecs.php.@authorgoes on classes and methods only — never on properties.- Don't use
string|null— use?string(short nullable notation). - Forget
parent::defineRules()and you lose all inherited validation. DateTimeHelperin elements/queries,Carbonin services — never mix in the same class.- Missing
@throwschains — document exceptions from called methods too, not just your own throws. - Using magic property access (
$plugin->settings,$app->view) instead of explicit getters ($plugin->getSettings(),$app->getView()) — PHPStan can't resolve__get()calls, so magic access passes at runtime but fails static analysis. Always use explicit getters for Yii2 components and Craft plugin properties.
Reference Files
Read the relevant reference file(s) for your task:
| Task | Read |
|---|---|
Writing PHPDocs, @author, @since, @throws, @var, @param, type references |
references/phpdoc-standards.md |
| Class structure, section headers, ordering, enums, control flow, comments, whitespace | references/class-organization.md |
| Naming classes, methods, properties, files, services, events, migrations | references/naming-conventions.md |
| CP Twig templates, form macros, translations, file headers, validation | references/templates-and-patterns.md |
| ECS, PHPStan, scaffolding commands, commit messages | references/tooling.md |
Critical Rules
- PHPDocs on everything: classes, methods, properties. No exceptions.
@throwschains: document every exception including uncaught from called methods.@authorand@sinceat the bottom of class/method docblocks, after a blank line.- Section headers with
// =========================================================================on every class. declare(strict_types=1)is NOT used in plugin source files — Craft's internal type coercion depends on PHP's default weak typing mode.- Private methods/properties prefixed with underscore:
_registerCpUrlRules(),$_items. addSelect()convention inbeforePrepare()— additive across extensions, prevents column conflicts.DateTimeHelperin elements/queries,Carbonin services — separate concerns prevent mixing date APIs in the same class.- Always scaffold with
ddev craft make <type> --with-docblocks, then customize. ddev composer check-csandddev composer phpstanmust pass before every commit.
PHP Standards
- Minimum PHP 8.2 (Craft CMS 5 requirement).
- PSR-12 baseline with Craft modifications (trailing commas, constant visibility).
craftcms/ecswithSetList::CRAFT_CMS_4preset (covers both Craft 4 and 5).- Short nullable notation:
?stringnotstring|null. - Always specify
voidreturn types. - Typed properties everywhere. No untyped public properties.
- Strict comparison always:
$foo === null,in_array($x, $y, true). - Casts over functions:
(int)$foonotintval($foo).
Section Header Order
// Traits
// Const Properties
// Static Properties
// Public Properties
// Protected Properties
// Private Properties
// Public Methods
// Protected Methods
// Private Methods
Only include sections that have content. Blank line after the separator, before the first item.
Control Flow
- Happy path last. Handle error conditions first with early returns.
- Avoid
else— use early returns instead. matchoverswitch— always.- Always use curly brackets even for single statements.
- Separate compound conditions into nested
ifstatements for readability. - Named arguments when calling methods with 3+ parameters.
Date Handling
- Elements and element queries:
craft\helpers\DateTimeHelper. - Services (date arithmetic):
Carbon\Carbon. - Never mix both in the same class.
Database Conventions
[[column]]quoting in Yii2 join conditions.addSelect()inbeforePrepare()— safely additive.postDateandexpiryDateinaddSelect()and indexed on element tables.Db::parseParam()for query parameters.Db::parseDateParam()for dates.- Foreign keys with explicit
CASCADE/SET NULLbehavior.
Naming Quick-Reference
| Thing | Convention | Example |
|---|---|---|
| Services (resource) | Plural | Entries, Volumes, Users |
| Services (utility) | Domain noun | Auth, Search, Gc |
| Queue jobs | Action verb, no suffix | ResaveElements, UpdateSearchIndex |
| Records | Same name as model | Namespace distinguishes |
| Events | Three patterns | SectionEvent, RegisterUrlRulesEvent, DefineHtmlEvent |
| Element actions | Action verb, no suffix | Delete, Duplicate, SetStatus |
| Enums | PascalCase cases, string/int backed | PropagationMethod, CmsEdition |
For the complete naming reference including file structure conventions, read references/naming-conventions.md.
Verification Checklist
Before every commit:
ddev composer check-cspassesddev composer phpstanpasses- Tests green
- PHPDocs complete on all new/modified code
@throwschains verified- Section headers present and correct
- Imports alphabetical and grouped
Weekly Installs
12
Repository
michtio/craftcm…e-skillsGitHub Stars
38
First Seen
9 days ago
Security Audits