PHPでクロージャーを使用する

PHP 5.3クロージャーの概要はその主要な革新の1つであり、リリースから数年が経過しましたが、この言語機能を使用するための標準的なプラクティスはまだありません。 この記事では、PHPでクロージャーを使用する最も興味深い可能性をすべて集めようとしました。



まず、それが何であるかを考えます-クロージャとPHPの機能は何ですか。



$g = 'test'; $c = function($a, $b) use($g){ echo $a . $b . $g; }; $g = 'test2'; var_dump($c); /* object(Closure)#1 (2) { ["static"]=> array(1) { ["g"]=> string(4) "test" } ["parameter"]=> array(2) { ["$a"] => string(10) "" ["$b"]=> string(10) "" } } */
      
      





ご覧のとおり、ラムダ関数のようなクロージャーは、渡されたパラメーターを格納するClosureクラスのオブジェクトです。 オブジェクトを関数として呼び出すために、__ invokeマジックメソッドがPHP5.3で導入されました。



 function getClosure() { $g = 'test'; $c = function($a, $b) use($g){ echo $a . $b . $g; }; $g = 'test2'; return $c; } $closure = getClosure(); $closure(1, 3); //13test getClosure()->__invoke(1, 3); //13test
      
      





use構造を使用して、ラムダ関数の親スコープからローカルスコープに変数を継承します。

構文はシンプルで簡単です。 Webアプリケーションの開発におけるこのような機能の使用は、完全に明確ではありません。 言語の新しい機能を使用して、いくつかの最新のフレームワークのコードを見て、それらのさまざまなアプリケーションをまとめようとしました。



コールバック関数



無名関数の最も明白な使用法は、コールバックとして使用することです。 PHPには、PHP 5.4で導入されたコールバックタイプまたはそのシノニムcallableを受け入れる多くの標準関数があります。 最も一般的なものは、array_filter、array_map、array_reduceです。 array_map関数は、配列要素の反復処理に使用されます。 コールバック関数が配列の各要素に適用され、処理された配列が結果として返されます。 私はすぐに、組み込み関数を使用して、ループ内で従来の配列処理のパフォーマンスを比較したいと思いました。 試しましょう。



 $x = range(1, 100000); $t = microtime(1); $x2 = array_map(function($v){ return $v + 1; }, $x); //Time: 0.4395 //Memory: 22179776 //--------------------------------------- $x2 = array(); foreach($x as $v){ $x2[] = $v + 1; } //Time: 0.0764 //Memory: 22174788 //--------------------------------------- function tr($v){ return $v + 1; } $x2 = array(); foreach($x as $v){ $x2[] = tr($v); } //Time: 0.4451 //Memory: 22180604
      
      





ご覧のとおり、多数の関数呼び出しのオーバーヘッドにより、パフォーマンスが著しく低下しますが、これは予想されることです。 テストは合成ですが、大きな配列を処理するタスクが頻繁に発生します。この場合、データ処理関数のアプリケーションは、アプリケーションの速度を大幅に低下させる可能性があります。 注意してください。 それにもかかわらず、現代のアプリケーションでは、このアプローチは非常に頻繁に使用されます。 特に、ハンドラが呼び出されたときではなく、どこかで宣言されている場合、コードをより簡潔にすることができます。



基本的に、このコンテキストでは、匿名関数の使用は、1つの機能を除いて、関数またはコールバック配列の文字列名を渡す従来の方法と変わりません-関数を作成するときにクロージャーを使用する、つまりスコープから変数を保存できます。 データベースに追加する前にデータの配列を処理する例を考えてみましょう。



 //  . $quoter = function($v) use($pdo){ return $pdo->quote($v);//    ,    :-) } $service->register('quoter', $quoter); … //-    //      PDO $data = array(...);//  $data = array_map($this->service->getQuoter(), $data); //   .
      
      





フィルタリングに匿名関数を使用すると非常に便利です。



 $x = array_filter($data, function($v){ return $v > 0; }); //vs $x = array(); foreach($data as $v) { if($v > 0){$x[] = $v} }
      
      





イベント。



