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();
}
}