gamedevには、宇宙飛行士との共通点やPHPのイテレータとの連携があります





こんにちは、Habr。



たまたま最近、 Pro PHPという素晴らしい本を手に入れました。この本では、セクション全体がイテレーターに当てられています。 はい、このトピックはすでにHabréで(そしておそらく複数回)提起されていることは知っていますが、それでもこの記事を追加できるようにするつもりです。 上記の記事の例のほとんどは、現実とはまったく異なります。 そして-もしあなたがイテレータの助けを借りて解決しようとしている本当の問題に興味があるなら-catへようこそ。



イテレーターはどのような動物ですか?



実際、イテレーターは、子要素の特定の走査を単純化できるオブジェクトです。 phpにはIteratorインターフェースがあり、これを実装することで目的の効果を実現できます。 SPL( Standart PHP Library )には、最も一般的で人気のあるイテレーターを実装するいくつかのクラスも含まれています。 彼らのリストはここにあります



では、なぜイテレータが必要なのか-配列だけを使用できますか?



PHPでは、オブジェクトまたはデータの列挙が配列に単純に「追加」され、その要素をソートできることが歴史的に何らかの形で発生しました。 9つの等しい部分に分割された正方形(マップなど)で表される要素の特定のフィールドからのデータがある状況を想像してください。 そして、あなたはすべての正方形を時計回りに回る必要があり、配列ではそれらはランダムに折り畳まれます。 あまり便利ではありませんか?



したがって、この場合、イテレータが役立ちます。 配列に要素を追加する代わりに、それらをイテレータに追加すると、それらを簡単に反復処理できます。 隣接するマップ要素の列挙を実装するコードの例は、次のとおりです。



