Does PHP have a terse way of creating a "lazy array"? - php

I want a terse way of doing something like this.
$this->requireAllConditionsAreTruthy([
function() { return $this->clan },
function() { return $this->team },
function() { return $this->memberToAdd },
function() { return $this->section },
function() { return $this->clan->hasTeamForSection($this->section) },
function() { return $this->memberToAdd->isInClan($this->clan) },
function() { return ! $this->team->hasMember($this->memberToAdd) },
function() { return ! $this->memberToAdd->isOnTeamForSection($this->section) }
]);
The purpose being that if something like $this->clan turns out to be falsey, I want to be able to throw an exception and log which condition failed and drop out of the array iteration before calling $this->clan->hasTeamForSection.
Does PHP have a terse way of defining a lazy-evaluated array?

It looks like the most straight forward solution is to yield each expression and treat the method like a generator. Using yield, each expression will be lazy-evaluated only when it's looped over.
public function authorization() {
yield $this->clan;
yield $this->team;
yield $this->memberToAdd;
yield $this->section;
yield $this->clan->hasTeamForSection($this->section);
yield $this->memberToAdd->isInClan($this->clan);
yield ! $this->team->hasMember($this->memberToAdd);
yield ! $this->memberToAdd->isOnTeamForSection($this->section);
}
Then to process the authorization, I can do this:
public function authorize() {
foreach ($this->authorization() as $key => $condition) {
if (!$condition) {
Log::debug(sprintf("Authorization condition %s failed in %s", $key, get_class($this));
throw new AuthorizationException;
}
}
}
That will keep the authorization validation to a minimum, while still providing more precise feedback about which condition failed, for debugging or general logging purposes.

Related

What is the correct way of using #scope directive?

I'm having trouble understanding how #scope directive works. When I add the directive to query definition and give the IsOwner: true Lighthouse adds it to the scope but the problem starts when I change it to IsOwner:false. Lighthouse still applies the scope into the query. Only time Lighthouse doesn't apply the scope when I remove IsOwner parameter from the query.
My query definition is like following;
listings(IsOwner: Boolean #scope(name:"IsOwner"), where: _ #whereConditions(columnsEnum: "ListingColumn"), orderBy: _ #orderBy(columnsEnum: "ListingColumn")) : [Listing!]! #paginate(maxCount: 50 defaultCount: 10)
And actual query is like below;
query ($min_lng:Float!, $min_lat:Float!, $max_lng:Float!, $max_lat:Float!, $first:Int, $page:Int, $withPaginatorInfo:Boolean!, $withListingImages:Boolean!, $withListingMeta:Boolean!, $withListingViews:Boolean!, $withListingActions:Boolean!, $withListingNotes:Boolean!, $withListingReminders:Boolean!, $withListingViewUser:Boolean = false, $conditions: ListingsWhereWhereConditions, $orderBy: [ListingsOrderByOrderByClause!]) {
listings(
bbox: {
min_lng: $min_lng
min_lat: $min_lat
max_lng: $max_lng
max_lat: $max_lat
}
IsOwner: false
where: $conditions
orderBy: $orderBy
first: $first
page: $page
) {
data {
...listingFields
}
paginatorInfo #include(if: $withPaginatorInfo) {
currentPage
lastPage
}
}
}
Model scope it like following;
public function scopeIsOwner($query)
{
return $query->join('listing_user', 'listing_user.listing_id', '=', 'listings.id')->where('listing_user.user_id', Auth::user()->id);
}
*** UPDATE ***
I figured it out after the comments, I changed the model scope to the following
public function scopeIsOwner($query, $isEnabled)
{
if ($isEnabled) {
return $query->join('listing_user', 'listing_user.listing_id', '=', 'listings.id')->where('listing_user.user_id', Auth::user()->id);
}
return $query;
}
I made minor changes on the schema like below,
listings(scopeIsOwner: Boolean #scope(name:"IsOwner"), bbox: BoundingBoxInput! #builder(method: "App\\GraphQL\\Builders\\BoundingBoxSearch#bbSearch"), where: _ #whereConditions(columnsEnum: "ListingColumn"), orderBy: _ #orderBy(columnsEnum: "ListingColumn")) : [Listing!]! #guard(with: ["api"]) #paginate(maxCount: 50 defaultCount: 10)
My final query and it's variable is like the following,
query ($min_lng:Float!, $min_lat:Float!, $max_lng:Float!, $max_lat:Float!, $first:Int, $page:Int, $withPaginatorInfo:Boolean!, $withListingImages:Boolean!, $withListingMeta:Boolean!, $withListingViews:Boolean!, $withListingActions:Boolean!, $withListingNotes:Boolean!, $withListingReminders:Boolean!, $withListingViewUser:Boolean = false, $scopeIsOwner:Boolean = false $conditions: ListingsWhereWhereConditions, $orderBy: [ListingsOrderByOrderByClause!]) {
listings(
bbox: {
min_lng: $min_lng
min_lat: $min_lat
max_lng: $max_lng
max_lat: $max_lat
}
scopeIsOwner: $scopeIsOwner
where: $conditions
orderBy: $orderBy
first: $first
page: $page
) {
data {
...listingFields
}
paginatorInfo #include(if: $withPaginatorInfo) {
currentPage
lastPage
}
}
}
Variables;
{
"min_lng": 29.0401317,
"min_lat": 41.0028473,
"max_lng": 29.0308512,
"max_lat": 40.9916271,
"withPaginatorInfo" : true,
"first": 10,
"page": 1,
"withListingImages": false,
"withListingMeta": false,
"withListingViews": false,
"withListingActions": false,
"withListingNotes": false,
"withListingReminders": false,
"withListingViewUser": false,
"conditions": {"AND": [{"column": "PRICE", "operator": "LTE","value": "190"}]},
"orderBy": [{ "column": "TENURE_ID", "order": "DESC" }],
"scopeIsOwner": false
}
As you can understand when I give true to scopeIsOwner variable endpoint enables the scope and manipulate the query accordingliy.
You are misunderstanding. See https://lighthouse-php.com/master/api-reference/directives.html#scope
The scope method will receive the client-given value of the argument as the second parameter.
Your scope should react to the argument the client passes.
There is a bug in Lighthouse library. Scope is added when parameter exist without checking value. ScopeDirective class and handleBuilder method are quite simple
public function handleBuilder($builder, $value)
{
$scope = $this->directiveArgValue('name');
try {
return $builder->{$scope}($value);
} catch (BadMethodCallException $exception) {
throw new DefinitionException(
$exception->getMessage()." in {$this->name()} directive on {$this->nodeName()} argument.",
$exception->getCode(),
$exception->getPrevious()
);
}
}
I think $value contains true or false and this variable should not be injected to scope here:
$builder->{$scope}($value);
Instead, should be something like:
if ($value == true) {
$builder->{$scope}();
}

What's the use of a callback function in PHP?

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'];
}
);

