3 回答

TA貢獻1887條經驗 獲得超5個贊
好吧,這至少是一個有趣的思想實驗……
class BeforeAfterIterator implements Iterator
{
private $iterator;
public function __construct(iterable $iterator, $before, $after)
{
if (!$iterator instanceof Iterator) {
$iterator = (function ($iterable) {
yield from $iterable;
})($iterator);
}
if ($iterator->valid()) {
$this->iterator = new AppendIterator();
$this->iterator->append(new ArrayIterator([$before]));
$this->iterator->append($iterator);
$this->iterator->append(new ArrayIterator([$after]));
} else {
$this->iterator = new ArrayIterator([]);
}
}
public function current()
{
return $this->iterator->current();
}
public function next()
{
$this->iterator->next();
}
public function key()
{
return $this->iterator->key();
}
public function valid()
{
return $this->iterator->valid();
}
public function rewind()
{
$this->iterator->rewind();
}
}
示例用法:
function generator() {
foreach (range(1, 5) as $x) {
yield "<li>$x";
}
}
$beforeAfter = new \BeforeAfterIterator(generator(), '<ul>', '</ul>');
foreach ($beforeAfter as $value) {
echo $value, PHP_EOL;
}
輸出
<ul>
<li>1
<li>2
<li>3
<li>4
<li>5
</ul>
如果您傳遞的迭代器沒有產生任何值,則不會得到任何輸出。
我絕不贊同這是一個好主意,這完全取決于你。我確信有一些更優雅的方法可以使用一些更晦澀的 SPL 類來做到這一點。通過擴展而不是組合來完成它也可能更簡單。

TA貢獻1856條經驗 獲得超11個贊
適用于所有情況(PHP >= 7.2)的解決方案是使用 double foreach
,其中第一個行為類似于 an if
,它不會真正執行循環,但會啟動它:
function iterateOverIt($iterable) {
// First foreach acts like a guard condition
foreach ($iterable as $_) {
// Pre-processing:
echo "<ul>\n";
// Real looping, this won't start a *NEW* iteration process but will continue the one started above:
foreach ($iterable as $item) {
echo "<li>", $item instanceof DateTime ? $item->format("c") : (
isset($item["col"]) ? $item["col"] : $item
), "</li>\n";
}
// Post-processing:
echo "</ul>\n";
break;
}
}

TA貢獻1784條經驗 獲得超8個贊
我認為我們不需要在語言中添加任何新內容。在 PHP 7.1 中,我們收到了一個新類型iterable。使用類型提示,您可以強制您的函數/方法僅接受可以迭代并且在語義上應該被迭代的參數(與不實現的對象相反Traversable)。
// Function that iterates on $iterable
function iterateOverIt(iterable $iterable) {
echo '<ul>'.PHP_EOL;
foreach ($iterable as $item) {
echo "<li>", $item instanceof DateTime ? $item->format("c") : (
isset($item["col"]) ? $item["col"] : $item
), "</li>\n";
}
echo '</ul>'.PHP_EOL;
}
當然,這并不能確保循環至少會迭代一次。在您的測試用例中,您可以簡單地使用輸出緩沖,但這不適用于僅當循環將迭代至少一次時才需要執行操作的情況。這是不可能的。實現這種連貫性在技術上是不可能的。讓我舉一個簡單的例子:
class SideEffects implements Iterator {
private $position = 0;
private $IHoldValues = [1, 1, 2, 3, 5, 8];
public function __construct() {
$this->position = 0;
}
public function doMeBefore() {
$this->IHoldValues = [];
echo "HAHA!".PHP_EOL;
}
public function rewind() {
$this->position = 0;
}
public function current() {
return $this->IHoldValues[$this->position];
}
public function key() {
return $this->position;
}
public function next() {
++$this->position;
}
public function valid() {
return isset($this->IHoldValues[$this->position]);
}
}
$it = new SideEffects();
if (1/* Some logic to check if the iterator is valid */) {
$it->doMeBefore(); // causes side effect, which invalidates previous statement
foreach ($it as $row) {
echo $row.PHP_EOL;
}
}
正如您在這個不起眼的示例中看到的那樣,您的代碼中不可能有如此完美的連貫性。
我們在 PHP 中有不同的迭代方式的原因是因為沒有“一刀切”的解決方案。你試圖做的恰恰相反。您正在嘗試創建一個解決方案,該解決方案適用于可以迭代的所有內容,即使它可能不應該被迭代。相反,您應該編寫可以適當處理所有方法的代碼。
function iterateOverIt(iterable $iterable) {
if (is_array($iterable)) {
echo 'The parameter is an array'.PHP_EOL;
if ($iterable) {
// foreach
}
} elseif ($iterable instanceof Iterator) {
echo 'The parameter is a traversable object'.PHP_EOL;
/**
* @var \Iterator
*/
$iterable = $iterable;
if ($iterable->valid()) {
// foreach
}
} elseif ($iterable instanceof Traversable) {
echo 'The parameter is a traversable object'.PHP_EOL;
// I donno, some hack?
foreach ($iterable as $i) {
// do PRE
foreach ($iterable as $el) {
// foreach calls reset first on traversable objects. Each loop should start anew
}
// do POST
break;
}
} else {
// throw new Exception?
}
}
如果您真的想要,您甚至可以使用 將普通對象包含在其中is_object(),但正如我所說,除非它實現,否則Traversable不要對其進行迭代。
- 3 回答
- 0 關注
- 141 瀏覽
添加回答
舉報