/** * @link https://bitbucket.org/t1gor/strategy/src/242e58cdcd60c61d02ae26d420da9d415117cb0d/application/model/map/MapTileNeighboursIterator.php?at=default */ class TileIterator implements Iterator { private $_side = 'north_west'; private $_neighbours = array(); private $_isValid = true; public function __construct($neighboursArray) { $this->_side = 'north_west'; $this->_neighbours = $neighboursArray; } /** * @return void */ function rewind() { $this->_side = 'north_west'; } /** * @return MapTile */ function current() { return $this->_neighbours[$this->_side]; } /** * @return string */ function key() { return $this->_side; } /** * Loop through neighbours clock-wise * * @return void */ function next() { switch ($this->_side) { case 'north_west': $this->_side = 'north'; break; case 'north': $this->_side = 'north_east'; break; case 'north_east': $this->_side = 'east'; break; case 'east': $this->_side = 'south_east'; break; case 'south_east': $this->_side = 'south'; break; case 'south': $this->_side = 'south_west'; break; case 'south_west': $this->_side = 'west'; break; // this is the end of a circle case 'west': $this->_isValid = false; break; } } function valid() { return $this->_isValid; } }
      
      





そして今、実際の呼び出し:

 //   , ..     $tilesStmt = PDO::prepare("SELECT * FROM tiles ... LIMIT 9"); $tilesStmt->execute(); $tiles = new TileIterator($tilesStmt->fetchAll());
      
      





それでは、誰にでも馴染みのある正しい順序でのみつぶします:

 foreach ($tiles as $tile) { ... }
      
      





はい、本当に悪くありません。 イテレータで他に何ができますか?



このトピックは非常に広範囲にわたるため、お気に入りの例を検討します。



LimitIteratorは、コードをデバッグまたはテストするときに非常に便利です。 特に、 PHPExcelで作業する場合 、文字列の反復処理で、ライブラリはRowIteratorクラスを使用します。 このクラスの名前はIteratorであることを意味します。 ドキュメントを解析するたびにすべての行が「ドラッグ」されるのを避けるために、 LimitIteratorRowIteratorをラップし、数十行だけで作業できます。



 //   ... $inputFileType = PHPExcel_IOFactory::identify('example.xlsx'); $objReader = PHPExcel_IOFactory::createReader($inputFileType); $document = $objReader->load($inputFile); $sheet = $document->getSheet(0); // ...     10  $dataForDebug = new LimitIterator($sheet->getRowIterator(), 0, 10);
      
      





FilterIteratorクラスを使用すると、オンザフライでデータを簡単にフィルタリングできます。 ある意味では、これはSQLクエリのWHERE部分に似ています。 たとえば、SDKがユーザーオブジェクトを返すBaseCamp Classic APIなどのサードパーティAPIを使用しているとします。 また、プロジェクトの変更については、一部をemialで通知する必要があります。 そして、3つのパラメーターに従って除外する必要があります:電子メール、ID、および名前。 前述のクラスにより、これを簡単に実行してサポートできます。



 /** * @link http://ua2.php.net/FilterIterator */ class NotificationFilter extends FilterIterator { /** *      */ private $_skip; /** * Build filter * * @param Iterator $iterator * @param array $filter -    ,    * @throws InvalidArgumentException */ public function __construct(Iterator $iterator, $filter) { if (!is_array($filter)) { throw new InvalidArgumentException("Filter should be an array. ".gettype($filter)." given."); } parent::__construct($iterator); $this->_skip = $filter; } /** * Check user data and make sure we can notify him/her * * Filtering by 2 params: * - Does the user belong to your company (avoid spamming clients)? * - Should we skipp the user based on the user ID * - Should we skipp the user based on the user email * * @link http://php.net/manual/filteriterator.accept.php * @link https://github.com/sirprize/basecamp/blob/master/example/basecamp/person/get-by-id * * @return bool */ public function accept() { // get current user from the Iterator $bcUser = $this->getInnerIterator()->current(); // check if skipped by ID $skippedById = in_array($bcUser->getId(), $this->_skip['byID']); // or by email $skippedByEmail = in_array($bcUser->getEmailAddress(), $this->_skip['byEmail']); // check that he/she belongs to your company $belongsToCompany = $yourCompanyBaseCampID === (int) $bcUser->getCompanyId()->__toString(); // notify only if belongs to your company and shouldn't be skipped return $belongsToCompany && !$skippedById && !$skippedByEmail; } }
      
      





したがって、NotificationFilter :: accept()メソッドでは、1人のユーザーのみを操作します。



また、 RecursiveIteratorIteratorを使用して多次元配列を1次元配列に簡単にキャストできます。RecursiveDirectoryIteratorなどを使用してディレクトリのファイルリストを取得すると便利です。



そして、宇宙プログラムはどこにありますか?



はい、ほとんど忘れていました。 イテレーターで「遊んで」いる間、イテレーターの使い方を自分で理解しようとして、次のアイデアがありました-GameDevハブとWeb開発の両方にあるHabrの投稿だけを読むにはどうすればいいですか? フィードでは、両方のハブから投稿を読み取ることができますが、意味がわかっていれば、投稿の共通部分は閲覧できません。 その結果、イテレーターを使用した小さなプロジェクトができました。



すべてのプロジェクトコードはBitBucketリポジトリにありますが、ここでは最も興味深い部分のみを公開します。 以下のコード:

 /** * Basic post class */ class HabraPost { public $name = ''; public $url = ''; public $hubs = null; public static $baseUrl = 'http://habrahabr.ru/hub/'; /** * Some hubs links */ protected static $fullHubList = array( 'infosecurity' => ' ', 'webdev' => '-', 'gdev' => 'Game Development', 'DIY' => 'DIY   ', 'pm' => ' ', 'programming' => '', 'space' => '', 'hardware' => '', 'algorithms' => '', 'image_processing' => ' ', ); public function __construct($name, $url, $hubs = array()) { $this->name = $name; $this->url = $url; $this->hubs = $hubs; } public static function getFullHubsList() { $list = self::$fullHubList; asort($list); return $list; } } /** * Post storage object * * @link http://php.net/manual/class.splobjectstorage.php */ class PostsStorage { private $_iterator; public function __construct() { $this->_iterator = new SplObjectStorage(); } /** * Add new post * * @param HabraPost $post * @return void */ public function save(HabraPost $post) { // reduce duplicates if (!$this->_iterator->contains($post)) { $this->_iterator->attach($post); } } /** * Get internal iterator * * @return SplObjectStorage */ public function getIterator() { return $this->_iterator; } } /** * Posts filtering class * * @link http://php.net/manual/class.filteriterator.php */ class HabraPostFilter extends FilterIterator { /** * Hubs to filter by */ private $_filterByHubs = array(); public function __construct(Iterator $iterator, $filteringHubs) { parent::__construct($iterator); $this->_filterByHubs = $filteringHubs; } /** * Accept * * @link http://php.net/manual/filteriterator.accept.php * @return bool */ public function accept() { $object = $this->getInnerIterator()->current(); $aggregate = true; foreach ($this->_filterByHubs as $filterHub) { $aggregate = $aggregate && in_array($filterHub, $object->hubs); } return $aggregate; } }
      
      





だから-アイデアは非常に簡単です:

  1. ユーザーは1つ以上のハブを選択し、
  2. 利用可能なHabrページを反復処理し、コンテンツへのリンクを収集し、
  3. これらすべてをPostsStorage



  4. HabraPostFilter



    使用したフィルタリング


その結果、スクリーンショットに似たものが得られます。



GameDev + Web開発








Habra効果に耐えられるホスティングを誰かが親切に提供してくれたら、プロジェクトを無料で利用できるようになります。



ご清聴ありがとうございました。



PS投稿へのコメントまたは個人的な通信で訂正/コメントを受け付けます。



UPD 誤字を訂正してくれたNikita_Rogatnevに感謝します。

UPD デモをありがとうhell0w0rd



All Articles