Doğrudan nesne özelliğine atanan kapatma çağrısı


109

Bir nesnenin özelliğine atadığım bir closure'u bir değişkene yeniden atamadan ve sonra onu çağırmadan doğrudan çağırabilmek istiyorum. Mümkün mü?

Aşağıdaki kod çalışmıyor ve nedenleri Fatal error: Call to undefined method stdClass::callback().

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback();

1
Tam da ihtiyacınız olan şey bu: github.com/ptrofimov/jslikeobject Daha da fazlası: $ this 'i kapanışlarda kullanabilir ve kalıtımı kullanabilirsiniz. Sadece PHP> = 5.4!
Renato Cuccinotto

Yanıtlar:


106

PHP7'den itibaren şunları yapabilirsiniz:

$obj = new StdClass;
$obj->fn = function($arg) { return "Hello $arg"; };
echo ($obj->fn)('World');

veya Closure :: call () kullanın , ancak bu bir StdClass.


PHP7'den önce __call, çağrıyı durdurmak ve geri aramayı başlatmak için sihirli yöntemi uygulamanız gerekir (bu StdClasselbette mümkün değildir , çünkü __callyöntemi ekleyemezsiniz )

class Foo
{
    public function __call($method, $args)
    {
        if(is_callable(array($this, $method))) {
            return call_user_func_array($this->$method, $args);
        }
        // else throw exception
    }
}

$foo = new Foo;
$foo->cb = function($who) { return "Hello $who"; };
echo $foo->cb('World');

Yapamayacağınızı unutmayın

return call_user_func_array(array($this, $method), $args);

içinde __callvücutta bu tetikleyecek çünkü __callsonsuz döngüye.


2
Bazen bu sözdizimini yararlı bulursunuz - call_user_func_array ($ this -> $ özellik, $ args); Çağrılabilir sınıf özelliği ile ilgili olduğunda, bir yöntemle değil.
Nikita Gopkalo

105

Bunu kapanışta __invoke çağırarak yapabilirsiniz, çünkü nesnelerin işlevler gibi davranmak için kullandığı sihirli yöntem budur:

$obj = new stdClass();
$obj->callback = function() {
    print "HelloWorld!";
};
$obj->callback->__invoke();

Elbette geriçağırım bir dizi veya dizge ise (PHP'de de geçerli geri çağırmalar olabilir) - sadece __invoke davranışına sahip kapanışlar ve diğer nesneler için bu işe yaramaz.


3
@marcioAlmada gerçi çok çirkin.
Mahn

1
@Mahn Kabul edilen cevaptan daha açık olduğunu düşünüyorum. Bu durumda açık olmak daha iyidir. Gerçekten "şirin" bir çözüm arıyorsanız, call_user_func($obj->callback)o kadar da kötü değil.
marcio

Ancak call_user_funcdizelerle de çalışır ve bu her zaman uygun değildir
Gherman

2
@cerebriform anlamsal olarak, $obj->callback->__invoke();beklendiği zaman yapmak zorunda kalmanın bir anlamı yok $obj->callback(). Bu sadece tutarlılık meselesi.
Mahn

3
@Mahn: Kabul edildi, tutarlı değil. Tutarlılık hiçbir zaman PHP'nin güçlü bir parçası olmadı. :) Ama bir nesne doğayı gördüğü zaman, markaları mantıklı mı düşünüyorum: $obj->callback instanceof \Closure.
bishop

24

İtibariyle PHP 7 aşağıdakileri yapabilirsiniz:

($obj->callback)();

Çok sağduyu, ama tamamen saçma. PHP7'nin büyük gücü!
tfont

10

PHP 7'den beri şu call()yöntem kullanılarak bir kapatma çağrılabilir :

$obj->callback->call($obj);

Yana PHP 7 keyfi işlemleri yürütmek mümkündür (...)(açıklanabilir olarak çok ifadeler Korikulum ):

($obj->callback)();

Diğer yaygın PHP 5 yaklaşımları şunlardır:

  • sihirli yöntemi kullanarak __invoke()( Brilliand tarafından açıklandığı gibi )

    $obj->callback->__invoke();
  • call_user_func()işlevi kullanarak

    call_user_func($obj->callback);
  • bir ifadede bir ara değişken kullanmak

    ($_ = $obj->callback) && $_();

Her yolun kendi artıları ve eksileri vardır, ancak en radikal ve kesin çözüm hala Gordon tarafından sunulan çözümdür .

class stdKlass
{
    public function __call($method, $arguments)
    {
        // is_callable([$this, $method])
        //   returns always true when __call() is defined.

        // is_callable($this->$method)
        //   triggers a "PHP Notice: Undefined property" in case of missing property.

        if (isset($this->$method) && is_callable($this->$method)) {
            return call_user_func($this->$method, ...$arguments);
        }

        // throw exception
    }
}

$obj = new stdKlass();
$obj->callback = function() { print "HelloWorld!"; };
$obj->callback();

7

Kullanmak mümkün görünüyor call_user_func().

call_user_func($obj->callback);

Yine de zarif değil .... @Gordon'un söylediği muhtemelen gitmenin tek yolu.


7

Eğer gerçekten ısrar ediyorsan . Başka bir geçici çözüm şudur:

$obj = new ArrayObject(array(),2);

$obj->callback = function() {
    print "HelloWorld!";
};

$obj['callback']();

Ama bu en güzel sözdizimi değil.

Ancak, PHP çözümleyici hep davranır T_OBJECT_OPERATOR, IDENTIFIER, (yöntem çağrısı gibi. ->Yöntem tablosunu atlamak ve bunun yerine özniteliklere erişmek için bir çözüm yok gibi görünüyor .


7

Bunun eski olduğunu biliyorum, ancak PHP 5.4+ kullanıyorsanız Traits'in bu sorunu iyi bir şekilde ele aldığını düşünüyorum.

İlk olarak, özellikleri çağrılabilir kılan bir özellik oluşturun:

trait CallableProperty {
    public function __call($method, $args) {
        if (property_exists($this, $method) && is_callable($this->$method)) {
            return call_user_func_array($this->$method, $args);
        }
    }
}

Ardından, bu özelliği sınıflarınızda kullanabilirsiniz:

class CallableStdClass extends stdClass {
    use CallableProperty;
}

Artık özellikleri anonim işlevler aracılığıyla tanımlayabilir ve bunları doğrudan çağırabilirsiniz:

$foo = new CallableStdClass();
$foo->add = function ($a, $b) { return $a + $b; };
$foo->add(2, 2); // 4

Vay be :) Bu yapmaya çalıştığımdan çok daha zarif. Tek sorum İngiliz sıcak / soğuk musluk bağlantı elemanları için olanla aynı: NEDEN zaten yerleşik değil?
dkellner

Teşekkürler :). Muhtemelen yarattığı belirsizlik nedeniyle inşa edilmemiştir. Örneğimde gerçekten "add" adında bir işlev ve "add" adında bir özellik olup olmadığını düşünün. Parantezin varlığı, PHP'ye bu ada sahip bir işlevi aramasını söyler.
SteveK

