<?php
class Liaison {
/**
* Used as a default for get() so an exception can be thrown
*/
const NOT_SET = 'KDSFunql;kuDF^*&3124tupD*(FFgpib143^(F&b$!@#TADFSSbnFLJKSD:U893y2149hoj;andf*URn';
/**
* Create a new Liaison object
*
* @param array $options ['bare'=>true] to disable loading of the built-in package
* @featured
*/
public function __construct(array $options = []){
$this->debug = $options['debug'] ?? false;
$this->addApi('lia:config.get',[$this, 'get']);
$this->addApiMethod('lia:config.get', 'get');
$this->addApi('lia:config.set',[$this, 'set']);
$this->addApiMethod('lia:config.set', 'set');
$this->addApi('lia:config.default',[$this, 'default']);
$this->addApiMethod('lia:config.default', 'default');
$this->addApi('lia:config.append',[$this, 'append']);
$this->addApiMethod('lia:config.append', 'append');
if (($options['bare']??false))return;
new \Lia\Package($this, dirname(__DIR__),['name'=>'lia']);
}
/**
* All mediators
* [ 'api.apiKey.handler' => $callable,
* 'method.methodName' => $callable,
* 'prefix.prefixName' => $callable,
* 'submediate.method.methodName' => $callable
* ]
*
*/
protected $mediators = [];
/**
* Global APIs.
* Accessible by calling $lia->api('api.name', 'handler', ...$args);
* [ 'api.name' => [ 'handler' => $callable, 'handler2' => $callable2]];
*/
protected $api = [];
/**
* Global prefixes.
* Accessible by declaring public function prefixWhatever(){} in a component
* ['prefix' => [0=>'api.name',1=>'handler'] , 'prefix2'...]
*
*/
protected $prefix = [];
/**
* Global methods executable by $lia->methodName()
* ['methodName' => [0=>'api.name',1=>'handler'] , 'method2'...]
*
*/
protected $method = [];
/**
* Map of values for set/get/append
*/
protected $valuesMap = [];
//
// Get & Set
//
/**
* set the default $value for the $key. This will never overwrite a previously set value.
*/
public function default($key, $value){
if (!\Lia\Utility\NS::has($this->valuesMap, $key)){
$this->set($key,$value);
}
}
/**
* Get a stored value.
*
* @param $key the key to retrieve
* @param $default any value to use if the key is not found.
* @throws if $key is not available and $default is not passed
*/
public function get($key, $default=self::NOT_SET){
if ($key=='compo'){
echo "\nget compo\n";
var_dump($key);
echo "\n";
var_dump(array_keys($this->valuesMap['compo']));
$key = array_keys($this->valuesMap['compo'])[1];
var_dump(array_keys($this->valuesMap['compo'][$key]));
exit;
exit;
}
if (\Lia\Utility\NS::has($this->valuesMap, $key)){
return \Lia\Utility\NS::get($this->valuesMap,$key);
}
if ($default==self::NOT_SET){
throw new \Lia\Exception\Base("Config '{$key}' is not set.");
} else {
return $default;
}
}
/**
* Set a value to $key
*
* @param $key the key to set, generally in form of `namespace:some.key`. Namespace & suggested key syntax are optional
* @param $value the value to set
*/
public function set($key, $value){
\Lia\Utility\NS::set($this->valuesMap, $key, $value);
}
/**
* Append a value to an indexed array. Create the array if not $key not set
*
* @todo Write a test for config::append()
*/
public function append($key, $value){
$existing = \Lia\Utility\NS::get($this->valuesMap, $key);
if ($existing===null)$existing = [];
if (!is_array($existing)){
throw new \Lia\Exception\Base("Cannot append to '$key'. It is already set & is not an array");
}
$existing[] = $value;
\Lia\Utility\NS::set($this->valuesMap, $key, $existing);
}
/**
* Append a value to an indexed array. Create the array if not $key not set
*
* @todo Write a test for config::append()
*/
public function arset($nsKey, $arrayKey, $value){
echo "\nBEGIN arset\n";
$existing = \Lia\Utility\NS::get($this->valuesMap, $nsKey);
if ($existing===null)$existing = [];
if (!is_array($existing)){
throw new \Lia\Exception\Base("Cannot append to '$nsKey'. It is already set & is not an array");
}
var_dump($nsKey);
var_dump($arrayKey);
echo "\n\n";
// var_dump(is_object($value));
$existing[$arrayKey] = $value;
\Lia\Utility\NS::set($this->valuesMap, $nsKey, $existing);
if ($nsKey=='lia:package'){
// echo "\n\nIS LIA PACKAGE\n";
// var_dump($value);
// echo "\n\n\n";
//
// echo "\n--------------------------------\n";
//
// var_dump($this->valuesMap['package']);
// echo "\n--------------------------------\n";
}
}
public function arget($nsKey, $arrayKey, $default=self::NOT_SET){
$array = $this->get($nsKey,$default);
if ($nsKey=='package'){
// echo "\n/////////////////////////////////////\n";
// var_dump(array_keys($this->valuesMap));
// var_dump($this->valuesMap['package']);
// echo "\n\n\n";
// echo "zeep\n";
// var_dump($nsKey);
// var_dump($arrayKey);
// var_dump($array);
// exit;
}
if (!isset($array[$arrayKey])&&$default===self::NOT_SET){
throw new \Lia\Exception\Base("Cannot get '$arrayKey' from '$nsKey'. '$nsKey' found, but '$arrayKey' not set.");
}
return $array[$arrayKey];
}
// public function setter($key, $globalMethodName, $setFunction = 'set'){
// $this->addMethod($globalMethodName,
// function(...$args) use ($key, $setFunction){
// $this->$setFunction($key, ...$args);
// }
// );
// }
//
// Short & Sweet
//
/** Get the full array of global apis */
public function getApiList(){
return $this->api;
}
/** Get the full array of global prefixes */
public function getApiPrefixes(){
return $this->prefix;
}
/** Get the full list of global methods */
public function getApiMethods(){
return $this->method;
}
//
//convenience methods for web developers
//
/**
* Add a global method to Liaison.
*
* @return string The uniqid() apikey the method points to. Handler is 'method'
*/
public function addMethod(string $globalMethodName, callable $callable){
$key = uniqid();
$this->addApi($key, $callable);
$this->addApiMethod($key, $globalMethodName);
return $key;
}
/**
* Add a global prefix to Liaison
*
* @return string The uniqid() apiKey the prefix points to. Handler is 'prefix'
*/
public function addPrefix($prefixName, $callable){
$key = uniqid();
$this->addApi($key, $callable);
$this->addApiPrefix($key,$prefixName);
return $key;
}
//
//adding to api
//
/**
* Run a conflict through a registered mediator.
* See addMediator() for more info
*
* @return the winner, as determined by the registered callable
* @throw \Lia\Exception\Base if a mediator is not set for the given key
*/
protected function mediate(string $mediatorKey, $old, $new){
if($old==null)return $new;
//@TODO consider adding partial wildcard mediators, like api.* & prefix.*
// This could even be api.apiKey.* if I want the same mediator for each handler
// I'm concerned about performance, so I'm reluctant to add this feature.
// But, It would make sense that mediation is an advanced feature that comes with some reasonable downsides
$mediator = $this->mediators[$mediatorKey] ?? $this->mediators['*'] ?? null;
if ($mediator==null){
throw new \Lia\Exception\Base("No mediator found to handle '{$mediatorKey}', but a duplicate was added.");
}
$winner = $mediator($mediatorKey, $old, $new);
return $winner;
}
/**
* Add a mediator to handle conflicts.
*
* Built-in mediatorPrefixes are: submediate, api, prefix, and method
*
* @param $mediatorKey `mediationPrefix:namespace:api.name`
* @param $callable function($mediatorKey, $oldThing, $newThing) <- usually new&old thing will be callables
* @todo How to handle no namespace on the api?
*/
public function addMediator(string $mediatorKey, callable $callable){
if (isset($this->mediators[$mediatorKey])){
$callable = $this->mediate('submediate:'.$mediatorKey, $this->mediators[$mediatorKey], $callable);
}
$this->mediators[$mediatorKey] = $callable;
}
/**
* Add an API
*
* @param $api `namespace:api.name`. Namespace is optional.
* @param $callable to be called when the named api is invoked
* @featured
*/
public function addApi(string $api, callable $callable){
if (\Lia\Utility\NS::has($this->api,$api)){
$callable = $this->mediate('api:'.$api,
\Lia\Utility\NS::get($this->api, $api),
$callable);
}
\Lia\Utility\NS::set($this->api, $api, $callable);
}
/**
* Add a global method, pointing to an api
*
* @param $api api to be invoked. `namespace:api.name`. namespace optional
* @param $methodName `$lia->$methodName()` will invoke the $api
* @featured
*/
public function addApiMethod(string $api, string $methodName){
if (isset($this->method[$methodName])){
$pointer = $this->mediate('method:'.$methodName, $this->method[$methodName], $api);
} else {
$pointer = $api;
}
$this->method[$methodName] = $pointer;
}
/**
* Add a global prefix to point to a particular api
*
* @param $api api to pass the prefixed method to. `namespace:api.name`. namespace optional
* @param $prefix declare `prefixWhatever` or `prefix_Whatever` & the method will be passed to the $api
*
* @featured
*/
public function addApiPrefix(string $api, string $prefix){
if (isset($this->prefix[$prefix])){
$pointer = $this->mediate('prefix:'.$prefix, $this->prefix[$prefix], $api);
} else {
$pointer = $api;
}
$this->prefix[$prefix] = $pointer;
}
//
// removing/modifying the api
//
/**
* Remove the given api.
* Does not unset associated prefixes/methods.
*
* @param $api the api to remove
*/
public function removeApi($api){
return \Lia\Utility\NS::unset($this->api,$api);
}
/**
* remove the given method name (doesn't affect the api)
*/
public function removeMethod($methodName){
unset($this->method[$methodName]);
}
/**
* remove the given prefix name (doesn't affect the api)
*/
public function removePrefix($prefixName){
unset($this->prefix[$prefixName]);
}
//
//calling the api
//
/** Check if the given API exists
* @featured
*/
public function hasApi(string $api){
return \Lia\Utility\NS::has($this->api, $api);
}
/**
* Check if the apiNamespace exists
* @param $apiKeyOrNamespace a namespace like `namespace` or an api key like `namespace:some.key`
*/
public function hasApiNamespace(string $apiOrNamespace){
return \Lia\Utility\NS::hasNamespace($this->api, $apiOrNamespace);
}
/**
* Call the given api
*
* @param $api `namespace:api.name`. namespace optional.
* @param $args whatever the api wants
* @return whatever the api returns
* @throw Lia\Exception\Base if the api handler is not set
* @featured
*/
public function api(string $api, ...$args){
$apiCallable = \Lia\Utility\NS::get($this->api,$api,$ns);
if ($apiCallable==null){
throw new \Lia\Exception\Base("Api '$api' doesn't exist.");
}
return $apiCallable(...$args);
}
/**
* Call the global method
*
* @throw Lia\Exception\Base if the global method is not set
*/
public function __call($methodName, $args){
if (!isset($this->method[$methodName])){
throw new \Lia\Exception\Base("Method '{$methodName}' has not been set on liaison.");
}
$m = $this->method[$methodName];
return $this->api($m, ...$args);
}
/**
* This is entirely for the sake of testing the Base Exception class. It should clearly NEVER be called in production
*/
public function testThrowBaseExceptionClass($level=0){
if ($level<4){
$this->testThrowBaseExceptionClass(++$level);
return;
}
throw new \Lia\Exception\Base("Testing the base exception class with nested calling.");
}
}