PHP 8.2 - sprawdź co nowego!

Funkcje > Lekcja 3

Argumenty funkcji

Argumenty to dane, które chcemy przesłać do wnętrza naszej funkcji. Definiujemy je w momencie deklaracji funkcji, w nawiasach, oddzielając przecinkami. Przypisujemy im zmienne, których następnie możemy użyć wewnątrz funkcji. Z kolei przy wywołaniu funkcji musimy podać ich wartości.

<?php
function showNumbers($number1, $number2)
{
    echo $number1 . ' ' . $number2;
}
showNumbers(4, 3); // result: 4 3
showNumbers(23, 7); // result: 23 7

Teraz może zacząłeś zastanawiać się: po co nam te argumenty? Słusznie, to trafne pytanie. Rzecz jest w tym, że zmienne zadeklarowane poza funkcją nie są w niej widoczne. Podobnie zmienne stworzone w funkcji - nie są widoczne poza nią. Jeśli więc chcemy przekazać coś do funkcji, musimy użyć argumentów. Nazywamy to zasięgiem zmiennych.

Jeśli jeszcze coś jest niejasne, to z pewnością zrozumiesz po obejrzeniu przykładu:

<?php
$number1 = 5;
function showNumbers()
{
    var_dump($number1); // result: Notice: Undefined variable: number1
    $number2 = 8;
}

showNumbers();
var_dump($number2); // result: Notice: Undefined variable: number2

Zmienne globalne

Zmienne globalne, nazywane też potocznie globalami, umożliwiają inne obejście tego problemu. Wskazujemy wartości, które chcemy widzieć w funkcji i „magicznie” stają się one widoczne. Jest to jednak bardzo zły sposób. Zagrożeń w związku ze zmiennymi globalnymi jest sporo, bo tracimy wtedy pełną kontrolę i przejrzystość tego, co się w kodzie dzieje.

<?php
$number1 = 5;
function showNumbers()
{
    global $number1, $number2;
    var_dump($number1); // result: int(5)
    $number2 = 8;
}

showNumbers();
var_dump($number2); // result: int(8)

Przykład pokazuję, abyś wiedział, co to jest, gdy spotkasz taki zapis. Podkreślam jednak raz jeszcze, że odradzam używanie globali, chyba że naprawdę musisz i jesteś świadomy tego, co robisz oraz jakie może to przynieść konsekwencje.

Wracamy do naszego celu i kodu z poprzedniej lekcji. Poprawmy go tak, aby zmienne (liczby) do działań można było przekazać z zewnątrz, przez argumenty.

<?php

$operation = '+';
$number1 = 8;
$number2 = 4;

function operationAddition($number1, $number2)
{
    echo $number1 + $number2;
}

function operationSubtraction($number1, $number2)
{
    echo $number1 - $number2;
}

function operationMultiplication($number1, $number2)
{
    echo $number1 * $number2;
}

function operationDivision($number1, $number2)
{
    echo $number1 / $number2;
}

switch ($operation) {
    case '+':
        operationAddition($number1, $number2);
        break;
    case '-':
        operationSubtraction($number1, $number2);
        break;
    case '*':
        operationMultiplication($number1, $number2);
        break;
    case '/':
        operationDivision($number1, $number2);
        break;
}

Dla jasności: zmienne, których wartości podstawiamy oraz zmienne tworzone wewnątrz funkcji jako argumenty, nie muszą nazywać się tak samo. W powyższym przykładzie to czysty przypadek.

Wartość domyślna

Przy wywołaniu funkcji musimy podać wartości dla wszystkich zadeklarowanych argumentów. Jeżeli tego nie zrobimy, zostanie zwrócony błąd. Istnieje jednak możliwość zdefiniowania wartości domyślnych dla każdego (lub tylko części) z nich - wtedy rzecz jasna błędów w przypadku nieprzekazania wartości nie będzie.

Domyślną wartość możesz skojarzyć z przypisywaniem wartości zmiennych, gdyż definiujemy ją tak jak przypisanie, przy użyciu =. Istnieje tylko jedna istotna uwaga: robimy to od końca (czyli od ostatniego, od prawej strony). Dlaczego? Ustawiamy od końca argumenty jako opcjonalne do wywołania funkcji, dzięki czemu w momencie ich podania interpreter wie, do których zmiennych je przekazać. Gdybyśmy mogli sobie ustawić taką wartość na przykład dla pierwszego argumentu, skąd interpreter miałby wiedzieć, który argument chcemy pominąć i użyć domyślnej wartości?

