Skip to content

feat: peek to pick one item without dequeing#1

Open
slowmove wants to merge 1 commit intomainfrom
feat/peek-to-pick-one-item-without-dequeing
Open

feat: peek to pick one item without dequeing#1
slowmove wants to merge 1 commit intomainfrom
feat/peek-to-pick-one-item-without-dequeing

Conversation

@slowmove
Copy link
Owner

No description provided.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a peek() capability to the queue/storage abstraction so callers can inspect the next item to be dequeued without removing it.

Changes:

  • Add peek(): ?string to StorageInterface and expose it via Queue::peek().
  • Implement peek() in File/SQLite/Redis/Beanstalkd storage adapters.
  • Add Pest tests for peek() behavior for FileStorage and SqliteStorage, plus a usage example.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
tests/StorageTest.php Adds coverage for peek() semantics (empty queue, idempotency, FIFO interaction).
src/Storage/StorageInterface.php Extends the storage contract with peek().
src/Storage/Adapters/SqliteStorage.php Implements peek() by selecting the oldest row.
src/Storage/Adapters/RedisStorage.php Implements peek() via LINDEX on the next-to-dequeue element.
src/Storage/Adapters/FileStorage.php Implements peek() by reading the first line under a shared lock.
src/Storage/Adapters/BeanstalkdStorage.php Implements peek() using Pheanstalk’s peekReady().
src/Queue.php Adds Queue::peek() passthrough to the storage adapter.
examples/peek.php Updates example to demonstrate peek().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +91 to +94
beforeEach(function () {
$this->file = tempnam(sys_get_temp_dir(), 'queue_sqlite_') . '.db';
$this->storage = new SqliteStorage($this->file);
});
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same tempnam() issue as above: appending ".db" leaks the original temp file created by tempnam(). Prefer using the returned path directly or explicitly rename+cleanup so the test suite doesn't leave stray temp files.

Copilot uses AI. Check for mistakes.
public function peek(): ?string
{
$data = $this->connection->querySingle("SELECT data FROM queue ORDER BY id ASC LIMIT 1");
return $data ?: null;
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$data ?: null will incorrectly convert valid queue items like the string "0" into null. Use a strict null check (e.g., return null only when querySingle() returns null) to keep peek() consistent with dequeue()’s $data ?? null behavior.

Suggested change
return $data ?: null;
return $data === null ? null : $data;

Copilot uses AI. Check for mistakes.
Comment on lines +56 to +65
public function peek(): ?string
{
try {
$this->beanstalkdClient->useTube($this->tube);
$job = $this->beanstalkdClient->peekReady();
return $job->getData();
} catch (\Throwable $th) {
return null;
}
}
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

peek() uses useTube() + peekReady(), but dequeue() uses watch($this->tube). peekReady() is not tube-scoped in the Beanstalk protocol, so this can return a job that this queue instance would never dequeue. Either implement peek in a tube-consistent way (e.g., watch+reserve+release with care) or document/rename this behavior as a global peek.

Copilot uses AI. Check for mistakes.

describe('FileStorage', function () {
beforeEach(function () {
$this->file = tempnam(sys_get_temp_dir(), 'queue_file_') . '.txt';
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tempnam() already creates a real temp file. Appending an extension creates a second path and leaves the original temp file behind (leaking files in /tmp). Use the path returned by tempnam() directly, or rename the temp file and ensure both the renamed file and the original are cleaned up.

Suggested change
$this->file = tempnam(sys_get_temp_dir(), 'queue_file_') . '.txt';
$this->file = tempnam(sys_get_temp_dir(), 'queue_file_');

Copilot uses AI. Check for mistakes.

public function exist(string $value): bool
{
$result = $this->connection->querySingle("SELECT COUNT(*) FROM queue WHERE data = '$value'");
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SqliteStorage::exist method builds an SQL query by interpolating the untrusted $value directly into the string: "SELECT COUNT(*) FROM queue WHERE data = '$value'". If exist is ever called with attacker-controlled input, an attacker can inject arbitrary SQL (e.g. using a value like 'foo' OR 1=1 --) to read or manipulate data in the same SQLite database. Use parameterized queries with bound parameters (as done in enqueue) rather than string concatenation for the WHERE clause to prevent SQL injection.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants