前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【译】现代化的PHP开发--迭代器Iterator

【译】现代化的PHP开发--迭代器Iterator

作者头像
Lemon黄
发布2019-10-14 17:45:30
2.2K0
发布2019-10-14 17:45:30
举报
文章被收录于专栏:Lemon黄Lemon黄

点击上方关注Lemon黄,么么哒!

听说

与魔鬼战斗的人,应当小心自己不要成为魔鬼。当你远远凝视深渊时,深渊也在凝视你。

——尼采

来源:https://www.startutorial.com/articles/view/modern-php-developer-iterator

译/Lemon黄

如果在PHP中使用过for循环,那么迭代的思想对我们而言并不陌生。将数组传递给for循环,并在循环内执行一些逻辑,但是你知道实际上可以将数组以外的数据结构传递给for循环吗?这就是迭代器(Iterator)可以发挥作用的地方。

1、Iterator的定义

以下是Wikipedia(维基百科)中对迭代器的摘要定义:

在计算机编程中,迭代器是使程序员能够遍历容器(尤其是列表)的对象。请注意,迭代器执行遍历并且还可以访问容器中的数据元素,但不执行迭代。 迭代器在行为上类似于数据库游标。

这里要记住一些关键点:

  • 迭代器使我们能够遍历容器。它类似于数组。
  • 迭代器不执行迭代。for进行了迭代。其他循环类型,例如foreach和while做迭代。

现在我们知道了Iterator(迭代器,下文不再做翻译)的定义,这个概念可能仍然有些晦涩,但是不用担心,我们还没有讲完。现在,我们已经知道了Iterator的工作原理类似于array,并且可以在for循环中进行遍历。

了解数组在for循环中的实际工作方式将对我们很有帮助。让我们看一下下面的代码:

代码语言:javascript
复制
$data = array(1,2,3,4);
for ($i=0; $i<count($data); $i++) {
    $key = $i;
    $value = $data[$i];
}

从上我们可以知道数组在for循环中的工作方式:

  • 步骤1,我们将$ i设置为0.($ i = 0)
  • 步骤2,我们检查$ i小于$ data的长度。($i<count($data) )
  • 步骤3,我们将$ i值增加1。($ i ++)
  • 步骤4,我们可以访问当前元素的键。($ key = $ i)
  • 步骤5,我们还可以获取当前元素的值。($ value = $ data [$ i])

我们可以将这些步骤抽象为简单的函数,如下所示:

  • Step 1 = rewind().
  • Step 2 = valid().
  • Step 3 = next().
  • Step 4 = key().
  • Step 5 = current().

在抽象级别上,我们可以想象,只要一个对象提供上述五个功能,就可以通过for循环遍历它。

实际上,迭代器不过是一个类,它实现了上面提到的所有五个步骤。在PHP中,标准PHP库(SPL)是旨在解决常见问题的接口和类的集合,它提供了标准的Iterator接口。

代码语言:javascript
复制
Iterator extends Traversable {
    /* Methods */
    abstract public mixed current ( void )
    abstract public scalar key ( void )
    abstract public void next ( void )
    abstract public void rewind ( void )
    abstract public boolean valid ( void )
}

2、自定义迭代器类

现在我们了解了迭代器是什么,是时候我们可以自己构建一个迭代器了。

我们的第一个迭代器代表了来自Github上的十大最受关注的PHP存储库。我们可以将其传递给foreach并像数组一样遍历它。我们将其命名为TrendingRepositoriesIterator。

首先,我们需要使我们的类实现Iterator接口。

代码语言:javascript
复制
class TrendingRepositoriesIterator implements Iterator
{
    public function rewind()
{
    }
 
    public function valid()
{
    }
 
    public function next()
{
    }
 
    public function key()
{
    }
 
    public function current()
{
    }
}

迭代器必须始终实现上述五个方法。TrendingRepositoriesIterator类的最终代码如下:

