İçeriğe geç
academia.sh

Ders 06 / 14

Fonksiyonlar

PHP fonksiyonlarında argüman ve dönüş davranışı: fazla argümanın sinyalsiz yutulması, eksik ve bilinmeyen argüman hataları, varsayılan değer ve static, değer ve referans geçişi, dönüş tipi ve ...$x ile fazlalığın görünür kılınması.

Yayın Güncelleme

Bu derste

  • Kullanıcı fonksiyonuna verilen fazla argümanın sinyalsiz yutulduğunu, yerleşik fonksiyonlarda ise reddedildiğini ölçerek gösterir
  • Eksik, bilinmeyen ve çakışan adlandırılmış argümanların hangi hatayı verdiğini ayırt eder
  • Varsayılan değer ile static değişkenin çağrılar arasındaki davranış farkını doğrular
  • ...$x ile fazlalığın görünür kılınabileceğini ve dönüş tipinin zorlama davranışını sayarak gösterir
İçindekiler

Denetim Akışı dersi, karşılıksız bir dalın switch’te sessizce geçtiğini, match’te ise yürütmeyi durdurduğunu ölçtü. Bu ders aynı soruyu çağrılara soruyor: bir fonksiyona beklenenden fazla argüman verildiğinde, fazlalık nereye gidiyor, eksik verildiğinde ne oluyor? Bu dersin ölçü sınıfı sessizdir: kullanıcının yazdığı bir fonksiyona fazla verilen argüman, hiçbir bildirim üretmeden yok sayılır.

PHP’de function anahtar sözcüğüyle bildirilen fonksiyon, return ile bir değer döndürür; parametrelerine tip ve varsayılan değer yazılabilir. Programlama Temelleri kursunda (M01/K02) fonksiyon kavramı kurulmuştu; burada yalnız PHP’nin bu kavrama nasıl davrandığı ölçülüyor. Bir değişikliğin çağırana ve başkasına görünüp görünmediği sorusu M11/K03’te nesneler için ayrıca ölçülmüştü; bu derste yalnız skaler ve dizi değerleri için gerektiği kadar anılıyor.

Fazla Argüman

Kullanıcı fonksiyonuna parametre sayısından fazla argüman verilirse PHP fazlayı hata saymaz: fonksiyon yine çalışır ve fazla argümanlar yok sayılır. Yerleşik fonksiyonlar bu konuda katıdır. Aşağıdaki dosya iki parametreli bir fonksiyona üç argüman verir ve aynı şeyi yerleşik strlen() ile dener; hata işleyicisi bir sayaç tutar.

<?php
// fazla.php — kullanici fonksiyonu fazla argumani yutar, yerlesik reddeder
$bildirim = 0;
set_error_handler(function (int $seviye) use (&$bildirim): bool {
    $bildirim++;
    return true;
});

function iki_topla($a, $b)
{
    return $a + $b;
}

echo 'iki_topla(1, 2, 3):  ', iki_topla(1, 2, 3), "\n";

try {
    $uzunluk = strlen('abc', 'x');
    echo 'strlen(abc, x):     ', $uzunluk, "\n";
} catch (ArgumentCountError $hata) {
    echo 'strlen(abc, x):     ', get_class($hata), "\n";
}
echo 'bildirim: ', $bildirim, "\n";
iki_topla(1, 2, 3):  3
strlen(abc, x):     ArgumentCountError
bildirim: 0

KN43. iki_topla(1, 2, 3) ifadesi 3 döndürdü, yani üçüncü argüman toplamda yer almadı ve bunun için hiçbir şey söylenmedi: sayaç sıfır kaldı. Aynı fazlalığa yerleşik strlen() bir ArgumentCountError ile karşılık verdi. Bu asimetri bir tasarım kararıdır: kullanıcı fonksiyonlarında fazla argümanın hata sayılmaması, bir fonksiyonun sonradan fazladan bir isteğe bağlı parametre kazanabilmesine izin verir. Bedeli, bir çağrıdaki yazım hatasının yutulabilmesidir.

Eksik ve Bilinmeyen Argüman

Fazlalığın aksine eksik argüman bir hatadır: zorunlu bir parametre doldurulmazsa ArgumentCountError fırlar. PHP, argümanların konumla verilmesinin yanında ada göre de verilmesine izin verir (adlandırılmış argüman, named argument). Aşağıdaki dosya beş çağrı yapar: eksik, tam, adlandırılmış, bilinmeyen adlı ve zaten konumla doldurulmuş bir parametreyi yeniden adlandıran bir çağrı.