<?php
$number = 3;
function showNumbers($number1, $number2 = 1)
{
    echo $number1 . ' ' . $number2;
}
showNumbers($number); // result: 3 1
<?php
$number = 3;
function showNumbers($number1 = 1, $number2) // próbujemy powiedzieć, aby pierwszy argument miał wartość domyślną
{
    echo $number1 . ' ' . $number2;
}
showNumbers($number); // result: Fatal error: Too few arguments to function showNumbers()
// podaliśmy tylko pierwszy argument, a drugiego nie, pomimo tego, że jest obowiązkowy

Definiuj więc argumenty tak, aby te opcjonalne znajdowały się możliwie na końcu.

Named arguments

Jest to całkowita nowość, którą wprowadza php 8.0, choć możesz kojarzyć taki mechanizm z innych języków. Chodzi o to, że w momencie przekazywania (nie definiowania!) argumentów, możemy konkretnie określić, co chcemy przekazać. Aby to zrobić, przed wartością podajemy nazwę (stąd się wzięło named), ale musi być ona taka, jak w deklaracji funkcji. Ważne jest to, że tutaj nie możemy dodać niczego więcej, nie robimy deklaracji nowego argumentu, tylko po prostu wskazujemy, która wartość co ma oznaczać. Nie ma znaczenia także kolejność.

<?php
$number = 3;
function showNumbers($number1, $number2 = 1, $number3 = 2)
{
    echo $number1 . ' ' . $number2 . ' ' . $number3;
}
showNumbers(number2: 6, number3: 7, number1: 5); // result: 5 6 7

Co więcej, możemy też mieszać „klasyczne” argumenty z nazwanymi oraz pomijać opcjonalne.

<?php
$number = 3;
function showNumbers($number1, $number2 = 1, $number3 = 2)
{
    echo $number1 . ' ' . $number2 . ' ' . $number3;
}
showNumbers(5, number3: 7); // result: 5 1 7

W prostych przypadkach może to nie zrobi aż takiej różnicy, ale w funkcjach, które przyjmują wiele opcjonalnych argumentów, jest bardzo wygodne. Fajny przykład podawany jest na stronie PHP, z funkcją htmlspecialchars (służy do zamiany znaków używanych przez HTML, na odpowiedniki, które tylko się wyświetlą, a nie zostaną potraktowane jak właściwy kod - dokumentacja):

htmlspecialchars($string, ENT_COMPAT | ENT_HTML401, 'UTF-8', false);
// same as
htmlspecialchars($string, double_encode: false);

W pierwszym wywołaniu drugi i trzeci argument mają wpisaną wartość domyślną tylko po to, aby w ostatnim zmienić domyślne true na false. Wykorzystując named arguments, nie ma już takiej potrzeby - wystarczy podać tylko to, co naprawdę chcemy zmienić. Zapis krótszy, od razu wiadomo, że akurat ten argument potrzebujemy zmienić i wartości domyślnych przepisywać nie trzeba :)

Typy argumentów

Od php >=7 istnieje możliwość określenia typów argumentów, jakie mają zostać przesłane (dla ścisłości: wcześniej można było określać tylko klasę, gdy argumentem był obiekt - o tym później). Robimy to poprzez dodanie typu przed nazwą zmiennej w deklaracji funkcji. Dla przykładu tak:

<?php
function showNumbers(int $number1, int $number2)
{
    echo $number1 . ' ' . $number2;
}
showNumbers(4, 3); // result: 4 3
showNumbers(23, 7); // result: 23 7

Co się stanie, gdy podamy wartość innego typu, niż zadeklarowany? Pewnie spodziewasz się, że wyrzucony zostanie błąd, ale nie do końca tak jest. Zależy to od sytuacji. Gdy zadeklarujesz, że zmienna $number ma być typu int, a prześlesz tekst, to owszem, działanie kodu zakończy się błędem. Jednak, gdy do zadeklarowanego inta, spróbujesz przesłać na przykład wartość typu float, to kod zadziała bez zarzutu - interpreter sam wykona rzutowanie i zamieni liczbę zmiennoprzecinkową na całkowitą („obetnie” końcówkę).

<?php
function showNumber(int $number)
{
    echo $number;
}
showNumber(5); // result: 5
showNumber(5.7); // result: 5
showNumber('PHPDevs'); // result: Uncaught TypeError: Argument 1 passed to showNumber() must be of the type integer, string given

Gdybyśmy jednak chcieli precyzyjnie określić, że zmienna ma być takiego typu, jaki zadeklarowaliśmy, możemy to wymusić. Wystarczy na samym początku pliku użyć declare() i ustawić strict_types na 1. Kontrola typowania może pomóc w uniknięciu różnego rodzaju błędów, podobnie jak pokazywałem to na przykład przy operatorach i porównywaniu z typem, więc generalnie polecam jej używać.