代码语言:javascript
复制
class TrendingRepositoriesIterator implements Iterator
{
    private $repos = [];
    private $pointer = 0;
    public function __construct()
{
        $this->populate();
    }
    public function rewind()
{
        $this->pointer = 0;
    }
    public function valid()
{
        return isset($this->repos[$this->pointer]);
    }
    public function next()
{
        $this->pointer++;
    }
    public function key()
{
        return $this->pointer;
    }
    public function current()
{
        return $this->repos[$this->pointer];
    }
    private function populate()
{
        $client = new GuzzleHttp\Client();
        $res = $client->request('GET', 'https://api.github.com/search/repositories', [ 'query' => ['q' => 'language:php', 'sort' => 'stars', 'order' => 'desc']]);
        $resInArray = json_decode($res->getBody(), true);
        $trendingRepos = array_slice($resInArray['items'], 0, 10);
        foreach ($trendingRepos as $rep) {
            $this->repos[] = $rep['name'];
        }
    }
}
  • public function populate():我们将不对此功能进行深入的介绍,因为这将违反我们本节的目的。基本上,此功能通过Github的公共API来从Github获取前10个已启动的PHP存储库,并将它们存储到$repos属性中。
  • private $repos:我们使用此属性来存储获取的存储库。
  • private $pointer:我们可以使用数组的内部指针来完成这项工作,但是由于我们正在构建自己的迭代器,因此我们希望保留完全的控制权。
  • public function __construct(): 实例化对象时,初始化$repos属性获取目标存储库。
  • public function rewind(): 我们可以使用它来将指针设置为第一个位置。
  • public function valid():只要设置了当前指针的值,它就有效。
  • public function next():用于将指针增加1个位置。
  • public function current():我们可以通过该函数返回当前指针的值。

让我们看一下TrendingRepositoriesIterator的用例,它可以像数组一样使用:

代码语言:javascript
复制
$trendingRepositoriesIterator = new TrendingRepositoriesIterator();
 
foreach ($trendingRepositoriesIterator as $repository) {
    echo $repository . "\n";
}
 
// 输出
laravel
symfony
CodeIgniter
DesignPatternsPHP
Faker
yii2
composer
WordPress
sage
cakephp

太棒了!现在,我们已经编写了第一个迭代器,正如你所看到的,它实际上非常容易和直接。

3、为什么要使用迭代器?

可能你仍然想知道为什么我们需要使用迭代器。我们不能只使用数组吗?答案是肯定的。在大多数情况下,虽然迭代器确实具有一些关键优势,但数组将足以胜任这项工作,我们将在后面分享这些优势。请记住,我们绝不建议在任何情况下都使用迭代器。

3.1、封装形式

在我们的第一个迭代器TrendingRepositoriesIterator中,遍历Github存储库的详细信息从外部获取,在内部隐藏完成。我们可以更新如何获取数据,从何处获取数据以及如何遍历资源。客户端代码无需更改。这就是所谓的封装,是面向对象编程的关键概念之一。

其他示例包括:

要遍历MySQl结果,我们可以使用:

代码语言:javascript
复制
$result = mysql_query("SELECT * FROM books");
 
// Iterate over the structure
while ( $row = mysql_fetch_array($result) ) {
    // do stuff
}

要遍历文本文件的内容,我们可以:

代码语言:javascript
复制
$fh = fopen("books.txt", "r");
// Iterate over the structure
while (!feof($fh)) {
   $line = fgets($fh);
   // do stuff with the line here
}

使用迭代器,我们可以封装遍历资源的过程,以便外部世界不了解内部操作。实际上,外界不需要知道我们从何处获取数据或如何以循环方式遍历数据。他们需要知道的是,他们可以像下面这样简单地进行迭代:

代码语言:javascript
复制
$bookIterator = new BookIterator();
foreach($bookIterator as $book) {
    // do stuff with $book
}

封装是一个非常强大的概念,它使我们能够编写简洁的代码。

3.2、高效的内存使用

有效的内存使用是迭代器的主要优势。

在我们的TrendingRepositoriesIterator类中,我们实际上可以动态地获取资源,这意味着仅当调用next()方法时,才从Github API获取数据。这种技术被称为懒加载。它仅在需要时才生成值,因此可以帮助我们节省大量内存。

3.3、易于添加其他功能

使用迭代器的另一个好处是我们可以装饰它以添加其他功能。以我们的TrendingRepositoriesIterator类为例。我们想从资源中排除“ laravel”。一种明显的方法是更新我们的原始类,尽管这当然不是我们在此要做的。