<?php
// eksik.php — eksik argumani ArgumentCountError, bilinmeyen adi Error reddeder
function selamla(string $ad, string $unvan = 'bay'): string
{
    return $unvan . ' ' . $ad;
}

$cagrilar = [
    'selamla()'                      => fn() => selamla(),
    "selamla('Ada')"                 => fn() => selamla('Ada'),
    "selamla(unvan: 'dr', ad: 'Ada')" => fn() => selamla(unvan: 'dr', ad: 'Ada'),
    "selamla('Ada', soyad: 'X')"     => fn() => selamla('Ada', soyad: 'X'),
    "selamla('Ada', ad: 'Ece')"      => fn() => selamla('Ada', ad: 'Ece'),
];
foreach ($cagrilar as $ad => $cagri) {
    try {
        $yazi = var_export($cagri(), true);
    } catch (Error $hata) {
        $yazi = get_class($hata);
    }
    printf("%-34s%s\n", $ad, $yazi);
}
selamla()                         ArgumentCountError
selamla('Ada')                    'bay Ada'
selamla(unvan: 'dr', ad: 'Ada')   'dr Ada'
selamla('Ada', soyad: 'X')        Error
selamla('Ada', ad: 'Ece')         Error

KN44. Eksik argüman ArgumentCountError verdi. Varsayılan değeri olan $unvan atlanınca 'bay Ada' üretildi. Adlandırılmış argümanlar sıradan bağımsız çalıştı: unvan önce yazıldığı hâlde 'dr Ada' çıktı. Fonksiyonda tanımlı olmayan soyad adı ve zaten konumla doldurulmuş ad parametresi birer Error fırlattı. Yani fazla konumlu argüman sessiz, fazla adlandırılmış argüman gürültülüdür; adlandırma, kullanıcıya fazla argümanın yutulmasından daha sıkı bir sözleşme verir.

Varsayılan Değer ve static

Varsayılan değer her çağrıda yeniden hesaplanır; yani bir parametrenin varsayılanı olarak yazılan boş dizi, her çağrıda yeni bir boş dizidir ve önceki çağrının eklediği elemanları taşımaz. Bir fonksiyon içinde static ile bildirilen değişken ise çağrılar arasında yaşar. Aşağıdaki dosya iki davranışı yan yana koyar.

<?php
// varsayilan.php — varsayilan her cagrida yenilenir, static cagrilar arasinda yasar
function ekle(int $eleman, array $liste = []): array
{
    $liste[] = $eleman;
    return $liste;
}

function say(): int
{
    static $sayac = 0;
    $sayac++;
    return $sayac;
}

echo 'ekle(1):  ', count(ekle(1)), "\n";
echo 'ekle(2):  ', count(ekle(2)), "\n";

say();
say();
echo 'say():    ', say(), "\n";
ekle(1):  1
ekle(2):  1
say():    3

KN45. ekle iki çağrıda da bir eleman döndürdü: varsayılan boş dizi her çağrıda yenilendi. say üçüncü çağrıda 3 verdi: static değişken fonksiyon her bittiğinde silinmedi. İkisi aynı ayrımı iki yönden gösterir: parametre ve yerel değişken çağrıya bağlıdır, static değişken fonksiyona bağlıdır. Hiçbiri bildirim üretmez.

Değer ve Referans

PHP argümanları varsayılan olarak değerle geçirir: fonksiyon kendi kopyası üzerinde çalışır ve çağıranın değişkenine dokunmaz. Parametre & ile bildirilirse fonksiyon çağıranın değişkenini değiştirir. Aşağıdaki dosya aynı değişikliği iki bildirimle yapar ve çağıranın dizisini sayar.

<?php
// deger.php — deger kopyalar, referans cagiranin degiskenini degistirir
function degerle(array $dizi, int $sayi): void
{
    $dizi[] = 99;
    $sayi++;
}

function referansla(array &$dizi, int &$sayi): void
{
    $dizi[] = 99;
    $sayi++;
}

$liste = [1, 2];
$n = 10;

degerle($liste, $n);
echo 'degerle:    ', count($liste), ' eleman, n: ', $n, "\n";

referansla($liste, $n);
echo 'referansla: ', count($liste), ' eleman, n: ', $n, "\n";
degerle:    2 eleman, n: 10
referansla: 3 eleman, n: 11

