Final Challenge — Solution Task: Add a findBySlug(string $slug): array method to the Post class, used to support pretty URLs like post.php?slug=my-first-post instead of an ID. It should throw PostNotFoundException if no matching post exists, exactly like find(). Then write post.php, which reads $_GET['slug'], calls the new method inside a try/catch, and either displays the post (escaped with htmlspecialchars) or a friendly "Post not found" message. ---- Post.php (findBySlug method added to the existing class) ---- pdo->prepare("SELECT * FROM posts WHERE slug = :slug"); $stmt->execute(['slug' => $slug]); $post = $stmt->fetch(); if (!$post) { throw new PostNotFoundException("No post found with slug '$slug'"); } return $post; } ?> ---- post.php ---- findBySlug($slug); } catch (PostNotFoundException $e) { $article = null; } ?>
= htmlspecialchars($article['body']) ?>
Post not found.
Expected output (for post.php?slug=my-first-post, a genuinely existing post): My First Post Hello, world! Expected output (for post.php?slug=does-not-exist): Post not found. Notes: - findBySlug() mirrors find() almost exactly - same prepared-statement pattern, same PostNotFoundException thrown on a missing row - the only real difference is the WHERE clause matching on "slug" instead of "id". - post.php reads $_GET['slug'] with the ?? '' safe-default pattern from Fundamentals Chapter 8, so a completely missing slug parameter doesn't trigger an undefined-key warning before the try/catch even runs. - Both the post's title and body are escaped with htmlspecialchars() before being echoed, exactly matching index.php's own rule from the chapter - since a post's stored content is, ultimately, still a form of user input being redisplayed. - The try/catch pattern deliberately sets $article to null on a caught PostNotFoundException rather than letting the exception propagate uncaught, which lets the rest of the page render a friendly message instead of crashing with a fatal, unhandled exception.