Functions

PHP · Lesson 14 of 50 8 min read Updated

As programs grow, you find yourself writing the same steps again and again: work out GST, format a price, check a phone number. A function lets you write those steps once, give them a name and reuse them anywhere.

In this lesson you will learn to write your own functions with modern typed parameters, call them in flexible ways with default and named arguments, and use the shorter function forms that modern PHP code relies on: closures, arrow functions and first-class callables.

Defining and calling a function

You define a function with the function keyword, a name, a list of parameters in brackets and a body in curly braces. The return statement sends a value back to the caller:

PHP
<?php
declare(strict_types=1);

function priceWithGst(int $price, int $gstRate): float
{
    return $price + $price * $gstRate / 100;
}

echo priceWithGst(1000, 18), "\n";
echo priceWithGst(2499, 5), "\n";
Output
1180
2623.95

The parts of the definition:

  • priceWithGst is the name. Function names follow the same rules as variable names, without the $. Use a verb or a clear description, in camelCase.
  • int $price, int $gstRate are the parameters. Each has a type and a name. The values you pass when calling, 1000 and 18, are called arguments.
  • : float after the brackets is the return type: this function always returns a float.
  • return ends the function and hands back the value.

With declare(strict_types=1); at the top of the file, calling priceWithGst('1000', 18) throws a TypeError instead of quietly converting the string. Types document what a function expects and catch mistakes early.

A function that returns nothing has the return type void. You may also meet never, for functions that always stop the script, for example by throwing an exception.

Return values, not echo

A common beginner habit is to echo inside every function. It is usually better to return the value and let the caller decide what to do with it: print it, store it in a database, or pass it to another function. A function that returns a value can be reused in many places; a function that prints can only print.

If a function reaches its end without a return, it returns null.

Default values and named arguments

A parameter can have a default value, which is used when the caller leaves that argument out. Parameters with defaults must come after the required ones.

Named arguments let you pass a value by parameter name instead of position. They make calls easier to read, and they let you skip optional parameters you do not need:

PHP
<?php
declare(strict_types=1);

function bookTicket(string $from, string $to, string $class = 'Sleeper', int $passengers = 1): string
{
    return "$passengers x $class ticket(s) from $from to $to";
}

echo bookTicket('Rohtak', 'Jaipur'), "\n";
echo bookTicket('Rohtak', 'Jaipur', '3A'), "\n";
echo bookTicket('Rohtak', 'Jaipur', passengers: 4), "\n";
echo bookTicket(to: 'Pune', from: 'Lucknow', class: '2A'), "\n";
Output
1 x Sleeper ticket(s) from Rohtak to Jaipur
1 x 3A ticket(s) from Rohtak to Jaipur
4 x Sleeper ticket(s) from Rohtak to Jaipur
1 x 2A ticket(s) from Lucknow to Pune

The third call skips $class and sets only $passengers. The last call passes everything by name, in any order. Named arguments also work with PHP's own functions, for example number_format(1299.5, decimals: 2).

Nullable and union types

Sometimes a parameter can be one of several types:

  • A nullable type such as ?string accepts a string or null. It is often paired with a default of null for optional values.
  • A union type such as int|float accepts any of the listed types.
PHP
<?php
declare(strict_types=1);

function applyCoupon(int|float $amount, ?string $coupon = null): int|float
{
    if ($coupon === 'FEST10') {
        return $amount * 0.9;
    }
    return $amount;
}

var_dump(applyCoupon(1000));
var_dump(applyCoupon(1000, 'FEST10'));
var_dump(applyCoupon(499.5));
Output
int(1000)
float(900)
float(499.5)

Without a coupon, the amount comes back unchanged with its original type. Use these types when a value truly can vary. If you find yourself writing a union of many types, the function is probably doing too much.

Variadic parameters

A variadic parameter, written with three dots, collects any number of arguments into an array. The same three dots used in a call do the opposite: they spread an array into separate arguments.

PHP
<?php
declare(strict_types=1);

function totalMarks(int ...$marks): int
{
    return array_sum($marks);
}

echo totalMarks(78, 91, 66), "\n";

$termMarks = [88, 92, 79, 85];
echo totalMarks(...$termMarks), "\n";
Output
235
344