KN46. Değerle geçirilen çağrıdan sonra dizi iki eleman, sayı on kaldı: fonksiyon kopyayı değiştirdi. Referansla geçirilen çağrıdan sonra dizi üç eleman, sayı on bir oldu. Bu ayrımın nesneler için sonucu M11/K03’te ölçülmüştü; burada gözlenen, dil & yazılmadıkça bir değişikliğin çağırana görünmediğidir. Referans parametre, fonksiyonun imzasından okunur: çağrıda hiçbir işaret yoktur.

Dönüş Değeri

return yazılmayan bir fonksiyon null döndürür. Dönüş tipi bildirilmişse PHP dönen değeri o tipe zorlar, tıpkı parametrelerde olduğu gibi: Değişkenler dersindeki zorlama kuralı burada da işler. Aşağıdaki dosya dönüş davranışını dört durumda gösterir.

<?php
// donus.php — return yoksa null, donus tipi zorlar veya reddeder
function bos()
{
}

function sayiya(): int
{
    return '5';
}

function bozuk(): int
{
    return 'bes';
}

function bosluk(): void
{
}

echo 'bos():    ', var_export(bos(), true), "\n";
echo 'bosluk(): ', var_export(bosluk(), true), "\n";

$sonuc = sayiya();
echo 'sayiya(): ', var_export($sonuc, true), ' ', get_debug_type($sonuc), "\n";

try {
    bozuk();
} catch (TypeError $hata) {
    echo 'bozuk():  ', get_class($hata), "\n";
}
bos():    NULL
bosluk(): NULL
sayiya(): 5 int
bozuk():  TypeError

KN47. return olmayan fonksiyon null döndürdü, void bildirilmiş olan da. : int bildirimli fonksiyon '5' dizgisini sessizce 5’e çevirdi; 'bes' ise zorlanamadığı için TypeError verdi. Dönüş tipi bu yüzden parametre tipiyle aynı kuralı izler: sayısal dizgi zorlanır, değilse reddedilir.

Fonksiyonun Tanımlandığı An

PHP fonksiyonu bir dosya yürütülmeye başlamadan önce tanıyabilir, ama her fonksiyon için değil. Dosyanın en üst düzeyinde koşulsuz bildirilen fonksiyon, dosya okunurken tanınır ve bildirimden önceki satırda çağrılabilir. Bir if bloğunun içinde bildirilen fonksiyon ise yalnız o blok yürütüldüğünde var olur. Hiç bildirilmemiş bir fonksiyonun çağrısı ise bir Error fırlatır. Aşağıdaki dosya üç durumu function_exists() ile ölçer.

<?php
// tanim.php — ust duzey fonksiyon onceden bilinir, kosullu olan calisinca tanimlanir
echo 'onceden:  ', var_export(function_exists('ust_duzey'), true), "\n";
echo 'kosullu:  ', var_export(function_exists('kosullu'), true), "\n";
echo 'cagri:    ', ust_duzey(), "\n";

function ust_duzey(): string
{
    return 'calisti';
}

if (true) {
    function kosullu(): string
    {
        return 'x';
    }
}
echo 'sonra:    ', var_export(function_exists('kosullu'), true), "\n";

try {
    yok_boyle_bir_sey();
} catch (Error $hata) {
    echo 'tanimsiz: ', get_class($hata), "\n";
}
onceden:  true
kosullu:  false
cagri:    calisti
sonra:    true
tanimsiz: Error

KN48. Koşulsuz bildirilen ust_duzey bildirim satırından önce de vardı ve çağrı çalıştı. Koşullu bildirilen kosullu blok yürütülmeden önce yoktu, yürütüldükten sonra oldu. Tanımsız bir ad ise Error verdi, yani durdurur sınıfındadır. Sessiz olan, çağrının hangi satırda yapıldığına bağlı olarak aynı adın bir yerde çalışıp başka yerde çalışmamasıdır: fonksiyonun varlığı dosyanın yapısına, koşullu olanın varlığı yürütme sırasına bağlıdır.

Kapanış ve Yakalanan Değişken

Anonim fonksiyon (closure) dış kapsamdaki değişkene kendiliğinden erişemez; use ile açıkça yakalaması gerekir. use ($x) değeri bildirim anında kopyalar, use (&$x) ise değişkenin kendisini bağlar. Ok fonksiyonu (arrow function) fn ise dış değişkeni oluşturulduğu anda değerle yakalar. Aşağıdaki dosya üç yakalama biçimini, yakalanan değişken sonradan değiştirildikten sonra çağırarak ölçer.

