english

learn php

learn css

learn html

learn javascript

learn php

15. code organization
10 minutes / 1908 words

fifteen

code organization

so far, we have looked at all the pieces of code required to create a program but we haven't talked about how to build a full application from all the techniques you now know. you can certainly just write a collection of functions and call them but, if it's not an incredibly simple application, you're looking at a php file tens or hundreds of thousands of lines long, perhaps longer. it's incredibly unwieldy and cumbersome, not to mention unnecessary.

the solution is require. it calls an external php file and runs it as if it's right there in the one you're currently executing. in practice, we generally use require_once, which makes sure each file is only called once. but, if you're organizing things well, this will never be an issue.

this is also how you can call external code like phpmailer and the pear extensions we've already talked about -- as well as many others available. the warning that comes along with this is that any code you get from a source on the internet will be running on your server as if you wrote it. be careful if it's from a source you don't explicitly trust.

a good approach is to have a central php program at index.php that calls all your other code as needed using require_once. that way you never have to worry about duplicating code but you don't have to run it all every time the program is called.

let's look at an example pair of files. index.php...

require_once "php/functions.php";
displayContent();

functions.php

function displayContent(){
    print_r("anteaters are my favorite animals.");
}

there are many approaches to organizing these files but i find it helpful to keep the required files in a directory called php, which is not accessible to the outside directly. that way, the only php that can be executed is index.php, which then gatekeeps access to the rest of your code and makes sure anything sent from a user is parsed before it's used.

there is a partner set of commands -- include and include_once. they function similarly but allow execution without the external files, which tends to lead to disastrous outcomes. i suggest pretending these two simply don't exist.

code separation

as i mentioned earlier, php technically allows the use of inline html code. this is a feature that, while still available, was designed with a much older technique for coding in mind and results in nothing short of chaos when used in a modern web environment. a much better approach is to output html from within the php code. there are many ways to do this. one of the best is to create or use an existing templating system. you already know all the things required to build a simple templating platform so let's put it together.

first, like all coding projects, you have to decide how to organize your files. we'll keep it simple for this example and keep php files in a directory called php and template files in a directory called template. we can use .php for all the php files (though the extension doesn't matter because they'll never be run using the web server, only called using require_once). for template files, we'll use .htm because they're html files and that will allow our text editor to automatically display properly-highlighted code for editing. again, the extension doesn't matter for anything but convenience because they're just text files that will be called by the code.

a note before we go on, the goal here is to have only one point of entry to your program -- index.php. everything else hides behind that gate and all data is processed through that like a filter. the only things that won't go through that path are external binary files like images, videos and pdfs.

while we've ignored the <?php at the beginning of files in all our examples so far, it's a good idea to remember that it's always there in php files. that's probably been clear until now but, as we're now looking at both php and html files working together, we'll start showing it in the examples for clarity. there is an idiosyncrasy with the require commands in php. if you're calling an external php file, that file still has to start with <?php, despite it being read in as text and the file that's calling it already contains those five characters. someone on the php development team probably has a reason for this to be the case.

whatever it is, much like it being required in the first place, it's certainly a bad reason.

index.php

<?php

require_once "php/template.php";

$html = [];
$html["language"] = "en";
$html["title"] = "all about beavers";
$protocol = !empty($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] !== "off" ? "https" : "http";
$html["url"] = $protocol . "://" . $_SERVER["HTTP_HOST"];
$html["content"] = "beavers are excellent swimmers and can even chew underwater without getting water in their lungs, thanks to special flaps at the backs of their mouths. (canadian geographic)";
$html["headerText"] = "welcome!";
$html["footerText"] = "bye!";

doOutput();

template.php

<?php

$templateDirectory = getcwd() . "/template/";

function stripControls(string $s = "")
{
    // removes control characters
    return $s = trim(preg_replace("/\p{C}/u", "", trim($s)));
}

function cleanTemplateString(string $s = "")
{
    // keeps letters, numbers, slashes, periods, equals and percentages
    return stripControls(preg_replace("/[^\p{L}\p{N}\/\.\=\%]/u", "", trim($s)));
}

function parseTemplateContent($template)
{
    return preg_replace_callback(
        "/\{\{(.*?)\}\}/s",
        function ($matches) {
            $code = cleanTemplateString($matches[1]);
            if (str_starts_with($code, "template/")) {
                return parseTemplate(substr($code, 9));
            }
            $c = getHtmlCode($code);
            return $c ? parseTemplateContent($c) : "";
        },
        $template,
    );
}

