<Website>

Automap

PHK Home

File: /src/Automap/Map.php

Size:13772
Storage flags:

<?php
//=============================================================================
//
// Copyright Francois Laupretre <automap@tekwire.net>
//
//   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 <automap@tekwire.net>
* @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(<key> => <target>)    The symbol table (filled from slots) */

private $symbols;

/** @var array(<ns> => <slot data>)    The symbols not loaded in the symbol table yet */

private $slots;

/** @var integer Symbol count of this map */

private $symcount;

/** @var array(<name> => <value>)    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
//===========================================================================
?>

For more information about the PHK package format: http://phk.tekwire.net