2012-02-05 12:56:54 +01:00
|
|
|
<?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;
|
2012-02-05 13:58:55 +01:00
|
|
|
private $source;
|
2012-02-05 12:56:54 +01:00
|
|
|
|
|
|
|
private $files;
|
|
|
|
private $requires;
|
|
|
|
|
|
|
|
private $ctrls;
|
|
|
|
private $daos;
|
|
|
|
private $extras;
|
|
|
|
private $hooks;
|
|
|
|
private $pages;
|
|
|
|
private $singletons;
|
|
|
|
private $views;
|
|
|
|
|
|
|
|
private $loaded = false;
|
|
|
|
private $config;
|
|
|
|
|
2012-02-05 13:58:55 +01:00
|
|
|
public function __construct( $name , $description , $config , $source )
|
2012-02-05 12:56:54 +01:00
|
|
|
{
|
|
|
|
$this->name = $name;
|
2012-02-05 13:58:55 +01:00
|
|
|
$this->source = $source;
|
2012-02-05 12:56:54 +01:00
|
|
|
|
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
2012-02-05 13:58:55 +01:00
|
|
|
public function source( )
|
|
|
|
{
|
|
|
|
return $this->source;
|
|
|
|
}
|
|
|
|
|
2012-02-05 12:56:54 +01:00
|
|
|
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;
|
|
|
|
|
2012-02-05 13:58:55 +01:00
|
|
|
private static $paths = array();
|
2012-02-05 12:56:54 +01:00
|
|
|
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( )
|
|
|
|
{
|
2012-02-05 13:58:55 +01:00
|
|
|
if ( empty( Loader::$paths ) ) {
|
|
|
|
array_push( Loader::$paths , dirname( __FILE__ ) );
|
|
|
|
}
|
2012-02-05 12:56:54 +01:00
|
|
|
$this->loadConfig( );
|
|
|
|
$this->loadPackageDescriptions( );
|
|
|
|
}
|
|
|
|
|
|
|
|
private function loadConfig( )
|
|
|
|
{
|
2012-02-05 13:58:55 +01:00
|
|
|
$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;
|
2012-02-05 12:56:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private function loadPackageDescriptions( )
|
|
|
|
{
|
2012-02-05 13:58:55 +01:00
|
|
|
foreach ( Loader::$paths as $source ) {
|
|
|
|
$this->loadPackageDescriptionsFrom( $source );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private function loadPackageDescriptionsFrom( $source )
|
|
|
|
{
|
|
|
|
if ( !( $dh = opendir( $source ) ) ) {
|
2012-02-05 12:56:54 +01:00
|
|
|
throw new LoaderException( "unable to access directory" );
|
|
|
|
}
|
|
|
|
|
|
|
|
while ( ( $entry = readdir( $dh ) ) !== false ) {
|
|
|
|
if ( $entry === '.' || $entry === '..' ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2012-02-05 13:58:55 +01:00
|
|
|
$path = "$source/$entry";
|
2012-02-05 12:56:54 +01:00
|
|
|
if ( is_dir( $path ) && is_file( "$path/package.inc.php" ) ) {
|
2012-02-05 13:58:55 +01:00
|
|
|
$this->loadDescription( $entry , $source );
|
2012-02-05 12:56:54 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
closedir( $dh );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-05 13:58:55 +01:00
|
|
|
private function loadDescription( $name , $source )
|
2012-02-05 12:56:54 +01:00
|
|
|
{
|
|
|
|
$package = array( );
|
2012-02-05 13:58:55 +01:00
|
|
|
require( $source . '/' . $name . '/package.inc.php' );
|
2012-02-05 12:56:54 +01:00
|
|
|
if ( empty( $package ) ) {
|
|
|
|
throw new LoaderException( "package '$name': no information" );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( ! array_key_exists( $name , $this->config ) ) {
|
|
|
|
$this->config[ $name ] = array( );
|
|
|
|
}
|
|
|
|
|
2012-02-05 13:58:55 +01:00
|
|
|
$package = new Package( $name , $package , $this->config[ $name ] , $source );
|
2012-02-05 12:56:54 +01:00
|
|
|
$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 );
|
|
|
|
}
|
|
|
|
|
2012-02-05 13:58:55 +01:00
|
|
|
$dir = $package->source() . '/' . $name;
|
2012-02-05 12:56:54 +01:00
|
|
|
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 {
|
2012-02-07 09:22:25 +01:00
|
|
|
try {
|
|
|
|
$reflection = new ReflectionClass( $cName );
|
|
|
|
} catch ( ReflectionException $e ) {
|
|
|
|
throw new LoaderException( "Class $cName from package $pName not found" );
|
|
|
|
}
|
2012-02-05 12:56:54 +01:00
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
2012-02-07 09:22:25 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-02-05 12:56:54 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2012-02-07 09:22:25 +01:00
|
|
|
|
2012-02-05 17:49:54 +01:00
|
|
|
public static function DirectCreate( $type , $convert , $args )
|
2012-02-05 12:56:54 +01:00
|
|
|
{
|
|
|
|
$name = array_shift( $args );
|
|
|
|
$cName = $convert ? Loader::convertName( $type , $name ) : $name;
|
|
|
|
return Loader::get( )->loadAndCreate( $type , $name , $cName , $args );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-05 13:58:55 +01:00
|
|
|
public static function AddPath( $path )
|
|
|
|
{
|
|
|
|
if ( empty( Loader::$paths ) ) {
|
|
|
|
array_push( Loader::$paths , dirname( __FILE__ ) );
|
|
|
|
}
|
|
|
|
if ( is_dir( $path ) ) {
|
|
|
|
array_push( Loader::$paths , $path );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-02-05 12:56:54 +01:00
|
|
|
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( )
|
|
|
|
{
|
2012-02-05 17:49:54 +01:00
|
|
|
return Loader::DirectCreate( 'extra' , false , func_get_args( ) );
|
2012-02-05 12:56:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function View( )
|
|
|
|
{
|
2012-02-05 17:49:54 +01:00
|
|
|
return Loader::DirectCreate( 'view' , true , func_get_args( ) );
|
2012-02-05 12:56:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function Ctrl( )
|
|
|
|
{
|
2012-02-05 17:49:54 +01:00
|
|
|
return Loader::DirectCreate( 'ctrl' , true , func_get_args( ) );
|
2012-02-05 12:56:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function Page( )
|
|
|
|
{
|
2012-02-05 17:49:54 +01:00
|
|
|
return Loader::DirectCreate( 'page' , true , func_get_args( ) );
|
2012-02-05 12:56:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public static function DAO( $name )
|
|
|
|
{
|
|
|
|
return Loader::get( )->getDao( $name );
|
|
|
|
}
|
2012-02-07 09:22:25 +01:00
|
|
|
|
|
|
|
public static function Find( $class , $type = 'extra' , $onlyLoadedPackages = true )
|
|
|
|
{
|
|
|
|
return Loader::get( )->findClasses( $class , $type , $onlyLoadedPackages );
|
|
|
|
}
|
2012-02-05 12:56:54 +01:00
|
|
|
}
|