Compare commits

...

3 commits

Author SHA1 Message Date
1549af5be7 perf: nullish coalescing of Content-Type header in Request/Router (#79)
Tiny refactor of the Content-Type header check which slightly improves the speed of this operation by not running through `array_key_exists()` first

Reviewed-on: https://codeberg.org/vegvisir/vegvisir/pulls/79
2025-07-20 07:11:07 +02:00
40f29a2799 refactor: RFC4288 parser support class cache file refactor (#77)
This PR essentially shuffles some of the methods around to follow our new style guide, and optimizes the cache file creation a very tiny bit. It needs more work in the future.

Reviewed-on: https://codeberg.org/vegvisir/vegvisir/pulls/77
2025-07-20 07:02:33 +02:00
f09e8eb766 refactor: bundle superglobal snapshot class as support file (#78)
This PR bundles my "GlobalSnapshot" library.. if you can even call it that, directly into Vegvisir. It's such a simple class that I don't think it needs to be loaded externally.

Reviewed-on: https://codeberg.org/vegvisir/vegvisir/pulls/78
2025-07-20 07:01:29 +02:00
7 changed files with 143 additions and 107 deletions

View file

@ -1,6 +1,5 @@
{ {
"require": { "require": {
"matthiasmullie/minify": "^1.3", "matthiasmullie/minify": "^1.3"
"vlw/globalsnapshot": "^1.0"
} }
} }

44
composer.lock generated
View file

@ -4,20 +4,20 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "44a5f71fb1b036c8e0a6f375537db6e6", "content-hash": "e7806260d775937d439159cde5165225",
"packages": [ "packages": [
{ {
"name": "matthiasmullie/minify", "name": "matthiasmullie/minify",
"version": "1.3.73", "version": "1.3.75",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/matthiasmullie/minify.git", "url": "https://github.com/matthiasmullie/minify.git",
"reference": "cb7a9297b4ab070909cefade30ee95054d4ae87a" "reference": "76ba4a5f555fd7bf4aa408af608e991569076671"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/matthiasmullie/minify/zipball/cb7a9297b4ab070909cefade30ee95054d4ae87a", "url": "https://api.github.com/repos/matthiasmullie/minify/zipball/76ba4a5f555fd7bf4aa408af608e991569076671",
"reference": "cb7a9297b4ab070909cefade30ee95054d4ae87a", "reference": "76ba4a5f555fd7bf4aa408af608e991569076671",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -28,8 +28,7 @@
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": ">=2.0", "friendsofphp/php-cs-fixer": ">=2.0",
"matthiasmullie/scrapbook": ">=1.3", "matthiasmullie/scrapbook": ">=1.3",
"phpunit/phpunit": ">=4.8", "phpunit/phpunit": ">=4.8"
"squizlabs/php_codesniffer": ">=3.0"
}, },
"suggest": { "suggest": {
"psr/cache-implementation": "Cache implementation to use with Minify::cache" "psr/cache-implementation": "Cache implementation to use with Minify::cache"
@ -67,7 +66,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/matthiasmullie/minify/issues", "issues": "https://github.com/matthiasmullie/minify/issues",
"source": "https://github.com/matthiasmullie/minify/tree/1.3.73" "source": "https://github.com/matthiasmullie/minify/tree/1.3.75"
}, },
"funding": [ "funding": [
{ {
@ -75,7 +74,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-03-15T10:27:10+00:00" "time": "2025-06-25T09:56:19+00:00"
}, },
{ {
"name": "matthiasmullie/path-converter", "name": "matthiasmullie/path-converter",
@ -129,33 +128,6 @@
"source": "https://github.com/matthiasmullie/path-converter/tree/1.1.3" "source": "https://github.com/matthiasmullie/path-converter/tree/1.1.3"
}, },
"time": "2019-02-05T23:41:09+00:00" "time": "2019-02-05T23:41:09+00:00"
},
{
"name": "vlw/globalsnapshot",
"version": "1.0.3",
"source": {
"type": "git",
"url": "https://codeberg.org/vlw/php-globalsnapshot",
"reference": "4bb9e4a08cccc3a64f5a2be2ff1c646b10975ce3"
},
"type": "library",
"autoload": {
"psr-4": {
"vlw\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Unlicense"
],
"authors": [
{
"name": "Victor Westerlund",
"email": "victor@vlw.se"
}
],
"description": "Capture and restore the state of all PHP superglobals.",
"time": "2024-11-23T09:23:42+00:00"
} }
], ],
"packages-dev": [], "packages-dev": [],

View file

@ -5,17 +5,17 @@
use VV; use VV;
use Vegvisir\Kernel\Env; use Vegvisir\Kernel\Env;
use Vegvisir\Kernel\Path; use Vegvisir\Kernel\Path;
use Vegvisir\Support\MimeType;
use Vegvisir\Request\Controller; use Vegvisir\Request\Controller;
use Vegvisir\Request\Support\MimeTypes;
use MatthiasMullie\Minify; use MatthiasMullie\Minify;
require_once Path::vegvisir("src/request/VV.php"); require_once Path::vegvisir("src/request/VV.php");
require_once Path::vegvisir("src/support/MimeType.php");
require_once Path::vegvisir("src/request/Controller.php"); require_once Path::vegvisir("src/request/Controller.php");
require_once Path::vegvisir("src/request/support/MimeTypes.php");
class Router extends VV { class Router extends VV {
private readonly MimeType $mime; private readonly MimeTypes $mime;
private readonly string $pathname; private readonly string $pathname;
public function __construct() { public function __construct() {
@ -23,7 +23,7 @@
$this->pathname = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH); $this->pathname = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
// Parse JSON from request body into PHP $_POST superglobal // Parse JSON from request body into PHP $_POST superglobal
if (array_key_exists("HTTP_CONTENT_TYPE", $_SERVER) and $_SERVER["HTTP_CONTENT_TYPE"] === "application/json") { if (($_SERVER["HTTP_CONTENT_TYPE"] ?? null) === "application/json") {
$_POST = json_decode(file_get_contents("php://input"), true) ?? []; $_POST = json_decode(file_get_contents("php://input"), true) ?? [];
} }
@ -57,7 +57,7 @@
private function resp_asset() { private function resp_asset() {
// Initialize the MimeType class on first static asset // Initialize the MimeType class on first static asset
if (!isset($this->mime)) { if (!isset($this->mime)) {
$this->mime = new MimeType(); $this->mime = new MimeTypes();
} }
$file = Path::public($this->pathname); $file = Path::public($this->pathname);

View file

@ -3,10 +3,12 @@
use Vegvisir\Kernel\ENV; use Vegvisir\Kernel\ENV;
use Vegvisir\Kernel\Path; use Vegvisir\Kernel\Path;
use Vegvisir\Request\Controller; use Vegvisir\Request\Controller;
use Vegvisir\Request\Support\Snapshot;
use vlw\GlobalSnapshot;
use MatthiasMullie\Minify; use MatthiasMullie\Minify;
require_once Path::vegvisir("src/request/support/Snapshot.php");
class VV extends Path { class VV extends Path {
// Raise an error if file at pathname is not found // Raise an error if file at pathname is not found
private static function is_file(string $pathname): bool { private static function is_file(string $pathname): bool {
@ -54,7 +56,7 @@
// Include a PHP file from project_root with optional arguments passed by reference to included file // Include a PHP file from project_root with optional arguments passed by reference to included file
public static function include(string $path, mixed &...$args) { public static function include(string $path, mixed &...$args) {
$snapshot = new GlobalSnapshot(); $snapshot = new Snapshot();
// Destruct pathname and query from path // Destruct pathname and query from path
[$pathname, $query] = strpos($path, "?") ? explode("?", $path, 2) : [$path, null]; [$pathname, $query] = strpos($path, "?") ? explode("?", $path, 2) : [$path, null];

View file

@ -0,0 +1,71 @@
<?php
namespace Vegvisir\Request\Support;
use Vegvisir\Kernel\Env;
class MimeTypes {
private const DEFAULT_URL = "https://raw.githubusercontent.com/apache/httpd/refs/heads/trunk/docs/conf/mime.types";
private const CACHE_FILENAME = "vegvisir_cache_mimetypes";
private readonly array $cache;
// Return an absolute pathname to the cache file
private static function get_cache_file_path(): string {
return sys_get_temp_dir() . "/" . self::CACHE_FILENAME;
}
public function __construct(public string $url = self::DEFAULT_URL) {
// Fetch and cache MIME-types if cache file does not exist
if (!is_file(self::get_cache_file_path())) {
$this->init_cache();
}
// Parse MIME-types from cache file as assoc array
$this->cache = json_decode(file_get_contents(self::get_cache_file_path()), true);
}
// Return MIME-type from extension
public function get_type_from_ext(string $ext): ?string {
return $this->cache[$ext] ?? null;
}
// Return extension from MIME-type
public function get_ext_from_type(string $type): ?string {
$ext = array_search($type, $this->cache);
return is_string($ext) ? $ext : null;
}
// Return extension of file if present
public function get_ext_from_file(string $path): ?string {
$ext = pathinfo($path, PATHINFO_EXTENSION);
return !empty($ext) ? $ext : null;
}
// Return MIME-type of a file
public function get_type_from_file(string $path): ?string {
return $this->get_type_from_ext(pathinfo($path, PATHINFO_EXTENSION));
}
// Parse an RFC 4288-compatible MIME-type list from URL set in environment variable
private function fetch_rfc_4288(): array {
// Courtesy of https://www.php.net/manual/en/function.mime-content-type.php#107798
// Modified slightly for readability and assoc array $s[ext] => [mime] instead of a text output
$s = [];
foreach (@explode("\n", @file_get_contents(Env::RFC_4288_URL->value())) as $x){
if (isset($x[0]) && $x[0] !== '#' && preg_match_all('#([^\s]+)#', $x, $out) && isset($out[1]) && ($c = count($out[1])) > 1) {
for($i = 1; $i < $c; $i++) {
$s[$out[1][$i]] = $out[1][0];
}
}
}
return $s;
}
// Create, fetch, and cache MIME-types
private function init_cache() {
file_put_contents(self::get_cache_file_path(), json_encode($this->fetch_rfc_4288()));
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace Vegvisir\Request\Support;
// Capture the current state of all superglobals.
// This will save a copy of all keys and values and any changes made to the superglobals
// can be restored to this point in time by calling $this->restore();
class Snapshot {
// Declare all PHP superglobals
private readonly array $_ENV;
private readonly array $_GET;
private readonly array $_POST;
private readonly array $_FILES;
private readonly array $_SERVER;
private readonly array $_COOKIE;
private readonly array $_REQUEST;
private readonly array $_SESSION;
// Declare additional PHP globals (for PHP 8.0+ compatability)
// These will not be captured, or altered by this class
private int $argc;
private array $argv;
// Native support for composer
private ?array $__composer_autoload_files;
public bool $captured = false;
public function __construct() {}
// Wipe all superglobals
private function truncate(string $global) {
global $$global;
$$global = [];
}
// Restore state of superglobals at the time of capture()
public function restore() {
foreach ($this as $global => $values) {
global $$global;
$this->truncate($global);
$$global = $this->{$global};
}
}
// Store current state of superglobals
public function capture() {
$this->captured = true;
foreach (array_keys($GLOBALS) as $global) {
$this->{$global} = $GLOBALS[$global];
}
}
}

View file

@ -1,62 +0,0 @@
<?php
namespace Vegvisir\Support;
use Vegvisir\Kernel\ENV;
class MimeType {
private const DEFAULT_TYPE = "application/x-empty";
private const CACHE_FILENAME = "php_vegvisir_types";
private readonly \stdClass $cache;
public function __construct() {
// Fetch and cache MIME-types if cache file does not exist
if (!is_file(self::get_cache_file_path())) {
$this->init_cache();
}
// Parse MIME-types from cache file
$this->cache = json_decode(file_get_contents(self::get_cache_file_path()));
}
// Return an absolute pathname to the cache file
private static function get_cache_file_path(): string {
return sys_get_temp_dir() . "/" . self::CACHE_FILENAME;
}
// Parse an RFC 4288-compatible MIME-type list from URL set in environment variable
private function fetch_types(): array {
// Courtesy of https://www.php.net/manual/en/function.mime-content-type.php#107798
// Modified slightly for readability and assoc array $s[ext] => [mime] instead of a text output
$s = array();
foreach (@explode("\n", @file_get_contents(Env::RFC_4288_URL->value())) as $x){
if (isset($x[0]) && $x[0] !== '#' && preg_match_all('#([^\s]+)#', $x, $out) && isset($out[1]) && ($c = count($out[1])) > 1) {
for($i = 1; $i < $c; $i++) {
$s[$out[1][$i]] = $out[1][0];
}
}
}
return $s;
}
// Create, fetch, and cache MIME-types
private function init_cache() {
// Create cache file
tempnam(sys_get_temp_dir(), self::CACHE_FILENAME);
// Fetch and write MIME-type as json to cache file
$cache = fopen(self::get_cache_file_path(), "w");
fwrite($cache, json_encode($this->fetch_types()));
fclose($cache);
}
public function get_type_from_ext(string $ext): string {
return $this->cache->$ext ?? self::DEFAULT_TYPE;
}
public function get_type_from_file(string $path): string {
return $this->get_type_from_ext(pathinfo($path, PATHINFO_EXTENSION));
}
}