Skip to content

Instantly share code, notes, and snippets.

@Nicd
Last active March 3, 2017 23:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Nicd/cca4875b8615349aead4004c9f13971b to your computer and use it in GitHub Desktop.
Save Nicd/cca4875b8615349aead4004c9f13971b to your computer and use it in GitHub Desktop.
PHP project base with MVT model, simple middleware system, DB basics, router, templates with shared layout
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Blah blah lorem ipsum.">
<meta name="author" content="Mikko Ahlroth">
<title>My Great Website</title>
<link rel="stylesheet" href="/app.css">
</head>
<body>
<div class="container">
<header class="header">
<div class="row">
<div class="col-xs-2">
<a class="logo" href="/"><img src="/my_great_logo.png" alt="Best site" title="Ever made" height="100%" /></a>
</div>
<div class="col-xs-10">
<nav role="navigation">
<ul class="nav nav-pills pull-right">
<li><a href="/login">Log in</a></li>
<li><a href="/signup">Sign up</a></li>
</ul>
</nav>
</div>
</div>
</header>
<main role="main">
<?php $render_inner(); ?>
</main>
<footer>
<hr />
<p class="text-center">
<a href="/changes">0.0.1</a>
<?php list($time, $unit) = calculate_request_time($request_start_time); ?>
<?= $time ?>&nbsp;<?= $unit ?>
<a href="/api">API docs</a>
<a href="/terms">Legal</a>
<a href="/plugins">Plugins</a>
<a href="/irc">IRC</a>
</p>
<p class="text-center">
Made with PHP 7.
</p>
</footer>
</div> <!-- /container -->
</body>
</html>
<?php
// Name of default layout file
const DEFAULT_LAYOUT = 'app';
const DB_HOST = 'localhost';
const DB_PORT = 5432;
const DB_NAME = 'blah';
const DB_USER = 'blah';
const DB_PASS = '1234';
<?php
/**
* Utility class for database stuff.
*/
class DB {
private $pdo;
public function __construct($db_host, $db_port, $db_name, $db_user, $db_pass) {
$dsn = "pgsql:host=$db_host;port=$db_port;dbname=$db_name;user=$db_user;password=$db_pass";
//$this->pdo = new PDO($dsn);
//$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
public function getPDO() {
return $this->pdo;
}
}
<?php
require_once 'utils.php';
const TOTAL_XP_CACHE = [
['C++', 16625],
['Elixir', 15316],
['Java', 215],
['Elm', 315531],
['C', 3155],
['Qwerty', 316361],
['Plain text', 57427],
['Python', 42747]
];
function frontpage($request_data, $db) {
// Load total language XPs from "cache"
$total_lang_xps = TOTAL_XP_CACHE;
usort($total_lang_xps, function ($a, $b) {
if ($a[1] === $b[1]) {
return 0;
}
return ($a[1] > $b[1]) ? -1 : 1;
});
$total_xp = array_reduce($total_lang_xps, function ($acc, $lang) {
return $acc + $lang[1];
}, 0);
$most_popular = array_slice($total_lang_xps, 0, 10);
// Render template
render_with_layout('front', array_merge($request_data, [
'total_lang_xps' => $total_lang_xps,
'total_xp' => $total_xp,
'most_popular' => $most_popular
]));
}
<div class="jumbotron">
<h2>Welcome to My Great Website!</h2>
<p class="lead">Write code, level up, show off! A free stats tracking service for programmers.</p>
</div>
<div class="row">
<div class="col-xs-12">
<hr />
</div>
</div>
<div class="row" id="index-elm-container">
<div class="col-xs-12">
<div class="row">
<div class="col-xs-12 col-md-6">
<h3>
Total XP
</h3>
<h2>
<?= format_xp($total_xp) ?>
</h2>
</div>
<div class="col-xs-12 col-md-6">
<h3>
Most popular languages
</h3>
<ol>
<?php foreach($most_popular as $tuple): ?>
<li>
<?= $tuple[0] ?>: <?= format_xp($tuple[1]) ?>&nbsp;XP
</li>
<?php endforeach; ?>
</ol>
</div>
</div>
</div>
</div>
<?php
// Convert all errors to exceptions
function exception_error_handler($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
return;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler("exception_error_handler");
require_once 'utils.php';
require_once 'config.php';
require_once 'db.php';
require_once 'router.php';
require_once 'routes.php';
$db = new DB(DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASS);
$router = new Router(ROUTES, $db);
$router->run();
<?php
// Plugs are simple middleware that transform the request. They get
// the request data and database handle as arguments and must return
// the new request data that is passed forward to the next plug, or
// render content and return false to halt processing of the request.
// Set the request start time into the request
function request_time($request_data, $db) {
$request_data['request_start_time'] = microtime(true);
return $request_data;
}
<?php
function profilepage($request_data, $db) {
$username = substr($_SERVER['PATH_INFO'], strlen('/users/'));
$user = get_user($db, $username);
render_with_layout('profile', array_merge($request_data, [
'user' => $user
]));
}
<div class="row">
<div class="col-xs-12 col-sm-5">
<h2
id="profile-username"
data-name="<?= $user['username'] ?>"
>
<?= $user['username'] ?>
</h2>
<ul class="profile-detail-list">
<li>
Programming since
<time datetime="<?= $user['inserted_at']->format('Y-m-d') ?>">
<?= $user['inserted_at']->format('Y-m-d') ?></time>.
</li>
</ul>
</div>
</div>
<?php
/**
* Simple router that calls the given page function based on the request URL.
*
* The given routes list should contain 3-tuples of:
* 1. Regex for matching the URL
* 2. Function to execute on a match
* 3. Plugs to use in request. Plugs are passed $request_data and should return a new
* copy of it which will be passed to the next plug and then to the request function.
* The second argument passed to all plugs and the request function is the database
* handler ($db).
*
* The first match is used. If there is no match, a 404 error is thrown.
*/
class Router {
private $routes;
private $db;
private $request_data;
public function __construct($routes, $db) {
$this->routes = $routes;
$this->db = $db;
$this->request_data = [];
}
public function run() {
$path = $_SERVER['PATH_INFO'] ?? '/';
foreach ($this->routes as $route) {
if (preg_match($route[0], $path)) {
foreach ($route[2] as $plug) {
$new_data = call_user_func($plug, $this->request_data, $this->db);
// If plug returned false, stop processing
if ($new_data === false) {
return;
}
$this->request_data = $new_data;
}
call_user_func($route[1], $this->request_data, $this->db);
return;
}
}
}
}
<?php
require_once 'plugs.php';
require_once 'front.page.php';
require_once 'profile.page.php';
const ROUTES = [
['(^/users/)', 'profilepage', ['request_time']],
['(^/$)', 'frontpage', ['request_time']]
];
<?php
require_once 'config.php';
// Calculate time taken from given time to current time
function calculate_request_time($time) {
$now = microtime(true);
$diff = $now - $time;
return humanize_seconds($diff);
}
const UNIT_MAPPING = [
[1, 's'],
[1000, 'ms'],
[1000 * 1000, 'µs'],
[1000 * 1000 * 1000, 'ns']
];
function humanize_seconds($diff) {
foreach (UNIT_MAPPING as $unit) {
$m = $diff * $unit[0];
if ($m > 1) {
return [$m, $unit[1]];
}
}
}
/**
* Render the given template file with the given data extracted as variables
* and embed it inside the given layout.
*
* The given layout must contain the command $render_inner() which will be replaced
* with the inner content.
*/
function render_with_layout($template, $data, $layout=DEFAULT_LAYOUT) {
$render_inner = function () use ($template, $data) {
extract($data, EXTR_SKIP);
require "${template}.tpl.php";
};
extract($data, EXTR_SKIP);
require "${layout}.tpl.php";
}
// Get from array with default value
function get($arr, $key, $default=null) {
if (array_key_exists($key, $arr)) {
return $arr[$key];
}
return $default;
}
// Format XP nicely
function format_xp($xp) {
return number_format($xp);
}
// Get user from database (now just returns fake user)
function get_user($db, $username) {
//$pdo = $db->getPDO();
//$q = $pdo->prepare('SELECT u0."id", u0."username", u0."email", u0."inserted_at" FROM "users" AS u0 WHERE (u0."username" = ?)');
//$q->bindValue(1, $username, PDO::PARAM_STR);
//$q->execute();
//return $q->fetch(PDO::FETCH_ASSOC);
return [
'username' => $username,
'inserted_at' => new DateTimeImmutable('now')
];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment