I would like to return a value from an asynchronous function in PHP ... I use icicle.io here, but I'm happy to use whatever, provided it does what I want to do! Anyway, this is some code below
<?php
require __DIR__ . '/vendor/autoload.php';
use Icicle\Coroutine\Coroutine;
use Icicle\Loop;
function getArray($int) {
yield array ($int, $int + 1, $int + 2);
}
function getArrays() {
$numbers = array (1, 4, 7);
$results = array();
foreach ($numbers as $number) {
array_push($results, (yield(getArray($number))));
}
yield call_user_func_array('array_merge', $results);
}
$coroutine = new Coroutine(getArrays());
$data = $coroutine->then(
function ($result) {
$data = print_r($result, true);
return "Result: {$data}\n";
},
function (Exception $e) {
echo "Error: {$e->getMessage()}\n";
}
)->done(function ($value) {
echo $value;
});
Loop\run();
What I'd really like to do is to put that last little bit in a function, so it looks more like this:
function sync() {
$coroutine = new Coroutine(getArrays());
$data = $coroutine->then(
function ($result) {
$data = print_r($result, true);
return "Result: {$data}\n";
},
function (Exception $e) {
echo "Error: {$e->getMessage()}\n";
}
)->done(function ($value) {
return $value;
});
Loop\run();
return /* the value */;
}
Then from my cool software, I can call sync() as if it's a synchronous function, blissfully unaware of the asynchronous shenanigans going on behind the scenes.
Has anyone done this, or have some suggestions as to how I might? At the moment the best I've come up with is (ab)using the output buffer & serialize()/unserialize() functions, but since I'm doing it all out of some desire to improve the performance, that seems rather backwards!!
You can synchronously wait for the resolution of an Awaitable (including a Coroutine) by using the wait() method. This method ticks the event loop until the coroutine is resolved. This means your sync() function can simply call this method on the coroutine object and return the result.
function sync() {
$coroutine = new Coroutine(getArrays());
return $coroutine->wait();
}
Related
I'm in the process of learning PHP, and the concept of callback functions have me slightly confused. I understand that there are synchronous and asynchronous callbacks and that the common callback in PHP is synchronous. I have read through a lot of information regarding this topic, but I'm none the wiser still.
How is this:
function string($string, $callback) {
$results = array(
'upper' => strtoupper($string),
'lower' => strtolower($string)
);
if(is_callable($callback)) {
call_user_func($callback, $results);
}
}
string('Danny', function($name) {
echo $name['upper'];
}
);
Different or better than this:
function string($string, $case) {
$options = [
'upper' => strtoupper($string),
'lower' => strtolower($string)
];
echo $options[$case];
}
string('Danny', 'upper');
Here's a typical example where the callback is better than the direct value:
function intricateProcessToCalculateDefault() {
//Takes 3 seconds
}
function valueOrDefault($value, $default = null) {
if ($value === null) {
if (is_callable($default)) {
return $default();
}
return $default;
}
return $value;
}
Now consider these two examples:
$valueToUse = valueOrDefault(
$_GET["parameterWithFallback"] ?? null,
intricateProcessToCalculateDefault()
);
// or
$valueToUse = valueOrDefault(
$_GET["parameterWithFallback"] ?? null,
function () { return intricateProcessToCalculateDefault(); }
);
Now here's the thing. If you don't pass parameterWithCallback this will take 3 seconds either way. However if you do then intricateProcessToCalculateDefault will never be called if passed as a callback and will therefore save you time.
In your case, since the function code is ran in either case, there's no benefit.
PHP is mostly synchronous so there's no easy way to get asynchronous functions. Some libraries do offer them but that's usually tied with calling a shell executable or something similar.
Another use case is for inversion for control. There are many generic algorithms which can be implemented with a "placeholder" for user code for. array_map is such an example.
$array = [ "string1", "sting2" ];
$action = "upper";
array_map(function ($value) use ($action) {
return ucfirst(strtolower($value));
}, $array);
In this case control is inverted. A PHP function is calling your function instead of the other way around.
In this example with a callback you could easily pass a different function without changing the original code:
<?php
function string($string, $callback) {
$results = array(
'upper' => strtoupper($string),
'lower' => strtolower($string)
);
if(is_callable($callback)) {
call_user_func($callback, $results);
}
}
string('Danny', function($name) {
echo $name['upper'];
}
);
string('Danny', function($name) {
echo 'Mr.'.$name['upper'];
}
);
string('Danny', function($name) {
echo 'Mrs.'.$name['upper'];
}
);
This question already has answers here:
How to use return inside a recursive function in PHP
(4 answers)
Closed 9 months ago.
I have a problem with a recursive function in PHP which returns a JSON object. When the the condition is met to run the function a second time I always get an empty object as result {}. Everything is executed as it would be in the first run, but I always get an empty result.
Here is my code (very much simplified, yet functioning):
public function run()
{
$result = null;
// .......
// there is alot other stuff here, that all runs
// perfectly through also the second run
// ......
// Conditional Routing
if($this->wfProfile->autoprocess){
// select new wfProfile and go again.
$this->autoprocess(function($data){
if($data['error']==0){
$result = null;
$this->run(); // from here we start over !
}else{
return $data;
}
});
}else{
return ['error'=>0,'message'=>'all good']; // this is where it should go at the end of second loop
}
}
There is no place in the whole class, that would return an empty JSON object. Something must be here, that I'm doing wrong or what I'm overseeing.
Edit (I don't think this helps)
private function autoprocess($callback)
{
if(is_callable($callback)){
$possibleWFprofiles = WfProfile::where('statusNow', $this->wfRequest->status)->where('conditionalRouting', 1)->get();
if($possibleWFprofiles->count() == 0){
// configuration error....
$result = ["error"=>1, 'message'=>"Unable to find Conditional Routing enabled WfProfiles: ".$this->wfRequest->status];
}
foreach($possibleWFprofiles as $possibleWfProfile){
if(array_search($possibleWfProfile->crFieldname, $this->wfRequestFields)===false){
// fieldname wrongly configured
$result = ["error"=>1, 'message'=>"Unable to find field ".$possibleWfProfile->crFieldname];
}
// see if this is the right one
if($this->wfRequest[$possibleWfProfile->crFieldname] == $possibleWfProfile->crValue){
$this->wfProfile = $possibleWfProfile;
$result = ['error'=>0,'message'=>'Off to loop 2'];
}
}
call_user_func($callback, $result);
}
}
When you make a return $data, inside a anonymous function, it will not be a run's return.
You are not doing nothing with this return in your autoprocess function.
You need to return something in autoprocess and then return in your if:
if($this->wfProfile->autoprocess){
// select new wfProfile and go again.
return $this->autoprocess(function($data){
if($data['error']==0){
$result = null;
return $this->run(); // from here we start over !
}else{
return $data;
}
});
}else{
return ['error'=>0,'message'=>'all good']; // this is where it should go at the end of second loop
}
You need to return your value, for example take this:
function callback($func, $val) {
return call_user_func($func, $val);
}
function run($val) {
if ($val < 10) {
callback(function($val) { return run($val + 1); }, $val);
}
return $val;
}
print(run(0));
this will print empty, but if you do:
function callback($func, $val) {
return call_user_func($func, $val);
}
function run($val) {
if ($val < 10) {
return callback(function($val) { return run($val + 1); }, $val);
}
return $val;
}
print(run(0));
it will print 10
Your function:
public function run()
{
$result = null;
// lets say this is true...
if($this->wfProfile->autoprocess){
// now we are here, where does this return a value???
$this->autoprocess(function($data){
// if it goes here, it never returns a value.
if($data['error']==0){
$result = null;
$this->run(); // from here we start over !
}else{ // if it returns here it still just returns to
// $this->autoprocess, which might return to the
// original run function, but you don't seem to be
// returning its return either...
return $data;
}
});
}else{
return ['error'=>0,'message'=>'all good']; // this is where it should go at the end of second loop
}
}
At the end I chose the imho less elegant way to solve this, so I used goto instead of calling the function again. This is easy to read and to debug/extend in future. So here we go:
public function run()
{
startover:
$result = null;
// more stuff going on here
// Conditional Routing
if($this->wfProfile->autoprocess){
// select new wfProfile and go again.
$result = $this->autoprocess();
if($result['error']==0){
goto startover; // easiest way :-)
}else{
return $result;
}
}else{
return ['error'=>0,'message'=>'all good'];
}
}
and here the autoprocess function
private function autoprocess()
{
$possibleWFprofiles = WfProfile::where('statusNow', $this->wfRequest->status)->where('conditionalRouting', 1)->get();
if($possibleWFprofiles->count() == 0){
// configuration error....
return ["error"=>1, 'message'=>"Unable to find Conditional Routing enabled WfProfiles: ".$this->wfRequest->status];
}
foreach($possibleWFprofiles as $possibleWfProfile){
if(array_search($possibleWfProfile->crFieldname, $this->wfRequestFields)===false){
// fieldname wrongly configured
return ["error"=>1, 'message'=>"Unable to find field ".$possibleWfProfile->crFieldname];
}
// see if this is the right one
if($this->wfRequest[$possibleWfProfile->crFieldname] == $possibleWfProfile->crValue){
$this->wfProfile = $possibleWfProfile;
return ['error'=>0,'message'=>'Off to loop 2'];
}
}
}
I have a function:
public function CustomerRating() {
$result = $db->query("...");
$row = $result->fetch_assoc();
if($row)
$output = $row['somefield'];
} else {
$output = "error";
}
return $output;
}
//somewhere on another page...
if(is_numeric($class->CustomerRating()) {
echo $class->CustomerRating;
} else {
echo "There is an error with this rating.";
}
Is there a better way to find errors? In this function, if no rows are returned, it doesn't mean an "error" per se, it simply means the value can't be calculated. When I check for the result of a function, I feel like there is a better way to check the data being returned before I display it in the if function. What's the best way to do this? I'd like to return a "false", but how would I check for that when calling the function? Thanks!
There are (in my opinion) 2 common ways:
Returning false
Many builtin PHP functions do that
Using SPL exceptions
Evolved PHP frameworks (Symfony2, ZF2, ...) do that
You need exceptions:
public function CustomerRating() {
$result = $db->query("...");
$row = $result->fetch_assoc();
if ($row !== null) {
return $row['somefield'];
} else {
throw new Exception('There is an error with this rating.');
}
}
// Somewhere on another page...
try {
echo $class->CustomerRating();
} catch (Exception $e) {
echo $e->getMessage();
}
Use exceptions. Avoid returning errors from functions and methods
Although returning false to indicate an error is prevalent in PHP libraries, there are several drawbacks:
you can not return a description about the error
if the false value is a valid return value of the function, then you can not use this approach
Another approach I see in my job is to return an array with both the normal result and the possible error, basically returning a pair, but then to get the real result you have to retrieve it from the array which is more unpleasant code to write
Exceptions are a full fledged solution to this problem but it's a bit cumbersome to write the try...catch block for simple errors. For a function that's documented to throw an exception, if you don't catch the exception when you call it, PhpStorm will complain about that, so in my opinion exceptions are better reserved for more severe errors
One way to return both the result and a possible error is to use a pass by reference parameter, which is used a lot in Objective C
/**
* get element from array
* #param $index int
* #param $list array
* #param $error object
*/
function getFromArray($index, $list, &$error=null) {
if ($index >= 0 && $index < count($list)) {
return $list[$index];
}
$error = "out of index";
return null;
}
$list = ['hello', 'world'];
$error = null;
$result = getFromArray(-1, $list, $error);
if ($error) {
echo "an error occurred " . $error;
} else {
echo $result;
}
if you don't care about the error, you can just call the function leaving out the error parameter
echo getFromArray(0, $list);
I would use exceptions - Saves on the confusion.
the best way to deal with errors is to throw an exception. that way you can have all kinds of different errors and handle them accordingly.
you can then just do:
try {
$myvar = CustomerRating();
//do something with it
} catch (Exception $e) {
echo $e->getMessage();
}
Try this out:
public function CustomerRating() {
$result = $db->query("...");
$row = $result->fetch_assoc();
if($row){
$output = $row['somefield'];
} else {
$output = false;
}
return $output;
}
//somewhere on another page...
if($class->CustomerRating() !== false) {
echo $class->CustomerRating();
} else {
echo "There is an error with this rating.";
}
This will make sure that it won't break if you return a zero.
I have Logger-Class which is logging everything. Objects will be logged with print_r to a human-readable state. My Problem is that I have a big MVC-Object. Everytime an Exception or Error occurs, the MVC-Object will also be printed in the Log by print_r. This results in a very very long Logfile that is not really friendly to read.
I tried to set a __toString() method to my MVC-Class but this don't work. I also get the complete MVC-Object in Log. The MVC is a Singleton and is referenced on every Object. So to simple exclude the Object before it cames into the print_r is not that easy.
Is there any way to exclude Objects from print_r?
My Methods:
LOG-Class errorHandler-Method:
public static function errorHandler($errno, $errstr, $errfile, $errline, $vars) {
//If # is set, don't do anything!
if(error_reporting() === 0) {
return;
}
//Get StackTrace with low memory usage ;)
$e = new Exception();
$stackStr = $e->getTraceAsString();
//Build ErrorMessage for Log
$message = 'File: '.$errfile.' - L: '.$errline."\n".
'Code: '.$errno."\n".
'Message: '.$errstr."\n".
'Vars: '.print_r($vars, true)."\n".
'Stacktrace: '.$stackStr;
self::error($message);
}
LOG-Class exceptionHandler-Method:
public static function exceptionHandler(Exception $e) {
$message = get_class($e).': '.$e->getMessage()."\n".
'File: '.$e->getFile().' - L: '.$e->getLine()."\n".
'Code: '.$e->getCode()."\n".
'Message: '.$e->getMessage()."\n".
'Stacktrace: '.$e->getTraceAsString();
self::error($message);
}
LOG-Class error-Method:
public static function error($data, $file='system.log') {
$config = Megaira_PropertyConfiguration::getInstance();
switch($config->get('LOG_MODE')) {
case'DEEPDEBUG':
case'ERROR':
case'WARNING':
case'INFO':
case'DEBUG':
self::writeToLog('ERROR', $data, $file);
break;
}
}
LOG-Class writeToLog-Method:
private static function writeToLog($mode='', $text='', $file=''){
if(!is_string($text) && !is_numeric($text)) {
$text = print_r($text, true);
}
$config = Megaira_PropertyConfiguration::getInstance();
if(!$config->get('LOGGINGACTIVE')) { return; }
self::writeLineToFile($mode, $text, $file);
}
Setup the Error- and Exception-handler:
//Set Error and Exception Handler
set_error_handler(array(new LOG(), 'errorHandler'));
set_exception_handler(array(new LOG(), 'exceptionHandler'));
Thanks
Some Testing:
public static function print_r_filtered($object, $ret=false) {
$filtered = array(
'Megaira_MVC'
);
$text = print_r($object, true);
foreach($filtered as $filter) {
$search = '#('.$filter.'\sObject)\n(\s+)\).*?\n\2\)\n#s';
$replace = "$1";
$text = preg_replace($search, $replace, $text);
}
if($ret)
return $text;
echo $text;
}
Did not work. Maybe RegEx fail?
Solution:
It was a Design flaw. The errorHandler is Logging all Objects that are used on the place the error occurs. So in the index.php are the following code:
$mvc = Megaira_MVC::getInstance();
So this peace of code produced a logging of the Var $mvc with print_r through the errorHandler in LOG-Class.
Conclusion for me: Don't use Variables on big Singleton-Objects or use unset() if the Var is not needed anymore.
__toString() is called, when the object is casted to a string. You may try something like
$objectString = method_exists($object, '__toString')
? (string) $object
: print_r($object, true);
Use is_object() to find out, if a value is an object or not.
At all
$string = !is_object($value) || method_exists($value, '__toString')
? (string) $value
: print_r($value, true);
You could wrap print_r with your own function that checks whether the provided data includes any objects, using the is_object() function. Similarly, you could use is_a() if you only want to exclude certain classes of objects.
as a html solution you can use:
<pre><?=print_r(log_content);?></pre>
to display your log file better. I am displaying the log files in this way.
if (is_object( $foo ))
{
print_r( $foo->__toString() );
} else {
print_r( $foo );
}
If you can alter the Logger class, you can check for the class befpre you print it:
if(!($var instanceof HeavyMVCObject)){
print_r($var);
}
Is there a trick in PHP 4 to implement functions which return functions? I expected that the following code would work:
function xxx($a) {
return function($b) {
print "a=$a, b=$b \n";
}
}
$f1 = xxx(1);
$f1(2);
Unfortunately, no luck in PHP 4. Probably it works in PHP 5, but I limited to PHP 4.
I tried to workaround with OO, but again failed (class declarations may not be nested):
class Closure {
function run($a) {
print "raise: NotImplementedException, instead: $a\n";
}
}
class WantCheckNesting extends Closure {
function run($a, $b) {
class Nested extends Closure {
function run($c) {
print "a=$a, b=$b, c=$c\n";
}
}
$o = new Nested();
return $o;
}
}
$d = new WantCheckNesting();
$e = $d->run(2, 3);
$e->run(4);
There is a function "create_function", but it is very limited: the body must be a string.
Any other ideas?
You're probably barking at the wrong tree. PHP is not a functional programming language. Some changes have been made starting with PHP 5.3, but even there you don't have your expected variable scopes that would allow you to do what you have in your examples.
The only tools you can use in PHP 4 are create_function and some ingenuity to write the function definition as a string.
<?php
function getMyFunc($a){
return create_function('$b', 'print "a='.$a.', b=$b";');
}
$f1 = getMyFunc(1);
$f1(2);
?>
...but even if this is simple for your posted example, it definitely isn't practical for more complex situations.
In PHP4 you can use Variable Functions like this, provided your function is already defined in the scope (so not a real closure).
THIS IS DIRTY CODE.
THIS IS NOT MUCH TESTED.
THIS IS NOT FAST (even slower than closures if that is possible).
It is just for fun and proving it "somehow" can be done if you really really need it.
Requires a writable tmp/ directory.
<?php
function xxx($a) {
$sid = GetUniversalSessionId();
$tmpfname= 'tmp/'.$sid.'.php';
$handle = fopen($tmpfname, "w");
fwrite($handle, '<?php
function func_'.($sid).'($b) {
$a='."'".str_replace("\'","\\'",str_replace("\\","\\\\",$a))."'".';
print "a=$a, b=$b \n";
}
?>');
fclose($handle);
include($tmpfname);
unlink($tmpfname);
return 'func_'.($sid);
}
$result=xxx(32);
$result(20);
// This is just to get a unique identifier for every connection and every function call:
// UNIVERSALSESSIONIDS v1.3
if(defined('UNIVERSALSESSIONIDS')) return;
define('UNIVERSALSESSIONIDS', TRUE);
function GetTimeStart() {
return strtotime('2006-07-10');
}
function GetUniversalSessionId() {
return GetCodedSid(letterNumBase(),16);
}
function GetCodedSid($baseChars,$Pad=0) {
$Zero=$baseChars{0};
list ($usec, $sec) = explode (' ', microtime());
$new_sid = ($sec-GetTimeStart()) . str_pad ((int)($usec * 100000000), 8, '0', STR_PAD_LEFT) . str_pad (rand (0, 9999), 4, '0', STR_PAD_LEFT);
$new_sid=ConvertDecToAnyStr($new_sid,$baseChars);
$new_sid=str_pad ($new_sid, $Pad, $Zero, STR_PAD_LEFT);
return $new_sid;
}
function divide($decstr,$decnum) {
$Start='';
$Result='';
$WRITE=FALSE;
do {
$Start.=substr($decstr,0,1);
$decstr=substr($decstr,1);
$DecStart=intval($Start);
$Rest=$DecStart%$decnum;
$PartDiv=($DecStart-$Rest)/$decnum;
if($PartDiv>0) $WRITE=TRUE;
if($WRITE) $Result.=$PartDiv;
$Start=$Rest;
} while ($decstr!='');
if($Result=='') $Result='0';
return array($Result,$Rest);
}
function bigBase() {
global $gSavedBigBase;
if(isset($gSavedBigBase)) return $gSavedBigBase;
$BigBase='';
for($i=33;$i<=128;$i++) $BigBase.=chr($i);
$gSavedBigBase=$BigBase;
return $BigBase;
}
function letterNumBase() {
global $gSavedBigBase2;
if(isset($gSavedBigBase2)) return $gSavedBigBase2;
$BigBase='';
for($i=ord('0');$i<=ord('1');$i++) $BigBase.=chr($i);
for($i=ord('A');$i<=ord('Z');$i++) $BigBase.=chr($i);
$BigBase.='_';
// for($i=ord('a');$i<=ord('z');$i++) $BigBase.=chr($i);
$gSavedBigBase2=$BigBase;
return $BigBase;
}
function ConvertDecToAny($decstr,$decbase) {
$Result=array();
do {
$Div=divide($decstr,$decbase);
$decstr=$Div[0];
$Rest=$Div[1];
$Result[]=$Rest;
} while($decstr!='0');
return $Result;
}
function ConvertDecToAnyStr($decstr,$baseChars='01') {
$decbase=strlen($baseChars);
$Result='';
do {
$Div=divide($decstr,$decbase);
$decstr=$Div[0];
$Rest=$Div[1];
$Result=$baseChars{$Rest}.$Result;
} while($decstr!='0');
return $Result;
}
?>