Trouble stopping the foreach loop depend upon promises resolve or reject

$msg_sent = false;
foreach ($channels as $channel) {
$resolve = function() use ( &$msg_sent )
{
$msg_sent = true;
};
$reject = function( \Exception $e )
{
error_log( $e, 3, './error.txt' . PHP_EOL );
};
$channel->send('hlw')->done( $resolve, $reject );
if ( $msg_sent ){
break;
} else {
continue;
}
}
As you can see above,
$msg_sent is false,
$channels is a array with 3 instance of same objects(have different values)
and when i tap send(), it returns a ExtendedPromiseInterface, in which $resolve is executed when message is sent and $reject when not sent.
So what i want to do is check if the message is sent or not, if not then continue the loop and try to send message to another channel, if sent then break the loop.
But unexpectedly it always return false and the loop runs even if the message is sent.
Hey ReactPHP team member here. That foreach loop will just send everything at (nearly) the same time. You could do something like this to only send a request after you receive the previous one with this (pseudo code), which resolves with true on success and false when all calls fail:
final class Bier
{
public function check($channels): PromiseInterface
{
$channel = array_shift($channels);
return $channel->send('hlw')->then(function () {
return resolve(true);
}, function () use ($channels) {
if (count($channels) === 0) {
return resolve(false);
}
return resolve($this->check($channels));
});
}
}

