Conversation
There was a problem hiding this comment.
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(): ?stringtoStorageInterfaceand expose it viaQueue::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.
| beforeEach(function () { | ||
| $this->file = tempnam(sys_get_temp_dir(), 'queue_sqlite_') . '.db'; | ||
| $this->storage = new SqliteStorage($this->file); | ||
| }); |
There was a problem hiding this comment.
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.
| public function peek(): ?string | ||
| { | ||
| $data = $this->connection->querySingle("SELECT data FROM queue ORDER BY id ASC LIMIT 1"); | ||
| return $data ?: null; |
There was a problem hiding this comment.
$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.
| return $data ?: null; | |
| return $data === null ? null : $data; |
| public function peek(): ?string | ||
| { | ||
| try { | ||
| $this->beanstalkdClient->useTube($this->tube); | ||
| $job = $this->beanstalkdClient->peekReady(); | ||
| return $job->getData(); | ||
| } catch (\Throwable $th) { | ||
| return null; | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
|
|
||
| describe('FileStorage', function () { | ||
| beforeEach(function () { | ||
| $this->file = tempnam(sys_get_temp_dir(), 'queue_file_') . '.txt'; |
There was a problem hiding this comment.
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.
| $this->file = tempnam(sys_get_temp_dir(), 'queue_file_') . '.txt'; | |
| $this->file = tempnam(sys_get_temp_dir(), 'queue_file_'); |
|
|
||
| public function exist(string $value): bool | ||
| { | ||
| $result = $this->connection->querySingle("SELECT COUNT(*) FROM queue WHERE data = '$value'"); |
There was a problem hiding this comment.
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.
No description provided.