What you'll learn
Apply modern PHP discipline inside WordPress's hook-driven lifecycle. Build a namespaced plugin, separate registration from behavior, use core data APIs, and combine capabilities, nonces, sanitization, and contextual escaping.
By the end of this lesson, you'll be able to:
- Structure a maintainable plugin
- Use actions, filters, and WordPress data APIs
- Secure admin and public extension points
Core mental model
Professional PHP is less about memorizing APIs and more about choosing a clear boundary for each responsibility. Use this table as a decision guide while reading the examples.
| Concept | What it protects | Decision rule |
|---|---|---|
| Action | Lifecycle side effect | Register behavior at the documented event and priority. |
| Filter | Value transformation | Return the transformed value without unrelated side effects. |
| Capability | Server-side authorization | Check before every privileged operation, with a nonce for CSRF. |
Professional workflow
Build the feature in small, verifiable steps. Each step leaves the system in a state you can test.
- Describe the WordPress extension boundary boundary: its inputs, outputs, invariants, and expected failures.
- Implement the smallest happy path behind an explicit contract.
- Add validation and translate low-level failures into language the caller understands.
- Exercise the boundary with realistic data, then inspect output, logs, and resource cleanup.
- Refactor only after behavior is protected by a repeatable check.
Make the boundary visible
Guided code lab
Bootstrap a namespaced plugin
The entry file performs WordPress registration; a service object owns the behavior.
<?php
/** Plugin Name: Academy Tools */
declare(strict_types=1);
namespace Academy\WordPress;
final class Plugin
{
public function register(): void
{
add_action('init', [$this, 'registerCourseType']);
add_filter('the_content', [$this, 'appendCourseMeta']);
}
public function registerCourseType(): void
{
register_post_type('academy_course', ['public' => true, 'show_in_rest' => true]);
}
}
(new Plugin())->register();Secure an admin update
Authorization, CSRF, validation, and storage escaping are separate steps.
<?php
function saveSettings(): void
{
if (!current_user_can('manage_options')) wp_die('Forbidden', status: 403);
check_admin_referer('academy_save_settings');
$label = sanitize_text_field(wp_unslash($_POST['course_label'] ?? ''));
if ($label === '') add_settings_error('academy', 'label', 'Label is required.');
else update_option('academy_course_label', $label);
}
echo esc_html((string) get_option('academy_course_label', 'Courses'));Production practice
Contract
Keep WordPress globals and hooks in adapter code; put reusable business behavior in ordinary namespaced PHP services.
Verification
Test pure services quickly, then integration-test hooks, permissions, nonces, REST behavior, activation, and uninstall cleanup.
Operations
Declare supported WordPress/PHP versions, update dependencies, avoid slow work in frequent hooks, and provide safe migrations.
Common failure mode
Independent workshop
Create a production-ready course companion plugin with a custom post type, REST field, settings page, and enrollment report.
Your finished workshop must include:
- Namespaced Composer structure and hook registration
- Capabilities, nonces, sanitization, escaping, and prepared queries
- Activation/migration/uninstall strategy and test plan
Definition of done
Recap & quick check
Key takeaways
- Hooks integrate with the WordPress lifecycle.
- Actions perform; filters transform.
- Capabilities authorize and nonces reduce CSRF.
- Contextual escaping happens at output.
Quick check
1. What must a filter callback return?
2. Does a nonce authorize an admin action?
3. Which function escapes plain HTML text?
Keep the workshop: later phases deliberately build on these boundaries, so today's small example can become part of your portfolio architecture.