*/ public function map( ProductRecord $record, ?ImageHints $imageHints = null ): array { return array( 'id' => $record->id(), 'provider' => $record->provider(), 'provider_item_id' => $record->providerItemId(), 'title' => $record->title(), 'brand' => $record->brand(), 'image_primary_url' => $imageHints !== null ? $imageHints->primaryUrl() : null, 'price' => self::buildPrice( $record ), 'availability_status' => $record->availabilityStatus() ?? 'unknown', 'last_synced_at' => self::formatRfc3339( $record->lastSyncedAt() ), 'sync_state' => $record->syncState(), ); } /** * Build the §6.1 `price` sub-object from a {@see ProductRecord}, * or null when the record carries no price amount. * * The `observed_at` field is sourced from * {@see ProductRecord::lastSyncedAt()} as a Phase-6 proxy; see * the file-level docblock for the rationale. * * @param ProductRecord $record Source record. * * @return array|null */ private static function buildPrice( ProductRecord $record ): ?array { $amount = $record->priceAmount(); if ( $amount === null ) { return null; } return array( 'amount' => $amount, 'currency' => $record->priceCurrency() ?? 'UNKNOWN', 'label' => $record->priceLabel(), 'observed_at' => self::formatRfc3339( $record->lastSyncedAt() ), ); } /** * Format a {@see DateTimeImmutable} as an RFC 3339 / ISO 8601 * UTC string per §2.4 ("原則: UTC で返す"), or null when * `$dt` is null. * * @param DateTimeImmutable|null $dt Timestamp to format, or null. */ private static function formatRfc3339( ?DateTimeImmutable $dt ): ?string { if ( $dt === null ) { return null; } return gmdate( 'Y-m-d\TH:i:s\Z', $dt->getTimestamp() ); } }