PHP'de kapatma nedir ve neden “kullan” tanımlayıcısını kullanır?


407

Bazı PHP 5.3.0özellikleri kontrol ediyorum ve sitede oldukça komik görünüyor bazı kod üzerinde koştu:

public function getTotal($tax)
{
    $total = 0.00;

    $callback =
        /* This line here: */
        function ($quantity, $product) use ($tax, &$total)
        {
            $pricePerItem = constant(__CLASS__ . "::PRICE_" .
                strtoupper($product));
            $total += ($pricePerItem * $quantity) * ($tax + 1.0);
        };

    array_walk($this->products, $callback);
    return round($total, 2);
}

anonim işlevlere örneklerden biri olarak .

Bunu bilen var mı? Herhangi bir belge var mı? Ve kötü görünüyor, hiç kullanılmalı mı?

Yanıtlar:


362

PHP bu şekilde kapanmayı ifade eder . Bu hiç de kötü değil ve aslında oldukça güçlü ve kullanışlı.

Temel olarak bunun anlamı, anonim işlevin, yerel değişkenleri (bu örnekte $taxve bir referansı $total) kapsamın dışında "yakalamasına" ve değerlerini (veya kendisine $totalreferans olması $totaldurumunda) içindeki durum olarak korumasına izin vermenizdir. anonim işlevin kendisi.


1
Yani SADECE kapaklar için mi kullanılıyor? Açıklama için teşekkürler, anonim işlev ve bir kapatma arasındaki farkı bilmiyordum
SeanDowney

136
useAnahtar kelime de kullanılır örtüşme ad . PHP 5.3.0'ın piyasaya sürülmesinden 3 yıl sonra, sözdiziminin function ... usehala resmi olarak belgelenmemiş olması, bu da kapanışları belgelenmemiş bir özellik haline getiriyor. Doktor anonim işlevleri ve kapanışları bile karıştırır . use ()Ben php.net üzerinde bulabildiğim tek (beta ve gayri resmi) belgeler kapanışları için RFC oldu .

2
Peki PHP'de fonksiyon kullanım kapanışları ne zaman uygulandı? Sanırım o zaman PHP 5.3 oldu? Şimdi PHP el kitabında bir şekilde belgelenmiş mi?
rubo77

@Mytskine Doktora göre, anonim işlevler Closure sınıfını kullanıyor
Manny Fleurmond

1
Şimdi usede traitiçine a eklemek için kullanılır class!
CJ Dennis

477

Daha basit bir cevap.

function ($quantity) use ($tax, &$total) { .. };

  1. Kapatma, bir değişkene atanan bir işlevdir, böylece onu geçirebilirsiniz
  2. Kapatma ayrı bir ad alanıdır, normalde bu ad alanının dışında tanımlanan değişkenlere erişemezsiniz. Orada geliyor kullanım anahtar kelime:
  3. use , kapatma içindeki sonraki değişkenlere erişmenizi (kullanmanızı) sağlar.
  4. kullanımı erken bağlanmadır. Bu, değişken değerlerin, KAPANIN TANIMI üzerine KOPYALANIR anlamına gelir. Bu nedenle$tax, bir nesne gibi bir işaretçi değilse, kapağın içindedeğiştirmeninharici bir etkisi yoktur.
  5. Değişkenleri, durumda olduğu gibi işaretçi olarak iletebilirsiniz &$total. Bu şekilde, $totalDOES değerinin değiştirilmesi harici bir etkiye sahip olur, orijinal değişkenin değeri değişir.
  6. Kapak içinde tanımlanan değişkenlere kapak dışından da erişilemez.
  7. Kapaklar ve fonksiyonlar aynı hıza sahiptir. Evet, bunları komut dosyalarınızın her yerinde kullanabilirsiniz.

@Mytskine'nin işaret ettiği gibi, muhtemelen en iyi derinlemesine açıklama kapanışlar için RFC'dir . (Bunun için onu oyla.)


