Static keyword doesn't work inside PHP generator function - php

It seems that the static keyword does not work inside generator functions?
In PHP 5.5:
function static_fn () {
static $p = 0;
echo "\nstatic_fn: p = " . $p . "\n" ;
$p++;
}
echo "calling static_fn()\n";
static_fn();
echo "calling static_fn()\n";
static_fn();
function static_gen() {
static $p = 0;
echo "\nstatic_gen: p = " . $p . "\n" ;
yield $p;
}
echo "calling static_gen()\n";
foreach(static_gen() as $stuff) {
echo $stuff . "\n";
}
echo "calling static_gen()\n";
foreach(static_gen() as $stuff) {
echo $stuff . "\n";
}
I get the result
calling static_fn()
static_fn: p = 0
calling static_fn()
static_fn: p = 1
calling static_gen()
static_gen: p = 0
0
calling static_gen()
static_gen: p = 0
0
That is, the static keyword worked successfully for the static_fn function which returned 0 and 1 from consecutive calls, but it did not work for the static_gen function where it was reinitialised to 0 in each call.
Does anyone know why this is so, and how one might work around it to replicate static variables in a generator?

Try this:
function static_gen() {
static $p = 0; #initialised
while ($p < 10) { #yield while $p is smaller than 10
$p++; #increased
echo "\nstatic_gen: p = " . $p . "\n" ;
yield $p; #yield value
}
}
That will work.
Sidenote: A generator function must yield values as long as values are given. So the function must provide it in one or a other way.

Just like #JustOnUnderMillions have commented above, static vars aren't shared among functions.
Static variables has scope per function.
Moreover, generators is not aimed to do what you've did (okay, maybe you are just testing something :)).
Anyway you could change you static_gen() to:
function static_gen()
{
static $p = 0;
echo "\nstatic_gen: p = " . $p . "\n";
yield $p++;
}
But again, it's is so weird.

Related

Can I use PHP anonymous function as an argument, without assigning the function to a variable?

Does PHP allow the use of an anonymous function as one of the arguments during concatenation?
If so, what is the proper syntax?
For example, here's an example of what I want to get to work:
$final_text = $some_initial_string . function ($array_of_strings)
{
$out = '';
foreach ($array_of_strings as $this_particular_string)
{
$out .= $this_particular_string;
}
return $out;
};
Note: the below is expected to work for PHP Version 7.x but does not work on PHP Version 5.6 (For 5.6, first assign the anonymous function to a variable)
/*
* Strings before & after
*/
$table_heading_text = "HEADING";
$table_bottom_text = "BOTTOM";
/*
* Use the function this way
*/
echo $table_heading_text . (function (array $array_of_strings)
{
$out = '';
foreach ($array_of_strings as $this_particular_string)
{
$out .= $this_particular_string;
}
return $out;
})(array(
"hi",
"mom"
)) . $table_bottom_text;
In short ...
function must return some value that can be converted to text
function definition must be enclosed in parenthesis ( ... )
Don't forget to have calling arguments after the function definition
Examples:
echo "BEFORE" . (function ($x){return $x;})(" - MIDDLE - ") . "AFTER";
echo "BEFORE" . (function (){return " - MIDDLE - ";})() . "AFTER";
Also, using implode() may be better for this particular task.

Can't write values into a global PHP array from a class method

