import
This commit is contained in:
commit
e92bfcf7f7
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
* text=auto
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.debug
|
162
lib/FWIF/FWIF.php
Normal file
162
lib/FWIF/FWIF.php
Normal file
|
@ -0,0 +1,162 @@
|
|||
<?php
|
||||
namespace FWIF;
|
||||
|
||||
class FWIF {
|
||||
public const CONTENT_TYPE = 'text/plain; charset=us-ascii'; // TODO: come up with a mime type
|
||||
|
||||
public const TYPE_NULL = 0; // NULL, no data
|
||||
public const TYPE_INTEGER = 0x01; // LEB128, implicit length
|
||||
public const TYPE_FLOAT = 0x02; // double precision IEEE 754, fixed length of 8 bytes
|
||||
public const TYPE_STRING = 0x03; // UTF-8 string, terminated with TYPE_TRAILER
|
||||
public const TYPE_ARRAY = 0x04; // List of values, terminated with TYPE_TRAILER
|
||||
public const TYPE_OBJECT = 0x05; // List of values with ASCII names, terminated with TYPE_TRAILER
|
||||
public const TYPE_BUFFER = 0x06; // Buffer with binary data, prefixed with a LEB128 length
|
||||
public const TYPE_DATE = 0x07; // A gregorian year, month and day, fixed length of * bytes
|
||||
public const TYPE_DATETIME = 0x08; // A gregorian year, month and day as well as an hour, minute and seconds component, fixed length of * bytes
|
||||
public const TYPE_PERIOD = 0x09; // A time period, fixed length of * bytes
|
||||
public const TYPE_TRAILER = 0xFF; // Termination byte
|
||||
|
||||
private const CODECS = [
|
||||
self::TYPE_NULL => 'Null',
|
||||
self::TYPE_INTEGER => 'Integer',
|
||||
self::TYPE_FLOAT => 'Float',
|
||||
self::TYPE_STRING => 'String',
|
||||
self::TYPE_ARRAY => 'Array',
|
||||
self::TYPE_OBJECT => 'Object',
|
||||
];
|
||||
|
||||
private static function isAssocArray($array): bool {
|
||||
if(!is_array($array) || $array === [])
|
||||
return false;
|
||||
return array_keys($array) !== range(0, count($array) - 1);
|
||||
}
|
||||
|
||||
private static function detectType($data): int {
|
||||
if(is_null($data))
|
||||
return self::TYPE_NULL;
|
||||
if(is_int($data))
|
||||
return self::TYPE_INTEGER;
|
||||
if(is_float($data))
|
||||
return self::TYPE_FLOAT;
|
||||
if(is_string($data)) // Should this check if a string is valid UTF-8 and swap over to TYPE_BUFFER?
|
||||
return self::TYPE_STRING;
|
||||
if(is_object($data) || self::isAssocArray($data))
|
||||
return self::TYPE_OBJECT;
|
||||
if(is_array($data))
|
||||
return self::TYPE_ARRAY;
|
||||
throw new FWIFUnsupportedTypeException(gettype($data));
|
||||
}
|
||||
|
||||
public static function encode($data): string {
|
||||
if($data instanceof FWIFSerializable)
|
||||
$data = $data->fwifSerialize();
|
||||
$type = self::detectType($data);
|
||||
return chr($type) . self::{'encode' . self::CODECS[$type]}($data);
|
||||
}
|
||||
|
||||
public static function decode(string $data) {
|
||||
return self::decodeInternal(new FWIFDecodeStream($data));
|
||||
}
|
||||
|
||||
private static function decodeInternal(FWIFDecodeStream $data) {
|
||||
$type = $data->readByte();
|
||||
if(!array_key_exists($type, self::CODECS)) {
|
||||
$hexType = dechex($type); $hexPos = dechex($data->getPosition());
|
||||
throw new FWIFUnsupportedTypeException("Unsupported type {$type} (0x{$hexType}) at position {$data->getPosition()} (0x{$hexPos})");
|
||||
}
|
||||
return self::{'decode' . self::CODECS[$type]}($data);
|
||||
}
|
||||
|
||||
private static function encodeNull($data): string { return ''; }
|
||||
private static function decodeNull(FWIFDecodeStream $data) { return null; }
|
||||
|
||||
private static function encodeInteger(int $number): string {
|
||||
$packed = ''; $more = 1; $negative = $number < 0; $size = PHP_INT_SIZE * 8;
|
||||
while($more) {
|
||||
$byte = $number & 0x7F;
|
||||
$number >>= 7;
|
||||
if($negative)
|
||||
$number |= (~0 << ($size - 7));
|
||||
if((!$number && !($byte & 0x40)) || ($number === -1 && ($byte & 0x40)))
|
||||
$more = 0;
|
||||
else
|
||||
$byte |= 0x80;
|
||||
$packed .= chr($byte);
|
||||
}
|
||||
return $packed;
|
||||
}
|
||||
private static function decodeInteger(FWIFDecodeStream $data): int {
|
||||
$number = 0; $shift = 0; $o = 0; $size = PHP_INT_SIZE * 8;
|
||||
do {
|
||||
$byte = $data->readByte();
|
||||
$number |= ($byte & 0x7F) << $shift;
|
||||
$shift += 7;
|
||||
} while($byte & 0x80);
|
||||
if(($shift < $size) && ($byte & 0x40))
|
||||
$number |= (~0 << $shift);
|
||||
return $number;
|
||||
}
|
||||
|
||||
private static function encodeFloat(float $number): string {
|
||||
return pack('E', $number);
|
||||
}
|
||||
private static function decodeFloat(FWIFDecodeStream $data): float {
|
||||
$packed = ''; for($i = 0; $i < 8; ++$i) $packed .= chr($data->readByte());
|
||||
return unpack('E', $packed)[1];
|
||||
}
|
||||
|
||||
private static function encodeString(string $string): string {
|
||||
$packed = ''; $string = unpack('C*', mb_convert_encoding($string, 'utf-8'));
|
||||
foreach($string as $char)
|
||||
$packed .= chr($char);
|
||||
return $packed . chr(self::TYPE_TRAILER);
|
||||
}
|
||||
private static function decodeAsciiString(FWIFDecodeStream $data): string {
|
||||
$string = '';
|
||||
for(;;) {
|
||||
$byte = $data->readByte();
|
||||
if($byte === self::TYPE_TRAILER)
|
||||
break;
|
||||
$string .= chr($byte);
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
private static function decodeString(FWIFDecodeStream $data): string { // This should decode based on the utf-8 spec rather than just
|
||||
return mb_convert_encoding(self::decodeAsciiString($data), 'utf-8'); // grabbing the FF terminated string representation.
|
||||
}
|
||||
|
||||
private static function encodeArray(array $array): string {
|
||||
$packed = '';
|
||||
foreach($array as $value)
|
||||
$packed .= self::encode($value);
|
||||
return $packed . chr(self::TYPE_TRAILER);
|
||||
}
|
||||
private static function decodeArray(FWIFDecodeStream $data): array {
|
||||
$array = [];
|
||||
for(;;) {
|
||||
if($data->readByte() === self::TYPE_TRAILER)
|
||||
break;
|
||||
$data->stepBack();
|
||||
$array[] = self::decodeInternal($data);
|
||||
}
|
||||
return $array;
|
||||
}
|
||||
|
||||
private static function encodeObject($object): string {
|
||||
$packed = ''; $array = (array)$object;
|
||||
foreach($array as $name => $value)
|
||||
$packed .= $name . chr(self::TYPE_TRAILER) . self::encode($value);
|
||||
return $packed . chr(self::TYPE_TRAILER);
|
||||
}
|
||||
private static function decodeObject(FWIFDecodeStream $data): object {
|
||||
$array = [];
|
||||
for(;;) {
|
||||
if($data->readByte() === self::TYPE_TRAILER)
|
||||
break;
|
||||
$data->stepBack();
|
||||
$name = self::decodeAsciiString($data);
|
||||
$array[$name] = self::decodeInternal($data);
|
||||
}
|
||||
return (object)$array;
|
||||
}
|
||||
}
|
31
lib/FWIF/FWIFDecodeStream.php
Normal file
31
lib/FWIF/FWIFDecodeStream.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
namespace FWIF;
|
||||
|
||||
class FWIFDecodeStream {
|
||||
private string $body;
|
||||
private int $length;
|
||||
private int $position = 0;
|
||||
|
||||
public function __construct(string $body) {
|
||||
$this->body = $body;
|
||||
$this->length = strlen($body);
|
||||
}
|
||||
|
||||
public function getLength(): int {
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
public function getPosition(): int {
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function stepBack(): void {
|
||||
$this->position = max(0, $this->position - 1);
|
||||
}
|
||||
|
||||
public function readByte(): int {
|
||||
if($this->position + 1 >= $this->length)
|
||||
return 0xFF;
|
||||
return ord($this->body[$this->position++]);
|
||||
}
|
||||
}
|
4
lib/FWIF/FWIFException.php
Normal file
4
lib/FWIF/FWIFException.php
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
namespace FWIF;
|
||||
|
||||
class FWIFException extends \Exception {}
|
6
lib/FWIF/FWIFSerializable.php
Normal file
6
lib/FWIF/FWIFSerializable.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace FWIF;
|
||||
|
||||
interface FWIFSerializable {
|
||||
function fwifSerialize();
|
||||
}
|
4
lib/FWIF/FWIFUnsupportedTypeException.php
Normal file
4
lib/FWIF/FWIFUnsupportedTypeException.php
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?php
|
||||
namespace FWIF;
|
||||
|
||||
class FWIFUnsupportedTypeException extends FWIFException {}
|
28
patchouli.txt
Normal file
28
patchouli.txt
Normal file
|
@ -0,0 +1,28 @@
|
|||
/\ /\
|
||||
_,, -‐=ンヘノ\_.::::\
|
||||
// _ :::::::`ヽ/
|
||||
ノ / ..::::;;::::::.... ヽ\:::::..:::∨ ̄ ̄`l
|
||||
r‐'::::〈::ノ _r、:::::::::::l |::::::: l|、 ̄ト、/
|
||||
r-‐─〉 ::::::r‐-、__〉 l\゙ー' ノ、: ;: l::::ヽj゙、 〉
|
||||
// /|ヽ_〉 / l l |! l l ̄ l (_ ハ/ ∨
|
||||
\/∧ノ_ノ! l l,,_|_l |l ! |_,,,_ト l l |
|
||||
_ノノ\_〉、ハ〈こ>リV゙´匕j 〉 !ヽ ヽ、
|
||||
, へヽ;;;;;`ヾミ`ヽ、⊥_ l /_ノ>-─┐、
|
||||
〉 l;;;;l;\;;;`ヾ三、ミ`ヽー/ 二三‐二二ニ⊥、\
|
||||
⌒T´::l;;;;l;;;;;;;`'';;ヽ、;;;`ヽ〉//二 -─..''.. ̄ ̄;// `'' ー-、
|
||||
,,, -─'''>、.l;;;;l;;;;;;;;;;;;;;;;;;;`;;ヾニィ´;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;//\_ 、 \
|
||||
/, /く ィュ|::l;;;;l;;;;;;;;;;;;;;;;;;;;;;;;;;|;;|:;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;// ノ `> ヾ ヽ
|
||||
__〈∠l _//| `::ノ.::l;;;;l;;;;;;;;;;;;;;;;;l\〈\;;;;∩;;;;;;;r─-、;;;;//'' ィ、 | l |、 |
|
||||
´  ̄ `Y .:::::l;;;;l;;;;;;;;;;;;;;;;;ト、ヽ〉 ヽノ |´∨:::::r‐ノ;;// \r'' | l ∨
|
||||
............... | ::::::l;;;;:ー-.、;;;;;;;;\ l::::_ノl/ 〈;;;;//l >| ̄丿 ノ!/
|
||||
:::::\.. :: ̄ >、;;\;;;;;|\ 〉! 、 `ヾ、ヽ:ヽ_ 〈
|
||||
::::::::: ̄ ̄へ `ヽイ:::::_>、ノノヽ ゞ !:ノ::::::. \ヽ_ _
|
||||
.. _::二-‐''/ ̄ハ:::`ーr-、 `´ィ:::..... 〉:::::::::::..  ̄\:::`ヽ
|
||||
:::. ,, ‐''´/.. ̄:::::,::-‐:/:::::::ノ:::::ヽ::::::|:::l:::..:::::::::─-ノ .:::|::.:::::::::: ヽ::::
|
||||
::::::;;: ‐''゙´ /:::::::::::::/ ::/::::::;ィ´: :: ::::::::::!::::\:::::::::..../::: l:::::.:::::: ::
|
||||
/ /ノ7:::::::/ /.:::::/ l:: :::::::::::::/:l ..:::: ̄ ̄`ー‐''´::::::\ _
|
||||
//´ └-/ / .::::/ ..::-─/ \ .....:::::::::::::. :: \:: ̄`ヽ
|
||||
.::. : / / /..:::::/ _ 二 -─/ ノー-、::::::::::::.. `ヽ::::::::::.
|
||||
.::::::._| / \_/ ::::::::/ / .::::\- 、:::... \:::::::..
|
||||
::::::/ !:: l\:: :. . ..::::::::::/ ..::::/ .:::::::!:::::ト、 `ヽ;:.. \ー-、
|
||||
/ |::/ ::::7 :: :::...::::::::::::: 〈 ::: /: .::::::::/::/ \ :. ヽ:::::
|
55
public/index.php
Normal file
55
public/index.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
namespace Patchouli;
|
||||
|
||||
use FWIF\FWIF;
|
||||
|
||||
require_once __DIR__ . '/../startup.php';
|
||||
|
||||
$request = Http\HttpRequest::create();
|
||||
|
||||
header('Content-Type: ' . FWIF::CONTENT_TYPE);
|
||||
|
||||
if($request->match('GET', '/packages')) {
|
||||
$tags = explode(';', (string)$request->getQueryParam('tags', FILTER_SANITIZE_STRING));
|
||||
$packages = empty($tags) ? Patchouli::getPackages() : Patchouli::getPackagesWithTags($tags);
|
||||
|
||||
$encoded = FWIF::encode($packages);
|
||||
echo strlen($encoded) . ' ' . $encoded;
|
||||
|
||||
echo "\r\n\r\n--------------------\r\n\r\n";
|
||||
|
||||
$jsonEncoded = json_encode($packages);
|
||||
echo strlen($jsonEncoded) . ' ' . $jsonEncoded;
|
||||
|
||||
echo "\r\n\r\n--------------------\r\n\r\n";
|
||||
|
||||
$hexdump = bin2hex($encoded); $hexdumpSect = 8; $hexdumpSize = 32;
|
||||
for($i = 0; $i < strlen($hexdump) / $hexdumpSize; ++$i) {
|
||||
$line = substr($hexdump, $i * $hexdumpSize, $hexdumpSize);
|
||||
echo str_pad(dechex($i * $hexdumpSize), 4, '0', STR_PAD_LEFT) . ' ';
|
||||
for($j = 0; $j < strlen($line) / $hexdumpSect; ++$j)
|
||||
echo substr($line, $j * $hexdumpSect, $hexdumpSect) . ' ';
|
||||
echo "\r\n";
|
||||
}
|
||||
|
||||
echo "\r\n--------------------\r\n\r\n";
|
||||
|
||||
var_dump([(object)$packages[0]->fwifSerialize()]);
|
||||
|
||||
echo "\r\n--------------------\r\n\r\n";
|
||||
|
||||
$decoded = FWIF::decode($encoded);
|
||||
var_dump($decoded);
|
||||
return;
|
||||
}
|
||||
|
||||
if($request->match('GET', '/')) {
|
||||
header('Content-Type: text/html; charset=utf-8');
|
||||
echo '<!doctype html><title>Patchouli</title><pre style="font-family:IPAMonaPGothic,\'IPA モナー Pゴシック\',Monapo,Mona,\'MS PGothic\',\'MS Pゴシック\',sans-serif;font-size:16px;line-height:18px;">';
|
||||
readfile(PAT_ROOT . '/patchouli.txt');
|
||||
echo '</pre>';
|
||||
return;
|
||||
}
|
||||
|
||||
http_response_code(404);
|
||||
echo '{"code":404,"message":"Path not found."}';
|
55
src/Dummy/DummyPackage.php
Normal file
55
src/Dummy/DummyPackage.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
namespace Patchouli\Dummy;
|
||||
|
||||
use Patchouli\IPackage;
|
||||
use Patchouli\Version;
|
||||
|
||||
class DummyPackage implements IPackage, \JsonSerializable {
|
||||
public function getId(): string {
|
||||
return 'package-id';
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return 'Human Readable Name';
|
||||
}
|
||||
|
||||
public function getVersion(): Version {
|
||||
return new Version;
|
||||
}
|
||||
|
||||
public function getDependencies(): array {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function fwifSerialize(): array {
|
||||
$data = [
|
||||
'null' => null,
|
||||
'zero' => 0,
|
||||
'u8' => 0x42,
|
||||
'u16' => 0x4344,
|
||||
'u24' => 0x454647,
|
||||
'u32' => 0x58596061,
|
||||
'u40' => 0x6263646566,
|
||||
'u48' => 0x676869707172,
|
||||
'u56' => 0x73747576777879,
|
||||
'u64' => 0x7481828384858687,
|
||||
'neg32' => -12345678,
|
||||
'neg64' => -1234567890987654,
|
||||
'float' => 12345.6789,
|
||||
'array' => ['e', 'a', 0x55],
|
||||
'object' => new \stdClass,
|
||||
'misaka' => '御坂 美琴',
|
||||
'id' => $this->getId(),
|
||||
'name' => $this->getName(),
|
||||
'version' => $this->getVersion(),
|
||||
'deps' => [],
|
||||
];
|
||||
foreach($this->getDependencies() as $dependency)
|
||||
$data['deps'][] = $dependency->getName();
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function jsonSerialize() {
|
||||
return $this->fwifSerialize();
|
||||
}
|
||||
}
|
82
src/Http/HttpRequest.php
Normal file
82
src/Http/HttpRequest.php
Normal file
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
namespace Patchouli\Http;
|
||||
|
||||
class HttpRequest {
|
||||
private string $method;
|
||||
private string $path;
|
||||
|
||||
private array $serverParams;
|
||||
private array $queryParams;
|
||||
private array $postParams;
|
||||
|
||||
public function __construct(array $server, array $query, array $post) {
|
||||
$this->method = $server['REQUEST_METHOD'];
|
||||
$this->path = '/' . trim(parse_url($server['REQUEST_URI'], PHP_URL_PATH), '/');
|
||||
$this->serverParams = $server;
|
||||
$this->queryParams = $query;
|
||||
$this->postParams = $post;
|
||||
}
|
||||
|
||||
public static function create(): self {
|
||||
return new static($_SERVER, $_GET, $_POST);
|
||||
}
|
||||
|
||||
public function getMethod(): string {
|
||||
return $this->method;
|
||||
}
|
||||
public function isMethod(string|array $method): bool {
|
||||
if(is_string($method))
|
||||
return $this->method === $method;
|
||||
return in_array($this->method, $method);
|
||||
}
|
||||
|
||||
public function getPath(): string {
|
||||
return $this->path;
|
||||
}
|
||||
public function isPath(string $path): bool {
|
||||
return $this->path === $path;
|
||||
}
|
||||
public function matchPath(string $regex, ?array &$args = null): bool {
|
||||
if(!preg_match($regex, $this->path, $matches))
|
||||
return false;
|
||||
$args = array_slice($matches, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function match(string|array $method, string $path, ?array &$args = null): bool {
|
||||
if(!$this->isMethod($method))
|
||||
return false;
|
||||
if($path[0] === '/')
|
||||
return $this->isPath($path);
|
||||
return $this->matchPath($path, $args);
|
||||
}
|
||||
|
||||
public function getHeader($name): string {
|
||||
$name = strtr(strtoupper($name), '-', '_');
|
||||
if($name === 'CONTENT_LENGTH' || $name === 'CONTENT_LENGTH')
|
||||
return $this->serverParams[$name];
|
||||
return $this->serverParams['HTTP_' . $name] ?? '';
|
||||
}
|
||||
|
||||
public function getServerParam(string $name): string {
|
||||
return $this->serverParams[$name] ?? '';
|
||||
}
|
||||
|
||||
public function getBody(): string {
|
||||
return file_get_contents('php://input');
|
||||
}
|
||||
|
||||
public function getQueryParams(): array {
|
||||
return $this->queryParams;
|
||||
}
|
||||
public function getQueryParam(string $name, int $filter = FILTER_DEFAULT, mixed $options = null): mixed {
|
||||
return filter_var($this->queryParams[$name] ?? null, $filter, $options);
|
||||
}
|
||||
|
||||
public function getPostParams(): array {
|
||||
return $this->postParams;
|
||||
}
|
||||
public function getPostParam(string $name, int $filter = FILTER_DEFAULT, mixed $options = null): mixed {
|
||||
return filter_var($this->postParams[$name] ?? null, $filter, $options);
|
||||
}
|
||||
}
|
10
src/IPackage.php
Normal file
10
src/IPackage.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
namespace Patchouli;
|
||||
|
||||
use FWIF\FWIFSerializable;
|
||||
|
||||
interface IPackage extends FWIFSerializable {
|
||||
function getName(): string;
|
||||
function getVersion(): Version;
|
||||
function getDependencies(): array;
|
||||
}
|
18
src/Patchouli.php
Normal file
18
src/Patchouli.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
namespace Patchouli;
|
||||
|
||||
use Patchouli\Dummy\DummyPackage;
|
||||
|
||||
class Patchouli extends SingleInstance {
|
||||
public function _getPackages(): array {
|
||||
return [new DummyPackage];
|
||||
}
|
||||
|
||||
public function _getPackagesWithTags(array $tags): array {
|
||||
return [new DummyPackage];
|
||||
}
|
||||
|
||||
public function _getPackage(string $packageName): IPackage {
|
||||
return new DummyPackage;
|
||||
}
|
||||
}
|
28
src/SingleInstance.php
Normal file
28
src/SingleInstance.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace Patchouli;
|
||||
|
||||
abstract class SingleInstance {
|
||||
private static $instance = null;
|
||||
|
||||
public static function getInstance(): self {
|
||||
if(self::$instance === null)
|
||||
self::$instance = new static;
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
public function __call(string $name, array $args) {
|
||||
if($name[0] === '_') {
|
||||
trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
return $this->{'_' . $name}(...$args);
|
||||
}
|
||||
|
||||
public static function __callStatic(string $name, array $args) {
|
||||
if($name[0] === '_') {
|
||||
trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR);
|
||||
return;
|
||||
}
|
||||
return self::getInstance()->{'_' . $name}(...$args);
|
||||
}
|
||||
}
|
19
src/Version.php
Normal file
19
src/Version.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
namespace Patchouli;
|
||||
|
||||
use FWIF\FWIFSerializable;
|
||||
use JsonSerializable;
|
||||
|
||||
class Version implements FWIFSerializable, JsonSerializable {
|
||||
public function fwifSerialize(): string {
|
||||
return (string)$this;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): string {
|
||||
return (string)$this;
|
||||
}
|
||||
|
||||
public function __toString(): string {
|
||||
return '1.0.0';
|
||||
}
|
||||
}
|
21
startup.php
Normal file
21
startup.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
namespace Patchouli;
|
||||
|
||||
define('PAT_STARTUP', microtime(true));
|
||||
define('PAT_ROOT', __DIR__);
|
||||
define('PAT_DEBUG', is_file(PAT_ROOT . '/.debug'));
|
||||
define('PAT_PUB', PAT_ROOT . '/public'); // WWW visible
|
||||
define('PAT_SRC', PAT_ROOT . '/src'); // Patchouli namespace
|
||||
define('PAT_LIB', PAT_ROOT . '/lib'); // Other unresolved namespaces
|
||||
|
||||
ini_set('display_errors', PAT_DEBUG ? 'on' : 'off');
|
||||
error_reporting(PAT_DEBUG ? -1 : 0);
|
||||
|
||||
set_include_path(PAT_SRC . PATH_SEPARATOR . PAT_LIB . PATH_SEPARATOR . get_include_path());
|
||||
spl_autoload_register(function(string $className) {
|
||||
$parts = explode('\\', trim($className, '\\'), 2);
|
||||
if($parts[0] === __NAMESPACE__)
|
||||
require_once PAT_SRC . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $parts[1]) . '.php';
|
||||
else
|
||||
require_once PAT_LIB . DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php';
|
||||
});
|
Reference in a new issue