サーチ…


クロージャの基本的な使い方

クロージャは、無名関数のPHP相当物です。名前を持たない関数。それが技術的に正しくない場合でも、クロージャの動作は関数と同じままですが、いくつかの追加機能があります。

クロージャは、名前なしで関数を宣言することによって作成されるClosureクラスのオブジェクトに過ぎません。例えば:

<?php

$myClosure = function() {
    echo 'Hello world!';
};

$myClosure(); // Shows "Hello world!"

$myClosureClosureインスタンスなので、実際に何ができるのか分かります(cf. http://fr2.php.net/manual/en/class.closure.php参照)

あなたがClosureを必要とする典型的なケースは、例えばusortのような関数にcallableものを与えなければならないときです。

ここでは、配列が各人の兄弟の数によってソートされる例を示します:

<?php

$data = [
    [
        'name' => 'John',
        'nbrOfSiblings' => 2,
    ],
    [
        'name' => 'Stan',
        'nbrOfSiblings' => 1,
    ],
    [
        'name' => 'Tom',
        'nbrOfSiblings' => 3,
    ]
];

usort($data, function($e1, $e2) {
    if ($e1['nbrOfSiblings'] == $e2['nbrOfSiblings']) {
        return 0;
    }
    
    return $e1['nbrOfSiblings'] < $e2['nbrOfSiblings'] ? -1 : 1;
});

var_dump($data); // Will show Stan first, then John and finally Tom

外部変数の使用

クロージャ内では、特殊キーワードuseを使用して外部変数を使用することができます 。例えば:

<?php

$quantity = 1;

$calculator = function($number) use($quantity) {
    return $number + $quantity;
};

var_dump($calculator(2)); // Shows "3"

「動的」クロージャを作成することで、さらに進んでいくことができます。追加したい数量に応じて、特定の電卓を返す関数を作成することができます。例えば:

<?php

function createCalculator($quantity) {
    return function($number) use($quantity) {
        return $number + $quantity;
    };
}

$calculator1 = createCalculator(1);
$calculator2 = createCalculator(2);

var_dump($calculator1(2)); // Shows "3"
var_dump($calculator2(2)); // Shows "4"

基本的なクロージャのバインディング

前述のように、ClosureはClosureクラスのインスタンスに過ぎず、さまざまなメソッドを呼び出すことができます。そのうちの1つはbindTo 。これは、クロージャを指定すると、指定されたオブジェクトにバインドされた新しいオブジェクトを返します。例えば:

<?php

$myClosure = function() {
    echo $this->property;
};

class MyClass
{
    public $property;

    public function __construct($propertyValue)
    {
        $this->property = $propertyValue;
    }
}

$myInstance = new MyClass('Hello world!');
$myBoundClosure = $myClosure->bindTo($myInstance);

$myBoundClosure(); // Shows "Hello world!"

クロージャバインディングとスコープ

この例を考えてみましょう:

<?php

$myClosure = function() {
    echo $this->property;
};

class MyClass
{
    public $property;

    public function __construct($propertyValue)
    {
        $this->property = $propertyValue;
    }
}

$myInstance = new MyClass('Hello world!');
$myBoundClosure = $myClosure->bindTo($myInstance);

$myBoundClosure(); // Shows "Hello world!"

property可視性をprotectedまたはprivateいずれかに変更してください。このプロパティにアクセスできないという致命的なエラーが表示されます。実際、クロージャがオブジェクトにバインドされていても、クロージャが呼び出される範囲は、そのアクセスを持つために必要な範囲ではありません。これがbindToの2番目の引数です。

privateである場合にプロパティにアクセスする唯一の方法は、それを許可するスコープからアクセスすることです。クラスのスコープ直前のコード例では、スコープが指定されていません。つまり、クロージャが作成された場所と同じスコープでクロージャが呼び出されました。それを変えよう:

<?php

$myClosure = function() {
    echo $this->property;
};

class MyClass
{
    private $property; // $property is now private

    public function __construct($propertyValue)
    {
        $this->property = $propertyValue;
    }
}

$myInstance = new MyClass('Hello world!');
$myBoundClosure = $myClosure->bindTo($myInstance, MyClass::class);

$myBoundClosure(); // Shows "Hello world!"

この2番目のパラメータを使用しない場合、クロージャは、クロージャが作成された場所と同じコンテキストで呼び出されます。たとえば、オブジェクトのコンテキストで呼び出されるメソッドのクラス内で作成されたクロージャは、メソッドのスコープと同じスコープを持ちます。

<?php

class MyClass
{
    private $property;

    public function __construct($propertyValue)
    {
        $this->property = $propertyValue;
    }

    public function getDisplayer()
      {
        return function() {
              echo $this->property;
        };
      }
}

$myInstance = new MyClass('Hello world!');

$displayer = $myInstance->getDisplayer();
$displayer(); // Shows "Hello world!"

1回の呼び出しでクロージャをバインドする

PHP7以降callメソッドのおかげで、1回の呼び出しでクロージャをバインドすることは可能です。例えば:

<?php

class MyClass
{
    private $property;

    public function __construct($propertyValue)
    {
        $this->property = $propertyValue;
    }
}

$myClosure = function() {
    echo $this->property;
};

$myInstance = new MyClass('Hello world!');

$myClosure->call($myInstance); // Shows "Hello world!"

bindToメソッドとは対照的に、気にする範囲はありません。この呼び出しに使用されるスコープは、 $myInstanceプロパティにアクセスまたは呼び出すときに使用されるスコープと同じ$myInstance

クロージャを使用してオブザーバパターンを実装する

一般に、オブザーバは、観察されたオブジェクトに対するアクションが発生したときに呼び出される特定のメソッドを持つクラスです。特定の状況では、オブザーバの設計パターンを実装するにはクロージャで十分です。

このような実装の詳細な例を次に示します。オブザーバーのプロパティーが変更されたときにオブザーバーに通知することを目的とするクラスを宣言しましょう。

<?php

class ObservedStuff implements SplSubject
{
    protected $property;
    protected $observers = [];

    public function attach(SplObserver $observer)
    {
        $this->observers[] = $observer;
        return $this;
    }

    public function detach(SplObserver $observer)
    {
        if (false !== $key = array_search($observer, $this->observers, true)) {
            unset($this->observers[$key]);
        }
    }

    public function notify()
    {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    public function getProperty()
    {
        return $this->property;
    }

    public function setProperty($property)
    {
        $this->property = $property;
        $this->notify();
    }
}

次に、異なるオブザーバーを表すクラスを宣言しましょう。

<?php

class NamedObserver implements SplObserver
{
    protected $name;
    protected $closure;

    public function __construct(Closure $closure, $name)
    {
        $this->closure = $closure->bindTo($this, $this);
        $this->name = $name;
    }

    public function update(SplSubject $subject)
    {
        $closure = $this->closure;
        $closure($subject);
    }
}

最後にこれをテストしましょう:

<?php

$o = new ObservedStuff;

$observer1 = function(SplSubject $subject) {
    echo $this->name, ' has been notified! New property value: ', $subject->getProperty(), "\n";
};

$observer2 = function(SplSubject $subject) {
    echo $this->name, ' has been notified! New property value: ', $subject->getProperty(), "\n";
};

$o->attach(new NamedObserver($observer1, 'Observer1'))
  ->attach(new NamedObserver($observer2, 'Observer2'));

$o->setProperty('Hello world!');
// Shows:
// Observer1 has been notified! New property value: Hello world!
// Observer2 has been notified! New property value: Hello world!

この例は、オブザーバが同じ性質を共有しているため(両方が「名前付きオブザーバ」)



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow