PHP coding standards and WordPress patterns for ATmosphere plugin. Use when writing PHP code, creating classes, implementing WordPress hooks, or structuring plugin files.
Installs into .claude/skills of the current project.
Are you the author of Code Style?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/automattic-code-style)
---
name: code-style
description: PHP coding standards and WordPress patterns for ATmosphere plugin. Use when writing PHP code, creating classes, implementing WordPress hooks, or structuring plugin files.
---
# ATmosphere PHP Conventions
Quick-reference for everyday work. Full reference: [`docs/php-coding-standards.md`](../../../docs/php-coding-standards.md) and [`docs/php-class-structure.md`](../../../docs/php-class-structure.md).
## Non-Negotiables
- **Text domain:** always `'atmosphere'`.
- **Tabs for indentation**, spaces inside parentheses, `array()` (not `[]`).
- **Backslash-prefix** WordPress and PHP global functions in namespaced code: `\get_option()`, `\add_action()`, `\apply_filters()`, `\strlen()`.
- **Use** imports for cross-namespace references — never inline `\Atmosphere\OAuth\Client`.
- **Yoda conditions** for value-vs-variable comparisons: `if ( 'value' === $variable )`.
- **`unreleased`** for `@since` / `@deprecated` tags on new code — the release script rewrites them.
- **Never call `\error_log()` directly** — route every log line through `Atmosphere\debug_log()`, which gates on `WP_DEBUG`, adds the `[atmosphere]` prefix, and collapses CRLF. Pass the message without the prefix.
## File and Class Layout
```
includes/
├── class-*.php # Atmosphere, API, Publisher, Backfill, Handle, Post_Types, Reaction_Sync, Autoloader.
├── functions.php
├── content-parser/ # Registry, base, and parsers for site.standard.document content.
├── oauth/ # Client, DPoP, Encryption, Nonce_Storage, Resolver.
├── transformer/ # Post, Document, Publication, Comment, Facet, TID (extend Base).
└── wp-admin/ # Admin UI.
integrations/ # Plugin-specific content-parser integrations.
```
After adding or renaming a class file under the `Atmosphere` namespace, no Composer autoload step is needed; runtime loading uses `includes/class-autoloader.php`.
## Transformer Pattern
```php
namespace Atmosphere\Transformer;
class Custom extends Base {
public function transform(): array {
// Build the AT Protocol record array.
}
public function get_collection(): string {
return 'app.custom.collection';
}
public function get_rkey(): string {
// Reserve or return the TID; persist to META_TID so it survives retries.
}
}
```
Always reserve the rkey via meta in `get_rkey()` — that meta key is the marker `Publisher::update_post()` uses to distinguish "never published" from "publish attempt failed mid-flight."
## Hook Quick-Reference
**Transform filters:** `atmosphere_transform_bsky_post`, `atmosphere_transform_comment`, `atmosphere_transform_document`, `atmosphere_transform_publication`, `atmosphere_transform_threadgate`.
**Records:** `atmosphere_record_tags` (tag/keyword list for both record types; runs before the 8-tag cap, so prefer it over the record-level transform filters for tag changes).
**Content / composition:** `atmosphere_content_parser` (deprecated; use `Content_Parser\Registry::register()`), `atmosphere_document_content`, `atmosphere_long_form_composition`, `atmosphere_teaser_thread_posts`.
**Gating:** `atmosphere_syncable_post_types`, `atmosphere_should_publish_comment`, `atmosphere_should_publish_bluesky_post` (return `false` for document-only publishing — no `app.bsky.feed.post` companion; pure filter, forward-only), `atmosphere_should_sync_reply`, `atmosphere_backfill_query_chunk_size`, `atmosphere_oauth_redirect_uri`, `atmosphere_client_metadata`, `atmosphere_publish_retry_delays` (backoff ladder for failed publish/update cron workers; length = retry budget; empty array disables retries).
**Actions:** `atmosphere_publishing`, `atmosphere_publish_post_result`, `atmosphere_publish_comment_result`, `atmosphere_update_skipped_unsynced_post`, `atmosphere_long_form_strategy_downgraded`, `atmosphere_reaction_synced`. `atmosphere_publishing` receives the current `WP_Post` and is not a request-wide guard.
**Logging:** `atmosphere_debug_log` — `(bool $enabled, string $message)`; defaults to the `WP_DEBUG` state, lets operators opt log lines in/out independently of `WP_DEBUG`.
**Test-only:** `atmosphere_pre_apply_writes` / `atmosphere_pre_get_record` — Publisher fixture uses these to short-circuit `apply_writes` / `get_record` before the HTTP layer.
Full signatures and docblocks: [`docs/php-coding-standards.md → Hook Patterns`](../../../docs/php-coding-standards.md#hook-patterns).
## Post Visibility and Federation Cleanup
Federation output is remote, site-wide state. Treat a post as publishable only when it is `publish`, its post type is supported, and `post_password` is empty. Do not use `post_password_required()` for AT Protocol records: it depends on the current visitor's unlock cookie and can leak protected content into PDS records.
When a previously-published post leaves public visibility (draft, pending, private, trash, custom non-public status, password applied, or post type support removed), delete remote records rather than updating them with redacted content. Status transitions may queue the normal delete event, but stale publish/update cron handlers must re-check visibility at fire time and call `Publisher::delete_post( $post )` directly when local record metadata exists.
## Cron Lifecycle — Three-Way Symmetry
Every plugin-owned `wp_schedule_*` hook MUST appear in `Atmosphere\get_cron_hooks()` (`includes/functions.php`). That list drives:
- `Atmosphere\deactivate()` (`atmosphere.php`)
- `Atmosphere\OAuth\Client::disconnect()`
- `uninstall.php`
When adding a new cron hook:
1. Add it to `get_cron_hooks()` — never duplicate the literal in deactivate / disconnect / uninstall.
2. If the handler issues PDS writes without re-checking `is_connected()`, the symmetry is load-bearing: a queued event from a previous connection would otherwise fire against a different repo on reconnect.
3. If the handler stores or sweeps post/comment meta keys, mirror those keys in `uninstall.php`.
This pattern was extracted in PR #32 (review by @kraftbj). Full rationale: [`docs/php-coding-standards.md → Cron-Specific Rules`](../../../docs/php-coding-standards.md#cron-specific-rules).
## Cron Handler Errors — Never Swallow `WP_Error`
Cron handlers in `register_async_hooks()` MUST surface `Publisher::*` errors via `debug_log()` — typically through `log_cron_error()`. `wp_schedule_single_event` does not retry, so a silent drop loses the only signal operators have for transient PDS failures, expired refresh tokens, or DPoP nonce drift. `debug_log()` gates the line behind `WP_DEBUG` (operators can opt in on production via the `atmosphere_debug_log` filter).
When the handler operates on records the caller has already lost local state for (e.g. `atmosphere_delete_comment_record` after the WP comment row is gone), include the TID/identifier in the log line so the orphan is recoverable manually.
## Inflight-State Races
When a cron handler writes meta both *before* an `apply_writes` call (e.g. `Comment::get_rkey()` persists `META_TID`) and *after* (e.g. `store_comment_result()` writes `META_URI`), and a concurrent state change can short-circuit the cleanup gates that key off the *post-call* meta, the handler MUST re-check eligibility after the call returns and roll back if needed.
Concrete pattern: `atmosphere_publish_comment` → `reconcile_comment_after_publish()`. Re-fetch the WP object, re-run the eligibility gate, schedule the orphan-cleanup cron (not direct delete) so transient PDS failures retry through the standard channel.
## When to Read the Full Docs
- **Naming conventions** (classes / methods / files / hooks) — [`docs/php-coding-standards.md → Naming Conventions`](../../../docs/php-coding-standards.md#naming-conventions).
- **Security** (escaping, sanitization, nonces, capability checks) — [`docs/php-coding-standards.md → Security Practices`](../../../docs/php-coding-standards.md#security-practices).
- **Error handling** (returning, checking, aggregating `WP_Error`) — [`docs/php-coding-standards.md → Error Handling`](../../../docs/php-coding-standards.md#error-handling).
- **Performance** (caching, capped collections, batching) — [`docs/php-coding-standards.md → Performance Considerations`](../../../docs/php-coding-standards.md#performance-considerations).
- **Documentation standards** (DocBlock format, `@since` policy, inline comment style) — [`docs/php-coding-standards.md → Documentation Standards`](../../../docs/php-coding-standards.md#documentation-standards).
- **Class structure** (where new classes go, namespace hierarchy, integration patterns) — [`docs/php-class-structure.md`](../../../docs/php-class-structure.md).