2
Docs2
2
Program Structure
A detailed overview of the PHPainfree2
framework structure.
By default, PHPainfree2
is designed to provide just enough
structure to allow you to rapidly build complex PHP-powered web sites and
applications.
The default project structure focuses on three main top-level directories:
htdocs/
- publicly accessible files and program entry-point.includes/
- Application objects, code, and Controllers.templates/
- Application views, templates, and partials.PHPainfree/
|-- htdocs/
| |-- .htaccess
| |-- index.php
| |-- css/
| |-- js/
| `-- images/
|-- includes/
| |-- PainfreeConfig.php
| |-- Painfree.php
| |-- App.php
| `-- Controllers/
`-- templates/
|-- app.php
`-- views/
2
Request Flow
Before diving in to PHP and looking at specific files, it's important
to understand the flow of a request into a PHPainfree2
application.
htdocs/.htaccess
When a request comes in to your Apache server running a PHPainfree2
application, the request is first processed by .htaccess
(1). This
Apache .htaccess file will ignore any URL that is trying to retrieve a file
or directory that exists, but for all other URLs will grab the entire request
path and rewrite the URL to index.php?route=$1
.
Thus, a request to server.com/some/path/to/load
will be
transformed into index.php?route=/some/path/to/load
, adding
the entire path into a $_REQUEST['route']
query parameter
and passing off the entire request to index.php
(2).
RewriteEngine On
RewriteBase /
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule ^(.+)$ index.php?route=$1&%{QUERY_STRING} [L]
PHPainfree/
|-- htdocs/
| |-- .htaccess
| |-- index.php
| |-- css/
| |-- js/
| `-- images/
|-- includes/
| |-- PainfreeConfig.php
| |-- Painfree.php
| |-- App.php
| `-- Controllers/
`-- templates/
|-- app.php
`-- views/
index.php
Once the request has been processed and passed off to htdocs/index.php
,
PHPainfree2
takes over.
<?php
set_include_path(get_include_path() . PATH_SEPARATOR . '../');
// Uncomment if you're using Composer for PHP modules
// require realpath('../vendor/autoload.php');
include 'includes/Painfree.php';
PHPainfree/
|-- htdocs/
| |-- .htaccess
| |-- index.php
| |-- css/
| |-- js/
| `-- images/
|-- includes/
`-- template/
2
Framework - includes/Painfree.php
Painfree.php
does a tiny bit of setup, and first loads
includes/PainfreeConfig.php
(4) before creating an
instance of class PHPainfree
.
$__painfree_start_time
].
require 'PainfreeConfig.php';
to bring in your application configuration settings.
class PHPainfree
to
process the rest of the request and pass application control to the Application
Singleton. $Painfree = new PHPainfree($PainfreeConfig);
.php
files
located in the includes/Autoload
folder.
PainfreeConfig.php
file.
include $Painfree->logic();
PainfreeConfig.php
file.
include $Painfree->view();
$__painfree_start_time = microtime(true);
require 'PainfreeConfig.php'; // you must have this file
$Painfree = new PHPainfree($PainfreeConfig);
$Painfree->URI = $_SERVER['SERVER_PORT'] == 80 ? 'http://' : 'https://';
$Painfree->URI .= $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'];
// process Autoload folder
$loaders = $Painfree->autoload();
foreach ( $loaders as $load ) {
include $load;
}
include $Painfree->logic(); // load the application logic controller
include $Painfree->view(); // load the view
class PHPainfree {
// cut for brevity
PHPainfree/
|-- htdocs/
|-- includes/
| |-- PainfreeConfig.php
| |-- Painfree.php
| |-- App.php
| `-- Controllers/
`-- templates/
includes/PainfreeConfig.php
PainfreeConfig.php
, read in by Painfree.php
and
passed directly into the new PHPainfree($PainfreeConfig);
constructor, does a lot of the heavy lifting in application setup.
This file contains a single array named $PainfreeConfig
that defines some of the default routes, connects to the database, and specifies
specific folders and scripts necessary for PHPainfree2
.
When you first download PHPainfree2
, the default
configuration provided loads a copy of this application and documentation
to serve as an example of how to structure a project. However, this
framework was designed to be non-opinionated, meaning
that you are intended to use this framework in a manner that best suits
your design intentions.
ApplicationController
, BaseView
, and
DefaultRoute
should be considered a starting-point
to launch your development efforts. In most projects, you'll most-likely
want to rename the ApplicationController from App.php
to
YourProjectName.php
as well as changing the name of the
class.
The class App {}
singleton instance
that PHPainfree2
will create handles all of the routing
and business logic for your program, so it's helpful to pick a short
name for this class and the instance it will create that matches your
goals for your project. This will be covered in more detail in step 5, below.
PHPainfree/
|-- htdocs/
|-- includes/
| |-- PainfreeConfig.php
| |-- Painfree.php
| |-- App.php
| `-- Controllers/
`-- templates/
<?php
$PainfreeConfig = array(
'ApplicationController' => 'App.php',
'BaseView' => 'app.php',
'DefaultRoute' => 'main',
'Database' => array(
'PrimaryDB' => array(
'host' => $_ENV['MYSQL_HOST'],
'user' => $_ENV['MYSQL_USER'],
'pass' => $_ENV['MYSQL_PASSWORD'],
'schema' => $_ENV['MYSQL_SCHEMA'],
'port' => $_ENV['MYSQL_PORT'],
),
),
'RouteParameter' => 'route',
'TemplateFolder' => 'templates',
'LogicFolder' => 'includes',
);
includes/App.php
Now comes the fun part!
This file is the heart of your specific application or website. At
this point, PHPainfree2
is almost completely finished
with all the magic that it does and you're expected to do the rest
of the application development yourself.
The provided App.php
makes an excellent starting point
for your project. This script, and the singleton you're expected to create,
will serve as the overall application state of your program for each
and every HTTP request that is passed to the server and handled by
PHPainfree2
.
For more detailed information about the ApplicationController, please see /docs/painfree-application-controller.
PHPainfree/
|-- htdocs/
|-- includes/
| |-- PainfreeConfig.php
| |-- Painfree.php
| |-- App.php
| `-- Controllers/
`-- templates/
class App
Overview
The provided ApplicationController is a stripped down version of several production ApplicationControllers that have been developed for several large companies serving hundreds of thousands of requests for complicated highly-interactive data-driven web applications. This file is an excellent starting point for projects of all sizes, ranging from quick Hackathon projects to high-availability production web applications.
includes/App.php
At the top of the ApplicationController, the code creates a global
Singleton instance of class App
defined
in this file.
This ApplicationController is just code that is automatically executed
by $Painfree->logic()
and
DOES NOT have to be fully defined in this file.
Some products are better suited having the ApplicationController
class stored in a different file and defined with proper namespacing,
or perhaps loaded automatically through a PSR-4 composer autoload
mechanism.
Build things to suit YOUR needs, not the framework's needs.
The ApplicationController is explored in detail in /docs/painfree-application-controller, but we'll quickly touch on the "magic" that happens here as a part of the request.
<?php
// It's common to rename the "App" class and object instance to match
// your specific product. Feel free to leave it as $App or rename it.
$App = new App();
class App {
// snipped...
public function __construct() {
global $Painfree;
$this->BASE_PATH = str_replace('/htdocs', '', $_SERVER['DOCUMENT_ROOT']);
// $this->db = $Painfree->db;
// OR
// require_once 'core/MySQLiHelpers.php';
// $this->db = new MySQLiHelpers($Painfree->db);
// Set up the route and prepare for routing
$this->route = $Painfree->route;
}
Now that the route variables have been prepared, the route()
method does the next four pieces of magic in a few short lines of code.
$App->view
.
HX-Request
is set
and set a boolean for our templates to use.
HX-Boosted
is set
and set a boolean for our templates to use.
Content-Type
is
set to "application/json" or a query parameter of ?json
was sent with the URL, and if so, set the response Content-Type and
immediately die and send back the contents of $this->data
encoded as JSON.
$App->route() : void;
In the case of an API JSON request, execution will immediately halt
and output the contents of $App->data
as a JSON blob. In all other cases, execution returns to
$Painfree
to render and load the templates.
In this ApplicationController, we leave execution having performed these important tasks:
$App->view
,
$App->id
, and
$App->action
.
includes/Controllers/{$App->view}.php
.
htmx
in $App->htmx
and
$App->htmx_boosted
.
header('X-Frame-Options: SAMEORIGIN');
if (file_exists("{$this->BASE_PATH}/includes/Controllers/{$this->view}.php")) {
require_once "Controllers/{$this->view}.php";
$headers = apache_request_headers();
if ( isset($headers['HX-Request']) &&
$headers['HX-Request'] === 'true' ) {
$this->htmx = true;
}
if ( isset($headers['HX-Boosted']) &&
$headers['HX-Boosted'] === 'true'
) {
$this->htmx_boosted = true;
}
if ( isset($headers['Content-Type']) &&
$headers['Content-Type'] === 'application/json' ) {
header('Content-Type: application/json; charset=utf-8');
die(json_encode($this->data));
}
} else {
header('HTTP/1.0 404 Not Found');
// We don't "die" here so that the developer can render a
// nice looking 404 template if they want.
}
After completing the route()
method, execution returns
to $Painfree
and $Painfree->view();
is called. This function loads the script defined in your PainfreeConfig.php
BaseView option, which is the script located in templates/
. In
the PainfreeConfig provided with this project, the initial BaseView template
is defined as templates/app.php
.
Your initial BaseView template is often going to be the most complicated
template in your project, performing double-duty as an HTML skeleton for
the rest of your views as well as dynamically loading view templates based
on the URL. In PHPainfree2
, this template also has code to handle
htmx partial templates.
Inside of templates/app.php
in the meat of our web template,
we'll load a template file in the template/views/
directory
if one exists with the same name as the value stored in
$App->view
.
In PainfreeConfig, we have our DefaultRoute
parameter
defined as "main"
, so our application will serve
templates/views/main.php
for any request to either
http://hostname.com/
(no path) as well as explictly called
like http://hostname.com/main
. The value of that view, "main"
automatically loads:
main
.phpmain
.php
This is one of the designs that makes developing projects with
PHPainfree2
so quick. Adding new pages is as simple as
dropping a file in the includes/Controllers/
folder and
templates/views/
folder. And as you develop more complicated
applications, allowing all of your Controller code to serve as the
business logic for your REST JSON API, you're able to do a lot more
with a lot less duplication.
<body id="app-body" class="bg-dark text-light">
<?php
include 'header.php';
?>
<?php
if ( file_exists("{$App->BASE_PATH}/templates/views/{$App->view}.php") ) {
include "views/{$App->view}.php";
} else {
include "views/404.php";
}
?>
<?php
include 'footer.php';
?>
PHPainfree/
|-- htdocs/
|-- includes/
`-- templates/
|-- app.php
`-- views/
`-- main.php
<?php
if (
$App->htmx &&
! $App->htmx_boosted &&
file_exists("{$App->BASE_PATH}/templates/views/{$App->view}.php")
) {
// If we are an htmx request and the "view" variable exists in the top-level
// templates folder, render that as an HTMX snippet.
//
// If we are an htmx request and there is a "sub-view" defined that lives
// inside a folder, render _THAT_ instead of the full top-level snippet.
//
// In _this_ application, we're overriding $App->id to act as our default
// "sub-view" route, but you should feel free to write whatever type of
// routing architecture that you want.
//
// This example requires that a top-level /templates/views/{$view}.php file
// exists **AND** a top-level /templates/views/{$view}/{$id}.php file to
// exist for this magic to occur.
//
// Each application built with PHPainfree should design their routing and
// template relationships however best suits that product.
$file_path = "{$App->BASE_PATH}/templates/views/{$App->view}/{$App->id}.php";
if (file_exists($file_path)) {
include_once "views/{$App->view}/{$App->id}.php";
} else {
include_once "views/{$App->view}.php";
}
} else {
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title><?= $Painfree->safe($App->title()); ?></title>
<link rel="icon" type="image/x-icon" href="/images/favicon.ico" />