<?php
/**
* api: php
* id: shared
* title: Canonic Autoloader / shared.phar
* description: Map-based autoloader across .php and .phar resources.
* version: 0.3.4-31
* depends: php:phar | php (>= 5.3)
* category: cli
* license: Public Domain
* url: https://fossil.include-once.org/canonic_autoloader/
* author: mario#include-once:org
* architecture: all
* main: .phar/stub.php
* pack: stub.php=, autoload.update.php, autoload.map.php
*
*
* Self-contained autoloader which provides .php and .phar class
* loading based on a map.
*
* Usable for per-project classpath and /usr/share/php handling.
* 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 still allows
* non-namespaced code to work. 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 map:
*
* php ./shared.phar
*
* For development (non-deployment) setups, configure automatic
* updates per environment setting, e.g. via .htaccess:
*
* SetEnv AUTOLOADER_UPDATE 1
*
* The phar could also be split up into separate autoload, map
* and update includes.
*
*/
// Declared just once for relocatable instantation
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=>"const");
// Base directory for relative paths or phar:// paths in map.
public $basedir = "";
public $basefn = "";
// Try to rebuild map on missing identifiers.
public $auto_update = 0;
/**
* 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);
$this->auto_update |= !empty($_SERVER["AUTOLOADER_UPDATE"])
and is_writeable($this->basefn); // apt for the default case of using shared.phar
}
/**
* 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))
{
// Convert relative paths
if ($path[0] != "/") {
// Relative directory, if no phar: prefix
if (strncmp($path, "phar://", 7)) {
$path = "$this->basedir/$path";
}
// If non-absolute phar:// url, inject basedir
elseif ($path[7] !== "/") {
$path = "phar://$this->basedir/" . substr($path, 7);
}
}
return include_once($path);
}
// Once more with feeling
if ($this->auto_update) {
return $this->auto_update() and $this($name, $type);
}
}
/**
* 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 (isset($this["fp"]) 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(
$i = new Canonic_Autoloader( include(self::pharBase($basefn) . self::MAP_FILE), $basefn )
);
self::$instances[] = $i;
}
// keep a reference
static $instances = array();
/**
* 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 relocatability.
class_exists("Canonic_Classmap", false) 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) . "/";
}
}
/**
* Loads meta data `map` from fpm/xpm-generated .phar/composer packages.
* The map[] has the same structure as shared.phars` itself.
*
*/
public static function addPhar($phar_fn) {
// open and scan for metadata
if ($main = reset(self::$instances) and $p = new Phar($phar_fn, 0, NULL)
and $p->hasMetadata() and $meta = $p->getMetadata() and !empty($meta["map"]))
{
// add to instance classmap by prepending phar location and scheme
foreach ($main->types as $type) {
foreach (array_change_key_case($meta["map"][$type]) as $id=>$fn) {
$main[$type][$id] = "phar://$phar_fn/$fn";
}
}
}
}
}
}
// 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(); ?>