How to get an entire function from a file
Okay, I'm reading the file now, line by line. I know every function name in the file as it is defined elsewhere in the XML document. Here's what should be:
function function_name
Where function_name is the name of the function.
I am getting all the function definitions from an XML document that I have already entered into the array of function names and I only need to grab those functions from the php file. And rebuild this php file so that only those functions are in it. That is, if the php file has more functions than what is defined in the XML tag, then I need to remove these functions and rewrite the .php file only with those functions that the user specified in the XML file.
So, the dilemma I'm running into is how to define an END for a line reading function, and I know that functions can have functions inside them. So I don't want to remove the functions inside them Just functions that are standalone and not defined in the accompanying XML file. Any ideas on how to do this?
Ok, now I am using the following function:
//!!! - Used to grab the contents of all functions within a file with the functions array.
function get_functions($source, $functions = array())
{
global $txt;
if (!file_exists($source) || !is_readable($source))
return '';
$tokens = token_get_all(file_get_contents($source));
foreach($functions as $funcName)
{
for($i=0,$z=count($tokens); $i<$z; $i++)
{
if (is_array($tokens[$i]) && $tokens[$i][0] == T_FUNCTION && is_array($tokens[$i+1]) && $tokens[$i+1][0] == T_WHITESPACE && is_array($tokens[$i+2]) && $tokens[$i+2][1] == $funcName)
break;
$accumulator = array();
// collect tokens from function head through opening brace
while($tokens[$i] != '{' && ($i < $z)) {
$accumulator[] = is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i];
$i++;
}
if($i == $z) {
// handle error
fatal_error($txt['error_occurred'], false);
} else {
// note, accumulate, and position index past brace
$braceDepth = 1;
$accumulator[] = '{';
$i++;
}
while($braceDepth > 0 && ($i < $z)) {
if(is_array($tokens[$i]))
$accumulator[] = $tokens[$i][1];
else {
$accumulator[] = $tokens[i];
if($tokens[$i] == '{') $braceDepth++;
else if($tokens[i] == '}') $braceDepth--;
}
$i++;
}
$functionSrc = implode(null,$accumulator);
}
}
return $functionSrc;
}
OK, so this is the content of this php file:
<?php
function module_testing($params)
{
// Is it installed?
$test_param = !isset($params['test_param']) ? 'Testing Testing 1 2 3!' : $params['test_param'];
// Grab the params, if they exist.
if (is_array($params))
{
echo $test_param;
}
// Throw an error.
else
module_error();
}
?>
and change it like this:
<?php
function module_testing($params)
{
// Is it installed?
$test_param isset$params'test_param' 'Testing Testing 1 2 3!' $params'test_param'
// Grab the params, if they exist.
if is_array$params
echo $test_param
// Throw an error.
else
module_error
?>
As you can see, a whole bunch of stuff went away here. And the last closing parenthesis is missing ... All I have to do is check if the function exists here function module_testing
and grab the whole function and write it to the same file. Seems simple enough, but WoW, this is some important coding for this minor thing IMO ...
Or I could also check if there is a function defined here that is not in $ functions, if so, than just remove that function. Perhaps it's easier with this approach?
a source to share
Ok guys, I managed to fix it perfectly, and on my own, and here is the perfect solution. I want to thank all of you for your help with this. Thanks you guys haven't helped me far here. But I knew it would be a simple, featureless solution tokenizer
. Perhaps you guys forgot that I have the name of each function? Anyway, thanks again, but no token functions are needed for this.
Greetings.
function remove_undefined_functions($source, $functions = array())
{
if (!file_exists($source) || !is_readable($source))
return '';
$code = '';
$removeStart = false;
$fp = fopen($source, 'rb');
while (!feof($fp))
{
$output = fgets($fp);
$funcStart = strpos(strtolower($output), 'function');
if ($funcStart !== false)
{
foreach($functions as $funcName)
{
if (strpos($output, $funcName) !== false)
{
$code .= $output;
$removeStart = false;
break;
}
else
$removeStart = true;
}
continue;
}
else
{
if (substr($output, 0, 2) == '?>' || !$removeStart)
$code .= $output;
}
}
fclose($fp);
// Rewrite the file with the functions that are defined.
$fo = @fopen($source, 'wb');
// Get rid of the extra lines...
@fwrite($fo, str_replace("\r\n", "\n", $code));
fclose($fo);
}
And this will make it so that if there is a function inside the function, then the user will have to define it, otherwise the function will not work as expected. Therefore, it is not very important for me, since they can have an unlimited number of functions and are better suited for each function - this is a function for itself.
a source to share
The specified Sarfraz PHP token is a good idea, especially if you are going to rewrite a lot of code outside of what you mentioned here.
However, this case can be quite simple, you won't need it.
A php function, if well formed, should have:
1) "Head", which looks like function funcname($arg1,...,$argn)
. You can probably find this and pull it out with a regex.
2) Following the head, a "body" that will consist of everything after the head, which comes in a pair of matched curly braces. So, you have to figure out how to match them. One way to do this is by specifying a variable $curlyBraceDepth
. Start it at 0, and then, starting at the curly brace that opens the body of the function, step through the code one character at a time. Increase every time you encounter an opening parenthesis $curlyBraceDepth
. Every time you encounter a closing parenthesis, shrink it. When$curlyBraceDepth < 1
(for example, when you go back to depth 0), you end up traversing the body of the function. While you are checking each character, you either need to accumulate every character you read into an array, or if you've already got it all in a string in memory, marking the start and end position, so you can pull it out later.
Now there is a big caveat here: if any of your functions treats the unmatched curly braces as characters within strings - not exactly normal, but perfectly legal and possible php - then you will also have to add conditional code to parse the strings as separate tokens. While you could write your own code to handle this, if you're worried about it as a corner, Tokenizer is probably the reliable way to go.
But you would use something like the algorithm I gave above when you go through the markers, anyway - find the markers that represent the head, sort the markers that contain the body counting T_CURLY_OPEN and T_CURLY_CLOSE to keep track of the depth of your parenthesis by accumulating markers. when you go and concatenate them when you reach zero depth of the parenthesis.
UPDATE (with Tokenizer)
token_get_all
takes care of combining individual source characters into syntactically meaningful PHP markers. Here's a quick example. Let's say we have the following PHP source line:
$s = '<?php function one() { return 1; }';
And we run it through token_get_all
:
$tokens = token_get_all($s);
If you do print_r
on this, here's what you'll see (with some inline comments):
Array
(
[0] => Array
(
[0] => 367 // token number (also known by constant T_OPEN_TAG)
[1] => <?php // token literal as found in source
[2] => 1
)
[1] => Array
(
[0] => 333 // token number (also known by constant T_FUNCTION)
[1] => function // token literal as found in source
[2] => 1
)
[2] => Array
(
[0] => 370 // token number (aka T_WHITESPACE)
[1] => // you can't see it, but it there. :)
[2] => 1
)
[3] => Array
(
[0] => 307 // token number (aka T_STRING)
[1] => one // hey, it the name of our function
[2] => 1
)
[4] => ( // literal token - open paren
[5] => ) // literal token - close paren
[6] => Array
(
[0] => 370
[1] =>
[2] => 1
)
[7] => {
[8] => Array
(
[0] => 370
[1] =>
[2] => 1
)
[9] => Array
(
[0] => 335
[1] => return
[2] => 1
)
[10] => Array
(
[0] => 370
[1] =>
[2] => 1
)
[11] => Array
(
[0] => 305
[1] => 1
[2] => 1
)
[12] => ;
[13] => Array
(
[0] => 370
[1] =>
[2] => 1
)
[14] => }
[15] => Array
(
[0] => 370
[1] =>
[2] => 1
)
[16] => Array
(
[0] => 369
[1] => ?>
[2] => 1
)
)
Note that some of the array elements are character literals (parentheses and parentheses, in fact, which makes this easier than I thought). Others are arrays containing a "token number" at index 0 and a literal token at index 1 (don't know what the value "1" is at 2nd index). If you want a "token name" - indeed a PHP constant that evaluates to a token number, you can use a function token_name
. For example, this familiar first token, 367, is referred to by its name and PHP constant T_OPEN_TAG.
If you want to use this to copy the source of function "one" from file A to file B, you can do $tokens = token_get_all(file_get_contents('file_A'))
and then search for a sequence of literal tokens denoting the start of that function - in our case T_FUNCTION, T_WHITESPACE and T_STRING equal to "one" ... So:
for($i=0,$z=count($tokens); $i<$z; $i++)
if( is_array($tokens[$i])
&& $tokens[$i][0] == T_FUNCTION
&& is_array($tokens[$i+1])
&& $tokens[$i+1][0] == T_WHITESPACE
&& is_array($tokens[$i+2])
&& $tokens[$i+2][1] == 'one')
break;
At this point, you will do what I described earlier: start with an opening curly brace for the function body with an indentation level of 1, watch the curly braces, track the depth, and accumulate tokens:
$accumulator = array();
// collect tokens from function head through opening brace
while($tokens[$i] != '{' && ($i < $z)) {
$accumulator[] = is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i];
$i++;
}
if($i == $z) {
// handle error
} else {
// note, accumulate, and position index past brace
$braceDepth = 1;
$accumulator[] = '{';
$i++;
}
while($braceDepth > 0 && ($i < $z)) {
if(is_array($tokens[$i]))
$accumulator[] = $tokens[$i][1];
else {
$accumulator[] = $tokens[i];
if($tokens[$i] == '{') $braceDepth++;
else if($tokens[i] == '}') $braceDepth--;
}
}
$functionSrc = implode(null,$accumulator);
a source to share
You probably want to try PHP Tokenizer.
http://www.php.net/manual/en/ref.tokenizer.php
From an external script:
<?php
var_dump(token_get_all(file_get_contents('myscript.php')));
?>
a source to share
The function will be, as far as I know, is always included in the brackets {}
. So your job is to scan the php file to start the function - you said it wasn't a problem - and then you have to scan until all open ones {
are closed.
But what if your function has a function or if clause or something else that also uses those brackets? To handle this, you need to execute im $counter
which counts for each {
and down for each }
. If counter = zero
the end of the function is reached.
Example: Your function:
//lots of functions
function f_unimportant($args) { //Scan the first "{" after your f_unimportant
//and set $counter=1;
if($args > '') { //increase $counter by 1
//Do stuff
} //decrease $counter by 1
echo $result;
} //decrease $counter by 1
//now $counter is zero and end of function is reached
The counter tells you the depth of your code. If the function depth = 0 is over.
Analysis. You have a $ char array where your phpfile is stored starting at function f_unimportant($args) {
.
$counter = 1;
$length = 0; //length of your function (to be able to delete it)
foreach($array as $char) {
$length ++;
if($char == '{') {
$counter ++;
}
else if($char == '}') {
$counter --;
}
if($counter == 0) {break;} //leave foreach because end of function is reached
}
//now you just delete $length chars from your phpfile starting at the position
//you already found out, where your function starts.
and don't forget to remove as well function f_unimportant($args) {
(it doesn't count in $ length!)
a source to share