Conversation
Configure PSR-4 autoload (Apiki\Favorites\), WordPress Coding Standards via PHPCS, and PHP 8.1 compatibility check. Define composer scripts for lint and lint:fix.
Favorite is an immutable readonly VO that carries a single favorite record between layers. FavoriteException is a domain exception that carries the appropriate HTTP status code in getCode(), so the REST controller can map business-rule violations directly to WP_Error responses.
Plugin::boot() is a stateless composition root — no Singleton. WordPress already guarantees the file is loaded once per request, so the equivalent goal is achieved without global state. Activator::activate() uses dbDelta to create the favorites table with a composite UNIQUE (user_id, post_id) constraint that prevents duplicates at the database level.
The repository receives wpdb via constructor and stores it in a readonly property. No method calls 'global $wpdb' — making the class testable and the dependency explicit. Provides typed CRUD operations: add, remove, find, list_for_user, count_for_user. Pre-checks for duplicates raise FavoriteException with HTTP 409; the UNIQUE index is the second line of defense.
Registers three routes: GET /favorites (paginated list),
POST /favorites (create), DELETE /favorites/{post_id} (remove).
HTTP methods reference WP_REST_Server constants (READABLE,
CREATABLE, DELETABLE) instead of raw strings. Authorization
goes through current_user_can('read'), routed via the WordPress
capability system rather than a bare is_user_logged_in() check.
Args validation and sanitization are declared via the route
schema so the WP REST stack rejects malformed input before it
reaches the callbacks.
apiki-favorites.php carries the plugin header, fails fast with an admin notice if PHP < 8.1, requires composer's autoload, and registers the activation hook plus Plugin::boot(). uninstall.php drops the favorites table when the plugin is deleted from the WordPress admin (not on deactivate, so toggling the plugin off does not destroy user data).
Three services:
- wordpress: WP 6.6 with PHP 8.1 on port 8080
- db: MySQL 8 with healthcheck
- cli: composer:2 image for running composer/phpcs without
polluting the host
The plugin folder is mounted directly into wp-content/plugins
so file changes on the host are visible immediately inside
the container.
Documents the three REST endpoints, run instructions via Docker, project structure, the persistence schema with rationale for a dedicated table over user_meta, and per-decision design notes.
Relax phpcs.xml.dist to exclude cosmetic rules that conflict
with modern PSR-12 style PHP 8.1 (tabs-over-spaces, array() over
[], brace placement, alignment, PHPDoc requirements). Keep the
WP rules that matter: security, escaping, nonces, capabilities,
i18n, database prepared statements.
Replace Plugin::TEXT_DOMAIN constant references with the
'apiki-favorites' string literal in i18n calls — WPCS requires
literals for static text-domain detection.
Add targeted phpcs:ignore comments where the sniffer produces
false positives that we deliberately keep:
- Exception messages flagged as 'unescaped output' (they are
log text, not HTML).
- SQL built via wpdb->prepare() into a local variable, which
the NotPrepared sniff cannot trace.
- DROP TABLE in uninstall.php, which is exactly what an
uninstall handler is supposed to do.
WordPress requires this constant set to 'local' (or 'development') to enable Application Passwords on plain HTTP — the standard auth mechanism for REST API calls in development. In production, the WP install runs behind HTTPS and the constant is irrelevant.
1. Harden FavoritesRepository::add() against race conditions. Two concurrent POSTs can both pass the find() null-check, so the second insert() may lose to the UNIQUE constraint. We now detect the duplicate-key error in $wpdb->last_error and re-throw as FavoriteException::already_exists (HTTP 409) instead of letting it bubble as a generic RuntimeException (HTTP 500). The application-level pre-check stays as the convenience path; this is the safety net. 2. Replace 'Seu Nome' placeholder in the plugin header with the real author name, so the WP Admin Plugins page shows a proper credit instead of an unfinished-looking placeholder.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Resumo
Plugin WordPress que permite a usuários logados favoritar e desfavoritar posts via WP REST API, com persistência em tabela própria.
Endpoints
Todos sob
/wp-json/apiki-favorites/v1:GET /favorites?page=1&per_page=10— lista paginada dos favoritos do usuário atual (200)POST /favoritescom body{"post_id": N}— favorita um post (201; 409 se duplicado; 404 se post não existe)DELETE /favorites/{post_id}— remove (204; 404 se não existe)Sem autenticação retorna 401.
Como rodar localmente (Docker)
Sobe os containers com
docker compose up -d. Acessahttp://localhost:8080e finaliza o setup do WordPress. Em Plugins, ativa o "Apiki Favorites". Em Configurações → Links permanentes, escolhe "Nome do post". Cria um Application Password no perfil do usuário e testa comcurlou Postman.Pra rodar o lint WPCS:
docker compose run --rm cli installseguido dedocker compose run --rm cli lint.Test plan (validação manual)
wp_apiki_favoritesPOST /favoritesretorna 201 com{id, user_id, post_id, created_at}GET /favoritesretorna 200 com array de favoritos e headersX-WP-Total/X-WP-TotalPagesPOSTduplicado retorna 409rest_already_favoritedPOSTcom post inexistente retorna 404rest_post_invalidDELETE /favorites/{id}retorna 204 sem bodyrest_forbidden_contextDecisões de design
Documentadas em detalhe no
README.md.