Coding a PHP 7 Framework #2 - Code Workshop

In this series of articles, we'll walk through the coding of a PHP micro-framework. It is assumed you are fairly familiar with PHP and coding / developing in general, however we'll try to explain each step so that a novice could follow.

If you need more space to see the code, click "Hide Sidebar >" above the Article Categories box to the right ->

The /engine/config.php file

The configuration file is arguably the most important file of a framework like this one (save perhaps for the /.htaccess file, without which nothing would work!). In it, we'll set up how the application will function, including:

String Functions

Remember in part 1 where we discussed using utf8_bin as the encoding method for our database tables? Well this is how we ensure consistency through our script:

<?php
/*
* String function settings
*/
mb_internal_encoding('UTF-8'); // Tell PHP that we're using UTF-8 strings until the end of the script use mb_ for string functions...
mb_http_output('UTF-8'); // Tell PHP that we'll be outputting UTF-8 to the browser

mb_internal_encoding('UTF-8'); configures our PHP script to deal with multi-byte characters strings as UTF8, meaning we can safely manipulate strings in the database (as we've stored those string using UTF8_BIN...). We are required to use the mb_ functions to do this however.

mb_http_output('UTF-8'); configures the output buffer of PHP to send data to the browser also UTF8 encoded. This is only part 3 of a 4 part series however (read part 1 of this article series for more information). When we come to make a HTML5 template, we'll also use <meta charset="utf-8" /> in the <head> as the final stage.

PHP Version

Since we're going to be using functional features only found in PHP version 7+, the next step is to test whether or not the system is actually running the correct version. We can use PHP_MAJOR_VERSION for this.

The DOC_ROOT constant

Our framework is going to be making a fair few requests to the file system (grabbing class files, including template elements etc). Instead of manually defining the document root in the application specific configuration, we can automatically grab it using:

/*
* set up directory root
*/
define('DOC_ROOT',dirname(__DIR__));

We'll make it a constant, as we don't want the value of DOC_ROOT changing after it's instantiation. The value of DOC_ROOT is set grabbing the directory of the parent directory (locally, specific to this php file). Since the file path is /engine/config.php, if we just used the magic __DIR__ constant, we'd get a DOC_ROOT including the /engine/ folder, therefor we use dirname() to get the current directory of the parent directory of the current file. Think of it like the ../ operator.

If for example the config file was buried in /engine/config/config.php, we'd then have to use dirname(dirname(__DIR__)); to find the current directory of the parent's parent...

Application Specific Configuration include

Every application built with this framework will obviously need a set of definitions specific to that application. In our case, we've provided /engine/app_config.php for this purpose, so the next few lines check whether this file exists and includes it:

We'll look at the creation of this file later in the article series. Note: Most of the defined() checks later in this /engine/config.php file are there in case they're not defined in /engine/app_config.php.

Error Reporting

In the first of the defined() checks, we test to see if this script should run in DEBUG mode, and if so, spit out all errors! Note the defined('DEBUG') check - this means we can define DEBUG as false in /engine/app_config.php for a live environment.

Default Constants

In much the same way as the DEBUG constant, here we can test for / set a few things we know we'll need in the HTML template later. The interesting one is the SESSION_IGNORES constant, which we'll reference further down the file. The SESSION_IGNORES constant stores a list of URL's that we may not want the session to have started in (for example when building an API).

Note: We're using the ALL CAPS format for constants for easy recognition in future files.

First of all, we define a few more contants that will get used throughout the execution, URL_REQUEST, URL_PATH and REQUEST_METHOD.

In the case of URL_PATH, we're using parse_url with our defined URL_REQUEST to return a string WITHOUT any aditional parameters (GET).

Then, in the inverse of this operation, we define REQUEST_QUERY_STRING as everything EXCEPT the URL_PATH, i.e. any GET params. This is only half the story however. Remember in the /.htaccess file that we forwarded the whole request to /index.php? Well this means that we need to rebuild the appended URL parameters back into the $_GET variable. We do this using mb_parse_str(), a function designed to take URL encoded strings and put them into a container.

Finally, the URL_PATH is split into an array, after being trimmed of '/' at the beginning and end of the string. As the comment indicates, the trim is important to ensure our URL_NODES array contains the correct number of nodes in the correct index. To make doubly sure that's the case, we also pass that array through a filter, getting rid of any blank values using array_filter(). The result of these operations is defined as URL_NODES.

Autoloading the right way

PHP is an interpreted language, parsed and executed at run-time. As such, it's important not to bog the execution of any given script down by unneccessarily including files we don't need. A common mistake is to include ALL class files that MIGHT get used in any given pathway through the code, however there is a better way of handling this.

Welcome spl_autoload_register(). This function takes a function name is it's argument and uses that to tell PHP where to look whenever someone wants to include a class file. As PHP is object oriented (or at least, it can be) this has the effect of only "including" a class file when it's first needed. This saves memory and execution time.

The nice thing is that you can have any number of functions loaded into the autload queue by using multiple spl_autoload_register() calls. PHP will go through each function registered in order until it either finds the requested file, or doesn't.

The lecAutoload() function takes in a $className argument, splits the string given to it by PHP into "bits", then looks in the /library/ folder we set up at the beginning of the project. Note the first use of our DOC_ROOT constant. We're limiting our autoload function to only allow classes defnined by a namespace, then a class name - however if you want to adhere more strictly to PSR-4, then by all means implement away here. In our case, we can structure our /library/ folder into /library/namespace/ folders, inside which we have className.class.php files. Check part 1 of this articles series to see that we set up a "Lectric" namespace folder already.

After registering our auto load function, we then give provision to include a Composer autoload file too, as it's likely any given application will want to include packages using Composer. This check assumes we''l put the /vendor/ folder for Composer in the document root of the project.

Database Connection and Handler through PDO

In our framework, we'll use PDO. Because you should. Namely, it allows easy prepared statements and a more secure way of talking to a database.

First we check if DB_HOST, DB_NAME, DB_USER and DB_PASSWORD have been defined (we would set these application specific constants in /engine/app_config.php), and if so then attempt to open a connection to that database, storing the result in a handler$lecDBH.

This block of code is surrounded in a try /catch control block to catch if this connection has failed. Here we're assuming that if we can't get to the database, then we should stop execution of the application.

There are a few noteworthy options we can set in the database handler:

ATTR_ERRMODE - Ensure that if a query execution fails, that the handler throws an exception. This allows us to catch that exception in a database class we'll create later.

MYSQL_ATTR_USE_BUFFERED_QUERY - We want the data to remain with the handler (or specifically on the db server) whilst we do something with it. We'll kill out every request resource in the aforementioned database class.

ATTR_STRINGIFY_FETCHES - This makes sure we get back data in the same TYPE as it's stored in the database, otherwise integers on the database are returned as their string equivalents. Nope!

ATTR_EMULATE_PREPARES - Amongst other stuff, this also means that we can't bundle multiple queries into one statement. This can avoid confusion.

SESSION or not SESSION

Sometimes, we don't want PHP to start a SESSION, or more specifically, most of the time we want a SESSION to start, but occasionally that stops us from doing something. Usually involving not sending any headers.

This code checks to see if the URL requested exists in our SESSION_IGNORE constant array. If it doesn't then as long as the session hasn't already started (PHP_SESSION_NONE) then go ahead and start it.

Conclusion

And that's the basic framework configuration file complete! In the next article in the series, we'll take a quick look at an example application specific configuration file, and then move onto our controller!