<?php
// kapanis.php — use degeri yakalar, & degiskeni baglar, ok fonksiyonu olustugu andaki degeri alir
$x = 1;
$degerle = function () use ($x) {
    return $x;
};
$referansla = function () use (&$x) {
    return $x;
};
$ok = fn() => $x;

$x = 2;
echo 'use ($x):  ', $degerle(), "\n";
echo 'use (&$x): ', $referansla(), "\n";
echo 'fn:        ', $ok(), "\n";

$artir = fn() => $x++;
$artir();
echo 'fn icinde artirma sonrasi x: ', $x, "\n";
use ($x):  1
use (&$x): 2
fn:        1
fn icinde artirma sonrasi x: 2

KN49. $x iki yapıldıktan sonra değerle yakalayan iki biçim eski değeri, yani 1’i gördü; yalnız & ile bağlanan 2’yi gördü. Ok fonksiyonunun içindeki artırma dışarıdaki $x’i değiştirmedi: değişken ok fonksiyonuna kopya olarak girdi. Ayrım, değer ve referans geçişinin kapanışlardaki karşılığıdır ve bildirim üretmez: yanlış biçimi seçen kod hata vermez, eski bir değerle çalışır.

Boş Değer Kabul Eden Parametre

Bir parametreye null verilebilmesi iki ayrı şeydir: parametrenin tipinin null kabul etmesi ve parametrenin atlanabilmesi. ?int $x null kabul eder ama zorunludur; ?int $x = null hem null kabul eder hem atlanabilir. Birleşik tip (int|string) ise gevşek kipte hangi tipe zorlanacağını bir sıraya göre seçer. Aşağıdaki dosya altı çağrıyı bir tabloda ölçer.

<?php
// bos_deger.php — ?int zorunludur, = null atlanabilir, birlesik tip zorlama sirasini secer
function zorunlu(?int $x)
{
    return var_export($x, true);
}

function atlanabilir(?int $x = null)
{
    return var_export($x, true);
}

function birlesik(int|string $x)
{
    return get_debug_type($x);
}

$cagrilar = [
    'zorunlu(null)'      => fn() => zorunlu(null),
    'zorunlu()'          => fn() => zorunlu(),
    'atlanabilir()'      => fn() => atlanabilir(),
    "birlesik('5')"      => fn() => birlesik('5'),
    'birlesik(5.0)'      => fn() => birlesik(5.0),
    'birlesik([])'       => fn() => birlesik([]),
    'birlesik(true)'     => fn() => birlesik(true),
];
foreach ($cagrilar as $ad => $cagri) {
    try {
        $yazi = $cagri();
    } catch (Error $hata) {
        $yazi = get_class($hata);
    }
    printf("%-20s%s\n", $ad, $yazi);
}
zorunlu(null)       NULL
zorunlu()           ArgumentCountError
atlanabilir()       NULL
birlesik('5')       string
birlesik(5.0)       int
birlesik([])        TypeError
birlesik(true)      int

KN50. zorunlu(null) çalıştı ama zorunlu() ArgumentCountError verdi: ? null kabul etmeyi sağlar, atlanabilirliği sağlamaz. atlanabilir() NULL döndürdü. Birleşik tipte tam eşleşen değer olduğu gibi kaldı; tam eşleşme yoksa PHP bir sıra izledi ve sonucu sessizce seçti; dizi gibi hiçbir üyeye çevrilemeyen değer ise TypeError verdi. Bu, tip bildirimi dersindeki gevşek kip gözleminin birleşik tiplerdeki karşılığıdır.

Geri Çağrılan Fonksiyon

Bir fonksiyona argüman olarak verilen fonksiyon (geri çağrı, callback) çağıran tarafından, bildiriminin beklediğinden fazla argümanla çağrılabilir. Kullanıcı fonksiyonu bu fazlalığı yine sessizce yutar; yerleşik fonksiyon ise yutmaz. Aşağıdaki dosya array_filter()’ı iki argüman gönderen kipte, tek parametreli bir kapanışla çalıştırır ve aynı sınamayı yerleşik strlen ile array_map() üzerinden yapar.

<?php
// geri_cagri.php — geri cagrilan kullanici fonksiyonu fazla argumani yutar, yerlesik yutmaz
$sayilar = [1, 2, 3, 4];

