arse/includes/loader.inc.php

538 lines
11 KiB
PHP
Raw Normal View History

<?php
final class LoaderException extends Exception { }
final class ConfigGetter
{
private $config;
private $package;
public function __construct( Package $package , $config )
{
$this->package = $package;
$this->config = $config;
}
public function get( $path = '' , $default = null , $fail = false )
{
if ( $path == '' ) {
return $this->config;
}
$aPath = explode( '/' , $path );
$config = &$this->config;
foreach ( $aPath as $name ) {
if ( !( is_array( $config ) && array_key_exists( $name , $config ) ) ) {
if ( $fail ) {
throw new LoaderException( "configuration key '$path' not found for package '"
. $this->package->name() . "'" );
}
return $default;
}
$config = &$config[ $name ];
}
return $config;
}
}
final class Package
{
private $name;
private $source;
private $files;
private $requires;
private $ctrls;
private $daos;
private $extras;
private $hooks;
private $pages;
private $singletons;
private $views;
private $loaded = false;
private $config;
public function __construct( $name , $description , $config , $source )
{
$this->name = $name;
$this->source = $source;
$fields = array( 'files' , 'requires' , 'daos' , 'views' , 'ctrls' , 'extras' , 'singletons' , 'pages' , 'hooks' );
foreach ( $fields as $field ) {
$this->getField( $description , $field );
}
if ( empty( $this->files ) ) {
throw new LoaderException( "package '{$this->name}': no files" );
}
if ( ! is_array( $config ) ) {
$config = array( );
}
$this->config = new ConfigGetter( $this , $config );
}
private function getField( $description , $field )
{
if ( ! array_key_exists( $field , $description ) ) {
$this->$field = array( );
return;
}
$value = $description[ $field ];
if ( !is_array( $value ) ) {
throw new LoaderException( "package '{$this->name}': '$field' must be an array" );
}
foreach ( $value as $item ) {
if ( !is_string( $item ) ) {
throw new LoaderException( "package '{$this->name}': '$field' contains non-string items" );
}
}
$this->$field = $value;
}
public function name( )
{
return $this->name;
}
public function source( )
{
return $this->source;
}
public function files( )
{
return $this->files;
}
public function requires( )
{
return $this->requires;
}
public function daos( )
{
return $this->daos;
}
public function views( )
{
return $this->views;
}
public function ctrls( )
{
return $this->ctrls;
}
public function singletons( )
{
return $this->singletons;
}
public function extras( )
{
return $this->extras;
}
public function pages( )
{
return $this->pages;
}
public function loaded( )
{
return $this->loaded;
}
public function hooks( )
{
return $this->hooks;
}
public function setLoaded( )
{
$this->loaded = true;
}
public function config( $path = '' , $default = null , $fail = false )
{
return $this->config->get( $path , $default , $fail );
}
public function getConfigAccess( )
{
return $this->config;
}
}
interface PackageAware
{
public function setPackage( Package $package );
}
interface TextSource
{
public function get( $what );
}
final class Loader
{
private static $loader = null;
private static $paths = array();
private $config;
private $packages = array( );
private $items = array(
'ctrls' => array( ) ,
'daos' => array( ) ,
'extras' => array( ) ,
'singletons' => array( ) ,
'views' => array( ) ,
'pages' => array( ) ,
);
private $loading = array( );
private $singletons = array( );
private $daos = array( );
private $textSource;
private function __construct( )
{
if ( empty( Loader::$paths ) ) {
array_push( Loader::$paths , dirname( __FILE__ ) );
}
$this->loadConfig( );
$this->loadPackageDescriptions( );
}
private function loadConfig( )
{
$mergedConfig = array( );
foreach ( Loader::$paths as $directory ) {
if ( ! file_exists( $directory . '/config.inc.php' ) ) {
continue;
}
$config = array( );
@include( $directory . '/config.inc.php' );
$mergedConfig = array_merge_recursive( $mergedConfig , $config );
}
$this->config = $mergedConfig;
}
private function loadPackageDescriptions( )
{
foreach ( Loader::$paths as $source ) {
$this->loadPackageDescriptionsFrom( $source );
}
}
private function loadPackageDescriptionsFrom( $source )
{
if ( !( $dh = opendir( $source ) ) ) {
throw new LoaderException( "unable to access directory" );
}
while ( ( $entry = readdir( $dh ) ) !== false ) {
if ( $entry === '.' || $entry === '..' ) {
continue;
}
$path = "$source/$entry";
if ( is_dir( $path ) && is_file( "$path/package.inc.php" ) ) {
$this->loadDescription( $entry , $source );
}
}
closedir( $dh );
}
private function loadDescription( $name , $source )
{
$package = array( );
require( $source . '/' . $name . '/package.inc.php' );
if ( empty( $package ) ) {
throw new LoaderException( "package '$name': no information" );
}
if ( ! array_key_exists( $name , $this->config ) ) {
$this->config[ $name ] = array( );
}
$package = new Package( $name , $package , $this->config[ $name ] , $source );
$this->packages[ $name ] = $package;
$this->config[ $name ] = null;
foreach ( array_keys( $this->items ) as $type ) {
$items = $package->$type( );
foreach ( $items as $item ) {
if ( array_key_exists( $item , $this->items[ $type ] ) ) {
$oName = $this->items[ $type ][ $item ];
$type = substr( $type , 0 , strlen( $type ) - 1 );
throw new LoaderException( "package '$name': conflict with '$oName' on $type '$item'" );
}
$this->items[ $type ][ $item ] = $name;
}
}
}
private function loadPackage( $name )
{
if ( ! array_key_exists( $name , $this->packages ) ) {
throw new LoaderException( "Package '$name' not found" );
}
$package = $this->packages[ $name ];
if ( $package->loaded( ) ) {
return;
}
if ( array_key_exists( $name , $this->loading ) ) {
throw new LoaderException( "Package '$name': recursive dependencies detected" );
}
$this->loading[ $name ] = 1;
foreach ( $package->requires( ) as $dependency ) {
$this->loadPackage( $dependency );
}
$dir = $package->source() . '/' . $name;
foreach ( $package->files( ) as $file ) {
require_once( "$dir/$file.inc.php" );
}
unset( $this->loading[ $name ] );
$package->setLoaded( );
$hooks = $package->hooks( );
if ( is_array( $hooks ) ) {
foreach ( $hooks as $hook ) {
$hook( $this , $package );
}
}
}
private function findItem( $name , $type )
{
$rType = $type . 's';
if ( ! array_key_exists( $rType , $this->items ) ) {
throw new LoaderException( "Invalid type '$type'" );
}
if ( ! array_key_exists( $name , $this->items[ $rType ] ) ) {
throw new LoaderException( "Item '$name' of type $type not found" );
}
$package = $this->items[ $rType ][ $name ];
$this->loadPackage( $package );
return $this->packages[ $package ];
}
private function createInstance( $package , $cName , $args )
{
if ( empty( $args ) ) {
$instance = new $cName();
} else {
try {
$reflection = new ReflectionClass( $cName );
} catch ( ReflectionException $e ) {
throw new LoaderException( "Class $cName from package $pName not found" );
}
$instance = $reflection->newInstanceArgs( $args );
}
if ( is_a( $instance , 'PackageAware' ) ) {
$instance->setPackage( $package );
}
return $instance;
}
private function loadAndCreate( $type , $name , $cName , $args )
{
$package = $this->findItem( $name , $type );
return $this->createInstance( $package , $cName , $args );
}
private function getSingleton( $name )
{
if ( ! array_key_exists( $name , $this->singletons ) ) {
$this->singletons[ $name ] = $this->loadAndCreate( 'singleton' , $name , $name , array( ) );
}
return $this->singletons[ $name ];
}
private function getDao( $name )
{
if ( array_key_exists( $name , $this->daos ) ) {
return $this->daos[ $name ];
}
$cName = Loader::convertName( 'DAO' , $name );
$instance = $this->loadAndCreate( 'dao' , $name , $cName , array( ) );
$instance->setDatabase( Loader::Singleton( 'Database' ) );
$this->daos[ $name ] = $instance;
return $instance;
}
public function findClasses( $class , $type = 'extra' , $onlyLoadedPackages = true )
{
$rType = $type . 's';
if ( ! array_key_exists( $rType , $this->items ) ) {
throw new LoaderException( "Invalid type '$type'" );
}
$convertName = ( $type !== 'extra' );
$result = array( );
foreach ( $this->items[ $rType ] as $item => $pName ) {
$cName = $convertName ? Loader::convertName( $type , $item ) : $item;
if ( ! strcasecmp( $cName , $class ) ) {
continue;
}
$package = $this->packages[ $pName ];
if ( ! $package->loaded( ) ) {
if ( $onlyLoadedPackages ) {
continue;
}
$this->loadPackage( $pName );
}
try {
$ref = new ReflectionClass( $cName );
} catch ( ReflectionException $e ) {
throw new LoaderException( "Class $cName for $item of type $type and package $pName not found" );
}
if ( $ref->implementsInterface( $class ) || $ref->isSubclassOf( $class ) ) {
$result[] = $item;
}
}
return $result;
}
private static function get( )
{
if ( Loader::$loader === null ) {
Loader::$loader = new Loader( );
}
return Loader::$loader;
}
private static function convertName( $type , $name )
{
$cName = ucfirst( $type ) . '_';
foreach ( explode( '_' , $name ) as $part ) {
$cName .= ucfirst( $part );
}
return $cName;
}
public static function DirectCreate( $type , $convert , $args )
{
$name = array_shift( $args );
$cName = $convert ? Loader::convertName( $type , $name ) : $name;
return Loader::get( )->loadAndCreate( $type , $name , $cName , $args );
}
public static function AddPath( $path )
{
if ( empty( Loader::$paths ) ) {
array_push( Loader::$paths , dirname( __FILE__ ) );
}
if ( is_dir( $path ) ) {
array_push( Loader::$paths , $path );
}
}
public static function PackageConfig( $name )
{
$loader = Loader::get( );
$loader->loadPackage( $name );
return $loader->packages[ $name ]->getConfigAccess( );
}
public static function TextSource( TextSource $source = null )
{
if ( $source !== null ) {
Loader::get( )->textSource = $source;
} else {
return Loader::get( )->textSource;
}
}
public static function Text( $what )
{
$source = Loader::get( )->textSource;
return $source ? $source->get( $what ) : $what;
}
public static function Load( $name , $type = 'extra' )
{
Loader::get( )->findItem( $name , $type );
}
public static function Singleton( $name )
{
return Loader::get( )->getSingleton( $name );
}
public static function Create( )
{
return Loader::DirectCreate( 'extra' , false , func_get_args( ) );
}
public static function View( )
{
return Loader::DirectCreate( 'view' , true , func_get_args( ) );
}
public static function Ctrl( )
{
return Loader::DirectCreate( 'ctrl' , true , func_get_args( ) );
}
public static function Page( )
{
return Loader::DirectCreate( 'page' , true , func_get_args( ) );
}
public static function DAO( $name )
{
return Loader::get( )->getDao( $name );
}
public static function Find( $class , $type = 'extra' , $onlyLoadedPackages = true )
{
return Loader::get( )->findClasses( $class , $type , $onlyLoadedPackages );
}
}