Laravel 5.7. Subtract model instances - php

I have 2 collections of models.
For example
$full = collect([
[
'name' => 'name1', //id was omitted intentionally
],
[
'name' => 'name2', //id was omitted intentionally
],
[
'name' => 'name3', //id was omitted intentionally
],
]);
$diff = collect([
[
'id' => 6,
'name' => 'name1',
],
]);
and I want to receive such a result after something like this
$full->diff($full);
$result = [
[
'name' => 'name2',
],
[
'name' => 'name3',
],
];
How to achieve that without filter() or reject() with contains() in a neater way?

It's hard to say why you don't want to use filter or reject with contains but there is another solution:
$result = $full->pluck('name')->diff($diff->pluck('name'))->map(function($name) {
return [
'name' => $name
];
});
dd($result->toArray());
As result you will get:
array:2 [▼
1 => array:1 [▼
"name" => "name2"
]
2 => array:1 [▼
"name" => "name3"
]
]

The diff method should work as needed with a new collection containing just the name property:
$comparableDiff = $diff->pluck('name');
$result = $full->diff($comparableDiff);

I haven't found a neater approach than
$profiles->reject(function ($profile) use ($existingProfiles) {
return $existingProfiles->pluck('name')->contains($profile->name);
})->values()
But thanks to everyone. I've upvoted your questions ;)

Related

Validate that JSON array has one associative array with fixed integer value

I am trying to validate some JSON using Opis's package. I am trying to validate that an array has at least one associative array with an id of value 1. Here is the code I've got:
$json = [
[
'id' => 1,
],
[
'id' => 2,
],
[
'id' => 3
]
];
$rules = [
'type' => 'array',
'contains' => [
'type' => 'array',
'properties' => [
'id' => [
'type' => 'integer',
'const' => 1,
],
],
'required' => ['id']
],
'minContains' => 1,
];
$validated = Common::validateJSON($json, json_encode($rules));
and here is the validateJSON method code:
public static function validateJSON($json, $rules)
{
$validator = new Validator();
// Validate
$result = $validator->validate($json, $rules);
if ($result->isValid()) {
return true;
}
$errorMessages = [];
if ($result->hasError()) {
$formatter = new ErrorFormatter();
$errorMessages[] = $formatter->format($result->error());
}
return $errorMessages;
}
so, in this case $validated returns:
array:1 [
0 => array:1 [
"/" => array:1 [
0 => "At least 1 array items must match schema"
]
]
]
changing $rules to this:
$rules = [
'type' => 'array',
'contains' => [
'type' => 'array',
],
'minContains' => 1,
];
returns the same result which is weird for me.
Changing const to any number doesn't change what is returned. So, my guess is that I am doing something wrong but I don't know what.
I've been googling various things nothing helped. I've been looking at the JSON schema site, particularly here but I haven't figured it out.
Before validating, as I am not json decoding the data as it is not coming from an http request, do this:
$json = json_encode($json);
$json = json_decode($json); // this, I think, will turn associative arrays into objects which makes it work
and the second type must be object.

Elasticsearch [Query Bool Must Match] performs OR operation instead of AND

I'm trying to perform a basic login operation where my view (front end part) accepts a username and password through a form
So in SQL, I must have an example query:
SELECT * FROM users WHERE username = $_POST['username'] AND password = $_POST['password'];
According to the official documentation of Elasticsearch PHP API, it must go like this:
$params = [
'index' => 'myIndex',
'type' => 'myType',
'body' => [
'query' => [
"bool" => [
"must" => [
"match" => [
"username" => 'email#email.com',
],
"match" => [
"password" => 'mypassword',
],
]
]
]
]
];
Unfortunately, it is displaying A LOT of documents so I presumed it's performing the OR operator instead of matching them together
FYI, if you would ever wonder why would there be so many documents displayed according to the "hits" property above, there are literally many user documents with the same password
Main Question
Is there any proper ES query to properly match my username AND password so I could only retrieve one document? I've been searching through with the official documentation, but nothing succeeds the desired output
Thank you very much!
You're almost there. You need to enclose your match queries in one more array, otherwise your bool/must becomes an associative array and that's not what you want (i.e. the second match filter gets discarded).
$params = [
'index' => 'myIndex',
'type' => 'myType',
'body' => [
'query' => [
"bool" => [
"must" => [
--> [
"match" => [
"username" => 'email#email.com',
]
--> ],
--> [
"match" => [
"password" => 'mypassword',
]
]
--> ]
]
]
]
];
With the help of sir Val, I was able to formulate a technique to try work-arounds for my query, and was able to display the result with the following:
$params = [
'index' => $index,
'type' => 'index',
'size' => 250,
'body' => [
'query' => [
'bool' => [
'must' => [
[
"match" => [
"usr_username" =>
[
"query" => $username,
"operator" => "and"
]
]
],
[
"match" => [
"usr_password" => [
"query" => $password,
"operator" => "and"
]
]
]
]
]
]
]
];

Multiple array validation in laravel

I have 3 types of data to validate
data in group
single data
single and data in group combined
This validation works for single data
$validator = Validator::make($request->all(), [
'tests.*.finding' => 'required',//works for single test
]);
Data sample for above
["tests"=>
[
0 => ["finding"=>""]
],
[
1 => ["finding"=>""]
]
]
And this validation works for data in group
$validator = Validator::make($request->all(), [
'tests.*.*.finding' => 'required',//works for group
]);
Data sample for above
["tests"=>
[
"A" =>[
[
0 => ["finding"=>""]
],
[
1 => ["finding"=>""]
]
],
"B" =>[
[
0 => ["finding"=>""]
],
[
1 => ["finding"=>""]
]
]
]
]
How to validate for single and data in group combined
Combined Data sample
["tests"=>
[
"A" =>[
[
0 => ["finding"=>""]
],
[
1 => ["finding"=>""]
]
]
],
[
0 => ["finding"=>""]
],
[
1 => ["finding"=>""]
]
]
Please help me to fix this, as 1st scenario always gives error for scenario second and vice versa.
This is the solution,Laravel provided sometimes rule to manage existence of element and then only proceed to check next rule.
So final validation rule is.
$validator = Validator::make($request->all(), [
'tests.*.*.finding' => 'sometimes|required',//works for group
'tests.*.finding' => 'sometimes|required',//works for single test
]);
Doc for this : https://laravel.com/docs/5.4/validation#conditionally-adding-rules
You can following code to fix the validation data.
$customFieldValidation = ["test_id" => 'required|numeric',
"test_two.id" => 'required|numeric',
]);
$this->setRules ( $customFieldValidation );
$customeAttributes = [
"test_three.*.id" => 'Test Three ' // custom message
];
$this->setCustomAttributes ($customeAttributes);
$this->_validate ();
I hope it's helpful to you.

Project Mongo collection array value under a condition

