<?php
/**
* api: php
* title: Canonic Autoloader / shared.phar
* description: Map-based autoloader across .php and .phar resources.
* version: 0.3.1
* depends: php:phar, php >= 5.3
* category: cli
* license: Public Domain
*
*
* Self-contained autoloader which provides .php and .phar class
* loading based on a map. Loader and map are also contained in a
* phar.
*
* It's meant for /usr/share/php handling, but can also be utilized
* for per-project autoloading. It's indifferent to PSR-0/PSR-4 or
* custom directory schemes. Because of the filename map, identifier
* names are handled in compliance to PHP semantics (unlike PSR-x);
* and non-namespaced code works as well. Additionally it prepares
* future rfc:function_autoloading support.
*
* Using the autoloader is as simple as:
*
* include_once("shared.phar");
*
* Invoke it from the commandline to update the internal classmap:
*
* php ./shared.phar
*
* Its phar can also be split up into separate autoload, map, update
* include files.
*
*/
//
if (!class_exists("Canonic_Autoloader")) {
class Canonic_Autoloader extends ArrayObject {
// Support for rfc:function_autoloading identifier typing.
public $types = array(1=>"class", 2=>"function", 4=>"constant");
// Base directory for relative paths or phar:// paths in map.
public $basedir = "";
public $basefn = "";
// Try to rebuild map on missing identifiers.
public $auto_update = 1;
/**
* Initialize map.
*
* @param array $map Contains a type->identifier->filename map.
* @param string $basefn Location of stub (shared.phar), which classpath basedir (usually __DIR__) is derived from.
*
*/
public function __construct($map, $basefn) {
$this->basedir = dirname($basefn);
$this->basefn = $basefn;
parent::__construct($map);
}
/**
* Look to-be loaded classname/function up in idmap,
* and load include script.
*
* @param string $name Identifier to autoload.
* @param int $type Wether it's a class, function, const identifier.
* @return void
*
*/
public function __invoke($name, $type="class") {
// Normalize identifier lookup
$name = strtolower($name);
if (is_int($type)) {
$type = $this->types[$type];
}
// Check for declaration
if (isset($this[$type][$name]))
{
$path = $this[$type][$name];
}
// And load according include script
if (isset($path))
{
return include_once($this->absolute_path($path, $this->basedir));
}
// Once more with feeling
if ($this->auto_update) {
$this->auto_update() and $this($name);
}
}
/**
* Turn relative paths and phar:// locations into absolute.
*
* @param string $path Dirname/Phar path.
* @param int $base Base directory to prepend on relative paths.
* @return string
*
*/
public function absolute_path($path, $base) {
// it was absolute already
if ($path[0] == "/") {
return $path;
}
// phar://
elseif (!strncmp($path, "phar://", 7)) {
// is relative
if ($path[7] != "/") {
$path = "phar://$base/" . substr($path, 7);
}
}
// relative dirname
else {
$path = "$base/$path";
}
}
/**
* Automatically update classmap ($this) at runtime, for development setups.
*
* @return boolean
*
*/
function auto_update() {
$this->auto_update = 0;
// Only succeeds if current fingerprint doesn't match
if (is_writeable($this->basefn) and $map = self::update($this->basefn, $this["fp"])) {
parent::__construct($map);
return TRUE;
}
}
/**
* Unpackaged .phar handling.
*
* The purpose of the following static methods is to allow having separate
* `autoload.php` and `autoload.map.php` and `autoload.update.php` scripts
* reside in the same directory, instead of in the shared.phar/
*
* pharBase() does decide on current __FILE__ location if run within .phar
* context, or as standalone include. The basedir and mapfile location are
* setup accordingly.
*
*/
// File locations, either phar: entries or in ./ local dir.
// Consider these fixated. In case of a global and local shared.phar, only either declaration of Canonic_Classmap can define them.
const MAP_FILE = "autoload.map.php";
const UPDATE_LIB = "autoload.update.php";
/**
* Create instance and register it.
*
*/
public static function hookup($basefn) {
spl_autoload_register(
new Canonic_Autoloader( include(self::pharBase($basefn) . self::MAP_FILE), $basefn )
);
}
/**
* Recreate classmap within the phar / Or as separate autoload.map.php file.
*
*/
public static function update($basefn, $fp=NULL) {
// Load utils manually. Now that's too unfortunate, but sensible for clean .phar distribution and some resiliency.
class_exists("Canonic_Classmap") or
include_once(self::pharBase($basefn) . self::UPDATE_LIB);
// Update from subdirectories.
return Canonic_Classmap::update( self::pharBase($basefn) . self::MAP_FILE , array(dirname($basefn)), $fp );
}
/**
* Base directory or phar:// root for instantiation __FILE__ (stub.php)
*
*/
public static function pharBase($basefn) {
// depending on if __FILE__ was a .phar
if (substr($basefn, -5) == ".phar") {
return "phar://$basefn/";
}
// use its surrounding directory if plain .php file
else {
return dirname($basefn) . "/";
}
}
}
}
// Register loader with phar-contained indentifier map.
Canonic_Autoloader::hookup(__FILE__);
// If autoloader.phar is invoked directly from php-cli, trigger update mode.
if (empty($_SERVER["GATEWAY_INTERFACE"]) and isset($_SERVER["argv"][0]) and (basename($_SERVER["argv"][0]) == basename(__FILE__))) {
Canonic_Autoloader::update(__FILE__);
}
// Phar stub end
__HALT_COMPILER(); ?>