| 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 and 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
namespace Atmosphere\Transformer;
class Custom extends Base {
public function transform(): array {
}
public function get_collection(): string {
return 'app.custom.collection';
}
public function get_rkey(): string {
}
}
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.
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_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 — Publisher fixture uses this to short-circuit apply_writes before the HTTP layer.
Full signatures and docblocks: 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:
- Add it to
get_cron_hooks() — never duplicate the literal in deactivate / disconnect / uninstall.
- 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.
- 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.
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.
- Security (escaping, sanitization, nonces, capability checks) —
docs/php-coding-standards.md → Security Practices.
- Error handling (returning, checking, aggregating
WP_Error) — docs/php-coding-standards.md → Error Handling.
- Performance (caching, capped collections, batching) —
docs/php-coding-standards.md → Performance Considerations.
- Documentation standards (DocBlock format,
@since policy, inline comment style) — docs/php-coding-standards.md → Documentation Standards.
- Class structure (where new classes go, namespace hierarchy, integration patterns) —
docs/php-class-structure.md.