章 17. 函数

用户自定义函数

一个函数可由以下的语法来定义:

例子 17-1. 展示函数用途的伪代码

<?php
function foo($arg_1, $arg_2, ..., $arg_n)
{
    echo
"Example function.\n";
    return
$retval;
}
?>

任何有效的 PHP 代码都有可能出现在函数内部,甚至包括其它函数和定义。

在 PHP 3 中,函数必须在被调用之前定义。而 PHP 4 则不再有这样的条件。除非函数如以下两个范例中有条件的定义。

如果一个函数以以下两个范例的方式有条件的定义,其定义必须在调用之前完成。

例子 17-2. 有条件的函数

<?php

$makefoo
= true;

/* We can't call foo() from here
   since it doesn't exist yet,
   but we can call bar() */

bar();

if (
$makefoo) {
  function
foo()
  {
    echo
"I don't exist until program execution reaches me.\n";
  }
}

/* Now we can safely call foo()
   since $makefoo evaluated to true */

if ($makefoo) foo();

function
bar()
{
  echo
"I exist immediately upon program start.\n";
}

?>

例子 17-3. 函数中的函数

<?php
function foo()
{
  function
bar()
  {
    echo
"I don't exist until foo() is called.\n";
  }
}

/* We can't call bar() yet
   since it doesn't exist. */

foo();

/* Now we can call bar(),
   foo()'s processesing has
   made it accessable. */

bar();

?>

PHP 中的所有函数和类都具有全局域,可以在内部定义外部调用,反之亦然。

PHP 不支持函数重载,也不可能取消定义或者重定义已声明的函数。

注: 函数名是非大小写敏感的,不过在调用函数的时候,通常使用其在定义时相同的形式。

PHP 3 虽然支持默认参数(更多信息请参照默认参数的值),但是却不支持可变的参数个数。PHP 4 支持:见可变长度的参数列表和涉及到的相关函数 func_num_args()func_get_arg(),以及 func_get_args() 以获取更多的信息。

在 PHP 中可以调用递归函数。但是要避免递归函数/方法调用超过 100-200 层,因为可能会破坏堆栈从而使当前脚本终止。

例子 17-4. 递归函数

<?php
function recursion($a)
{
    if (
$a < 20) {
        echo
"$a\n";
        
recursion($a + 1);
    }
}
?>


add a note add a note User Contributed Notes
leblanc at tamu dot edu
04-Nov-2006 12:31
manual says:
>nor is it possible to undefine or redefine previously-declared functions.

you can undefine or redefine a function.. or so it says.. using
runkit_function_remove
runkit_function_redefine
maybe to implement perl's Memoize module
tom pittlik
05-Feb-2006 03:53
This is a way of formatting different strings with different mandatory functions and is especially useful when processing form data or outputting database fields using structured configuration files:

<?

function format_string($string,$functions)
{
  
$funcs = explode(",",$functions);

   foreach (
$funcs as $func)
   {
       if (
function_exists($func)) $string = $func($string);
   }

   return
$string;
}

echo
format_string("  <b>  this is a test        </b>","strip_tags,strtoupper,trim");
// outputs "THIS IS A TEST"

