Page Menu
Home
In-Portal Phabricator
Search
Configure Global Search
Log In
Files
F1050578
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
Thu, Jul 3, 12:18 AM
Size
9 KB
Mime Type
text/x-php
Expires
Sat, Jul 5, 12:18 AM (1 d, 6 h)
Engine
blob
Format
Raw Data
Handle
678807
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 array
* @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
);
}
$table_output
=
array
();
$scan_path
=
preg_replace
(
'/^'
.
preg_quote
(
FULL_PATH
,
'/'
)
.
'/'
,
'...'
,
$this
->
scanPath
,
1
);
$table_output
[]
=
$scan_path
;
// The "Path" column.
$this
->
load
(
self
::
CACHE_FILE_STRUCTURE
,
true
);
$this
->
load
(
self
::
CACHE_FILE_HASHES
,
true
);
$start
=
microtime
(
true
);
$files
=
$this
->
scan
();
$table_output
[]
=
sprintf
(
'%.4f'
,
microtime
(
true
)
-
$start
)
.
's'
;
// The "Scanned in" column.
$start
=
microtime
(
true
);
$this
->
createParser
();
foreach
(
$files
as
$file
)
{
$this
->
parseFile
(
$file
);
}
$table_output
[]
=
sprintf
(
'%.4f'
,
microtime
(
true
)
-
$start
)
.
's'
;
// The "Parsed in" column.
ksort
(
$this
->
classToFileMap
);
ksort
(
$this
->
fileHashes
);
ksort
(
$this
->
classInfo
);
$this
->
store
(
self
::
CACHE_FILE_STRUCTURE
);
$this
->
store
(
self
::
CACHE_FILE_HASHES
);
return
$table_output
;
}
/**
* 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. Please use 'in-portal classmap:rebuild' command 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