Chapter 3: Architecting the Multi-Site Environment

Introduction: One Codebase, Many Sites

The foundation of our multi-site platform isn't in PHP, but a level below it: the web server. Before a single line of PHP is executed, the server must recognise an incoming request for one domain and route it to our single application, specifying which site it needs to be. This chapter provides the blueprint for that environment.

The Virtual Host Configuration

Our architecture relies on a single, comprehensive Virtual Host file for each site. This file contains all the directives needed to manage traffic, performance, security, and routing for that specific domain. By keeping all rules within this single file and disabling .htaccess overrides, we achieve the best possible performance.

Here is the complete, production-ready virtual host file for our book's sample site, oophp.uk.

# /etc/apache2/sites-available/oophp.uk.conf

# --- HTTP to HTTPS Redirect ---
<VirtualHost *:80>
    ServerName oophp.uk
    ServerAlias www.oophp.uk
    Redirect permanent / [https://oophp.uk/](https://oophp.uk/)
</VirtualHost>

# --- Main SSL-enabled Virtual Host ---
<VirtualHost *:443>
    # --- Core Settings ---
    ServerName oophp.uk
    ServerAlias www.oophp.uk
    DocumentRoot "/var/www/oophp.uk"
    ServerAdmin author@oophp.uk
    Protocols h2 http/1.1

    # --- Application Configuration (Variables for PHP) ---
    Include /var/www/siteEnVars/common
    Include /var/www/siteEnVars/oophp.uk

    # --- Logging ---
    ErrorLog "/var/www/siteLogs/errorLogs/oophp.uk.log"
    CustomLog "/var/www/siteLogs/accessLogs/oophp.log" common

    # --- Security Headers ---
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"

    # --- Performance & Caching ---
    <IfModule mod_deflate.c>
        AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript
    </IfModule>
    <IfModule mod_expires.c>
        ExpiresActive On
        ExpiresDefault "access plus 5 days"
        ExpiresByType text/html "access plus 1 day"
        ExpiresByType image/jpeg "access plus 60 days"
        ExpiresByType image/png "access plus 60 days"
        ExpiresByType image/gif "access plus 60 days"
        ExpiresByType text/javascript "access plus 1 day"
        ExpiresByType text/css "access plus 1 day"
    </IfModule>

    # --- Custom Error Pages ---
    ErrorDocument 403 /error_pages/403.html
    ErrorDocument 404 /error_pages/404.html
    ErrorDocument 500 /error_pages/500.html
    
    # --- Routing & Security Rules ---
    RewriteEngine On

    # Rule 1: Redirect www to non-www
    RewriteCond %{HTTP_HOST} ^www\.oophp\.uk [NC]
    RewriteRule ^(.*)$ [https://oophp.uk/$1](https://oophp.uk/$1) [L,R=301]

    # Rule 2: Block bad bots
    RewriteCond %{HTTP_USER_AGENT} "Mb2345Browser|LieBaoFast|zh-CN|MicroMessenger|zh_CN|Kinza|Datanyze|serpstatbot|spaziodati|OPPO\sA33|AspiegelBot|aspiegel|PetalBot|Bytespider|Amazonbot" [NC]
    RewriteRule .* - [F,L]

    # Rule 3: Front Controller
    RewriteCond %{REQUEST_URI} !/index.php
    RewriteCond %{REQUEST_URI} !/favicon.ico
    RewriteCond %{REQUEST_URI} !/_r
    RewriteCond %{REQUEST_URI} !/robots.txt
    RewriteCond %{REQUEST_URI} !/ads.txt
    RewriteCond %{REQUEST_URI} !/manifest.json
    RewriteCond %{REQUEST_URI} !/sw.js
    RewriteCond %{REQUEST_URI} !/sitemaps
    RewriteCond %{REQUEST_URI} !/sitemap.xml
    RewriteCond %{REQUEST_URI} !/fileUpload
    RewriteCond %{REQUEST_URI} !/program.log
    RewriteCond %{REQUEST_URI} !/stripeEndPoint
    RewriteRule ^(.*)$ /index.php [L,QSA]

    # --- SSL Configuration ---
    SSLCertificateFile /etc/letsencrypt/live/oophp.uk/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/oophp.uk/privkey.pem
    Include /etc/letsencrypt/options-ssl-apache.conf

    # --- Directory Permissions ---
    <Directory "/var/www/oophp.uk">
        AllowOverride None
        Require all granted
        AddDefaultCharset UTF-8
        AddType image/x-icon .ico
        Include /var/www/siteEnVars/cors-whitelist.conf
    </Directory>

    <Directory "/var/www/oophp.uk/_r">
        Options -Indexes
    </Directory>
</VirtualHost>

##Constants vs. Variables

In our application, we use define() or const to create constants for values that never change, such as file paths or configuration settings. A strong and long-standing convention in PHP is to declare these constants in UPPER_CASE_WITH_UNDERSCORES.

PHP

define('CLASS_DIRECTORY', '/var/www/AvMoz-Codebase/Classes/');

While it might seem jarring at first, this is a professional best practice for a key reason: readability. When you or another developer sees an identifier in all caps, it immediately signals that its value is a constant and is not expected to change. It visually distinguishes constants from variables ($camelCase) and class names (PascalCase).

This convention is codified in the PSR-1 Basic Coding Standard, which is the foundational style guide for the modern PHP community. Adhering to this standard makes your code more familiar and easier to read for other developers.

Conclusion: A Solid Foundation

This server-level architecture is the bedrock of our integrated application. By handling site identification and all major rules within Apache, our PHP code remains clean, generic, and decoupled from the domains it serves. This approach is not only performant and secure, but it makes adding a new site to the platform a trivial task: create a new database, add a new environment file, and enable a new virtual host. The core application requires no changes.

Shopping summary

Item

SubTotal: £

Empty Basket

this is a hidden panel

Completed in milliseconds

Completed in milliseconds

This is the top Panel

This is the bottom Panel