?>
gnirts dot REMOVE_THIS at HATE_SPAM dot gmail dot com
05-Jan-2006 02:11
If you want to validate that a string could be a valid function name, watch out. preg_match() matches anywhere inside the test string, so strings like 'foo#' and '    bar' will pass with the regex that they give ([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)

The solution is to use anchors in the regex (^ and $) and check the offset of the match (using PREG_OFFSET_CAPTURE).

<?

function is_valid_function_name( $function_name_to_test )
{
  
$number_of_matches = preg_match( '<^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$>'
                                      
, $function_name_to_test
                                      
, $match_offset_array
                                      
, PREG_OFFSET_CAPTURE );

   if(
$number_of_matches === false )
   {
      
trigger_error( 'Error with preg_match' , E_USER_WARNING );
   }

   if(
$match_offset_array[0][1] !== 0 || $number_of_matches !== 1 )
   {
       return
false;
   }
   else
   {
       return
true;
   }
}

?>
p at onion dot whitefyre dot com
19-Jul-2005 11:39
One potentially useful feature is that in function and variable names bytes within 0x7f-0xff are allowed. This means you can use any UTF-8 in a variable name.

As a simple example (this only uses latin-1 characters, but the concept is the same):

<?php
function ($n) {
   return
pow($n, 2);
}

function (
$string) {
   return
'<p>'.str_replace("\n\n", "</p>\n<p>", $string).'</p>';
}

echo (
4); //16

echo ("Some text\n\nbroken into paragraphs");
?>

You can use this to write PHP code in your native language, or even better make creative use of symbols like above to make your code understandable by everyone.
bjorn_nelson at yahoo dot com
30-Jun-2005 09:44
@ gilthansNOSPAAM at gmailSPAAMBLOCK dot com

Your first example works just fine.  There is no reason that pass-by-reference arguments can't work with recursion.

This is what I used:

<?php
$foo
= 0;
function
bar(&$foo){
  
$foo++;
   echo
$foo."\n";
   if(
$foo < 10)
      
bar($foo);
}
bar($foo);
?>
dma05 at web dot de
27-Apr-2005 08:24
@ gilthansNOSPAAM at gmailSPAAMBLOCK dot com

i think you just fried your stack like the manual reads ;)
works up to ~17000-30000 iterations for me... although, that's on a linux box with php5 as an apache2 module...
maybe the cgi version has a smaller stack...

the problem seems logical, because every time your function iterates, it puts another pointer on the stack (the one to your variable), and sooner or later you're out of memory there, so no more pointers to be added which should make it crash, if you're using one global variable, then there's no pointers to be added to your stack and it won't run out (well, not so fast at least, you still need to add other pointers to the stack but at least one less)...
riseofthethorax at earthlink dot net
03-Apr-2005 09:49
I know functions can't be undefined, now..

But how about this..

Include a file, the file defines a variable called
"$function_name".. $function_name contains
the name of the actual function.. The actual function_name
is either created insitu or when the include file was created.
The actual function name, in any case, has a random string
associated with it (20 character alphanumeric?)..

The file including this function, could somehow dynamically
call the function using the $function_name variable..

Since the actual function name is some name plus a random string, it produces a kind of virtual scope (or a statically improbable conflict). Then  you could call the same function multiples of times in the process of a script, and not have to worry about function name clashes.. Of course this is a memory leak in the making, but it allows one to essentially call similar named functions in the current state of PHP configuration..
gilthansNOSPAAM at gmailSPAAMBLOCK dot com
29-Mar-2005 01:36
Note that a function that calls a variable by reference CANNOT be used recursively, it will generate a CGI error (at least on my windows platform).
Thus:
<?php
$foo
= 0;
function
bar(&$foo){
  
$foo++;
   echo
$foo."\n";
   if(
$foo < 10)
      
bar($foo);
}
?>
Will NOT work.
Instead, you should just use global variables.
<?php
$foo
= 0;
function
bar(){
   global
$foo;
  
$foo++;
   echo
$foo."\n";
   if(
$foo < 10)
      
bar($foo);
}
?>
This, of course, assuming that you need to use $foo in other functions or parts of code. Otherwise you can simply pass the variable regulary and there should be no problems.
roy at intelligentdashimaging dot com
08-Feb-2005 05:17
Here's how to get static function behavior and instantiated object behavior in the same function
<?php

// Test of static/non-static method overloading, sort of, for php
class test {
   var
$Id = 2;
   function
priint ($id = 0) {
       if (
$id == 0) {
           echo
$this->Id;
           return;
       }
       echo
$id;
       return;
   }
}
$tc = new test ();
$tc->priint ();
echo
"\n";
test::priint (1);
/* output:
2
1
*/
?>
spy-j at rainbowtroopers dot com
21-Dec-2004 07:01
Take care when using parameters passed by reference to return new values in it:

<?PHP

function foo( &$refArray, ....) {
 
  UNSET(
$refArray); //<-- fatal!!!

 
for (....) {
  
$refArray[] = ....
  }
//end for

}//end foo

?>

I tried to make sure the passed variable is empty so i can fill it up as an array. So i put the UNSET. Unfortunately, this seems to cause a loss with the reference to the passed variable: the filled array was NOT returned in the passed argument refArray!