我们可以使用SPL的CallbackFilterIterator装饰原始的迭代器,而TrendingRepositoriesIterator完全不需要更改。

代码语言:javascript
复制
$trendingRepositoriesIterator = new TrendingRepositoriesIterator();
    return $value != 'laravel';
});
foreach ($newTrendingRepositoriesIterator as $repository) {
    echo $repository . "\n";
}
// 输出
symfony
CodeIgniter
DesignPatternsPHP
Faker
yii2
composer
WordPress
sage
cakephp

最有意义的部分是没有对象的重复。仅当TrendingRepositoriesIterator命中next()方法时,才会触发该回调,然后将相应地应用该逻辑。这是节省内存和提高性能的好方法。

4、SPL迭代器

既然我们了解了使用迭代器的强大功能和好处,那么使用迭代器解决合适的问题是一个很好的实践。但是,如果在遇到新问题时都要我们自己编写迭代器,则这将非常耗时,因为它确实需要我们实现一组预定义的函数。

幸运的是,PHP在提供了一组迭代器以解决一些常见问题方面做得很好。在以下各节中,我们将研究SPL提供的一组通用迭代器。再回顾一下,标准PHP库的SPL标准旨在提供一组接口和类,以解决常见问题。

5、ArrayObject与SPL ArrayIterator

在PHP中,数组是八种基本类型之一。PHP提供了79个函数来处理与数组相关的任务(参考)。使用数组是完全合适的,但是有时我们可能希望将数组用作对象,这具体取决于我们对面向对象编程的了解。在这种情况下,PHP提供了两个类来使数组成为面向对象代码中的一等公民。

5.1、ArrayObject

第一个我们可以选择的类是ArrayObject类。此类允许对象作为数组操作。

让我们看一下它的类签名:

代码语言:javascript
复制
ArrayObject implements IteratorAggregate , ArrayAccess , Serializable , Countable{
 ...
 public ArrayIterator getIterator ( void )
 ...
}

如上所述,ArrayObject实现了IteratorAggregate。 那么什么是IteratorAggregate呢?它是创建外部迭代器的接口。简而言之,它是创建迭代器的快速方法,而不是使用五个方法(rewind,valid,current,key and valu)实现Iterator接口,IteratorAggregate允许你将该任务委托给另一个迭代器。你需要做的就是实现一个方法getIterator()。

代码语言:javascript
复制
IteratorAggregate extends Traversable {
    abstract public Traversable getIterator ( void )
}

ArrayObject实现IteratorAggregate。它为迭代器功能创建一个外部ArrayIterator。

当ArrayObject实现IteratorAggregate时,我们可以像数组一样在foreach循环中使用它。

代码语言:javascript
复制
$books = array(
    'Head First Design Patterns',
    'Clean Code: A Handbook of Agile Software Craftsmanship',
    'Domain-Driven Design: Tackling Complexity in the Heart of Software',
    'Agile Software Development, Principles, Patterns, and Practices',
);
 
$booksAsArrayObject = new ArrayObject($books);
 
foreach ($booksAsArrayObject as $book) {
    echo $book . "\n";
}
 
// 输出
Head First Design Patterns
Clean Code: A Handbook of Agile Software Craftsmanship
Domain-Driven Design: Tackling Complexity in the Heart of Software
Agile Software Development, Principles, Patterns, and Practices

我们要使用ArrayObject的主要原因是可以以面向对象的方式来使用数组。

代码语言:javascript
复制
books = array(
    'Head First Design Patterns',
    'Clean Code: A Handbook of Agile Software Craftsmanship',
    'Domain-Driven Design: Tackling Complexity in the Heart of Software',
    'Agile Software Development, Principles, Patterns, and Practices',
);
 
$booksAsArrayObject->append('The Pragmatic Programmer: From Journeyman to Master'); // --- vs ---
$books[] = 'The Pragmatic Programmer: From Journeyman to Master';

5.2、ArrayIterator

ArrayIterator的工作方式类似于ArrayObject。

让我们看看它的类签名:

代码语言:javascript
复制
ArrayIterator implements ArrayAccess , SeekableIterator , Countable , Serializable {
}