Returning a value from asynchronous PHP

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();
}

how to iterate over JSON property not knowing if it's array or not?

I have this problem where an API responds to me with DEPARTURESEGMENT sometimes containing only one object, and sometimes containing an array of objects. Depending on which case it is, I seem to need different logics in my foreach-loop.
Response A:
{
"getdeparturesresult":{
"departuresegment":[{
"departure":{
"location":{
"#id":"7461018",
"#x":"12.523958",
"#y":"57.938402",
"name":"Noltorps centrum"
},
"datetime":"2014-12-04 23:05"
},
"direction":"Alingsås station",
"segmentid":{
"mot":{
"#displaytype":"B",
"#type":"BLT",
"#text":"Buss"
},
"carrier":{
"name":"Västtrafik",
"url":"http://www.vasttrafik.se/",
"id":"279",
"number":"1"
}
}
},
{
"departure":{
"location":{
"#id":"7461018",
"#x":"12.523958",
"#y":"57.938402",
"name":"Noltorps centrum"
},
"datetime":"2014-12-04 23:05"
},
"direction":"Alingsås station",
"segmentid":{
"mot":{
"#displaytype":"B",
"#type":"BLT",
"#text":"Buss"
},
"carrier":{
"name":"Västtrafik",
"url":"http://www.vasttrafik.se/",
"id":"279",
"number":"1"
}
}
}
]
}
}
Works with this loop:
foreach ($apiData->getdeparturesresult->departuresegment as $m) {
While this response B:
{
"getdeparturesresult":{
"departuresegment":{
"departure":{
"location":{
"#id":"7461018",
"#x":"12.523958",
"#y":"57.938402",
"name":"Noltorps centrum"
},
"datetime":"2014-12-04 23:05"
},
"direction":"Alingsås station",
"segmentid":{
"mot":{
"#displaytype":"B",
"#type":"BLT",
"#text":"Buss"
},
"carrier":{
"name":"Västtrafik",
"url":"http://www.vasttrafik.se/",
"id":"279",
"number":"1"
}
}
}
}
}
needs a loop like this (otherwise it throws an error):
foreach ($apiData->getdeparturesresult as $m) {
Is there a way to write the loop failsafe for whether DEPARTURESEGMENT is an array of objects or just one object (the brackets [] is the only difference to the structure of the json right?) or do I have to somehow test and see first whether DEPARTURESEGMENT is an array or not, and dispatch to two different loops depending on the outcome?
You have a few methods that can help you:
is_array
is_object
instanceof // if you receive specific object
gettype
json_decode second parameter, which if is set to true, tries to decode the json as an array
In you situation, you would probably be fine by doing the following:
if (is_object($entry)) {
handleObject($entry);
} elseif (is_array($entry) && count($entry)) {
foreach ($entry as $e) {
handleObject($e);
}
}
I have this little useful function in my standard repertoire:
function iter($x) {
if(is_array($x))
return $x;
if(is_object($x)) {
if($x instanceof \Iterator)
return $x;
if(method_exists($x, 'getIterator'))
return $x->getIterator();
return get_object_vars($x);
}
return array($x);
}
This way you can use any variable with foreach without having to check it beforehand:
foreach(iter($whatever) as $item)
...
How about checking whether it's an array or not with is_array?
I made a simple example of it's usage here - http://codepad.org/WNjbIPZF

Categories