i am using the Mongo php library in a service that is responsible of storing and retrieving some social data, specifically from Facebook, and just a snippet, it goes something like this, a collection of posts insights:
{
"_id":"5865aa8e9bbbe400010f97a2",
"insights":[
{
"name":"post_story_adds_unique",
"period":"lifetime",
"values":[
{
"value":10
}
]
},
{
"name":"post_story_adds",
"period":"lifetime",
"values":[
{
"value":11
}
]
},
{
"name":"post_story_adds_by_action_type_unique",
"period":"lifetime",
"values":[
{
"value":{
"like":10,
"comment":1
}
}
]
},
{
"name":"post_story_adds_by_action_type",
"period":"lifetime",
"values":[
{
"value":{
"like":10,
"comment":1
}
}
]
},
{
"name":"post_impressions_unique",
"period":"lifetime",
"values":[
{
"value":756
}
]
}
]
},
{
"_id":"586d939b9bbbe400010f9f12",
"insights":[
{
"name":"post_story_adds_unique",
"period":"lifetime",
"values":[
{
"value":76
}
]
},
{
"name":"post_story_adds",
"period":"lifetime",
"values":[
{
"value":85
}
]
},
{
"name":"post_story_adds_by_action_type_unique",
"period":"lifetime",
"values":[
{
"value":{
"like":73,
"comment":8,
"share":2
}
}
]
},
{
"name":"post_story_adds_by_action_type",
"period":"lifetime",
"values":[
{
"value":{
"like":74,
"comment":9,
"share":2
}
}
]
},
{
"name":"post_impressions_unique",
"period":"lifetime",
"values":[
{
"value":9162
}
]
}
]
}
We can note that all the posts' sub-documents, i.e. metrics, are present in each post, surely, with their corresponding values.
I believe that the data structure presented above is not optimal to work with; if faced with the situation to project a couple of these sub-documents values, say post_story_adds_by_action_type's and post_impressions_unique's value property, we need to run a condition on all the sub-documents in order to match the name property to post_story_adds_by_action_type or post_impressions_unique.
I tried $elemMatch(projection), but as the documentation says, it only returns the first matching array element. I was able to do so as such:
$this->db->$collection->find([],
[
'projection' => [
'insights' => [
'$elemMatch' => [
'name' => 'post_impressions_unique'
]
],
'_id' => 0,
'insights.values.value' => 1,
],
]);
A MongoDB\Driver\Cursor object is returned with only the desired value, by setting this 'insights.values.value' => 1, of the sub-documents that have name equal to post_impressions_unique.
The first thing i thought about was, of course, to use the $or logical operator:
$this->db->$collection->find([],
[
'projection' => [
'insights' => [
'$elemMatch' => [
'$or' => [
[
'name' => [
'$eq' => 'post_story_adds_by_action_type',
]
],
[
'name' => [
'$eq' => 'post_impressions_unique',
]
]
]
]
]
],
'_id' => 0,
'insights.name' => 1,
'insights.values.value.like' => 1,
'insights.values.value' => 1,
]);
Note the projections 'insights.values.value.share' => 1 'insights.values.value' => 1 corresponding to the different sub-documents value position.
Of course this didn't work, i got an array of post_impressions_unique sub-documents alone; so i had to try the aggregation framework https://docs.mongodb.com/manual/reference/operator/aggregation/filter/ :
$this->db->$name->aggregate(
[
[
'$project' => [
'insights' => [
'$filter' => [
'input' => '$insights',
'as' => 'name',
'cond' => [
'$or' => [
[
'$eq' => [
'name', '$post_story_adds_by_action_type',
]
],
[
'$eq' => [
'name', '$post_impressions_unique',
]
],
],
],
],
],
'_id' => 0,
'values.value.like' => 1,
'values.value' => 1,
],
]
]);
This didn't work either, in this case i got an array of empty insights objects.
I considered using the laravel-mongodb package and take advantage of the Eloquent builder.
DB::collection($collection)->project(['insights' => ['$elemMatch' => ['name' => 'post_story_adds_unique']]])->get();
or
DB::collection($collection)->project(['insights' => [
'$filter' => [
'input' => '$insights',
'as' => 'name',
'cond' => [
'name' => 'post_story_adds_unique',
]
]
],
'values.value.like' => 1
])->get();
But still i couldn't get the value within the sub-document. I checked the Builder::project() function and it seems that it internally use the aggregation framework as well, but i wasn't able to figure out the appropriate syntax to do so, if any.
My questions are as follow:
1- How can i retrieve specific sub-documents' insights.values.value and name properties, where name matches post_impressions_unique? An vice versa, how to retrieve the sub-documents' insignts.values.value.share and name properties when the latter matches post_story_adds_by_action_type?
2- What is the correct syntax to use $filter within an $project(aggregation)?
This is basically most of the research i have been doing, and it feels as if i am running in circles.
Appreciate your help.
Thank you.
You can try something like this.
Use $$ notation to access the variable defined in iteration in $filter and $map.
$map is used to trim the response to display values and name from insights array.
[
[
'$project' => [
'insights' => [
'$map' => [
'input' => [
'$filter' => [
'input' => '$insights',
'as' => 'insightf',
'cond' => [
'$or' => [
[
'$eq' => [
'$$insightf.name', 'post_story_adds_by_action_type',
]
],
[
'$eq' => [
'$$insightf.name', 'post_impressions_unique',
]
],
],
],
],
],
'as' => 'insightm',
'in' => [
'values' => '$$insightm.values.value',
'name' => '$$insightm.name',
],
],
],
'_id' => 0
],
]
];

Elasticsearch for laravel - Get documents matching one or more categories?

The following code works for me and returns the blog posts matching the query
$params = [
'index' => 'blog',
'type' => 'post',
'body' => [
'query' => [
'multi_match' => [
'query' => $request->get('query'),
'fields' => ['title', 'description']
]
]
]
];
$response = \Elasticsearch::search($params); // works perfect
How can I change the code above so I can get all documents which their category matches one of the $categories values
The $categories variable is a variable that I'm getting from check boxes,
I tried with the code below but for some reason it return empty result :
// $categories = $request->get('categories');
$categories = ["News", "Technology"];
$params = [
'index' => 'blog',
'type' => 'post',
'body' => [
"query"=> [
"filtered"=> [
"query"=> [
"match_all"=> []
],
"query"=>[
"terms"=> [
"category"=> $categories
]
]
]
]
]
];
$response = \Elasticsearch::search($params); // return 0 hits
Any idea on how I can get the documents based on one or more categories instead of getting them based on a matching words ?
You are having two query clauses at the same level inside filtered clause which is not allowed.
"query"=> [
"filtered"=> [
"query"=> [ <-- (1)
"match_all"=> []
],
"query"=>[ <-- (2)
"terms"=> [
"category"=> $categories
]
]
]
If you are trying to query only the categories, you should remove the first clause. You can write your query as
"query"=> [
"filtered"=> [
"query"=>[
"terms"=> [
"category"=> $categories
]
]
]
]

Categories