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
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());
}
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;
}, []);
}
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.
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.
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.