*/ private array $records = array(); /** * Replace cached candidate rows. * * @param list> $rows Product-shaped rows. */ public function replaceAll( array $rows ): int { $records = array(); $nextId = 1; $now = gmdate( ProductRecord::MYSQL_DATETIME_FORMAT ); foreach ( $rows as $row ) { $provider = self::stringField( $row, 'provider' ); $providerItemId = self::stringField( $row, 'provider_item_id' ); $title = self::stringField( $row, 'title' ); $detailUrl = self::stringField( $row, 'detail_url' ); if ( $provider === '' || $providerItemId === '' || $title === '' || $detailUrl === '' ) { continue; } $row['id'] = $nextId++; $row['provider'] = $provider; $row['provider_item_id'] = $providerItemId; $row['canonical_hash'] = self::stringField( $row, 'canonical_hash' ) !== '' ? self::stringField( $row, 'canonical_hash' ) : hash( 'sha256', $provider . ':' . $providerItemId ); $row['title'] = $title; $row['detail_url'] = $detailUrl; $row['snapshot_version'] = isset( $row['snapshot_version'] ) ? (int) $row['snapshot_version'] : 1; $row['sync_state'] = self::stringField( $row, 'sync_state' ) !== '' ? self::stringField( $row, 'sync_state' ) : ProductRecord::DEFAULT_SYNC_STATE; $row['lifecycle_state'] = self::stringField( $row, 'lifecycle_state' ) !== '' ? self::stringField( $row, 'lifecycle_state' ) : ProductRecord::DEFAULT_LIFECYCLE_STATE; $row['created_at'] = self::stringField( $row, 'created_at' ) !== '' ? self::stringField( $row, 'created_at' ) : $now; $row['updated_at'] = self::stringField( $row, 'updated_at' ) !== '' ? self::stringField( $row, 'updated_at' ) : $now; $row['last_synced_at'] = self::stringField( $row, 'last_synced_at' ) !== '' ? self::stringField( $row, 'last_synced_at' ) : $now; $records[] = ProductRecord::fromArray( $row ); } $this->records = $records; return count( $this->records ); } /** * List cached candidate rows. * * @param ProductListQuery $query List query. */ public function findPaginated( ProductListQuery $query ): ProductListResult { $records = array_values( array_filter( $this->records, static function ( ProductRecord $record ) use ( $query ): bool { if ( $query->provider() !== null && $record->provider() !== $query->provider() ) { return false; } if ( $query->search() !== null && stripos( $record->title(), $query->search() ) === false ) { return false; } if ( $query->syncState() !== null && $record->syncState() !== $query->syncState() ) { return false; } if ( $query->lifecycleState() !== null && $record->lifecycleState() !== $query->lifecycleState() ) { return false; } return true; } ) ); usort( $records, static function ( ProductRecord $left, ProductRecord $right ) use ( $query ): int { $value = match ( $query->sortKey() ) { ProductListQuery::SORT_KEY_CREATED_AT => $left->createdAt() <=> $right->createdAt(), ProductListQuery::SORT_KEY_TITLE => strcasecmp( $left->title(), $right->title() ), ProductListQuery::SORT_KEY_LAST_SYNCED_AT => ( $left->lastSyncedAt()?->getTimestamp() ?? 0 ) <=> ( $right->lastSyncedAt()?->getTimestamp() ?? 0 ), default => $left->updatedAt() <=> $right->updatedAt(), }; return $query->sortOrder() === ProductListQuery::SORT_ORDER_ASC ? $value : -$value; } ); $total = count( $records ); $offset = ( $query->page() - 1 ) * $query->perPage(); $items = array_slice( $records, $offset, $query->perPage() ); return new ProductListResult( $items, $total, $query->page(), $query->perPage() ); } /** * Count cached candidate rows. */ public function count(): int { return count( $this->records ); } /** * @param array $row Row. * @param string $key Field key. */ private static function stringField( array $row, string $key ): string { $value = $row[ $key ] ?? ''; return is_scalar( $value ) ? trim( (string) $value ) : ''; } }