PHP is not my forte but I've tried some OO code in it today. All fine except that my $replyArray array at global level doesn't get written by the jumble() class method of my solSet object. The penultimate var_dump in my code shows an empty array. I've tried throwing around global keywords and that hasn't helped. Because I am explicitly passing this variable by reference to my newly instantiated class, shouldn't this be enough?
Thanks!
kk
<?php
//create a solution set for translation of incoming user login requests
$widhi = 600;
$tileDim = 25;
$randArray = array ("0","1","2","3","4","5");
$replyArray = array ();
//create 5 positions and ensure neither overlap or edge collision
class solSet
{
var $pos1;
var $pos2;
var $pos3;
var $pos4;
var $pos5;
var $pos6;
public function jumble($wh,$ts,$arrShuf,$reply)
{
foreach($this as $key => $value)
{
$newX = rand (($ts/2),$wh - ($ts/2));
$newY = rand (($ts/2),$wh - ($ts/2));
$randNo = array_pop($arrShuf);
$value = "" . $newX . "_" . $newY . "_" . $randNo;
$this->$key = $value;
//push coords onto an array for later ajax
$pushElem = "" . $newX . "_" . $newY;
$reply[] = $pushElem;
}
}
}
//scramble the random number array for later popping
shuffle($randArray);
//make a solution object
$aSolSet = new solSet;
$aSolSet->jumble($widhi,$tileDim,$randArray,$replyArray);
//store it in a session
session_start();
$_SESSION["solSet"] = $aSolSet;
echo var_dump($replyArray);
echo json_encode($aSolSet);
?>
This seems to relate:
Using a global array inside a class
But this is what I have done. Also the whole world and his dog are saying that the global keyword is "doing it wrong". What to do?
Your jumble method needs to take $replyArray by reference - by default, PHP functions operate by value, which means that they operate on a copy of the variable, rather than modifying it. See http://php.net/manual/en/language.references.pass.php
Change
public function jumble($wh,$ts,$arrShuf,$reply)
to
public function jumble($wh,$ts,$arrShuf,&$reply)
The ampersand in front of the variable name means that the parameter is passed by reference.
Alternatively, instead of updating the global var from within the class (bad for re-use and portability) you can simply return your new shuffled array:
public function jumble($wh,$ts,$arrShuf)
{
$reply = array();
foreach($this as $key => $value)
{
$newX = rand (($ts/2),$wh - ($ts/2));
$newY = rand (($ts/2),$wh - ($ts/2));
$randNo = array_pop($arrShuf);
$value = "" . $newX . "_" . $newY . "_" . $randNo;
$this->$key = $value;
//push coords onto an array for later ajax
$pushElem = "" . $newX . "_" . $newY;
$reply[] = $pushElem;
}
return $reply;
}
... and update your global $replyArray with the response:
//make a solution object
$aSolSet = new solSet;
$replyArray = $aSolSet->jumble($widhi,$tileDim,$randArray);
You don't even need to pass the $reply parameter into your method at all then (note, one less parameter) and everything is all nice and self-contained.
reference the global array in the function
global $replyArray;

PHP generator yield the first value, then iterate over the rest

I have this code:
<?php
function generator() {
yield 'First value';
for ($i = 1; $i <= 3; $i++) {
yield $i;
}
}
$gen = generator();
$first = $gen->current();
echo $first . '<br/>';
//$gen->next();
foreach ($gen as $value) {
echo $value . '<br/>';
}
This outputs:
First value
First value
1
2
3
I need the 'First value' to yielding only once. If i uncomment $gen->next() line, fatal error occured:
Fatal error: Uncaught exception 'Exception' with message 'Cannot rewind a generator that was already run'
How can I solve this?
The problem is that the foreach try to reset (rewind) the Generator. But rewind() throws an exception if the generator is currently after the first yield.
So you should avoid the foreach and use a while instead
$gen = generator();
$first = $gen->current();
echo $first . '<br/>';
$gen->next();
while ($gen->valid()) {
echo $gen->current() . '<br/>';
$gen->next();
}
chumkiu's answer is correct. Some additional ideas.
Proposal 0: remaining() decorator.
(This is the latest version I am adding here, but possibly the best)
PHP 7+:
function remaining(\Generator $generator) {
yield from $generator;
}
PHP 5.5+ < 7:
function remaining(\Generator $generator) {
for (; $generator->valid(); $generator->next()) {
yield $generator->current();
}
}
Usage (all PHP versions):
function foo() {
for ($i = 0; $i < 5; ++$i) {
yield $i;
}
}
$gen = foo();
if (!$gen->valid()) {
// Not even the first item exists.
return;
}
$first = $gen->current();
$gen->next();
$values = [];
foreach (remaining($gen) as $value) {
$values[] = $value;
}
There might be some indirection overhead. But semantically this is quite elegant I think.
Proposal 1: for() instead of while().
As a nice syntactic alternative, I propose using for() instead of while() to reduce clutter from the ->next() call and the initialization.
Simple version, without your initial value:
for ($gen = generator(); $gen->valid(); $gen->next()) {
echo $gen->current();
}
With the initial value:
$gen = generator();
if (!$gen->valid()) {
echo "Not even the first value exists.<br/>";
return;
}
$first = $gen->current();
echo $first . '<br/>';
$gen->next();
for (; $gen->valid(); $gen->next()) {
echo $gen->current() . '<br/>';
}
You could put the first $gen->next() into the for() statement, but I don't think this would add much readability.
A little benchmark I did locally (with PHP 5.6) showed that this version with for() or while() with explicit calls to ->next(), current() etc are slower than the implicit version with foreach(generator() as $value).
Proposal 2: Offset parameter in the generator() function
This only works if you have control over the generator function.
function generator($offset = 0) {
if ($offset <= 0) {
yield 'First value';
$offset = 1;
}
for ($i = $offset; $i <= 3; $i++) {
yield $i;
}
}
foreach (generator() as $firstValue) {
print "First: " . $firstValue . "\n";
break;
}
foreach (generator(1) as value) {
print $value . "\n";
}
This would mean that any initialization would run twice. Maybe not desirable.
Also it allows calls like generator(9999) with really high skip numbers. E.g. someone could use this to process the generator sequence in chunks. But starting from 0 each time and then skipping a huge number of items seems really a bad idea performance-wise. E.g. if the data is coming from a file, and skipping means to read + ignore the first 9999 lines of the file.
solutions provided here does not work if you need to iterate more than once.
so I used iterator_to_array function to convert it to array;
$items = iterator_to_array($items);

