OctoberCMS Should this throw an error? - laravel

If I (incorrectly) define a belongsToMany as follows (Note the ] after the table key):
public $belongsToMany = [
'phonetypes' => [
'NigglesLogic\Suitespots\Models\Phonetype',
'table' => 'niggleslogic_suitespots_contact_phonetypes'],
'pivot' => ['phone_number']
];
I can view the page and no error is thrown. However the pivot data does not show.
If I change the above to this (with the ] after the pivot key, where I intended it to be):
public $belongsToMany = [
'phonetypes' => [
'NigglesLogic\Suitespots\Models\Phonetype',
'table' => 'niggleslogic_suitespots_contact_phonetypes',
'pivot' => ['phone_number']
]
];
The pivot data is shown when I refresh the page.
I realize that I made a typing error but I should OctoberCMS at least warn me about this situation?

Hmm, code structure of October CMS mostly check configuration and if its not correctly formatted then it ignore that thing.
Why ? : Because October CMS is based on Laravel and all the relational configuration is passed to the Laravel then it will initialize relations.
so it's really hard to predict what kind of mistake user will made, it's rather easy to just use key which needed for configuration and ignore rest.
So in your case: (I have rewritten your code in proper structure)
public $belongsToMany = [
'phonetypes' => [
'NigglesLogic\Suitespots\Models\Phonetype',
'table' => 'niggleslogic_suitespots_contact_phonetypes'
],
'pivot' => ['phone_number']
];
You can see that after restructuring it looks like we have defined 2 relations, one is phonetypes and other pivot, so when we try to fetch one of them
in first case [phonetypes] : it can not find proper relational data from table, but table exists so it returns null not an error
in second case [pivot] : we are not using it as we dont consider it as relation, still if we try to use it, it will not find table phone_number so it may throw an error.
for detecting error we need something like more predictable behavior.
[ Yes October CMS could show you a warning but to show that warning they need to write more code and it seems code size get double if they check everything :( ]
(we can not show warning for this) for ex if we define route:
Route::get('/som-item', function() { ...code... })
see, I mistakenly write /som-item but but, I need to use /some-item url so laravel can not able to give me warning about this as its correct route for it, it will show me that /some-item not defined error BUT it will not tell me that dude you did spelling mistake kind of WARNING :).
[ we don't see warning until we get some AI in code :) ]

Related

Laravel Eloquent updateOrCreate doesn't work properly

I once wrote probably same question last time and I'm back..
Laravel Eloquent firstOrCreate doesn't work properly
On the last question, I found that fillable property filters update field manifest. So, if you want to update a table based on fieldA and fieldB, then your code might be..
$modelOrRelation->updateOrCreate(
['fieldA' => 'a', 'fieldB' => 'b'], ['otherfields' => 'update value']
);
and you MUST specify those fields on fillable property. $fillable = ['fieldA', 'fieldB', ...]
This is what I know about firstOrCreate and updateOrCreate.
At this time, following code generate many same rows. It looks like, the first parameter ['candle_date_time_kst'] do nothing..
// candleRelation is hasMany relation..
$candleRelation = $market->candles($period);
$created = $created->add($candleRelation->updateOrCreate(
[
'candle_date_time_kst' => $time,
],
$item
));
This creates many same candle_date_time_kst value rows. At this time, fillable property already filled target fields.
What else do I miss?
Is updateOrCreate should not trust? I didn't think so.. There are something I miss... any insight?
#220114 update
So, I do my homework..
Using DB::getQueryLog(), I get this query..
It looks like, updateOrCreate() remembers the last update value. Then if I reuse same eloquent relation object for another updateOrCreate(), method use last update parameter again. It makes and clause, so return record is none..
So, I use newQuery() method for initialize query bindings.
$created->add($candleRelation->newQuery()->updateOrCreate(
[
'candle_date_time_kst' => $time
],
$item
));
#220114
Unfortunately, retest reveals newQuery() actually not helping..
I tried $relation->newModelInstance() and getting same bindings.
What I trying to do is getting same parent binding without anything else. .. anyone knows?
Based on binding, when I get relation model I can get clean binding also. So I just do below..
$created->add($market->candles($period)->updateOrCreate(
[
'candle_date_time_kst' => $item['candle_date_time_kst']
],
$item
));
Only change is $candleRelation to $market->candles($period).
On each attempt, new relation instance produce so binding problem won't even exists.
.... I'm mad.
you need to supply an array in the format
[ column => value, ... ] not [ value ]
I had a similar problem a time ago. And the UpdateOrInsert method solved it.
Unfortunately, this method is Query Builder, not eloquent. But to achieve this result that was the only really working solution to me.
The issue for only happened when I tried to use more than 1 column on where clause, like in your example.

createMany equivalent for updateOrCreate

Is there a createMany equivalent for updateOrCreate in Laravel for when you want to "updateOrCreate" many records in one call?
In other words, is there a sort of updateOrCreateMany function?
From the documentation there doesn't appear to be a function with that name, but maybe there's another friendly Laravel way of doing this... or should I just use a foreach (maybe also using MySQL's insert on duplicate feature)?
Based upon the documentation of Laravel, no there is no method on eloquent like updateOrCreateMany
If you give it a thought, it makes sense because the execution will anyway need to be sequential. As its possible that some entry might affect the previous one so the insertion or updation mode cannot be concurrent. So even if there was some method like that, it would anyway use a loop internally to execute query one by one.
To clear further let's say there was a method updateOrCreateMany which would probably take input like array of arrays like so:
Model::updateOrCreateMany([
[
'email' => 'x#y.com'
],
[
'email' => 'x2#y2.com'
],
[
'email' => 'x#y.com'
]
],
[
[
'name' => 'X Y'
],
[
'name' => 'X2 Y2'
],
[
'name' => 'X Y Updated'
]
]);
As you can see that record 3 is supposed to update the record 1 so basically, you cannot enter all at once by only comparing each with the database image of the insertion time of record1, that way you will enter in error of duplicated email as x#y.com did not exist initially.
Hence, there is no updateOrCreateMany method because it cannot be done concurrent, it needs to be sequential which means some kind of looping mechanism like foreach.
By the way Good Question! +1
Makes sense?
I suggest using a foreach loop on your data and using updateOrCreate method
foreach($records as $record){
Model::updateOrCreate([$record->id, $record->name], [$record->likes, $record->dislikes]);
}

What do "except" and "idColumn" refers in "unique:table,column,except,idColumn"? from Laravel docs

To validate the update of the e-mail of a user already registered I have the next function to exclude the "unique" rule for the current User:
public function updateRules() {
return [
'name' => 'required',
'email' => 'required|unique:users,email,'.$this->id,
];
}
In the Laravel docs https://laravel.com/docs/5.7/validation#rule-unique I found the syntax unique:table,column,except,idColumn with 4 parameters:
table: refers to the table name "users"
column: refers the column name "email"
except: I'm taking it as the id of the Model instance I want to exclude from the "unique" verification
idColumn: I have no idea about this
Someone could clarify what do except and idColumn refers to?
Note for those obsessed with duplicate questions: I'm not asking how to do the rule exclusion for the update, because it seems to work just fine, I've read those questions and answers. I'm making helper functions and I need to know exactly what those two parameters means.
So you are right about the except parameter, it is the id that you want to be excluded from the check.
And idColumn is optional in case your column is not called id but user_id for example. So you will use:
'required|unique:users,email,'.$this->id . ',user_id';

unique between 2 columns

i need to create a validation to my table "candidate_knowledges", basically in this table it accepts to columns (candidate_id, software_id), i cannot let create user_id and software more then one. but i think my validation is wrong or im not doing it right.
What im trying to say in validation is that can only exist one software_id and one candidate_id on the table, this way the candidate dont have duplicate entries.
Ex: 'software_id' => 'required|integer|unique:candidate_knowledges,candidate_id,'.$candidate->id,
Here is a way to allow only one software for each candidate:
$rules = [
'software_id' =>
'required',
'integer',
'min:1',
Rule::unique('candidate_knowledges')->where('candidate_id', $candidate->id),
],
];
In general I would suggest using fluent validation syntax (it's match easier to use and update), but if you can't (laravel < 5.3 or any other reasons):
'software_id' => 'required|integer|unique:candidate_knowledges,NULL,NULL,candidate_id,' . $candidate->id,
Hope it helps.

Laravel validation: unique with multiple columns and soft_delete

I am trying to do a Laravel validation rules as follow:
"permalink" => "required|unique:posts,permalink,hotel_id,deleted_at,NULL|alpha_dash|max:255",
The explanation to the rules is:
I have a table "Posts" in my system with the following fields (among others): hotel_id, permalink, deleted_at. If MySQL would allow make an unique index with null values, the sql would be:
ALTER TABLE `posts`
ADD UNIQUE `unique_index`(`hotel_id`, `permalink`, `deleted_at`);
So: I just add a new row IF: the combination of hotel_id, permalink and deleted_atfield (witch must be NULL) are unique.
If there is already a row where the permalink and hotel_id field are the same and 'deleted_at' field is NULL, the validation would return FALSE and the row wouldnt be inserted in the database.
Well. I don't know why, but the query Laravel is building looks like:
SELECT count(*) AS AGGREGATE FROM `posts`
WHERE `hotel_id` = the-permalink-value AND `NULL` <> deleted_at)
What the heck...
The query I was hoping Laravel build to validation is:
SELECT count(*) AS AGGREGATE FROM `posts`
WHERE `permalink` = 'the-permalink-value' AND `hotel_id` = ? AND `deleted_at` IS NULL
Could someone explain me how this effectively works? Because everywhere I look it looks like this:
$rules = array(
'field_to_validate' =>
'unique:table_name,field,anotherField,aFieldDifferentThanNull,NULL',
);
Does anyone could help me?
Thank you
all.
Finally, I got a proper understanding of the validation (at least, I think so), and I have a solution that, if it is not beautiful, it can helps someone.
My problem, as I said before, was validate if a certain column (permalink) is unique ONLY IF other columns values had some specific values. The problem is the way Laravel validation string rules works. Lets get to it:
First I wrote this:
"permalink" => "required|unique:posts,permalink,hotel_id,deleted_at,NULL|alpha_dash|max:255",
And it was generating bad queries. Now look at this:
'column_to_validate' => 'unique:table_name,column_to_validate,id_to_ignore,other_column,value,other_column_2,value_2,other_column_N,value_N',
So. The unique string has 3 parameters at first:
1) The table name of the validation
2) The name of the column to validate the unique value
3) The ID of the column you want to avoid (in case you are editing a row, not creating a new one).
After this point, all you have to do is put the other columns in sequence like "key,value" to use in your unique rule.
Oh, easy, an? Not so quickly, paw. If you're using a STATIC array, how the heck you will get your "currently" ID to avoid? Because $rules array in Laravel Model is a static array. So, I had to came up with this:
public static function getPermalinkValidationStr() {
$all = Input::all();
# If you are just building the frozenNode page, just a simple validation string to the permalink field:
if(!array_key_exists('hotel', $all)) {
return 'required|alpha_dash|max:255';
}
/* Now the game got real: are you saving a new record or editing a field?
If it is new, use 'NULL', otherwise, use the current id to edit a row.
*/
$hasId = isset($all['id']) ? $all['id'] : 'NULL';
# Also, check if the new record with the same permalink belongs to the same hotel and the 'deleted_at' field is NULL:
$result = 'required|alpha_dash|max:255|unique:posts,permalink,' . $hasId . ',id,hotel_id,' . $all['hotel'] . ',deleted_at,NULL';
return $result;
}
And, in the FrozenNode rules configuration:
'rules' => array(
'hotel_id' => 'required',
'permalink' => Post::getPermalinkValidationStr()
),
Well. I dont know if there is a easiest way of doing this (or a much better approach). If you know something wrong on this solution, please, make a comment, I will be glad to hear a better solution. I already tried Ardent and Observer but I had some problems with FrozenNode Administrator.
Thank you.

Resources