Seeking your help to convert one big array with PHP generators.
Below is my code for which I need rework:
I am getting a result set from a service call and assigning all to an array:
foreach ($objects->result as $pointStdObject) {
$pointStdObjects[] = $pointStdObject;
}
This piece of code is inside a while loop which queries for records with an offset of 1000.
Issue is $pointStdObjects[] tends to get very huge and I get PHP out of memory exception.
Later I again need to use this same array as:
foreach ($pointStdObjects as $pointStdObject) {
$point = $this->pointFactory->createPointFromStdObject($pointStdObject);
if (!$point) {
continue;
}
$points[] = $point;
}
return $points;
Please suggest if we can leverage PHP generators or yield here
function getStd()
{
///your code before that
foreach ($objects->result as $pointStdObject) {
yield $pointStdObject;
}
}
function useStd()
{
foreach (getStd() as $pointStdObject) {
$point = $this->pointFactory->createPointFromStdObject($pointStdObject);
if (!$point) {
continue;
}
$points[] = $point;
}
return $points;
}
I'm having an array_reduce function which I am willing to exit when specific criteria is met.
$result = array_reduce($input, function($carrier, $item) {
// do the $carrier stuff
if (/* god was one of us */) {
break; //some break analogue
}
return $carrier;
});
How do I achieve this? Or should I use foreach instead?
array_reduce is used to write functional-style code which always iterates over the full array. You can either rewrite to use a regular foreach loop to implement short circuiting logic, or you can simply return the current $carrier unmodified. This will still iterate over your full array, but it will not alter the result (as you said, this is more alike to continue)
Firstly, let me say that array_reduce is probably one of my favorite functions - I am famous (well, in a very small circle) for taking 40 lines of clearly written code and replacing them with four harder-to-follow 10 line array_reduce calls to do the same thing!
Sadly, PHP array functions seem bound to want to complete their task. This, combined with the inability to make a recursive unnamed function, makes this common situation difficult to deal with. Not wanting to put a lot of ugly for loops in my code, I tend to bury them in another function (see reduce below) as did an earlier poster.
It's worth pointing out that this is in no way as efficient as using array functions, and, in most circumstances, it's better just to let the array reduce function use a "done" flag to spin quickly through the unneeded values. At any rate, this is something reasonably array_reduce like (the evaluation function using a null return to indicate its finished). The goal is to add up the numbers in the array until you get to a 4.
<?php
$init = 0;
$arr = [1,2,3,4,5,6,7,8,9,0];
$func = function($c, $it) {
if ($it == 4) return null;
return $c + $it;
};
function reduce($arr, $f, $init) {
for ($c = $init; count($arr); ) {
$newc = $f($c, array_shift($arr));
if (!isset($newc)) break;
$c = $newc;
}
return $c;
}
echo reduce($arr, $func, $init) . "\n"; // 6
According to a similar answer.
Break array_walk from anonymous function
The best and the worst way to complete this is Exception.
Not recommend this way, but this way is the solution to your question:
try {
$result = array_reduce( $input, function ( $carrier, $item ) {
// do the $carrier stuff
$condition = true;
if ( $condition ) {
throw new Exception;
}
return $carrier;
} );
} catch ( Exception $exception ) {
echo 'Break';
}
The way I would solve the problem
I would create a global function or write PHP extension and add a function
There is a good answer about writing PHP extension:
How to make a PHP extension
array_reduce_2();
But there is a problem with breaking implementation.
Need to detect which condition to out of function.
Below implementation, array_reduce_2 checks if a callback returned.
If so - breaking out of execution.
This way allows checking if execution has broken by checking return type of value.
array_reduce_2 implementation
According to #wordragon notice, implemented the ability to pass an associative array as param too.
function array_reduce_2( $array, $callback, $initial = null ) {
$len = count( $array );
$index = 0;
if ( $len == 0 && count( func_get_args() ) == 1 ) {
return null;
}
$values = array_values( $array );
$result = $initial ?? $values[ $index ++ ];
foreach ( $values as $value ) {
$result = $callback( $result, $value );
if ( ! is_callable( $result ) ) {
continue;
}
// break;
return $result;
}
return $result;
}
I've used the idea from JS implementation and rewrite for PHP accordingly
https://gist.github.com/keeto/229931
Detecting if the break occured
$input = [ 'test', 'array', 'god was one of us' ];
$result = array_reduce_2( $input, function ( $carrier, $item ) {
// do the $carrier stuff
if ( $item === 'god was one of us' ) {
return function () {
return 'god was one of us';
};
}
return $carrier;
} );
$is_break = is_callable( $result );
if ( $is_break ) {
echo $result();
exit;
}
Important to note!
This array_reduce_2 implementation works properly only if you don't need to return the normal value as a callback.
I suggest using foreach loops instead. The reasons to not use array_reduce are:
Sound reasons:
It is not statically type-checked. So code inspections do not show type errors if there are any in the input or callback arguments.
It returns mixed, so inspections do not show errors if you misuse the result, or they may show false positive if you use it properly.
You cannot break.
Opinionated reasons:
It is harder on the eye. Having a $result and adding to it in a loop (or whatever you do) is way easier to read than grasping that something is returned and then passed as a $carry accumulator in the next call.
It makes me lazy to extract functions properly. If I extract one operation to a callback, I then may find the code short enough to not extract the whole array operation to a function which should really be done in the first place.
If you use a condition to break, there is a good chance you may one day need other arguments to that callback function. With the callback signature being fixed, you would have to pass arguments with use keyword which really much harder to read than a non-callback.
breakable_array_reduce()
function breakable_array_reduce(array $array, callable $callback, $initial = null) {
$result = $initial;
foreach ($array as $value) {
$ret = $callback($result, $value);
if (false === $ret) {
return $result;
} else {
$result = $ret;
}
}
return $result;
}
Usage
// array of 10 values
$arr = [
1,
1,
1,
1,
1,
1,
1,
1,
1,
1
];
// callback function which stops once value >= 5
$sum_until_five = function($initial, $value) {
if ($initial >= 5) {
return false;
} else {
return $initial + $value;
}
};
// calculate sum of $arr using $sum_until_five()
$sum = breakable_array_reduce($arr, $sum_until_five);
// output: 5
echo $sum;
Explanation
breakable_array_reduce() will work just like array_reduce() unless/until callback $callback returns bool(false)
Alternate implementation using array keys:
breakable_array_reduce_keyed()
function breakable_array_reduce_keyed(array $array, callable $callback, $initial = null) {
$result = $initial;
foreach ($array as $key => $value) {
$ret = $callback($result, $value, $key);
if (false === $ret) {
return $result;
} else {
$result = $ret;
}
}
return $result;
}
Usage
// array of values
$arr = [
'foo' => 1,
'bar' => 1,
'baz' => 1
];
// callback function which stops when $key === 'baz'
$sum_until_baz = function($initial, $value, $key) {
if ('baz' === $key) {
return false;
} else {
return $initial + $value;
}
};
// calculate sum of $arr using $sum_until_baz()
$sum = breakable_array_reduce($arr, $sum_until_baz);
// output: 2
echo $sum;
P.S. I just wrote and fully tested this locally.
I am trying to return values in php for sql injection, but the return is stopping my function. Here is the example:
function ex($input) {
if (strlen($input) > 5)
{
return $input;
}
return ":the end";
}
echo ex("helloa");
When I use return inside the function it ends it and ex("helloa") == "helloa" not "helloa:the end" like I want it to.
Concatenating Strings in a Function
When you want to have multiple strings, and actually wanna concatenate (or join) them. You can keep on adding them together in a variable and then, return that variable at the end of the function.
function ex($input) {
$return = "";
if (strlen($input) > 5)
{
$return .= $input;
}
$return .= ":the end";
return $return;
}
echo ex("helloa");
Using an array to pseudo-return multiple values
If you really want to return multiple values/strings, you can instead tell the function to return an array. You can only return one output though from a function.
function ex($input) {
// this array acts as a container/stack where you can push
// values you actually wanted to return
$return = array();
if (strlen($input) > 5)
{
$return[] = $input;
}
$return[] = ":the end";
return $return;
}
// you can use `implode` to join the strings in this array, now.
echo implode("", ex("helloa"));
Use
function ex($input) {
return (strlen($input) > 5 ? $input : '') . ":the end";
}
for some reason $post is always < 0. The indoxOf function works great. I use it on ohter codes and it works great
for some reason even after I add the element like this array_push($groups, $tempDon); on the next loop i continues to return -1
$donations = $this->getInstitutionDonations($post->ID);
$groups=array();
foreach( $donations as $don ) : setup_postdata($don);
$pos = $this->indexOf($don, $groups);
print_r($pos);
if($pos < 0)
{
$tempDom = $don;
$tempDon->count = 1;
array_push($groups, $tempDon);
}
else
{
$tempDom = $groups[$pos];
$tempDon->count++;
array_splice($tempDon);
array_push($groups, $tempDon);
echo '<br><br><br>ahhhhhhhhhh<br><br>';
}
endforeach;
protected function indexOf($needle, $haystack) { // conversion of JavaScripts most awesome
for ($i=0;$i<count($haystack);$i++) { // indexOf function. Searches an array for
if ($haystack[$i] == $needle) { // a value and returns the index of the *first*
return $i; // occurance
}
}
return -1;
}
This looks like an issue of poor proofreading to me (note $tempDom vs $tempDon):
$tempDom = $don;
$tempDon->count = 1;
array_push($groups, $tempDon);
Your else block has similar issues.
I also completely agree with #hakre's comment regarding syntax inconsistencies.
EDIT
I'd also like to recommend that you make use of PHP's built-in array_search function in the body of your indexOf method rather than rolling your own.
At the risk of self-embarrassment, could anyone tell me how to use return here:
function dayCount() {
for ($dayBegin = 1; $dayBegin < 32; $dayBegin++)
{
"<option value=\"".$dayBegin."\">".$dayBegin."</option>";
}
}
My issue is that I am passing this function to Smarty via
$dayCount = dayCount();
$smarty->assign('dayCount', $dayCount);
and
{$dayCount}
but instead the HTML is going straight to the buffer, right before <html> (thanks Hamish), not inside the HTML element I want.
Any help on this?
You need to build up the return statement
function dayCount() {
$return = array();
for ($dayBegin = 1; $dayBegin < 32; $dayBegin++)
{
$return[] = "<option value=\"".$dayBegin."\">".$dayBegin."</option>";
}
return $return;
}
Although this is building up an array like you asked. When outputting it, you would need to implode it.
implode('', $dayCount);
Or otherwise, build up a string instead of an array.