function getHtmlCode($code)
{
    global $html;
    if (isset($html[$code])) {
        return $html[$code];
    }
    if (isset($html[$code . "Default"])) {
        return $html[$code . "Default"];
    }
    return null;
}

function parseTemplate($t)
{
    return parseTemplateContent(getTemplate($t));
}

function getTemplate($t)
{
    global $templateDirectory;
    $f = $templateDirectory . $t . ".htm";
    return is_file($f) && is_readable($f) ? file_get_contents($f) : "";
}

function doOutput()
{
    print_r(parseTemplate("primary"));
}

primary.htm

<!doctype html>
<html lang="{{language}}">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=10.0" />
        <title>{{title}}</title>
        <base href="{{url}}" />
    </head>
    <body>
        {{template/header}}
        <content role="main"> {{content}} </content>
        {{template/footer}}
    </body>
</html>

header.htm

<header>{{headerText}}</header>

footer.htm

<footer>{{footerText}}</footer>

on the topic of odd behavior in php to be aware of, before we go through this whole process step by step, you may have noticed a series of if blocks that appear unnecessary like...

if (isset($html[$code])) {
    return $html[$code];
}

you would automatically imagine this would be far cleaner and easier to read as...

isset($html[$code]) && return $html[$code];

unfortunately, that doesn't work because php differentiates between function behavior and command behavior. return can't be chained like a function so it must be wrapped in an if block. hopefully, this will be addressed in a future release of php and return and all non-function commands will behave like functions. until then, this is just something to keep in mind and a source of necessarily messier code.

index.php does three things. first, it requires template.php. second, it sets a series of variables inside an array called $html. third, it calls a function from template.php called doOutput.

the purpose of the rest of your program would be to determine the contents of that $html array so the template processor can deliver the results. in this example, however, we're just setting them manually at the beginning of the file and calling the default template.

the only piece of that code that might feel new is where it determines the protocol and server address. we have already talked about the built-in constants in php. $_SERVER contains a collection of server-specific variables and these vary wildly between server software and platforms. we're checking to see if the server is secure (http/https) and getting its address then putting them together like any other variables.

turning to template.php, let's begin with stripControls and cleanTemplateString. you've already seen regular expression replacement and the trim command used like this, including with unicode classes like letters and punctuation. control characters are special hidden characters that can be used to inject malicious code so it's a good idea to strip those from any strings. the template strings are restricted to only letters, numbers, slashes, periods, equals and percentages so all other characters are removed. we're loading these templates from the filesystem so it's best not to potentially cause security problems with special characters.

we can follow the process through starting with doOutput, the function called from index.php. doOutput just prints the result of running the parseTemplate on primary.htm. this is a typical approach to a templating system -- have a default template like primary.htm that is called unless something else has already been called elsewhere in the page but allow other templates to be used by not hardcoding primary.htm into the main template function.

parseTemplate takes the name of the template file (in this case, primary) and passes its name to getTemplate then takes whatever is returned and passes that to parseTemplateContent. at this point, it should be clear that it's important to name functions based on what they're doing or it's easy to get lost.

getTemplate checks to make sure the file exists and is readable then reads it and returns the result.

parseTemplateContent is where the majority of the work happens, though it's still only a few lines of code. we've used preg_replace before but preg_replace_callback is the next step in the replacement process -- it doesn't just replace what is matched with a fixed string or regular expression code but calls a function on it. /\{\{(.*?)\}\}/s matches anything inside {{}}. preg_replace_callback creates an array of matches. we're calling it matches here. [0] is the entire match. [1] is the first group inside the match -- in this case, the thing inside {{}}, what we're referring to as the code. after passing it to cleanTemplateString, we assign it to $code as a string.

the first if in the sub-function checks to see if $code begins with template/. if it does, we treat it as a call to another template file. this happens for header.htm and footer.htm in this example.

as we saw before, $c is a common shorthand for "content". we pass $code to getHtmlCode.

getHtmlCode checks to see if $code exists in the $html array. if it does, it returns that. if it doesn't, it checks to see if $code plus Default exists and returns that. that allows you to have defaults and override them easily while still preserving the defaults for comparison. finally, if neither exists, it just returns null, which is evaluated as false or empty.

returning to our sub-function, if $c, the return from getHtmlCode has content, run that through parseTemplateContent because it hasn't been parsed yet. otherwise return an empty string -- in other words, ignore the template command because it can't find anything to match it but don't break the html, just leave it blank. remember, the $template on the next line is just the next parameter for preg_replace_callback, what it's searching in. it's just not necessarily easy to keep track because we've looked at the sub-function in the middle.

assignments

learn php

learn css

learn html

learn javascript

powered by akelei. an avi sato project.