// // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //============================================================================= /** * @copyright Francois Laupretre * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0 * @category Automap * @package Automap *///========================================================================== //============================================================================= /** * A map instance (created from an existing map file) * * When the PECL extension is not present, this class is instantiated when the * map is loaded, and it is used by the autoloader. * * When the extension is present, this class is instantiated only when explicitely * referenced and is not used by the autoloader. * * API status: Public * Included in the PHK PHP runtime: Yes * Implemented in the extension: No *///========================================================================== namespace Automap { if (!class_exists('Automap\Map',false)) { class Map { /** Runtime API version */ const VERSION='3.0.0'; /** We cannot load maps older than this version */ const MIN_MAP_VERSION='3.0.0'; /** Map files start with this string */ const MAGIC="AUTOMAP M\024\x8\6\3";// Magic value for map files (offset 0) //-------------------------- /** The absolute path of the map file */ private $path; /** @var array( => ) The symbol table (filled from slots) */ private $symbols; /** @var array( => ) The symbols not loaded in the symbol table yet */ private $slots; /** @var integer Symbol count of this map */ private $symcount; /** @var array( => ) The map options */ private $options; /** @var string The version of \Automap\Build\Creator that created the map file */ private $version; /** @var string The minimum runtime version needed to understand the map file */ private $minVersion; /** @var integer Load flags */ private $flags; /** @var string Absolute base path */ private $basePath; //----- /** * Construct a map object from an existing map file (real or virtual) * * @param string $path Path of the map file to read * @param integer $flags Combination of Automap load flags (@see Automap) * @param string Reserved for internal use (PHK). Never set this. */ public function __construct($path,$flags=0,$_bp=null) { $this->path=self::mkAbsolutePath($path); $this->flags=$flags; try { //-- Get file content if (($buf=@file_get_contents($this->path))===false) throw new \Exception('Cannot read map file'); $bufsize=strlen($buf); if ($bufsize<70) throw new \Exception("Short file (size=$bufsize)"); //-- Check magic if (substr($buf,0,14)!=self::MAGIC) throw new \Exception('Bad Magic'); //-- Check min runtime version required by map $this->minVersion=trim(substr($buf,14,12)); if (version_compare($this->minVersion,self::VERSION) > 0) throw new \Exception($this->path.': Cannot understand this map.'. ' Requires at least Automap version '.$this->minVersion); //-- Check if the map format is not too old $this->version=trim(substr($buf,26,12)); if (strlen($this->version)==0) throw new \Exception('Invalid empty map version'); if (version_compare($this->version,self::MIN_MAP_VERSION) < 0) throw new \Exception('Cannot understand this map. Format too old.'); $map_major_version=$this->version{0}; //-- Check file size if (strlen($buf)!=($sz=(int)substr($buf,38,8))) throw new \Exception('Invalid file size. '.$sz.' should be '.strlen($buf)); //-- Check CRC if (!($flags & Mgr::CRC_CHECK)) { $crc=substr($buf,46,8); $buf=substr_replace($buf,'00000000',46,8); if ($crc!==hash('adler32',$buf)) throw new \Exception('CRC error'); } //-- Symbol count $this->symcount=(int)substr($buf,54,8); //-- Read data $dsize=(int)substr($buf,62,8); if (($buf=unserialize(substr($buf,70,$dsize)))===false) throw new \Exception('Cannot unserialize data from map file'); if (!is_array($buf)) throw new \Exception('Map file should contain an array'); if (!array_key_exists('options',$buf)) throw new \Exception('No options array'); if (!is_array($this->options=$buf['options'])) throw new \Exception('Options should be an array'); if (!array_key_exists('map',$buf)) throw new \Exception('No symbol table'); if (!is_array($this->slots=$buf['map'])) throw new \Exception('Slot table should contain an array'); $this->symbols=array(); //-- Compute base path if (!is_null($_bp)) $this->basePath=$_bp; else $this->basePath=self::combinePath(dirname($this->path) ,$this->option('basePath'),true); } catch (\Exception $e) { $this->symbols=array(); // No retry later throw new \Exception($path.': Cannot load map - '.$e->getMessage()); } } //--------- // Check if a given file is a map file public function isMapFile($path) { return (substr(file_get_contents($path),0,strlen(self::MAGIC))===self::MAGIC); } //--------- /** * Combines a type and a symbol in a 'key' * * Starting with version 3.0, Automap is fully case-sensitive. This allows for * higher performance and cleaner code. * * Do not use this method (reserved for use by other Automap classes) * * @param string $type one of the 'T_' constants * @param string $name The symbol value (case sensitive) * @return string Symbol key */ public static function key($type,$name) { return $type.trim($name,'\\'); } //--------- /** * Load a slot into the symbol table * * @param string $ns Normalized namespace. Must correspond to an existing slot (no check) * @return null */ private function loadSlot($ns) { $this->symbols=array_merge($this->symbols,unserialize($this->slots[$ns])); unset($this->slots[$ns]); } //--------- /** * Extracts the namespace from a symbol name * * The returned value has no leading/trailing separator. * * Do not use: access reserved for Automap classes * * @param string $name The symbol value (case sensitive) * @return string Namespace. If no namespace, returns an empty string. */ public static function nsKey($name) { $name=trim($name,'\\'); $pos=strrpos($name,'\\'); if ($pos!==false) return substr($name,0,$pos); else return ''; } //--- // These utility functions return 'read-only' properties public function path() { return $this->path; } public function flags() { return $this->flags; } public function options() { return $this->options; } public function version() { return $this->version; } public function minVersion() { return $this->minVersion; } public function basePath() { return $this->basePath; } //--- public function option($opt) { return (isset($this->options[$opt]) ? $this->options[$opt] : null); } //--- public function symbolCount() { return $this->symcount; } //--- // The entry we are exporting must be in the symbol table (no check) // We need to use combinePath() because the registered path (rpath) can be absolute private function exportEntry($key) { $entry=$this->symbols[$key]; $a=array( 'stype' => $key{0}, 'symbol' => substr($key,1), 'ptype' => $entry{0}, 'rpath' => substr($entry,1) ); $a['path']=(($a['ptype']===Mgr::F_EXTENSION) ? $a['rpath'] : self::combinePath($this->basePath,$a['rpath'])); return $a; } //--- public function getSymbol($type,$symbol) { $key=self::key($type,$symbol); if (!($found=array_key_exists($key,$this->symbols))) { if (count($this->slots)) { $ns=self::nsKey($symbol); if (array_key_exists($ns,$this->slots)) $this->loadSlot($ns); $found=array_key_exists($key,$this->symbols); } } return ($found ? $this->exportEntry($key) : false); } //------- /** * Try to resolve a symbol using this map * * For performance reasons, we trust the map and don't check if the symbol is * defined after loading the script/extension/package. * * @param string $type One of the \Automap\Mgr::T_xxx symbol types * @param string Symbol name including namespace (no leading '\') * @param integer $id Used to return the ID of the map where the symbol was found * @return exported entry if found, false if not found */ public function resolve($type,$name,&$id) { if (($this->flags & Mgr::NO_AUTOLOAD) || (($entry=$this->getSymbol($type,$name))===false)) return false; //-- Found $path=$entry['path']; // Absolute path switch($entry['ptype']) { case Mgr::F_EXTENSION: if (!dl($path)) return false; break; case Mgr::F_SCRIPT: //echo("Loading script file : $path\n");//TRACE { require($path); } break; case Mgr::F_PACKAGE: // Remove E_NOTICE messages if the test script is a package - workaround // to PHP bug #39903 ('__COMPILER_HALT_OFFSET__ already defined') // In case of embedded packages and maps, the returned ID corresponds to // the map where the symbol was finally found. error_reporting(($errlevel=error_reporting()) & ~E_NOTICE); $mnt=require($path); error_reporting($errlevel); $pkg=\PHK\_Mgr::instance($mnt); $id=$pkg->automapID(); return Mgr::map($id)->resolve($type,$name,$id); break; default: throw new \Exception('<'.$entry['ptype'].'>: Unknown target type'); } return $entry; } //--- public function symbols() { /* First, load every remaining slot */ foreach(array_keys($this->slots) as $ns) $this->loadSlot($ns); /* Then, convert every entry to the export format */ $ret=array(); foreach(array_keys($this->symbols) as $key) $ret[]=$this->exportEntry($key); return $ret; } //--- // Proxy to \Automap\Tools\Display::show() public function show($format=null,$subfile_to_url_function=null) { return Tools\Display::show($this,$format,$subfile_to_url_function); } //--- // Proxy to \Automap\Tools\Check::check() public function check() { return Tools\Check::check($this); } //--- public function export($path=null) { if (is_null($path)) $path="php://stdout"; $fp=fopen($path,'w'); if (!$fp) throw new \Exception("$path: Cannot open for writing"); foreach($this->symbols() as $s) { fwrite($fp,$s['stype'].'|'.$s['symbol'].'|'.$s['ptype'].'|'.$s['rpath']."\n"); } fclose($fp); } //--------------------------------- /** * Transmits map elements to the PECL extension * * Reserved for internal use * * The first time a given map file is loaded, it is read by Automap\Map and * transmitted to the extension. On subsequent requests, it is retrieved from * persistent memory. This allows to code complex features in PHP and maintain * the code in a single location without impacting performance. * * @param string $version The version of data to transmit (reserved for future use) * @return array */ public function _peclGetMap($version) { $st=array(); foreach($this->symbols() as $s) { $st[]=array($s['stype'],$s['symbol'],$s['ptype'],$s['path']); } return $st; } //============ Utilities (taken from external libs) ============ // We need to duplicate these methods here because this class is included in the // PHK PHP runtime, which does not include the \Phool\xxx classes. //----- Taken from \Phool\File /** * Combines a base path with another path * * The base path can be relative or absolute. * * The 2nd path can also be relative or absolute. If absolute, it is returned * as-is. If it is a relative path, it is combined to the base path. * * Uses '/' as separator (to be compatible with stream-wrapper URIs). * * @param string $base The base path * @param string|null $path The path to combine * @param bool $separ true: add trailing sep, false: remove it * @return string The resulting path */ private static function combinePath($base,$path,$separ=false) { if (($base=='.') || ($base=='') || self::isAbsolutePath($path)) $res=$path; elseif (($path=='.') || is_null($path)) $res=$base; else //-- Relative path : combine it to base $res=rtrim($base,'/\\').'/'.$path; return self::trailingSepar($res,$separ); } /** * Adds or removes a trailing separator in a path * * @param string $path Input * @param bool $flag true: add trailing sep, false: remove it * @return bool The result path */ private static function trailingSepar($path, $separ) { $path=rtrim($path,'/\\'); if ($path=='') return '/'; if ($separ) $path=$path.'/'; return $path; } /** * Determines if a given path is absolute or relative * * @param string $path The path to check * @return bool True if the path is absolute, false if relative */ private static function isAbsolutePath($path) { return ((strpos($path,':')!==false) ||(strpos($path,'/')===0) ||(strpos($path,'\\')===0)); } /** * Build an absolute path from a given (absolute or relative) path * * If the input path is relative, it is combined with the current working * directory. * * @param string $path The path to make absolute * @param bool $separ True if the resulting path must contain a trailing separator * @return string The resulting absolute path */ private static function mkAbsolutePath($path,$separ=false) { if (!self::isAbsolutePath($path)) $path=self::combinePath(getcwd(),$path); return self::trailingSepar($path,$separ); } //--- } // End of class //=========================================================================== } // End of class_exists //=========================================================================== } // End of namespace //=========================================================================== ?>