*/ private const VALID_STATUSES = array( self::STATUS_IN_STOCK, self::STATUS_OUT_OF_STOCK, self::STATUS_LIMITED, self::STATUS_UNKNOWN, ); /** * Whether the availability region should be rendered at all. */ private bool $visible; /** * One of the {@see self::STATUS_*} tokens. */ private string $status; /** * Pre-formatted availability label, or null. */ private ?string $label; /** * Construct an availability view with explicit values for * every field. * * Validation summary: * * - `status` is trimmed and matched against * {@see self::VALID_STATUSES}; out-of-set tokens raise * {@see InvalidArgumentException}. * - `label`, when non-null, is trimmed and rejected when * the trim leaves an empty string — "present but empty" * is treated as a programming error. * * @param bool $visible Whether the region renders. * @param string $status One of {@see self::VALID_STATUSES}. * @param string|null $label Pre-formatted label, or null. * * @throws InvalidArgumentException When any field violates the * per-field rules summarized * above. */ public function __construct( bool $visible, string $status, ?string $label ) { $this->visible = $visible; $this->status = self::normalizeStatus( $status ); $this->label = self::normalizeOptionalString( 'label', $label ); } /** * Construct an availability view with the region suppressed. * * Convenience factory for the §10.6 "初期版では任意表示" hidden * branch so call sites do not have to spell out the canonical * "status defaults to unknown" arguments. Returns a view with * `visible = false`, `status = 'unknown'`, `label = null`. */ public static function hidden(): self { return new self( false, self::STATUS_UNKNOWN, null ); } /** * Closed set of accepted §10.6 status tokens, in declaration * order. * * @return list */ public static function validStatuses(): array { return self::VALID_STATUSES; } /** * Whether the availability region should be rendered at all. */ public function visible(): bool { return $this->visible; } /** * §10.6 status token. */ public function status(): string { return $this->status; } /** * Pre-formatted availability label, or null. */ public function label(): ?string { return $this->label; } /** * 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->visible === $other->visible && $this->status === $other->status && $this->label === $other->label; } /** * Validate and normalize the `status` field at construction. * * The token is trimmed first so a carelessly-decoded payload * is normalized to the same canonical form a freshly- * constructed token would carry. The trimmed value MUST * appear in {@see self::VALID_STATUSES}. * * @param string $status Raw token as supplied by the caller. * * @throws InvalidArgumentException When `$status` is not a * member of the closed set. */ private static function normalizeStatus( string $status ): string { $trimmed = trim( $status ); if ( ! in_array( $trimmed, self::VALID_STATUSES, true ) ) { $message = sprintf( 'AvailabilityView status must be one of [%s]; got "%s".', implode( ', ', self::VALID_STATUSES ), $status ); // 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( 'AvailabilityView %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; } }