[ Index ]

PHP Cross Reference of Automap

title

Body

[close]

/Automap/ -> Map.php (source)

   1  <?php
   2  //=============================================================================
   3  //
   4  // Copyright Francois Laupretre <automap@tekwire.net>
   5  //
   6  //   Licensed under the Apache License, Version 2.0 (the "License");
   7  //   you may not use this file except in compliance with the License.
   8  //   You may obtain a copy of the License at
   9  //
  10  //       http://www.apache.org/licenses/LICENSE-2.0
  11  //
  12  //   Unless required by applicable law or agreed to in writing, software
  13  //   distributed under the License is distributed on an "AS IS" BASIS,
  14  //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15  //   See the License for the specific language governing permissions and
  16  //   limitations under the License.
  17  //
  18  //=============================================================================
  19  /**
  20  * @copyright Francois Laupretre <automap@tekwire.net>
  21  * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, V 2.0
  22  * @category Automap
  23  * @package Automap
  24  *///==========================================================================
  25  
  26  //=============================================================================
  27  /**
  28  * A map instance (created from an existing map file)
  29  *
  30  * When the PECL extension is not present, this class is instantiated when the
  31  * map is loaded, and it is used by the autoloader.
  32  *
  33  * When the extension is present, this class is instantiated only when explicitely
  34  * referenced and is not used by the autoloader.
  35  *
  36  * API status: Public
  37  * Included in the PHK PHP runtime: Yes
  38  * Implemented in the extension: No
  39  *///==========================================================================
  40  
  41  namespace Automap {
  42  
  43  if (!class_exists('Automap\Map',false)) 
  44  {
  45  class Map
  46  {
  47  /** Runtime API version */
  48  
  49  const VERSION='3.0.0';
  50  
  51  /** We cannot load maps older than this version */
  52   
  53  const MIN_MAP_VERSION='3.0.0';
  54  
  55  /** Map files start with this string */
  56  
  57  const MAGIC="AUTOMAP  M\024\x8\6\3";// Magic value for map files (offset 0)
  58  
  59  //--------------------------
  60  /** The absolute path of the map file */
  61  
  62  private $path;            
  63  
  64  /** @var array(<key> => <target>)    The symbol table (filled from slots) */
  65  
  66  private $symbols;
  67  
  68  /** @var array(<ns> => <slot data>)    The symbols not loaded in the symbol table yet */
  69  
  70  private $slots;
  71  
  72  /** @var integer Symbol count of this map */
  73  
  74  private $symcount;
  75  
  76  /** @var array(<name> => <value>)    The map options */
  77  
  78  private $options;
  79  
  80  /** @var string The version of \Automap\Build\Creator that created the map file */
  81  
  82  private $version;
  83  
  84  /** @var string The minimum runtime version needed to understand the map file */
  85  
  86  private $minVersion;
  87  
  88  /** @var integer Load flags */
  89  
  90  private $flags;
  91  
  92  /** @var string Absolute base path */
  93  
  94  private $basePath;
  95  
  96  //-----
  97  /**
  98  * Construct a map object from an existing map file (real or virtual)
  99  *
 100  * @param string $path Path of the map file to read
 101  * @param integer $flags Combination of Automap load flags (@see Automap)
 102  * @param string Reserved for internal use (PHK). Never set this.
 103  */
 104  
 105  public function __construct($path,$flags=0,$_bp=null)
 106  {
 107  $this->path=self::mkAbsolutePath($path);
 108  $this->flags=$flags;
 109  
 110  try
 111  {
 112  //-- Get file content
 113  
 114  if (($buf=@file_get_contents($this->path))===false)
 115      throw new \Exception('Cannot read map file');
 116  $bufsize=strlen($buf);
 117  if ($bufsize<70) throw new \Exception("Short file (size=$bufsize)");
 118  
 119  //-- Check magic
 120  
 121  if (substr($buf,0,14)!=self::MAGIC) throw new \Exception('Bad Magic');
 122  
 123  //-- Check min runtime version required by map
 124  
 125  $this->minVersion=trim(substr($buf,14,12));    
 126  if (version_compare($this->minVersion,self::VERSION) > 0)
 127      throw new \Exception($this->path.': Cannot understand this map.'.
 128          ' Requires at least Automap version '.$this->minVersion);
 129  
 130  //-- Check if the map format is not too old
 131  
 132  $this->version=trim(substr($buf,26,12));
 133  if (strlen($this->version)==0)
 134      throw new \Exception('Invalid empty map version');
 135  if (version_compare($this->version,self::MIN_MAP_VERSION) < 0)
 136      throw new \Exception('Cannot understand this map. Format too old.');
 137  $map_major_version=$this->version{0};
 138  
 139  //-- Check file size
 140  
 141  if (strlen($buf)!=($sz=(int)substr($buf,38,8)))
 142      throw new \Exception('Invalid file size. '.$sz.' should be '.strlen($buf));
 143  
 144  //-- Check CRC
 145  
 146  if (!($flags & Mgr::CRC_CHECK))
 147      {
 148      $crc=substr($buf,46,8);
 149      $buf=substr_replace($buf,'00000000',46,8);
 150      if ($crc!==hash('adler32',$buf)) throw new \Exception('CRC error');
 151      }
 152  
 153  //-- Symbol count
 154  
 155  $this->symcount=(int)substr($buf,54,8);
 156  
 157  //-- Read data
 158  
 159  $dsize=(int)substr($buf,62,8);
 160  if (($buf=unserialize(substr($buf,70,$dsize)))===false)
 161      throw new \Exception('Cannot unserialize data from map file');
 162  if (!is_array($buf))
 163      throw new \Exception('Map file should contain an array');
 164  if (!array_key_exists('options',$buf)) throw new \Exception('No options array');
 165  if (!is_array($this->options=$buf['options']))
 166      throw new \Exception('Options should be an array');
 167  if (!array_key_exists('map',$buf)) throw new \Exception('No symbol table');
 168  if (!is_array($this->slots=$buf['map']))
 169      throw new \Exception('Slot table should contain an array');
 170  $this->symbols=array();
 171  
 172  //-- Compute base path
 173  
 174  if (!is_null($_bp)) $this->basePath=$_bp;
 175  else $this->basePath=self::combinePath(dirname($this->path)
 176      ,$this->option('basePath'),true);
 177  
 178  }
 179  catch (\Exception $e)
 180      {
 181      $this->symbols=array(); // No retry later
 182      throw new \Exception($path.': Cannot load map - '.$e->getMessage());
 183      }
 184  }
 185  
 186  //---------
 187  // Check if a given file is a map file
 188  
 189  public function isMapFile($path)
 190  {
 191  return (substr(file_get_contents($path),0,strlen(self::MAGIC))===self::MAGIC);
 192  }
 193  
 194  //---------
 195  /**
 196  * Combines a type and a symbol in a 'key'
 197  *
 198  * Starting with version 3.0, Automap is fully case-sensitive. This allows for
 199  * higher performance and cleaner code.
 200  *
 201  * Do not use this method (reserved for use by other Automap classes)
 202  *
 203  * @param string $type one of the 'T_' constants
 204  * @param string $name The symbol value (case sensitive)
 205  * @return string Symbol key
 206  */
 207  
 208  public static function key($type,$name)
 209  {
 210  return $type.trim($name,'\\');
 211  }
 212  
 213  //---------
 214  /**
 215  * Load a slot into the symbol table
 216  *
 217  * @param string $ns Normalized namespace. Must correspond to an existing slot (no check)
 218  * @return null
 219  */
 220  
 221  private function loadSlot($ns)
 222  {
 223  $this->symbols=array_merge($this->symbols,unserialize($this->slots[$ns]));
 224  unset($this->slots[$ns]);
 225  }
 226  
 227  //---------
 228  /**
 229  * Extracts the namespace from a symbol name
 230  *
 231  * The returned value has no leading/trailing separator.
 232  *
 233  * Do not use: access reserved for Automap classes
 234  *
 235  * @param string $name The symbol value (case sensitive)
 236  * @return string Namespace. If no namespace, returns an empty string.
 237  */
 238  
 239  public static function nsKey($name)
 240  {
 241  $name=trim($name,'\\');
 242  $pos=strrpos($name,'\\');
 243  if ($pos!==false) return substr($name,0,$pos);
 244  else return '';
 245  }
 246  
 247  //---
 248  // These utility functions return 'read-only' properties
 249  
 250  public function path() { return $this->path; }
 251  public function flags() { return $this->flags; }
 252  public function options() { return $this->options; }
 253  public function version() { return $this->version; }
 254  public function minVersion() { return $this->minVersion; }
 255  public function basePath() { return $this->basePath; }
 256  
 257  //---
 258  
 259  public function option($opt)
 260  {
 261  return (isset($this->options[$opt]) ? $this->options[$opt] : null);
 262  }
 263  
 264  //---
 265  
 266  public function symbolCount()
 267  {
 268  return $this->symcount;
 269  }
 270  
 271  //---
 272  // The entry we are exporting must be in the symbol table (no check)
 273  // We need to use combinePath() because the registered path (rpath) can be absolute
 274  
 275  private function exportEntry($key)
 276  {
 277  $entry=$this->symbols[$key];
 278  
 279  $a=array(
 280      'stype'        => $key{0},
 281      'symbol'     => substr($key,1),
 282      'ptype'        => $entry{0},
 283      'rpath'        => substr($entry,1)
 284      );
 285  
 286  $a['path']=(($a['ptype']===Mgr::F_EXTENSION) ? $a['rpath']
 287      : self::combinePath($this->basePath,$a['rpath']));
 288  
 289  return $a;
 290  }
 291  
 292  //---
 293  
 294  public function getSymbol($type,$symbol)
 295  {
 296  $key=self::key($type,$symbol);
 297  if (!($found=array_key_exists($key,$this->symbols)))
 298      {
 299      if (count($this->slots))
 300          {
 301          $ns=self::nsKey($symbol);
 302          if (array_key_exists($ns,$this->slots)) $this->loadSlot($ns);
 303          $found=array_key_exists($key,$this->symbols);
 304          }
 305      }
 306  return ($found ? $this->exportEntry($key) : false);
 307  }
 308  
 309  //-------
 310  /**
 311  * Try to resolve a symbol using this map
 312  *
 313  * For performance reasons, we trust the map and don't check if the symbol is
 314  * defined after loading the script/extension/package.
 315  *
 316  * @param string $type One of the \Automap\Mgr::T_xxx symbol types
 317  * @param string Symbol name including namespace (no leading '\')
 318  * @param integer $id Used to return the ID of the map where the symbol was found
 319  * @return exported entry if found, false if not found
 320  */
 321  
 322  public function resolve($type,$name,&$id)
 323  {
 324  if (($this->flags & Mgr::NO_AUTOLOAD)
 325          || (($entry=$this->getSymbol($type,$name))===false)) return false;
 326  
 327  //-- Found
 328  
 329  $path=$entry['path']; // Absolute path
 330  switch($entry['ptype'])
 331      {
 332      case Mgr::F_EXTENSION:
 333          if (!dl($path)) return false;
 334          break;
 335  
 336      case Mgr::F_SCRIPT:
 337          //echo("Loading script file : $path\n");//TRACE
 338          { require($path); }
 339          break;
 340  
 341      case Mgr::F_PACKAGE:
 342          // Remove E_NOTICE messages if the test script is a package - workaround
 343          // to PHP bug #39903 ('__COMPILER_HALT_OFFSET__ already defined')
 344          // In case of embedded packages and maps, the returned ID corresponds to
 345          // the map where the symbol was finally found.
 346      
 347          error_reporting(($errlevel=error_reporting()) & ~E_NOTICE);
 348          $mnt=require($path);
 349          error_reporting($errlevel);
 350          $pkg=\PHK\_Mgr::instance($mnt);
 351          $id=$pkg->automapID();
 352          return Mgr::map($id)->resolve($type,$name,$id);
 353          break;
 354  
 355      default:
 356          throw new \Exception('<'.$entry['ptype'].'>: Unknown target type');
 357      }
 358  return $entry;
 359  }
 360  
 361  //---
 362  
 363  public function symbols()
 364  {
 365  /* First, load every remaining slot */
 366  
 367  foreach(array_keys($this->slots) as $ns) $this->loadSlot($ns);
 368  
 369  /* Then, convert every entry to the export format */
 370  
 371  $ret=array();
 372  foreach(array_keys($this->symbols) as $key) $ret[]=$this->exportEntry($key);
 373  
 374  return $ret;
 375  }
 376  
 377  //---
 378  // Proxy to \Automap\Tools\Display::show()
 379  
 380  public function show($format=null,$subfile_to_url_function=null)
 381  {
 382  return Tools\Display::show($this,$format,$subfile_to_url_function);
 383  }
 384  
 385  //---
 386  // Proxy to \Automap\Tools\Check::check()
 387  
 388  public function check()
 389  {
 390  return Tools\Check::check($this);
 391  }
 392  
 393  //---
 394  
 395  public function export($path=null)
 396  {
 397  if (is_null($path)) $path="php://stdout";
 398  $fp=fopen($path,'w');
 399  if (!$fp) throw new \Exception("$path: Cannot open for writing");
 400  
 401  foreach($this->symbols() as $s)
 402      {
 403      fwrite($fp,$s['stype'].'|'.$s['symbol'].'|'.$s['ptype'].'|'.$s['rpath']."\n");
 404      }
 405  
 406  fclose($fp);
 407  }
 408  
 409  //---------------------------------
 410  /**
 411  * Transmits map elements to the PECL extension
 412  *
 413  * Reserved for internal use
 414  *
 415  * The first time a given map file is loaded, it is read by Automap\Map and
 416  * transmitted to the extension. On subsequent requests, it is retrieved from
 417  * persistent memory. This allows to code complex features in PHP and maintain
 418  * the code in a single location without impacting performance.
 419  *
 420  * @param string $version The version of data to transmit (reserved for future use)
 421  * @return array
 422  */
 423  
 424  public function _peclGetMap($version)
 425  {
 426  $st=array();
 427  foreach($this->symbols() as $s)
 428      {
 429      $st[]=array($s['stype'],$s['symbol'],$s['ptype'],$s['path']);
 430      }
 431  
 432  return $st;
 433  }
 434  
 435  //============ Utilities (taken from external libs) ============
 436  // We need to duplicate these methods here because this class is included in the
 437  // PHK PHP runtime, which does not include the \Phool\xxx classes.
 438  
 439  //----- Taken from \Phool\File
 440  /**
 441  * Combines a base path with another path
 442  *
 443  * The base path can be relative or absolute.
 444  *
 445  * The 2nd path can also be relative or absolute. If absolute, it is returned
 446  * as-is. If it is a relative path, it is combined to the base path.
 447  *
 448  * Uses '/' as separator (to be compatible with stream-wrapper URIs).
 449  *
 450  * @param string $base The base path
 451  * @param string|null $path The path to combine
 452  * @param bool $separ true: add trailing sep, false: remove it
 453  * @return string The resulting path
 454  */
 455  
 456  private static function combinePath($base,$path,$separ=false)
 457  {
 458  if (($base=='.') || ($base=='') || self::isAbsolutePath($path))
 459      $res=$path;
 460  elseif (($path=='.') || is_null($path))
 461      $res=$base;
 462  else    //-- Relative path : combine it to base
 463      $res=rtrim($base,'/\\').'/'.$path;
 464  
 465  return self::trailingSepar($res,$separ);
 466  }
 467  
 468  /**
 469  * Adds or removes a trailing separator in a path
 470  *
 471  * @param string $path Input
 472  * @param bool $flag true: add trailing sep, false: remove it
 473  * @return bool The result path
 474  */
 475  
 476  private static function trailingSepar($path, $separ)
 477  {
 478  $path=rtrim($path,'/\\');
 479  if ($path=='') return '/';
 480  if ($separ) $path=$path.'/';
 481  return $path;
 482  }
 483  
 484  /**
 485  * Determines if a given path is absolute or relative
 486  *
 487  * @param string $path The path to check
 488  * @return bool True if the path is absolute, false if relative
 489  */
 490  
 491  private static function isAbsolutePath($path)
 492  {
 493  return ((strpos($path,':')!==false)
 494      ||(strpos($path,'/')===0)
 495      ||(strpos($path,'\\')===0));
 496  }
 497  
 498  /**
 499  * Build an absolute path from a given (absolute or relative) path
 500  *
 501  * If the input path is relative, it is combined with the current working
 502  * directory.
 503  *
 504  * @param string $path The path to make absolute
 505  * @param bool $separ True if the resulting path must contain a trailing separator
 506  * @return string The resulting absolute path
 507  */
 508  
 509  private static function mkAbsolutePath($path,$separ=false)
 510  {
 511  if (!self::isAbsolutePath($path)) $path=self::combinePath(getcwd(),$path);
 512  return self::trailingSepar($path,$separ);
 513  }
 514  
 515  //---
 516  } // End of class
 517  //===========================================================================
 518  } // End of class_exists
 519  //===========================================================================
 520  } // End of namespace
 521  //===========================================================================
 522  ?>


Generated: Thu Jun 4 18:32:29 2015 Cross-referenced by PHPXref 0.7.1