App.php

<?php

namespace Tiny;

class App implements \Tiny\IFace\App {
    use \Tiny\Getter;

    protected $user;
    protected $lock;
    protected $baseUrl;
    protected $envoy;
    protected $apps = [];
    protected $router;

    protected $data = [];

    public function __construct($appDir,$baseUrl='/'){
        $this->dir = $appDir;
        $this->baseUrl = $baseUrl;
        $this->includePhp();
        $this->router = $this->createRail('Router',$this->dir.'/public',$this->baseUrl);
    }
    public function deliver($url=NULL){
        $response = $this->getResponse($url);
        return $response ? $response->send() : false;
    }
    public function isRequested($url=NULL){
        $url = $url ?? $_SERVER['REQUEST_URI'];

        return $this->router->isRequested($url);
    }
    public function getResponse($url=NULL){
        $url = $url ?? $_SERVER['REQUEST_URI'];
        $path = $this->router->filePath($url);
        $urlParts = $this->router->getUrlParts($url);
        $content = '';
        $response = $this->createRail('Response');
        $frame = $this->createRail('Frame',$this->dir.'/frame',$response->output());

        $response->setBaseUrl($this->baseUrl);
        if ($path!=false){
            $pubFile = $this->createRail('PubFile',$path);
            $pubFile->exposeMethod('showResponseAt',[$response,'showAt']);
            $pubFile->exposeMethod('url',[$this->router,'url']);
            $pubFile->exposeMethod('message',[$response,'message']);
            $pubFile->exposeObjectParams((object)$urlParts,['slug','fileKey','action']);
            $pubFile->exposeMethod('get',[$this,'get']);
            $pubFile->exposeMethod('has',[$this,'has']);
            $pubFile->exposeMethod('addScript',[$frame,'addScript']);
            $pubFile->exposeMethod('unlocksAny',[$this->user,'unlocksAny']);
            $pubFile->exposeMethod('cleanUrl',[$this->router,'cleanUrl']);
            if ($this->user!=null){
                $pubFile->exposeMethod('allows',[$this->user,'unlocksAny']);
            }
            $response->setType($pubFile->getType());
            $content = $pubFile->content();
            $response->setContent($content);
        } else {
            $requestedApps = [];
            foreach ($this->apps as $app){
                if ($app->isRequested()){
                    $requestedApps[] = $app;
                }
            }
            if (count($requestedApps)==0){
                throw new \Exception("There is no file or sub-app to respond to this request.");
            } else if (count($requestedApps)>1){
                throw new \Exception("There are multiple apps responsive to this request.");
            }
            $subApp = $requestedApps[0];
            $subResponse = $subApp->getResponse($url);
            if ($subResponse->getType()=='redirect'){
                $subResponse->send();
                return;
            }
            $content = $subResponse->output();
            $response->setType($subResponse->getType());
            $response->setContent($content);
        }


        if ($response->getType()=='html'){
            $frame->setContent($response->output());
            $response->setContent($frame->output());
        } 

        return $response;
    }
    public function disablePublic($fileKey,$action='*',$slug='*'){
        $this->router->disablePublic($fileKey,$action,$slug);
    }

    public function addApp($app){
        $this->apps[] = $app;
    }

    public function loadApp($appDir,$baseUrl='/'){
        $this->apps[] = \Tiny::appFromDir($appDir,$baseUrl);
    }

    public function set($key, $value){
        $this->data[$key] = $value;
    }
    public function get($key){
        if (isset($this->data[$key])){
            return $this->data[$key];
        }
        throw new \Exception("Key '{$key}' is not set & cannot be retrieved.");
    }
    public function has($key){
        return isset($this->data[$key]);
    }

    protected function includePhp($path=null){
        if ($path!==null)throw new \Exception("Variable path is not currently permitted on method includePhp. You must pass NULL or nothing");
        $path = $path ?? $this->dir.'/php';
        if (!is_dir($path))return;
        $dir = opendir($path);
        while ($file = readdir($dir)){
            if ($file=='.'||$file=='..')continue;
            if (is_dir($newPath=$path.'/'.$file)){
                continue;
                $this->includePhpFiles($newPath);
            }
            $ext = pathinfo($file,PATHINFO_EXTENSION);
            if (strtolower($ext)=='php'){
                include($newPath);
            }
        }
    }

    public function setUser(\Tiny\IFace\User $user){
        $this->user = $user;
    }

    protected function createRail($railName,...$params){
        $this->allRails = $this->allRails ?? $this->getAllRails();
        $allRails = $this->allRails;
        $railInterface = 'Tiny\\IFace\\'.$railName;
        if (isset($allRails[$railInterface])){
            $class = $allRails[$railInterface];
            return new $class(...$params);
        } 
        $class = '\\Tiny\\'.$railName;

        return new $class(...$params);
    }

    protected function getAllRails(){
        $appDir = $this->dir;
        $rails = [];
        $railDir = $appDir.'/tiny';
        if (is_dir($railDir)){
            $files = scandir($railDir);
            foreach ($files as $file){
                if ($file=='.'||$file=='..'){
                    continue;
                } 
                $path = $railDir.'/'.$file;
                $ext = pathinfo($path,PATHINFO_EXTENSION);
                if ($ext!='php')continue;
                include_once($path);
                $class = $this->getClassFromFile($path);
                $interfaces = $this->getInterfacesFromClass($class);
                $interfaces = array_combine($interfaces,$interfaces);
                foreach ($interfaces as $interface){
                    $rails[$interface] = $class;
                }
            }
        }
        return $rails;
    }
    protected function getClassFromFile($file){
        $fp = fopen($file, 'r');
        $class = $namespace = $buffer = '';
        $i = 0;
        while (!$class) {
            if (feof($fp)) break;

            $buffer .= fread($fp, 512);
            $tokens = token_get_all($buffer);

            if (strpos($buffer, '{') === false) continue;

            for (;$i<count($tokens);$i++) {
                if ($tokens[$i][0] === T_NAMESPACE) {
                    for ($j=$i+1;$j<count($tokens); $j++) {
                        if ($tokens[$j][0] === T_STRING) {
                            $namespace .= '\\'.$tokens[$j][1];
                        } else if ($tokens[$j] === '{' || $tokens[$j] === ';') {
                            break;
                        }
                    }
                }

                if ($tokens[$i][0] === T_CLASS) {
                    for ($j=$i+1;$j<count($tokens);$j++) {
                        if ($tokens[$j] === '{') {
                            $class = $tokens[$i+2][1];
                        }
                    }
                }
            }
        }
        if ($class=='')return '';
        return $namespace.'\\'.$class;
    }
    protected function getInterfacesFromClass($class){
        if ($class=='')return [];
        $ref = new \ReflectionClass($class);
        return $ref->getInterfaceNames();
    }
}