Lia.php

<?php

class Lia {

    /**
     * Array of addons without namespaces, like 'router'
     */
    public $addons = [];
    /**
     * array of addons with namespaces, like 'lia:server.router'
     */
    public $fqn_addons = [];
    /**
     * array of global methods
     */
    public $methods = [];
    /** Array of packages identified by names like 'server' */
    public $packages = [];
    /** packages where key is fqn, like 'lia:server' */
    public $fqn_packages = [];


    /** key=>value properties */
    public array $props = [];

    /**
     * Array where `key` is the prefix and `value` is a callable that is executed when a prefixed-method is found
     * callables must accept following args:
     * function($object, $method, $dot_name){}
     * Where $dot_name has the prefix removed & each `_` replaced with `.`
     */
    public $prefixes = [];

    public function __construct(){

    }

    /**
     * Scan object for prefixed methods & pass them to the prefix handler
     *
     * @param $prefix a method prefix to scan for like 'on'
     * @param $object the object to scan for methods
     */
    public function scan(string $prefix, object $object){
        $methods = get_class_methods($object);

        if (!isset($this->prefixes[$prefix])){ 
            throw new \Exception("No handler for prefix '$prefix'.\n\n");
        }
        $handler = $this->prefixes[$prefix];
        $len = strlen($prefix);
        foreach ($methods as $m){
            if (substr($m,0,$len)==$prefix){
                $dot_name = strtolower(substr($m,$len));
                $dot_name = str_replace('_','.',$dot_name);
                $handler($object, $m, $dot_name);
            }
        }
    }

    public function __parse__($key,&$addon,&$prop){
        $pos = strpos($key, ':');
        if ($pos!==false){
            $pos2 = strpos($key,'.');
            $pos3 = strpos($key,'.', $pos2+1);
            $addon_name = substr($key,0,$pos3);
            $addon = $this->fqn_addons[$addon_name];
            $prop = substr($key,$pos3+1);
            return;
        } 
        
        $pos2 = strpos($key,'.');
        if ($pos2!==false){
            $addon_name = substr($key,0,$pos2);
            $addon = $this->addons[$addon_name]??null;
            $prop = substr($key, $pos2+1);
            return;
        }
    }

    /**
     * Set a key/value to an addon
     *
     * @param $key a key like 'lia:server.cache.dir`, `cache.dir`
     * @param $value value to set to the property on the given addon
     */
    public function set($key, $value){
        $this->__parse__($key, $addon, $prop);
        if ($addon==null){
            $this->props[$key] = $value;
        } else {
            $addon->$prop = $value;
        }
    }
    /**
     * @param $value a value to append to an array property on the given addon
     * @param $key a key like `lia:server.cache.dir` or `cache.dir`
     */
    public function append($key,$value){
        $this->__parse__($key, $addon, $prop);
        $addon->$prop[] = $value;
    }

    /**
     * @param $key a key like `lia:server.cache.dir` or `cache.dir`
     */
    public function get($key){
        $this->__parse__($key, $addon, $prop);
        if ($addon==null){
            return $this->props[$key];
        } else {
            return $addon->$prop; //= $value;
        }
    }

    public function has($key){
        $this->__parse__($key, $addon, $prop);
        if ($addon==null){
            return isset($this->props[$key]);
        } else {
            return isset($addon->$prop);
        }
    }

    // public function default($key, $value){
        // $conf = &$this->props;
        // foreach (explode('.', $key) as $key){
            // $conf = &$conf[$key];
        // }
        // if (empty($conf)){
            // $conf = $value;
        // }
    // }

    /**
     * Add an addon by it's fully qualified name
     * @param $addon, generally a `\Lia\Addon`, but can be any object
     * @param $fqn the fully qualified name like `namespace:package.addon_name`
     */
    public function addAddon(object $addon, string $fqn){
        $this->fqn_addons[$fqn] = $addon;
        $pos = strrpos($fqn, '.');
        $name = substr($fqn, $pos+1);
        $this->addons[$name] = $addon;

    }
    // public function addAddon($addon, $name){
        // $this->addons[$name] = $addon;
    // }

    public function addMethod($name, $callable){
        $methods = &$this->methods;
        foreach (explode('.', $name) as $key){
            $methods = &$methods[$key];
        }
        $methods = $callable;
    }