就它们实现的接口而言,它几乎与ArrayObject相同。唯一的区别是,它不是ArrayObject实现的ArrayIterator接口,而是实现了SeekableIterator。

我们使用ArrayIterator的方式与在foreach循环中使用ArrayObject的方式相同:

代码语言:javascript
复制
$books = array(
    'Head First Design Patterns',
    'Clean Code: A Handbook of Agile Software Craftsmanship',
    'Domain-Driven Design: Tackling Complexity in the Heart of Software',
    'Agile Software Development, Principles, Patterns, and Practices',
);
$booksAsArrayIterator = new ArrayIterator($books);
foreach ($booksAsArrayIterator as $book) {
    echo $book . "\n";
}
// Output
Head First Design Patterns
Clean Code: A Handbook of Agile Software Craftsmanship
Domain-Driven Design: Tackling Complexity in the Heart of Software
Agile Software Development, Principles, Patterns, and Practices

以面向对象的方式使用数组:

代码语言:javascript
复制
$books = array(
    'Head First Design Patterns',
    'Clean Code: A Handbook of Agile Software Craftsmanship',
    'Domain-Driven Design: Tackling Complexity in the Heart of Software',
    'Agile Software Development, Principles, Patterns, and Practices',
);
$booksAsArrayIterator = new ArrayIterator($books);
$booksAsArrayIterator->append('The Pragmatic Programmer: From Journeyman to Master'); // --- vs ---
$books[] = 'The Pragmatic Programmer: From Journeyman to Master';

5.3、Comparison

您可能想知道何时使用ArrayObject和何时使用ArrayIterator。重要的是要了解ArrayObject和ArrayIterator之间的区别和关系。

正如我们在ArrayObject部分中已经发现的那样,ArrayObject实际上将ArrayIterator创建为外部迭代器。可以说ArrayIterator做了ArrayObject的工作,并且它提供了更多的功能,特别是寻找位置。这是通过实现SeekableIterator来完成的。

除了将指针作为迭代器从上到下移动之外,它还允许随机跳转到某个位置。

代码语言:javascript
复制
$books = array(
    'Head First Design Patterns',
    'Clean Code: A Handbook of Agile Software Craftsmanship',
    'Domain-Driven Design: Tackling Complexity in the Heart of Software',
    'Agile Software Development, Principles, Patterns, and Practices',
);
$booksAsArrayIterator = new ArrayIterator($books);
$booksAsArrayIterator->seek(3);
echo $booksAsArrayIterator->current();
// 输出
Agile Software Development, Principles, Patterns, and Practices

最后,ArrayIterator是SPL的一部分,而ArrayObject不是。

6、迭代文件系统

列出给定目录的内容是一项非常常见的任务。PHP提供了许多用于处理文件系统的功能。其中之一是scandir()。

假设给我们一个任务,以列出给定目录中的所有文件,如下所示:

代码语言:javascript
复制
---books
|   ---book_item_1.txt
|   ---book_item_2.txt
|   ---book_item_3.txt
|    ---book_item_4.txt

我们可以通过scandir()完成它,如下所示:

代码语言:javascript
复制
$books = scandir("books");
foreach($books as $book) {
    echo $book . "\n";
}
// 输出
.
..
book_item_1.txt
book_item_2.txt
book_item_3.txt
book_item_4.txt

这是两个虚拟目录(“.”和“ ..”),您可以在文件系统的每个目录中找到它们。

由于本节是关于迭代器的,因此我们将介绍一些用于处理文件系统的迭代器。希望在您的下一个项目中,您将能够利用其中的一些。三个方便的迭代器派上用场:DirectoryIterator,FilesystemIterator和RecursiveDirectoryIterator。

在研究它们中的每一个之前,先了解一下它们的继承关系是很有用的:

代码语言:javascript
复制
DirectoryIterator extends SplFileInfo
FilesystemIterator extends DirectoryIterato
RecursiveDirectoryIterator extends FilesystemIterator

6.1、DirectoryIterator

DirectoryIterator类提供了一个用于查看文件系统目录内容的简单接口。

为了完成相同的任务,我们可以使用DirectoryIterator:

代码语言:javascript
复制
$books = new DirectoryIterator('books');
foreach($books as $book) {
    echo $book->getFilename() . "\n";
}
// 输出
.
..
book_item_1.txt
book_item_2.txt
book_item_3.txt
book_item_4.txt

创建DirectoryIterator对象所需的唯一参数是目录的路径。与scandir函数相比,DirectoryIterator返回一个对象,而不是文件名作为字符串。该对象包含与文件有关的各种信息,我们可以使用这些信息。

6.2、FilesystemIterator

要通过使用FilesystemIterator完成相同的任务,我们可以使用:

代码语言:javascript
复制
$books = new FilesystemIterator('books');
foreach($books as $book) {
    echo $book->getFilename() . "\n";
}
//输出
book_item_1.txt
book_item_2.txt
book_item_3.txt
book_item_4.txt

它看起来与DirectoryIterator几乎相同,除了FilesystemIterator自动过滤出两个虚拟目录。

他们真的一样吗?我们可以使用一种简单的方法来区分这些差异:

代码语言:javascript
复制
$books = new DirectoryIterator('books');
foreach($books as $key=>$value) {
    echo $key . ' is a type of '. gettype($key) . "\n";
    echo $value . ' is a type of '. get_class($value) . "\n";
}
 
echo '-------------------------'."\n";
 
$books = new FilesystemIterator('books');
foreach($books as $key=>$value) {
    echo $key . ' is a type of '. gettype($key) . "\n";
    echo $value . ' is a type of '. get_class($value) . "\n";
}

从CLI运行上述脚本的结果是:

代码语言:javascript
复制
0 is a type of integer
. is a type of DirectoryIterator
1 is a type of integer
.. is a type of DirectoryIterator
2 is a type of integer
book_item_1.txt is a type of DirectoryIterator
3 is a type of integer
book_item_2.txt is a type of DirectoryIterator
4 is a type of integer
book_item_3.txt is a type of DirectoryIterator
5 is a type of integer
book_item_4.txt is a type of DirectoryIterator
 
--------------------------------
 
books/book_item_1.txt is a type of string
books/book_item_1.txt is a type of SplFileInfo
books/book_item_2.txt is a type of string
books/book_item_2.txt is a type of SplFileInfo
books/book_item_3.txt is a type of string
books/book_item_3.txt is a type of SplFileInfo
books/book_item_4.txt is a type of string
books/book_item_4.txt is a type of SplFileInfo

现在我们可以看到它们在内部实际上是完全不同的:

  • DirectoryIterator在循环中返回一个整数作为键,并返回一个DirectoryIterator作为值。
  • FilesystemIterator返回完整路径的字符串作为键,并返回SplFileInfo对象作为循环中的值。

实际上,FilesystemIterator具有更多的灵活性。创建FilesystemIterator对象时,它类似于DirectoryIterator接受目录路径作为第一个参数。此外,您可以选择将第二个参数作为标志传递。该标志能够配置此功能的各个方面。

  • FilesystemIterator :: CURRENT_AS_PATHNAME:此标志将使FilesystemIterator返回文件路径而不是SplFileInfo对象作为值。
  • FilesystemIterator :: CURRENT_AS_FILEINFO:此标志将使FilesystemIterator返回SplFileInfo对象作为值。这是默认行为。 我们不必显式设置它。
  • FilesystemIterator :: CURRENT_AS_SELF:此标志将使FilesystemIterator返回FilesystemIterator本身作为值。
  • FilesystemIterator :: KEY_AS_PATHNAME:此标志将使FilesystemIterator返回文件路径作为键。这是默认行为。您不必显式设置它。
  • FilesystemIterator :: KEY_AS_FILENAME:此标志将使FilesystemIterator返回文件名和扩展名而不是文件路径作为键。
  • FilesystemIterator :: FOLLOW_SYMLINKS:该标志将使RecursiveDirectoryIterator :: hasChildren()遵循符号链接。
  • FilesystemIterator :: NEW_CURRENT_AND_KEY:此标志有助于一次设置另外两个标志(FilesystemIterator :: KEY_AS_FILENAME和FilesystemIterator :: CURRENT_AS_FILEINFO)。
  • FilesystemIterator :: SKIP_DOTS:此标志将使FilesystemIterator忽略虚拟目录(“.”和“ ..”)。
  • FilesystemIterator :: UNIX_PATHS:尽管PHP脚本运行在哪个系统上,但该标志将使FilesystemIterator使用Unix样式的目录splitter()。

