<?php
namespace Lia;
/**
* Encaspulate multiple components, views, routes & other bits in a single directory as a package.
*
* @featured
* @tag setup
*/
class OldPackage implements \LiaisonInterface\PackageLifeCycle {
/**
* The configured name of this package or uniqid() if not specified
*/
public $name;
/**
* The root dir for the package
* @featured
*/
public $dir;
/**
* the liaison instance
* @featured
*/
public $lia;
/**
* All compos added. `['name'=>$compoObject, 'name2'=>$otherCompo]`
* @featured
*/
protected $compos = [];
/**
* Components implementing `LiaisonInterface\PackageLifeCycle`
* @warning only initialized after first call to `lifecycle()`
*/
protected $lifecycleCompos = [];
/**
* Configurations for the package
* This implementation is going to change, but I don't know how that's gonna work yet. I just know I don't like it.
*/
//@export_start(Usage.Package.DefaultConfigs)
protected $config = [
'namespace'=>'',
'dir'=>
[
'component' => 'core',
// 'public' => 'public',
'autoload'=> 'class',
'view' => 'view',
'cache' => 'cache',
'package_lifecycle' => 'package_lifecycle',
'public'=>[ // @TODO rethink config structure. public maybe shouldn't be nested under dir? Idk....
'dir' => 'public',
'baseUrl'=> '',
]
],
'route'=>
[
'force_trail_slash'=>true,
'index_names'=>
[
'index'
],
'hidden_extensions'=>
[
'php'
],
],
'class'=>
[
'view' => '\\Lia\\Obj\\View'
],
'views'=>
[
// 'conflict' => 'throw'
]
];
//@export_end(Package.DefaultConfigs)
/**
* Initialize package.
*
* - Configs are loaded from a config file in the package. Any keys set in the $config array will replace values set in the config file
* - Setup is only performed if dir is non-null
*
* @param \Liaison $lia a Liaison instance
* @param string $dir the root directory for the package or null to skip setup.
* @param array $configs Configs to set. Pass ['name'=>'SomeName'], to later use `$lia->package('SomeName')` to retrieve it
*
* @featured
* @note removed type from first param to support incremental backward-compatible changes
*/
public function __construct( $lia, ?string $dir=null, array $configs = []){
$this->dir = $dir;
$this->lia = $lia;
if ($dir!=null){
$this->setup($configs, $dir);
}
}
/**
* Setup the package.
* @warning Can be called multiple times but shouldn't be.
*/
public function setup(array $configs, ?string $dir = null){
if ($dir!==null)$this->dir = $dir;
if (!is_dir($this->dir)){
$class = get_class($this);
throw new \Lia\Exception("Package dir must be set via `new ${class}(\$lia, \$dir)` or ${class}->setup(\$configs, \$dir) call. '$dir' is not valid");
}
$lia = $this->lia;
$this->setup_config_file($this->dir.'/config.json');
$configs = \Liaison\Utility\DotNotation::nestedFromDotted($configs);
$this->config = array_replace_recursive($this->config, $configs);
$this->setup_components($this->dir('component'));
$this->lifecycle('onComponentsLoaded');
$this->setup_autoload($this->dir('autoload'));
$this->setup_views(realpath($this->dir('view')),
['lia'=>$lia, 'package'=>$this]
);
$this->setup_public_routes($this->dir('public'));
$this->lifecycle('onPrePackageReady');
$this->lifecycle('onPackageReady');
$name = $this->config['name'] ?? uniqid();
if ($this->get('namespace')==null)$this->set('namespace',$name);
$this->name = $name;
if ($lia->hasApi('lia:package.add')){
$lia->api('lia:package.add', $this, $this->get('namespace'));
}
}
/** @override */
public function onComponentsLoaded(){}
/** @override */
public function onPrePackageReady(){}
/** @override */
public function onPackageReady(){}
/**
* Execute the given lifecycle method on each component in this package
* Execute same-named php file in the package's `package_lifecycle` dir
* @param $name the name of the lifecycle method
*/
protected function lifecycle($name){
if ($this->lifecycleCompos==null){
foreach ($this->compos as $c){
if ($c instanceof \LiaisonInterface\PackageLifeCycle)
$this->lifecycleCompos[] = $c;
}
}
$this->$name();
foreach ($this->lifecycleCompos as $compo){
$compo->$name();
}
//@todo test running hook files in lifecycle() package method
if (is_dir($hookDir=$this->dir('package_lifecycle'))){
if (is_file($hookFile=$hookDir.'/'.$name.'.php')){
require($hookFile);
}
}
}
//
// utility functions for processing/setup
//
/**
* Add a component to the package.
*
* @param string $name name to use for fetching the component
* @param mixed $component Generally a \Lia\Compo, but may be anything.
* @tag setup
*/
public function addComponent($name, $component){
$name = strtolower($name);
$this->compos[$name] = $component;
}
/**
* Load Config file. Currently `$package->dir.'/config.json'`.
* @param string $configFile the config file to load
* @tag setup
*/
protected function setup_config_file(string $configFile){
if (file_exists($configFile)){
$json = file_get_contents($configFile);
$configs = json_decode($json,true);
$configs = \Liaison\Utility\DotNotation::nestedFromDotted($configs);
$merged = array_replace_recursive($this->config,$configs);
$this->config = $merged;
}
}
/**
* Convert a relative file path into a pattern for the Router component
*
* @todo move file path => pattern conversion into router
* @tag utility, routing
*/
public function fileToPattern($relFile){
$pattern = $relFile;
$ext = pathinfo($relFile,PATHINFO_EXTENSION);
$hidden = $this->config['route']['hidden_extensions'];
if (in_array($ext,$hidden)){
$pattern = substr($pattern,0,-(strlen($ext)+1));
$ext = '';
}
$indexNames = $this->config['route']['index_names'];
$base = basename($pattern);
if (in_array($base,$indexNames)){
$pattern = substr($pattern,0,-strlen($base));
}
if ($ext==''
&&$this->config['route']['force_trail_slash'])$pattern .= '/';
$pattern = str_replace(['///','//'], '/', $pattern);
return $pattern;
}
// get stuff/info from package
/**
* Set a config on the package (Does NOT propagate to Liaison)
* @param $dotKey
* @featured
*/
public function set($key, $value){
$array = \Liaison\Utility\DotNotation::nestedFromPair($key,$value);
$this->config = array_replace_recursive($this->config, $array);
}
/**
* Get a config from the package (NOT from Liaison)
* @featured
*/
public function get($key){
return \Liaison\Utility\DotNotation::getNestedValue($key, $this->config);
}
/**
* Get the configured class for the given key.
* Shorthand for `get('class.$key');`
* @featured
*/
public function class($forKey){
return $this->config['class'][$forKey];
}
/**
* Get the named component from the package
*
* @param $name usually the component's class name (without namespace)
* @featured
*/
public function compo($name){
$name = strtolower($name);
$compo = $this->compos[$name] ?? null;
return $compo;
if ($compo==null){
$packageName = $this->get('name') ?? '--unnamed-package--';
throw new \Lia\Exception("A compo with name '{$name}' does not exist for package {$packageName}");
}
return $compo;
}
/**
* Get the path to a directory in this package, or the package root dir if `$forKey==null`
*
* @param string $dirName configured directory key/name.
* @return string directory path
* @featured
*/
public function dir($forKey=null){
if ($forKey==null)return $this->dir;
$dir = $this->config['dir'][$forKey] ?? null;
if ($dir===null)return null;
if (is_array($dir))$dir = $dir['dir'];
return $this->dir.'/'.$dir;
}
/**
* Prepends base url to the given path & sets $getparams as querystring. Replaces all double shashes (`//`) with single (`/`)
*
* @param $getParams key=>value array or a query string
* @return a url path (no domain)
* @todo create a version of this method in the Router component
* @featured
*/
public function url($path, $getParams=null){
$get = $getParams;
$qs = '';
if (is_array($get)){
$qs = http_build_query($get);
} else {
$qs = ''.$get;
}
$qs = substr($qs,0,1)=='?' ? $qs : '?'.$qs;
$base = $this->get('dir.public.baseUrl');
$url = '/'.$base.'/'.$path;
$url = str_replace(['////','///','//'], '/', $url);
$url = $qs=='?' ? $url : $url.$qs;
return $url;
}
//
//load all the pieces of a package
//
/**
* Adds configured 'autoload' dir to the Autoloader component
*
* @param $dir the directory to setup for autoloading
* @tag setup
*/
public function setup_autoload($dir){
if ($this->lia->hasApi('lia:autoload.addDir'))
$this->lia->api('lia:autoload.addDir', $dir);
}
/**
* Load all components from the configured 'component' dir
*
* @param $dir the directory components are in
* @tag setup
*/
protected function setup_components(string $dir){
$classes = \Lia\Utility\ClassFinder::classesFromDir($dir);
// var_dump($dir);
// var_dump($classes);
// exit;
if (count($classes)==0)return;
$components = [];
foreach ($classes as $info){
// if (!in_array('Lia\\iCore\\Compo',$info['interfaces']))continue;
$className = $info['class'];
$compo = new $className($this);
}
}
/**
* Add all files in configured 'public' dir to the Router component
*
* @param $dir the directory to scan for public routed files
* @param $prefix the prefix for all urls
* @tag setup
*/
public function setup_public_routes($dir, $prefix=null){
$prefix = $prefix ?? $this->config['dir']['public']['baseUrl'];
$lia = $this->lia;
$files = \Lia\Utility\Files::all($dir,$dir);
foreach ($files as $relFile){
$pattern = $this->fileToPattern($relFile);
$pattern = $prefix.$pattern;
$pattern=$this->lia->clean_url($pattern);
$path = $dir.$relFile;
$lia->api('lia:route.add', $pattern, $path, $this);
}
}
/**
* Add all views in configured 'view' dir to the View Component
*
* @param string $dir a view directory to setup
* @param array $args a key=>value array of args to extract to all views
* @todo add a way to configure $args for your package, without having to override the package class
* @tag setup
*/
public function setup_views(string $dir, array $args){
$lia = $this->lia;
$files = \Lia\Utility\Files::all($dir,$dir, '.php');
//set view conflict mode
$hasConfigApi = $lia->hasApi('lia:config.get');
$oldConflictMode = $hasConfigApi ? $lia->api('lia:config.get', 'lia:view.conflictMode') : null;
$conflictMode = $this->config['views']['conflict'] ?? $oldConflictMode ?? 'throw';
if ($hasConfigApi)$lia->api('lia:view.setConflictMode', $conflictMode);
foreach ($files as $f){
//remove leading `/` and trailing `.php` in a pretty simple, dumb way
$viewName = substr($f,1,-4);
$viewName = $this->get('namespace').':'.$viewName;
$class = $this->class('view');
$dir = $dir;
if ($hasConfigApi&&$viewName=='theme')$lia->api('lia:view.setConflictMode', 'overwrite');
$lia->api('lia:view.add', $class, $dir, $viewName, $args);
if ($hasConfigApi&&$viewName=='theme')$lia->api('lia:view.setConflictMode', $conflictMode);
}
$mth = array_map(function($v){if (!is_string($v)&&!is_null($v))return 'not-string';}, $lia->methods);
// $mth = $lia->methods;
$lia->api('lia:view.setConflictMode', $oldConflictMode);
//@export_end(Usage.View.AddView)
}
}