{"id":"768472b2-94c5-4b59-921f-e74f3bb46efd","shortId":"KFUX5N","kind":"skill","title":"craft-php-guidelines","tagline":"Craft CMS 5 PHP coding standards and conventions. ALWAYS load this skill when writing, editing, reviewing, or discussing any PHP file in a Craft CMS plugin or module — even for small edits. Also load when running ECS, PHPStan, or scaffolding with ddev craft make. Covers: PHPDoc b","description":"# Craft CMS 5 PHP Guidelines\n\nComplete PHP coding standards and conventions for Craft CMS 5 plugin and module development. These extend Craft's official coding guidelines with project-specific conventions.\n\n**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).\n\n## Companion Skills — Always Load Together\n\n- **`craftcms`** — Architecture patterns, element lifecycle, controllers, events, migrations. Required for any Craft plugin or module development.\n- **`ddev`** — All commands run through DDEV. Required for running ECS, PHPStan, scaffolding, and tests.\n\n## Documentation\n\n- Official coding guidelines: https://craftcms.com/docs/5.x/extend/coding-guidelines.html\n- Class reference: https://docs.craftcms.com/api/v5/\n- Generator reference: https://craftcms.com/docs/5.x/extend/generator.html\n\nWhen unsure about a convention, `WebFetch` the coding guidelines page for the authoritative answer.\n\n## Common Pitfalls\n\n- `addSelect()` is the convention in `beforePrepare()` — safely additive when multiple extensions contribute columns.\n- `$_instances` is not a Craft convention — private properties use underscore prefix but meaningful names like `$_items`, `$_sections`.\n- Records use the **same class name** as models (namespace distinguishes). Alias when importing both: `use ...\\records\\MyEntity as MyEntityRecord;`.\n- Queue jobs have **no \"Job\" suffix** — `ResaveElements`, not `ResaveElementsJob`.\n- `declare(strict_types=1)` is NOT used in plugin source files. Only in standalone config files like `ecs.php`.\n- `@author` goes on classes and methods only — never on properties.\n- Don't use `string|null` — use `?string` (short nullable notation).\n- Forget `parent::defineRules()` and you lose all inherited validation.\n- `DateTimeHelper` in elements/queries, `Carbon` in services — never mix in the same class.\n- Missing `@throws` chains — document exceptions from called methods too, not just your own throws.\n- 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.\n\n## Reference Files\n\nRead the relevant reference file(s) for your task:\n\n| Task | Read |\n|------|------|\n| Writing PHPDocs, `@author`, `@since`, `@throws`, `@var`, `@param`, type references | `references/phpdoc-standards.md` |\n| Class structure, section headers, ordering, enums, control flow, comments, whitespace | `references/class-organization.md` |\n| Naming classes, methods, properties, files, services, events, migrations | `references/naming-conventions.md` |\n| CP Twig templates, form macros, translations, file headers, validation | `references/templates-and-patterns.md` |\n| ECS, PHPStan, scaffolding commands, commit messages | `references/tooling.md` |\n\n## Critical Rules\n\n1. PHPDocs on everything: classes, methods, properties. No exceptions.\n2. `@throws` chains: document every exception including uncaught from called methods.\n3. `@author` and `@since` at the bottom of class/method docblocks, after a blank line.\n4. Section headers with `// =========================================================================` on every class.\n5. `declare(strict_types=1)` is NOT used in plugin source files — Craft's internal type coercion depends on PHP's default weak typing mode.\n6. Private methods/properties prefixed with underscore: `_registerCpUrlRules()`, `$_items`.\n7. `addSelect()` convention in `beforePrepare()` — additive across extensions, prevents column conflicts.\n8. `DateTimeHelper` in elements/queries, `Carbon` in services — separate concerns prevent mixing date APIs in the same class.\n9. Always scaffold with `ddev craft make <type> --with-docblocks`, then customize.\n10. `ddev composer check-cs` and `ddev composer phpstan` must pass before every commit.\n\n## PHP Standards\n\n- Minimum PHP 8.2 (Craft CMS 5 requirement).\n- PSR-12 baseline with Craft modifications (trailing commas, constant visibility).\n- `craftcms/ecs` with `SetList::CRAFT_CMS_4` preset (covers both Craft 4 and 5).\n- Short nullable notation: `?string` not `string|null`.\n- Always specify `void` return types.\n- Typed properties everywhere. No untyped public properties.\n- Strict comparison always: `$foo === null`, `in_array($x, $y, true)`.\n- Casts over functions: `(int)$foo` not `intval($foo)`.\n\n## Section Header Order\n\n```\n// Traits\n// Const Properties\n// Static Properties\n// Public Properties\n// Protected Properties\n// Private Properties\n// Public Methods\n// Protected Methods\n// Private Methods\n```\n\nOnly include sections that have content. Blank line after the separator, before the first item.\n\n## Control Flow\n\n- **Happy path last.** Handle error conditions first with early returns.\n- **Avoid `else`** — use early returns instead.\n- **`match` over `switch`** — always.\n- **Always use curly brackets** even for single statements.\n- **Separate compound conditions** into nested `if` statements for readability.\n- **Named arguments** when calling methods with 3+ parameters.\n\n## Date Handling\n\n- **Elements and element queries**: `craft\\helpers\\DateTimeHelper`.\n- **Services** (date arithmetic): `Carbon\\Carbon`.\n- Never mix both in the same class.\n\n## Database Conventions\n\n- `[[column]]` quoting in Yii2 join conditions.\n- `addSelect()` in `beforePrepare()` — safely additive.\n- `postDate` and `expiryDate` in `addSelect()` and indexed on element tables.\n- `Db::parseParam()` for query parameters. `Db::parseDateParam()` for dates.\n- Foreign keys with explicit `CASCADE` / `SET NULL` behavior.\n\n## Naming Quick-Reference\n\n| Thing | Convention | Example |\n|-------|-----------|---------|\n| Services (resource) | Plural | `Entries`, `Volumes`, `Users` |\n| Services (utility) | Domain noun | `Auth`, `Search`, `Gc` |\n| Queue jobs | Action verb, no suffix | `ResaveElements`, `UpdateSearchIndex` |\n| Records | Same name as model | Namespace distinguishes |\n| Events | Three patterns | `SectionEvent`, `RegisterUrlRulesEvent`, `DefineHtmlEvent` |\n| Element actions | Action verb, no suffix | `Delete`, `Duplicate`, `SetStatus` |\n| Enums | PascalCase cases, string/int backed | `PropagationMethod`, `CmsEdition` |\n\nFor the complete naming reference including file structure conventions, read `references/naming-conventions.md`.\n\n## Verification Checklist\n\nBefore every commit:\n\n1. `ddev composer check-cs` passes\n2. `ddev composer phpstan` passes\n3. Tests green\n4. PHPDocs complete on all new/modified code\n5. `@throws` chains verified\n6. Section headers present and correct\n7. Imports alphabetical and grouped","tags":["craft","php","guidelines","craftcms","claude","skills","michtio","agent-skills","claude-code","claude-code-plugin","claude-code-skills","claude-skills"],"capabilities":["skill","source-michtio","skill-craft-php-guidelines","topic-agent-skills","topic-claude-code","topic-claude-code-plugin","topic-claude-code-skills","topic-claude-skills","topic-content-modeling","topic-craft-cms","topic-craft-cms-5","topic-craftcms","topic-ddev","topic-php","topic-twig"],"categories":["craftcms-claude-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/michtio/craftcms-claude-skills/craft-php-guidelines","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add michtio/craftcms-claude-skills","source_repo":"https://github.com/michtio/craftcms-claude-skills","install_from":"skills.sh"}},"qualityScore":"0.469","qualityRationale":"deterministic score 0.47 from registry signals: · indexed on github topic:agent-skills · 39 github stars · SKILL.md body (6,785 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-01T18:56:48.611Z","embedding":null,"createdAt":"2026-04-18T22:19:33.904Z","updatedAt":"2026-05-01T18:56:48.611Z","lastSeenAt":"2026-05-01T18:56:48.611Z","tsv":"'-12':552 '/api/v5/':155 '/docs/5.x/extend/coding-guidelines.html':150 '/docs/5.x/extend/generator.html':160 '1':100,238,413,458,827 '10':527 '2':422,834 '3':433,691,839 '4':447,566,571,842 '5':7,54,66,454,549,573,849 '6':479,853 '7':487,859 '8':498 '8.2':546 '9':515 'access':311,332 'across':493 'action':776,796,797 'addit':184,492,726 'addselect':177,488,722,731 'alia':217 'alphabet':861 'also':37 'alway':13,111,340,516,581,595,667,668 'analysi':339 'answer':174 'api':510 'app':314,322 'architectur':115 'argument':686 'arithmet':704 'array':599 'auth':771 'author':253,366,434 'authorit':173 'avoid':658 'b':51 'back':808 'baselin':553 'beforeprepar':182,491,724 'behavior':753 'blank':445,637 'bottom':439 'bracket':671 'call':300,329,431,688 'carbon':285,502,705,706 'cascad':750 'case':806 'cast':603 'chain':296,424,851 'check':531,831 'check-c':530,830 'checklist':823 'class':88,151,211,256,293,374,386,417,453,514,713 'class/method':441 'cms':6,29,53,65,548,565 'cmsedit':810 'code':9,59,76,146,168,848 'coercion':470 'column':189,496,716 'comma':558 'command':132,407 'comment':382 'commit':408,541,826 'common':175 'companion':109 'comparison':594 'complet':57,813,844 'compon':346 'compos':529,535,829,836 'compound':677 'concern':506 'condit':653,678,721 'config':249 'conflict':497 'const':615 'constant':559 'content':636 'contribut':188 'control':119,380,646 'convent':12,62,82,108,165,180,195,489,715,759,819 'core':83,107 'correct':858 'cover':49,568 'cp':394 'craft':2,5,28,47,52,64,73,106,125,194,348,466,520,547,555,564,570,699 'craft-php-guidelin':1 'craftcm':114 'craftcms.com':149,159 'craftcms.com/docs/5.x/extend/coding-guidelines.html':148 'craftcms.com/docs/5.x/extend/generator.html':158 'craftcms/ecs':561 'critic':411 'cs':532,832 'cur':670 'custom':526 'databas':714 'date':509,693,703,745 'datetimehelp':282,499,701 'db':737,742 'ddev':46,130,135,519,528,534,828,835 'declar':97,235,455 'default':475 'definehtmlev':794 'definerul':275 'delet':801 'depend':471 'develop':70,129 'discuss':22 'distinguish':216,788 'docblock':442,524 'docs.craftcms.com':154 'docs.craftcms.com/api/v5/':153 'document':144,297,425 'domain':769 'duplic':802 'earli':656,661 'ec':41,139,404 'ecs.php':252 'edit':19,36 'element':117,695,697,735,795 'elements/queries':284,501 'els':659 'entri':764 'enum':379,804 'error':652 'even':33,672 'event':120,391,789 'everi':426,452,540,825 'everyth':87,416 'everywher':588 'exampl':760 'except':298,421,427 'expiryd':729 'explicit':318,342,749 'extend':72 'extens':187,494 'fail':337 'file':25,104,245,250,352,357,389,400,465,817 'first':644,654 'flow':381,647 'foo':596,607,610 'foreign':746 'forget':273 'form':397 'function':605 'gc':773 'generat':156 'get':328 'getset':321 'getter':319,343 'getview':323 'goe':254 'green':841 'group':863 'guidelin':4,56,77,147,169 'handl':651,694 'happi':648 'header':377,401,449,612,855 'helper':700 'hint':95 'import':219,860 'includ':428,632,816 'index':733 'inherit':280 'instanc':190 'instead':316,663 'int':606 'intern':468 'intval':609 'item':205,486,645 'job':227,230,775 'join':720 'key':747 'last':650 'lifecycl':118 'like':204,251 'line':446,638 'load':14,38,112 'lose':278 'macro':398 'magic':309,331 'make':48,521 'match':105,664 'meaning':202 'messag':409 'method':89,258,301,387,418,432,626,628,630,689 'methods/properties':481 'migrat':121,392 'minimum':544 'miss':294 'mix':289,508,708 'mode':478 'model':214,786 'modif':556 'modul':32,69,128 'multipl':186 'must':537 'myentiti':223 'myentityrecord':225 'name':203,212,385,685,754,784,814 'namespac':215,787 'nest':680 'never':260,288,707 'new/modified':847 'notat':272,576 'noun':770 'null':267,580,597,752 'nullabl':271,575 'offici':75,145 'order':378,613 'page':170 'param':370 'paramet':692,741 'parent':274 'parsedateparam':743 'parseparam':738 'pascalcas':805 'pass':333,538,833,838 'path':649 'pattern':116,791 'php':3,8,24,55,58,473,542,545 'phpdoc':50,85,365,414,843 'phpstan':42,140,324,405,536,837 'pitfal':176 'plugin':30,67,102,126,243,312,320,349,463 'plural':763 'postdat':727 'prefix':200,482 'present':856 'preset':567 'prevent':495,507 'principl':84 'privat':196,480,623,629 'project':80 'project-specif':79 'propagationmethod':809 'properti':91,197,262,310,350,388,419,587,592,616,618,620,622,624 'protect':621,627 'psr':551 'public':591,619,625 'queri':698,740 'queue':226,774 'quick':756 'quick-refer':755 'quot':717 'read':353,363,820 'readabl':684 'record':207,222,782 'refer':152,157,351,356,372,757,815 'references/class-organization.md':384 'references/naming-conventions.md':393,821 'references/phpdoc-standards.md':373 'references/templates-and-patterns.md':403 'references/tooling.md':410 'regardless':92 'registercpurlrul':485 'registerurlrulesev':793 'relev':355 'requir':122,136,550 'resaveel':232,780 'resaveelementsjob':234 'resolv':327 'resourc':762 'return':584,657,662 'review':20 'rule':412 'run':40,133,138 'runtim':335 'safe':183,725 'scaffold':44,141,406,517 'search':772 'section':206,376,448,611,633,854 'sectionev':792 'separ':505,641,676 'servic':287,390,504,702,761,767 'set':313,751 'setlist':563 'setstatus':803 'short':270,574 'sinc':367,436 'singl':674 'skill':16,110 'skill-craft-php-guidelines' 'small':35 'sourc':103,244,464 'source-michtio' 'specif':81 'specifi':582 'standalon':248 'standard':10,60,543 'statement':675,682 'static':338,617 'strict':98,236,456,593 'string':266,269,577,579 'string/int':807 'structur':375,818 'suffix':231,779,800 'switch':666 'tabl':736 'task':361,362 'templat':396 'test':143,840 'thing':758 'three':790 'throw':295,307,368,423,850 'togeth':113 'topic-agent-skills' 'topic-claude-code' 'topic-claude-code-plugin' 'topic-claude-code-skills' 'topic-claude-skills' 'topic-content-modeling' 'topic-craft-cms' 'topic-craft-cms-5' 'topic-craftcms' 'topic-ddev' 'topic-php' 'topic-twig' 'trail':557 'trait':614 'translat':399 'true':602 'twig':395 'type':94,99,237,371,457,469,477,585,586 'uncaught':429 'underscor':199,484 'unsur':162 'untyp':590 'updatesearchindex':781 'use':198,208,221,241,265,268,308,341,461,660,669 'user':766 'util':768 'valid':281,402 'var':369 'verb':777,798 'verif':822 'verifi':852 'view':315 'visibl':560 'void':583 'volum':765 'weak':476 'webfetch':166 'whitespac':383 'with-docblock':522 'write':18,364 'x':600 'y':601 'yii2':345,719","prices":[{"id":"a884f8e0-58fa-4118-a565-957f2e37cb77","listingId":"768472b2-94c5-4b59-921f-e74f3bb46efd","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"michtio","category":"craftcms-claude-skills","install_from":"skills.sh"},"createdAt":"2026-04-18T22:19:33.904Z"}],"sources":[{"listingId":"768472b2-94c5-4b59-921f-e74f3bb46efd","source":"github","sourceId":"michtio/craftcms-claude-skills/craft-php-guidelines","sourceUrl":"https://github.com/michtio/craftcms-claude-skills/tree/main/skills/craft-php-guidelines","isPrimary":false,"firstSeenAt":"2026-04-18T22:19:33.904Z","lastSeenAt":"2026-05-01T18:56:48.611Z"}],"details":{"listingId":"768472b2-94c5-4b59-921f-e74f3bb46efd","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"michtio","slug":"craft-php-guidelines","github":{"repo":"michtio/craftcms-claude-skills","stars":39,"topics":["agent-skills","claude-code","claude-code-plugin","claude-code-skills","claude-skills","content-modeling","craft-cms","craft-cms-5","craftcms","ddev","php","twig"],"license":"mit","html_url":"https://github.com/michtio/craftcms-claude-skills","pushed_at":"2026-04-30T21:00:38Z","description":"Production-ready Claude Code skills, agents, and project templates for Craft CMS 5 development","skill_md_sha":"f4a599b9bcc5899ad8715dc6bbbe62b62977d917","skill_md_path":"skills/craft-php-guidelines/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/michtio/craftcms-claude-skills/tree/main/skills/craft-php-guidelines"},"layout":"multi","source":"github","category":"craftcms-claude-skills","frontmatter":{"name":"craft-php-guidelines","description":"Craft CMS 5 PHP coding standards and conventions. ALWAYS load this skill when writing, editing, reviewing, or discussing any PHP file in a Craft CMS plugin or module — even for small edits. Also load when running ECS, PHPStan, or scaffolding with ddev craft make. Covers: PHPDoc blocks (@author, @since, @throws chains, documenting exceptions), section headers (=========), class organization, naming conventions (services, queue jobs, records, events, enums), defineRules() and validation, beforePrepare() and addSelect(), MemoizableArray, DateTimeHelper vs Carbon, strict_types and declare(strict_types=1) usage, short nullable notation (?string), typed properties, void return types, control flow patterns (early returns, match over switch), CP Twig template conventions, form macros, translations (Craft::t), ECS/PHPStan configuration, scaffolding commands, and the verification checklist. Triggers on: writing service classes, models, controllers, elements, element queries, records, queue jobs, migrations, or any PHP class in a Craft CMS context. Also triggers on PHP code review, refactoring, or style questions for Craft plugins and modules. NOT for front-end Twig templates (use craft-twig-guidelines), template architecture (use craft-site), or CP JavaScript/Garnish (use craft-garnish). If you are touching PHP code in a Craft CMS context, you need this skill."},"skills_sh_url":"https://skills.sh/michtio/craftcms-claude-skills/craft-php-guidelines"},"updatedAt":"2026-05-01T18:56:48.611Z"}}