クロージャーはイベントハンドラーとして理想的です。 例えば



 //-   . $this->events->on(User::EVENT_REGISTER, function($user){ //      .. }); $this->events->on(User::EVENT_REGISTER', function($user){ // email  . }); //    $this->events->trigger(User::EVENT_REGISTER, $user);
      
      





ロジックをイベントハンドラーから削除すると、一方でコードがクリーンになり、一方でエラーの検索が複雑になります。現在どのハンドラーがハングしているかわからない人にとっては、システムの動作が予期しないものになることがあります。



検証



クロージャは、スクリプトの実行時に実行される場合とされない場合がある変数のロジックを基本的に保持します。 これはバリデーターを実装するために必要なものです:



 $notEmpty = function($v){ return strlen($v) > 0 ? true : “    ”; }; $greaterZero = function($v){ return $v > 0 ? true : “    ”; }; function getRangeValidator($min, $max){ return function($v) use ($min, $max){ return ($v >= $min && $v <= $max) ? true : “    ”; }; }
      
      





後者の場合、別の関数を返す高階関数を使用します-定義済みの値境界を持つバリデーター。 バリデータは、たとえば次のように使用できます。



 class UserForm extends BaseForm{ public function __constructor() { $this->addValidator('email', Validators::$notEmpty); $this->addValidator('age', Validators::getRangeValidator(18, 55)); $this->addValidator('custom', function($v){ //some logic }); } /** *    . */ public function isValid() { … $validationResult = $validator($value); if($validationResult !== true){ $this->addValidationError($field, $validationResult); } … } }
      
      





フォームの使用は典型的な例です。 検証は、通常のクラス、モデルなどのセッターおよびゲッターでも使用できます。 ルールが関数の形式ではなく、構成中のルールの形式で記述されている場合、宣言型検証は優れたプラクティスと見なされますが、このアプローチは非常に役立つ場合があります。



表現



symfonyにはクロージャーの非常に興味深い用途があります。 ExprBuilderクラス 、フォームの式を構築できるエンティティを定義します



 ... ->beforeNormalization() ->ifTrue(function($v) { return is_array($v) && is_int(key($v)); }) ->then(function($v) { return array_map(function($v) { return array('name' => $v); }, $v); }) ->end() ...
      
      





私が理解するように、Symfonyでは、これはネストされた構成配列の処理を作成するために使用される内部クラスです(正しくない場合は修正してください)。 興味深いアイデアは、チェーンの形式で式を実装することです。 原則として、この形式で式を記述するクラスを実装することは非常に可能です。



 $expr = new Expression(); $expr ->if(function(){ return $this->v == 4;}) ->then(function(){$this->v = 42;}) ->else(function(){}) ->elseif(function(){}) ->end() ->while(function(){$this->v >=42}) ->do(function(){ $this->v --; }) ->end() ->apply(function(){/*Some code*/}); $expr->v = 4; $expr->exec(); echo $expr->v;
      
      





もちろん、アプリケーションは実験的なものです。 実際、これは何らかのアルゴリズムの記録です。 このような関数の実装はかなり複雑です-式は、理想的には操作のツリーを格納する必要があります。 興味深いコンセプト、おそらくどこかでそのようなデザインが役に立つでしょう。



ルーティング



多くのミニフレームワークでは、ルーティングは匿名関数で機能するようになりました。



 App::router('GET /users', function() use($app){ $app->response->write('Hello, World!'); });
      
      





十分かつ簡潔に快適。



キャッシング



それにもかかわらず、それはすでに議論されていました。



 $someHtml = $this->cashe->get('users.list', function() use($app){ $users = $app->db->table('users)->all(); return $app->template->render('users.list', $isers); }, 1000);
      
      





ここで、getメソッドはキー「users.list」を使用してキャッシュの有効性をチェックし、有効でない場合、データの関数にアクセスします。 3番目のパラメーターは、データストレージの期間を決定します。



オンデマンド初期化



いくつかのメソッドで呼び出すMailerサービスがあるとします。 使用する前に設定する必要があります。 毎回初期化しないように、遅延オブジェクト作成を使用します。



 //-   . $service->register('Mailer', function(){ return new Mailer('user', 'password', 'url'); }); //-   $this->service('Mailer')->mail(...);
      
      





オブジェクトの初期化は、最初に使用する前にのみ発生します。



オブジェクトの動作を変更する



スクリプトの実行中にオブジェクトの動作をオーバーライドすると便利な場合があります-メソッドの追加、古いメソッドのオーバーライドなど。 閉会はここで私たちを助けます。 PHP5.3では、このためにさまざまな回避策を使用する必要がありました。



 class Base{ public function publicMethod(){echo 'public';} private function privateMethod(){echo 'private';} //       . public function __call($name, $arguments) { if($this->$name instanceof Closure){ return call_user_func_array($this->$name, array_merge(array($this), $arguments)); } } } $b = new Base; //   $b->method = function($self){ echo 'I am a new dynamic method'; $self->publicMethod(); //        }; $b->method->__invoke($b); //    $b->method(); //      //call_user_func($b->{'method'}, $b); //  
      
      







原則として、古いメソッドを再定義できますが、それは同様の方法で定義された場合のみです。 あまり便利ではありません。 したがって、PHP 5.4では、クロージャーをオブジェクトに関連付けることが可能になりました。



 $closure = function(){ return $this->privateMethod(); } $closure->bindTo($b, $b); //     $closure();
      
      





もちろん、オブジェクトの変更は機能しませんでしたが、クロージャーはプライベート関数とプロパティにアクセスできます。



デフォルトのパラメーターとしてデータアクセス方法に渡す



GET配列から値を取得する例。 存在しない場合、値は関数を呼び出すことによって取得されます。

 $name = Input::get('name', function() {return 'Fred';});
      
      







高階関数



バリデータを作成する例はすでにあります。 リチウムフレームワークの例を挙げます



 /** * Writes the message to the configured cache adapter. * * @param string $type * @param string $message * @return closure Function returning boolean `true` on successful write, `false` otherwise. */ public function write($type, $message) { $config = $this->_config + $this->_classes; return function($self, $params) use ($config) { $params += array('timestamp' => strtotime('now')); $key = $config['key']; $key = is_callable($key) ? $key($params) : String::insert($key, $params); $cache = $config['cache']; return $cache::write($config['config'], $key, $params['message'], $config['expiry']); }; }
      
      





メソッドはクロージャを返します。クロージャを使用して、メッセージをキャッシュに書き込むことができます。



テンプレートに渡す



データをテンプレートに転送するだけでなく、たとえば、値を取得するためにテンプレートコードから呼び出すことができる構成済み関数を転送すると便利な場合があります。



 //  $layout->setLink = function($setId) use ($userLogin) { return '/users/' . $userLogin . '/set/' . $setId; }; //  <a href=<?=$this->setLink->__invoke($id);?>>Set name</a>
      
      





この場合、ユーザーエンティティへの複数のリンクがテンプレートで生成され、それらのリンクのアドレスにログインが表示されました。



再帰的クロージャー定義



最後に、再帰的クロージャーをどのように定義できるか。 これを行うには、使用するクロージャー参照を渡し、コードで呼び出します。 再帰の終了を忘れないでください



 $factorial = function( $n ) use ( &$factorial ) { if( $n == 1 ) return 1; return $factorial( $n - 1 ) * $n; }; print $factorial( 5 );
      
      







例の多くはピンと張っています。 何年も彼らなしで生きてきた-そして何もない。 ただし、クロージャを適用することは、PHPにとって十分自然な場合もあります。 この機能を上手に使用すると、コードが読みやすくなり、プログラマーの効率が向上します。 新しいパラダイムに合わせて思考を少し調整するだけで、すべてが適切に配置されます。 一般に、Pythonなどの他の言語でそのようなものがどのように使用されるかを比較することをお勧めします。 ここで誰かが自分自身のために新しい何かを見つけたことを願っています。 そしてもちろん、誰かがクロージャーの他の興味深いアプリケーションを知っているなら、私はあなたのコメントを本当に楽しみにしています。 よろしくお願いします!



All Articles