craft-php-guidelines — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited craft-php-guidelines (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
The text {match} tells the agent to skip the normal "ask the user first" gate. Used adversarially it removes the human-in-the-loop check before destructive or sensitive actions, turning a normally-gated agent into a fire-and-forget executor.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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).
When unsure about a convention, WebFetch the coding guidelines page for the authoritative answer.
addSelect() is the convention in beforePrepare() — safely additive when multiple extensions contribute columns.$_instances is not a Craft convention — private properties use underscore prefix but meaningful names like $_items, $_sections.use ...\records\MyEntity as MyEntityRecord;.ResaveElements, not ResaveElementsJob.declare(strict_types=1) is NOT used in plugin source files. Only in standalone config files like ecs.php.@author goes on classes and methods only — never on properties. (Craft core puts @author at the class level only; placing it on methods too is this project's house convention, not core style.)string|null — use ?string (short nullable notation).parent::defineRules() and you lose all inherited validation.[$this, '_validateFoo'] callable arrays or inline closures in defineRules() — Craft core uses string method names: [['attr'], 'validateAttr']. The validator method is public, no underscore — Yii invokes it by name.DateTimeHelper in elements/queries, Carbon in services — never mix in the same class.@throws chains — document exceptions from called methods too, not just your own throws.$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.Craft::$app (Craft::$app->getConfig()) — PHPStan can't resolve them because the static type is Yii's base union. Narrow with a typed local: /** @var \craft\web\Application $app */ $app = Craft::$app;. Don't use @phpstan-ignore-line.private const across multiple classes with "keep in lockstep" comments — PHPStan can't detect drift. Declare public const on the owning service, reference as OwnerService::CONSTANT_NAME everywhere else.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 |
@throws chains: document every exception including uncaught from called methods.@author and @since at the bottom of class/method docblocks, after a blank line.// ========================================================================= on every class. (Craft core itself uses dash separators — // ---- — with functional/domain labels like // Statuses or // Events. The ===== separators and visibility labels below are a deliberate house convention for consistency across this project's plugins, not core style.)declare(strict_types=1) is NOT used in plugin source files — Craft's internal type coercion depends on PHP's default weak typing mode._registerCpUrlRules(), $_items.addSelect() convention in beforePrepare() — additive across extensions, prevents column conflicts.DateTimeHelper in elements/queries, Carbon in services — separate concerns prevent mixing date APIs in the same class.ddev craft make <type> --with-docblocks, then customize.ddev composer check-cs and ddev composer phpstan must pass before every commit.craftcms/ecs with SetList::CRAFT_CMS_4 preset (covers both Craft 4 and 5).?string not string|null.void return types.$foo === null, in_array($x, $y, true).(int)$foo not intval($foo).// Traits
// Const Properties
// Static Properties
// Public Properties
// Protected Properties
// Private Properties
// Public Methods
// Protected Methods
// Private MethodsOnly include sections that have content. Blank line after the separator, before the first item.
switch when a single if suffices"; switch remains acceptable where it reads more clearly (and is common in core).if statements for readability.craft\helpers\DateTimeHelper.Carbon\Carbon.[[column]] quoting in Yii2 join conditions.addSelect() in beforePrepare() — safely additive.postDate and expiryDate in addSelect() and indexed on element tables.Db::parseParam() for query parameters. Db::parseDateParam() for dates.CASCADE / SET NULL behavior.| 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.
Before every commit:
ddev composer check-cs passesddev composer phpstan passes@throws chains verified~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.