= 1). * @param string $provider Lowercase provider key. * @param string $providerLabel Human-readable provider name. * @param string $providerItemId Provider-side item id. * @param string $title Normalized product title. * @param string|null $brand Brand name, or null. * @param string|null $description Short description, or null. * @param string|null $detailUrl Detail page URL, or null. * @param string|null $affiliateUrl Tracked affiliate URL, or null. * * @throws InvalidArgumentException When any field violates the * per-field rules summarized * above. */ public function __construct( int $id, string $provider, string $providerLabel, string $providerItemId, string $title, ?string $brand, ?string $description, ?string $detailUrl, ?string $affiliateUrl ) { $this->id = self::normalizeId( $id ); $this->provider = self::normalizeRequiredString( 'provider', $provider ); $this->providerLabel = self::normalizeRequiredString( 'providerLabel', $providerLabel ); $this->providerItemId = self::normalizeRequiredString( 'providerItemId', $providerItemId ); $this->title = self::normalizeRequiredString( 'title', $title ); $this->brand = self::normalizeOptionalString( 'brand', $brand ); $this->description = self::normalizeOptionalString( 'description', $description ); $this->detailUrl = self::normalizeOptionalString( 'detailUrl', $detailUrl ); $this->affiliateUrl = self::normalizeOptionalString( 'affiliateUrl', $affiliateUrl ); } /** * AFFILUN-internal numeric product identifier. */ public function id(): int { return $this->id; } /** * Lowercase provider key. */ public function provider(): string { return $this->provider; } /** * Human-readable provider name for display. */ public function providerLabel(): string { return $this->providerLabel; } /** * Provider-side product identifier. */ public function providerItemId(): string { return $this->providerItemId; } /** * §10.1 normalized product title. */ public function title(): string { return $this->title; } /** * §10.2 brand name, or null. */ public function brand(): ?string { return $this->brand; } /** * §10.3 short product description, or null. */ public function description(): ?string { return $this->description; } /** * Canonical detail page URL, or null. */ public function detailUrl(): ?string { return $this->detailUrl; } /** * Tracked / affiliate URL, or null. */ public function affiliateUrl(): ?string { return $this->affiliateUrl; } /** * Report whether this view equals the supplied one. * * Equality is structural: every field must match with strict * comparison. * * @param self $other Other view to compare against. */ public function equals( self $other ): bool { return $this->id === $other->id && $this->provider === $other->provider && $this->providerLabel === $other->providerLabel && $this->providerItemId === $other->providerItemId && $this->title === $other->title && $this->brand === $other->brand && $this->description === $other->description && $this->detailUrl === $other->detailUrl && $this->affiliateUrl === $other->affiliateUrl; } /** * Validate the `id` field at construction. * * @param int $id Raw identifier as supplied by the caller. * * @throws InvalidArgumentException When `$id` is not strictly * positive. */ private static function normalizeId( int $id ): int { if ( $id < 1 ) { $message = sprintf( 'ProductInfoView id must be a strictly positive integer; got %d.', $id ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } return $id; } /** * Validate and normalize a required string field at * construction. * * Trims surrounding whitespace and rejects the result when it * is the empty string. * * @param string $field Field name for the error message. * @param string $value Raw value as supplied by the caller. * * @throws InvalidArgumentException When `$value` is empty * after trimming. */ private static function normalizeRequiredString( string $field, string $value ): string { $trimmed = trim( $value ); if ( $trimmed === '' ) { $message = sprintf( 'ProductInfoView %s must be a non-empty string.', $field ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } return $trimmed; } /** * Validate and normalize a nullable string field that, when * non-null, must carry non-empty content. * * @param string $field Field name for the error message. * @param string|null $value Raw value, or null. * * @throws InvalidArgumentException When `$value` is the empty * string after trimming. */ private static function normalizeOptionalString( string $field, ?string $value ): ?string { if ( $value === null ) { return null; } $trimmed = trim( $value ); if ( $trimmed === '' ) { $message = sprintf( 'ProductInfoView %s must be null or a non-empty string.', $field ); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Developer-facing message, never rendered to HTML. throw new InvalidArgumentException( $message ); } return $trimmed; } }