Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F102025650
PhutilSymbolLoader.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Sun, Feb 16, 08:11
Size
10 KB
Mime Type
text/x-php
Expires
Tue, Feb 18, 08:11 (2 d)
Engine
blob
Format
Raw Data
Handle
24252680
Attached To
rPHU libphutil
PhutilSymbolLoader.php
View Options
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* 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.
*/
/**
* Query and load Phutil classes, interfaces and functions. PhutilSymbolLoader
* is a query object which selects symbols which satisfy certain criteria, and
* optionally loads them. For instance, to load all classes in a module:
*
* $symbols = id(new PhutilSymbolLoader())
* ->setType('class')
* ->setLibrary('example')
* ->setModule('some/module')
* ->selectAndLoadSymbols();
*
* When you execute the loading query, it returns a dictionary of matching
* symbols:
*
* array(
* 'class$Example' => array(
* 'type' => 'class',
* 'name' => 'Example',
* 'library' => 'libexample',
* 'module' => 'examples/example',
* ),
* // ... more ...
* );
*
* The **library** and **module** keys show where the symbol is defined. The
* **type** and **name** keys identify the symbol itself.
*
* NOTE: You can not use libphutil functions in this file, it must be loaded
* before they are. This includes id() and idx()!
*
* @task config Configuring the Query
* @task load Loading Symbols
* @task internal Internals
*
* @group library
*/
final
class
PhutilSymbolLoader
{
private
$type
;
private
$library
;
private
$base
;
private
$module
;
private
$name
;
private
$concrete
;
private
$suppressLoad
;
/**
* Select the type of symbol to load, either ##class## or ##function##.
*
* @param string Type of symbol to load.
* @return this
* @task config
*/
public
function
setType
(
$type
)
{
$this
->
type
=
$type
;
return
$this
;
}
/**
* Restrict the symbol query to a specific library; only symbols from this
* library will be loaded.
*
* @param string Library name.
* @return this
* @task config
*/
public
function
setLibrary
(
$library
)
{
// Validate the library name; this throws if the library in not loaded.
$bootloader
=
PhutilBootloader
::
getInstance
();
$bootloader
->
getLibraryRoot
(
$library
);
$this
->
library
=
$library
;
return
$this
;
}
/**
* Restrict the symbol query to a single module.
*
* @param string Module name.
* @return this
* @task config
*/
public
function
setModule
(
$module
)
{
$this
->
module
=
$module
;
return
$this
;
}
/**
* Restrict the symbol query to a single symbol name, e.g. a specific class
* or function name.
*
* @param string Symbol name.
* @return this
* @task config
*/
public
function
setName
(
$name
)
{
$this
->
name
=
$name
;
return
$this
;
}
/**
* Restrict the symbol query to only descendants of some class. This will
* strictly select descendants, the base class will not be selected. This
* implies loading only classes.
*
* @param string Base class name.
* @return this
* @task config
*/
public
function
setAncestorClass
(
$base
)
{
$this
->
base
=
$base
;
return
$this
;
}
/**
* Restrict the symbol query to only concrete symbols; this will filter out
* abstract classes.
*
* NOTE: This currently causes class symbols to load, even if you run
* @{method:selectSymbolsWithoutLoading}.
*
* @param bool True if the query should load only concrete symbols.
* @return this
* @task config
*/
public
function
setConcreteOnly
(
$concrete
)
{
$this
->
concrete
=
$concrete
;
return
$this
;
}
/* -( Load )--------------------------------------------------------------- */
/**
* Execute the query and select matching symbols, then load the modules where
* they are defined so they can be used.
*
* @return dict A dictionary of matching symbols. See top-level class
* documentation for details. These symbols will be loaded
* and available.
* @task load
*/
public
function
selectAndLoadSymbols
()
{
$map
=
array
();
$bootloader
=
PhutilBootloader
::
getInstance
();
if
(
$this
->
library
)
{
$libraries
=
array
(
$this
->
library
);
}
else
{
$libraries
=
$bootloader
->
getAllLibraries
();
}
if
(
$this
->
type
)
{
$types
=
array
(
$this
->
type
);
}
else
{
$types
=
array
(
'class'
,
'function'
,
);
}
$symbols
=
array
();
foreach
(
$libraries
as
$library
)
{
$map
=
$bootloader
->
getLibraryMap
(
$library
);
foreach
(
$types
as
$type
)
{
if
(
$type
==
'interface'
)
{
$lookup_map
=
$map
[
'class'
];
}
else
{
$lookup_map
=
$map
[
$type
];
}
// As an optimization, we filter the list of candidate symbols in
// several passes, applying a name-based filter first if possible since
// 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
// worthwhile to microoptimize a bit because this code path is very hot
// and we save 5-10ms per page for a very moderate increase in
// complexity.
if
(
$this
->
name
)
{
// If we have a name filter, just pick the matching name out if it
// exists.
if
(
isset
(
$lookup_map
[
$this
->
name
]))
{
$filtered_map
=
array
(
$this
->
name
=>
$lookup_map
[
$this
->
name
],
);
}
else
{
$filtered_map
=
array
();
}
}
else
{
// Otherwise, start with everything.
$filtered_map
=
$lookup_map
;
}
if
(
$this
->
module
)
{
foreach
(
$lookup_map
as
$name
=>
$module
)
{
if
(
$module
!=
$this
->
module
)
{
unset
(
$filtered_map
[
$name
]);
}
}
}
foreach
(
$filtered_map
as
$name
=>
$module
)
{
$symbol
=
array
(
'type'
=>
$type
,
'name'
=>
$name
,
'library'
=>
$library
,
'module'
=>
$module
,
'standalone'
=>
!
empty
(
$map
[
'standalone'
]),
);
if
(!
empty
(
$map
[
'requires_class'
][
$name
]))
{
$symbol
[
'requires_class'
]
=
$map
[
'requires_class'
][
$name
];
}
if
(!
empty
(
$map
[
'requires_interface'
][
$name
]))
{
$symbol
[
'requires_interface'
]
=
$map
[
'requires_interface'
][
$name
];
}
$symbols
[
$type
.
'$'
.
$name
]
=
$symbol
;
}
}
}
if
(
$this
->
base
)
{
$tree
=
array
();
$libraries
=
$bootloader
->
getAllLibraries
();
foreach
(
$libraries
as
$library
)
{
$map
=
$bootloader
->
getLibraryMap
(
$library
);
foreach
(
$map
[
'requires_class'
]
as
$child
=>
$parent
)
{
$tree
[
$parent
][]
=
$child
;
}
foreach
(
$map
[
'requires_interface'
]
as
$child
=>
$parents
)
{
foreach
(
$parents
as
$parent
)
{
$tree
[
$parent
][]
=
$child
;
}
}
}
$names
=
$this
->
selectDescendantsOf
(
$tree
,
$this
->
base
);
foreach
(
$symbols
as
$symbol_key
=>
$symbol
)
{
$type
=
$symbol
[
'type'
];
if
(
$type
==
'class'
||
$type
==
'interface'
)
{
if
(
isset
(
$names
[
$symbol
[
'name'
]]))
{
continue
;
}
}
unset
(
$symbols
[
$symbol_key
]);
}
}
if
(!
$this
->
suppressLoad
)
{
foreach
(
$symbols
as
$symbol
)
{
$this
->
loadSymbol
(
$symbol
);
}
}
if
(
$this
->
concrete
)
{
// Remove 'abstract' classes.
foreach
(
$symbols
as
$key
=>
$symbol
)
{
if
(
$symbol
[
'type'
]
==
'class'
)
{
$reflection
=
new
ReflectionClass
(
$symbol
[
'name'
]);
if
(
$reflection
->
isAbstract
())
{
unset
(
$symbols
[
$key
]);
}
}
}
}
return
$symbols
;
}
/**
* Execute the query and select matching symbols, but do not load the modules
* where they are defined. This will perform slightly better if you are only
* interested in the existence of the symbols and don't plan to use them;
* otherwise, use ##selectAndLoadSymbols()##.
*
* @return dict A dictionary of matching symbols. See top-level class
* documentation for details.
* @task load
*/
public
function
selectSymbolsWithoutLoading
()
{
$this
->
suppressLoad
=
true
;
$result
=
$this
->
selectAndLoadSymbols
();
$this
->
suppressLoad
=
false
;
return
$result
;
}
/**
* Load one class by name from any available library. Useful for autoload,
* etc. Throws @{class:PhutilMissingSymbolException} if the class can not
* be loaded.
*
* @param string Class name to load.
* @return void
* @task load
*/
public
static
function
loadClass
(
$class_name
)
{
$loader
=
new
PhutilSymbolLoader
();
$symbols
=
$loader
->
setType
(
'class'
)
->
setName
(
$class_name
)
->
selectAndLoadSymbols
();
if
(!
$symbols
)
{
throw
new
PhutilMissingSymbolException
(
$class_name
);
}
}
/* -( Internals )---------------------------------------------------------- */
/**
* @task internal
*/
private
function
selectDescendantsOf
(
$tree
,
$root
)
{
$result
=
array
();
foreach
(
$tree
[
$root
]
as
$child
)
{
$result
[
$child
]
=
true
;
if
(!
empty
(
$tree
[
$child
]))
{
$result
+=
$this
->
selectDescendantsOf
(
$tree
,
$child
);
}
}
return
$result
;
}
/**
* @task internal
*/
private
function
loadSymbol
(
array
$symbol_spec
)
{
if
(
$symbol_spec
[
'type'
]
==
'function'
)
{
$this
->
loadFunctionSymbol
(
$symbol_spec
);
}
else
{
$this
->
loadClassOrInterfaceSymbol
(
$symbol_spec
);
}
}
/**
* @task internal
*/
private
function
loadFunctionSymbol
(
array
$symbol_spec
)
{
$name
=
$symbol_spec
[
'name'
];
if
(
function_exists
(
$name
))
{
return
;
}
$bootloader
=
PhutilBootloader
::
getInstance
();
$bootloader
->
loadModule
(
$symbol_spec
[
'library'
],
$symbol_spec
[
'module'
]);
if
(!
function_exists
(
$name
))
{
throw
new
PhutilMissingSymbolException
(
$name
);
}
}
/**
* @task internal
*/
private
function
loadClassOrInterfaceSymbol
(
array
$symbol_spec
)
{
$name
=
$symbol_spec
[
'name'
];
if
(
class_exists
(
$name
,
false
)
||
interface_exists
(
$name
,
false
))
{
return
;
}
$bootloader
=
PhutilBootloader
::
getInstance
();
if
(
empty
(
$symbol_spec
[
'standalone'
]))
{
$bootloader
->
loadModule
(
$symbol_spec
[
'library'
],
$symbol_spec
[
'module'
]);
}
else
{
$bootloader
->
loadClass
(
$name
,
$symbol_spec
[
'library'
],
$symbol_spec
[
'module'
]);
}
if
(!
class_exists
(
$name
,
false
)
&&
!
interface_exists
(
$name
,
false
))
{
throw
new
PhutilMissingSymbolException
(
$name
);
}
}
}
Event Timeline
Log In to Comment