I have found that replacing unset as follows seems to work correctly:

<?PHP

function bar( &$refArray, ....) {
  
  if (!EMPTY(
$refArray)) $refArray = '';

  : : :

}
//end bar

?>
chris at infinitycubed dot net
11-Aug-2004 09:39
Remember, theres an additional overhead when calling functions, so if performance is key to what you are doing, you want to avoid calling out to other functions.

In a function I was making that found out the distance and angle from 1 point to another, there were 3 calls to a small validation function. It averaged ~0.017 seconds for 400 calls to the function, and with these calls replaced with the actual code, this lowered to 0.011-0.012. So, if you need performance, avoid using other functions.
jose at rondamagazine dot com
17-Jul-2004 09:34
A PHP4 -> PHP5 migration issue:

In PHP5, you can't declare a function inside a class method and call it from inside the method. An example:

<?php
class A {
   function
f() {
     function
inside_f() {
         echo
"I'm inside_f";
     }
     echo
"I'm f";
    
inside_f();
   }
}
?>

PHP5 reports an "Fatal error:  Non-static method A::inside_f() cannot be called statically ..."

Solution: Convert inside_f() to a class method, so you can call it from f() as $this->inside_f().
This will work in PHP4 and PHP5.
And, if you don't mind PHP4 compatibility, you should probably declare inside_f() as a private method, because it is declared inside f() for its private use (at least, that's what I believed I was doing; only now have I discovered that the original inside_f(), as declared, is a global function).
removeloop at removesuperinfinite dot com
04-Aug-2003 04:56
Method overloading is however permitted.

<?php
class A {
       function
A() { }

       function
ech() {
              
$a = func_get_args();
               for(
$t=0;$t<count($a); $t++ ) {
                       echo
$a[$t];
               }
       }
}       

$test = new A();
$test->ech(0,1,2,3,4,5,6,7,8,9);

?>

// output:
// 0123456789
danvk at rice dot edu
10-Jun-2003 02:04
While PHP does allow you to define a function within another function, this feature doesn't work in at all the same way that it does in most other languages. The most common use of an inner subroutine that I've seen is to define a helper function which can't be seen from outside, and still has access to the outer subroutine's local variables. Like this:

function a(){
  function b(){
   print $localVariable;
  }
  $localVariable = 10;
  b();
}

In PHP, this will cause all sorts of problems. Calling a() won't print anything, since $localVariable hasn't been defined within b(), even though it is defined within a(). And calling a() twice will result in an error that you're trying to redefine b().
kop at meme dot com
08-Jun-2003 06:08
You can preface a (builtin php ?) function call with an @ sign, as in:

@foo();

This will supress the injection of error messages into the data stream output to the web client.  You might do this, for example, to supress the display of error messages were foo() a database function and the database server was down.  However, you're probably better off using php configuration directives or error handling functions than using this feature.

See the section on error handling functions.
Storm
10-May-2003 10:55
I think it worthy of noting (for noobies such as myself):
You can define access to a global variable before it is defined when defining a function as long as the variable is defined before the function is called. This had me baffled for a few hours til I tried it out...lol. Here's a quick example:

Perfectly valid:
-------------------------
function hello() {
   global $hi;

   echo $hi;
}

$hi = 'Hi There!'; // Var defined after function is defined

hello();
-------------------------

I know most of the Gurus persay already knew this, but I didn't! :p This helps ;-)
php at simoneast dot net
11-Apr-2003 12:59
If you're frustrated by not having access to global variables from within your functions, instead of declaring each one (particularly if you don't know them all) there are a couple of workarounds...

If your function just needs to read global variables...

