📖 PHP MVC Moving to Production

At some point you will want to move your site to a production (live) server. When you do this, you will want to secure some of the site information like database credentials out of the site root directory so they are hidden from visitors. You may also need to set the entry of the site to a specific directory that is not the default web root directory used to serve HTML pages. This article covers some basic methods for preparing your site for publication.

Hiding App Information

There is information in our app that we want to keep from prying eyes. In order to do this, we need to move the information to a file where we will be hiding it outside of the web root directory where it will not be accessible to visitors. Let's begin by creating a .env file in the project root directory alongside the .htaccess file. Now we add information we don't want accessible in the web root directory. In this case, we'll start with the database connection credentials.

Create a New .env File

In the new .env file, add the following.

DB_HOST=localhost
DB_NAME=adv_php
DB_USER=adv_php_user
DB_PASSWORD=secret

Create the new Dotenv.php File

Next, we create a new file in the Framework directory named Dotenv.php that will read the contents of the .env file, add the values to a new $_ENV super global array so it is available to the entire app.

The $path variable is the path to the new .env file we will provide when we call the load method form the Dotenv class. 

<?php

declare(strict_types=1);

namespace Framework;

class Dotenv
{
    public function load(string $path): void
    {
        $lines = file($path, FILE_IGNORE_NEW_LINES);

        foreach($lines as $line) {

            list($name, $value) = explode("=", $line, 2);

            $_ENV[$name] = $value;
        }
    }
}

Update the Front Controller

Now we add the call to the Dotenv class into the front controller index.php file. This is the index.php file located in the site root directory where the site routes are found. Place the following code in the file between the spl_autoload_register() and the $router = new Framework\Router lines.

I've included the spl_autoload_register() and the new Router lines to give you a better understanding where this goes. Please do not duplicate those lines in your file. The // print_r($_ENV); line is for debugging. Un-comment this line to see the contents of the $_ENV super global.

<?php
...
spl_autoload_register(function (string $class_name) {

    require "src/" . str_replace("\\", "/", $class_name) . ".php";
});

// add these lines to your code
$dotenv = new Framework\Dotenv;

$dotenv->load(".env");

// print_r($_ENV);

$router = new Framework\Router;

Test this in your browser. You should see something like this.

Array
(
    [DB_HOST] => localhost
    [DB_NAME] => adv_php
    [DB_USER] => adv_php_user
    [DB_PASSWORD] => secret
)

Update the Product Model

Now that the $_ENV contents are available to the app, you can add the values to your Product.php model file. Edit the getConnection() action as follows.

<?php
...

    public function getConnection(): PDO
    {
        $dsn = "mysql:
                host={$_ENV['DB_HOST']};
                dbname={$_ENV['DB_NAME']};
                charset=utf8;
                port=3306";

        return new PDO($dsn, $_ENV['DB_USER'], $_ENV['DB_PASSWORD'], [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_EMULATE_PREPARES, false
        ]);
    }

Be sure to test the site to make sure all pages load properly.

Secure the Site

Now that we have the .env file created, we can begin to secure the site files. There are a number of files that should not be available to visitors. These files need to be hidden. The way to do this is to create a new public folder with the site files that should be available to visitors in the site app folder and then set rules for the server to refuse access to the other files. We do this with the .htaccess file.

Files that should be available to visitors in the site files are images, css and so forth. Files that should not be available are the framework, app and .env files.

Update the .htaccess File

Open the .htaccess file. There are only 2 rules currently in the file. These are used to redirect all web requests to the main index.php file we are using as the front controller to aid in routing HTTP requests to the proper controller. This is the contents of our current .htaccess file in the rot directory.

RewriteEngine On
RewriteRule ^ index.php

These are Apache (server) directives, not PHP. We need to tell the server to load certain files that are requested by visitors instead of redirecting all requests to the index.php for processing normal page requests. Think, css, images, etc. Our new .htaccess rewrite conditions (RewriteCond) are inserted before the RewriteRule as follows to allow loading of files, directories and symbolic links(in this order). If the request does not fit one of these patterns, then we redirect to the index.php.

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-l
RewriteRule ^ index.php
Secure the Other Files

Currently, our server configuration will load any files requested in our site including css, images and also the .env file with our database login credentials. We do not want this information exposed. We need a way of limiting the files available to the public.

Add a public Directory

Create a new directory in the site root and name it public. This directory will hold our publicly available site files. Now you can move the front controller (index.php) file, the .htaccess file and any other normal site files (css, images, js, etc). At this point, you should be able to access your site files in the new public folder, but we can still see the .env file as well. We need to fix this.

Edit the Server Configuration

There are 2 ways to fix this issue. The preferred way is to edit the server config file (httpd-vhosts.conf) like we did at the beginning of this class to direct traffic to the new public directory. This may not be available if you are on a shared host server.

It will help the upcoming migration if you will modify your site file directory structure to match the new server. The new server has this path to your project.

https://mywebtraining.net/webdev/AvatarName/

The root directory of the new server is the domain root: mywebtraining.net. Your project is in a sub-directory named webdev and your public facing site files are in the public folder in your project directory. If you are using XAMPP, your root domain directory is the htdocs folder. TO copy the site file system to your local computer, you should create a webdev directory in your htdocs directory on your local computer. Create a new directory in the webdev directory named exactly after your avatar (case sensitive). Move your project to this new avatar folder.

htdocs/webdev/AvatarName/

Now your site directory structure should match your server directory structure making testing your site links much more like they will be on the new production server. Open the httpd-vhosts.conf file located in your XAMPP installation at xampp->apache->conf->extra and set the DocumentRoot and Directory values to point at the htdocs directory so the local server will act like the production server regarding relative references.

