Tutorials Logic, IN info@tutorialslogic.com

PHP Functions: Parameters, Returns, Closures, and Side Effects

PHP Function Contracts

PHP functions define callable behavior with local scope, explicit parameters, named and variadic arguments, return contracts, closures, arrow functions, references, and multiple callable forms.

Strong function design makes inputs, mutation, timing, errors, and effects visible, maps external dispatch through allowlists, and tests boundaries without depending on mutable global state.

Parameters and Returns

Calculate a Line Total

Calculate a Line Total
<?php
function lineTotal(float $price, int $quantity = 1): float
{
    return $price * $quantity;
}

echo lineTotal(125.50, 2);
Output
251

The caller receives a number and decides whether to print, format, store, or compare it.

Pure Functions

A pure function returns the same result for the same input and does not modify outside state. Pure functions are straightforward to test and combine.

Format a Slug

Format a Slug
<?php
function slugify(string $title): string
{
    $normalized = strtolower(trim($title));
    $replaced = preg_replace('/[^a-z0-9]+/', '-', $normalized);

    return trim($replaced ?? '', '-');
}

echo slugify('  PHP Function Guide  ');
Output
php-function-guide

Closures and Arrow Functions

A closure is a function value passed to another function. Traditional anonymous functions import outer variables with use. Arrow functions automatically capture referenced outer values by value.

Use a named function for behavior reused across the application and a short closure for behavior local to one operation.

Map Prices with Tax

Map Prices with Tax
<?php
$taxRate = 0.18;
$prices = [100, 250];
$withTax = array_map(
    fn (float $price): float => $price * (1 + $taxRate),
    $prices
);

echo implode(', ', $withTax);
Output
118, 295

Variadic Arguments

A variadic parameter collects remaining arguments into an array and must be final. The spread operator unpacks an array into arguments.

Sum Any Number of Scores

Sum Any Number of Scores
<?php
function totalScores(int ...$scores): int
{
    return array_sum($scores);
}

$values = [8, 9, 10];
echo totalScores(...$values);
Output
27

Side Effect Boundaries

Behavior Preferred design
Calculate a total Return the number
Format a label Return the string
Write a file Name the I/O action and report failure
Send a response Keep framework output at the controller boundary

Declaration and Scope

A user-defined function creates a reusable callable with parameters, a body, and an optional return contract. Function names normally live in a namespace and are case-insensitive when called, but code should preserve declared case. Use names that describe an outcome or action rather than implementation mechanics.

Functions have their own local variable scope. Variables from the outer file are not automatically available inside a named function. Pass dependencies and data as parameters; broad `global` access hides inputs, creates test coupling, and makes one request affect another in long-running processes.

A function declaration can be conditionally defined, but the definition occurs only when execution reaches that branch. Avoid conditional global function definitions in application code because call availability then depends on runtime order. Classes or explicit callable registration provide clearer extension points.

PHP does not support overloading user-defined functions by signature. A second function with the same resolved name causes a redeclaration error. Model variants through optional parameters, value objects, distinct names, or polymorphic collaborators according to the domain.

  • Name functions from their observable result or action.
  • Pass dependencies instead of reading mutable globals.
  • Avoid runtime-conditional function declarations.
  • Use explicit APIs instead of signature overloading assumptions.

Parameters and Arguments

Parameters are declared by the function; arguments are supplied by the caller. Required parameters should precede optional parameters, and defaults must be valid constant expressions under the supported PHP version. Use defaults only when they represent a real domain default, not to conceal missing required input.

Named arguments bind by parameter name, can skip optional parameters, and make some calls clearer. They also make public parameter names part of the compatibility surface. Do not rename parameters casually in public functions, interfaces, or inherited methods when callers may use named arguments.

A variadic parameter gathers remaining arguments into an array and must be last. Argument unpacking expands arrays or Traversable values under supported rules. Prefer an iterable or options object when a call can accept an unbounded collection or many independent switches.

Arguments are passed by value by default using PHP copy-on-write semantics for arrays and strings; objects use object handles. An ampersand parameter allows the function to rebind the caller variable. Reserve by-reference parameters for APIs whose mutation is unmistakable, and never add `&` as a performance superstition.

  • Keep required inputs explicit and ordered first.
  • Treat public parameter names as named-argument API.
  • Use variadics for a coherent repeated argument type.
  • Use references only for deliberate caller mutation.

Return Contracts

A function without an explicit return statement returns null. Every branch should honor one documented contract so callers do not need to guess whether false, null, an empty array, an object, or an exception signals failure. Use a value object or enum-backed result when several outcomes need data.

