サーチ…
クロージャの基本的な使い方
クロージャは、無名関数のPHP相当物です。名前を持たない関数。それが技術的に正しくない場合でも、クロージャの動作は関数と同じままですが、いくつかの追加機能があります。
クロージャは、名前なしで関数を宣言することによって作成されるClosureクラスのオブジェクトに過ぎません。例えば:
<?php
$myClosure = function() {
echo 'Hello world!';
};
$myClosure(); // Shows "Hello world!"
$myClosure
はClosure
インスタンスなので、実際に何ができるのか分かります(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!
この例は、オブザーバが同じ性質を共有しているため(両方が「名前付きオブザーバ」)