7、展望CachingIterator

在本节中,我们将介绍一个迭代器,该迭代器可以窥视迭代中的下一个元素。此功能使我们能够做很多有用的事情,例如在迭代器到达列表末尾时执行不同的操作。

具有这种强大功能的类是CachingIterator。

首先让我们看一下它的类签名,然后,我们将详细介绍它的用法。

代码语言:javascript
复制
CachingIterator extends IteratorIterator

CachingIterator继承自IteratorIterator。 什么是IteratorIterator? 它只是引擎盖下另一个迭代器的包装。 它将把五个Itertator方法(rewind(),current(),key(),valid(),next())调用转发给它所环绕的迭代器。 我们还可以通过调用方法getInnerIterator()来检索内部迭代器。

由于此类的性质,内部迭代器的指针总是比CachingIterator向前移动一步,并且CachingIterator提供了一个hasNext()方法来告诉我们它是否到达列表的末尾。 这就是CachingIterator向前看的方式。

现在,让我们来实践一下。

代码语言:javascript
复制
$books = array(
    'Head First Design Patterns',
    'Clean Code: A Handbook of Agile Software Craftsmanship',
    'Domain-Driven Design: Tackling Complexity in the Heart of Software',
    'Agile Software Development, Principles, Patterns, and Practices',
);
$booksAsCachingIterator = new CachingIterator(new ArrayIterator($books));
foreach ($booksAsCachingIterator as $book) {
    echo 'current book - ' . $book . PHP_EOL;
    if ($booksAsCachingIterator->hasNext()) {
        echo '----------------------------' . PHP_EOL;
    }
}

在CLI中运行上述脚本的结果:

代码语言:javascript
复制
current book - Head First Design Patterns
next book - Clean Code: A Handbook of Agile Software Craftsmanship
 
----------------------------
 
current book - Clean Code: A Handbook of Agile Software Craftsmanship
next book - Domain-Driven Design: Tackling Complexity in the Heart of Software
 
----------------------------
 
current book - Domain-Driven Design: Tackling Complexity in the Heart of Software
next book - Agile Software Development, Principles, Patterns, and Practices
 
----------------------------
 
current book - Agile Software Development, Principles, Patterns, and Practices

与其他迭代器类似,要创建CachingIterator实例,我们将迭代器作为第一个参数传递给类承包商。正如我们所看到的,向前偷看的真正魔力是由hasNext()方法提供的。 该方法可以告诉我们是否存在下一个立即元素。

除了第一个参数之外,CachingIterator还可以选择接受第二个参数作为标志。

  • CachingIterator :: CALL_TOSTRING:它将返回当前元素的__toString作为值。 这是默认行为。
  • CachingIterator :: CATCH_GET_CHILD:它将捕获访问子级时引发的所有异常。
  • CachingIterator :: TOSTRING_USE_KEY:将迭代器强制转换为循环中的字符串时,它将返回键值。
代码语言:javascript
复制
$books = array(
    'Head First Design Patterns',
    'Clean Code: A Handbook of Agile Software Craftsmanship',
    'Domain-Driven Design: Tackling Complexity in the Heart of Software',
    'Agile Software Development, Principles, Patterns, and Practices',
);
 
