Controllers bridge the gap between users and databases
We’ve made it past the basic MySQL functionality. I do want to cover normalization, and will do that in the next article, but I believe we need to introduce Controllers. It will simplify the concept when you start learning about Laravel, since it is an MVC framework.
https://blog.devgenius.io/php-p91-mysql-crud-1988f82072f5
What is an MVC Framework?
MVC stands for Model, View, Controller. I’m not going to go into strict definitions like something, something, business logic. Let’s speak human-talk.
- Model — The Class that interacts with the database. In our previous examples, this is the
Author
class. - View — The HTML and CSS. What the user will see.
- Controller — The class that directs communication between the Model and the View. We’ll get data out of the model and send it to the View. Once the view mixes everything together, it will return
- Route — We still need our route file. When the request comes in, it’s going to direct that request to a controller and the controller will handle the rest.
The Plan
Currently, our route file is what’s handling most of the controller functionality as well. We’ll need to:
- Create an
AuthorController
- Move certain data out of our
index.php
route file and into ourAuthorController
.
<?php
require_once("./Author.php");
use Authors\Author;
if ( !isset($_GET['page']) ) {
?>
<a href="index.php?page=authors">View All Authors</a> |
<a href="index.php?page=create">Create Author</a>
<?php
die();
}
$page = $_GET['page'];
if ( $page == "create" ) {
header("Location: Create.php");
die();
}
if ( $page == "authors" ) {
header("Location: ViewAll.php");
die();
}
if ( $page == "author" && isset($_GET['author_id']) ) {
header("Location: Show.php?author_id=" . $_GET['author_id']);
die();
}
if ( $page == "author" ) {
if ( isset($_POST['_method']) && $_POST['_method'] == "put" ) {
$author = new Author;
$update_success = $author->update($_POST);
if ( $update_success ) {
header("Location: index.php?page=author&author_id=" . $_POST['id']);
die();
} else {
die("Could not update");
}
}
if ( isset($_POST['_method']) && $_POST['_method'] == "delete" ) {
$author = new Author;
$delete_success = $author->delete($_POST);
if ( $delete_success ) {
header("Location: index.php?page=authors");
die();
} else {
die("Could not delete");
}
}
if ( isset($_POST['submit']) ) {
$author = new Author;
$insert_success = $author->insert($_POST);
if ( $insert_success ) {
header("Location: index.php?page=authors");
die();
} else {
die("Could not insert");
}
}
}
This is what our index.php
file looks like right now. We’re performing basic CRUD operations on the Author so we’ll need to create a controller that can handle those operations.
There is also some stuff that we’ll have to do to each of our Views. It’s currently mixed with retrieving data from the database and injecting it into our HTML code. That’s not the function of the view, so we’ll need to move those out one by one. We’ll also convert each of our views into classes. In other frameworks, you probably wouldn’t do this, but there’s a lot more background logic that goes into it.
AuthorController
Time to generate a basic skeleton for the AuthorController. It needs to perform the following functionality:
- View All Authors
- Show a Single Author
- Show a Single Author in a Form ready to update
- Process an Update
- Show a blank form to insert an author
- Insert an Author
- Delete an Author
<?php
namespace Authors;
class AuthorController
{
public function index()
{
// Return all the authors
}
public function create()
{
// Return form to create a new author
}
public function store( array $author )
{
// Store a new author
}
public function show( int $id )
{
// Show one author
}
public function edit( int $id )
{
// Display Author in a form
}
public function update( array $author )
{
// Update an author
}
public function destroy( array $author )
{
// Delete an author
}
}
Now we just have to populate it. Before we do, let’s take a look at our folder structure.
Application/
index.php
Controllers/
AuthorController.php
HomeController.php
Models/
Author.php
Views/
author/
create.php
edit.php
index.php
show.php
home/
index.php
Basic Prep
Let’s start off basic. The first thing that our index.php file did was check whether a page was present. If it’s not, it presents links to various pages. We don’t want those pages to be present there.
<?php
//...
if ( !isset($_GET['page']) ) {
?>
<a href="index.php?page=authors">View All Authors</a> |
<a href="index.php?page=create">Create Author</a>
<?php
die();
}
We want them stored in their own View. With that view, we also need a controller: the HomeController
. But do we really? In Laravel, you can return a View directly in your route file. We’ll go ahead and create the HomeController
since it gives us a nice and simple look at creating a controller.
Let’s take a step by step approach and look at it without the controller and with the controller.
Create Application/Views/home/index.php
that contains the HomeView class with our links stored in the index
method. This is going to be needed either way.
<?php
namespace Views\Home;
class HomeView
{
public function index(): string
{
$result = '<a href="index.php?page=authors">View All Authors</a> | ';
$result .= '<a href="index.php?page=create">Create Author</a>';
return $result;
}
}
The class is pretty straightforward. It just has one method that returns two links.
<?php
require_once("./Views/home/index.php");
use Views\Home\HomeView;
if ( !isset($_GET['page']) ) {
$home_view = new HomeView;
echo $home_view->index();
die();
}
// ...
The route just grabs the HomeView
and echos
out whatever was returned in the index
method, which are the two links. The application then terminates.
What does it look like with the controller. First, we need a HomeController
. We’ll create that in Application/Controllers/HomeController.php
.
<?php
namespace Controllers\Home;
require_once("Views/home/index.php");
use Views\Home\HomeView;
class HomeController
{
public function index()
{
$home_view = new HomeView;
echo $home_view->index();
}
}
The index.php
route file needs to just point to the HomeController->index
method.
<?php
require_once("./Controllers/HomeController.php");
use Controllers\Home\HomeController;
if ( !isset($_GET['page']) ) {
$home_controller = new HomeController;
$home_controller->index();
die();
}
We didn’t really cut down on any code. We just moved the code around, and actually created more code. What’s the point? It’s all about separation of logic. That’s not what the route is supposed to do: it’s what the controller should do. And sometimes the controller seems unnecessary. But we’ll see shortly how much purpose the controller actually serves when we get into our AuthorController
.
Author Create
The first one for author is pretty simple. It’s almost identical to what we just did. This is the form that’s going to be displayed when the user wants to add a new author. What do we need to do?
- Create
Views/author/create.php
. - Add code to
Controllers/HomeController.php
- Modify
index.php
to call thecreate
<?php
namespace Views\Author;
class CreateView
{
public function index(): string
{
$result = '<form action="index.php?page=author" method="post">';
$result .= '<div>';
$result .= 'First Name: <input type="text" name="first_name">';
$result .= '</div>';
$result .= '<div>';
$result .= 'Last Name: <input type="text" name="last_name">';
$result .= '</div>';
$result .= '<div>';
$result .= 'Email: <input type="text" name="email">';
$result .= '</div>';
$result .= '<div>';
$result .= '<input type="submit" name="submit" value="Create">';
$result .= '</div>';
$result .= '</form>';
return $result;
}
}
The AuthorController
needs to echo out the HTML.
<?php
namespace Controllers\Author;
require_once("Views/author/create.php");
use Views\Author\CreateView;
class AuthorController
{
//...
public function create()
{
$home_view = new CreateView;
echo $home_view->index();
}
//...
}
The last bit is to modify the index.php
route file.
<?php
require_once("./Controllers/HomeController.php");
require_once("./Controllers/AuthorController.php");
use Controllers\Home\HomeController;
use Controllers\Author\AuthorController;
if ( !isset($_GET['page']) ) {
$home_controller = new HomeController;
$home_controller->index();
die();
}
$page = $_GET['page'];
if ( $page == "create" ) {
$author_controller = new AuthorController;
$author_controller->create();
die();
}
Author Index
Next step is to see all of the authors. Once again, let’s go through the plan. Our current code has both of the retrieval process and the view creation in one file.
<?php
require_once("./Author.php");
use Authors\Author;
$author = new Author;
$authors = $author->select_all();
foreach( $authors as $author ) {
?>
<div>
<a href="index.php?page=author&author_id=<?php echo $author['id']; ?>">
<?php echo $author['id'] . ": " . $author['first_name'] . " " . $author['last_name']; ?>
</a>
</div>
<?php
}
That needs to be split apart. First, let’s create the Views/author/index.php
file. It’s going to contain one function that accepts an array of data. These will be all of the authors. Each author is loop through and the final HTML output is generated. The HTML is returned.
<?php
namespace Views\Author;
class IndexView
{
public function index( array $authors ): string
{
$result = '';
foreach( $authors as $author )
{
$result .= '<div>';
$result .= '<a href="index.php?page=author&author_id=' . $author['id'] . '">';
$result .= $author['id'] . ": " . $author['first_name'] . " " . $author['last_name'];
$result .= '</a>';
$result .= '</div>';
}
return $result;
}
}
Where does the $authors
argument come from? From the AuthorController
.
<?php
namespace Controllers\Author;
require_once("Models/Author.php");
require_once("Views/author/create.php");
require_once("Views/author/index.php");
use Models\Author;
use Views\Author\CreateView;
use Views\Author\IndexView;
class AuthorController
{
public function index()
{
$author = new Author;
$authors = $author->select_all();
$index_view = new IndexView;
echo $index_view->index($authors);
}
public function create()
{
$home_view = new CreateView;
echo $home_view->index();
}
//...
}
We brought in our Author
model into the AuthorController
since the controller communicates with both the model and the views. The select_all
method returns all of the authors from the authors
table. Those authors are then sent as an argument to the IndexView->index
method. Since the index
method returns HTML, we can just echo
out the information.
Last part is modifying the index.php
route file.
<?php
require_once("./Controllers/HomeController.php");
require_once("./Controllers/AuthorController.php");
use Controllers\Home\HomeController;
use Controllers\Author\AuthorController;
if ( !isset($_GET['page']) ) {
$home_controller = new HomeController;
$home_controller->index();
die();
}
$page = $_GET['page'];
if ( $page == "create" ) {
$author_controller = new AuthorController;
$author_controller->create();
die();
}
if ( $page == "authors" ) {
$author_controller = new AuthorController;
$author_controller->index();
die();
}
Author Show
Once the user clicks on the link in View All Authors, they’re taken to see the individual author. We haven’t created too much of our View already since we bundled Show and Edit into one. We’ll split it apart here.
So what happens when the user clicks on the link in view all authors?
- The link is sent to
index.php
with two parameters:page
andauthor_id
. - The
author_id
is passed to theAuthorController->show
method. - The method will retrieve the author’s contents out of the
authors
table for that specificid
and pass it to theShowView
. TheShowView
will generate the HTML. - The
AuthorController->show
methodechos
out the returend HTML content.
First, the view. It will accept an author and generate the content for the author to be displayed. At the bottom will be a link to edit the author. The link is not clean: it has to be edit_author
since we didn’t follow the 100% perfect Laravel naming convention. We should have added some type of additional argument, such as type
, and stated that the page
is author
and the type
is edit
. I didn’t want to confuse the topic any more than that, so we’ll just keep it as edit_author
.
The AuthorController->show
method will be calling this view and passing it the $author
argument.
<?php
namespace Controllers\Author;
require_once("Models/Author.php");
require_once("Views/author/create.php");
require_once("Views/author/index.php");
require_once("Views/author/show.php");
use Models\Author;
use Views\Author\CreateView;
use Views\Author\IndexView;
use Views\Author\ShowView;
class AuthorController
{
public function index()
{
$author = new Author;
$authors = $author->select_all();
$index_view = new IndexView;
echo $index_view->index($authors);
}
public function create()
{
$home_view = new CreateView;
echo $home_view->index();
}
public function show( int $id )
{
$author = new Author;
$show_author = $author->select( $id );
$show_view = new ShowView;
echo $show_view->index($show_author);
}
//...
}
The show
method also receives an argument: it’s the $id
. Where is that coming from? It’s coming from the index.php
route file.
<?php
require_once("./Controllers/HomeController.php");
require_once("./Controllers/AuthorController.php");
use Controllers\Home\HomeController;
use Controllers\Author\AuthorController;
if ( !isset($_GET['page']) ) {
$home_controller = new HomeController;
$home_controller->index();
die();
}
$page = $_GET['page'];
if ( $page == "create" ) {
$author_controller = new AuthorController;
$author_controller->create();
die();
}
if ( $page == "authors" ) {
$author_controller = new AuthorController;
$author_controller->index();
die();
}
if ( $page == "author" && isset($_GET['author_id']) ) {
$author_controller = new AuthorController;
$author_controller->show( $_GET['author_id'] );
die();
}
Our route file is starting to look really clean, as is our controller. That’s one huge benefit of having a separation of logic. You know exactly where specific type of code should be.
Author Edit
This is almost identical to Show with the exception that a form will be displayed.
The EditView
returns the form. It populates the form with the data passed from the $author
argument.
<?php
namespace Controllers\Author;
require_once("Models/Author.php");
require_once("Views/author/create.php");
require_once("Views/author/edit.php");
require_once("Views/author/index.php");
require_once("Views/author/show.php");
use Models\Author;
use Views\Author\CreateView;
use Views\Author\EditView;
use Views\Author\IndexView;
use Views\Author\ShowView;
class AuthorController
{
public function index()
{
$author = new Author;
$authors = $author->select_all();
$index_view = new IndexView;
echo $index_view->index($authors);
}
public function create()
{
$home_view = new CreateView;
echo $home_view->index();
}
public function show( int $id )
{
$author = new Author;
$show_author = $author->select( $id );
$show_view = new ShowView;
echo $show_view->index($show_author);
}
public function edit( int $id )
{
$author = new Author;
$edit_author = $author->select( $id );
$edit_view = new EditView;
echo $edit_view->index($edit_author);
}
//...
}
The edit
method receives the $id
from the index.php
route file.
<?php
require_once("./Controllers/HomeController.php");
require_once("./Controllers/AuthorController.php");
use Controllers\Home\HomeController;
use Controllers\Author\AuthorController;
if ( !isset($_GET['page']) ) {
$home_controller = new HomeController;
$home_controller->index();
die();
}
$page = $_GET['page'];
if ( $page == "create" ) {
$author_controller = new AuthorController;
$author_controller->create();
die();
}
if ( $page == "authors" ) {
$author_controller = new AuthorController;
$author_controller->index();
die();
}
if ( $page == "author" && isset($_GET['author_id']) ) {
$author_controller = new AuthorController;
$author_controller->show( $_GET['author_id'] );
die();
}
if ( $page == "edit_author" && isset($_GET['author_id']) ) {
$author_controller = new AuthorController;
$author_controller->edit( $_GET['author_id'] );
die();
}
And we’re done with the views (almost). We’ll need to figure out where to add the Delete
button, but we’ll make that modification after we finish the store
and update
methods.
Author Store
When the user clicks on the Update
button to update a specific user, the data is sent to the server and is currently being processed in the route file. Time to extract that data and move it to the AuthorController
.
<?php
namespace Controllers\Author;
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once("Models/Author.php");
require_once("Views/author/create.php");
require_once("Views/author/edit.php");
require_once("Views/author/index.php");
require_once("Views/author/show.php");
use Models\Author;
use Views\Author\CreateView;
use Views\Author\EditView;
use Views\Author\IndexView;
use Views\Author\ShowView;
class AuthorController
{
public function index()
{
$author = new Author;
$authors = $author->select_all();
$index_view = new IndexView;
echo $index_view->index($authors);
}
public function create()
{
$home_view = new CreateView;
echo $home_view->index();
}
public function show( int $id )
{
$author = new Author;
$show_author = $author->select( $id );
$show_view = new ShowView;
echo $show_view->index($show_author);
}
public function edit( int $id )
{
$author = new Author;
$edit_author = $author->select( $id );
$edit_view = new EditView;
echo $edit_view->index($edit_author);
}
public function update( array $author_details )
{
$author = new Author;
$update_success = $author->update( $author_details );
if ( $update_success ) {
header("Location: index.php?page=edit_author&author_id=" . $author_details['id']);
die();
} else {
die("Could not update");
}
}
// ...
}
The $author_details
argument is passed from the route. It’s just the contents of the $_POST
array.
<?php
require_once("./Controllers/HomeController.php");
require_once("./Controllers/AuthorController.php");
use Controllers\Home\HomeController;
use Controllers\Author\AuthorController;
if ( !isset($_GET['page']) ) {
$home_controller = new HomeController;
$home_controller->index();
die();
}
$page = $_GET['page'];
if ( $page == "create" ) {
$author_controller = new AuthorController;
$author_controller->create();
die();
}
if ( $page == "authors" ) {
$author_controller = new AuthorController;
$author_controller->index();
die();
}
if ( $page == "author" && isset($_GET['author_id']) ) {
$author_controller = new AuthorController;
$author_controller->show( $_GET['author_id'] );
die();
}
if ( $page == "edit_author" && isset($_GET['author_id']) ) {
$author_controller = new AuthorController;
$author_controller->edit( $_GET['author_id'] );
die();
}
if ( $page == "author" ) {
if ( isset($_POST['_method']) && $_POST['_method'] == "put" ) {
$author_controller = new AuthorController;
$author_controller->update( $_POST );
die();
}
}
Author Delete
Where should we place the button? We can place it on the ShowView
page or the EditView
page. I can see arguments for both, but I’ll add it to the EditView
page.
I know, hideous. But we’re here for functionality, not beauty. When we click Delete
, our route will direct the request to the AuthorController->destroy
method, which will in turn delete the author and redirect the user back to the view all authors page.
<?php
namespace Controllers\Author;
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
require_once("Models/Author.php");
require_once("Views/author/create.php");
require_once("Views/author/edit.php");
require_once("Views/author/index.php");
require_once("Views/author/show.php");
use Models\Author;
use Views\Author\CreateView;
use Views\Author\EditView;
use Views\Author\IndexView;
use Views\Author\ShowView;
class AuthorController
{
public function index()
{
$author = new Author;
$authors = $author->select_all();
$index_view = new IndexView;
echo $index_view->index($authors);
}
public function create()
{
$home_view = new CreateView;
echo $home_view->index();
}
public function show( int $id )
{
$author = new Author;
$show_author = $author->select( $id );
$show_view = new ShowView;
echo $show_view->index($show_author);
}
public function edit( int $id )
{
$author = new Author;
$edit_author = $author->select( $id );
$edit_view = new EditView;
echo $edit_view->index($edit_author);
}
public function update( array $author_details )
{
$author = new Author;
$update_success = $author->update( $author_details );
if ( $update_success ) {
header("Location: index.php?page=edit_author&author_id=" . $author_details['id']);
die();
} else {
die("Could not update");
}
}
public function destroy( array $author_details )
{
$author = new Author;
$delete_success = $author->delete( $author_details );
if ( $delete_success ) {
header("Location: index.php?page=authors");
die();
} else {
die("Could not delete");
}
}
}
The index.php
file continues to look simple.
<?php
require_once("./Controllers/HomeController.php");
require_once("./Controllers/AuthorController.php");
use Controllers\Home\HomeController;
use Controllers\Author\AuthorController;
if ( !isset($_GET['page']) ) {
$home_controller = new HomeController;
$home_controller->index();
die();
}
$page = $_GET['page'];
if ( $page == "create" ) {
$author_controller = new AuthorController;
$author_controller->create();
die();
}
if ( $page == "authors" ) {
$author_controller = new AuthorController;
$author_controller->index();
die();
}
if ( $page == "author" && isset($_GET['author_id']) ) {
$author_controller = new AuthorController;
$author_controller->show( $_GET['author_id'] );
die();
}
if ( $page == "edit_author" && isset($_GET['author_id']) ) {
$author_controller = new AuthorController;
$author_controller->edit( $_GET['author_id'] );
die();
}
if ( $page == "author" ) {
if ( isset($_POST['_method']) && $_POST['_method'] == "put" ) {
$author_controller = new AuthorController;
$author_controller->update( $_POST );
die();
}
if ( isset($_POST['_method']) && $_POST['_method'] == "delete" ) {
$author_controller = new AuthorController;
$author_controller->destroy( $_POST );
die();
}
}
Author Store
What happens when the user enters the author’s information and clicks on the Create button? The post
request is sent and we capture it. This is the last modification that we need to do. Seems kind of sad.
The AuthorController
is now complete.
<?php
namespace Controllers\Author;
require_once("Models/Author.php");
require_once("Views/author/create.php");
require_once("Views/author/edit.php");
require_once("Views/author/index.php");
require_once("Views/author/show.php");
use Models\Author;
use Views\Author\CreateView;
use Views\Author\EditView;
use Views\Author\IndexView;
use Views\Author\ShowView;
class AuthorController
{
public function index()
{
$author = new Author;
$authors = $author->select_all();
$index_view = new IndexView;
echo $index_view->index($authors);
}
public function create()
{
$home_view = new CreateView;
echo $home_view->index();
}
public function store( array $author_details )
{
$author = new Author;
$insert_success = $author->insert( $author_details );
if ( $insert_success ) {
header("Location: index.php?page=authors");
die();
} else {
die("Could not insert");
}
}
public function show( int $id )
{
$author = new Author;
$show_author = $author->select( $id );
$show_view = new ShowView;
echo $show_view->index($show_author);
}
public function edit( int $id )
{
$author = new Author;
$edit_author = $author->select( $id );
$edit_view = new EditView;
echo $edit_view->index($edit_author);
}
public function update( array $author_details )
{
$author = new Author;
$update_success = $author->update( $author_details );
if ( $update_success ) {
header("Location: index.php?page=edit_author&author_id=" . $author_details['id']);
die();
} else {
die("Could not update");
}
}
public function destroy( array $author_details )
{
$author = new Author;
$delete_success = $author->delete( $author_details );
if ( $delete_success ) {
header("Location: index.php?page=authors");
die();
} else {
die("Could not delete");
}
}
}
The index.php
route file is also complete.
Summary
We did it. We created a controller! Two, actually. Thanks for powering through this article. I hope that you can see the logical progression on how we built out our controllers. I know that I snuck the MVC architectural pattern in here, but it just seemed like the next logical progression from our CRUD MySQL article. See you next time.
FROM CREATION TO DELETION: MYSQL’S COMPLETE DATA TOOLKIT
What are CRUD operations? Create, Read, Update, Delete. The last piece of the MySQL puzzle is to combine those operations into one file.
Controllers bridge the gap between users and databases
PHP – P92: controllers
Controller — The class that directs communication between the Model and the View. We’ll get data out of the model and send it to the View. Once the view mixes everything together, it will return
NORMALIZATION IN MYSQL STREAMLINES DATABASE ORGANIZATION
PHP – P93: MYSQL NORMALIZATION
The last topic is normalization. What does it mean to normalize a database? In a nutshell, removing duplicate content and streamlining your database.