    public function has_method(string $method_name): bool {
        return isset($this->methods[$method_name]);
    }
    // public function _api($name, ...$args){
    //     return $this->_lia->api($name,...$args);
    // }
    // public function api($name, ...$args){
    //     $methods = &$this->methods;
    //     foreach (explode('.', $name) as $key){
    //         $methods = &$methods[$key];
    //     }
    //
    //     return $methods(...$args);
    // }

    /**
     * @param $prefix a method prefix
     * @param $method a callable that will handle any methods using the prefix
     */
    public function addPrefix(string $prefix, callable $method){
        // var_dump("AddPrefix: $prefix");
        $this->prefixes[$prefix] = $method;
    }

    /**
     * Get an addon by it's fully-qualified-name
     * @param $fqn string like 'lia:server.router'
     */
    public function addon($fqn): \Lia\Addon {
        if (!isset($this->fqn_addons[$fqn])){
            throw new \Lia\Exception(\Lia\Exception::ADDON_NOT_SET,$fqn,implode(", ", array_keys($this->fqn_addons)));
        }
        return $this->fqn_addons[$fqn];
    }
    /**
     * Get a package by it's fully-qualified-name
     * @param $fqn string like 'lia:server'
     */
    public function package($fqn): \Lia\Package {
        if (!isset($this->fqn_packages[$fqn])){
            throw new \Lia\Exception(\Lia\Exception::PACKAGE_NOT_SET,$fqn,implode(", ", array_keys($this->fqn_packages)));
        }
        return $this->fqn_packages[$fqn];
    }

    public function __call($method, $args){
        return $this->methods[$method](...$args);
    }

    /**
     * get the named addon
     * @param $addon the name of the addon
     */
    public function __get($addon){
        $a = $this->addons[$addon];
        return $a;
    }

    /**
     * get the named addon
     * @param $name the addon name
     * @param $addon the addon to set
     */
    public function __set($name, $addon){
        $this->addons[$name] = $addon;
    }

    public function dump_thing($thing){
        $thing = $this->__map_array__($thing);
        print_r($thing);
        unset($thing);
    }

    /**
     * Dump a bunch of info about liaison. Methods. Addons. Properties.
     * @param $thing a variable to dump or null to dump liaison fully
     */
    public function dump($thing=null){
        $c = [$this, '__map_array__'];
        if ($thing!=null){
            print_r($c($thing));
            return;
        }


        echo "\n\n\n-----------\nMethods:\n";
        $methods = array_map($c, $this->methods);
        print_r($methods);
        unset($methods);

        echo "\n\n\n-----------\nLia Props:\n";
        $props = array_map($c, $this->props);
        print_r($props);
        unset($props);

        echo "\n\n\n-----------\nPrefixes:\n";
        $prefixes = array_map($c, $this->prefixes);
        print_r($prefixes);
        unset($prefixes);
        echo "\n\n\n";

        echo "\n\n\n-----------\nAddons, Short Name:\n";
        $addons = array_map($c, $this->addons);
        print_r($addons);
        unset($addons);

        echo "\n\n\n-----------\nAddons, fqn:\n";
        $fqn_addons = array_map($c,$this->fqn_addons);
        print_r($fqn_addons);
        unset($fqn_addons);

        echo "\n\n\n-----------\nPackage Properties Tree:\n";
        $package_props = [];
        $addon_props = [];
        foreach ($this->packages as $k=>$p){
            $package_props[$k] = array_map($c,get_object_vars($p));
            foreach ($p->addons as $addon_key => $addon){
                $addon_props[$k][$addon_key] = array_map($c,get_object_vars($addon));
            }
        } 
        print_r($package_props);
        unset($package_props);

        echo "\n\n\n-----------\nAddon Properties Tree:\n";
        print_r($addon_props);
        unset($addon_props);

    }

    public function __map_array__($value){
        if (is_callable($value)&&is_array($value)
            &&is_object($value[0]))return get_class($value[0]).'#'.spl_object_id($value[0]).'->'.$value[1];
        if (is_object($value))return get_class($value).'#'.spl_object_id($value);
        if (is_array($value))return array_map([$this, '__map_array__'], $value);

        return $value;
    }

}