commit
c8c58fc72b
26 changed files with 2403 additions and 0 deletions
@ -0,0 +1,5 @@ |
||||
.idea |
||||
.vscode |
||||
.DS_Store |
||||
|
||||
vendor |
@ -0,0 +1,21 @@ |
||||
# The MIT License |
||||
|
||||
Copyright (C) 2024, MikO <miko@massivedynamic.eu> |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
||||
this software and associated documentation files (the “Software”), to deal in |
||||
the Software without restriction, including without limitation the rights to |
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
||||
of the Software, and to permit persons to whom the Software is furnished to do |
||||
so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,26 @@ |
||||
{ |
||||
"name": "massivedynamic/lucidity", |
||||
"description": "a lightweight php framework", |
||||
"type": "library", |
||||
"version" : "0.0.1", |
||||
"license": "MIT", |
||||
"autoload": { |
||||
"psr-4": { |
||||
"massivedynamic\\lucidity\\": "src/" |
||||
} |
||||
}, |
||||
"authors": [ |
||||
{ |
||||
"name": "MikO", |
||||
"email": "miko@massivedymamic.eu" |
||||
} |
||||
], |
||||
"require": { |
||||
"robmorgan/phinx": "^0.16.2", |
||||
"mustache/mustache": "^2.14", |
||||
"psr/container": "^2.0" |
||||
}, |
||||
"require-dev": { |
||||
"phpstan/phpstan": "^1.11" |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,103 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity { |
||||
|
||||
use Exception; |
||||
use Throwable; |
||||
|
||||
use massivedynamic\lucidity\TemplateEngine; |
||||
use massivedynamic\lucidity\Utils; |
||||
use massivedynamic\lucidity\containers\ServiceContainer; |
||||
use massivedynamic\lucidity\services\Router; |
||||
use massivedynamic\lucidity\services\Service; |
||||
|
||||
abstract class Application { |
||||
protected static ?Application $App = null; |
||||
|
||||
/** @var array<string, mixed> */ |
||||
protected array $globals; |
||||
protected TemplateEngine $templateEngine; |
||||
protected ServiceContainer $serviceContainer; |
||||
protected Router|Service $router; |
||||
|
||||
public function __construct(private string $templatesPath, string $partialsPath, string $cachePath) { |
||||
set_exception_handler(fn(Throwable $exception) => $this->handleException($exception)); |
||||
|
||||
if (self::$App instanceof Application) |
||||
throw new Exception("Only one instance of `Application` is possible"); |
||||
session_start(); |
||||
self::$App = $this; |
||||
$this->globals = [ |
||||
"errors" => [] |
||||
]; |
||||
$this->templateEngine = new TemplateEngine($this, $templatesPath, $partialsPath, $cachePath); |
||||
$this->serviceContainer = new ServiceContainer($this); |
||||
$this->router = $this->serviceContainer->get(Router::class); |
||||
} |
||||
|
||||
public function fetchService(string $id) : Service | Application { |
||||
return $this->serviceContainer->get($id); |
||||
} |
||||
|
||||
public function publishGlobal(string $key, mixed $value) : void { |
||||
if ($key === "errors") |
||||
throw new Exception("The 'errors' global can not be accessed directly. Use `Application::addError()` instead."); |
||||
$this->globals[$key] = $value; |
||||
} |
||||
|
||||
/** |
||||
* @param array<string> | string | null $errors |
||||
*/ |
||||
public function addError(array | string | null $errors) : void { |
||||
if (!$errors) |
||||
return; |
||||
if (!is_array($errors)) |
||||
$errors = [$errors]; |
||||
$this->globals["errors"] = array_merge((array)$this->globals["errors"], $errors); |
||||
} |
||||
|
||||
/** |
||||
* @return array<string, mixed> |
||||
*/ |
||||
public function globals() : array { |
||||
return $this->globals; |
||||
} |
||||
|
||||
private function handleException(Throwable $exception) : void { |
||||
//Utils::Dump($exception->getTrace()); |
||||
|
||||
self::View("LucidityException", [ |
||||
"exception" => $exception, |
||||
"trace" => $exception->getTrace(), |
||||
"exceptionType" => $exception::class, |
||||
"lucidityVersion" => \Composer\InstalledVersions::getVersion("massivedynamic/lucidity"), |
||||
"phpVersion" => phpversion() |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* @param string $template |
||||
* @param array<string, mixed> $context |
||||
*/ |
||||
public static function View(string $template, array $context = []) : void { |
||||
self::$App->templateEngine->view($template, $context); |
||||
} |
||||
|
||||
public static function Instance() : Application { |
||||
return self::$App; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
namespace { |
||||
|
||||
/** |
||||
* @param string $template |
||||
* @param array<string, mixed> $context |
||||
*/ |
||||
function view(string $template, array $context = []) : void { |
||||
massivedynamic\lucidity\Application::View($template, $context); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,10 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity; |
||||
|
||||
enum RequestType : string { |
||||
case GET = "GET"; |
||||
case POST = "POST"; |
||||
case PUT = "PUT"; |
||||
case DELETE = "DELETE"; |
||||
} |
@ -0,0 +1,31 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity; |
||||
|
||||
use Mustache_Engine; |
||||
use Mustache_Loader_FilesystemLoader; |
||||
use Mustache_Loader_CascadingLoader; |
||||
|
||||
class TemplateEngine extends Mustache_Engine { |
||||
public function __construct(private Application $app, private string $templatesPath, string $partialsPath, string $cachePath) { |
||||
parent::__construct([ |
||||
"cache" => $cachePath, |
||||
"loader" => new Mustache_Loader_CascadingLoader([ |
||||
new Mustache_Loader_FilesystemLoader($templatesPath), |
||||
new Mustache_Loader_FilesystemLoader(dirname(__FILE__)."/../views") |
||||
]), |
||||
"partials_loader" => new Mustache_Loader_CascadingLoader([ |
||||
new Mustache_Loader_FilesystemLoader($partialsPath), |
||||
new Mustache_Loader_FilesystemLoader(dirname(__FILE__)."/../views/partials") |
||||
]), |
||||
"helpers" => [ |
||||
"notEmpty" => fn(array $array) : bool => count($array) > 0 |
||||
], |
||||
"pragmas" => [Mustache_Engine::PRAGMA_BLOCKS, Mustache_Engine::PRAGMA_FILTERS] |
||||
]); |
||||
} |
||||
|
||||
public function view(string $template, array $context = []) : void { |
||||
echo $this->render("$template.mustache", array_merge($this->app->globals(), $context)); |
||||
} |
||||
} |
@ -0,0 +1,16 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity; |
||||
|
||||
class Utils { |
||||
public static function Dump(mixed $content) : void { |
||||
echo "<pre>"; |
||||
print_r($content); |
||||
echo "</pre>"; |
||||
} |
||||
|
||||
public static function DD(mixed $content) : void { |
||||
self::Dump($content); |
||||
die(); |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\containers; |
||||
|
||||
use Exception; |
||||
use ReflectionClass; |
||||
|
||||
use massivedynamic\lucidity\Application; |
||||
use Psr\Container\ContainerInterface; |
||||
|
||||
abstract class Container implements ContainerInterface { |
||||
protected array $instances; |
||||
|
||||
public function __construct(protected Application $app) { |
||||
$this->instances = []; |
||||
} |
||||
|
||||
public function get(string $id) : mixed { |
||||
if ($id === Application::class || is_subclass_of($id, Application::class)) |
||||
return $this->app; |
||||
if (!$this->has($id)) |
||||
$this->instantiate($id); |
||||
|
||||
return $this->instances[$id]; |
||||
} |
||||
|
||||
public function has(string $id) : bool { |
||||
return array_key_exists($id, $this->instances); |
||||
} |
||||
|
||||
public function emplace(string $type, ...$args) : bool { |
||||
if (array_key_exists($type, $this->instances)) |
||||
return false; |
||||
$this->instances[$type] = new $type(...$args); |
||||
|
||||
return true; |
||||
} |
||||
|
||||
protected function instantiate(string $id) : void { |
||||
$classInfo = new ReflectionClass($id); |
||||
$constructor = $classInfo->getConstructor(); |
||||
$arguments = []; |
||||
|
||||
if ($constructor === null) { |
||||
$this->instances[$id] = $classInfo->newInstance(); |
||||
return; |
||||
} |
||||
|
||||
foreach ($constructor->getParameters() as $parameter) { |
||||
$type = $parameter->getType(); |
||||
if ($type === null) |
||||
throw new Exception("constructor arguments need to name a type"); |
||||
array_push($arguments, $this->get($type->getName())); |
||||
} |
||||
$this->instances[$id] = $classInfo->newInstanceArgs($arguments); |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\containers; |
||||
|
||||
use massivedynamic\lucidity\containers\Container; |
||||
use massivedynamic\lucidity\controllers\Controller; |
||||
|
||||
class ControllerContainer extends Container { |
||||
public function get(string $id) : Controller { |
||||
return parent::get($id); |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\containers; |
||||
|
||||
use massivedynamic\lucidity\Application; |
||||
use massivedynamic\lucidity\containers\Container; |
||||
use massivedynamic\lucidity\services\Service; |
||||
|
||||
class ServiceContainer extends Container { |
||||
public function get(string $id) : Service | Application { |
||||
return parent::get($id); |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\controllers; |
||||
|
||||
class Controller { |
||||
public function __construct() { |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\exceptions; |
||||
|
||||
use Exception; |
||||
use Throwable; |
||||
|
||||
class UnknownValidatorException extends Exception { |
||||
public function __construct(string $validator, ?Throwable $previous = null) { |
||||
parent::__construct("Unknown validator '$validator'", 1000, $previous); |
||||
} |
||||
} |
@ -0,0 +1,66 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\models; |
||||
|
||||
use ReflectionClass; |
||||
use ReflectionProperty; |
||||
use ReflectionUnionType; |
||||
|
||||
use massivedynamic\lucidity\Application; |
||||
use massivedynamic\lucidity\Utils; |
||||
use massivedynamic\lucidity\models\Model; |
||||
use massivedynamic\lucidity\services\Database; |
||||
use massivedynamic\lucidity\services\Service; |
||||
|
||||
abstract class DatabaseModel extends Model { |
||||
protected static ?string $TableName = null; |
||||
|
||||
protected Service | Database $db; |
||||
|
||||
public function __construct() { |
||||
$this->db = Application::Instance()->fetchService(Database::class); |
||||
} |
||||
|
||||
public function save() { |
||||
$classInfo = new ReflectionClass($this); |
||||
$properties = $classInfo->getProperties(ReflectionProperty::IS_PROTECTED); |
||||
$columns = []; |
||||
$values = []; |
||||
$types = []; |
||||
|
||||
foreach ($properties as $property) { |
||||
if ($property->getType() instanceof ReflectionUnionType || $property->isStatic() || !$property->getType()->isBuiltin()) |
||||
continue; |
||||
|
||||
array_push($columns, $property->name); |
||||
array_push($values, $this->{$property->name}); |
||||
array_push($types, Database::PrimitiveTypeToBindParam($property->getType()->getName())); |
||||
} |
||||
$placeholders = array_fill(0, count($columns), '?'); |
||||
|
||||
$qs = "INSERT INTO ".self::TableName()." (".implode(", ", $columns).") VALUES (".implode(", ", $placeholders).")"; |
||||
$query = $this->db->prepare($qs); |
||||
$query->bind_param(implode("", $types), ...$values); |
||||
} |
||||
|
||||
public function all() : array { |
||||
$result = $this->db->query("SELECT * FROM ".self::TableName()); |
||||
|
||||
$out = []; |
||||
while ($row = $result->fetch_object()) |
||||
array_push($out, $row); |
||||
|
||||
return $out; |
||||
} |
||||
|
||||
private static function TableName() : string { |
||||
return self::$TableName ?? self::Type2TableName(static::class); |
||||
} |
||||
|
||||
private static function Type2TableName(string $type) : string { |
||||
$parts = explode("\\", $type); |
||||
$basename = strtolower(array_pop($parts)); |
||||
|
||||
return substr($basename, -1) === "s" ? $basename : $basename."s"; |
||||
} |
||||
} |
@ -0,0 +1,5 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\models; |
||||
|
||||
abstract class Model {} |
@ -0,0 +1,25 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\services; |
||||
|
||||
use mysqli; |
||||
|
||||
use massivedynamic\lucidity\Utils; |
||||
use massivedynamic\lucidity\services\Service; |
||||
|
||||
class Database extends mysqli implements Service { |
||||
public static function PrimitiveTypeToBindParam(string $type) : string { |
||||
switch($type) { |
||||
case "bool": |
||||
return "i"; |
||||
case "string": |
||||
return "s"; |
||||
case "int": |
||||
return "i"; |
||||
case "float": |
||||
return "d"; |
||||
default: |
||||
return "X"; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,57 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\services; |
||||
|
||||
use massivedynamic\lucidity\RequestType; |
||||
use massivedynamic\lucidity\Utils; |
||||
use massivedynamic\lucidity\services\Service; |
||||
use massivedynamic\lucidity\validators\Validator; |
||||
|
||||
class Request implements Service { |
||||
public readonly RequestType $type; |
||||
public readonly string $query; |
||||
/** @var array<string, string> */ |
||||
private readonly array $get; |
||||
/** @var array<string, string> */ |
||||
private readonly array $post; |
||||
|
||||
public function __construct() { |
||||
$queryString = parse_url("/".ltrim($_SERVER["REQUEST_URI"], '/'), PHP_URL_PATH); |
||||
assert($queryString !== false, "unreachable"); |
||||
|
||||
$this->type = RequestType::tryFrom($_SERVER["REQUEST_METHOD"]) ?? RequestType::GET; |
||||
$this->query = !$queryString ? "" : $queryString; |
||||
$this->get = $_GET; |
||||
$this->post = $_POST; |
||||
} |
||||
|
||||
/** |
||||
* @param array<string, array<string>> $attributes |
||||
*/ |
||||
public function validate(array $attributes) : bool { |
||||
$validator = new Validator($this->all(), $attributes); |
||||
|
||||
return $validator->validate() === null; |
||||
} |
||||
|
||||
public function match(string $pattern) : bool { |
||||
$pattern = "/^".preg_replace("/\//", "\/", $pattern)."$/"; |
||||
|
||||
return preg_match($pattern, $this->query) === 1; |
||||
} |
||||
|
||||
/** |
||||
* @return array<string, string> |
||||
*/ |
||||
public function all() : array { |
||||
return $this->type === RequestType::POST ? $this->post : $this->get; |
||||
} |
||||
|
||||
public function get(string $key, ?string $defaultValue = null) : ?string { |
||||
return isset($this->get[$key]) ? $this->get[$key] : $defaultValue; |
||||
} |
||||
|
||||
public function post(string $key, ?string $defaultValue = null) : ?string { |
||||
return isset($this->post[$key]) ? $this->post[$key] : $defaultValue; |
||||
} |
||||
} |
@ -0,0 +1,108 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\services { |
||||
|
||||
use Exception; |
||||
use ReflectionFunction; |
||||
use ReflectionMethod; |
||||
|
||||
use massivedynamic\lucidity\Application; |
||||
use massivedynamic\lucidity\Utils; |
||||
use massivedynamic\lucidity\RequestType; |
||||
use massivedynamic\lucidity\containers\ControllerContainer; |
||||
use massivedynamic\lucidity\services\Request; |
||||
use massivedynamic\lucidity\services\Service; |
||||
|
||||
class Router implements Service { |
||||
private ControllerContainer $controllerContainer; |
||||
private array $routes; |
||||
|
||||
public function __construct(private Application $app, private Request $request) { |
||||
$this->controllerContainer = new ControllerContainer($app); |
||||
$this->routes = [ |
||||
RequestType::GET->value => [], |
||||
RequestType::POST->value => [], |
||||
RequestType::PUT->value => [], |
||||
RequestType::DELETE->value => [], |
||||
]; |
||||
} |
||||
|
||||
private function addCallback(RequestType $type, string $route, callable | array $callback) : void { |
||||
$this->routes[$type->value][$route] = $callback; |
||||
} |
||||
|
||||
public function get(string $route, callable | array $callback) : void { |
||||
$this->addCallback(RequestType::GET, $route, $callback); |
||||
} |
||||
|
||||
public function post(string $route, callable | array $callback) : void { |
||||
$this->addCallback(RequestType::POST, $route, $callback); |
||||
} |
||||
|
||||
public function put(string $route, callable | array $callback) : void { |
||||
$this->addCallback(RequestType::PUT, $route, $callback); |
||||
} |
||||
|
||||
public function delete(string $route, callable | array $callback) : void { |
||||
$this->addCallback(RequestType::DELETE, $route, $callback); |
||||
} |
||||
|
||||
public function view(string $route, string $template, array $context = []) { |
||||
$this->addCallback(RequestType::GET, $route, fn() => view($template, $context)); |
||||
} |
||||
|
||||
public function route() : void { |
||||
$matched = false; |
||||
foreach ($this->routes[$this->request->type->value] as $route => $callback) { |
||||
if ($this->request->match($route)) { |
||||
$matched = true; |
||||
$this->call($callback); |
||||
} |
||||
} |
||||
|
||||
if (!$matched) |
||||
view("404"); |
||||
} |
||||
|
||||
public static function Redirect(string $target) : void { |
||||
header("Location: $target"); |
||||
} |
||||
|
||||
private function call(callable | array $callback) : void { |
||||
$callbackInfo = null; |
||||
|
||||
if (is_array($callback)) { |
||||
assert(count($callback) === 2, "only callables can be routed"); |
||||
[$class, $method] = $callback; |
||||
$callbackInfo = new ReflectionMethod($class, $method); |
||||
$arguments = $this->getArgs($callbackInfo); |
||||
$callbackInfo->invokeArgs($this->controllerContainer->get($class), $arguments); |
||||
} else { |
||||
$callbackInfo = new ReflectionFunction($callback); |
||||
$arguments = $this->getArgs($callbackInfo); |
||||
$callbackInfo->invokeArgs($arguments); |
||||
} |
||||
} |
||||
|
||||
private function getArgs(ReflectionFunction | ReflectionMethod $function) : array { |
||||
$arguments = []; |
||||
foreach ($function->getParameters() as $parameter) { |
||||
$type = $parameter->getType(); |
||||
if ($type === null) |
||||
throw new Exception("constructor arguments need to name a type"); |
||||
array_push($arguments, $this->app->fetchService($type->getName())); |
||||
} |
||||
|
||||
return $arguments; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
namespace { |
||||
|
||||
function redirect(string $target) { |
||||
massivedynamic\lucidity\services\Router::Redirect($target); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,6 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\services; |
||||
|
||||
interface Service { |
||||
} |
@ -0,0 +1,11 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\validators; |
||||
|
||||
// @struct |
||||
class Validation { |
||||
public function __construct( |
||||
public readonly ValidationType $type, |
||||
public readonly string $parameter |
||||
){} |
||||
} |
@ -0,0 +1,9 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\validators; |
||||
|
||||
enum ValidationType : string { |
||||
case REQUIRED = "required"; |
||||
case MIN = "min"; |
||||
case MAX = "max"; |
||||
} |
@ -0,0 +1,87 @@ |
||||
<?php declare(strict_types=1); |
||||
|
||||
namespace massivedynamic\lucidity\validators; |
||||
|
||||
use Exception; |
||||
|
||||
use massivedynamic\lucidity\exceptions\UnknownValidatorException; |
||||
use massivedynamic\lucidity\validators\Validation; |
||||
use massivedynamic\lucidity\validators\ValidationType; |
||||
use massivedynamic\lucidity\Utils; |
||||
|
||||
class Validator { |
||||
private array $errors; |
||||
private array $validators; |
||||
|
||||
public function __construct(private array $pool, array $validation) { |
||||
$this->errors = []; |
||||
$this->validators = self::ParseValidationArray($validation); |
||||
} |
||||
|
||||
public function validate() : ?array { |
||||
foreach ($this->validators as $key => $validators) { |
||||
foreach($validators as $validator) { |
||||
switch ($validator->type) { |
||||
case ValidationType::REQUIRED: |
||||
$this->validateRequired($key); |
||||
break; |
||||
case ValidationType::MIN: |
||||
$this->validateMin($key, $validator); |
||||
break; |
||||
case ValidationType::MAX: |
||||
$this->validateMax($key, $validator); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
return count($this->errors) > 0 ? $this->errors : null; |
||||
} |
||||
|
||||
private function validateMin(string $key, Validation $validator) : void { |
||||
$length = strlen($this->pool[$key]); |
||||
if ($length < intval($validator->parameter)) |
||||
array_push($this->errors, "Field '$key' must be at least $validator->parameter characters long (is $length)"); |
||||
|
||||
} |
||||
|
||||
private function validateMax(string $key, Validation $validator) : void { |
||||
$length = strlen($this->pool[$key]); |
||||
if ($length > intval($validator->parameter)) |
||||
array_push($this->errors, "Field '$key' must be at least $validator->parameter characters long (is $length)"); |
||||
|
||||
} |
||||
|
||||
private function validateRequired(string $key) : void { |
||||
if (!isset($this->pool[$key])) |
||||
array_push($this->errors, "Field '$key' is required"); |
||||
} |
||||
|
||||
/** |
||||
* @@param array<string, array> $validation |
||||
*/ |
||||
public static function ParseValidationArray(array $validation) : array { |
||||
$out = []; |
||||
|
||||
foreach ($validation as $key => $validators) { |
||||
$validators = explode("|", $validators); |
||||
if (count($validators) < 1) |
||||
continue; |
||||
|
||||
$out[$key] = []; |
||||
foreach ($validators as $validator) { |
||||
$parts = explode(":", $validator); |
||||
[$type, $argument] = count($parts) === 2 ? $parts : [$parts[0], ""]; |
||||
$argument = $argument ?? ""; |
||||
$typeResolved = ValidationType::tryFrom(strtolower($type)); |
||||
|
||||
if ($typeResolved === null) |
||||
throw new UnknownValidatorException($type); |
||||
|
||||
$validator = new Validation($typeResolved, $argument); |
||||
array_push($out[$key], $validator); |
||||
} |
||||
} |
||||
|
||||
return $out; |
||||
} |
||||
} |
@ -0,0 +1,12 @@ |
||||
{{<LucidityLayout}} |
||||
|
||||
{{$content}} |
||||
<main class="card"> |
||||
<h1>HTTP 404</h1> |
||||
<p> |
||||
page not found |
||||
</p> |
||||
</main> |
||||
{{/content}} |
||||
|
||||
{{/LucidityLayout}} |
@ -0,0 +1,35 @@ |
||||
{{< LucidityLayout}} |
||||
|
||||
{{$content}} |
||||
|
||||
<article class="card"> |
||||
<span class="badge"> |
||||
{{exceptionType}} |
||||
</span> |
||||
<h2> |
||||
{{exception.getMessage}} |
||||
</h2> |
||||
{{#exception.getFile}} |
||||
<a href="zed://file/{{.}}:{{exception.getLine}}" class="file">{{.}}:{{exception.getLine}}</a> |
||||
{{/exception.getFile}} |
||||
<section class="info"> |
||||
<span>PHP {{phpVersion}}</span> |
||||
<span>lucidity {{lucidityVersion}}</span> |
||||
</section> |
||||
</article> |
||||
|
||||
<article class="card edgeless"> |
||||
{{#trace}} |
||||
<article class="list-item"> |
||||
{{#file}} |
||||
<span class="file">{{.}}:{{line}}</span> |
||||
{{/file}} |
||||
<h4>{{class}}<h4> |
||||
<h3>{{function}}</h3> |
||||
</article> |
||||
{{/trace}} |
||||
</article> |
||||
|
||||
{{/content}} |
||||
|
||||
{{/LucidityLayout}} |
@ -0,0 +1,72 @@ |
||||
<html> |
||||
<head> |
||||
<title>{{title}}</title> |
||||
<style type=text/css> |
||||
* { |
||||
box-sizing: border-box; |
||||
} |
||||
|
||||
body { |
||||
font-family: sans-serif; |
||||
padding: 2rem; |
||||
background: #e5e7eb; |
||||
color: #333; |
||||
} |
||||
|
||||
.badge { |
||||
background : #ddd; |
||||
display: inline-block; |
||||
padding: 0.5rem 1rem; |
||||
border-radius: 4px; |
||||
font-weight: bold; |
||||
color: #444; |
||||
} |
||||
|
||||
.card { |
||||
background: white; |
||||
box-shadow: 0 0 16px rgba(0, 0, 0, 0.3); |
||||
margin-bottom: 4rem; |
||||
position: relative; |
||||
} |
||||
.card:not(.edgeless) { |
||||
padding: 2rem; |
||||
} |
||||
|
||||
.list-item { |
||||
padding: 1rem 2rem; |
||||
transition: all 0.2s ease-in-out; |
||||
} |
||||
.list-item:not(:last-of-type) { |
||||
border-bottom : solid 1px #ddd; |
||||
} |
||||
.list-item:hover { |
||||
background: #dd524c; |
||||
} |
||||
.list-item:hover, .list-item:hover * { |
||||
color: white !important; |
||||
} |
||||
|
||||
.file { |
||||
color: #aaa; |
||||
display: block; |
||||
} |
||||
|
||||
.info { |
||||
display: flex; |
||||
white-space: nowrap; |
||||
gap: 1rem; |
||||
color: #888; |
||||
font-weight: bold; |
||||
position: absolute; |
||||
top: 2rem; |
||||
right: 2rem; |
||||
} |
||||
|
||||
.info * { |
||||
flex: 1; |
||||
} |
||||
</style> |
||||
</head> |
||||
<body> |
||||
|
||||
{{$content}}{{/content}} |
Loading…
Reference in new issue