db->prepare( 'SELECT * FROM %i WHERE `id` = %d AND `deleted_at` IS NULL LIMIT 1', $this->table( 'customers' ), $id ); $row = $this->db->get_row( $sql, ARRAY_A ); return is_array( $row ) ? $this->hydrate( $row ) : null; } /** * Find by UUID. * * {@inheritDoc} * * @param Uuid $uuid Parameter. * @since 0.1.0 */ public function find_by_uuid( Uuid $uuid ): ?Customer { $sql = $this->db->prepare( 'SELECT * FROM %i WHERE `uuid` = %s AND `deleted_at` IS NULL LIMIT 1', $this->table( 'customers' ), $uuid->to_string() ); $row = $this->db->get_row( $sql, ARRAY_A ); return is_array( $row ) ? $this->hydrate( $row ) : null; } /** * Find by email address. * * {@inheritDoc} * * @param EmailAddress $email Parameter. * @since 0.1.0 */ public function find_by_email( EmailAddress $email ): array { $sql = $this->db->prepare( 'SELECT * FROM %i WHERE `email` = %s AND `deleted_at` IS NULL', $this->table( 'customers' ), $email->to_string() ); $rows = $this->db->get_results( $sql, ARRAY_A ); if ( ! is_array( $rows ) ) { return []; } $out = []; foreach ( $rows as $row ) { $out[] = $this->hydrate( $row ); } return $out; } /** * Paginated list with optional search. * * {@inheritDoc} * * @param int $limit Max rows. * @param int $offset Offset. * @param string|null $search Optional search term. * @return array{items: list, total: int} * * @since 0.1.0 */ public function find_all( int $limit, int $offset, ?string $search = null ): array { $tbl = $this->table( 'customers' ); $where = '`deleted_at` IS NULL'; if ( null !== $search && '' !== trim( $search ) ) { $like = '%' . $this->db->esc_like( trim( $search ) ) . '%'; $where .= $this->db->prepare( ' AND (`first_name` LIKE %s OR `last_name` LIKE %s OR `email` LIKE %s)', $like, $like, $like ); } $count_sql = $this->db->prepare( "SELECT COUNT(*) FROM %i WHERE $where", $tbl ); $total = (int) $this->db->get_var( $count_sql ); $data_sql = $this->db->prepare( "SELECT * FROM %i WHERE $where ORDER BY `id` DESC LIMIT %d OFFSET %d", $tbl, max( 1, min( 100, $limit ) ), max( 0, $offset ) ); $rows = $this->db->get_results( $data_sql, ARRAY_A ); if ( ! is_array( $rows ) ) { return [ 'items' => [], 'total' => 0 ]; } $out = []; foreach ( $rows as $row ) { $out[] = $this->hydrate( $row ); } return [ 'items' => $out, 'total' => $total ]; } /** * Persist the entity. * * {@inheritDoc} * * @param Customer $customer Parameter. * @since 0.1.0 */ public function save( Customer $customer ): void { $data = $this->to_row( $customer ); if ( null === $customer->id() ) { $this->db->insert( $this->table( 'customers' ), $data ); $customer->assign_id( $this->last_insert_id() ); return; } $this->db->update( $this->table( 'customers' ), $data, [ 'id' => $customer->id() ] ); } /** * Soft-delete the entity. * * {@inheritDoc} * * @param Customer $customer Parameter. * @param DateTimeImmutable $when Parameter. * @since 0.1.0 */ public function soft_delete( Customer $customer, DateTimeImmutable $when ): void { $customer->soft_delete( $when ); $this->save( $customer ); } /** * Map a database row to an entity. * * @param array $row Database row. */ private function hydrate( array $row ): Customer { return new Customer( (int) $row['id'], new Uuid( (string) $row['uuid'] ), null !== $row['wp_user_id'] ? (int) $row['wp_user_id'] : null, (string) $row['first_name'], null !== $row['last_name'] ? (string) $row['last_name'] : null, new EmailAddress( (string) $row['email'] ), null !== $row['email_verified_at'] ? $this->parse_datetime( (string) $row['email_verified_at'] ) : null, null !== $row['phone'] && '' !== $row['phone'] ? new PhoneNumber( (string) $row['phone'] ) : null, null !== $row['phone_verified_at'] ? $this->parse_datetime( (string) $row['phone_verified_at'] ) : null, null !== $row['timezone'] && '' !== $row['timezone'] ? new TimeZoneId( (string) $row['timezone'] ) : null, null !== $row['locale'] ? (string) $row['locale'] : null, null !== $row['notes'] ? (string) $row['notes'] : null, (bool) (int) $row['marketing_consent'], null !== $row['consent_recorded_at'] ? $this->parse_datetime( (string) $row['consent_recorded_at'] ) : null, CustomerStatus::from( (string) $row['status'] ), $this->decode_meta( isset( $row['meta'] ) ? (string) $row['meta'] : null ), $this->parse_datetime( (string) $row['created_at'] ), $this->parse_datetime( (string) $row['updated_at'] ), null !== $row['deleted_at'] ? $this->parse_datetime( (string) $row['deleted_at'] ) : null, ); } /** * Map an entity to a database row. * * @param Customer $customer Entity. * @return array */ private function to_row( Customer $customer ): array { return [ 'uuid' => $customer->uuid()->to_string(), 'wp_user_id' => $customer->wp_user_id(), 'first_name' => $customer->first_name(), 'last_name' => $customer->last_name(), 'email' => $customer->email()->to_string(), 'email_verified_at' => null === $customer->email_verified_at() ? null : $this->format_datetime( $customer->email_verified_at() ), 'phone' => null === $customer->phone() ? null : $customer->phone()->to_string(), 'phone_verified_at' => null === $customer->phone_verified_at() ? null : $this->format_datetime( $customer->phone_verified_at() ), 'timezone' => null === $customer->timezone() ? null : $customer->timezone()->to_string(), 'locale' => $customer->locale(), 'notes' => $customer->notes(), 'marketing_consent' => $customer->marketing_consent() ? 1 : 0, 'consent_recorded_at' => null === $customer->consent_recorded_at() ? null : $this->format_datetime( $customer->consent_recorded_at() ), 'status' => $customer->status()->value, 'meta' => $this->encode_meta( $customer->meta() ), 'created_at' => $this->format_datetime( $customer->created_at() ), 'updated_at' => $this->format_datetime( $customer->updated_at() ), 'deleted_at' => null === $customer->deleted_at() ? null : $this->format_datetime( $customer->deleted_at() ), ]; } }