この記事では主にphpにおけるクロージャの詳細な使い方を紹介します。必要な友人は参考にしてください
匿名関数であるクロージャはphp5.3で導入され、匿名関数としても知られています。リテラルの意味は、名前が定義されていない関数です。たとえば、次のコード (ファイル名は do.php) では、
<?php function A() { return 100; }; function B(Closure $callback) { return $callback(); } $a = B(A()); print_r($a);//输出:Fatal error: Uncaught TypeError: Argument 1 passed to B() must be an instance of Closure, integer given, called in D:\web\test\do.php on line 11 and defined in D:\web\test\do.php:6 Stack trace: #0 D:\web\test\do.php(11): B(100) #1 {main} thrown in D:\web\test\do.php on line 6 ?>
A() A() は、A が「匿名」関数ではないため、B のパラメーターとして使用することはできません。
したがって、これを次のように変更する必要があります:
<?php $f = function () { return 100; }; function B(Closure $callback) { return $callback(); } $a = B($f); print_r($a);//输出100 <? $func = function( $param ) { echo $param; }; $func( 'hello word' ); //输出:hello word
クロージャを実装する
匿名関数を通常の関数のパラメータとして渡し、それらを返すこともできます。これにより、単純なクロージャが実装されます。
以下に 3 つの例を示します:
<?php //例一 //在函数里定义一个匿名函数,并且调用它 function printStr() { $func = function( $str ) { echo $str; }; $func( ' hello my girlfriend ! ' ); } printStr();//输出 hello my girlfriend ! //例二 //在函数中把匿名函数返回,并且调用它 function getPrintStrFunc() { $func = function( $str ) { echo $str; }; return $func; } $printStrFunc = getPrintStrFunc(); $printStrFunc( ' do you love me ? ' );//输出 do you love me ? //例三 //把匿名函数当做参数传递,并且调用它 function callFunc( $func ) { $func( ' no!i hate you ' ); } $printStrFunc = function( $str ) { echo $str.'<br>'; }; callFunc( $printStrFunc ); //也可以直接将匿名函数进行传递。如果你了解js,这种写法可能会很熟悉 callFunc( function( $str ) { echo $str; //输出no!i hate you } );
クロージャと外部変数を接続するキーワード: USE
クロージャは、それが配置されているコード ブロックのコンテキストで一部の変数と値を保存できます。 PHP のデフォルトでは、匿名関数は、それが配置されているコード ブロック内のコンテキスト変数を呼び出すことはできませんが、 use キーワードを使用する必要があります。
別の例を見てみましょう (OK、私はお金がありません、私は下品です):
<?php function getMoney() { $rmb = 1; $dollar = 8; $func = function() use ( $rmb ) { echo $rmb; echo $dollar; }; $func(); } getMoney(); //输出:1
ご覧のとおり、 use キーワードでドルが宣言されていないため、これでは取得できません匿名関数 なので、開発中にこの問題に注意する必要があります。
匿名関数でコンテキスト変数を変更できるかどうか疑問に思う人もいるかもしれませんが、それは不可能であることがわかりました:
<?php function getMoney() { $rmb = 1; $func = function() use ( $rmb ) { echo $rmb.'<br>'; //把$rmb的值加1 $rmb++; }; $func(); echo $rmb; } getMoney(); //输出: //1 //1
そうですね、 use は変数 clone のコピーのみを参照していることがわかりました。しかし、変数をコピーするのではなく完全に参照したい場合はどうすればよいでしょうか? この効果を実現するには、実際に変数の前に & 記号を追加します:
<?php function getMoney() { $rmb = 1; $func = function() use ( &$rmb ) { echo $rmb.'<br>'; //把$rmb的值加1 $rmb++; }; $func(); echo $rmb; } getMoney(); //输出: //1 //2
さて、匿名関数がコンテキスト変数を参照できるようにします。匿名関数を外部に返すと、匿名関数は use で参照した変数を保存しますが、外部からはこれらの変数を取得できなくなります。このようにすると、「クロージャ」の概念がより明確になるかもしれません。
説明に基づいて上の例を変更してみましょう:
<?php function getMoneyFunc() { $rmb = 1; $func = function() use ( &$rmb ) { echo $rmb.'<br>'; //把$rmb的值加1 $rmb++; }; return $func; } $getMoney = getMoneyFunc(); $getMoney(); $getMoney(); $getMoney(); //输出: //1 //2 //3
さて、これで、クラス内の匿名関数を呼び出したい場合はどうすればよいでしょうか?デモ
<?php class A { public static function testA() { return function($i) { //返回匿名函数 return $i+100; }; } } function B(Closure $callback) { return $callback(200); } $a = B(A::testA()); print_r($a);//输出 300
に直接アクセスしてください。ここでは、A::testA() が名前のない関数を返します。
バインディングの概念
上の例のクロージャは単なるグローバル匿名関数です。 さて、今度は匿名関数を持つクラスを指定したいと思います。また、この匿名関数のアクセス スコープはグローバルではなく、クラスのアクセス スコープであることも理解できます。
次に、「匿名関数をクラスにバインドする」必要があります。
<?php class A { public $base = 100; } class B { private $base = 1000; } $f = function () { return $this->base + 3; }; $a = Closure::bind($f, new A); print_r($a());//输出 103 echo PHP_EOL; $b = Closure::bind($f, new B , 'B'); print_r($b());//输出1003
上記の例では、匿名関数 f に不可解な this があり、このキーワードは、この匿名関数をクラスにバインドする必要があることを意味します。
バインド後は、Aにそのような関数があるかのようになりますが、この関数がパブリックであってもプライベートであっても、バインドの最後のパラメータはこの関数の呼び出し可能なスコープを示します。
上でbindToを見てきたので、公式Webサイトの紹介を見てみましょう
(PHP 5 >= 5.4.0, PHP 7)
Closure::bind — クロージャーをコピーし、指定された$thisオブジェクトとクラススコープをバインドします。
説明
public static Closure Closure::bind ( Closure $closure , object $newthis [,mixed $newscope = 'static' ] )
このメソッドは Closure::bindTo() の静的バージョンです。詳細については、ドキュメントを参照してください。
パラメータ
closure
バインドする必要がある匿名関数。
newthis
匿名関数にバインドされたオブジェクト、または非バインド クロージャを作成するには NULL が必要です。
newscope
クロージャにバインドするクラススコープ、または「静的」は変更がないことを意味します。オブジェクトが渡された場合は、オブジェクトの型名が使用されます。 クラス スコープは、クロージャ内の $this オブジェクトのプライベートな保護されたメソッドの可視性を決定するために使用されます。 (注: クラス名またはクラスのインスタンスを渡すことができます。デフォルト値は「static」で、変更がないことを意味します。)
戻り値:
新しい Closure オブジェクトを返すか、失敗した場合は FALSE を返します
<?php class A { private static $sfoo = 1; private $ifoo = 2; } $cl1 = static function() { return A::$sfoo; }; $cl2 = function() { return $this->ifoo; }; $bcl1 = Closure::bind($cl1, null, 'A'); $bcl2 = Closure::bind($cl2, new A(), 'A'); echo $bcl1(), "\n";//输出 1 echo $bcl2(), "\n";//输出 2
理解を深めるために例を見てみましょう:
<?php class A { public $base = 100; } class B { private $base = 1000; } class C { private static $base = 10000; } $f = function () { return $this->base + 3; }; $sf = static function() { return self::$base + 3; }; $a = Closure::bind($f, new A); print_r($a());//这里输出103,绑定到A类 echo PHP_EOL; $b = Closure::bind($f, new B , 'B'); print_r($b());//这里输出1003,绑定到B类 echo PHP_EOL; $c = $sf->bindTo(null, 'C'); //注意这里:使用变量#sf绑定到C类,默认第一个参数为null print_r($c());//这里输出10003
もう一度デモを見てみましょう:
<?php /** * 复制一个闭包,绑定指定的$this对象和类作用域。 * * @author fantasy */ class Animal { private static $cat = "加菲猫"; private $dog = "汪汪队"; public $pig = "猪猪侠"; } /* * 获取Animal类静态私有成员属性 */ $cat = static function() { return Animal::$cat; }; /* * 获取Animal实例私有成员属性 */ $dog = function() { return $this->dog; }; /* * 获取Animal实例公有成员属性 */ $pig = function() { return $this->pig; }; $bindCat = Closure::bind($cat, null, new Animal());// 给闭包绑定了Animal实例的作用域,但未给闭包绑定$this对象 $bindDog = Closure::bind($dog, new Animal(), 'Animal');// 给闭包绑定了Animal类的作用域,同时将Animal实例对象作为$this对象绑定给闭包 $bindPig = Closure::bind($pig, new Animal());// 将Animal实例对象作为$this对象绑定给闭包,保留闭包原有作用域 echo $bindCat(),'<br>';// 输出:加菲猫,根据绑定规则,允许闭包通过作用域限定操作符获取Animal类静态私有成员属性 echo $bindDog(),'<br>';// 输出:汪汪队, 根据绑定规则,允许闭包通过绑定的$this对象(Animal实例对象)获取Animal实例私有成员属性 echo $bindPig(),'<br>';// 输出:猪猪侠, 根据绑定规则,允许闭包通过绑定的$this对象获取Animal实例公有成员属性
上記の例を通して、匿名バインディングを理解することは実際には難しくありません...拡張デモを見てみましょう(特性機能の紹介)
<?php /** * 给类动态添加新方法 * * @author fantasy */ trait DynamicTrait { /** * 自动调用类中存在的方法 */ public function __call($name, $args) { if(is_callable($this->$name)){ return call_user_func($this->$name, $args); }else{ throw new \RuntimeException("Method {$name} does not exist"); } } /** * 添加方法 */ public function __set($name, $value) { $this->$name = is_callable($value)? $value->bindTo($this, $this): $value; } } /** * 只带属性不带方法动物类 * * @author fantasy */ class Animal { use DynamicTrait; private $dog = '汪汪队'; } $animal = new Animal; // 往动物类实例中添加一个方法获取实例的私有属性$dog $animal->getdog = function() { return $this->dog; }; echo $animal->getdog();//输出 汪汪队
たとえば、現在のショッピング環境を使用します
<?php /** * 一个基本的购物车,包括一些已经添加的商品和每种商品的数量 * * @author fantasy */ class Cart { // 定义商品价格 const PRICE_BUTTER = 10.00; const PRICE_MILK = 30.33; const PRICE_EGGS = 80.88; protected $products = array(); /** * 添加商品和数量 * * @access public * @param string 商品名称 * @param string 商品数量 */ public function add($item, $quantity) { $this->products[$item] = $quantity; } /** * 获取单项商品数量 * * @access public * @param string 商品名称 */ public function getQuantity($item) { return isset($this->products[$item]) ? $this->products[$item] : FALSE; } /** * 获取总价 * * @access public * @param string 税率 */ public function getTotal($tax) { $total = 0.00; $callback = function ($quantity, $item) use ($tax, &$total) { $pricePerItem = constant(__CLASS__ . "::PRICE_" . strtoupper($item)); //调用以上对应的常量 $total += ($pricePerItem * $quantity) * ($tax + 1.0); }; array_walk($this->products, $callback); return round($total, 2); } } $my_cart = new Cart; // 往购物车里添加商品及对应数量 $my_cart->add('butter', 10); $my_cart->add('milk', 3); $my_cart->add('eggs', 12); // 打出出总价格,其中有 3% 的销售税. echo $my_cart->getTotal(0.03);//输出 1196.4
補足: クロージャは USE キーを使用して外部変数を接続できます。
要約: PHP クロージャの特性は、JS のクロージャはもちろんのこと、CLASS を使用して実際に同様の、またはさらに強力な機能を実現できます。今後の PHP のクロージャ サポートの改善に期待するほかありません。ただし、匿名関数は依然として非常に便利です。たとえば、preg_replace_callback などの関数を使用する場合、コールバック関数を外部で宣言する必要はありません。クロージャを適切に使用すると、コードをより簡潔で洗練されたものにすることができます。
以上がPHPでのクロージャのサンプルチュートリアルを要約します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。