Changeset View
Changeset View
Standalone View
Standalone View
src/symbols/PhutilSymbolLoader.php
<?php | <?php | ||||
/** | /** | ||||
* Query and load Phutil classes, interfaces and functions. PhutilSymbolLoader | * Query and load Phutil classes, interfaces and functions. | ||||
* is a query object which selects symbols which satisfy certain criteria, and | |||||
* optionally loads them. For instance, to load all classes in a library: | |||||
* | * | ||||
* `PhutilSymbolLoader` is a query object which selects symbols which satisfy | |||||
* certain criteria, and optionally loads them. For instance, to load all | |||||
* classes in a library: | |||||
* | |||||
* ```lang=php | |||||
* $symbols = id(new PhutilSymbolLoader()) | * $symbols = id(new PhutilSymbolLoader()) | ||||
* ->setType('class') | * ->setType('class') | ||||
* ->setLibrary('example') | * ->setLibrary('example') | ||||
* ->selectAndLoadSymbols(); | * ->selectAndLoadSymbols(); | ||||
* ``` | |||||
* | * | ||||
* When you execute the loading query, it returns a dictionary of matching | * When you execute the loading query, it returns a dictionary of matching | ||||
* symbols: | * symbols: | ||||
* | * | ||||
* ```lang=php | |||||
* array( | * array( | ||||
* 'class$Example' => array( | * 'class$Example' => array( | ||||
* 'type' => 'class', | * 'type' => 'class', | ||||
* 'name' => 'Example', | * 'name' => 'Example', | ||||
* 'library' => 'libexample', | * 'library' => 'libexample', | ||||
* 'where' => 'examples/example.php', | * 'where' => 'examples/example.php', | ||||
* ), | * ), | ||||
* // ... more ... | * // ... more ... | ||||
* ); | * ); | ||||
* ``` | |||||
* | * | ||||
* The **library** and **where** keys show where the symbol is defined. The | * The **library** and **where** keys show where the symbol is defined. The | ||||
* **type** and **name** keys identify the symbol itself. | * **type** and **name** keys identify the symbol itself. | ||||
* | * | ||||
* NOTE: This class must not use libphutil functions, including id() and idx(). | * NOTE: This class must not use libphutil functions, including @{function:id} | ||||
* and @{function:idx}. | |||||
* | * | ||||
joshuaspence: As a side note, we could potentially formalize this with an `@no-phutil` annotation. | |||||
* @task config Configuring the Query | * @task config Configuring the Query | ||||
* @task load Loading Symbols | * @task load Loading Symbols | ||||
* @task internal Internals | * @task internal Internals | ||||
*/ | */ | ||||
final class PhutilSymbolLoader { | final class PhutilSymbolLoader { | ||||
private $type; | private $type; | ||||
private $library; | private $library; | ||||
private $base; | private $base; | ||||
private $name; | private $name; | ||||
private $concrete; | private $concrete; | ||||
private $pathPrefix; | private $pathPrefix; | ||||
private $suppressLoad; | private $suppressLoad; | ||||
/** | /** | ||||
* Select the type of symbol to load, either ##class## or ##function##. | * Select the type of symbol to load, either `class`, `function` or | ||||
* `interface`. | |||||
* | * | ||||
* @param string Type of symbol to load. | * @param string Type of symbol to load. | ||||
* @return this | * @return this | ||||
* | |||||
* @task config | * @task config | ||||
*/ | */ | ||||
public function setType($type) { | public function setType($type) { | ||||
$this->type = $type; | $this->type = $type; | ||||
return $this; | return $this; | ||||
} | } | ||||
/** | /** | ||||
* Restrict the symbol query to a specific library; only symbols from this | * Restrict the symbol query to a specific library; only symbols from this | ||||
* library will be loaded. | * library will be loaded. | ||||
* | * | ||||
* @param string Library name. | * @param string Library name. | ||||
* @return this | * @return this | ||||
* | |||||
* @task config | * @task config | ||||
*/ | */ | ||||
public function setLibrary($library) { | public function setLibrary($library) { | ||||
// Validate the library name; this throws if the library in not loaded. | // Validate the library name; this throws if the library in not loaded. | ||||
$bootloader = PhutilBootloader::getInstance(); | $bootloader = PhutilBootloader::getInstance(); | ||||
$bootloader->getLibraryRoot($library); | $bootloader->getLibraryRoot($library); | ||||
$this->library = $library; | $this->library = $library; | ||||
return $this; | return $this; | ||||
} | } | ||||
/** | /** | ||||
* Restrict the symbol query to a specific path prefix; only symbols defined | * Restrict the symbol query to a specific path prefix; only symbols defined | ||||
* in files below that path will be selected. | * in files below that path will be selected. | ||||
* | * | ||||
* @param string Path relative to library root, like "apps/cheese/". | * @param string Path relative to library root, like "apps/cheese/". | ||||
* @return this | * @return this | ||||
* | |||||
* @task config | * @task config | ||||
*/ | */ | ||||
public function setPathPrefix($path) { | public function setPathPrefix($path) { | ||||
$this->pathPrefix = str_replace(DIRECTORY_SEPARATOR, '/', $path); | $this->pathPrefix = str_replace(DIRECTORY_SEPARATOR, '/', $path); | ||||
return $this; | return $this; | ||||
} | } | ||||
/** | /** | ||||
* Restrict the symbol query to a single symbol name, e.g. a specific class | * Restrict the symbol query to a single symbol name, e.g. a specific class | ||||
* or function name. | * or function name. | ||||
* | * | ||||
* @param string Symbol name. | * @param string Symbol name. | ||||
* @return this | * @return this | ||||
* | |||||
* @task config | * @task config | ||||
*/ | */ | ||||
public function setName($name) { | public function setName($name) { | ||||
$this->name = $name; | $this->name = $name; | ||||
return $this; | return $this; | ||||
} | } | ||||
/** | /** | ||||
* Restrict the symbol query to only descendants of some class. This will | * Restrict the symbol query to only descendants of some class. This will | ||||
* strictly select descendants, the base class will not be selected. This | * strictly select descendants, the base class will not be selected. This | ||||
* implies loading only classes. | * implies loading only classes. | ||||
* | * | ||||
* @param string Base class name. | * @param string Base class name. | ||||
* @return this | * @return this | ||||
* | |||||
* @task config | * @task config | ||||
*/ | */ | ||||
public function setAncestorClass($base) { | public function setAncestorClass($base) { | ||||
$this->base = $base; | $this->base = $base; | ||||
return $this; | return $this; | ||||
} | } | ||||
/** | /** | ||||
* Restrict the symbol query to only concrete symbols; this will filter out | * Restrict the symbol query to only concrete symbols; this will filter out | ||||
* abstract classes. | * abstract classes. | ||||
* | * | ||||
* NOTE: This currently causes class symbols to load, even if you run | * NOTE: This currently causes class symbols to load, even if you run | ||||
* @{method:selectSymbolsWithoutLoading}. | * @{method:selectSymbolsWithoutLoading}. | ||||
* | * | ||||
* @param bool True if the query should load only concrete symbols. | * @param bool True if the query should load only concrete symbols. | ||||
* @return this | * @return this | ||||
* | |||||
* @task config | * @task config | ||||
*/ | */ | ||||
public function setConcreteOnly($concrete) { | public function setConcreteOnly($concrete) { | ||||
$this->concrete = $concrete; | $this->concrete = $concrete; | ||||
return $this; | return $this; | ||||
} | } | ||||
/* -( Load )--------------------------------------------------------------- */ | /* -( Load )--------------------------------------------------------------- */ | ||||
/** | /** | ||||
* Execute the query and select matching symbols, then load them so they can | * Execute the query and select matching symbols, then load them so they can | ||||
* be used. | * be used. | ||||
* | * | ||||
* @return dict A dictionary of matching symbols. See top-level class | * @return dict A dictionary of matching symbols. See top-level class | ||||
* documentation for details. These symbols will be loaded | * documentation for details. These symbols will be loaded | ||||
* and available. | * and available. | ||||
* | |||||
* @task load | * @task load | ||||
*/ | */ | ||||
public function selectAndLoadSymbols() { | public function selectAndLoadSymbols() { | ||||
$map = array(); | $map = array(); | ||||
$bootloader = PhutilBootloader::getInstance(); | $bootloader = PhutilBootloader::getInstance(); | ||||
if ($this->library) { | if ($this->library) { | ||||
Show All 26 Lines | foreach ($libraries as $library) { | ||||
$lookup_map = $map['class']; | $lookup_map = $map['class']; | ||||
} else { | } else { | ||||
$lookup_map = $map[$type]; | $lookup_map = $map[$type]; | ||||
} | } | ||||
// As an optimization, we filter the list of candidate symbols in | // As an optimization, we filter the list of candidate symbols in | ||||
// several passes, applying a name-based filter first if possible since | // several passes, applying a name-based filter first if possible since | ||||
// it is highly selective and guaranteed to match at most one symbol. | // it is highly selective and guaranteed to match at most one symbol. | ||||
// This is the common case and we land here through __autoload() so it's | // This is the common case and we land here through `__autoload()` so | ||||
// worthwhile to microoptimize a bit because this code path is very hot | // it's worthwhile to micro-optimize a bit because this code path is | ||||
// and we save 5-10ms per page for a very moderate increase in | // very hot and we save 5-10ms per page for a very moderate increase in | ||||
// complexity. | // complexity. | ||||
if ($this->name) { | if ($this->name) { | ||||
// If we have a name filter, just pick the matching name out if it | // If we have a name filter, just pick the matching name out if it | ||||
// exists. | // exists. | ||||
if (isset($lookup_map[$this->name])) { | if (isset($lookup_map[$this->name])) { | ||||
$filtered_map = array( | $filtered_map = array( | ||||
$this->name => $lookup_map[$this->name], | $this->name => $lookup_map[$this->name], | ||||
); | ); | ||||
} else { | } else { | ||||
▲ Show 20 Lines • Show All 68 Lines • ▼ Show 20 Lines | /* -( Load )--------------------------------------------------------------- */ | ||||
/** | /** | ||||
* Execute the query and select matching symbols, but do not load them. This | * Execute the query and select matching symbols, but do not load them. This | ||||
* will perform slightly better if you are only interested in the existence | * will perform slightly better if you are only interested in the existence | ||||
* of the symbols and don't plan to use them; otherwise, use | * of the symbols and don't plan to use them; otherwise, use | ||||
* @{method:selectAndLoadSymbols}. | * @{method:selectAndLoadSymbols}. | ||||
* | * | ||||
* @return dict A dictionary of matching symbols. See top-level class | * @return dict A dictionary of matching symbols. See top-level class | ||||
* documentation for details. | * documentation for details. | ||||
* | |||||
* @task load | * @task load | ||||
*/ | */ | ||||
public function selectSymbolsWithoutLoading() { | public function selectSymbolsWithoutLoading() { | ||||
$this->suppressLoad = true; | $this->suppressLoad = true; | ||||
$result = $this->selectAndLoadSymbols(); | $result = $this->selectAndLoadSymbols(); | ||||
$this->suppressLoad = false; | $this->suppressLoad = false; | ||||
return $result; | return $result; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 112 Lines • Show Last 20 Lines |
As a side note, we could potentially formalize this with an @no-phutil annotation.