4
$closure = function ($value) use ($localVar as $alias) { //stuff};Parse: syntax error, unexpected 'as' (T_AS), expecting ',' or ')'
Use

1
Php5.3 ile de onaylanan @KalZekdor kullanımdan kaldırıldı. Cevabı güncelledim, çaba için teşekkürler.
zupa

4
Nokta # 5 bu şekilde, bir işaretçi gibi değeri değiştirmenin de &$totalbir iç etkiye sahip olduğunu eklerdim. Diğer bir deyişle, tanımlandıktan sonra kapağın $total dışının değerini değiştirirseniz, yeni değer yalnızca bir işaretçi ise iletilir.
billynoah

2
@ AndyD273, globalsadece genel ad alanına erişime useizin verirken , ana ad alanındaki değişkenlere erişmeye izin vermesi dışında amaç gerçekten çok benzerdir . Küresel değişkenler genellikle kötülük olarak kabul edilir. Ebeveyn kapsamına erişmek genellikle bir kapatma oluşturmaktır. Kapsamı sınırlı olduğu için “kötü” değildir. JS gibi diğer diller örtük olarak üst kapsamın değişkenlerini kullanır (kopyalanan değer olarak değil, işaretçi olarak).
Mart'ta zupa

1
Bu satır benim iki saat boşuna arama durduYou can pass in variables as pointers like in case of &$total. This way, modifying the value of $total DOES HAVE an external effect, the original variable's value changes.
BlackPearl

69

function () use () {}PHP kapanması gibidir.

Olmadan use, işlev üst kapsam değişkenine erişemez

$s = "hello";
$f = function () {
    echo $s;
};

$f(); // Notice: Undefined variable: s
$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$f(); // hello

useDeğişkenin değeri fonksiyon tayin edildiği zaman, adı verilen zaman değil ila

$s = "hello";
$f = function () use ($s) {
    echo $s;
};

$s = "how are you?";
$f(); // hello

use ile değişken referans &

$s = "hello";
$f = function () use (&$s) {
    echo $s;
};

$s = "how are you?";
$f(); // how are you?

4
Bu okuduktan sonra ben biraz daha kaydırma pişman değilim ama sanırım üçüncü blok yazım hatası için küçük bir düzenleme gerekir. $ Obj yerine $ s olmalıdır.
Kullanıcı

53

kapanışları güzel! onlar anonim fonksiyonları ile gelen sorunları çözmek ve gerçekten zarif kod mümkün kılan (en azından biz php hakkında konuşmak sürece).

javascript programcıları her zaman, bazen bile bilmeden, kapanışları kullanır, çünkü bağlı değişkenler açıkça tanımlanmadı - php "kullanım" için ne olduğunu.

yukarıdakilerden daha iyi gerçek dünya örnekleri var. diyelim ki çok boyutlu bir diziyi bir alt değere göre sıralamanız gerekiyor, ancak anahtar değişiyor.

<?php
    function generateComparisonFunctionForKey($key) {
        return function ($left, $right) use ($key) {
            if ($left[$key] == $right[$key])
                return 0;
            else
                return ($left[$key] < $right[$key]) ? -1 : 1;
        };
    }

    $myArray = array(
        array('name' => 'Alex', 'age' => 70),
        array('name' => 'Enrico', 'age' => 25)
    );

    $sortByName = generateComparisonFunctionForKey('name');
    $sortByAge  = generateComparisonFunctionForKey('age');

    usort($myArray, $sortByName);

    usort($myArray, $sortByAge);
?>

Uyarı: denenmemiş kod (atm yüklü php5.3 yok), ama böyle bir şey gibi görünmelidir.

bir dezavantajı var: php geliştiricileri bir sürü kapanışlarla karşı karşıya kalırsanız biraz çaresiz olabilir.

kapanışların güzel-ty anlamak için, ben size başka bir örnek vereceğim - bu sefer javascript. sorunlardan biri kapsam belirleme ve tarayıcının doğal eşzamansızlığıdır. özellikle, window.setTimeout();(veya aralıklı) söz konusu olduğunda . Bu nedenle, setTimeout işlevine bir işlev iletirsiniz, ancak parametreler sağlamak kodu yürüttüğü için gerçekten herhangi bir parametre veremezsiniz!

function getFunctionTextInASecond(value) {
    return function () {
        document.getElementsByName('body')[0].innerHTML = value; // "value" is the bound variable!
    }
}

var textToDisplay = prompt('text to show in a second', 'foo bar');

// this returns a function that sets the bodys innerHTML to the prompted value
var myFunction = getFunctionTextInASecond(textToDisplay);

window.setTimeout(myFunction, 1000);

myFunction, bir tür önceden tanımlanmış parametre içeren bir işlev döndürür!

dürüst olmak gerekirse, ben php çok daha 5.3 ve anonim fonksiyonları / kapanışlarını seviyorum. ad alanları daha önemli olabilir, ancak çok daha az seksi olurlar .


4
ohhhhhhhh, bu yüzden Kullanımlar ekstra değişkenleri geçmek için kullanılır , bunun komik bir görev olduğunu düşündüm. Teşekkürler!
SeanDowney

38
dikkatli ol. parametreler işlev ARAYILDIĞINDA değerleri iletmek için kullanılır. kapanışları, işlev TANIMLANDIĞINDA değerleri "geçmek" için kullanılır.
stefs

Javascript'te, işlevlere ilk bağımsız değişkenler belirtmek için bind () kullanılabilir - bkz. Kısmen uygulanan işlevler .
Sᴀᴍ Onᴇᴌᴀ

17

Zupa, 'kullanım' ile kapanışları ve EarlyBinding ile 'kullanılan' değişkenlere referans verme arasındaki farkı açıklayan harika bir iş çıkardı.

Bu yüzden bir değişken (= kopyalama) erken bağlama ile bir kod örneği yaptım:

<?php

$a = 1;
$b = 2;

$closureExampleEarlyBinding = function() use ($a, $b){
    $a++;
    $b++;
    echo "Inside \$closureExampleEarlyBinding() \$a = ".$a."<br />";
    echo "Inside \$closureExampleEarlyBinding() \$b = ".$b."<br />";    
};

echo "Before executing \$closureExampleEarlyBinding() \$a = ".$a."<br />";
echo "Before executing \$closureExampleEarlyBinding() \$b = ".$b."<br />";  

$closureExampleEarlyBinding();

echo "After executing \$closureExampleEarlyBinding() \$a = ".$a."<br />";
echo "After executing \$closureExampleEarlyBinding() \$b = ".$b."<br />";

/* this will output:
Before executing $closureExampleEarlyBinding() $a = 1
Before executing $closureExampleEarlyBinding() $b = 2
Inside $closureExampleEarlyBinding() $a = 2
Inside $closureExampleEarlyBinding() $b = 3
After executing $closureExampleEarlyBinding() $a = 1
After executing $closureExampleEarlyBinding() $b = 2
*/

?>

Bir değişkene referans veren örnek (değişkenin önündeki '&' karakterine dikkat edin);

<?php

$a = 1;
$b = 2;

$closureExampleReferencing = function() use (&$a, &$b){
    $a++;
    $b++;
    echo "Inside \$closureExampleReferencing() \$a = ".$a."<br />";
    echo "Inside \$closureExampleReferencing() \$b = ".$b."<br />"; 
};

echo "Before executing \$closureExampleReferencing() \$a = ".$a."<br />";
echo "Before executing \$closureExampleReferencing() \$b = ".$b."<br />";   

$closureExampleReferencing();

echo "After executing \$closureExampleReferencing() \$a = ".$a."<br />";
echo "After executing \$closureExampleReferencing() \$b = ".$b."<br />";    

/* this will output:
Before executing $closureExampleReferencing() $a = 1
Before executing $closureExampleReferencing() $b = 2
Inside $closureExampleReferencing() $a = 2
Inside $closureExampleReferencing() $b = 3
After executing $closureExampleReferencing() $a = 2
After executing $closureExampleReferencing() $b = 3
*/

?>

2

PHP son yıllara kadar AST ve PHP yorumlayıcısını ayrıştırıcıyı değerlendirme kısmından izole etmiştir. Kapanışın getirildiği süre boyunca, PHP'nin ayrıştırıcısı değerlendirme ile büyük ölçüde bağlantılıdır.

Bu nedenle, kapanış ilk olarak PHP'ye tanıtıldığında, yorumlayıcının, hangi ayrıştırmada hangi değişkenlerin kullanılacağını bilmek için bir yöntemi yoktur, çünkü henüz ayrıştırılmaz. Bu yüzden kullanıcı, zend'in yapması gereken ev ödevini yaparak açık içe aktarma yoluyla zend motorunu memnun etmelidir.

Bu PHP'de sözde basit yoldur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.