2
Docs2
2
ApplicationController
A detailed overview of the PHPainfree2
ApplicationController.
includes/App.php
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
.
What | Where | How |
---|---|---|
Defined | PainfreeConfig.php | $PainfreeConfig['ApplicationController']; |
Called | Painfree.php | $Painfree->logic(); |
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.
<?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;
}
Directly below the instantiation, you can see a few lines commented out with a recommendation for how to build these types of ApplicationControllers.
A common PHPainfree2
design pattern is to attach several
other required object Singletons to the primary ApplicationController
Singleton. In the example code that is commented out, a
class User
singleton is imported
and instantiated as a public member of the $App
instance.
Because this example ApplicationController uses the $App
instance as the "application state" and execution director, it would
be typical to have several such instances defined and attached to
the $App
instance at run-time. There are several such
pre-defined example classes available in this repository, and you
can learn more about them at /docs/painfree-examples.
$App = new App();
// any internal classes should be defined below
// require_once 'includes/App/User.php';
// $App->User = new User();
// start routing and handle the request
$App->route();
$App->route()
Finally, on line 10, a tiny bit of magic happens with the call to
$App->route()
. This code, defined
inside the class is an example of an ApplicationController consuming
the structured route of $Painfree->route
to dynamically handle requests and pass them to the correct controller.
Using this function, or one like it, allows PHPainfree2
projects to orchestrate complex behaviors at URLs defined entirely
by the name of the first value in the URL path.
// start routing and handle the request
$App->route();
/**
* App Singleton
*/
class App {
// ...
$App->route()
➡️
$this->__setRoutes()
Inside $App->route()
, the first thing we do is call the
__setRoutes()
private method. This function takes the route
that PHPainfree2
has defined and splits it up into various
individual components. This ApplicationController has defined a routing
format as: /$VIEW/$ID/$ACTION
, so __setRoutes()
examines $Painfree->route
and assigns each piece to public
member variables in $App
App::setRoutes()
private function __setRoutes() : void {
$routes = explode('/', $this->route);
// default route is set in PainfreeConfig.php
if ( count($routes) ) {
$this->view = array_shift($routes);
} else {
$this->view = $this->route;
}
if ( count($routes) ) {
$this->id = array_shift($routes);
}
if ( count($routes) ) {
$this->action = array_shift($routes);
}
}
/**
* $App->route() takes the $Painfree->route value and attempts
* to load a controller from `/includes/Controllers/`.
*
* @return void
*/
public function route() : void {
global $Painfree;
// the default routing pattern is
// /:VIEW/:ID/:ACTION
$this->__setRoutes();
header('X-Frame-Options: SAMEORIGIN');
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.
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.
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" />
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';
?>