Files
peptide-repo-core/includes/repositories/class-pr-core-peptide-repository.php
peptiderepo c17718b533 fix(repo): drop stale TAX_FAMILY references (v0.2.1 P1 hotfix)
v0.2.0 removed TAX_FAMILY from PR_Core_Peptide_CPT but left two stale
references in PR_Core_Peptide_Repository:
  - find_all() family filter branch (class-pr-core-peptide-repository.php:95-102)
  - post_to_dto() families taxonomy lookup (class-pr-core-peptide-repository.php:138)

JSON-LD emission on single-peptide templates called find_by_id() ->
post_to_dto() which hit the undefined constant and killed every peptide
detail page render mid-wp_head().

Fix:
  - family filter is silently ignored until a future minor bump
    removes it from the REST schema.
  - post_to_dto() hardcodes $families = [] so the DTO still carries
    the field (REST response shape unchanged for existing clients).

Out-of-scope follow-ups:
  - Post-mortem on why the v0.2.0 QA gate missed this (unit tests
    exercised the CPT class in isolation, not the repository's
    consumption of its constants).
  - Add an integration-style test for JSON-LD emission on a single
    peptide so any future CPT-class refactor catches repository drift.
2026-04-22 09:56:10 +00:00

162 lines
5.6 KiB
PHP

<?php
declare(strict_types=1);
/**
* Repository for peptide CPT records.
*
* What: CRUD operations over peptide posts, returning typed DTOs.
* Who calls it: PR_Core (public API), REST controller, admin UI, consumer plugins.
* Dependencies: WordPress WP_Query, PR_Core_Peptide_DTO.
*
* @see dto/class-pr-core-peptide-dto.php — Return type.
* @see cpt/class-pr-core-peptide-cpt.php — CPT and meta registration.
* @see repositories/class-pr-core-dosing-repository.php — Related dosing data.
*/
class PR_Core_Peptide_Repository {
/**
* Find a single peptide by post ID.
*
* @param int $id WordPress post ID.
* @return PR_Core_Peptide_DTO|null Null if not found or wrong post type.
*/
public function find_by_id( int $id ): ?PR_Core_Peptide_DTO {
$post = get_post( $id );
if ( ! $post || PR_Core_Peptide_CPT::POST_TYPE !== $post->post_type ) {
return null;
}
return $this->post_to_dto( $post );
}
/**
* Find a peptide by slug.
*
* @param string $slug Post slug.
* @return PR_Core_Peptide_DTO|null
*/
public function find_by_slug( string $slug ): ?PR_Core_Peptide_DTO {
$posts = get_posts( [
'post_type' => PR_Core_Peptide_CPT::POST_TYPE,
'name' => sanitize_title( $slug ),
'posts_per_page' => 1,
'post_status' => 'any',
] );
return ! empty( $posts ) ? $this->post_to_dto( $posts[0] ) : null;
}
/**
* Search peptides by name or alias.
*
* @param string $query Search term.
* @param int $limit Max results (default 20).
* @return PR_Core_Peptide_DTO[]
*/
public function search( string $query, int $limit = 20 ): array {
$query = sanitize_text_field( $query );
$posts = get_posts( [
'post_type' => PR_Core_Peptide_CPT::POST_TYPE,
's' => $query,
'posts_per_page' => $limit,
'post_status' => 'publish',
] );
return array_map( [ $this, 'post_to_dto' ], $posts );
}
/**
* Find all peptides matching filters.
*
* @param array<string, mixed> $filters Supported: status, category, family,
* evidence_strength, per_page, page.
* @return PR_Core_Peptide_DTO[]
*/
public function find_all( array $filters = [] ): array {
$args = [
'post_type' => PR_Core_Peptide_CPT::POST_TYPE,
'posts_per_page' => (int) ( $filters['per_page'] ?? 100 ),
'paged' => (int) ( $filters['page'] ?? 1 ),
'orderby' => 'title',
'order' => 'ASC',
];
$args['post_status'] = $filters['status'] ?? 'publish';
if ( ! empty( $filters['category'] ) ) {
$args['tax_query'] = [ [
'taxonomy' => PR_Core_Peptide_CPT::TAX_CATEGORY,
'field' => 'slug',
'terms' => sanitize_text_field( $filters['category'] ),
] ];
}
// v0.2.0: `family` filter silently ignored — `pr_peptide_family` taxonomy
// was removed with the CPT consolidation. Callers passing `family` get
// all-category results rather than a fatal. Remove this key from the
// REST schema in a future minor bump.
if ( ! empty( $filters['evidence_strength'] ) ) {
$args['meta_query'] = [ [
'key' => 'evidence_strength',
'value' => sanitize_text_field( $filters['evidence_strength'] ),
] ];
}
$posts = get_posts( $args );
return array_map( [ $this, 'post_to_dto' ], $posts );
}
/**
* Count total peptides matching a status.
*
* @param string $status Post status (default 'publish').
* @return int
*/
public function count( string $status = 'publish' ): int {
$counts = wp_count_posts( PR_Core_Peptide_CPT::POST_TYPE );
return (int) ( $counts->$status ?? 0 );
}
/**
* Convert a WP_Post to a Peptide DTO.
*
* @param \WP_Post $post WordPress post object.
* @return PR_Core_Peptide_DTO
*/
private function post_to_dto( \WP_Post $post ): PR_Core_Peptide_DTO {
$aliases_raw = get_post_meta( $post->ID, 'aliases', true );
$aliases = json_decode( $aliases_raw ?: '[]', true ) ?: [];
$categories = wp_get_post_terms( $post->ID, PR_Core_Peptide_CPT::TAX_CATEGORY, [ 'fields' => 'names' ] );
// v0.2.0: `pr_peptide_family` taxonomy removed. DTO `families` field
// preserved as empty array to keep the REST response shape stable for
// clients; will be dropped in a future minor bump with a release note.
$families = [];
return new PR_Core_Peptide_DTO( [
'id' => $post->ID,
'title' => $post->post_title,
'slug' => $post->post_name,
'content' => $post->post_content,
'excerpt' => $post->post_excerpt,
'status' => $post->post_status,
'display_name' => get_post_meta( $post->ID, 'display_name', true ) ?: '',
'aliases' => $aliases,
'molecular_formula' => get_post_meta( $post->ID, 'molecular_formula', true ) ?: '',
'molecular_weight' => (float) get_post_meta( $post->ID, 'molecular_weight', true ),
'cas_number' => get_post_meta( $post->ID, 'cas_number', true ) ?: '',
'drugbank_id' => get_post_meta( $post->ID, 'drugbank_id', true ) ?: '',
'chembl_id' => get_post_meta( $post->ID, 'chembl_id', true ) ?: '',
'evidence_strength' => get_post_meta( $post->ID, 'evidence_strength', true ) ?: 'preclinical',
'editorial_review_status' => get_post_meta( $post->ID, 'editorial_review_status', true ) ?: 'draft',
'last_editorial_review_at' => get_post_meta( $post->ID, 'last_editorial_review_at', true ) ?: '',
'medical_editor_id' => (int) get_post_meta( $post->ID, 'medical_editor_id', true ),
'categories' => is_array( $categories ) ? $categories : [],
'families' => $families,
] );
}
}