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