Inside the function, $marks is an ordinary array, and the type int applies to every item.

Variable scope

Variables created inside a function are local: they exist only while the function runs, and the function cannot see variables from outside. This keeps functions independent. If a function needs a value, pass it as an argument; if the caller needs a result, return it.

Closures and arrow functions

A function does not always need a name. An anonymous function (also called a closure) can be stored in a variable or passed to another function. To use an outside variable inside it, list that variable after use. The value is copied at the moment the closure is created.

An arrow function, written fn(...) => expression, is a shorter form for closures that return one expression. It captures outside variables automatically, again by value.

PHP
<?php
declare(strict_types=1);

$shop = 'Meera Sweets';
$greet = function (string $customer) use ($shop): string {
    return "Welcome to $shop, $customer!";
};
$shop = 'Changed later';
echo $greet('Rohan'), "\n";

$gstRate = 18;
$withGst = fn(int $price): float => $price + $price * $gstRate / 100;
echo $withGst(500), "\n";
Output
Welcome to Meera Sweets, Rohan!
590

The greeting still says "Meera Sweets", because use copied the value before $shop changed. Notice the semicolon after the closing brace of the closure: the whole thing is an assignment statement. Closures and arrow functions are most useful as callbacks, functions you hand to another function such as array_map() or usort(), which you will use in Array Functions.

First-class callable syntax

Writing a function name followed by (...) creates a closure from that function without calling it. This is called the first-class callable syntax, added in PHP 8.1:

PHP
<?php
declare(strict_types=1);

$shout = strtoupper(...);
echo $shout('pune'), "\n";

$cities = [' Jaipur', 'Kochi ', ' Lucknow '];
$clean = array_map(trim(...), $cities);
echo implode(', ', $clean), "\n";
Output
PUNE
Jaipur, Kochi, Lucknow

trim(...) is a real closure object, so your editor and PHP can check it. You will see this syntax with the PHP 8.5 pipe operator from Operators, and it also works with methods, as in $order->total(...).

Common mistakes

  • Forgetting return. The function then returns null, and the caller gets nothing useful.
  • Echoing instead of returning. Return values so the function can be reused.
  • Using an outside variable without passing it. Inside a function it is undefined. Pass it as an argument, or use use for closures.
  • Putting optional parameters before required ones. PHP shows a deprecation message and treats the optional one as required. Put defaults last.
  • Misspelling a named argument. bookTicket(passenger: 2) fails with "Unknown named parameter $passenger".

Key points

  • Define functions with typed parameters and a return type, and start files with declare(strict_types=1);.
  • Return values rather than printing them; a function without return gives null.
  • Default values make parameters optional; named arguments pass values by name in any order.
  • ?string accepts a string or null; int|float accepts either type.
  • ...$items collects many arguments into an array, and ...$array spreads an array into arguments.
  • Closures capture outside variables with use; arrow functions fn capture them automatically.
  • strlen(...) turns any function into a closure you can pass around.

Exercise

Write a function emi(int $principal, float $annualRate = 10.5, int $months = 12): float that returns the monthly instalment using the formula P * r * (1 + r) ** n / ((1 + r) ** n - 1), where r is the annual rate divided by 1,200 and n is the number of months. Round the result to 2 decimal places. Call it for a ₹60,000 loan with the default rate and months, and then for the same loan over 24 months using a named argument.

Show solution
PHP
<?php
declare(strict_types=1);

function emi(int $principal, float $annualRate = 10.5, int $months = 12): float
{
    $r = $annualRate / 1200;
    $growth = (1 + $r) ** $months;

    return round($principal * $r * $growth / ($growth - 1), 2);
}

echo emi(60_000), "\n";
echo emi(60_000, months: 24), "\n";
Output
5288.92
2782.56

The second call skips $annualRate and sets only $months by name, so the default rate of 10.5% is still used. Storing (1 + $r) ** $months in a variable avoids working it out twice.

Quick Quiz

1. What does a function return if it has no return statement?

2. Which call sets only $passengers and keeps the default for $class?

3. What does ?string $coupon = null mean?

4. How does an arrow function fn get access to outside variables?

Learn PHP with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call