<VirtualHost *:80>
    DocumentRoot "C:\xampp\htdocs"
    ServerName phpmvc.localhost
    ServerAlias phpmvc.localhost

    <Directory "C:\xampp\htdocs">
        Require all granted
        AllowOverride All
    </Directory>
</VirtualHost>
Edit the .htaccess File

The second way to resolve this is to add another .htaccess file to the web root folder that tells the server to direct the web traffic to the public folder. Add the following rewrite rules to the new .htacess file in the site web root. The first line turns on the rewrite engine and the second line removes the public folder from the URL and redirects all traffic to the same public folder. This will remove the unwanted files like the .env from public access.

RewriteEngine On
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule (.*) public/$1 [L]

Once you finish creating the new .htaccess file and move the app files to the new public folder, you can still access the style.css file from the URL without public in the URL, but we get errors when trying to access the .env file and any of the normal site requests. This means we are half way to our resolution.

Edit the Front Controller

The problem with our site is that the autoloader is trying to load files based on everything being in the src folder. This worked when our front controller was in the same folder but now it is in the public folder. To fix this, we add a defined constant to the front controller so we can set the root path of the site.

In the index.php front controller, define the following ROOT_PATH constant and add it to the require statement in the autoloader. The __DIR__ constant returns the name of the current folder. Adding dirname method to the __DIR__ constant returns the path of the parent folder. Since the dirname(__DIR__) function returns the path name without a trailing slash (/), be sure to add it to the require src path. Also, be sure to add the same to the load(.env) method.

<?php
...

define("ROOT_PATH", dirname(__DIR__));

$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

spl_autoload_register(function (string $class_name) {

    require ROOT_PATH . "/src/" . str_replace("\\", "/", $class_name) . ".php";
});

$dotenv = new Framework\Dotenv;

$dotenv->load(ROOT_PATH . "/.env");

Make sure you add the same ROOT_PATH prefix to both of the require error views as well.

<?php
...
require ROOT_PATH . "/views/$template";
Sample File Directory Structure

File Structure

↧ ADV-PHP (SITE ROOT)
↳ public
| ↳ .htaccess
| ↳ index.php
| ↳ style.css
↳ src
↳ views
↳ .env
↳ .htaccess

Migrating to the Production Server

Your site should be working on localhost, but when we move to the production server, the setting on the new server will be different. We will be hosting our project in a sub-directory of the domain and won't be able to use the slash (/) at the beginning of our sire references. We need to modify our site to work on the host before we move the site to its final location.

Move the Database

Moving the database is generally an easy process that includes exporting the existing table and data then importing it to the new db server. You will also need to update the .env file to reflect your new db login credentials. You should test your login credentials on the new server by logging into the db server using PHPMyAdmin.

Update the .htaccess Files

The .htaccess files won't work on the new server due to the server configuration. The .htaccess file currently in the site root directory should be excluded (deleted) from the migration. The .htaccess file in the public directory needs to be updated as follows.

.htaccess

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews
    </IfModule>

    RewriteEngine On

    # Redirect Trailing Slashes If Not A Folder...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)/$ /$1 [L,R=301]

    # Handle Front Controller...
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>
Links and References

The rest of the changes are related to relative references for our site. We are currently using the slash (/) to prepend all reference attributes (href, src, require, etc.). We need to edit these links to reflect the new location of our project on the new server. We will be required to update all links and references on all pages of our site. In order to make this process more maintainable in the future, we are going to define another site constant and use it to prepend our site links and references.

In the front controller where we process the REQUEST_URI, we need to add the new constant definition and modify the $path process.

<?php
...

define("ROOT_PATH", dirname(__DIR__));
// add the new WEB_ROOT definition to match the location of your project on the new server
// Change the AvatarName to the avatar name of your project folder on the new server
define("WEB_ROOT", "/webdev/AvatarName/public/");

// remove the following line
// $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
// add this line with the WEB_ROOT reference
$path = str_replace(WEB_ROOT, "", parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));

// the autoloader stays the same
spl_autoload_register(function (string $class_name) {

    require ROOT_PATH . "/src/" . str_replace("\\", "/", $class_name) . ".php";
});

Make sure you add the same ROOT_PATH prefix to both of the require error views as well.

<?php
...
require ROOT_PATH . "/views/$template";

Go through the Products.php controller and update the Header() redirect links. There should be 3.

<?php
...
header("Location: " . WEB_ROOT . "products/{$id}/show");

Add a site nav menu to the header.php view so all pages will have a site nav menu. Edit the menu links to add the WEB_ROOT prefix.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= $title ?></title>
    <link rel="stylesheet" href="<?= WEB_ROOT ?>css/style.css">
</head>

<body>
    <nav>
        <a href="<?= WEB_ROOT ?>home">Home</a>
        <a href="<?= WEB_ROOT ?>products">Products</a>
        <a href="<?= WEB_ROOT ?>products/new">New Product</a>
    </nav>

Go through the other views and add the WEB_ROOT prefix to all a tag href values and form action attributes.


<!-- form tag -->
<form action="<?= WEB_ROOT ?>products/<?= $product["id"] ?>/delete" method="post">

<!-- link tag -->
<p><a href="<?= WEB_ROOT ?>products/<?= $product["id"] ?>/show">Cancel</a></p>

Final Site Migration and Evaluation

Add any remaining CSS styles you want for your site. You can add images, layouts and whatever else you want. Organize these resources in the public directory like any other site.

Now it's time to upload your files to the production server. Make sure you test all the pages and functionality of your site and correct any errors you find. If you get errors while testing your site, check the error log file in the public folder for server caught errors. Make sure you turn off error reporting in the front controller when you are finished. Also, don't forget to edit the .env file so the database credentials match the production server.