Ok, so this is getting way out of hand. PHP
isn’t supposed to be this hard, but apparently, it is. Probably because people really love to copy and paste from tutorial websites that are decades old. Tutorial websites ARE the worst websites to learn PHP
from. For one, they don’t teach you about security at all. And second, they teach you the wrong way regardless if you know it’s wrong or not.
So I am going to discuss about how to write PHP
properly in 2018. I’ll be discussing about something called Separation of Logic. Some people call this Separation of Concern(s), but I like to call it Separation of Logic. The terms don’t really matter right now so you can use them loosely if you want in this topic. Also, remember that this topic is tailored towards beginners.
Separation of Logic or Separation of Concern(s) is pretty much a method or ideology that separates each logic so that you can manage things in an easier manner. For this topic, we are going to try and “mimic” or “use” the idea of MVC
. MVC
stands for Model
, View
, Controller
. MVC
is a pretty standard business idea that many applications use. MVC
isn’t even just for PHP
, you can see MVC
in other languages such as C#
, Python
, and even in Ruby
. It’s not going to be an actual MVC
, we are just going to use the idea of it. We are also going to write this topic in procedural
because that’s what beginners love to write their PHP
in. Short of OO
, this is pretty darn close to MVC
.
So the first thing we are going to do is determine what kind of action it is. Is it a GET
or is it a POST
? If it’s a GET
, what kind of GET
is it? Are we going to need the database? This is where we brainstorm and plan how we want our application to work.
Let’s start with the basic of this topic. So we’re pretty much going to plan things out first. Let’s decide that we want to make something simple. Pretty much, a Hello World
page. This is simple. So using Separation of Logic, we pretty much will separate our controller
from our views
.
index.php
<?php
// Create our $helloWorld variable
$helloWorld = 'Hello World!';
// Include our index_views.php file
require_once 'index_views.php';
index_views.php
<!DOCTYPE html>
<html>
<head>
<title>First Hello World Page</title>
</head>
<body>
<p><?php print($helloWorld); ?></p>
</body>
</html>
“Why and how does it work?” you may ask. It’s very simple. The index.php
file does not rely on the variable to actually have it working. So if say index_views.php
file is missing or has a typo, all you would really be seeing is a white page. In your error logs, you’ll see a file or directory does not exist
error or open file stream
error if you don’t have permissions to that file. So in reality, the variable actually gets passed down to the index_views.php
file.
We are also using require_once
because in PHP
, the most abused functions are include
and echo
. They are also the most misunderstood functions in PHP
as well. There is a difference between include
, require
, include_once
, require_once
. This is why you DO NOT learn from tutorial websites. They are terrible in every aspect.
Let’s discuss what the differences are.
include
- Includes a file.
- If a file is missing or has a typo, continue with execution.
- If a file does not have the right permission for the server to read, continue with execution.
require
- Includes a file.
- If a file is missing or has a typo, halt execution either right before or exactly at the required line.
- If a file does not have the right permission for the server to read, halt execution either right before or exactly at the required line.
include_once
- Does what
include
does, but does it once if you are including the same file more than once.
require_once
- Does what
require
does, but does it once if you are including the same file more than once.
The reason why it is recommended to use require
instead of include
is because of what is listed for include
. You may not know or care about what those listed bullets mean, but it will be your disadvantage for ignoring those bullets.
Let’s make a scenario. Say for example, you have something like the below.
index.php
<?php
include 'Scenario.php';
echo $scenario;
Scenario.php
<?php
$scenario = 'What will happen to this variable?';
Ok, so it looks pretty simple correct? Now, let’s say we mistyped Scenario.php
and had it like Secnario.php
. Notice the c
and the e
are switched around. Guess what you’ll get now? If you installed your development environment correctly and have errors enabled, you’ll get an error message saying Undefined index
and a file or directory does not exist
error. Undefined index
errors typically refer to variables that are referenced, but never really created. They are also from referencing associative arrays like $_POST
or $_GET
that does not contain that index.
But you know for sure that you created the variable $scenario
in Scenario.php
correct? Well that’s the thing. That’s what include
does. Again, look at the second bullet for include
. It says continue with execution
. This means that even if we mistyped our Scenario.php
file, it will still continue executing. This means that it will try and echo out $scenario
. But again, $scenario
never really was created so you’ll get the Undefined index
error.
Now imagine having 500 variables in other files that you are using include
for. Guess what you’ll be seeing? 500 Undefined index
errors. Now you don’t know which file is missing and what is going on. You have looked through all your files and you can’t find where the problem started from. This is why you don’t use include
. require
will halt exactly at that problem. If Scenario.php
doesn’t exist, it will stop executing everything. This means that echo $scenario;
will never happen. This also gives you a chance to figure out what is going on. Once you have fixed the typo in your require
line, the problem will go away and therefore you can continue with your day. That is why we’ll be using require_once
in this topic.
So let’s get back on track. In our first example at the very top, you’ll see that we have separated our PHP
from our HTML
. We have very little PHP
in our HTML
and that’s what we want. The idea of this method is to separate all the PHP
heavy stuff and put it in its own file. We will consider these files as “controllers”. Any files that the user will access will be our controller. So if they go to https://localhost/test.php
, the file test.php
will be considered as our “controller”. The file index_views.php
will be consider as our “views”. Really though, you don’t have to name them as such. Just name them appropriately.
So I have showed you the simple GET
scenario where you can implement this method. Let’s dig into something a little more complex. Let’s say we want to use POST
and in our POST
logic, we want to grab data from our database. So let’s try it out. So we know that we have to make sure that the request was through POST
. We aren’t going to be using if(isset($_POST[...]))
because this is not the correct way of checking for form submission. This is a dirty hack to bypass form submission checking and form validation. if(isset($_POST[...]))
will also fail in certain Internet Explorer versions. The correct way is if($_SERVER['REQUEST_METHOD'] == 'POST')
. So let’s get this underway.
The generic template for this method will look something like this. We basically check to make sure that it’s a POST
request. If it isn’t do something else. The “something else” can be as simple as redirecting the user back to the original form.
<?php
if($_SERVER['REQUEST_METHOD'] == 'POST') {
} else {
}
We’ll be requiring a config file as well. This is for the database information and we’ll be requiring the model file. This method kind of violates the MVC
pattern, but usually, you’ll have a bootstrap of some sort to load in your model file.
<?php
require_once 'config.php';
if($_SERVER['REQUEST_METHOD'] == 'POST') {
} else {
}
So that’s how it’ll look like. Let’s continue further. So in our config.php
file, we’ll start by creating a few constants for the database information.
<?php
session_start();
define('DB_HOST', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', 'root');
define('DB', 'test');
That’s simple enough. All we pretty much did was start our sessions and created our constants for our database information. Don’t worry, no one cares what my database usernames and passwords are. In PHP
, you can create constants in 2 ways.
// First way
define('CONSTANT_NAME', 'constant value');
// Second way
const CONSTANT_NAME = 'constant value';
The second way can only be written within classes
. But in newer versions of PHP
, you can create them outside of classes
. We won’t get into what classes
are because classes
will be difficult to example to a beginner who still has no clue what the difference between include
and require
is. The next step is to create the database connection. We’ll be pretty much doing it in the config.php
file.
<?php
session_start();
define('DB_HOST', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', 'root');
define('DB', 'test');
$options = [
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING
];
$db = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB, DB_USERNAME, DB_PASSWORD, $options);
If you did it correctly, you should get a PDO Object ()
if you do a print_r
on the $db
variable. print_r
is for printing arrays or objects. Again, we won’t discuss what objects
are either because this falls under OO
which for beginners, you won’t understand a single thing about them. Let’s continue further. So we’re just going to basically require the model file inside the config.php
file like so.
<?php
session_start();
define('DB_HOST', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', 'root');
define('DB', 'test');
$options = [
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
PDO::ATTR_ERRMODE => PDO::ERRMODE_WARNING
];
$db = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB, DB_USERNAME, DB_PASSWORD, $options);
require_once 'model.php';
The reason why we are using those options within our PDO
connection is because we want to set the default fetch mode to fetch objects since I am an object oriented person. If you want to fetch arrays, replace PDO::FETCH_OBJ
to PDO::FETCH_ASSOC
. The difference between these 2 is that FETCH_OBJ
will set the default fetch mode to use ->
while FETCH_ASSOC
sets the default fetch mode to use ['']
. Then we’ll set the error mode to error warning.
So now let’s start writing our controller file.
<?php
require_once 'config.php';
if($_SERVER['REQUEST_METHOD'] == 'POST') {
// Validation here
if(!isset($_POST['grab']) OR empty($_POST['grab'])) {
// Just set 1 to this error.
// There's nothing really special with this variable other than creating it
// And we'll be referencing it in a little bit.
$_SESSION['grab_error'] = 1;
header('Location: index.php'); // Redirect to our index file
die(); // Die will ignore everything after
} else {
print_r($_POST['grab']);
}
} else {
require_once 'index_views.php';
if(isset($_SESSION['grab_error'])) {
unset($_SESSION['grab_error']);
}
}
So you typically don’t want to print to the screen in a controller, but we’re going to do it for testing purposes. I also said not to use if(isset($_POST[...]))
before. The only time when if(isset($_POST[...]))
is acceptable and an exception is during form validation. Form validation and form submission checking are 2 different things. Form submission checking is meant to check if the form was submitted. Form validation is to validate that the field contains what you want it to contain. For instance, if you have a field for someone’s first name, you obviously don’t want it to have numbers do you? That’s where “form validation” comes in. Using if(isset($_POST[...]))
during this time is appropriate because someone might modify the HTML
stuff on their side. By not checking in case these fields exist, you will end up with Undefined index
errors if someone happens to modify the page on their side. So this is the only time when using if(isset($_POST[...]))
is an exception.
We are also unsetting the grab_error
session after the index_views.php
file because we are going to make it have a kind of feel when they refresh the page, the error message is gone. It doesn’t make sense to keep the error message there when the user refreshes the page and they didn’t do anything else.
Now let’s continue with our index_views.php
file.
<!DOCTYPE html>
<html>
<head>
<title>Grab something from the database</title>
</head>
<body>
<?php
if(isset($_SESSION['grab_error'])) {
?>
<p>Please type something into the "grab" text field.</p>
<?php
}
?>
<form action="//<?php print($_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI']); ?>" method="POST">
<input type="text" name="grab" placeholder="Type something into this field."><br>
<button type="submit">Submit</button>
</form>
</body>
</html>
So in this view
file, we are pretty much checking to see if the grab_error
session is set. If it is, we display a custom message to the user. We then create our form. We’ll “try” to guess what the URL
is. You can leave the action
attribute empty, but I believe it is best to put a URL
there. So when you type something into the “grab” text field, you’ll get a POST
array with the field grab
as an index.
Ok, the next step is to remove the print_r
in our controller
file and start using our model
file to grab data. So let’s create our model
file.
<?php
function grabData($db, $string) {
$sql = 'SELECT someRandomColumn, anotherRandomColumn, whyNotAnotherOne FROM myTable WHERE someRandomColumn = :string';
$prepare = $db->prepare($sql);
$parameters = [
':string' => $string
];
$prepare->execute($parameters);
if($prepare->rowCount()) {
return $prepare->fetchAll();
} else {
return false;
}
}
So in our model
file, we pretty much create a function
called grabData
. This function
requires 2 arguments. The first argument is our database connection and the second argument is the string that we want to use from our text field. We are also using Prepared Statements
because we are dealing with user inputs. It is always best to use Prepared Statements
when dealing with queries that have WHERE
, SET
, and INSERT
in them. These are the queries that will have user input. Stop using ->query
or _query
. THIS IS UNSAFE to use when dealing with user inputs.
Ok, so we have our model
file created, let’s implement the very last step in our controller
file and we should have everything working.
<?php
require_once 'config.php';
if($_SERVER['REQUEST_METHOD'] == 'POST') {
// Validation here
if(!isset($_POST['grab']) OR empty($_POST['grab'])) {
// Just set 1 to this error.
// There's nothing really special with this variable other than creating it
// And we'll be referencing it in a little bit.
$_SESSION['grab_error'] = 1;
header('Location: index.php'); // Redirect to our index file
die(); // Die will ignore everything after
} else {
// Validate "grab" first.
// Assuming you already did.
$grab = ....;
$returned = grabData($db, $grab);
if($returned == false) {
// The returned result is false.
// Do something else like give the user a 404 error page or give them an error message.
} else {
require_once 'returned_result.php';
}
}
} else {
require_once 'index_views.php';
if(isset($_SESSION['grab_error'])) {
unset($_SESSION['grab_error']);
}
}
So as you can see, assuming you have already done your validations, all we really do that’s different is we run the grabData
function. Pass in $db
from our config.php
because the variable $db
is global at this point since we required the config.php
file. Then we check to see if the results returned false. If it returned false, that means that the string that the user typed doesn’t exist within the database. If it did exist, it would trigger the else
statement and run the require_once
line.
This method is fairly close to MVC
. If you take a look at CodeIgniter
, you’ll see something that’s similar.
public function index() {
$this->load->model('ModelName');
$this->ModelName->grabData($db, $grab);
}
Do you see anything different from our model call and CodeIgniter
’s? Other than the OO
style, it’s pretty much the same. CodeIgniter
still needs to load the model
file and within the model
file, you have the function grabData
. It still requires the same set of arguments. Though if you are using a proper MVC
application, you don’t need to have the $db
connection anymore because you’ll most likely be able to load the $db
connection from within the model
file itself. So you’ll only be required 1 argument for the model call.
So pretty much wrapping this topic up, we separate all 3 logics and we have a much cleaner work environment that we can manage more smoother.
- Separate the
PHP
heavy stuff into thecontrollers
. - Separate the
HTML
stuff into theviews
. - Separate the database calls into the
models
.
This pretty much runs like an MVC
application. And in the future, if you happen to want to switch from this kind of method to using a real MVC
application, you can simply transition without a problem because you’ll have that mind set of how it’s supposed to work already. You would just need to learn OO
. You can also use PHP
in your views
as well. Just have a minimal or things that aren’t PHP
heavy like moving files or creating files, those would have to go in the controllers
.