table = \ABeCe\Booking\Infrastructure\Database\Tables::name( $wpdb, $suffix ); } /** @return array{items: list, total: int} */ public function list( int $page, int $per_page, array $filters = [] ): array { $wpdb = $this->wpdb; [ $where, $values ] = $this->listWhere( $filters ); $items = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM %i' . $where . ' ORDER BY ' . $this->orderBy . ' LIMIT %d OFFSET %d', $this->table, ...array_merge( $values, [ $per_page, ( $page - 1 ) * $per_page ] ) ), ARRAY_A ); $this->checkRead(); $total = $wpdb->get_var( $wpdb->prepare( 'SELECT COUNT(*) FROM %i' . $where, $this->table, ...$values ) ); $this->checkRead(); return [ 'items' => array_map( [ $this, 'serialize' ], $items ), 'total' => (int) $total ]; } public function find( int $id ): ?array { return BookingReadScope::remember($this->wpdb,$this->table.':find:'.$id,function()use($id){ $wpdb = $this->wpdb; $row = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE id = %d', $this->table, $id ), ARRAY_A ); $this->checkRead(); return $row ? $this->serialize( $row ) : null; }); } public function findBySlug( string $slug ): ?array { $wpdb = $this->wpdb; $row = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM %i WHERE slug = %s', $this->table, $slug ), ARRAY_A ); $this->checkRead(); return $row ? $this->serialize( $row ) : null; } public function save( array $data, ?int $id ): array { BookingReadScope::invalidate(); $data['updated_at'] = current_time( 'mysql', true ); if ( null === $id ) { $data['created_at'] = $data['updated_at']; } $previous = $this->wpdb->suppress_errors( true ); try { $result = null === $id ? $this->wpdb->insert( $this->table, $data ) : $this->wpdb->update( $this->table, $data, [ 'id' => $id ], null, [ '%d' ] ); if ( false === $result ) { if ( str_contains( $this->wpdb->last_error, 'Duplicate entry' ) ) { throw $this->duplicateException(); } throw new CatalogException( 'abece_booking_database_error', __( 'The item could not be saved. Please try again.', 'abece-booking' ), 500 ); } $id ??= (int) $this->wpdb->insert_id; } finally { $this->wpdb->suppress_errors( $previous ); } return $this->find( $id ) ?? throw new CatalogException( 'abece_booking_not_found', __( 'The item was not found.', 'abece-booking' ), 404 ); } protected function serialize( array $row ): array { foreach ( $row as $key => $value ) { if ( str_starts_with( $key, 'is_' ) ) { $row[ $key ] = (bool) $value; } elseif ( in_array( $key, [ 'id', 'sport_id', 'capacity', 'sort_order', 'member_booking_days', 'guest_booking_days', 'public_release_hours', 'min_booking_minutes', 'max_booking_minutes', 'booking_step_minutes', 'wp_user_id', 'member_id', 'season_id', 'membership_type_id', 'membership_fee_amount', 'resource_id', 'day_of_week', 'sunrise_offset_minutes', 'sunset_offset_minutes', 'created_by_user_id' ], true ) ) { $row[ $key ] = null === $value ? null : (int) $value; } } return $row; } public function delete( int $id ): void { BookingReadScope::invalidate(); if ( false === $this->wpdb->delete( $this->table, [ 'id' => $id ], [ '%d' ] ) ) { throw new CatalogException( 'abece_booking_database_error', __( 'The item could not be deleted. Please try again.', 'abece-booking' ), 500 ); } } protected function listWhere( array $filters ): array { return [ '', [] ]; } protected function duplicateException(): CatalogException { return new CatalogException( 'abece_booking_duplicate_slug', __( 'This slug is already in use.', 'abece-booking' ), 409, 'slug' ); } protected function checkRead(): void { if ( $this->wpdb->last_error ) { throw new CatalogException( 'abece_booking_database_error', __( 'The items could not be loaded. Please try again.', 'abece-booking' ), 500 ); } } }