$tek_parametre = array_filter($sayilar, fn($v) => $v > 2, ARRAY_FILTER_USE_BOTH);
echo 'tek parametreli: ', implode(',', $tek_parametre), "\n";

$iki_parametre = array_filter($sayilar, fn($v, $k) => $k % 2 === 0, ARRAY_FILTER_USE_BOTH);
echo 'iki parametreli: ', implode(',', $iki_parametre), "\n";

try {
    $sonuc = array_map('strlen', ['a'], ['b']);
    echo 'strlen: ', count($sonuc), "\n";
} catch (ArgumentCountError $hata) {
    echo 'strlen: ', get_class($hata), "\n";
}
tek parametreli: 3,4
iki parametreli: 1,3
strlen: ArgumentCountError

KN51. İki argümanla çağrılan tek parametreli kapanış anahtarı hiç görmeden çalıştı ve 3,4 verdi. İki parametreli olan anahtarı kullandı ve çift indeksli iki elemanı, 1,3’ü seçti. İki diziyle çağrılan array_map() ise strlen’e iki argüman gönderdi ve ArgumentCountError aldı. Aynı fazla argüman, geri çağrının türüne göre bir yerde sessiz, bir yerde durdurucu oldu.

Sınırlayıcı Ölçüm

Kullanıcı fonksiyonundaki sessiz yutmanın sınırı, fazlalığı görünür kılmanın mümkün olmasıdır. İki yol vardır: func_num_args() fonksiyon içinde gerçekte kaç argüman verildiğini söyler; ...$x sözdizimi ise kalan bütün argümanları bir dizide toplar ve fazlalık o dizinin uzunluğunda görünür. Aşağıdaki dosya üç yolu aynı çağrıyla ölçer.

<?php
// gorunur.php — func_num_args ve ...$x fazlaligi gorunur kilar
function iki_topla($a, $b)
{
    return $a + $b;
}

function say_argumani($a, $b)
{
    return func_num_args();
}

function topla(int ...$sayilar): int
{
    return array_sum($sayilar);
}

function kac_tane(...$x): int
{
    return count($x);
}

echo 'iki_topla(1, 2, 3):    ', iki_topla(1, 2, 3), "\n";
echo 'say_argumani(1, 2, 3): ', say_argumani(1, 2, 3), "\n";
echo 'topla(1, 2, 3):        ', topla(1, 2, 3), "\n";
echo 'kac_tane(1, 2, 3):     ', kac_tane(1, 2, 3), "\n";
iki_topla(1, 2, 3):    3
say_argumani(1, 2, 3): 3
topla(1, 2, 3):        6
kac_tane(1, 2, 3):     3

KN52. iki_topla üçüncü argümanı yine yuttu ve 3 verdi. func_num_args() aynı çağrıda 3 saydı: yutulan argüman, fonksiyonun içinden ölçülebilir. ...$sayilar ile yazılan fonksiyon üç sayıyı da topladı ve 6 verdi; ...$x ise fazlalığı bir dizi olarak teslim etti ve count 3 söyledi. Sessiz sınıf bu yüzden kaçınılmaz değildir: fonksiyon yazarı, fazlalığın görünür olacağı bir imza seçebilir.

Özet

  • Fonksiyonların ölçü sınıfı sessizdir: kullanıcı fonksiyonuna verilen fazla konumlu argüman bildirim üretmeden yok sayılır, yerleşik fonksiyon ise ArgumentCountError fırlatır.
  • Eksik zorunlu argüman ArgumentCountError, bilinmeyen ya da çakışan adlandırılmış argüman Error verir; adlandırma konumdan sıkıdır.
  • Varsayılan değer her çağrıda yenilenir, static değişken çağrılar arasında yaşar.
  • Argümanlar değerle geçer; & yalnız imzada yazılırsa çağıranın değişkeni değişir.
  • return olmayan fonksiyon null döndürür; dönüş tipi sayısal dizgiyi zorlar, olmayanı reddeder.
  • func_num_args() ve ...$x yutulan fazlalığı görünür kılar.

Sonraki Adım

Fazla argümanın nereye gittiği görüldü: yok sayıldı ya da bir dizide toplandı. İndeksli ve İlişkisel Diziler dersi o diziye bakıyor: iki dizi birleştirildiğinde, aynı anahtarı taşıyan elemanlar için anahtarlar nasıl birleşiyor?

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat