PHP Mongo MapReduce - How to Call Server Loaded JavaScript Functions - php

First, I want to say that this is more of a PHP Mongo Driver issue than MongoDB issue.
I have a problem with calling MapReduce through PHP. I have 3 custom functions inside Mongo for MapReduce: mapItems, reduceItems, finalizeItems.
When I test my functions from inside Mongo Shell, everything works great:
db.loadServerScripts();
db.runCommand({
mapreduce: 'items',
map: mapItems,
reduce: reduceItems,
finalize: finalizeItems,
out: {inline: 1},
scope: {members: {a1: 0, a2: 0}
});
Now when I try to do the same in PHP Mongo Driver, nothing works.
$db->command([
'mapreduce' => 'items',
'map' => 'mapItems',
'reduce' => 'reduceItems',
'finalize' => 'finalizeItems',
'out' => ['inline' => 1],
'scope' => ['members' => ['a1' => 0, 'a2' => 0]]
]);
I thought maybe I need to use MongoCode for the functions, but that still didn't work:
$db->command([
'mapreduce' => 'items',
'map' => new \MongoCode('mapItems'),
'reduce' => new \MongoCode('reduceItems'),
'finalize' => new \MongoCode('finalizeItems'),
'out' => ['inline' => 1],
'scope' => ['members' => ['a1' => 0, 'a2' => 0]]
]);
Then I thought maybe the PHP driver is being stupid, so I copy pasted the functions into PHP and tried them:
$db->command([
'mapreduce' => 'items',
'map' => new \MongoCode($mapFn),
'reduce' => new \MongoCode($reduceFn),
'finalize' => new \MongoCode($finalizeFn),
'out' => ['inline' => 1],
'scope' => ['members' => ['a1' => 0, 'a2' => 0]]
]);
And this worked.
How do I need to pass the internal functions to Mongo through PHP?

What you have done so far
This is a classic case of things not working how you seem to think they work. Let's break this down logically for everyone to understand.
So what you clearly have done to date is create some JavaScript function definitions and stored them in system.js. Now anything that has been stored in such a way can be called by server side JavaScript operations, which is fine. An example:
db.system.js.save({
"_id": squareThis",
"value": function(x) { return x*x; }
})
db.test.insert({ "a": 3 ))
db.test.find(function() { return squareThis(this.a) == 9 })
So that works as expected where the "server" can use the function. Not if you did this ( as you mention earlier ) :
db.loadServerScripts();
Now any functions in system.js are now available for the "shell client" to use as well. So you can now do this in the JavaScript REPL:
> squareThis(3) == 9
true
What you are doing wrong
Now you want to code with PHP. So you go to call your JavaScript methods you created on the "server" from your PHP code and you try this:
'map' => new \MongoCode('mapItems'),
So what is MongoCode? Well it basically defines a BSON type for a code reference ( expected to be JavaScript ) and sends that to the server.
The clear problem here is that "mapItems" is not JavaScript code. You "shell client" recognized that, but only after calling db.loadServerScripts(), which of course it can do, because that client understands JavaScript.
The same is true for the earlier attempt. Your PHP mapReduce command expects to have MongoCode objects that actually contain valid JavaScript. These are going to be sent to the server where it can acutally call functions defined there, but you still need to provide the mapReduce command with "real code" that does something.
Fixing It
Fortunately there is a way around this, in that you can basically "define a function to call a function" essentially "wrapping" the method calls so the "server" loaded code can be called.
The sytax varies depending on if the function is supposed to return anything so:
$db->command([
'mapreduce' => 'items',
'map' => new \MongoCode('function() { mapItems(this) }'),
'reduce' => new \MongoCode('function(key,values) { return reduceItems(key,values) }'),
'finalize' => new \MongoCode('function(key,value) { return finalizeItems(key,value)}'),
'out' => ['inline' => 1],
'scope' => ['members' => ['a1' => 0, 'a2' => 0]]
]);
So now your PHP mapReduce command sends up it's "packaged command" with valid JavaScript functions attached to the appropriate method stages. These "code wrappers" execute on the "server" and can then call the respective "server" methods that are stored in system.js.
The reason for the explicit argument passing i.e
function(key,values){ serverFunction(key,values) }
Is because your "wrappers" need to create the same sort of "signatures" as expected by the functions of "mapReduce" in general. And since they are "calling" the other methods "server side", then you need to "pass-through" the expected parameters.
This also has another implication for the "mapper" function. You are likely referring to all document content through the this context of JavaScript objects. Once you do this then you cannot do that anymore and will need to "explicitly" define a document argument to your "mapper"
db.system.js({
"_id": "mapper",
"value": function(doc) {
// use doc instead of this
}
And then even from the Mongo Shell you will also need to call like this:
db.runCommand({
"mapReduce": "items",
"mapper": function() { mapper(this) }
In order for the logic to work.
So server side functions are not really what you thought they are. You can use them, but you need to follow the rules of how they can be used.
In general it is probably better that you simply just code all of these up in your client rather than jump through all the hoops. It is afterall "JavaScript" execution for which the only language is "JavaScript", so that is why alternate methods of processing are generally preferred as long as they can be applied.
And if your document structure needs JavaScript processing, then you probably have your document structure wrong in the first place. So the better case here is "re-evaluate" your structure and processing needs to get you "cleaner" and "faster" code.

Related

How do you implement OpenAI GPT-3 Api Client in PHP?

I need help understanding the vague instructions on https://packagist.org/packages/orhanerday/open-ai
I downloaded the package from https://github.com/orhanerday/open-ai
I installed the package by running "composer require orhanerday/open-ai" in my Command Prompt
Instructions stop making sense from there.....
What does the "use Orhanerday\OpenAi\OpenAi;" code mean and where is it applied?
Am I to create a php file say index.php with content:
<?php
use Orhanerday\OpenAi\OpenAi;
$complete = $open_ai->complete([
'engine' => 'davinci',
'prompt' => 'Hello',
'temperature' => 0.9,
'max_tokens' => 150,
'frequency_penalty' => 0,
'presence_penalty' => 0.6,
]
?>
how and where do I add my api key? Do I create a file Orhanerday\OpenAi\OpenAi.php and enter my api key there?
i.e. OPENAI_API_KEY=sk-**********************************************
You should define the $open_ai variable as an OpenAI object by passing your private KEY value, like; new OpenAi('Your-OPENAI-KEY');
An Example;
<?php
use Orhanerday\OpenAi\OpenAi;
$open_ai = new OpenAi('OPEN-AI-KEY');// <- define the variable.
$complete = $open_ai->complete([
'engine' => 'davinci',
'prompt' => 'Hello',
'temperature' => 0.9,
'max_tokens' => 150,
'frequency_penalty' => 0,
'presence_penalty' => 0.6,
]);
I also add the Quick Start Part to orhanerday/OpenAI readme.
First you have you include thé 'autoload'file. 'use' doesn't mean that you have to create a file yourself.

php class in an array

I have a php file where I have an array with some data in classes like this: (in javascript)
array = [
{engine: "V6",
capacity: 2.5,
hp: 300
}]
I tried to put this into a php file but it gave me 500 error
$array = [{engine => "V6",
capacity=> 2.5,
hp=> 300}];
I think I may be doing something wrong over here, could someone confirm?
You can't just translate JavaScript code into PHP without understanding the syntax of both languages.
In JavaScript, this creates an array containing an object:
var array = [
{
engine: "V6",
capacity: 2.5,
hp: 300
}
]
In PHP, the common equivalent would be an array containing another (associative) array, which would be written like this:
$array = [
[
'engine' => "V6",
'capacity' => 2.5,
'hp' => 300
]
];
As an aside, since it's been mentioned elsewhere on this page, you could also write the same thing in JSON like this:
[
{
"engine": "V6",
"capacity": 2.5,
"hp": 300
}
]
If you called JSON.stringify(array) in JavaScript, or json_encode($array) in PHP, that's how the JSON string you'd get would look.
The JSON looks very similar to the JavaScript, because JSON's syntax was based on JavaScript's. In fact, the above would be valid JavaScript code, and you can use (arguably, abuse) json_encode as a way to generate JavaScript code using PHP.
Note that none of these contains any kind of "class" - a class is a way of defining a type of object, from which you want to make several instances.
You are mixing PHP with JSOL or JSON. PHP doesn't read JSOL nor JSON natively.
<?php
$array = [
[
'engine' => 'V6',
'capacity' => 2.5,
'hp' => 300
],
];
echo json_encode($array);
See https://3v4l.org/5b9V1

How to add a "pipe" into a get parameter during guzzle query building

I'm using an api that has a "range" parameter that can apply to several different parameter items. The range i'm focusing on is "price". I'm using guzzle in laravel and according to the api documentation, the query for this particular parameter should be written like this "&range_facet=price|500|2500|250"...this is broken down into the minimum, maximum, and interval values of the price range parameter. That's not necessarily important to this question. When i try and run this query as is, i get nothing returned. When I remove that particular parameter, i get values but obviously they're not filtered the way i want them to be. When i run this in Insomnia, the pipes are replaced by "%7C", which is obviously (obviously?) not interpreted by the api as it's not how it's waiting for the GET request to be made. How can I insert the pipes into the query so that it calls the correct way?
I've tried to create an additional nested array with the price value being broken up into key value pairs but that didn't work either.
'range_facets' => ['price'['start'=>'500', end=>'2500', 'interval'=>'250']],
$client = new Client();
$result = $client->request('GET', "http://api.example.com", [
'headers' => [
'Host' => 'example-host',
'Content-Type' => 'application/json'
],
'query' => [
'api_key' => 'my_api_key',
'range_facets' => 'price|500|2500|250',
'year' => $year,
'latitude' => '30.170222',
'longitude' => '92.01320199',
'radius' => 500,
'start' => 0,
'rows' => 50
]
]);
I'd like to filter my prices but I need the pipe to be able to do it.
This is exactly how it should be. %7C should be decoded on the server side to | automatically (about query string encoding).
I bet the issue is in different place.

understanding ElasticSearch routing

I am trying to use the elasticsearch routing mapping to speed up some queries, but I am not getting the expected result set (not worried about the query performance just yet)
I am using Elastic to set up my mapping:
$index->create(array('number_of_shards' => 4,
'number_of_replicas' => 1,
'mappings'=>array("country"=>array("_routing"=>array("path"=>"countrycode"))),
'analysis' => array(
'analyzer' => array(
'indexAnalyzer' => array(
'type' => 'keyword',
'tokenizer' => 'nGram',
'filter' => array('shingle')
),
'searchAnalyzer' => array(
'type' => 'keyword',
'tokenizer' => 'nGram',
'filter' => array('shingle')
)
)
) ), true);
If I understand correctly, what should happen is that each result should now have a field called "countrycode" with the value of "country" in it.
The results of _mapping look like this:
{"postcode":
{"postcode":
{"properties":
{
"area1":{"type":"string"},
"area2":{"type":"string"},
"city":{"type":"string",
"include_in_all":true},
"country":{"type":"string"},
"country_iso":{"type":"string"},
"country_name":{"type":"string"},
"id":{"type":"string"},
"lat":{"type":"string"},
"lng":{"type":"string"},
"location":{"type":"geo_point"},
"region1":{"type":"string"},
"region2":{"type":"string"},
"region3":{"type":"string"},
"region4":{"type":"string"},
"state_abr":{"type":"string"},
"zip":{"type":"string","include_in_all":true}}},
"country":{
"_routing":{"path":"countrycode"},
"properties":{}
}
}
}
Once all the data is in the index if I run this command:
http://localhost:9200/postcode/_search?pretty=true&q=country:au
it responds with 15740 total items
what I was expecting is that if I run the query like this:
http://localhost:9200/postcode/_search?routing=au&pretty=true
Then I was expecting it to respond with 15740 results
instead it returns 120617 results, which includes results where country is != au
I did note that the number of shards in the results went from 4 to 1, so something is working.
I was expecting that in the result set there would be an item called "countrycode" (from the rounting mapping) which there isn't
So I thought at this point that my understand of routing was wrong. Perhaps all the routing does is tell it which shard to look in but not what to look for? in other words if other country codes happen to also land in that particular shard, the way those queries are written will just bring back all records in that shard?
So I tried the query again, this time adding some info to it.
http://localhost:9200/postcode/_search?routing=AU&pretty=true&q=country:AU
I thought by doing this it would force the query into giving me just the AU place names, but this time it gave me only 3936 results
So I Am not quite sure what I have done wrong, the examples I have read show the queries changing from needing a filter, to just using match_all{} which I would have thought would only being back ones matching the au country code.
Thanks for your help in getting this to work correctly.
Almost have this working, it now gives me the correct number of results in a single shard, however the create index is not working quite right, it ignores my number_of_shards setting, and possibly other ones too
$index = $client->getIndex($indexname);
$index->create(array('mappings'=>array("$indexname"=>array("_routing"=>array("required"=>true))),'number_of_shards' => 6,
'number_of_replicas' => 1,
'analysis' => array(
'analyzer' => array(
'indexAnalyzer' => array(
'type' => 'keyword',
'tokenizer' => 'nGram',
'filter' => array('shingle')
),
'searchAnalyzer' => array(
'type' => 'keyword',
'tokenizer' => 'nGram',
'filter' => array('shingle')
)
)
) ), true);
I can at least help you with more info on where to look:
http://localhost:9200/postcode/_search?routing=au&pretty=true
That query does indeed translate into "give me all documents on the shard where documents for country:AU should be sent."
Routing is just that, routing ... it doesn't filter your results for you.
Also i noticed you're mixing your "au"s and your "AU"s .. that might mix things up too.
You should try setting required on your routing element to true, to make sure that your documents are actually stored with routing information when being indexed.
Actually to make sure your documents are indexed with proper routing explicitly set the route to lowercase(countrycode) when indexing documents. See if that helps any.
For more information try reading this blog post:
http://www.elasticsearch.org/blog/customizing-your-document-routing/
Hope this helps :)

jQuery.parseJSON fails to parse valid json

load : function() {
var mastery = $('div.mastery.trees');
this.object = $.parseJSON($('a.json.data', mastery).text());
console.log(this.object);
}
this fails silently (unless i add a try/catch block around parseJSON) regardless of the content of the json data (the data i want to use - see below - or even just some very simple test data)
actual data
{"points":{"allowed":30,"current":0,"offense":0,"defense":0,"utility":0},"current":{"offense":{},"defense":{},"utility":{}},"trees":{"offense":{"Deadliness":{"max":3,"req":""},"Cripple":{"max":1,"req":""},"Plentiful_Bounty":{"max":1,"req":""},"Archmages_Savvy":{"max":3,"req":""},"Sorcery":{"max":4,"req":{"offense":4}},"Alacrity":{"max":4,"req":{"offense":4}},"Burning_Embers":{"max":1,"req":{"offense":8}},"Archaic_Knowledge":{"max":1,"req":{"offense":8,"Sorcery":4}},"Sunder":{"max":3,"req":{"offense":8}},"Offensive_Mastery":{"max":2,"req":{"offense":8}},"Brute_Force":{"max":3,"req":{"offense":12}},"Lethality":{"max":3,"req":{"offense":16}},"Improved_Rally":{"max":1,"req":{"offense":16}},"Havoc":{"max":1,"req":{"offense":20}}},"defense":{"Menders_Faith":{"max":1,"req":""},"Resistance":{"max":3,"req":""},"Preservation":{"max":1,"req":""},"Hardiness":{"max":3,"req":""},"Strength_of_Spirit":{"max":3,"req":{"defense":4,"Resistance":3}},"Evasion":{"max":4,"req":{"defense":4}},"Defensive_Mastery":{"max":2,"req":{"defense":8}},"Nimbleness":{"max":1,"req":{"defense":8,"Evasion":4}},"Harden_Skin":{"max":3,"req":{"defense":8,"Hardiness":3}},"Veterans_Scars":{"max":4,"req":{"defense":12}},"Willpower":{"max":1,"req":{"defense":12}},"Ardor":{"max":3,"req":{"defense":16}},"Reinforce":{"max":1,"req":{"defense":16}},"Tenacity":{"max":1,"req":{"defense":20}}},"utility":{"Spatial_Accuracy":{"max":1,"req":""},"Good_Hands":{"max":3,"req":""},"Perseverance":{"max":3,"req":""},"Haste":{"max":1,"req":""},"Awareness":{"max":4,"req":{"utility":4}},"Expanded_Mind":{"max":4,"req":{"utility":4}},"Greed":{"max":1,"req":{"utility":8}},"Meditation":{"max":3,"req":{"utility":8}},"Utility_Mastery":{"max":2,"req":{"utility":8}},"Insight":{"max":1,"req":{"utility":8}},"Quickness":{"max":3,"req":{"utility":12}},"Blink_of_an_Eye":{"max":1,"req":{"utility":12}},"Intelligence":{"max":3,"req":{"utility":16}},"Mystical_Vision":{"max":1,"req":{"utility":16}},"Presence_of_the_Master":{"max":1,"req":{"utility":20}}}}}
test data
{"json":1}
jsonlint.com tells me both of these data-sets are valid json so why wont it parse properly?
all the data comes from a php array thats converted via json_encode()
return array(
"points" => array( // for js
"allowed" => 30,
"current" => 0,
"offense" => 0,
"defense" => 0,
"utility" => 0,
),
"current" => array( // for js
"offense" => new stdClass,
"defense" => new stdClass,
"utility" => new stdClass,
),
"trees" => array(
"offense" => $offense, // the trees
"defense" => $defense,
"utility" => $utility,
),
);
been staring at it for a while and just cant see where the error would be coming from
does anyone have any ideas?
From your question:
a simple mistake being overlooked that i only picked up on when
checking the console.log as suggested by JamWaffles
the gist of it is this; var mastery is picking up 2 divs (the parent
that i want and the direct child which i dont) - and as both are
parents to the json data.. thats where the duplication comes from
silly
thanks jamwaffles!

Categories