How to write proper PHP


#1

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 the controllers.
  • Separate the HTML stuff into the views.
  • 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.


What is different between include_once and require_once in php?
The best practices for creating a toolbar
All methods of separating presentation from code
#2

It’s been a long month and I forgot to mention that you should be enabling errors and debugging them while you are in development. So add this to the top of your PHP file.

NOTE


Remember to only enable these functions during development. You should either remove or comment out these lines below. You should not be displaying any kind of errors to the screen on a production site. Only do it on a development site.

/*
    Use this line only if you are running PHP 7.
    This line has to be first before anything. Even before session_start();

    declare(strict_types=1);
*/

// This line is to enable displaying errors on the screen.
ini_set('display_errors', 'TRUE');

// The error_reporting function tells PHP to report an error if it ever happens.
// What you pass into that function is the level for the kind of reports.
// -1, 0, 1, E_ERROR, E_WARNING, E_PARSE, E_NOTICE, and E_ALL
error_reporting(-1);

// Send those logs to a file.
// Typically though, you should be specifying this line in a php.ini file.
ini_set('error_log', 'PATH-2-MY-ERROR-LOG.php');

This is good for debugging and testing on development environments. You shouldn’t be debugging when you have a whole system already. You should be debugging and testing while you write each line. This is what makes a good programmer.


#3

It is worth noting:

  1. declaration(strict_types=1); only applies to the current file. Logic behind this is that legacy PHP files, scripts, libraries, etc can be included without affecting the current application.
  2. `error_reporting(…); applies to this file and all included files.

#4

10 posts were split to a new topic: Differentiate between localhost and live


#6

Shouldn’t you indicate which versions of programs you are limiting yourself to so people won’t stumble on this page when it gets out of date?


#7

Nope. This layout is not mutal exclusive so it works on all versions of PHP including 7.3 alpha. This is not a syntax problem if it ever comes to version change. Whatever you want to throw in your controller file is up to you. This layout is meant for you to separate your HTML from your PHP. From the time PHP was made, this kind of idea was already available. People just never used it because they believe that a single file is some how much easier to manage.

It’s actually not easier. It’s actually harder to manage a single PHP file. This has happened before where people think that using just a single PHP file would be easy. Then when they start adding in complex operations, I guess it’s not so easy now is it? A couple of months ago, we had someone do the same thing on this forum. They thought that it was going to be easy stuffing everything into 1 single PHP file. Then they started nesting PHP code with HTML code and then started adding if(isset($_POST[...])) to check for a certain scenario, but guess what ended up happening? The whole entire file was a big mess. They couldn’t figure out why it wasn’t triggering the expected if statements. If they had separated their files to make it more neater and easier to maintain, they wouldn’t be having that problem.

But unless the PHP team removes either require_once or require, I don’t think this is going away. Highly doubt they would too because require_once and require is actually the business standard. Look at every single 3rd party vendor out there for PHP, how many of them do you think uses the function require_once or require? All of them do. CodeIgniter does, Symfony does, CakePHP does, I believe even WordPress does. Not sure so correct me on that. But you get the point. The only way this idea becomes obsolete is when they start removing the basic include functions.


#8

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.