What is the correct way of using #scope directive? - php

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

Related

php defined json structure type hint

I want to type hint this json structure in php:
{
"settings": {
"signup": {
"logging": true,
"forcePrompt": false,
"completedSteps": [
1,
2,
3
]
},
"trash": {
"retentionDays": 30,
"enabled": true
}
}
}
is there some way how I can do that with a single "Settings" class instead of having to define separate classes for every nested attribute (signup, trash).
in typescript I can just define at like this:
{
settings: {
signup: {
logging: boolean,
forcePrompt: boolean,
completedSteps: number[]
},
trash: {
retentionDays: number,
enabled: boolean
}
}
}
No, but you can roll your own and get pretty close. For example, I've a class that acts as a settings storage and extends ArrayObject for that, so it's pretty standard non-exotic stuff.
If you're fine with just checking the leaf attributes (e.g. "Is attribute X a boolean?" instead of "Is attribute X as a child of attribute Y a boolean"?), you could do a basic type checking like so (I've shortened the example code a bit, you'll find the full code here):
// Your data
$data = '{
"settings": {
"signup": {
"logging": true,
"forcePrompt": false,
"completedSteps": [
1,
2,
3
]
},
"trash": {
"retentionDays": 30,
"enabled": true
}
}
}
';
// We'll use this for type hinting
$defaults = '{
"settings": {
"signup": {
"logging": true,
"forcePrompt": false,
"completedSteps": []
},
"trash": {
"retentionDays": 1,
"enabled": true
}
}
}
';
/**
* Basic collection object.
*
* Usage:
* ------
* $collection->acme_social->twitter->client_id
*/
class Collection extends \ArrayObject {
/**
* Initialize and turn all levels of the given
* array into a collection object.
*
* #param Array $data
*/
public function __construct(array $data, array $defaults = []) {
parent::__construct($data, \ArrayObject::ARRAY_AS_PROPS);
// Turn all arrays into Collections
$iterator = new \RecursiveIteratorIterator(
new \RecursiveArrayIterator($this), \RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $key => $value) {
// Evaluate types
array_walk_recursive($defaults, function ($v, $k) use ($key, $value) {
if ($key == $k) {
$expected = gettype($v);
$given = gettype($value);
if ($expected != $given) {
throw new InvalidArgumentException("{$k} needs to be of type {$expected}, {$given} given.");
}
}
});
if (is_array($value)) {
$iterator->getInnerIterator()->offsetSet($key, new static($value));
}
}
}
}
try {
$collection = new Collection(json_decode($data, true), json_decode($defaults, true));
}
catch(Exception $e) {
var_dump($e->getMessage());
}

Implementing filter of nested arrays in PHP/Wordpress

