PHP / Laravel - Foreach store into database (\Grammar::parameterize() error) - php

I have an array, which looks like this:
array:3 [▼
"field" => array:2 [▼
0 => "fromName"
1 => "from"
]
"operator" => array:2 [▼
0 => "="
1 => "="
]
"value" => array:2 [▼
0 => "Oliver"
1 => "oliver#mywebsite.com"
]
]
I am trying to save the above array, into my database table called email_rules:
Below is my code.
StreamEmailRulesController.php:
public function store(Stream $stream)
{
//Validate the request.
//Validate the request.
$attributes = request()->validate([
'field' => 'required|array|min:1',
'field.*' => [
'required', 'string',
Rule::in(['fromName', 'from']),
],
'operator' => 'required|array|min:1',
'operator.*' => [
'required', 'string',
Rule::in(['=', '!=', 'matches']),
],
'value' => 'required|array|min:1',
'value.*' => 'required|string|min:3|max:255',
]);
//Add the document to the database.
$stream->addRules($attributes);
//Return back.
return redirect()->back();
}
Now the $stream->addRules() function is responsible for saving the data to the database.
Stream.php:
/**
* A stream can have many rules.
*/
public function rules()
{
return $this->hasMany(EmailRule::class);
}
/**
* Add Email Rules(s) to the stream
*
* #return Illuminate\Database\Eloquent\Model
*/
public function addRules(array $attributes)
{
return $this->rules()->create($attributes);
}
Now, above does not work. I get below error:
Argument 1 passed to Illuminate\Database\Grammar::parameterize() must be of the type array, int given,
What am I doing wrong?

