فهرست منبع

Demo of PHP based routing with htaccess redirects and some templating

Lukas Angerer 4 سال پیش
کامیت
9584b4872e

+ 29 - 0
default.conf

@@ -0,0 +1,29 @@
+<VirtualHost *:80>
+        # The ServerName directive sets the request scheme, hostname and port that
+        # the server uses to identify itself. This is used when creating
+        # redirection URLs. In the context of virtual hosts, the ServerName
+        # specifies what hostname must appear in the request's Host: header to
+        # match this virtual host. For the default virtual host (this file) this
+        # value is not decisive as it is used as a last resort host regardless.
+        # However, you must set it for any further virtual host explicitly.
+        #ServerName www.example.com
+
+        ServerAdmin webmaster@localhost
+        DocumentRoot /var/www/app/wwwroot
+
+        # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
+        # error, crit, alert, emerg.
+        # It is also possible to configure the loglevel for particular
+        # modules, e.g.
+        #LogLevel info ssl:warn
+
+        ErrorLog ${APACHE_LOG_DIR}/error.log
+        CustomLog ${APACHE_LOG_DIR}/access.log combined
+
+        # For most configuration files from conf-available/, which are
+        # enabled or disabled at a global level, it is possible to
+        # include a line for only one particular virtual host. For example the
+        # following line enables the CGI configuration for this host only
+        # after it has been globally disabled with "a2disconf".
+        #Include conf-available/serve-cgi-bin.conf
+</VirtualHost>

+ 12 - 0
docker-compose.yml

@@ -0,0 +1,12 @@
+version: "3.0"
+services:
+  php:
+    image: "phprewrite:8.0.3"
+    build:
+      context: ./services
+      dockerfile: ./Dockerfile-php
+    volumes:
+      - "./default.conf:/etc/apache2/sites-enabled/000-default.conf"
+      - ".:/var/www/app"
+    ports:
+      - 8421:80

+ 2 - 0
services/Dockerfile-php

@@ -0,0 +1,2 @@
+FROM php:8.0.3-apache
+RUN a2enmod rewrite

+ 28 - 0
src/autoloader.php

@@ -0,0 +1,28 @@
+<?php
+
+class AutoLoader
+{
+    private static $loader;
+
+    public static function loadClass($class)
+    {
+        self::$loader->loadClassInternal($class);
+    }
+
+    public static function registerLoader()
+    {
+        if (null !== self::$loader)
+        {
+            return self::$loader;
+        }
+
+        spl_autoload_register(array("AutoLoader", "loadClass"), true, true);
+
+        self::$loader = new AutoLoader();
+    }
+
+    public function loadClassInternal($class)
+    {
+        require __DIR__ . "/lib/" . $class . ".php";
+    }
+}

+ 15 - 0
src/bootstrap.php

@@ -0,0 +1,15 @@
+<?php
+
+// Having an "autoloader" (see https://www.php.net/manual/en/language.oop5.autoload.php) allows us to just
+// use classes without having to worry where exactly those PHP files are located. This particular autoloader
+// is extremely simple.
+require_once __DIR__ . "/autoloader.php";
+AutoLoader::registerLoader();
+
+// Since we now have an autoloader, we can just "use" the PageContext class and the autloader will actually
+// find and load it. The page context represents all the contextual information that we have in our current
+// request and we use it to define our page rendering mechanism. This mechanism is mostly based on some
+// very simple conventions for where certain types of files are placed.
+$context = new PageContext($_SERVER["REQUEST_URI"]);
+// We just call "render" which will do the rest for us.
+$context->render();

+ 72 - 0
src/lib/PageContext.php

@@ -0,0 +1,72 @@
+<?php
+
+class PageContext
+{
+    // Initial request path
+    public $requestPath;
+    // Path to the actual target page PHP file
+    public $targetPage;
+    // Title of the page - this is usually set in the page PHP file
+    public $title;
+    // This holds the "main content" of the page that will be wrapped in a simple HTML wrapper
+    public $content;
+    // The currently logged in user - if the user is actually logged in
+    public $user;
+
+    public function __construct($requestPath)
+    {
+        $this->requestPath = $requestPath;
+
+        $targetPage = $this->requestPath;
+        if ($targetPage == "/")
+        {
+            $targetPage = "/home";
+        }
+
+        $targetPage = __DIR__ . "/../pages" . $targetPage . ".php";
+
+        $this->targetPage = $targetPage;
+        $context = array(
+            "requestUri" => $_SERVER["REQUEST_URI"],
+            "targetPage" => $targetPage,
+            "template" => function ($path) {
+                echo "TEMPLATE: " . $path;
+            },
+        );
+
+        $this->user = User::loadFromContext();
+    }
+
+    public function render()
+    {
+        $context = $this;
+
+        // Start output buffering - everything that is "printed" while we are buffering
+        // is captured for later use which allows us to insert the content into a larger
+        // construct like the overall page template.
+        ob_start();
+
+        if (is_file($this->targetPage))
+        {
+            include $this->targetPage;
+        }
+        else
+        {
+            include __DIR__ . "/../pages/404.php";
+        }
+
+        // End buffering and return its contents
+        $this->content = ob_get_clean();
+
+        // Now we take that "content" and wrap it inside of a page template so that the result
+        // will be a fully functional HTML page.
+        $this->template("html");
+    }
+
+    public function template($path, $variables = array())
+    {
+        $context = $this;
+        extract($variables);
+        include __DIR__ . "/../templates/" . $path . ".php";
+    }
+}

