Members | Series | Title | Release | Length |
---|---|---|---|---|
Pragmatic PHP |
Invalid Form Submissions
In this video, we tackle the next critical part of the form experience—handling invalid submissions with grace. When a user submits a form with missing or incorrect data, we want to redirect them back, repopulate the form, and show helpful error messages. 💡
Here’s what we build in version v19-redirect-with-errors: - Define the ideal usage: - session()->invalid($errors)->redirect_back(); - Then… we make it real! 💥 - Introduce invalid() and redirect_back() methods in the Session class - Store transient $old and $errors values to survive the redirect and improve UX ✨ - Refactor Session::old() into a more flexible Session::flash() and flash_message() pattern 🔁 - Improve the old($key, $default) helper to escape HTML using htmlspecialchars() for security 🛡️ - Add a validation_message() helper to display specific errors next to form fields - Update the review form: - Loosen validation so comment is optional - Add hidden rating input with default of 0 - Test with extreme values (e.g., rating=10, long name) to confirm validation is triggered - Clean up temporary debug outputs (like var_dump()) and finalize the flow ✅ This update makes your app smarter, friendlier, and much more user-friendly when things go wrong. A polished user experience, even on failure? Now that’s pragmatic. 🚀 |
Thu, Jun 12, 2025 | 18m:5s | |
Pragmatic PHP |
Database Model Abstractions
In this video, we take a big step toward cleaner, more maintainable code by introducing a Model abstraction layer—a pattern that reduces boilerplate and centralizes logic around database operations. This lands in version v20-db-models.
Here’s what gets done: ✅ Create a Models/Photo class - Move the all() and find() logic out of PhotoController and into this model ✅ Create a Models/Review class - Same deal: centralize data logic away from controllers - Introduce a protected static ?string $table property 🧱 Extract shared logic into a Framework/Model base class: - static table() throws by default—models must define their own table - Implement reusable all() and find() methods 🔄 Refactor all() and find() with: - static::query() → creates an instance of the model - get() → used by all(), calls build_query() to prepare the SQL and params - build_query() → generates the SQL from query state 🧠 Reimagine find() usage like this: - static::query()->where('id', '=', $id)->first(); - Then implement: - A where() method that collects conditions into $wheres - Modify build_query() to handle WHERE clauses - A first() method to return the first matching result 🚀 Refactor the PhotoController to use the new model APIs - Add an insert() method on the base Model class for creating new records By the end of this refactor, your models are smart, reusable, and expressive—freeing your controllers from repetitive SQL and focusing them on application logic. Pragmatic PHP is feeling pretty elegant right about now. 👌 |
Tue, Jun 17, 2025 | 26m:22s | |
Pragmatic PHP |
Fluent Validation
In this step, we introduced a flexible and expressive validation system.
🧱 Created the Framework/Validation class and added a validate() helper to globals.php. ✍️ The goal was to write intuitive validation logic like: validate('rating')->integer()->min(0)->max(5)->required(); validate('name')->string()->max(100); validate('comment')->string()->max(1000); 🧪 This fluent API allowed chaining validation rules for each input field, improving both readability and maintainability. 🔧 We fixed the num_stars constraint in the migration SQL to properly enforce the rating range. 🧹 Ran the migration and reseeded the database to apply the changes. 🕵️ Also refined the reviews logic to default the name to “Anonymous” when one isn’t provided. This new validation infrastructure enhances data safety and gives us a consistent, clean way to validate user input throughout the application. |
Thu, Jun 19, 2025 | 17m:11s | |
Pragmatic PHP |
User Registration
🔹 Controller Setup
- Create a new RegistrationController inside Http/, with index() and store() methods - index() displays the registration form - store() temporarily calls dd($_POST) to confirm input 🔹 View - Add the form view at views/registration.index.view.php 🔹 Validation - Write validation rules in fluent style: - $email = validate('email')->email()->required()->unique('users', 'email'); - Build out the validation logic to support this pattern 🔹 User Model - Create a Models/User class - Move relevant DB logic from the controller into the model 🔹 Model Base Enhancements - In the Model base class: - Add $is_count to build_query() - Implement a count() method that supports basic tallying - In the Database class: - Add a count() method to execute counting queries - Add a unique() validation method using model and count 🔹 Data Insertion - Assemble a $data array with validated inputs - Call User::insert($data) to create the new user That’ll wrap up the registration feature and land us at v22-registering-users 🏁 |
Tue, Jun 24, 2025 | 20m:42s | |
Pragmatic PHP |
User Authentication
📁 Controller Setup
- Create a new AuthenticationController inside Http/ with three methods: - login() – shows the login form - authenticate() – handles login logic - logout() – clears user session 📄 View - Add views/login/index.view.php to render the login form 🔀 Routing - Register the necessary routes: - GET /login → shows the login form - POST /login → processes authentication - POST /logout (or GET) → logs the user out 🧠 Authentication Logic - Inside authenticate(): - Validate email and password inputs - Lookup user by email from the database - Use password_verify() to check if password matches - On success → Session::login($user_id) - On failure → redirect back with error message 🔐 Session Enhancements - Add login($user_id) to store user ID in session - Add logout() to destroy the session cleanly 📌 And that lands us at v23-authentication. |
Thu, Jun 26, 2025 | 9m:3s | |
Pragmatic PHP |
Auth & Application Workflow
🛠️ Session Enhancements
- Add guest() and user() methods to the Session class - guest() → returns true if no user is logged in - user() → returns current user object (or null) 🎨 Navigation Improvements - Update the top navigation bar to reflect authentication state: - Show Register / Login links when user is not logged in - Show user avatar and menu when logged in - Fix any related JavaScript issues 👤 Gravatar Integration - Create a new helper class: app/Code/Gravatar.php - Use the user’s email to generate their avatar - Display avatar in the nav for logged-in users 🖼️ Conditional Upload View - Modify the upload page to only display the upload form if the user is logged in 📌 And that ties up the loose ends, landing us at v24-authentication-loose-ends. |
Tue, Jul 1, 2025 | 15m:30s | |
Pragmatic PHP |
Uploading Photos
🔒 UI Adjustments Based on Auth State
• Hide the notification bell in the top nav if the user isn’t logged in 🧭 Routing & Form Setup • Create an /upload route • Set the form’s action and stub in the store() method with a dd($_POST) to start • Remove the unused “title” field from the upload form 🧪 Validation Process • Define validation for the uploaded photo: $file = validate('photo')->file($allowed_mimes)->max(4016053)->required(); • Add $allowed_mimes and $max_file_size constants to the Photo model • Implement file() and update the max() method in the Validation class • Fix Validation::clean_value() to properly handle file inputs ⚠️ Missing Validation Messages • Ensure validation errors are displayed in these views: • registration.index • auth.index • upload.index 💾 File Storage & DB Entry • Save the uploaded file to the appropriate location • Create a new photo record in the database 🚫 .gitignore Update • Add public/photos to the .gitignore file to prevent uploaded files from being committed ⸻ 📌 And that completes the upload feature, bringing us to v25-upload-photos. Ready to show off some pictures! |
Thu, Jul 3, 2025 | 19m:17s | |
Pragmatic PHP |
Photo Deletion
🖼️ Improved Photo Show Page
In photo.show, hide the summary and reviews if none exist 🔗 Delete Route & Form Add a new route using the DELETE method In photo.show, display a delete form only if the logged-in user owns the photo Include a hidden input field: _METHOD = DELETE Update the Router class to detect the method via: $_POST['_METHOD'] ?? $_SERVER['REQUEST_METHOD'] 🔧 Controller Refactor Split responsibilities: ReviewController: keep only the store method, and fix redirect_back() usage PhotoController: remove the store method 🧨 Destroy Method in PhotoController Ensure the user is authenticated Validate the photo exists Validate ownership of the photo Delete the photo file: Add directory() method to the Photo model Add convert_to_path() method to the Photo model Delete the corresponding database record: Add a delete() method to the base Model class 📌 That wraps up the deletion flow and lands us at v26-delete-photos — now users can clean up after themselves! |
Tue, Jul 8, 2025 | 22m:11s | |
Pragmatic PHP |
Second Refactor & Review
🧠 Model Hydration Fixes
- Solved the issue where results weren’t instances of the child model class - Introduced hydrate() and hydrate_rows() to handle model instantiation - Added PHPDoc annotations with generics to assist with static analysis and better typing - Updated type hints across Policies and Controllers for clarity and consistency 📦 Model Enhancements - Added an update() method - Introduced pagination support with skip() and take() - Added sorting via order_by() and order_by_desc() - Modified / route to use pagination 🛡️ New Policy Classes - PhotoController: created Http/Policies/PhotoPolicy - AuthController: created and used AuthPolicy - RegisterController: created UserPolicy - ReviewController: created ReviewPolicy - UploadController: updated to use PhotoPolicy - Fixed return $data in UploadController to include a proper URL 📌 All of that polish and organization lands us at v26-review-refactor — making the codebase smoother, smarter, and more maintainable. |
Thu, Jul 10, 2025 | 19m:31s | |
Pragmatic PHP |
Blade / Twig Style Templates
In this video, we upgrade our templating engine to support Blade-style syntax, bringing a more expressive and readable feel to our views. Here’s what we did:
Enhanced the View class to support: - Escaped output: {{ $user->name }} - Raw output: {!! $user->name !!} Updated all relevant views to use the new shorthand syntax for cleaner templates. - 🛠️ Fixed a display issue in photo.show where old_rating wasn’t mapping correctly (it was showing a 4 instead of the correct 1). ✨ With this little dose of syntax sugar, we land at v27-blade-envy — giving our views a modern touch without bringing in a full framework. |
Tue, Jul 15, 2025 | 7m:48s | |
Pragmatic PHP |
Final Review & Refactor
This video is all about the last layer of polish — a final walkthrough to tidy up code, views, and structure across the project. Here’s what we touched:
🖼️ Views - index.view.php: added wider screen support with max-w-7xl. - Removed a duplicate header to clean up the layout. 🧱 Framework Cleanup - Database.php: fixed IDE issues and added helpful annotations. - globals.php: minor tweaks to smooth dev experience. - Model.php: highlighted PHPDoc comments, reviewed skip(), take(), and orderBy() methods. - Session.php and View.php: both got the “FII” treatment (Fix IDE Issues). 📦 Http Layer - Policies: - UserPolicy: FII. - Controllers: - AuthenticationController, PhotoController, RegisterController, ReviewController, and UploadController: all reviewed and cleaned up. 🌐 Public Entry Point - index.php: adjusted try/catch block for better error handling and clarity. 🎯 And that wraps things up at v28-review-refactor — setting the stage for a maintainable, scalable app with a solid foundation. |
Thu, Jul 17, 2025 | 12m:54s |