Funkcie v PHP

V predchádzajúcom článku sme si ukázali, ako si “rozbehat” PHP-čko, a predstavili niektoré základné prvky. V tejto časti by sme ti chceli doplniť vedomosti o funkciách – ako ich vytvárať, volať a spracovávať ich výstup. Popíšeme si klasické funkcie, ale aj anonymné a aj tzv. arrow funkcie.

Funkcia je základný stavebný prvok programu. Je to „podprogram”, ktorý nám umožňuje  vykonať postupnosť príkazov. Funkcia spravidla dostane na vstupe niekoľko vstupných argumentov (alebo aj žiadny) a môže vrátiť nejakú hodnotu alebo len vykonať príkazy. Funkcie nám pomôžu znovu použiť rovnaký kód na viacerých miestach (môžeme to nazvať aj ako princíp DRY – Don’t repeat yourself). Takto si svoj program vieš sprehľadniť a rozdeliť na menšie funkčné časti. Funkcie máme vstavané (built-in) a užívateľsky definované (user defined).

Funkcia sa skladá z niekoľkých častí, a to:

  • názov (meno) –  pomenovanie funkcie, ktoré by malo vystihovať jej úlohu – krátke a výstižné (pozri si PHP: Rules – Manual)

  • vstupné argumenty – zoznam vstupných argumentov, ktoré sú potrebné pre správny beh funkci



  • telo funkcie – postupnosť príkazov, ktoré vykonajú požadovanú úlohu



  • návratová hodnota – funkcia môže vrátiť výstupnú hodnotu pomocou príkazu return



  • popis funkcie – popis funkcie, jej parametrov, návratovej hodnoty


Volať funkciu môžeme len vtedy, ak je pred volaním definovaná (vytvorená), tzn. že náš program ju už pozná. Funkciu vytvoríme pomocou kľúčového slova function. 

Príklad funkcie, ktorá nerobí nič. Má meno, žiadne vstupné argumenty, prázdne telo, nevracia nič (žiadnu návratovú hodnotu):

<?php
function nothing()
{}

alebo môžeme vyskúšať niečo zmysluplné. napr.:

<?php
function content_of_circle_using_radius($radius)
{ 
    return M_PI * $radius ** 2;
}

Táto funkcia má meno, vstupný argument, telo aj návratovú hodnotu. Takto definovaná funkcia (ako už aj z názvu vyplýva) nám vráti obsah kruhu podľa zadaného argumentu (mal by to byť polomer). Z definície to vieme logicky vyvodiť.

V programe ale môžu vzniknúť situácie, kedy funkcia nevráti očakávaný výsledok. Napr. ak $radius = ‚xXx‘; prinajlepšom to vyhodí výnimku (Exception – od php v8.0) alebo vráti 0. A čo návratová hodnota? V tomto prípade je to stále float, no ak nepoznáme (nevidíme) telo funkcie, môže to byť čokoľvek. Nemali by sme sa teda na float spoliehať, ale mali by sme si to po volaní funkcie aj ošetriť, aby sa nám program správal podľa očakávania.

 Môžeme tomu trošku predísť a doplniť definíciu funkcie o typy vstupných argumentov a typ návratovej hodnoty – popísať to v popise funkcie. Aby nám to aj interpreter overoval a vyhodnocoval, zapneme tzv. strict mode. Na začiatok súboru vložíme declare(strict_types=1). V striktnom režime PHP očakáva hodnoty s typom zhodným s cieľovými typmi. Ak sa vyskytne nesúlad, PHP vyvolá výnimku (TypeError). Teda: 

<?php
declare(strict_types=1);
/**
* @param float $radius radius, should be positive value
*
* @return float
*/
function content_of_circle_using_radius(float $radius): float
{
   return M_PI * $r ** 2;
}

Teraz už z definície vieme, že argument musí byť float a návratová hodnota bude tiež float. Na to sa už môžeme určite spoľahnúť.

Pri vytváraní funkcií je niekedy vhodné, aby sme argumentu nastavili východiskovú (default) hodnotu. Používa sa to najmä vtedy, ak predpokladáme, že daný argument bude vo väčšine prípadov rovnaký. Pre definovanie východiskovej hodnoty ju jednoducho priradíme k premennej.

<?php
declare(strict_types=1);
function how_you_feel(string $name, int $age = 25): string
{
   return $name . " feels " . $age . " years old";
}
how_you_feel("Tomas"); // returns Tomas feels 25 years old
how_you_feel("Viktor", 43); // returns Viktor feels 43 years old

Týmto sme si vlastne povedali, že daný argument je nepovinný a pri volaní funkcie ho môžeme vynechať. Tu ale treba dbať na to, že nepovinné argumenty, resp. argumenty s default hodnotami, by mali byť umiestnené za povinné argumenty. Od PHP v8.0.0 je “deprecated” (zastarané) deklarovať nepovinné argumenty pred povinnými. Je tam ale výnimka – ak má nepovinný argument určený typ a má deklarovanú default hodnotu null, vtedy je možné ho umiestniť pred povinný argument. Teda je v podstate nutné ho pri volaní zadať, ale môžeme použiť hodnotu null

<?php
declare(strict_types=1);
function foo($bar, $baz) {} // all ok
function foo($bar, $baz = null) {} // all ok
function foo($bar = null, $baz){} // deprecated as of PHP v8.0.0
function foo(int $bar = null, $baz){} // all ok