I have in the process of moving some code from the front-end (in JavaScript) to the server-side (which is PHP) where it will be filtered and sent out in an API call, and I can't seem to get the filter working properly on the back-end. The code takes an array of objects and filters it for the objects where a certain nested field (which is also an array of objects) contains certain values. The basic shape of the API:
{
"id": 1217,
"name": "Best product ever",
"tags": [
{
"id": 125,
"name": "Important Value",
"slug": "important-value"
},
{
"id": 157,
"name": "Value",
"slug": "value"
},
{
"id": 180,
"name": "Value",
"slug": "value"
},
{
"id": 126,
"name": "Value",
"slug": "value"
},
{
"id": 206,
"name": "Other Important Value",
"slug": "other-important-value"
}
}
The working JS code:
let productAttributes = ['important-value', 'value', 'value', 'value', 'other-important-value'];
filterResults(results) {
let filteredResults = results.filter(product => {
return product.tags.find(tag => {
return tag.slug === this.productAttributes[0];
});
});
if (this.productAttributes[0] !== 'certain important value') {
filteredResults = filteredResults.filter(product => {
return product.tags.find(tag => {
return tag.slug === this.productAttributes[4];
});
});
}
return filteredResults;
}
And the (not yet working) PHP code:
function get_awesome_products() {
$baseRequest = 'https://myawesomeapi/wp-json/wc/v3/products/?
consumer_key=xxxx&consumer_secret=xxxx&per_page=100&page=';
for ($count = 1; $count <= 9; $count++ ) {
$request = wp_remote_get( $baseRequest . (string)$count);
$body = wp_remote_retrieve_body( $request );
$data = array_values( json_decode( $body, true ));
if ($count < 2) {
$completeProductList = $data;
} else {
$completeProductList = array_merge($completeProductList, $data);
}
}
// The code above this comment is doing what I expect, the code below is not.
$filteredProducts = null;
foreach ($completeProductList as &$product) {
$tagArray = $product['tags'];
if (in_array($reg_test_array[0], $tagArray, true) &&
in_array($reg_test_array[4], $tagArray, true))
{
array_push($filteredProducts, $product);
}
unset($product);
return new WP_REST_Response($filteredProducts, 200);
The impression I get is that I need to write a custom function to take the place of Array.prototype.find(), but I'm not strong in PHP and am having trouble wrapping my head around it.
EDIT: Edited to add example of object being filtered and additional PHP code
You could also use the PHP equivalent function array_filter (among a few other array-specific functions) for this task.
Example:
// Your 0 and 4 index values from $reg_test_array
$importantTags = [ "important-value", "other-important-value" ];
$filteredProducts = array_filter($completeProductList, function($product) use ($importantTags) {
return (bool)array_intersect($importantTags, array_column($product['tags'], 'slug'));
});
return new WP_REST_Response($filteredProducts , 200);
Sandbox
This should be equivalent to the JavaScript code you posted, but done without looping through the filtered results twice.
Without knowing the context of important-value and other-important-value, and how they come to be ordered in the $attributes array, it's a little difficult to improve upon the conditional checks used. What I've written thus far however feels like a code smell to me, because it's reliant hard coded values.
function filterResults(array $results, array $attributes)
{
return array_reduce($results, function ($filteredResults, $result) use ($attributes) {
// Extract tag slugs from result
$tagSlugs = array_column($result['tags'], 'slug');
// Append result to filtered results where first attribute exists in tag slugs;
// Or first attribute is not *other-important-value* and fourth attribute exists in tag slugs
if (in_array($attribute[0], $tagSlugs) && ($attribute[0] === 'other-important-value' || in_array($attribute[4], $tagSlugs))) {
$filteredResults[] = $result;
}
return $filteredResults;
}, []);
}

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

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.

how to search in Mongodb by querying multiple fields in the same document

I'm going over a project I've taken over which is crashing in this doctrine/mongoDB query:
/**
* Deactivate all Device documents via set 'activate = false' and unsetting
* passenger field for all devices that has passenger = $oPassenger
* and Device._id != oDevice
* #param Document\Device $oDevice
* #param Document\Passenger $oPassenger
*/
public function deactivateDuplicateDevices(Document\Device $oDevice,
Document\Passenger $oPassenger)
{
$passengerId = new \MongoId($oPassenger->getId());
$deviceId = new \MongoId($oDevice->getId());
return $this->createQueryBuilder('Device')
->update()
->multiple(true)
->field('activated')->set(false)
->field('passenger')->unsetField()->equals($passengerId)
->field('_id')->notEqual($deviceId)
->getQuery()
->execute();
}
it seems to me that the author is running multiple queries on the same document ie
find all documents where
passenger == passengerId AND
device != deviceId
I'm trying to recreate this query piece by piece (in JSON and running it on the mongo console) to see where the problem is. this is the final input I got (to the mongo initiated, this may seem absurd.. but please bear with me):
db.Device.update(
{
passenger:{
$ne: "538c6eac3c0ab224080041aa"
}
},
{
_id:{
$ne: "538eb8d205dafff40a0041ad"
}
}
{
$unset:{
passenger:""
}
},
{
$set:
{
activated:false
}
}
)
I wanted to test the first part, namely the multiple queries (obviously searching for passenger alone and _id alone work.. but when i combine them):
db.Device.find({
_id:{
$ne:ObjectId("538eb8d205dafff40a0041ad")
}
},
{
passenger:{
$ne:"538c6eac3c0ab224080041aa"
}
}
)
I get this error:
error: {
"$err" : "Can't canonicalize query: BadValue Unsupported projection option: passenger: { $ne: \"538c6eac3c0ab224080041aa\" }",
"code" : 17287
}
any idea what i'm doing wrong here?
You should use $and (http://docs.mongodb.org/manual/reference/operator/query/and/)
db.Device.find({ $and: [ {
_id:{
$ne:ObjectId("538eb8d205dafff40a0041ad")
}
},
{
passenger:{
$ne:"538c6eac3c0ab224080041aa"
}
} ] }
)
You can simply do
db.Device.find({
_id: {$ne: ObjectId("538eb8d205dafff40a0041ad")},
passenger: {$ne: "538c6eac3c0ab224080041aa"}
})
or
db.Device.update({
_id: {$ne: ObjectId("538eb8d205dafff40a0041ad")},
passenger: {$ne: "538c6eac3c0ab224080041aa"}
},
{
$unset: {passenger: 1},
$set: {activated: false}
})
Note that there is only one query object argument and one update object argument. There are other optional arguments you can check out in the docs.

Function is not returning true or false, if it is correct

I am writing a function which checks nested keys if it exists in JSON, but i hav stuck at place when if code is correct then it must return true or false but it is not. it returns null value
php function is
function checkNestedKeysExists($JSONRequest,$keyCheckArray){
$currentKey = current($keyCheckArray);
$JSONRequest = array_change_key_case($JSONRequest, CASE_LOWER);
if(array_key_exists($currentKey,$JSONRequest)){
if($currentKey==end($keyCheckArray)){
return true;
}
else {
array_shift($keyCheckArray);
$this->checkNestedKeysExists($JSONRequest[$currentKey],$keyCheckArray);
//echo "F";
}
}
else{
return false;
}
}
given array is
$keyCheckArray = array('data','device_info','deviceid');
and $JSONRequest is
{
"timestamp": "2014-01-01 11:11:11",
"data": {
"requestid": "bcpcvssi1",
"device_info": {
"os": "Android",
"deviceId": "123123",
"userProfile": {
"email": [
"abc#gmail.com"
],
"gender": "Male",
"age": "19",
"interest": [
"Apple",
"Banana"
]
}
}
}
}
Modify the line of your code where you make recursive call like following
return $this->checkNestedKeysExists($JSONRequest[$currentKey],$keyCheckArray);
So it will return the result of the call
pass $JSONRequest in
json_decode($JSONRequest, true);
EDIT: Sorry I got it wrong in first time.
Use array[0] instead of current() if you are shifting elements, maybe it is making a problem. And of course, do the var_dump() to check values.
$currentkey = 'data' and end($keyCheckArray) = 'deviceid'. That will never return true, hence you have no return values specified and it will return null.
two advices:
give the function with every possible way to end the function a valid return value.
create a variable for every fixed outcome like end($keyCheckArray).
If have tested your function (and edited it for testing purposes):
function checkNestedKeysExists($JSONRequest,$keyCheckArray){
$currentKey = current($keyCheckArray);
$JSONRequest = array_change_key_case($JSONRequest, CASE_LOWER);
$endValue = end($keyCheckArray);
if(array_key_exists($currentKey,$JSONRequest)){
print 'currentKey = '.$currentKey.", end = ".$endValue."<br>\n";
if($currentKey== $endValue){
return 'correct';
}else {
array_shift($keyCheckArray);
$p = checkNestedKeysExists($JSONRequest[$currentKey],$keyCheckArray);
print "p = ".$p."<br>\n";
//echo "F";
return $currentKey;
}
}
else{
return false;
}
}
The output is like this:
correct
device_info
data
I suggest you change your function into a while loop. Once you have found the requested result, return true.

Categories