function aCoolFunction () {
   extract($GLOBALS);
....

This creates a copy of all the global variables in the function scope.  Notice that because it's a copy of the variables, changing them won't affect the variables outside the function and the copies are lost at the conclusion of the function.

If you need to write to your global variables, I haven't tested it, but you could probably loop through the $GLOBALS array and make a "global" declaration for each one.  Then you could modify the variables.

Please note that this shouldn't be standard practice, but only in the case where a function needs access to all the global variables when they may be different from one call to another.  Use the "global var1, var2..." declaration where possible.

Hope that helps some people.

Simon.
nutbar at innocent dot com
13-Mar-2003 04:06
Regarding the comments about having to declare global variables inside of functions before you can use them...

Lots of you seem to complain about having to declare lots of variables, when really there's one simple solution to this:

global $GLOBALS;

This will define the $GLOBALS variable inside your code, and since that variable is basically like the mother of all variables - *presto*, you now have access to any variable in PHP.
mittag /// add /// marcmittag /// de
23-Jan-2003 08:31
To devciti at yahoo dot com

The section "returning values" of the docu says:

=====
You can't return multiple values from a function, but similar results can be obtained by returning a list.

function small_numbers()
{
   return array (0, 1, 2);
}
list ($zero, $one, $two) = small_numbers();
=====
arathorn at ifrance dot com
01-Jan-2003 11:02
If u want to put somes variables in function that was'nt passed by it, you must use "global" :

<?php

$op2
= blabla;
$op3 = blabla;

function
foo($op1)
{
  global
$op2, $op3;

   echo
$op1;
   echo
$op2;
   echo
$op3;
}

?>
misc dot anders at feder dot dk
24-Dec-2002 07:28
PHP allows you to address functions in a very dynamic way:

$foo = "bar";
$foo("fubar");

The above will call the bar function with the "fubar" argument.
albaity at php4web dot com
26-Oct-2002 10:06
To use class from function
you need first to load class OUT the function
and then you can use the class functions from your function
example :
class Cart
{
   var $items;  // Items in our shopping cart
  
   // Add $num articles of $artnr to the cart
 
   function add_item ($artnr, $num)
   {
       $this->items[$artnr] += $num;
   }
  
   // Take $num articles of $artnr out of the cart
 
   function remove_item ($artnr, $num)
   {
       if ($this->items[$artnr] > $num) {
           $this->items[$artnr] -= $num;
           return true;
       } else {
           return false;
       } 
   }
}

------------------------
<?php
$cart
= new Cart;

function
additem($var1,$var2){
$cart->add_item($var1, $var2);
}
additem(1,10);
?>
germanAlonso at keltoi-web dot com
10-Aug-2002 10:17
Although yasuo_ohgaki@hotmail.com has already pointed the recursion support on PHP, here's another example, wich shows clearly the mechanism of recursive algorithms:

function fact($i){
  if($i==1){
   return 1;
  }else{
   return $i*fact($i-1);
  }
}

It returns $i! (supposing $i is a valid positive integer greater than 0).
bishop
01-May-2002 10:54
Consider:

function a() {
   function b() {
       echo 'I am b';
   }
   echo 'I am a';
}

a();
a();

As you might NOT expect, the second call to a() fails with a "Cannot redeclare b()" error.  This behaviour is correct, insofar as PHP doesn't "allow functions to be redefined."

A work around:
function a() {
   if ( ! function_exists('b') ) {
       function b() {
           echo 'I am b';
       }
   }
   echo 'I am a';
}
bishop
01-May-2002 10:49
The documentation statement:

"In PHP 3, functions must be defined before they are referenced. No such requirement exists in PHP 4."

is not entirely accurate (or at least misleading).

Consider:
function a() {
   function b() {
       echo 'I am b';
   }
   echo 'I am a';
}

You MUST call a() before you can even think about using b().  Why? The parser hasn't touched the scope inside function a (for efficiency reasons), so b has yet to be defined or even *declared*.

The documentation (I believe) refers to this behaviour:
a();

function a() {
  echo 'I am a';
}

which is perfectly valid and runs as you probably expect.  However, the following does not work as expected (or documented):

function a() {
   b();

   function b() {
       echo 'I am b';
   }
   echo 'I am a';
}

a();

Rather than getting "I am b" followed by "I am a" you get a parse error ("Call to undefined function b()").

So, the bottom line: Gewaehrleistungsausschluss
fabio at city dot ac dot uk
14-Feb-2002 10:57
As a corollary to other people's contributions in this section, you have to be careful when transforming a piece of code in to a function (say F1). If this piece of code contains calls to another function (say F2), then each variable used in F2 and defined in F1 must be declared as GLOBAL both in F1 and F2. This is tricky.
xpaz at somm dot com
15-Nov-2001 04:47
It is possible to define a function from inside another function.
The result is that the inner function does not exist until the outer function gets executed.

For example, the following code:

function a () {
  function b() {
   echo "I am b.\n";
  }
  echo "I am a.\n";
}
if (function_exists("b")) echo "b is defined.\n"; else echo "b is not defined.\n";
a();
if (function_exists("b")) echo "b is defined.\n"; else echo "b is not defined.\n";

echoes:

b is not defined.
I am a.
b is defined.

Classes too can be defined inside functions, and will not exist until the outer function gets executed.
aboyd at ssti dot com
05-Apr-2001 11:34
[Editor's note: put your includes in the beginning of your script. You can call an included function, after it has been included                --jeroen]

The documentation states: "In PHP 3, functions must be defined before they are referenced. No such requirement exists in PHP 4."

I thought it wise to note here that there is in fact a limitation: you cannot bury your function in an include() or require().  If the function is in an include()'d file, there is NO way to call that function beforehand.  The workaround is to put the function directly in the file that calls the function.
yasuo_ohgaki at hotmail dot com
09-Mar-2001 02:42
PHP supports recursion. I thought it worth to mention.

Simple Quick Sort using recursion works perfectly.

== OUTPUT ==
Recursion TEST

Array
(
   [0] => 12
   [1] => 23
   [2] => 35
   [3] => 45
   [4] => 56
   [5] => 67
)
== END OUTPUT ==

== QUICK SORT CODE ==
<?php

echo('<br>Recursion TEST<br>');

function
swap(&$v, $i, $j) {
 
$temp = $v[$i];
 
$v[$i] = $v[$j];
 
$v[$j] = $temp;
}

// Quick Sort integer array - $int_array[$left] .. $int_array[$right]
function qsort(&$int_array, $left, $right) {
 if (
$left >= $right)
  return;
// Do nothing if there are less than 2 array elements
 
swap ($int_array, $left, intval(($left+$right)/2));
 
$last = $left;
 for (
$i = $left + 1; $i <= $right; $i++)
  if (
$int_array[$i] < $int_array[$left])
  
swap($int_array, ++$last, $i);
 
swap($int_array, $left, $last);
 
qsort($int_array, $left, $last-1);
 
qsort($int_array, $last+1, $right);
}

$val = array(56,23,45,67,12,35);

qsort($val, 0, count($val)-1);

echo
'<pre>';
print_r ($val);
echo
'</pre>';

?>
== END QUICK SORT ==
kop at meme dot com
15-Dec-2000 07:14
See also about controlling the generation of error messages by putting @ in front of the function before you call it, in the section "error control operators".
php-general at list dot php dot net
06-Dec-2000 08:51
If a user-defined function does not explicitly return a value, then it
 will return NULL. For most truth tests, this can be considered as FALSE.

For example:

function print_list ($array)
{
   print implode ("<br />", $array);
}

if (print_list ($HTTP_POST_VARS))
   print "The print_list function returned a value that can be considered true."
else
   print "The print_list function returned a value that can be considered false."
GMCardoe at netherworldrpg dot net
24-Apr-2000 05:02
Stack overflow means your function called itself recursivly too many times and just completely filled up the processes stack. That error is there to stop a recursive call from completely taking up the entire system memory.
cap at capsi dot cx
22-Feb-2000 08:19
When using a function within a function, using global in the inner function will not make variables available that have been first initialized within the outer function.
php at paintbot dot com
05-Feb-2000 11:32
Important Note to All New Users: functions do NOT have default access to GLOBAL variables.  You must specify globals as such in your function using the 'global' type/keyword.  See the section on variables:scope.

This note should also be added to the documentation, as it would help the majority of programmers who use languages where globals are, well, global (that is, available from anywhere).  The scoping rules should also not be buried in subsection 4 of the variables section.  It should be front and center because I think this is probably one of the most non-standard and thus confusing design choices of PHP.

[Ed. note: the variables $_GET, $_POST, $_REQUEST, $_SESSION, and $_FILES are superglobals, which means you don't need the global keyword to use them inside a function]
brooke at wayport dot net
08-Nov-1999 04:21
Many people ask how to call functions that are in other files. See "Require"  and "Include" in the manual.