未加星标

PHP Generators Sending “Gotchas”

字体大小 | |
[开发(php) 所属分类 开发(php) | 发布者 店小二05 | 时间 2016 | 作者 红领巾 ] 0人收藏点击收藏

If you’re reading this, you’re probably already aware of just how usefulphp’s Generators are for improving performance and/or reducing memory overheads while keeping your code clean and easy to read. We can use a simple foreach() in the main body of our code, as though iterating over an array, but without the overheads of needing to actually build an array.The Generator itself handles a lot of the boilerplate that we’d otherwise have to write, which also means that we createsimpler, cleanercode.

Unlike their equivalentin some programming languages, PHP’s Generators allow you to send data into the Generator itself; not simply at initialisation (the arguments that we pass to the Generator when instantiating it); but also betweeniterations. This has its own uses, and again, allows us to move code from our main blocks and methods into the Generator itself.

Less commonlyrecognised is that we can combine these two features, creating Generators that can be used to provide data for our main code block and methods, while allowing us to send data to the Generator that can actually modify its behaviour dependent on circumstance.

However, there are a few “gotchas” when we combine Generators that both return and accept data in this way, and it really helps to be aware of them when we’re developing, otherwise it can create problems.

So if we start with a simple “incrementor” Generator, something like:

function adjustableIncrementor($value = 1, $increment = 1) { for($i = 0; $i <= 6; ++$i) { yield $value; $value += $increment; } }

we can call it like:

foreach(adjustableIncrementor(0, 250) as $key => $value) { echo sprintf('%d => %s', $key, number_format($value)), PHP_EOL; }

(overriding the default arguments with the values that we want to use in this instance)and the resulting output would appear as:

0 => 0 1 => 250 2 => 500 3 => 750 4 => 1,000 5 => 1,250 6 => 1,500

Note that the Generator is automatically generating a key for us for each iteration of the loop.

Of course, this is a pretty simplistic use of a Generator, and we could just as easily write that code without bothering with using a Generator; but bear with me, we’ll get there soon enough.

Now taking a quick look at a Generator that will accept data, the syntax would look something like:

function bitComposer($blockSize = 8) { $block = 0; for($bit = $blockSize - 1; $bit >= 0; --$bit) { $block += (yield) ? pow(2, $bit) : 0; } return $block; }

and we call it by assigning the Generator object to a variable, and calling the send() method against that instance:

$bitData = [1, 1, 0, 1, 1, 0, 1, 1, 1]; // 439 decimal $bitComposer = bitComposer(count($bitData)); foreach($bitData as $value) { $bitComposer->send($value); } echo $bitComposer->getReturn();

which gives us the expected output of 439 .

Note that this particular example is taking advantage of the newer “return value” feature that was added to Generators in PHP7 to return the final result; but the method of sending has been available in PHP Generators since they were first introduced in version 5.5.

OK! So this isn’t a particularly useful example, simply sending the individual bit values from the data array into the Generator, where we aggregate those bits together and then return the final result as a decimal number. We could achieve the same more easily using bin2dec() ; but I’m using it just to demonstrate the basic syntax for sending/yielding values into a Generator.

However, it becomes much more interesting when we combine the two together, using the send() method to change the behaviour of the Generator while actually iterating over it.

So let’s take our original “incrementor” Generator, and dynamically change the increment value that we use for each iteration, so that it’s no longer a constant increment.

function adjustableIncrementor($value = 1, $increment = 1) { for($i = 0; $i <= 6; ++$i) { $value += (yield $value); } }

and we’ll call it using:

$incrementor = adjustableIncrementor(0, 0); foreach($incrementor as $key => $value) { echo sprintf('%d => %s', $key, number_format($value)), PHP_EOL; $incrementor->send(++$value); }

so withineach iteration of the foreach loop we modify the increment that will be used for the next value. What we expect to see here is a set of returned values following a familiarbinary sequence:

0 => 0 1 => 1 2 => 3 3 => 7 4 => 15 5 => 31 6 => 63

Effectively, each returned value should be 2^key - 1

However, let’s take a look at the actual output:

0 => 0 2 => 1 4 => 3 6 => 7

WTF ?!?That’s not what we expected! Nothing like what we expected to see.And therein lies our big“gotcha”.

So what’s actually happening here? Let’s add some additional debugging outputs inside ourGenerator to find out:

function adjustableIncrementor($value = 1, $increment = 1) { for($i = 0; $i <= 6; ++$i) { $increment = (yield $value); var_dump($increment); $value += $increment; } }

And if we run our loop again, we see something interesting:

0 => 0 int(1) NULL 2 => 1 int(2) NULL 4 => 3 int(4) NULL 6 => 7 int(8)

Every iteration of our calling loop seems to yield 2 values into the Generator, not just the value that we’re sending, but an additional null value.

I’ve not confirmed this with any of the PHP core developers, but my thinking is that the implicit call to the Generator’s next() method in the foreach loop sends an “implicit” null to prevent blocking in case your application has failed to call send() itself. Somehow, when both yielding from the Generator and sending to the Generator, this triggers a second iteration of the Generator’s internal loop, so the automatically generated key is incremented again.

Whatever the reasoning behind this, we need to take control of the key and manage it ourselves when we write a Generator that works with yielding both in and out of the Generator.

function adjustableIncrementor($value = 1, $increment = 1) { for($key = 0; $key <= 6; ++$key) { $increment = (yield $key => $value); if (is_null($increment)) { --$key; } $value += $increment; } }

Now, finally, we get the result that we expected:

0 => 0 1 => 1 2 => 3 3 => 7 4 => 15 5 => 31 6 => 63

We also need tomake allowance for the double loop potentially affectingthe value that we are calculating. In my example here, we’reusing an addition to update each step value, and an additional + 0 in the Generator loop won’t affect our result ( $value + 0 simply results in $value , so no damage done); but if we were using multiplication to calculate a product, then * 0 wouldgive 0 values when it yielded them back to our main calling code for all but the first iteration ( $value *0 gives 0 , which is definitely not what we want).

A Generator that does multiply by the value sent into it each iteration:

本文开发(php)相关术语:php代码审计工具 php开发工程师 移动开发者大会 移动互联网开发 web开发工程师 软件开发流程 软件开发工程师

主题: PHP
分页:12
转载请注明
本文标题:PHP Generators Sending “Gotchas”
本站链接:http://www.codesec.net/view/480624.html
分享请点击:


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 开发(php) | 评论(0) | 阅读(42)