Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F1126182
ClassMapBuilder.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Fri, Sep 5, 6:38 AM
Size
10 KB
Mime Type
text/x-php
Expires
Sun, Sep 7, 6:38 AM (31 m, 31 s)
Engine
blob
Format
Raw Data
Handle
728563
Attached To
rINP In-Portal
ClassMapBuilder.php
View Options
<?php
/**
* @version $Id$
* @package In-Portal
* @copyright Copyright (C) 1997 - 2015 Intechnic. All rights reserved.
* @license GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
namespace
Intechnic\InPortal\Core\kernel\utility\ClassDiscovery
;
use
PhpParser\Lexer
;
use
PhpParser\NodeTraverser
;
use
PhpParser\NodeVisitor\NameResolver
;
use
PhpParser\Parser
;
defined
(
'FULL_PATH'
)
or
die
(
'restricted access!'
);
class
ClassMapBuilder
{
const
CACHE_FORMAT
=
2
;
const
CACHE_FILE_STRUCTURE
=
'class_structure.php'
;
const
CACHE_FILE_HASHES
=
'file_hashes.php'
;
/**
* Path to scan.
*
* @var string
*/
protected
$scanPath
;
/**
* Path to store cache into.
*
* @var string
*/
protected
$cachePath
;
/**
* List of classes (key - class, value - file).
*
* @var array
*/
protected
$classToFileMap
=
array
();
/**
* Class information used during cache building (file > class > class_info).
*
* @var array
*/
protected
$buildingCache
=
array
();
/**
* Class information (type, extends, implements, etc.).
*
* @var array
*/
protected
$classInfo
=
array
();
/**
* Stores hash of each file on given path.
*
* @var array
*/
protected
$fileHashes
=
array
();
/**
* Parser.
*
* @var Parser
*/
protected
$parser
;
/**
* Node traverser.
*
* @var NodeTraverser
*/
protected
$traverser
;
/**
* Name of file, that is currently processed.
*
* @var string
*/
protected
$currentFile
;
/**
* Returns builder array for all eligible folders.
*
* @param array $module_info Module info.
*
* @return static[]
*/
public
static
function
createBuilders
(
array
$module_info
=
null
)
{
$ret
=
array
();
if
(
!
isset
(
$module_info
)
)
{
// No module information given > scan everything.
$ret
[]
=
new
static
(
FULL_PATH
.
DIRECTORY_SEPARATOR
.
'core'
);
foreach
(
glob
(
MODULES_PATH
.
'/*'
,
GLOB_ONLYDIR
)
as
$module_folder
)
{
if
(
\kModulesHelper
::
isInPortalModule
(
$module_folder
)
)
{
$ret
[]
=
new
static
(
$module_folder
);
}
}
}
else
{
// Module information given > scan only these modules.
foreach
(
$module_info
as
$module_name
=>
$module_data
)
{
if
(
$module_name
==
'In-Portal'
)
{
continue
;
}
$ret
[]
=
new
static
(
FULL_PATH
.
DIRECTORY_SEPARATOR
.
rtrim
(
$module_data
[
'Path'
],
'/'
));
}
}
return
$ret
;
}
/**
* Creates ClassMapBuilder instance.
*
* @param string $scan_path Path to scan.
*/
public
function
__construct
(
$scan_path
)
{
$this
->
scanPath
=
$scan_path
;
$this
->
assertPath
(
$this
->
scanPath
);
$this
->
cachePath
=
$this
->
scanPath
.
'/install/cache'
;
$this
->
assertPath
(
$this
->
cachePath
);
}
/**
* Validates that path exists and is directory.
*
* @param string $path Path.
*
* @return void
* @throws \InvalidArgumentException When invalid path is given.
*/
protected
function
assertPath
(
$path
)
{
if
(
!
file_exists
(
$path
)
||
!
is_dir
(
$path
)
)
{
throw
new
\InvalidArgumentException
(
'Path "'
.
$path
.
'" is not a folder or doesn
\'
t exist'
);
}
}
/**
* Returns class map and class information, that was built previously.
*
* @return array
*/
public
function
get
()
{
$this
->
load
(
self
::
CACHE_FILE_STRUCTURE
,
false
);
return
array
(
$this
->
classToFileMap
,
$this
->
classInfo
);
}
/**
* Builds class map.
*
* @return void
* @throws \RuntimeException When PHP parser not found.
*/
public
function
build
()
{
if
(
!
class_exists
(
'PhpParser
\P
arser'
)
)
{
$error_msg
=
'PHP Parser not found. Make sure, that Composer dependencies were '
;
$error_msg
.=
'installed using "php composer.phar install --dev" command.'
;
throw
new
\RuntimeException
(
$error_msg
);
}
$scan_path
=
preg_replace
(
'/^'
.
preg_quote
(
FULL_PATH
,
'/'
)
.
'/'
,
'...'
,
$this
->
scanPath
,
1
);
echo
$this
->
strPad
(
'path "'
.
$scan_path
.
'"'
,
40
);
$this
->
load
(
self
::
CACHE_FILE_STRUCTURE
,
true
);
$this
->
load
(
self
::
CACHE_FILE_HASHES
,
true
);
$start
=
microtime
(
true
);
$files
=
$this
->
scan
();
echo
$this
->
strPad
(
'scanned in '
.
sprintf
(
'%.4f'
,
microtime
(
true
)
-
$start
)
.
's'
,
25
);
$start
=
microtime
(
true
);
$this
->
createParser
();
foreach
(
$files
as
$file
)
{
$this
->
parseFile
(
$file
);
}
echo
$this
->
strPad
(
'parsed in '
.
sprintf
(
'%.4f'
,
microtime
(
true
)
-
$start
)
.
's'
,
25
);
echo
PHP_EOL
;
ksort
(
$this
->
classToFileMap
);
ksort
(
$this
->
fileHashes
);
ksort
(
$this
->
classInfo
);
$this
->
store
(
self
::
CACHE_FILE_STRUCTURE
);
$this
->
store
(
self
::
CACHE_FILE_HASHES
);
}
/**
* Pads text with spaces from right side.
*
* @param string $text Text.
* @param integer $length Pad length.
*
* @return string
*/
protected
function
strPad
(
$text
,
$length
)
{
return
str_pad
(
$text
,
$length
,
' '
,
STR_PAD_RIGHT
);
}
/**
* Loads cache from disk.
*
* @param string $filename Filename.
* @param boolean $for_writing Load cache for writing or reading.
*
* @return void
*/
protected
function
load
(
$filename
,
$for_writing
)
{
$file_path
=
$this
->
getCacheFilename
(
$filename
);
if
(
!
file_exists
(
$file_path
)
)
{
return
;
}
$cache
=
include
$file_path
;
if
(
$cache
[
'cache_format'
]
!=
self
::
CACHE_FORMAT
)
{
return
;
}
if
(
$filename
===
self
::
CACHE_FILE_STRUCTURE
)
{
$class_info
=
$cache
[
'class_info'
];
if
(
$for_writing
)
{
foreach
(
$cache
[
'classes'
]
as
$class
=>
$file
)
{
if
(
!
isset
(
$this
->
buildingCache
[
$file
])
)
{
$this
->
buildingCache
[
$file
]
=
array
();
}
$this
->
buildingCache
[
$file
][
$class
]
=
$class_info
[
$class
];
}
}
else
{
$this
->
classToFileMap
=
$cache
[
'classes'
];
$this
->
classInfo
=
$class_info
;
}
}
elseif
(
$filename
===
self
::
CACHE_FILE_HASHES
)
{
$this
->
fileHashes
=
$cache
[
'file_hashes'
];
}
}
/**
* Scans path for files.
*
* @return array
*/
protected
function
scan
()
{
$files
=
array
();
$directory_iterator
=
new
\RecursiveDirectoryIterator
(
$this
->
scanPath
);
$filter_iterator
=
new
CodeFolderFilterIterator
(
$directory_iterator
);
foreach
(
new
\RecursiveIteratorIterator
(
$filter_iterator
,
\RecursiveIteratorIterator
::
SELF_FIRST
)
as
$file
)
{
/* @var \SplFileInfo $file */
if
(
$file
->
isFile
()
&&
$file
->
getExtension
()
===
'php'
)
{
$relative_path
=
preg_replace
(
'/^'
.
preg_quote
(
FULL_PATH
,
'/'
)
.
'/'
,
''
,
$file
->
getPathname
(),
1
);
$files
[
$relative_path
]
=
true
;
}
}
// Don't include cache file itself in cache.
$exclude_file
=
preg_replace
(
'/^'
.
preg_quote
(
FULL_PATH
,
'/'
)
.
'/'
,
''
,
$this
->
getCacheFilename
(
self
::
CACHE_FILE_STRUCTURE
),
1
);
unset
(
$files
[
$exclude_file
]);
$exclude_file
=
preg_replace
(
'/^'
.
preg_quote
(
FULL_PATH
,
'/'
)
.
'/'
,
''
,
$this
->
getCacheFilename
(
self
::
CACHE_FILE_HASHES
),
1
);
unset
(
$files
[
$exclude_file
]);
return
array_keys
(
$files
);
}
/**
* Create parser.
*
* @return void
*/
protected
function
createParser
()
{
\kUtil
::
setResourceLimit
();
ini_set
(
'xdebug.max_nesting_level'
,
3000
);
$this
->
parser
=
new
Parser
(
new
Lexer
());
$this
->
traverser
=
new
NodeTraverser
();
$this
->
traverser
->
addVisitor
(
new
NameResolver
());
$this
->
traverser
->
addVisitor
(
new
ClassDetector
(
$this
));
}
/**
* Parses a file.
*
* @param string $file Path to file.
*
* @return void
*/
protected
function
parseFile
(
$file
)
{
$this
->
currentFile
=
$file
;
$code
=
file_get_contents
(
FULL_PATH
.
$file
);
$current_hash
=
filesize
(
FULL_PATH
.
$file
);
$previous_hash
=
isset
(
$this
->
fileHashes
[
$file
])
?
$this
->
fileHashes
[
$file
]
:
0
;
if
(
$current_hash
===
$previous_hash
)
{
// File wasn't change since time, when cache was built.
if
(
isset
(
$this
->
buildingCache
[
$file
])
)
{
foreach
(
$this
->
buildingCache
[
$file
]
as
$class
=>
$class_info
)
{
$this
->
addClass
(
$class
,
$class_info
);
}
}
}
else
{
// Parse file, because it's content doesn't match the cache.
$this
->
fileHashes
[
$file
]
=
$current_hash
;
$statements
=
$this
->
parser
->
parse
(
$code
);
$this
->
traverser
->
traverse
(
$statements
);
}
}
/**
* Stores cache to disk.
*
* @param string $filename Cache filename.
*
* @return void
* @throws \RuntimeException When cache could not be written.
*/
protected
function
store
(
$filename
)
{
$cache
=
array
(
'cache_format'
=>
self
::
CACHE_FORMAT
);
if
(
$filename
===
self
::
CACHE_FILE_STRUCTURE
)
{
$cache
[
'classes'
]
=
$this
->
classToFileMap
;
$cache
[
'class_info'
]
=
$this
->
classInfo
;
}
elseif
(
$filename
===
self
::
CACHE_FILE_HASHES
)
{
$cache
[
'file_hashes'
]
=
$this
->
fileHashes
;
}
$cache
=
$this
->
prettyVarExport
(
$cache
);
$at
=
'@'
;
$file_content
=
<<<EOPHP
<?php
// {$at}codingStandardsIgnoreFile
/**
* This file is automatically {$at}generated. Use 'php tools/build_class_map.php' to rebuild it.
*/
return {$cache};
EOPHP;
$file_path
=
$this
->
getCacheFilename
(
$filename
);
// Don't bother saving, because file wasn't even changed.
if
(
file_exists
(
$file_path
)
&&
file_get_contents
(
$file_path
)
===
$file_content
)
{
return
;
}
if
(
file_put_contents
(
$file_path
,
$file_content
)
===
false
)
{
throw
new
\RuntimeException
(
'Unable to save cache to "'
.
$file_path
.
'" file'
);
}
}
/**
* Prettified var_export.
*
* @param mixed $data Data.
*
* @return string
*/
protected
function
prettyVarExport
(
$data
)
{
$result
=
var_export
(
$data
,
true
);
$result
=
preg_replace
(
"/=>
\n
[ ]+array
\\
(/s"
,
'=> array ('
,
$result
);
$result
=
str_replace
(
array
(
'array ('
,
' '
),
array
(
'array('
,
"
\t
"
),
$result
);
return
$result
;
}
/**
* Returns cache filename.
*
* @param string $filename Filename.
*
* @return string
*/
protected
function
getCacheFilename
(
$filename
)
{
return
$this
->
cachePath
.
'/'
.
$filename
;
}
/**
* Adds class to the map.
*
* @param string $class Class.
* @param array $class_info Class info.
*
* @return void
*/
public
function
addClass
(
$class
,
array
$class_info
)
{
$this
->
classInfo
[
$class
]
=
$class_info
;
$this
->
classToFileMap
[
$class
]
=
$this
->
currentFile
;
}
}
Event Timeline
Log In to Comment