Asynchronous/Concurrent Patterns in PHP
A typical PHP script runs from start to finish within a single request, then the process ends. Sending a welcome email, generating a large PDF report, or resizing an uploaded image can take seconds — far too slow to make a visitor wait for, and risky to do directly inside the request/response cycle at all.
PHP's Request Model — Why This Matters
Node.js / long-running processes
- One process stays alive, handling many requests over time
- Genuine in-process async/await is natural
Traditional PHP (with most web servers)
- Each request typically starts a fresh process/script execution
- No natural way to "keep working after the response is sent" reliably
This is exactly why PHP relies on a separate mechanism — a job queue, backed by a process that runs independently of any web request — rather than genuine in-request asynchronous code, for anything that needs to happen "in the background."
The Queue Pattern
Instead of doing slow work directly, the request "pushes" a description of the job (what needs doing, with what data) onto a queue — typically a database table or Redis — and responds to the visitor right away. A completely separate, long-running worker process continuously checks the queue and processes jobs as they appear.
A Minimal Database-Backed Queue
The Worker — A Long-Running Separate Script
The worker runs entirely outside the web server — typically started once and kept alive indefinitely by a process supervisor — repeatedly polling for new jobs. This is a fundamentally different execution model from a normal web request, which is exactly why it needs to be designed and run separately.
'failed' (rather than leaving it stuck as 'pending' forever, or deleting it) preserves a record of what went wrong and allows a retry strategy to be built — exactly the kind of deliberate error handling from Intermediate Chapter 3, applied to a background process instead of a request.
Why Not Just Use a Separate Thread?
PHP doesn't have built-in multi-threading the way some other languages do (true threads sharing memory within one process). The queue pattern sidesteps this entirely — instead of threads sharing memory, completely separate PHP processes communicate only through the queue (a database row, or a message broker like Redis), which is simpler to reason about and naturally survives a worker crashing or restarting.
Real-World Tooling
This chapter's example is intentionally minimal to show the underlying mechanism clearly. Real projects typically use a dedicated queue library — Laravel's built-in Queue system (Chapter 6), or standalone packages — which add retry logic, delayed jobs, and proper worker process management on top of the same basic pattern demonstrated here.
Coding Challenges
Design (in writing) the SQL schema for a "jobs" table suitable for the queue pattern shown in this chapter — list each column, its type, and a one-sentence reason it's needed. Include at minimum: an id, a job type, a payload, a status, and a created timestamp.
📄 View solutionWrite a queueJob(PDO $pdo, string $type, array $payload) function that inserts a new pending job with a JSON-encoded payload, generalising the chapter's queueEmail() to handle any job type. Then show how it would be called for two different job types: 'send_email' and 'resize_image'.
📄 View solutionExtend the worker loop from this chapter to dispatch to a different function depending on the job's "type" column (using match()), supporting both 'send_email' and 'resize_image' job types, each with its own try/catch and markJobComplete/markJobFailed calls. Explain in a comment what would happen if one job type's handler threw an uncaught exception, with no try/catch around the dispatch at all.
📄 View solutionChapter 8 Quick Reference
- PHP's request model — typically one script run per request, no natural in-process background work
- Queue pattern — push a job description, respond immediately, a separate worker processes it later
- Jobs table / Redis — common backing stores for a queue
- Worker — a long-running CLI script, kept alive by a process supervisor, polling for pending jobs
- Failed jobs — mark deliberately, don't silently drop; enables retry strategies
- No native threads — separate processes + a shared queue, rather than shared-memory threading
- Real projects use dedicated tooling (Laravel Queues, etc.) — this chapter shows the underlying mechanism
- Next chapter: capstone — a small REST API with auth, database, tests