<?php
use Lia\Events;
class Lia {
public string $cache_dir = __DIR__.'/../../cache/';
/** array <string namespace, \Lia\App $app> */
public array $apps;
/**
* array<\Lia\Events $event_name, array $callable_list>
* callable_list is an array of callables.
* Each event defines its own callback signature & is typically defined via an interface
* Example: Lia\ResponseInterface has signatures for events emitted by Liaison during deliver()
* Handling an event does not require the interface be implemented.
*
* For complex systems, I recommend using an interface.
*/
public array $events;
/** array<string $method_name, callable $callable> */
public array $methods;
/**
* Whether ready() has been called or not.
* `deliver()` only calls `ready()` when $is_ready is false.
*/
public bool $is_ready = false;
/**
* Add a global method to Liaison. Overwrites if it already exists.
*
* @param $method_name method name
* @param $callable any callable
*/
public function addMethod(string $method_name, callable|string $callable){
$this->methods[$method_name] = $callable;
}
public function addApp(\Lia\AppInterface $app){
$this->apps[$app->getNamespace()] = $app;
}
/**
* Load an addon via it's fully qualified name.
*
* @param $addon_fqn, a fully qualified name like 'lia:router'. namespace ('lia') and addon name ('router') MUST be separated by a colon (':').
* @return \Lia\AddonInterface an addon
*/
public function addon(string $addon_fqn): \Lia\AddonInterface {
$pos = strpos($addon_fqn, ":");
if ($pos===false)throw new \Exception("Addon name '$addon_fqn' MUST contain a colon (':').");
$app_namespace = substr($addon_fqn,0,$pos);
$addon_name = substr($addon_fqn,$pos+1);
if (!isset($this->apps[$app_namespace])){
// @TODO add `$lia->app()` method and call that instead of error checking in addon()
throw new \Exception("App '$app_namespace' is not set. Addon '$addon_fqn' cannot be loaded.");
}
$addon = $this->apps[$app_namespace]->getAddon($addon_name);
if (!($addon instanceof Lia\AddonInterface)){
throw new \Exception("Addon '$addon_fqn' MUST implement 'Lia\\AddonInterface'");
}
return $addon;
}
/** Emit an event
*
* @param $event_name the string event name
* @param ...$args any args to pass to registered callables
*
* @return array<int index, mixed value> of responses from each execution of the vent
*/
public function emit(string $event_name, ...$args): array {
$responses = [];
foreach ($this->events[$event_name]??[] as $callable){
$responses[] = $callable(...$args);
}
return $responses;
}
public function hook(string $event_name, callable|array $callable){
$this->events[$event_name][] = $callable;
}
/** ready up all apps */
public function ready(){
foreach ($this->apps as $app){
$app->onLiaisonReady($this);
}
$this->emit(Events::Ready->value, $this);
$this->is_ready = true;
}
/**
* Call onFinish() on all apps, then emit Events::Finish event.
* Does NOT stop execution. Use terminate() to stop execution or `exit` on your own
*
*/
public function finish(){
foreach ($this->apps as $app){
$app->onFinish($this);
}
$this->emit(Events::Finish->value, $this);
}
/**
* Call onTerminate() on all apps, then emit Events::terminate
* `exit`s.
*/
public function terminate(){
foreach ($this->apps as $app){
$app->onTerminate($this);
}
$this->emit(Events::terminate->value, $this);
exit;
}
/**
*
*/
public function deliver(\Lia\Http\Request $request = new \Lia\Http\Request(), ?\Lia\Http\Response $response = null){
$response = is_null($response) ? new \Lia\Http\Response($request) : $response;
if (!$this->is_ready){
$this->ready();
}
foreach ($this->apps as $app){
$app->onStartRequest($request,$response);
}
$this->emit(Events::StartRequest->value, $request, $response);
$event_responses = $this->emit(Events::GetHttpRoutes->value, $request, $response);
$routes = array_merge(...$event_responses);
if (count($routes)==0){
$this->emit(Events::NoHttpRoutes->value, $request, $response);
} else if (count($routes)>1){
$this->emit(Events::MultipleHttpRoutes->value, $request, $response, $routes);
} else {
$route = $routes[0];
$this->emit(Events::SingleHttpRoute->value, $request, $response, $route);
}
$this->emit(Events::ApplyTheme->value, $request, $response, $routes);
$response->send();
$this->emit(Events::EndRequest->value, $request, $response, $routes);
$this->finish();
}
/**
*
*/
public function execute(\Lia\CliExecution $cli){
$this->ready();
foreach ($this->apps as $app){
$app->onCliReady($this);
}
$this->emit(Events::CliReady->value, $this);
// @TODO actually execute the cli responder
// @TODO maybe onCliFinished() # yes, probably
$this->finish();
}
/**
* Throw an exception & report error to user.
*/
public function throw(\Exception $e, string $user_message = ""){
// @TODO log and/or print message
throw $e;
}
}