<?php
declare(strict_types=1);
function showNumber(int $number)
{
    echo $number;
}
showNumber(5); // result: 5
showNumber(5.7); // result: Uncaught TypeError: Argument 1 passed to showNumber() must be of the type integer, float given
showNumber('PHPDevs'); // result: Uncaught TypeError: Argument 1 passed to showNumber() must be of the type integer, string given

Od php >=7.1 istnieje także możliwość dopuszczenia wartości null nawet wtedy, gdy mamy określony typ. Jest to możliwe poprzez dopisanie znaku zapytania ? przed danym typem.

<?php
function showNumber(?int $number)
{
    echo $number;
}
showNumber(8); // result: 8
showNumber(null); // result: nothing

Warto zauważyć, że ?int $number i int $number = null to coś innego. Pierwszy przypadek nie ma wartości domyślnej, więc null będzie dopuszczalny, ale i tak musi zostać przekazany jako argument, nie można go pominąć tak jak w zapisie drugim.

Wiele typów dla jednego argumentu

Od php 8.0 mamy dostępną dodatkową opcję. Możemy określić wiele typów dla danego argumentu. Jak do tej pory nie było to możliwe. Argument pozostawiało się bez określonego typu i często wspierano się specjalnym komentarzem PHPDoc (wspomniałem o nim słowo na początku) z elementem @param. Taki zapis nie ma realnego wpływu na działanie kodu, jest po prostu informacją dla programisty czy edytora.

<?php
/**
 * @param int|string $value
 */
function show($value)
{
    // ...
}

Teraz zapiszemy po prostu wiele typów w deklaracji i będzie to egzekwowane. Oddzielenie kolejnych typów wykonujemy również poprzez |:

<?php
function show(int|string $value)
{
    // ...
}

Podchodziłbym jednak do tego z rozwagą. Najpierw pomyśl jak przygotować kod tak, aby operował on na danych konkretnego typu, a w razie faktycznej konieczności dopuść ich więcej. Przyjmowanie wielu kompletnie innych danych może być kłopotliwe podczas użycia wartości. Z rozwagą to rzecz jasna nie oznacza, że wcale. Wszystko zależy od sytuacji.

Typ null, true i false

Co ciekawe, php 8.2 wprowadza możliwość użycia nowych typów, które jednocześnie są dokładnymi wartościami: true i false. Do tej pory można było wskazać jako typ po prostu bool (czyli prawda lub fałsz). Od tej wersji pojawiła się funkcjonalność zadeklarowania konkretnie jednej z tych wartości. Podobnie można także określić pustą wartość - null. Oczywiście nadal jest też możliwość łączenia tych typów z innymi poprzez |.

W teorii możemy więc posiadać choćby taką deklarację:

<?php
function show(int|null|false $value)
{
    // ...
}

Z nową wiedzą przeróbmy znów nasz kalkulator. Użycie wartości domyślnej raczej się nie sprawdzi, więc to tutaj odpuścimy (przećwiczysz w zadaniu domowym), ale już deklarację typu jak najbardziej możemy sobie dodać. Jako iż może zaistnieć potrzeba operowania na liczbach z przecinkiem, pozwólmy na wartości typu float.

<?php

function operationAddition(float $number1, float $number2) { /* ... */ }

function operationSubtraction(float $number1, float $number2) { /* ... */ }

function operationMultiplication(float $number1, float $number2) { /* ... */ }

function operationDivision(float $number1, float $number2) { /* ... */ }

// ...

Ćwiczenia

  1. Przygotuj funkcję showName wyświetlającą imię przekazane do niej jako argument.

    Przykładowe rozwiązanie

  2. Napisz funkcję prepareCart sprawdzającą, czy klient może złożyć zamówienie o cenie $price, gdy stan jego konta wynosi $money. Zadeklaruj odpowiednie typy dla argumentów. Trzeci argument $promotion powinien być typu logicznego (prawda lub fałsz). Wartością domyślną w przypadku jego nieprzekazania powinno być false. W przypadku gdy promocja ma obowiązywać (true) od łącznej ceny zamówienia ($price) należy odjąć 10. Jeśli klient może dokonać zamówienia, wyświetl komunikat OK.

    <?php
    $price = 22.90;
    $money = 50;
    // your code here
    

    Przykładowe rozwiązanie

Poprzednia lekcja Następna lekcja

Udostępnij

  • Facebook
  • Twitter

Komentarze