+ 20 - 0
src/lib/User.php

@@ -0,0 +1,20 @@
+<?php
+
+class User
+{
+    public $isAuthenticated;
+    public $username;
+
+    public static function loadFromContext()
+    {
+        // This is of course just dummy code to pretend that we have some user
+        // and login information.
+        return new User("Test Osteron");
+    }
+
+    public function __construct($username)
+    {
+        $this->username = $username;
+        $this->isAuthenticated = true;
+    }
+}

+ 5 - 0
src/pages/404.php

@@ -0,0 +1,5 @@
+<?php
+    $context->title = "Not Found";
+?>
+<h1>404 - Not Found</h1>
+<p>The page with the path <code><?= $context->targetPage ?></code> does not exist</p>

+ 20 - 0
src/pages/home.php

@@ -0,0 +1,20 @@
+<?php
+    // Here we can set the <title> of the page. Since we actually render the "main content" (i.e. this file here)
+    // before we execute the wrapper HTML template (src/templates/html.php), we can define the value here and it
+    // can be used in the wrapper code.
+    $context->title = "Home!!!";
+?>
+<!-- Wome actual HTML content -->
+<h1>🏠 Home</h1>
+<!--
+    We can also use other, smaller "templates" to build up our page. Here, we just load the navigation snippet
+    that we can see in src/templates/navigation.
+-->
+<?= $context->template("navigation") ?>
+
+<?php
+    if ($context->user != null)
+    {
+        print '<div>You are logged in as <span class="username">' . $context->user->username . '</span></div>';
+    }
+?>

+ 19 - 0
src/pages/welcome.php

@@ -0,0 +1,19 @@
+<?php
+    $context->title = "Welcome";
+?>
+<h1>Welcome</h1>
+<?= $context->template("navigation") ?>
+<p>
+    Of course, there is much to say here, but alas! I am at a loss for words
+</p>
+<!--
+    We can also do some "fancy" stuff with templates like passing additional parameters to them so
+    that they can create a slightly different result depending on where / how exactly they are called.
+-->
+<?= $context->template("block", array(
+    "title" => "Welcome Block",
+    "content" => array(
+        "some text",
+        "some more text",
+    )
+)) ?>

+ 15 - 0
src/pages/welcome/details.php

@@ -0,0 +1,15 @@
+<?php
+    $context->title = "Welcome / Details";
+?>
+<h1>Details</h1>
+<?= $context->template("navigation") ?>
+<p>
+    Here, we have a "child page" of the "Welcome" page.
+</p>
+<?= $context->template("block", array(
+    "title" => "Detail Block",
+    "content" => array(
+        "we are using the same 'block template' as for the welcome page",
+        "just with different parameters",
+    )
+)) ?>

+ 11 - 0
src/templates/block.php

@@ -0,0 +1,11 @@
+<hr />
+<div>
+    <h4><?= $title ?></h4>
+    <?php
+        for ($i = 0; $i < count($content); $i++)
+        {
+            print "<p>" . $content[$i] . "</p>";
+        }
+    ?>
+</div>
+<hr />

+ 10 - 0
src/templates/html.php

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title><?= $context->title ?></title>
+        <link rel="stylesheet" type="text/css" href="/main.css">
+    </head>
+    <body>
+        <?= $context->content ?>
+    </body>
+</html>

+ 5 - 0
src/templates/navigation.php

@@ -0,0 +1,5 @@
+<ul>
+    <li><a href="/">Home</a></li>
+    <li><a href="/welcome">Welcome</a></li>
+    <li><a href="/welcome/details">Details (Welcome)</a></li>
+</ul>

+ 10 - 0
wwwroot/.htaccess

@@ -0,0 +1,10 @@
+RewriteEngine on
+
+# block files and folders beginning with a dot, such as .git
+# except for the .well-known folder, which is used for Let's Encrypt and security.txt
+RewriteRule (^|/)\.(?!well-known\/) index.php [L]
+
+# make site links work
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule ^(.*) index.php [L]

+ 6 - 0
wwwroot/index.php

@@ -0,0 +1,6 @@
+<?php
+
+// With the rewrite rules in .htaccess ALL requests that don't match a file that is actually in
+// the wwwroot are "routed" through this index.php file. All we need to do here is to initiate
+// our "request pipeline" that eventually renders the requested page.
+require __DIR__ . '/../src/bootstrap.php';

+ 8 - 0
wwwroot/main.css

@@ -0,0 +1,8 @@
+
+body {
+    font-family: Arial, Helvetica, sans-serif;
+}
+
+.username {
+    color: red;
+}