pushing string into empty array gives warning

I created empty array and pushing val into it.
$var = array();
function printTag($tags) {
foreach($tags as $t) {
echo $t['token'] . "/" . $t['tag'] . " ";
if($t['tag'] == 'NN' OR $t['tag']== 'JJ'){
array_push($var, $t['token']) ;
}
}
echo "\n";
}
code looks fine for me but gives error:
array_push() expects parameter 1 to be array, null given in /var/www/html/postag/poscall.php on line 9
what is wrong here?
entire code:
<?php
// little helper function to print the results
include ("postag.php");
$var = array();
function printTag($tags) {
foreach($tags as $t) {
echo $t['token'] . "/" . $t['tag'] . " ";
if($t['tag'] == 'NN' OR $t['tag']== 'JJ'){
array_push($var, $t['token']) ;
}
}
echo "\n";
}
$tagger = new PosTagger('lexicon.txt');
$tags = $tagger->tag('The quick brown fox jumped over the lazy dog. this is really yummy and excellent pizza I have seen have really in love it it');
printTag($tags);
?>
Your $var = array(); statement is outside the function and out of the scope of the function. Put that inside the function and it'll remove the warning
function printTag($tags) {
$var = array();
foreach($tags as $t) {
echo $t['token'] . "/" . $t['tag'] . " ";
if($t['tag'] == 'NN' OR $t['tag']== 'JJ'){
array_push($var, $t['token']) ;
}
}
echo "\n";
}
The problem in your case is that $var is not in the scope of your function, so it gets implicitly declared as null (and this raises a notice too).
That said, this seems like a good case array_reduce():
$var = array_reduce($tags, function(&$result, $t) {
if (in_array($t['tag'], ['NN', 'JJ'])) {
$result[] = $t['token'];
}
// you could do some output here as well
return $result;
}, []);
It filters and maps at the same time and the return value is the array you want.
Alternatively, just declare $var inside the function and return it:
function printTag(array $tags)
{
$var = [];
foreach($tags as $t) {
// ...
}
return $var;
}
// ...
$var = printTag($tags);

function functionName(if clause here) possible?

function getTemplate($tpl if ($vars) echo ", $vars";)...function
Is this possible somehow?
The above wont work.
Thanks
Optional arguments with default values
It looks like you want an optional argument, which you can accomplish by defining a default value in the function definition:
function getTemplate($tpl, $vars=null)
{
}
You can call this function as getTemplate($foo) or getTemplate($foo,$bar). See the PHP manual page on function arguments for more details.
Variable numbers of arguments
It's also possible to write functions which take a variable number of arguments, but you need to use func_num_args, func_get_arg and func_get_args functions to get at them. Here's an example from the manual
<?php
function foo()
{
$numargs = func_num_args();
echo "Number of arguments: $numargs<br />\n";
if ($numargs >= 2) {
echo "Second argument is: " . func_get_arg(1) . "<br />\n";
}
$arg_list = func_get_args();
for ($i = 0; $i < $numargs; $i++) {
echo "Argument $i is: " . $arg_list[$i] . "<br />\n";
}
}
foo(1, 2, 3);
?>
Calling a function with a variable number of parameters
To round off this answer even more, suppose you'd build an array of 1..n values and wanted to pass it to the foo() function defined above? You'd use call_user_func_array
$values=(1,2,3,4);
call_user_func_array('foo', $values);
This is the equivalent of calling
foo(1,2,3,4);
What's so bad about
function getTemplate($tpl, $vars=null) {}
?
if ($vars) { getTemplate($tpl, $vars); }
else {{ getTemplate($tpl, null); }
(semi-pseudo code)
Or:
getTemplate($tpl, ($vars)?$vars:null); // not sure
getTemplate($tpl, (!empty($vars))?$vars:null);
Also, if you would like a technique similar to echo:
$code = "getTemplate($pl";
if ( $vars ) { $code = $code . ", $vars);"; }
else { $code = $code . ");"; }
$ret = eval($code);
Although this is usually considered bad practice (never say never).
Please note that all code sent to eval will be executed directly. So don't put user input in an eval() call.

Categories