Pri deklarácii funkcie môžeme ďalej použiť “…” tzv. spread operator. Tento zápis nám určí, že funkcia môže mať variabilný počet argumentov.

<?php
declare(strict_types=1);
function foo($bar, ...$baz) {}
foo(1, 2); // valid call
foo(1, 2, 3); // valid call

Argument $baz je pole argumentov, tzn. že v tele funkcie môžeme argument prechádzať ako klasické pole napr. pomocou foreach. Môžeme dokonca nastaviť typ pre dane variabilné argumenty, 

<?php
declare(strict_types=1);
function foo($bar, int ...$baz) {}
foo(1, 2); // valid call
foo(1, 2, 3); // valid call
foo(1, 2, "3"); // raise TypeError

„…“ môžeme použiť nielen pri definovaní funkcie, ale aj pri volaní.

<?php
declare(strict_types=1);
function foo($bar, $baz) {}
$args = [1, 2];
foo(...$args); // $bar = 1, $baz = 2

Všetky definície funkcie museli doteraz pri volaní striktne dodržiavať poradie argumentov. Ak by sme mali napr. funkciu s troma nepovinnými parametrami a chceli by sme zmeniť iba ten posledný, musíme nastaviť aj ostatné.

To sa ale príchodom PHP v8.0 zmenilo. Zaviedli sa pomenované argumenty ako rozšírenie existujúcich pozičných parametrov. Pomenované argumenty umožňujú odovzdávať argumenty funkcii na základe názvu parametra, a nie na základe pozície parametra. To robí argumenty nezávislými na poradí a umožňuje ľubovoľne preskakovať predvolené hodnoty.

<?php
declare(strict_types=1);
function foo(
    int $bar = null,
    int $baz = 1,
    string $qux = '',
    string $quux = '',
) {}
foo(qux: 'some string'); // named argument
// $bar = null, $baz = 1, qux = 'some string', $quux = ''
foo(null, 1, 'some string'); // old style
// $bar = null, $baz = 1, qux = 'some string', $quux = ''

Od verzie PHP v8.1 vieme tiež použiť aj “…”(spread operator). Na poradí argumentov v poli nezáleží.

<?php
declare(strict_types=1);
$args = [
    'baz' => 2,
    'bar' => 4
];
foo(...$args, quux: 'some string');
// $bar = 4, $baz = 2, qux = '', $quux = 'some string'

Povedali sme si ako máme predávať argumenty do funkcie. Bolo to hodnotou, default hodnotou, spread operatorom a pomenovanými argumentami. Poslednou možnosťou je predávanie argumentov pomocou referencie. To znamená, že ak zmeníme argument v tele funkcie, zmení sa aj samotný argument mimo tela funkcie, v rodičovskom (parent) scope. Stačí, ak pred argument v definícii funkcie pridáme znak ampersand (&).

PHP štandardne predáva argumenty hodnotou, avšak ak argument je inštancia objektu, ten je predaný referenciou.

<?php
declare(strict_types=1);
$a = 1
function modify(int &$a): int { return $a++; }
$result = modify($a);
echo $result; // 1
echo $a;      // 2

V úvode sme uviedli, že funkcia sa skladá z niekoľkých častí. Jednou z nich je názov. Funkcia musí mať názov, ak ju chceme volať. Rozumné, avšak existujú aj tzv. anonymné funkcie. Majú všetko, čo bolo uvedené, no nemajú meno/názov. Sú známe ako closures. Využívajú sa hlavne ako callable argumenty funkcii. Ale majú aj ďalšie využitie, keďže sú implementáciou Closure triedy.

Ako callable argument ich môžeme použiť napr. pri funkcii usort. Tá zotriedi vstupné pole podľa návratovej hodnoty z anonymnej funkcie.

<?php
declare(strict_types=1);
$arr = [
    'baz' => 2,
    'bar' => 4,
    'qux' => 1
];
usort($arr, function($a, $b) {
    if ($a === $b) return 0;
    return ($a < $b) ? -1 : 1;
});

Anonymné funkcie majú vlastný lokálny scope, takže nie je možné použiť premennú mimo telo funkcie. Ak by sme predsa len potrebovali pristúpiť k premennej definovanej mimo telo funkcie, použijeme use operator.

<?php
declare(strict_types=1);
$message = "I am compare %s and %s".PHP_EOL;
$anonym = function($a, $b) use ($message) {
    echo sprintf($message, $a, $b);
    if ($a === $b) return 0;
    return ($a < $b) ? -1 : 1;
};
$arr = [4, 6, 8, 0];
usort($arr, $anonym);

Ak by sme potrebovali jednoduchú funkciu napr. násobiť konštantou každý prvok poľa, môžeme použiť tzv. arrow function. Definuje sa jednoducho a to fn(argumenty) => expr. Má rovnakú funkčnosť, ako anonymná funkcia, ale premenné vieme použiť z rodičovského (parent) scope. 

<?php
declare(strict_types=1);
$arr = [4, 6, 8, 0];
$multiplier = 5;
$result = array_map(fn($x) => $x * $multiplier, $arr);
// same as 
$result = array_map(
              function($x) use ($multiplier) { 
                  return $x * $multiplier;
              },
              $arr
          );

Ďalší blog nášho PHP seriálu sa bude venovať generátorom. Dovtedy, kódeniu zdar :)