$booksAsCachingIterator = new CachingIterator(new ArrayIterator($books), CachingIterator::TOSTRING_USE_KEY;
 
foreach ($booksAsCachingIterator as $key=>$book) {
    echo $booksAsCachingIterator . PHP_EOL;
}
// 输出 0
1
2
3
  • CachingIterator :: TOSTRING_USE_CURRENT:将迭代器强制转换为循环中的字符串时,它将返回当前值。
代码语言:javascript
复制
$books = array(
    'Head First Design Patterns',
    'Clean Code: A Handbook of Agile Software Craftsmanship',
    'Domain-Driven Design: Tackling Complexity in the Heart of Software',
    'Agile Software Development, Principles, Patterns, and Practices',
);
 
$booksAsCachingIterator = new CachingIterator(new ArrayIterator($books), CachingIterator::TOSTRING_USE_CURRENT);
 
foreach ($booksAsCachingIterator as $key=>$book) {
    echo $booksAsCachingIterator . PHP_EOL;
}
// 输出
Head First Design Patterns
Clean Code: A Handbook of Agile Software Craftsmanship
Domain-Driven Design: Tackling Complexity in the Heart of Software
Agile Software Development, Principles, Patterns, and Practices
  • CachingIterator :: TOSTRING_USE_INNER:当将迭代器强制转换为循环中的字符串时,它将返回强制转换为字符串的内部迭代器。 如果在与前面的示例相同的代码中设置此标志,则它将引发异常。 这是因为ArrayIterator没有实现__toString()方法。
  • CachingIterator :: FULL_CACHE:如果CachingIterator无法执行某种类型的缓存,则其名称中不会包含关键字“ caching”。 设置此标志后,如果需要迭代结果以备将来使用,它将缓存结果。

8、 生成器Generator

现在,我们对迭代器的好处深信不疑。它们封装了遍历的详细信息,并且比创建内存数组要有效得多。但是,一切都有其代价。要创建迭代器,我们仍然必须实现SPL Iterator接口。您可能对迭代器感到恐惧,并且不想实现Iterator接口所约定的这五个方法。实施它们非常耗时,有时甚至很复杂。

从PHP 5.5开始,我们将不会再受到这个困扰。 PHP引入了一些生成器,它们提供了一种简单的方法来实现简单的迭代器,而又不会增加实现迭代器接口的类的开销或复杂性。

究竟是什么生成器? 生成器类似于普通的PHP函数,不同之处在于它具有特殊的关键字“ yield”。

以下是生成器功能的简单示例。 在实际的应用程序中,我们将不会有这样的生成器-此处仅用于演示:

代码语言:javascript
复制
function booksGenerator()
{
    $books = array(
        'Head First Design Patterns',
        'Clean Code: A Handbook of Agile Software Craftsmanship',
        'Domain-Driven Design: Tackling Complexity in the Heart of Software',
        'Agile Software Development, Principles, Patterns, and Practices',
    );
 
    foreach ($books as $book) {
        yield $book;
    }
}
 
foreach (booksGenerator() as $book) {
    echo $book . PHP_EOL;
}
 
// 输出
Head First Design Patterns
Clean Code: A Handbook of Agile Software Craftsmanship
Domain-Driven Design: Tackling Complexity in the Heart of Software
Agile Software Development, Principles, Patterns, and Practices

当内部发现yield关键字时,PHP在内部实现了生成器功能。首次调用生成器函数时,PHP将创建一个Generator对象。这个Generator对象是内部类Generator的一个实例,并且Generator类实现Iterator接口。这样,用户就可以创建迭代器而无需编写合同规定的代码,这一切都要归功于PHP Generator。

当我们需要提供步长值时,将调用yield。 将其视为常规迭代器中函数或当前方法的返回。

让我们将第一个迭代器类TrendingRepositoriesIterator中的一个转换为生成器函数:

代码语言:javascript
复制
function trendingRepositoriesGenerator()
{
    $client = new GuzzleHttp\Client();
    $res = $client->request('GET', 'https://api.github.com/search/repositories', [ 'query' => ['q' => 'language:php', 'sort' => 'stars', 'order' => 'desc'] ]);
    $resInArray = json_decode($res->getBody(), true);
    $trendingRepos = array_slice($resInArray['items'], 0, 10);
    foreach ($trendingRepos as $rep) {
        yield $rep['name'];
    };
}

事实证明,使用生成器的代码要少得多。 我们也可以像使用TrendingRepositoriesIterator一样,在foreach循环中使用它:

代码语言:javascript
复制
foreach (trendingRepositoriesGenerator() as $repo) {
    echo $repo . PHP_EOL;
}

请注意,生成器本身并没有提供任何特殊功能,它们只是使创建迭代器更加简单。 换句话说,它们绝对不是迭代器的替代品。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Lemon黄 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档