Skip to content

Instantly share code, notes, and snippets.

@pimbrouwers
Last active July 20, 2025 12:54
Show Gist options
  • Select an option

  • Save pimbrouwers/93fc248c6ac1a0276271b6422180e1c5 to your computer and use it in GitHub Desktop.

Select an option

Save pimbrouwers/93fc248c6ac1a0276271b6422180e1c5 to your computer and use it in GitHub Desktop.
Turnpike

Turnpike

<?php
$db = new Turnpike\Db($dsn, DB_USER, DB_PASS);
$viewEngine = new Turnpike\PhpViewEngine(TEMPLATE_ROOT);
$router = new Turnpike\Router;

// Middleware 
$router->Run(function (Turnpike\Request $req, Turnpike\Response $res) {
  // ...
}
          
// Controllers
$userController = new MyApp\Features\User\UserController($viewEngine);
$router->RouteMethod('/^\/user\/login\/?$/i', $userController, 'Login');
          
// Handle incoming request 
$router->Execute(function ($request, $response) use ($viewEngine) {
  $response->setBody($viewEngine->render('404.php'));
});
<?php
namespace Turnpike;
use PDO;
class Db extends PDO
{
public function __construct($dsn, $username = null, $password = null, $options = [])
{
$default_options = [
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
];
$options = array_replace($default_options, $options);
parent::__construct($dsn, $username, $password, $options);
}
public function Execute($sql, $args = null)
{
if (!$args) {
return $this->query($sql);
}
$stmt = $this->prepare($sql);
$stmt->execute($args);
return $stmt;
}
}
class Request
{
private $method;
private $uri;
private $headers;
private $query;
public function __construct()
{
$args = array(
'REQUEST_METHOD' => FILTER_DEFAULT,
'QUERY_STRING' => FILTER_DEFAULT,
'REQUEST_URI' => FILTER_DEFAULT);
$serverVars = filter_input_array(INPUT_SERVER, $args);
$this->method = $serverVars['REQUEST_METHOD'];
$this->uri = str_replace('?' . $serverVars['QUERY_STRING'], '', $serverVars['REQUEST_URI']);
$this->headers = getallheaders() ?? array();
$this->query = filter_input_array(INPUT_GET);
}
public function GetMethod: string
{
return $this->method;
}
public function GetUri(): string
{
return $this->uri;
}
public function GetQuery(): array
{
return !empty($this->query) ? $this->query : [];
}
public function GetBody() : array
{
switch ($this->method) {
case 'POST':
return filter_input_array(INPUT_POST);
default:
return array();
}
}
public function GetHeaders() : array
{
return $this->headers;
}
public function GetCookie(string $name)
{
return filter_input(INPUT_COOKIE, $name, FILTER_SANITIZE_STRING);
}
}
class Response
{
private $headers = array();
private $statusCode = 200;
private $body = '';
public function AddHeader(string $name, string $value): void
{
$this->headers[$name] = $value;
}
public function SetContentType(string $contentType)
{
$this->AddHeader('Content-Type', $contentType);
}
public function GetStatusCode(): int
{
return $this->statusCode;
}
public function SetStatusCode(int $code): void
{
$this->statusCode = $code;
}
public function SetCookie(string $name, string $content, array $options = [])
{
$defaults = [
'expires' => 0,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict'
];
setcookie($name, $content, $defaults + $options);
}
public function RemoveCookie(string $name)
{
setcookie($name, '', 1, '/');
}
public function GetBody(): string
{
return $this->body;
}
public function SetBody(string $body): void
{
$this->body = $body;
}
public function Execute(): void
{
foreach ($this->headers as $k => $v) {
header($k . ': ' . $v);
}
http_response_code($this->statusCode);
echo $this->body;
exit;
}
public function Redirect(string $url)
{
header('Location: '.$url, true, 301);
exit();
}
}
class Router
{
private $routes = array();
private $middleware = array();
public function Route(string $pattern, $callback)
{
$this->routes[$pattern] = $callback;
}
public function RouteMethod(string $pattern, $instance, string $method)
{
$this->routes[$pattern] = [$instance, $method];
}
public function Run(callable $middleware)
{
array_push($this->middleware, $middleware);
}
public function Execute(callable $notFoundHandler = null)
{
$request = new Request;
$response = new Response;
$notFound = true;
foreach ($this->middleware as $k => $m) {
if (is_callable($m)) {
call_user_func_array($m, array($request, $response));
}
}
foreach ($this->routes as $pattern => $action) {
if (is_callable($action) || is_array($action)) {
if (preg_match($pattern, $request->getUri(), $params)) {
$notFound = false;
call_user_func_array($action, array_merge(array($request, $response), array_slice($params, 1)));
}
}
}
if ($notFound && is_callable($notFoundHandler)) {
$response->setStatusCode(404);
call_user_func_array($notFoundHandler, array($request, $response));
}
$response->execute();
}
}
interface ViewEngine
{
public function render(string $templatePath, $data = null): string;
}
class PhpViewEngine implements ViewEngine
{
private $templateRoot;
function __construct(string $templateRoot)
{
$this->templateRoot = $templateRoot;
}
function render(string $templatePath, array $data = [])
{
extract($data, EXTR_SKIP);
$filePath = $this->templateRoot . $templatePath;
if (!file_exists($filePath)) {
throw new \Exception("Template file not found: " . $filePath);
}
ob_start();
include_once($this->templateRoot . $templatePath);
return ob_get_clean();
}
}
class Page
{
private $viewEngine;
public function __construct(ViewEngine $viewEngine)
{
$this->viewEngine = $viewEngine;
}
public function Render(string $templatePath, $data = null): string
{
return $this->viewEngine->render($templatePath, $data);
}
}
class Controller
{
protected $viewEngine;
function __construct(PhpViewEngine $viewEngine)
{
$this->viewEngine = $viewEngine;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment