Quick Reference: CRUD Operations in Slim MVC
This cheatsheet provides a quick reference for implementing CRUD operations in your Slim MVC application.
Checklist for Implementing CRUD Operations
Section titled “Checklist for Implementing CRUD Operations”When implementing CRUD operations, you should follow these steps for each resource (e.g., products, categories, orders, users, etc.) in the following order:
- ✅ Create a Model class (extends
BaseModel, injectPDOService) - ✅ Create a Controller class (extends
BaseController, injectContainerandModel(s)) - ✅ Implement 7 standard controller methods (index, show, create, store, edit, update, delete)
- ✅ Create 4 view files (index, show, create, edit) for the resource
- ✅ Register 7 routes in
web-routes.phpfor the resource - ✅ Use named routes for redirects
- ✅ Follow RESTful URI patterns
- ✅ Validate input data before passing to model methods
- ✅ Use Post-Redirect-Get pattern (PRG) pattern after successful form submissions to prevent duplicate submissions
- ✅ Test all routes in browser
Extracting Input Data from the Client Request
Section titled “Extracting Input Data from the Client Request”In your controller’s callback methods, you can extract the request data (route parameters, POST form data, query string parameters) from the request object using the following methods:
1. Get route parameter (from {id} placeholder)
Section titled “1. Get route parameter (from {id} placeholder)”- Inputs passed in the URI path are called route parameters.
- Route parameters are typically used for parametric routes like
/products/{id}and they help identify the specific item to be displayed, edited, or deleted. For example,/products/123 - You can access them using the
$argsarray parameter in your controller’s callback method.
public function routeCallback(Request $request, Response $response, array $args): Response{ $productId = (int) $args['id']; // E.g., /products/123 -> $args['id'] = '123'}2. Get POST form data
Section titled “2. Get POST form data”- Inputs passed in the POST request body are called form data.
- Form data is typically used for form submissions or API requests. For example,
/products - You can access them using the
$request->getParsedBody()method.
public function routeCallback(Request $request, Response $response, array $args): Response{ $formData = $request->getParsedBody(); $productName = $formData['name'] ?? ''; $productPrice = $formData['price'] ?? 0;}3. Get query string parameters
Section titled “3. Get query string parameters”- Inputs passed in the GET request query string are called query string parameters.
- Query string parameters are typically used for search queries or filters. For example,
/products?search=laptop&sort=price - You can access them using the
$request->getQueryParams()method.
public function routeCallback(Request $request, Response $response, array $args): Response{ $queryParams = $request->getQueryParams(); $search = $queryParams['search'] ?? ''; $sort = $queryParams['sort'] ?? 'name';}Creating a Controller Class
Section titled “Creating a Controller Class”Create a controller class by extending BaseController. The constructor should call the parent constructor with the Container instance and inject model instance(s) into the controller’s constructor.
Standard Signatures of the Controller Callback Methods
Section titled “Standard Signatures of the Controller Callback Methods”<?phpuse Psr\Http\Message\ResponseInterface as Response;use Psr\Http\Message\ServerRequestInterface as Request;
class YourController extends BaseController{ public function __construct(Container $container, private YourModel $model) { parent::__construct($container); }
// Display list of items (GET request) public function index(Request $request, Response $response, array $args): Response
// Show single item details (GET request) public function show(Request $request, Response $response, array $args): Response
// Display a form for creating a new item (GET request) public function create(Request $request, Response $response, array $args): Response
// Handle a request to save a new item to the database (POST request) public function store(Request $request, Response $response, array $args): Response
// Display a form for editing an item (GET request) public function edit(Request $request, Response $response, array $args): Response
// Handle a request to save changes to an item (POST request) public function update(Request $request, Response $response, array $args): Response
// Handle a request to delete an item (GET request) public function delete(Request $request, Response $response, array $args): Response}Rendering Views and Redirecting to Other Routes
Section titled “Rendering Views and Redirecting to Other Routes”The BaseController class provides two methods for displaying views and redirecting to other routes:
render()→ Display a view template using a view template file name (e.g.,products/index.php)redirect()→ Redirect to a named route using a named route name (e.g.,products.index)
Displaying Views using render()
Section titled “Displaying Views using render()”The render() method takes three parameters:
$response→ PSR-7 response object to modify$view_name→ Name of the view template file (e.g.,products/index.php)$data→ Associative array of data to pass to the view (optional)
// Basic renderingreturn $this->render($response, 'products/index.php');
// Passing associative array of data to view$data = [ 'product' => $this->model->findById((int) $args['id']), 'title' => 'Product Details'];return $this->render($response, 'products/show.php', $data);HTTP Redirects using redirect()
Section titled “HTTP Redirects using redirect()”- The
redirect()method is used to redirect the user to a named route. - It is typically used after a successful form submission (in the Post-Redirect-Get (PRG) pattern) to prevent duplicate submissions or after a data operation (like delete or update).
The redirect() method takes five parameters:
$request→ PSR-7 request object for route context$response→ PSR-7 response object to modify$route_name→ Named route to redirect to (e.g.,products.index,products.edit)$uri_args→ Replaces route placeholders like{id}in/products/{id}(optional)$query_params→ Adds query strings like?page=2&sort=name(optional)
Method Signature:
protected function redirect( Request $request, Response $response, string $route_name, // Named route to redirect to array $uri_args = [], // Optional: Replaces {placeholders} in route array $query_params = [] // Optional: Adds ?key=value to URL): ResponseExamples of Redirects
Section titled “Examples of Redirects”// Simple redirect (no parameters)return $this->redirect($request, $response, 'home.index');
// With route parameter (replaces {id}) → /products/123return $this->redirect($request, $response, 'products.show', ['id' => $productId]);
// With query parameters only → /products?category=electronics&sort=pricereturn $this->redirect($request, $response, 'products.index', [], [ 'category' => 'electronics', 'sort' => 'price']);
// With BOTH route parameter and query string → /products/123/edit?tab=detailsreturn $this->redirect($request, $response, 'products.edit', ['id' => $productId], [ 'tab' => 'details']);Standard CRUD Routes URI Patterns
Section titled “Standard CRUD Routes URI Patterns”| Action | Method | URI Pattern | Controller Method | Route Name |
|---|---|---|---|---|
| List all items | GET | /products | index() | products.index |
| Show one item | GET | /products/{id} | show() | products.show |
| Show create form | GET | /products/create | create() | products.create |
| Handle save new item | POST | /products | store() | - |
| Show edit form | GET | /products/{id}/edit | edit() | products.edit |
| Save changes | POST | /products/{id} | update() | - |
| Delete item | GET | /products/{id}/delete | delete() | products.delete |
Route Registration Example
Section titled “Route Registration Example”use App\Controllers\ProductsController;
// 1) List all products (GET request)$app->get('/products', [ProductsController::class, 'index']) ->setName('products.index'); // 👉 named route for redirecting after successful form submissions
// 2) Show single product (GET request)$app->get('/products/{id}', [ProductsController::class, 'show']) ->setName('products.show');
// 3) Show a form for creating a new product (GET request)$app->get('/products/create', [ProductsController::class, 'create']) ->setName('products.create');
// 4) Handle a request to save a new product to the database (POST request)$app->post('/products', [ProductsController::class, 'store']);
// 5) Show a form for editing a product (GET request)$app->get('/products/{id}/edit', [ProductsController::class, 'edit']) ->setName('products.edit');
// 6) Handle a request to save changes to a product (POST request)$app->post('/products/{id}', [ProductsController::class, 'update']);
// 7) Handle a request to delete a product (GET request)$app->get('/products/{id}/delete', [ProductsController::class, 'delete']) ->setName('products.delete');Model Class Structure
Section titled “Model Class Structure”The BaseModel class provides a set of convenient methods for database operations using prepared statements. All methods use named or positional parameters for security against SQL injection.
Create a model class by extending BaseModel. The constructor should call the parent constructor with the PDOService instance.
<?phpnamespace App\Domain\Models;
use App\Helpers\Core\PDOService;
class ProductsModel extends BaseModel{ public function __construct(PDOService $db_service) { parent::__construct($db_service); }}Retrieve Multiple Records using selectAll()
Section titled “Retrieve Multiple Records using selectAll()”// Get all recordspublic function getAllProducts(): array{ return $this->selectAll('SELECT * FROM products ORDER BY created_at DESC');}
// With conditions (named parameters)public function getProductsByCategory(int $categoryId): array{ return $this->selectAll('SELECT * FROM products WHERE category_id = :id', ['id' => $categoryId]);}Retrieve Single Record using selectOne()
Section titled “Retrieve Single Record using selectOne()”// Find by ID (positional parameter)public function findById(int $id): array|false{ return $this->selectOne('SELECT * FROM products WHERE id = ?', [$id]);}
// Find by slug (named parameter)public function findBySlug(string $slug): array|false{ return $this->selectOne('SELECT * FROM products WHERE slug = :slug', ['slug' => $slug]);}Count Records using count()
Section titled “Count Records using count()”// Count allpublic function getTotalProducts(): int{ return $this->count('SELECT COUNT(*) FROM products');}
// Count with conditionpublic function countActiveProducts(): int{ return $this->count('SELECT COUNT(*) FROM products WHERE status = :status', ['status' => 'active']);}Insert a new record
Section titled “Insert a new record”// INSERTpublic function create(array $data): int{ return $this->execute( 'INSERT INTO products (name, price, created_at) VALUES (:name, :price, :created_at)', ['name' => $data['name'], 'price' => $data['price'], 'created_at' => date('Y-m-d H:i:s')] );}Update an existing record
Section titled “Update an existing record”// UPDATEpublic function update(int $id, array $data): int{ return $this->execute( 'UPDATE products SET name = :name, price = :price WHERE id = :id', ['id' => $id, 'name' => $data['name'], 'price' => $data['price']] );}Delete an existing record
Section titled “Delete an existing record”// DELETEpublic function delete(int $id): int{ return $this->execute('DELETE FROM products WHERE id = :id', ['id' => $id]);}Get Last Inserted ID using lastInsertId()
Section titled “Get Last Inserted ID using lastInsertId()”public function createAndGetId(array $data): string{ $this->execute('INSERT INTO products (name, price) VALUES (:name, :price)', $data); return $this->lastInsertId();}Quick Tips
Section titled “Quick Tips”- Named parameters → Use
:namefor better readability in SQL queries - Route names → Always use
->setName()for routes you’ll redirect to (e.g.,products.index,products.edit) - Route order → Place
/createroutes before/{id}routes - PRG pattern → Always redirect after POST requests to prevent duplicate submissions
- Validation → Validate all input data before passing to model methods
- Error handling → Use try-catch blocks and redirect on errors
- Security → Use prepared statements (all
BaseModelmethods do this automatically)