2

Peki, kapanışı bir değişkene kaydetmenin ve değişken olarak adlandırmanın aslında (garip) daha hızlı olduğu vurgulanmalı, çağrı miktarına bağlı olarak, xdebug ile (çok hassas ölçüm) oldukça fazla oluyor, bundan bahsediyoruz 1,5 (__invoke'u doğrudan çağırmak yerine bir değişken kullanarak çarpan. Bunun yerine, kapanışı bir değişkende saklayın ve çağırın.


1

İşte kabul edilen yanıta dayalı ancak stdClass'ı doğrudan genişleten başka bir alternatif:

class stdClassExt extends stdClass {
    public function __call($method, $args)
    {
        if (isset($this->$method)) {
            $func = $this->$method;
            return call_user_func_array($func, $args);
        }
    }
}

Kullanım örneği:

$foo = new stdClassExt;
$foo->blub = 42;
$foo->whooho = function () { return 1; };
echo $foo->whooho();

Kullanmanız call_user_funcya __invokeda kullanmanız muhtemelen daha iyidir .


1

Güncellenmiş:

$obj = new stdClass();
$obj->callback = function() {
     print "HelloWorld!";
};

PHP> = 7:

($obj->callback)();

PHP> = 5.4:

$callback = $obj->callback;  
$callback();

Bunu denedin mi Çalışmıyor. Call to undefined method AnyObject::callback()(Elbette AnyObject sınıfı mevcuttur.)
Kontrollfreak

0

PHP 5.4 veya üstünü kullanıyorsanız, özel davranışı çağırmak için çağrılabilir bir nesneyi nesnenizin kapsamına bağlayabilirsiniz. Öyleyse, örneğin aşağıdaki ayarları yaparsanız ..

function run_method($object, Closure $method)
{
    $prop = uniqid();
    $object->$prop = \Closure::bind($method, $object, $object);
    $object->$prop->__invoke();
    unset($object->$prop);
}

Ve böyle bir sınıfta çalışıyordun ..

class Foo
{
    private $value;
    public function getValue()
    {
        return $this->value;
    }
}

Kendi mantığınızı, sanki nesnenizin kapsamı dahilinde çalışıyormuşsunuz gibi çalıştırabilirsiniz.

$foo = new Foo();
run_method($foo, function(){
    $this->value = 'something else';
});

echo $foo->getValue(); // prints "something else"

0

Bunun PHP5.5'te çalıştığını not ediyorum

$a = array();
$a['callback'] = function() {
    print "HelloWorld!";
};
$a['callback']();

Birinin bir psuedo-nesne kapanış koleksiyonu oluşturmasına izin verir.

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.