Return type declarations enforce the returned value under PHP type rules. Union and nullable types can represent legitimate alternatives, while `never` states that normal control never returns. Avoid `mixed` when a narrower contract is knowable; it pushes validation into every caller.

Returning by reference is a specialized feature and requires both a reference-return declaration and compatible caller behavior. It exposes internal storage to mutation and is rarely appropriate for application APIs. Return values or dedicated mutation methods instead.

Generators return a Generator object immediately and use `yield` to produce values lazily; they do not follow an ordinary one-value return flow. Keep generator, iterable, and scalar-return contracts distinct in names, types, documentation, and tests.

  • Keep all normal paths within one return contract.
  • Use the narrowest useful declared return type.
  • Avoid exposing internal state through reference returns.
  • Distinguish lazy generators from eager value returns.

Closure Capture and Lifetime

An anonymous function is a Closure object and can capture outer variables through `use`. Capture by value takes the value at closure creation under PHP semantics; capture by reference observes and can change the outer variable. Make reference capture visible because it creates shared mutable state.

Arrow functions use concise `fn` syntax, contain one expression, and automatically capture referenced outer variables by value. They are excellent for short transformations and predicates, but a named closure is clearer when behavior needs statements, error handling, or explanation.

A closure declared `static` is not bound to an object through `$this`. Use static closures for callbacks that do not need instance state; this documents independence and avoids accidental object retention in long-lived callback registries.

Closures can retain large objects or service containers beyond the intended request or job lifetime. Capture only required values, unregister stored callbacks, and be careful in workers where process memory survives many jobs.

  • Choose capture by value or reference deliberately.
  • Use arrows for one clear expression.
  • Declare callbacks static when they need no instance.
  • Release long-lived closures that retain large state.

Callables and Dispatch

PHP callables may be closures, function-name strings, method arrays, invokable objects, or first-class callable objects. Prefer first-class callable syntax or explicit closures when refactoring support and static analysis matter. Validate configured callables before registering them.

Variable functions call the name stored in a variable, but language constructs are not ordinary functions and cannot all be invoked this way. Never dispatch directly from a request parameter. Map external action names to an allowlisted callable registry and authorize the action separately.

An invokable object implements `__invoke` and can carry typed dependencies and configuration. It works well for command handlers, validators, and middleware where a bare closure would hide construction and test setup. Keep invocation focused on one responsibility.

Callbacks should document timing, repeatability, argument shape, return use, exception behavior, and ownership. A caller needs to know whether a callback runs immediately, later, once, per item, or until it returns a stop signal.

  • Prefer refactorable first-class callables and closures.
  • Dispatch external action names through an allowlist.
  • Use invokable objects for dependency-bearing handlers.
  • Document callback timing and failure behavior.

Function Design and Tests

Separate pure calculations from I/O, time, randomness, environment, and global state. Pass effectful collaborators behind narrow interfaces or callables. A pure core with a small imperative shell is easier to test and reuse than a function that reads every dependency internally.

Validate input at the boundary that understands it, then keep internal functions typed around valid values. Do not repeat the same defensive checks in every helper or accept raw request arrays deep inside the domain. Convert to named values once.

Test normal input, each boundary, default and named calls, invalid types, thrown dependencies, reference mutation where intentional, closure capture timing, and callback failure. Verify side effects and output, not private helper call counts alone.

Profile before optimizing function structure. Database round trips, filesystem access, network calls, repeated parsing, and poor algorithms dominate ordinary call overhead. Keep OpCache and production configuration in mind, but preserve readable contracts first.

  • Keep pure decisions separate from effectful adapters.
  • Convert raw input into valid domain values once.
  • Test defaults, named calls, captures, and dependency failures.
  • Optimize measured I/O and algorithms before call syntax.
Before you move on

Mastery Check

4 checks
  • Pass data and dependencies instead of reading globals.
  • Keep every branch within one return contract.
  • Choose closure capture and callback lifetime deliberately.
  • Test input boundaries, effects, and dependency failures.

Function Design Check

0 of 2 checked

Q1. Why return a calculated value instead of echoing inside the function?

Q2. Where must a variadic parameter appear?

Function Contract Boundary

  • Coercion hides invalid input

    Without strict types, scalar arguments may be coerced in surprising ways. Validate external data first and use explicit parameter and return types for internal contracts.

Try this next

Extract Useful Behavior

0 of 2 completed

  1. Accept subtotal and percentage, validate the range, and return the discounted total.
  2. Use array_filter() and an arrow function to keep scores at or above a configurable pass mark.
Browse Free Tutorials

Explore 500+ free tutorials across 20+ languages and frameworks.