When a page fails, at which stage did the request stop? This chapter gives you a diagnostic method based on the real journey.
The complete journey
A request crosses a pipeline, not a magic file.
Every stage receives a meaningful object, performs a limited responsibility, and passes the result onward. This makes the application observable: a status, log, or test can confirm every transition.
Before PHPAML
Understand the protocol the framework organizes.
PHPAML does not replace HTTP: it turns raw messages into objects and stages that are easier to reason about. These five ideas let you understand the rest instead of memorizing a pipeline.
HTTP is a conversation
The browser and server do not directly share memory. They exchange messages. A request describes what the client wants; a response describes what the server decided. Cookies, parameters, content, and useful headers must therefore be transmitted explicitly.
The server does not send only a page. It first sends a status, then headers, then an optional body. JSON APIs, redirects, files, and HTML pages all use this protocol with different content and statuses.
A request has an intention
GET requests a representation without intentionally modifying the resource. POST submits a new action or creates a resource. PATCH changes a portion; DELETE requests removal. Correct methods make routes predictable, testable, and compatible with HTTP tools.
Identical paths do not necessarily identify the same operation. GET /books displays the collection, while POST /books creates a book. The router must consider both path and method.
HTTP is stateless
Two successive requests are independent messages. The server does not automatically know they belong to the same user. A session generally uses a cookie identifier and retrieves corresponding server-side data.
This matters while debugging. A page may work anonymously but fail with an expired session; a missing cookie may trigger a redirect before the controller. Inspect request context rather than only its URL.
Headers carry context
Accept announces the desired format; Content-Type describes the sent body; Authorization carries access proof; Cookie links a session; security headers constrain browser behavior.
They are not cosmetic. JSON with a wrong Content-Type may not decode. Missing Cache-Control may display an old response. A wrong CSP can leave HTML visible while blocking JavaScript interactions.
A response must be coherent
Content, status, and headers must tell the same story. A Book not found page sent as 200 misleads tests, search engines, and caches. JSON sent as text/html complicates clients.
PHPAML gathers these elements in Response so the controller expresses a clear decision. The view produces content; it should not independently decide HTTP status or send late headers.
The browser creates the request
When you enter /books/42, the browser sends an HTTP GET request. It contains a method, path, headers, and possibly cookies. No controller has been selected yet.
Inspect method, URL, parameters, and cookies in network tools.
public/index.php boots PHPAML
The web server directs the request to the single PHP entry point. It loads the runtime and hands the request to the framework. It contains neither business rules nor a manual page list.
When every page fails, check the server, public/index.php, and runtime.
Middleware protects the journey
Before the controller, middleware may open the session, apply security headers, limit traffic, or verify authentication. Each receives the request and either continues or responds immediately.
A 401, 403, 419, or 429 may come from middleware before the controller.
The router selects the action
The router compares GET and /books/42 with routes/webapp.php. /books/{id} matches and provides id=42 to the controller. A route describes the entrance rather than the complete processing.
use App\Controllers\BookController;
Route::get('/books/{id}', [BookController::class, 'show']);A 404 often means the path, method, or parameter did not match.
The controller coordinates
BookController validates the identifier, asks the model for the book, and selects a response. It translates an HTTP intention into an application operation without becoming the database or view.
final class BookController
{
public function show(Request $request): Response
{
$id = (int) $request->route('id');
$book = Book::find($id);
if ($book === null) {
return Response::notFound();
}
return View::render('books/show', ['book' => $book]);
}
}Log validated input and the business result, never secrets.
The model provides the result
The model retrieves the book and applies domain rules. When absent, the controller produces 404. When present, its prepared data moves to presentation.
Test found and missing cases: they should produce 200 and 404.
The response returns to the browser
The view generates content, Response sets status and headers, and outgoing middleware may complete security. The server finally sends bytes to the browser.
Content-Type: text/html; charset=UTF-8Content-Security-Policy: default-src 'self'<h1>The Last Lighthouse</h1>
Verify status, headers, and content; a beautiful page with a wrong status remains incorrect.
Read errors
HTTP status tells the story.
Missing route or resource
Use 404 when the URL is unknown or books/42 does not exist.
Wrong method
The path exists but not for the received method: POST sent to a GET-only route.
Understood but invalid input
The form is readable, but one value violates validation rules.
Internal error
An unexpected exception prevents the normal response. Log details without exposing them in production.
Guided workshop
Add GET /books/{id}.
- Declare the route and its dynamic parameter.
- Create BookController::show and validate id.
- Return 404 when the model finds nothing.
- Render the page with status 200 when the book exists.
- Test /books/42, /books/999, and POST /books/42.
Solution and quiz
Explain the journey in your own words.
The route matches GET /books/{id}. The router extracts 42. The controller validates it, queries Book, and selects either 404 or a view. Response then carries status, headers, and content through outgoing middleware to the server.
Why should POST /books/42 return 405?
Because the path is known, but no POST route accepts it. A 404 would incorrectly claim that the resource or path is unknown.
Can middleware prevent controller execution?
Yes. It may immediately return an authentication, CSRF, or rate-limit response. That is why diagnosis begins before the controller.