> $extras Selected extras. * @param array $custom_fields Custom fields. * @param int $price_minor Line price minor units. * @param string $currency Currency code. * @param int|null $hold_id Soft-hold booking id. */ public function __construct( private readonly ?int $service_id = null, private readonly ?int $duration_id = null, private readonly ?int $location_id = null, private readonly ?int $agent_id = null, private readonly bool $any_agent = false, private readonly ?DateTimeImmutable $start_utc = null, private readonly ?DateTimeImmutable $end_utc = null, private readonly ?DateTimeImmutable $buffer_start_utc = null, private readonly ?DateTimeImmutable $buffer_end_utc = null, private readonly ?string $timezone = null, private readonly int $attendees = 1, private readonly array $extras = [], private readonly array $custom_fields = [], private readonly int $price_minor = 0, private readonly string $currency = 'GBP', private readonly ?int $hold_id = null, ) {} /** * Empty draft. */ public static function empty(): self { return new self(); } /** * Hydrate from cart-item / draft JSON. * * @param array|null $data Payload. */ public static function from_array( ?array $data ): self { if ( null === $data ) { return self::empty(); } $extras = $data['extras'] ?? []; $fields = $data['custom_fields'] ?? []; return new self( isset( $data['service_id'] ) ? (int) $data['service_id'] : null, isset( $data['duration_id'] ) ? (int) $data['duration_id'] : null, isset( $data['location_id'] ) ? (int) $data['location_id'] : null, isset( $data['agent_id'] ) ? (int) $data['agent_id'] : null, isset( $data['any_agent'] ) && true === $data['any_agent'], self::parse_instant( $data['start_utc'] ?? null ), self::parse_instant( $data['end_utc'] ?? null ), self::parse_instant( $data['buffer_start_utc'] ?? null ), self::parse_instant( $data['buffer_end_utc'] ?? null ), isset( $data['timezone'] ) && is_string( $data['timezone'] ) ? $data['timezone'] : null, isset( $data['attendees'] ) ? max( 1, (int) $data['attendees'] ) : 1, is_array( $extras ) ? array_values( $extras ) : [], is_array( $fields ) ? $fields : [], isset( $data['price_minor'] ) ? (int) $data['price_minor'] : 0, isset( $data['currency'] ) && is_string( $data['currency'] ) ? $data['currency'] : 'GBP', isset( $data['hold_id'] ) ? (int) $data['hold_id'] : null, ); } /** * Service id. */ public function service_id(): ?int { return $this->service_id; } /** * Duration id. */ public function duration_id(): ?int { return $this->duration_id; } /** * Location id. */ public function location_id(): ?int { return $this->location_id; } /** * Agent id. */ public function agent_id(): ?int { return $this->agent_id; } /** * Any-agent preference. */ public function any_agent(): bool { return $this->any_agent; } /** * Start UTC. */ public function start_utc(): ?DateTimeImmutable { return $this->start_utc; } /** * End UTC. */ public function end_utc(): ?DateTimeImmutable { return $this->end_utc; } /** * Buffer start. */ public function buffer_start_utc(): ?DateTimeImmutable { return $this->buffer_start_utc; } /** * Buffer end. */ public function buffer_end_utc(): ?DateTimeImmutable { return $this->buffer_end_utc; } /** * Timezone. */ public function timezone(): ?string { return $this->timezone; } /** * Attendees. */ public function attendees(): int { return $this->attendees; } /** * Extras. * * @return list> */ public function extras(): array { return $this->extras; } /** * Custom fields. * * @return array */ public function custom_fields(): array { return $this->custom_fields; } /** * Price minor units. */ public function price_minor(): int { return $this->price_minor; } /** * Currency. */ public function currency(): string { return $this->currency; } /** * Soft-hold id. */ public function hold_id(): ?int { return $this->hold_id; } /** * With service selection. * * @param int $service_id Service. * @param int|null $duration_id Duration. * @param int $attendees Attendees. * @param int $price_minor Price. * @param string $currency Currency. */ public function with_service( int $service_id, ?int $duration_id, int $attendees, int $price_minor, string $currency ): self { return new self( $service_id, $duration_id, $this->location_id, $this->agent_id, $this->any_agent, $this->start_utc, $this->end_utc, $this->buffer_start_utc, $this->buffer_end_utc, $this->timezone, $attendees, $this->extras, $this->custom_fields, $price_minor, $currency, $this->hold_id, ); } /** * With extras. * * @param list> $extras Extras. * @param int $price_minor Updated price. */ public function with_extras( array $extras, int $price_minor ): self { return new self( $this->service_id, $this->duration_id, $this->location_id, $this->agent_id, $this->any_agent, $this->start_utc, $this->end_utc, $this->buffer_start_utc, $this->buffer_end_utc, $this->timezone, $this->attendees, array_values( $extras ), $this->custom_fields, $price_minor, $this->currency, $this->hold_id, ); } /** * With location. * * @param int|null $location_id Location. */ public function with_location( ?int $location_id ): self { return new self( $this->service_id, $this->duration_id, $location_id, $this->agent_id, $this->any_agent, $this->start_utc, $this->end_utc, $this->buffer_start_utc, $this->buffer_end_utc, $this->timezone, $this->attendees, $this->extras, $this->custom_fields, $this->price_minor, $this->currency, $this->hold_id, ); } /** * With agent. * * @param int|null $agent_id Agent id. * @param bool $any_agent Any preference. */ public function with_agent( ?int $agent_id, bool $any_agent ): self { return new self( $this->service_id, $this->duration_id, $this->location_id, $agent_id, $any_agent, $this->start_utc, $this->end_utc, $this->buffer_start_utc, $this->buffer_end_utc, $this->timezone, $this->attendees, $this->extras, $this->custom_fields, $this->price_minor, $this->currency, $this->hold_id, ); } /** * With datetime. * * @param DateTimeImmutable $start_utc Start. * @param DateTimeImmutable $end_utc End. * @param DateTimeImmutable $buffer_start_utc Buffer start. * @param DateTimeImmutable $buffer_end_utc Buffer end. * @param string $timezone Zone. * @param int|null $hold_id Hold. */ public function with_datetime( DateTimeImmutable $start_utc, DateTimeImmutable $end_utc, DateTimeImmutable $buffer_start_utc, DateTimeImmutable $buffer_end_utc, string $timezone, ?int $hold_id = null ): self { return new self( $this->service_id, $this->duration_id, $this->location_id, $this->agent_id, $this->any_agent, $start_utc, $end_utc, $buffer_start_utc, $buffer_end_utc, $timezone, $this->attendees, $this->extras, $this->custom_fields, $this->price_minor, $this->currency, $hold_id ?? $this->hold_id, ); } /** * Persistable array. * * @return array */ public function to_array(): array { return [ 'service_id' => $this->service_id, 'duration_id' => $this->duration_id, 'location_id' => $this->location_id, 'agent_id' => $this->agent_id, 'any_agent' => $this->any_agent, 'start_utc' => null === $this->start_utc ? null : $this->start_utc->format( 'c' ), 'end_utc' => null === $this->end_utc ? null : $this->end_utc->format( 'c' ), 'buffer_start_utc' => null === $this->buffer_start_utc ? null : $this->buffer_start_utc->format( 'c' ), 'buffer_end_utc' => null === $this->buffer_end_utc ? null : $this->buffer_end_utc->format( 'c' ), 'timezone' => $this->timezone, 'attendees' => $this->attendees, 'extras' => $this->extras, 'custom_fields' => $this->custom_fields, 'price_minor' => $this->price_minor, 'currency' => $this->currency, 'hold_id' => $this->hold_id, ]; } /** * Parse an ISO instant. * * @param mixed $value Value. */ private static function parse_instant( mixed $value ): ?DateTimeImmutable { if ( $value instanceof DateTimeImmutable ) { return $value; } if ( ! is_string( $value ) || '' === $value ) { return null; } try { return new DateTimeImmutable( $value ); } catch ( \Exception ) { return null; } } }