If you dump $attributes you may be getting an int (bool) as a pass or fail or even json, depending on what's going in, from the validation. This might just be a matter of changing syntax from
$attributes = request()->validate([...
to
$attributes= $this->validate(request(), [...
I believe your issue is that you're trying to save an array as a singular value. IE those attributes need to be iterated over to create a new set of rules for each one, instead. Normally, I'd expect to see the array ready to create individual objects. In this case, though it looks like it is structured to create individual fields (field, operator, value), so looping through those may not do what you wish either -- it provides multiple fields to the create construct, rather than a full set of object params for a new rule(). I think Laravel is hinting that you may wish to change your request/return structure to match the model format.

I think it could be the array structure. Can you modify the array to?:
[
[
"field" => "fromName",
"operator" => "=",
"value" => "Oliver"
],
[
"field" => "from",
"operator" => "=",
"value" => "oliver#mywebsite.com"
],
]
EDIT:
In the Controller add a loop like this:
...
foreach ($attributes as $key => $value) {
foreach ($value as $k => $v) {
$data [$k][$key] = $v;
}
}
//Add the document to the database.
$stream->addRules($data);

The problem was that Laravels create or createMany expect an array with key => pair values, where the key corresponds to the database columns.
This article from Adam Wathan helped me out a lot.
This is what I ended up doing:
$requestData = collect(request()->only('field', 'operator', 'value'));
$rules = $requestData->transpose()->map(function ($ruleData) {
return new EmailRule([
'field' => $ruleData[0],
'operator' => $ruleData[1],
'value' => $ruleData[2],
]);
})->toArray();
//Add the rules to the database.
$stream->addRules($rules);

Related

Laravel updateOrCreate with hasMany relationship

I have users that can have multiple parameters. These are called for example user_param_1, user_param_2, ..., user_param_n. This is dynamic. It is a separate table user_parameters, which stores id, user_id, name and value. The relationship is a belongsTo and hasMany between Users and UserParameters. The problem is:
When editing, I want to keep it dynamically and if the user has an user_param_n+1, it should be created. But I already have problems to write the condition for the existing parameters.
I create myself an userParameters array, which contains from the $request variable only the necessary parameters. The array looks like this:
[
0 => [
"name" => "par1"
"value" => "var1"
]
1 => [
"name" => "par2"
"value" => "var2"
]
2 => [
"name" => "par3"
"value" => "var3"
]
]
Then I want to save it. My controller knows the user, so I can access to $user->id.
foreach ($userParameters as $userParameter) {
$user->parameters()->updateOrCreate(['id' => $user->parameters->id, 'user_id' => $user->id], $userParameter);
}
The issue is, that $user->parameters is an array of eloquent models. The condition is wrong. I can't access id directly. But how I can solve it? I need something like "['id' => [IF-DATABASE-ID-EXISTS-IN-ARRAY-$user-parameters]"... but how in an eloquent way?
Thanks in advance!
Best regards
I think you need to get the existing parameter using user_id and parameter_name cuz it's the uniqueness of that parameter row, if there is no parameter with this name it will create it with user_id & parameter_name and parameter_value passed to updateOrCreate function
foreach ($parameters_from_request as $parameter) {
$user->parameters()
->updateOrCreate(
[
'name' => $parameter['name'] ,
'user_id' => $user->id
],[
'name' => $parameter['name'],
'value'=> $parameter['value']
]);
}

How to validate an array of datetimes in Laravel?

I have a form that submits an array named prazos, and I want to make sure each item is a valid datetime or null. Following the answers to this question and the Laravel docs, I have this in my Controller:
use Illuminate\Support\Facades\Validator;
// ...
$rules = array([
'prazos' => 'required|array',
'prazos.*' => 'nullable|date'
]);
$validator = Validator::make($request->all(), $rules);
$data = $validator->valid()['prazos'];
foreach($data as $id => $prazo) {
// use $data to update my database
// ...
}
The issue is, the validator is not actually stopping invalid content. If I try to submit "loldasxyz" or other gibberish, I get an error from the database. What am I doing wrong?
Note: previously I had been using validators with the syntax $data = $request->validate($rules), but for some reason it didn't work for the array-type data ($data came back empty). I'm not sure if there is some difference in how those different methods work.
Edit: this is what the parameter bag in $request looks like when I test it (the indices are ids, which is why they start at 1):
#parameters: array:3 [▼
"_token" => "Rf6mAp4lqhpZzQRxaxYsees1M0NfrFKpbGe4Hy28"
"_method" => "PUT"
"prazos" => array:5 [▼
1 => "2021-03-22 21:21"
2 => "2021-03-03 11:27"
3 => "jhbkjhg"
4 => null
5 => "2021-03-02 14:21"
]
]
And this is what the validated $data comes out as:
array:5 [▼
1 => "2021-03-22 21:21"
2 => "2021-03-03 11:27"
3 => "jhbkjhg"
4 => null
5 => "2021-03-02 14:21"
]
I wish it would tell me the third value is invalid.
Heres what youre looking for:
https://laravel.com/docs/8.x/validation#rule-date-equals
The field under validation must be equal to the given date. The dates will be passed into the PHP strtotime function in order to be converted into a valid DateTime instance.

Laravel validator with a wildcard

i want to make a laravel validator that validates the the fields inside an un-named array ( 0,1,2,3 ) that is inside an array
so my array is like
array [ //the form data
"items" => array:2 [ //the main array i want to validate
0 => array:2 [ // the inner array that i want to validate its data
"id" => "1"
"quantity" => "1000"
]
1 => array:2 [
"id" => "1"
"quantity" => "1000"
]
// other fields of the form,
]
]
so what i want is something like
$validator = Validator::make($request->all(), [
'items.*.id' => 'required' //notice the star *
]);
Laravel 5.2
The syntax in the question is now supported
http://laravel.com/docs/master/validation#validating-arrays
Laravel 5.1
First create the validator with all of your other rules. Use the array rule for items
$validator = Validator::make($request->all(), [
'items' => 'array',
// your other rules here
]);
Then use the Validator each method to apply a set of rules to every item in the items array.
$validator->each('items', [
'id' => 'required',
'quantity' => 'min:0',
]);
This will automatically set these rules for you...
items.*.id => required
items.*.quantity => min:0
https://github.com/laravel/framework/blob/5.1/src/Illuminate/Validation/Validator.php#L261
You could simply do something like that:
$rules = [];
for($i = 0; $i < 10; $i++) {
$rules["items.$i.id"] = "required";
}
$validator = \Validator::make($request->all(), $rules);

Using elasticsearch, how to create an index for a document that contains an array, and append to that array in the future

In my example code I am using the php client library, but it should be understood by anyone familiar with elasticsearch.
I'm using elasticsearch to create an index where each document contains an array of nGram indexed authors. Initially, the document will have a single author, but as time progresses, more authors will be appended to the array. Ideally, a search could be executed by an author's name, and if any of the authors in the array get matched, the document will be found.
I have been trying to use the documentation here for appending to the array and here for using the array type - but I have not had success getting this working.
First, I want to create an index for documents, with a title, array of authors, and an array of comments.
$client = new Client();
$params = [
'index' => 'document',
'body' => [
'settings' => [
// Simple settings for now, single shard
'number_of_shards' => 1,
'number_of_replicas' => 0,
'analysis' => [
'filter' => [
'shingle' => [
'type' => 'shingle'
]
],
'analyzer' => [
'my_ngram_analyzer' => [
'tokenizer' => 'my_ngram_tokenizer',
'filter' => 'lowercase',
]
],
// Allow searching for partial names with nGram
'tokenizer' => [
'my_ngram_tokenizer' => [
'type' => 'nGram',
'min_gram' => 1,
'max_gram' => 15,
'token_chars' => ['letter', 'digit']
]
]
]
],
'mappings' => [
'_default_' => [
'properties' => [
'document_id' => [
'type' => 'string',
'index' => 'not_analyzed',
],
// The name, email, or other info related to the person
'title' => [
'type' => 'string',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
'authors' => [
'type' => 'list',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
'comments' => [
'type' => 'list',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
]
],
]
]
];
// Create index `person` with ngram indexing
$client->indices()->create($params);
Off the get go, I can't even create the index due to this error:
{"error":"MapperParsingException[mapping [_default_]]; nested: MapperParsingException[No handler for type [list] declared on field [authors]]; ","status":400}
HAD this gone successfully though, I would plan to create an index, starting with empty arrays for authors and title, something like this:
$client = new Client();
$params = array();
$params['body'] = array('document_id' => 'id_here', 'title' => 'my_title', 'authors' => [], 'comments' => []);
$params['index'] = 'document';
$params['type'] = 'example_type';
$params['id'] = 'id_here';
$ret = $client->index($params);
return $ret;
This seems like it should work if I had the desired index to add this structure of information to, but what concerns me would be appending something to the array using update. For example,
$client = new Client();
$params = array();
//$params['body'] = array('person_id' => $person_id, 'emails' => [$email]);
$params['index'] = 'document';
$params['type'] = 'example_type';
$params['id'] = 'id_here';
$params['script'] = 'NO IDEA WHAT THIS SCRIPT SHOULD BE TO APPEND TO THE ARRAY';
$ret = $client->update($params);
return $ret;
}
I am not sure how I would go about actually appending a thing to the array and making sure it's indexed.
Finally, another thing that confuses me is how I could search based on any author in the array. Ideally I could do something like this:
But I'm not 100% whether it will work. Maybe there is something fundemental about elasticsearch that I am not understanding. I am completely new to so any resources that will get me to a point where these little details don't hang me up would be appreciated.
Also, any direct advice on how to use elasticsearch to solve these problems would be appreciated.
Sorry for the big wall of text, to recap, I am looking for advice on how to
Create an index that supports nGram analysis on all elements of an array
Updating that index to append to the array
Searching for the now-updated index.
Thanks for any help
EDIT: thanks to #astax, I am now able to create the index and append to the value as a string. HOWEVER, there are two problems with this:
the array is stored as a string value, so a script like
$params['script'] = 'ctx._source.authors += [\'hello\']';
actually appends a STRING with [] rather than an array containing a value.
the value inputted does not appear to be ngram analyzed, so a search like this:
$client = new Client();
$searchParams['index'] = 'document';
$searchParams['type'] = 'example_type';
$searchParams['body']['query']['match']['_all'] = 'hello';
$queryResponse = $client->search($searchParams);
print_r($queryResponse); // SUCCESS
will find the new value but a search like this:
$client = new Client();
$searchParams['index'] = 'document';
$searchParams['type'] = 'example_type';
$searchParams['body']['query']['match']['_all'] = 'hel';
$queryResponse = $client->search($searchParams);
print_r($queryResponse); // NO RESULTS
does not
There is no type "list" in elasticsearch. But you can use "string" field type and store array of values.
....
'comments' => [
'type' => 'string',
'analyzer' => 'my_ngram_analyzer',
'term_vector' => 'yes',
'copy_to' => 'combined'
],
....
And index a document this way:
....
$params['body'] = array(
'document_id' => 'id_here',
'title' => 'my_title',
'authors' => [],
'comments' => ['comment1', 'comment2']);
....
As for the script for apending an element to array, this answer may help you - Elasticsearch upserting and appending to array
However, do you really need to update the document? It might be easier to just reindex it as this is exactly what Elasticsearch does internally. It reads the "_source" property, does the required modification and reindexes it. BTW, this means that "_source" must be enabled and all properties of the document should be included into it.
You also may consider storing comments and authors (as I understand these are authors of comments, not the document authors) as child document in ES and using "has_child" filter.
I can't really give you specific solution, but strongly recommend installing Marvel plugin for ElasticSearch and use its "sense" tool to check how your overall process works step by step.
So check if your tokenizer is properly configured by running tests as described at http://www.elastic.co/guide/en/elasticsearch/reference/1.4/indices-analyze.html.
Then check if your update script is doing what you expect by retrieving the document by running GET /document/example_type/some_existing_id
The authors and comments should be arrays, but not strings.
Finally perform the search:
GET /document/_search
{
'query' : {
'match': { '_all': 'hel' }
}
}
If you're building the query yourself rather than getting it from the user, you may use query_string with placeholders:
GET /document/_search
{
'query' : {
'query_string': {
'fields': '_all',
'query': 'hel*'
}
}
}

Laravel: extra field sync with array

Im trying to save data inside a pivot table with an extra field called data.
when i save i have this array:
[
5 => "files"
4 => "pictures"
3 => "tags"
1 => "thumbs"
]
My table looks like this:
project_id
option_id
name
The ids shown above refer to option_id and the string to name inside the database.
When i try to use sync like this: $project->options()->sync($data);
$data is the array shown above
Im getting a error thats its trying to save the option_id with "files".
Here is how i build up the data that i use for sync:
Im trying to get what you suggested but dont know how to achieve it:
here is how i build up the array:
foreach($request->input('option_id') as $id) {
$option['option_id'][] = $id;
$option['data'][] = $request->input('data')[$id];
}
$data = array_combine($option['option_id'], $option['data']);
This is covered in the manual:
Adding Pivot Data When Syncing
You may also associate other pivot table values with the given IDs:
$user->roles()->sync(array(1 => array('expires' => true)));
In your example, you would have to change your array to look something like below but I believe this would translate to:
$data = [
5 => [ 'name' => "files" ],
4 => [ 'name' => "pictures" ],
3 => [ 'name' => "tags" ],
1 => [ 'name' => "thumbs" ],
];
$project->options()->sync($data);
I believe you may also need to modify how your Project model relates itself to your Options model:
// File: app/model/Project.php
public function options()
{
return $this->belongsToMany('Option')->withPivot('name');
}
This is also noted in the linked-to manual page:
By default, only the keys will be present on the pivot object. If your pivot table contains extra attributes, you must specify them when defining the relationship.
Update
Try creating your $data array like this:
$data = [];
foreach($request->input('option_id') as $id) {
$data[$id] = [ 'name' => $request->input('data')[$id] ];
}

Categories