· 4 years ago · Apr 21, 2021, 02:08 PM
1
2© Andrew Beak 2017
3Andrew BeakPHP 7 Zend Certification Study Guidehttps://doi.org/10.1007/978-1-4842-3246-0_2
42. Functions
5Andrew Beak1
6(1)
7Grafham, Cambridgeshire, UK
8
9Functions are packages of code that can be used to execute a sequence of instructions. Any valid code can be used inside a function, including calls to other functions and classes.
10In PHP, function names are case insensitive and can be referenced before they are defined, unless they are defined in a conditional code block. Functions can be built-in, provided by an extension, or user-defined. Functions are distinct from language constructs.
11Arguments
12Arguments to a function, also known as parameters, allow you to pass values into the function scope. Arguments are passed as a comma-separated list and are evaluated left to right.
13Argument Type Declarations
14You can specify what type of variable may be passed as an argument.
15This is useful because PHP is a loosely typed language and if you specify exactly what sort of variable you expect, then your function is going to be more reliable and your code easier to read. Another advantage is that giving type hints helps your IDE to give you more meaningful hints.
16If your function is called and the variable passed is the incorrect type, then PHP 7 will raise a TypeError exception .
17To specify the type of argument that you are expecting, you add the type name in front of the argument definition, like this:
18<?php
19// $itemName must be a string and $details must be an array
20function addToShoppingCart(string $itemName, array $details) {}
21/*
22$paymentObject must be an object that either:
23implements the PaymentProviderInterface interface,
24or is any child of a class that does
25*/
26function requestPayment(PaymentProviderInterface $paymentObject) {}
27/*
28$employee must be an object that is either:
29an instance of the Employee class,
30or is any child of a class that does
31*/
32function calculateWage(Employee $employee) {}
33// $callback must be a callable
34function performCalculation(callable $method) {}
35In the previous example, I've shown that we can tell PHP to expect scalar variables, composite variables (arrays and objects), and callables. We discuss exactly what callables are a little later in this chapter.
36The following table summarizes what types can be declared.
37Type
38
39Description
40Class name or Interface
41
42The parameter must be an instance of, or a child of, the specified class or interface.
43self
44
45The parameter must be an instance of the current class.
46array
47
48bool
49
50float
51
52int
53
54string
55
56iterable
57
58The parameter must be either an array or an instanceof traversable.
59callable
60
61The parameter must be a valid callable.
62Note
63When I say “ancestor” class, I’m referring to any superclass of your class: the parent, the parent’s parent, and so on. Likewise, I’m using the word “child” to denote a child, grandchild, great-grandchild, and so on.
64You cannot use type aliases. For example, you cannot use boolean in place of bool as a type declaration; if you do so, PHP will expect an instance of a class called boolean, like this:
65<?php
66function A(boolean $a) {var_dump($a);}
67A(true);
68// Fatal error: Uncaught TypeError: Argument 1 passed to A() must be an instance of boolean, boolean given,
69There are two ways that you can enforce scalar type hinting: coercive (default) and strict.
70You configure the mode per file by placing a declare directive at the top of the file. This will affect the way that PHP enforces the function arguments as well as the function return type.
71Note
72Setting strict mode is done per file.
73In coercive mode, PHP will automatically try to cast variables of the wrong type to the expected type.
74In the following example, the script outputs 'string' because PHP silently casts the integer we pass to a string.
75<?php
76function sayHello(string $name) {
77 echo gettype($name);
78}
79sayHello(100); // string
80If, however, we were to specify strict mode, then PHP will generate a TypeError, as in this example:
81<?php
82declare(strict_types=1);
83function sayHello(string $name) {
84 echo gettype($name);
85}
86sayHello(100);
87/*
88Fatal error: Uncaught TypeError: Argument 1 passed to sayHello() must be of the type string, integer given,
89*/
90Alternate Null Type Syntax
91PHP 7.1 introduced a new way to type hint variables that may be null. You can prefix the type hint with a question mark to indicate that the variable may either be null or of the specified type. Here's an example:
92<?php
93function myFunc(?MyObject $myObj)
94{
95 echo 'hello world';
96}
97// this is allowed
98myFunc(null);
99// this produces a fatal error: Too few arguments
100myFunc();
101Note
102The argument is not optional; you have to explicitly pass null or an object of the specified type.
103Optional Arguments
104You can specify a default value for a parameter that has the effect of making it optional .
105Note
106PHP 7 will throw an ArgumentCountError 1 if you do not supply all the mandatory parameters to a function. You can only omit passing parameters that are optional.
107In the following example, if the user does not supply a message, the function assumes it will be world.
108<?php
109function sayHi($message = 'world') {
110 echo 'Hello $message';
111}
112sayHi();
113Overloading Functions
114In other programming languages, overloading usually refers to declaring multiple functions with the same name but with differing quantities and types of arguments. PHP views overloading as providing the means to dynamically “create” properties and methods.
115PHP will not let you redeclare the same function name. However, PHP does let you call a function with different arguments and offers you some functions to be able to access the arguments that a function was called with.
116Here are three of these functions:
117Function
118
119Returns
120func_num_args()
121
122How many arguments were passed to the function
123func_get_arg($num)
124
125Parameter number $num (zero based)
126func_get_args()
127
128All parameters passed to the function as an array
129Here is an example showing how a function can accept any number of parameters of any sort, and how you can access them:
130<?php
131function myFunc() {
132 foreach(func_get_args() as $arg => $value) {
133 echo '$arg is $value' . PHP_EOL;
134 }
135}
136myFunc('variable', 3, 'parameters');
137/*
1380 is variable
1391 is 3
1402 is parameters
141*/
142The following code illustrates an obscure difference between PHP 7 and PHP 5:
143<?php
144function myFunc($data) {
145 $data = 'Changed';
146 echo func_get_arg(0);
147}
148In PHP 5, this outputs Variable, but in PHP 7, it outputs Changed. This shows that in PHP 7 if you change the value of an argument in a function, then the value returned by func_get_arg() will be the new value and not the original value.
149Variadics
150PHP 5.6 introduced variadics that explicitly accept a variable number of parameters. By using the ... token in your argument list, you specify that the function will accept a variable number of parameters.
151The variadic parameters are made available in your function as an array.
152If you are mixing normal fixed parameters with a variadic syntax, then the variadic parameter must be the last parameter in the list of parameters.
153The PHP manual2 has a very clear example that shows the interaction between compulsory, optional, and variadic parameters:
154<?php
155function parameterTypeExample($required, $optional = null, ...$variadicParams) {
156 printf('Required: %d; Optional: %d; number of variadic parameters: %d'.'\n',
157 $required, $optional, count($variadicParams));
158}
159f(1);
160f(1, 2);
161f(1, 2, 3);
162f(1, 2, 3, 4);
163f(1, 2, 3, 4, 5);
164This outputs:
165$req: 1; $opt: 0; number of params: 0
166$req: 1; $opt: 2; number of params: 0
167$req: 1; $opt: 2; number of params: 1
168$req: 1; $opt: 2; number of params: 2
169$req: 1; $opt: 2; number of params: 3
170Note that the variadic parameter is made available as an ordinary array $params.
171References
172By default, PHP passes arguments to functions by value, but it is possible to pass them by reference. You can do this by declaring the argument as a pass by reference, as in this example:
173<?php
174function addOne(&$arg) {
175 $arg++;
176}
177$a = 0;
178addOne($a);
179echo $a; // 1
180The & operator marks the parameter as being passed by reference. Changes to this parameter in the function will change the variable passed to it.
181If a function argument is not defined as being a reference, then you cannot pass a reference in that argument.
182This code will generate a fatal error:
183<?php
184function addOne($arg) {
185 $arg++;
186}
187$a = 0;
188addOne(&$a); // fatal error as of PHP 5.4.0
189echo $a;
190Variable Functions
191Variable functions are similar in concept to variable variable names. They’re easiest to explain with a syntax example:
192<?php
193function foo() {
194 echo 'Foo';
195}
196$var = 'foo';
197$var(); // calls foo()
198We can see that if PHP encounters a variable name that has parentheses appended to it then it evaluates the variable. It then looks for a function that has a name matching this evaluation. If it finds a matching function, it is executed; otherwise, the normal error generation will occur.
199Note
200Language constructs such as we saw earlier are not functions. You cannot use them as variable functions.
201You can call any callable as a variable function. We’ll discuss callables a little later in the “Callables, Lambdas, and Closures” section.
202Returns
203Using the return statement will prevent further code from executing in your function. If you return from the root scope, then your program will terminate.
204<?php
205return 'hello';
206echo 'This is never run';
207PHP will return NULL if you do not specify a return value for your function using the return keyword.
208In the “Generators” section, we deal with the yield keyword. These are similar enough to returns to mention in passing here, but important enough to have their own section later.
209Generators let you write a function that will generate successive members of an array that you can iterate over without needing to hold the entire data set in memory. At the end of the yielded list of values, the generator can optionally return a final value.
210In PHP 7, we can specify what type of variable we expect to return. We discuss this in the next section.
211Return Type Declarations
212We previously looked at how you can declare what variable type your function arguments will be. You can also specify what variable type the function will return.
213To do so, you place a colon and the type name after the parameters braces. The same types are available for return types as can be specified for the arguments.
214Let’s look at an example:
215<?php
216function getFullName(string $firstName, string $lastName): string {
217 return 123;
218}
219$name = getFullName('Mary', 'Moon');
220echo gettype($name); // string
221Because PHP is in coercive mode by default, it converts the integer 123 to a string when it returns from the function. If we declared strict mode, then PHP would generate a TypeError, just as it did when we were looking at argument type declarations.
222Note
223You can't specify strict mode for just one of the return or argument type declarations. If you specify strict mode, it will affect both.
224Return Void
225If the function is going to return null, you can specify that it will return 'void' (PHP 7.1+), as in this example:
226<?php
227function sayHello(): void {
228 echo 'Hello World';
229}
230// Hello World
231sayWorld();
232Caution
233Trying to specify that it will return null will result in a fatal error.
234Return by Reference
235It is possible to declare a function so that it returns a reference to a variable, rather than a copy of the variable. The PHP Manual notes that you should not do this as a performance optimization, but rather only when you have a valid technical reason to do so.
236To declare a function as return by reference, you place an & operator in front of its name:
237<?php
238function &getValue() {...}
239Then, when calling the function, you also place the & operator in front of the call:
240<?php
241$myValue = &getValue();
242After this call, the $myValue variable will contain a reference to the variable that the getValue() function returns.
243Note
244Notice the difference between returning by reference (which is allowed) and passing an argument by reference at runtime (which is not).
245The function itself must return a variable. If you try to return, for example, a numeric literal like 1, a runtime error will be generated.
246Two use cases for this are the Factory pattern and for obtaining a resource like a file handle or database connection.
247Variable Scope in Functions
248As in other languages, the scope of a PHP variable is the context in which it was defined. PHP has three levels of scope—global, function, and class. Every time a function is called, a new function scope is created.
249You can include global scope variables into your function in one of two ways:
250<?php
251$glob = 'Global variable';
252function myFunction() {
253 global $glob; // first method
254 $glob = $GLOBALS['glob']; // second method
255 $glob = 'Changed';
256}
257myFunction();
258echo $glob; // Changed
259Note that the two methods have an identical effect of allowing you to use the $glob variable in myFunction() and have it refer to the $glob variable declared in the global scope.
260Caution
261Most coding standards strongly discourage global variables because they introduce problems when writing tests, can introduce weird context problems, and make debugging more difficult.
262Lambda and Closure
263A lambda in PHP is an anonymous function that can be stored as a variable.
264<?php
265$lambda = function($a, $b) {
266 echo $a + $b;
267};
268echo gettype($lambda); // true
269echo (int)is_callable($lambda); // 1
270echo get_class($lambda); // Closure
271We can see that in PHP lambdas and closures are implemented as objects instantiated from the Closure 3 class.
272Lambda variables and closures can both be used in functions that accept a callable.
273Note
274You can use the is_callable() function to check if a variable is a callable.
275A closure in PHP is an anonymous function that encapsulates variables so they can be used once their original references are out of scope. Another way of putting this is to say that the anonymous function “closes over” variables that are in the scope it was defined in.
276In practical syntax in PHP, we define a closure like this:
277<?php
278$string = 'Hello World!';
279$closure = function() use ($string) {
280 echo $string;
281};
282$closure();
283That looks nearly identical to a lambda, but notice the use ($string) syntax that occurs just before the code block begins.
284The effect of this is to take the $string variable that exists in the same scope of the closure and make it available within the closure.
285Note
286You can call lambdas and closures using the syntax you use for variable functions.
287In this lambda example, the function only had access to the parameters it was passed, and nothing from the containing scope would be passed in. Calling echo $string would result in a warning because the variable doesn’t exist.
288Early and Late Binding
289There are two ways in which a variable can be bound: early and late.
290In early binding, we know the value and type of the variable before we use it at runtime. This is usually done in some static declarative manner. The value of the variable that is used inside the parameter will be the value that it was when the closure was defined.
291By contrast, when we use late binding we do not know what the variable type or value is until we call the closure. PHP will coerce the variable to a specific type and value when it needs to operate on it.
292When it binds a variable to a closure, PHP will use early binding by default. If you want to use late binding, you should use a reference when importing.
293This will all be a lot clearer when you walk through a simple example:
294<?php
295$a = 'some string';
296// early binding (default)
297$b = function() use ($a) {
298 echo $a;
299};
300$a = 'Hello World';
301// some string
302$b();
303Here we are using the default (early) binding method to bind the value of $a to the lambda $b.
304The value of $a is “some string” when we define the lambda. Therefore, when we call the lambda, the value 'some string' is output, even though we have changed the value of $a after declaring the lambda.
305If we were to specify that $a is to be used as a reference, then the output would be 'Hello World', like this:
306<?php
307$a = 'some string';
308// late binding (reference)
309$b = function() use (&$a) {
310 echo $a;
311};
312$a = 'Hello World';
313// Hello World
314$b();
315Binding Closures to Scopes
316When you create a closure, it “closes over” the current scope and so can be thought of as being bound to a particular scope. The Closure class has two methods—bind and bindTo—and they allow you to change the scope to which the variable is bound:
317<?php
318class Animal {
319 public function getClosure() {
320 $boundVariable = 'Animal';
321 return function() use ($boundVariable) {
322 return $this->nature . ' ' . $boundVariable;
323 };
324 }
325}
326class Cat extends Animal {
327 protected $nature = 'Awesome';
328}
329class Dog extends Animal {
330 protected $nature = 'Friendly';
331}
332$cat = new Cat;
333$closure = $cat->getClosure();
334echo $closure(); // Awesome Animal
335$closure = $closure->bindTo(new Dog);
336echo $closure(); // Friendly Animal
337There are two important things to notice in this code.
338First, binding the closure to a different object returns a duplicate of the original, so you have to assign the result of calling bindTo() to a variable.
339Second, the new closure will have the same bound variables and body, but a different bound object and scope. In the previous example, the $boundVariable is duplicated into the new closure when we bind to the new object.
340Self-Executing Closures
341You can create self-executing anonymous functions in PHP 7 using syntax very similar to JavaScript:
342<?php
343(function() {
344 echo 'Self-executing anonymous function';
345 echo $definedInClosure = 'Variable set';
346})();
347var_dump(isset($definedInClosure)); // bool(false)
348Note in this example that the variable we define inside the closure is not defined outside the scope of the closure. You can use this sort of structure to avoid polluting your global scope.
349Callables
350Callables were introduced as a type hint for functions in PHP 5.4
351They are callbacks that some functions, for example usort(), accept.
352A callable for a function such as usort() can be one of the following:
353
354 An inline anonymous function
355 A lambda or closure variable
356 A string denoting a PHP function (but not language constructs)
357 A string denoting a user-defined function
358 An array containing an instance of an object in the first element, and the string name of the function to call in the second element
359 A string containing the name of a static method in a class (PHP 5.2.3+)
360
361Note
362You can’t use a language construct as a callable.
363There are examples of all of these in the PHP manual page on callables.4
364Chapter 2 Quiz
365Q1: What is the output of the following code?
366<?php
367declare(strict_types=1);
368function multiply(float $a, float $b): int {
369 return $a * $b;
370}
371$six = multiply(2, 3);
372echo gettype($six);
373Int
374Float
375Fatal error: Uncaught TypeError
376Q2: Some PHP functions, like echo, do not need you to use brackets when calling them. Is this true?
377Yes, because you can call it like this: echo 'hello';
378Yes, because echo is a special case.
379No, because echo is a language construct and not a function. All PHP functions require you to use brackets when calling them.
380No, because all PHP functions require you to use brackets when calling them, except echo, which only requires brackets when you use more than one argument.
381Q3: You cannot use empty() as a callback for the usort() function.
382True
383False
384Q4: What is the output of the following code?
385<?php
386(function Hello() {
387 echo 'Hello World!';
388})();
389Nothing
390Hello World
391An error message and 'Hello World'
392Just an error message
393Q5: What is the output of the following code?
394<?php
395declare(strict_types=1);
396function multiply(float $a, float $b): float {
397 return (double)$a * (double)$b;
398}
399$six = multiply(2, 3);
400echo gettype($six);
401int
402double
403float
404This generates a TypeError
405Q6: What is the output of the following code?
406<?php
407function complicated($compulsory, ...$extras) {
408 echo 'I have ' . func_get_args() . ' arguments';
409}
410complicated(1,2,3,4);
4111
4122
4134
414This produces a notice error
415Q7: How would you refer to the parameter with the value cat in the following function?
416<?php
417function complicated($compulsory, ...$extras, $animal) {
418 // I want to reference the variable with the value 'cat'
419}
420complicated(1,2,3,'cat');
421$animal
422$extras[1]
423$extras[2]
424This produces an error
425Q8: What will this code output?
426<?php
427if (!is_callable(function(){echo 'Hello';})) {
428 function sayHello() {
429 echo 'World!';
430 }
431}
432sayHello();
433Hello
434World!
435Hello World!
436This produces an error
437Q9: What will this code output?
438<?php
439namespace A;
440function Hello() { echo __NAMESPACE__; }
441namespace B;
442function Hello() { echo __NAMESPACE__; }
443namespace C;
444\B\Hello();
445A
446B
447C
448This produces an error; functions cannot be namespaced
449Q10: What will this code output?
450<?php
451namespace A;
452$closure = function() { echo __NAMESPACE__; };
453namespace B;
454$closure = function() { echo __NAMESPACE__; };
455namespace C;
456$closure();
457A
458B
459C
460This produces an error; the closure is not defined in namespace C
461This produces an error; functions and closures cannot be namespaced
462Footnotes
4631
464We deal with this sort of error in Chapter 11 on error handling.
465
4662
467https://secure.php.net/manual/en/migration56.new-features.php
468
4693
470https://php.net/manual/en/class.closure.php
471
4724
473https://php.net/manual/en/language.types.callable.php
474
475