Laravel You requested 1 items, but there are only 0 items available - php

I am getting row's column randomly to seed database, using eloquent :
$physician = SelectOption::where('select_option_group_id', 1)->pluck('name')->random();
it works if data exists in select_options table. But if it does not exists, it gives an error :
You requested 1 items, but there are only 0 items available.
I want to leave it empty, if it's empty.

Check if collection is not empty prior doing random():
$collection = SelectOption::where('select_option_group_id', 1)->pluck('name');
if (!$collection->isEmpty()) {
$physician = $collection->random();
} else {
...
}

Use inRandomOrder() instead:
$physician = SelectOption::where('select_option_group_id', 1)->inRandomOrder()->first();
$name = is_null($physician) ? 'No data available' : $physician->name;

Check the order of your seeder or the date of your migration file. The migrations file is executing by date ex: 20220429_16_43_02_create_order_table.php. So, if you have 20220416_.... after 20220429_.... that throw the exception.
Solution: Change (rename) the date of the file.

Related

Laravel Eloquent: any way to update DB with dynamic fields?

I am trying to implement the "Edit Application Settings" feature. After a bit of thinking, my configuration values are stored in the DB with key -> value structure, like this:
id
key
value
1
logo_path
img/logo.png
As you can see, for each setting, there is only a key & value column. I made an App Service provider to cache them forever, and a helper function (config('setting_key')) to get the value, but now I'd like to update it in the most efficient way.
The user interface consists of the <form action="post" ...> and input with a corresponding name, like this: <input name="setting_key_name" ... />. As you can see, the name attribute here has the value of the key column value and the actual value of the input would be the value column value (a bit of confusion here).
First thing that came to my mind, was to make a foreach loop and find & update every row in DB, but IMHO it is very unoptimized way, cause if the page has a form with 10 values, it is 10 SQL queries. But till now, this is what I've done:
$keys = collect($request->except('_token'))->keys()->toArray();
// get all settings if the key name matches the request's input name
$setting = Setting::whereIn('key', $keys)->get();
$logo = self::GENERAL_APP_LOGO; // contant with a key-name (general_application_logo);
if($request->has(self::GENERAL_APP_LOGO) && $request->$logo) {
// Processing uploaded image here;
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name); // Using an upload trait
$setting->where('key', $logo)->value = self::LOGO_IMAGE_PATH . $name; // just a try to update the DB this way
}
foreach ($keys as $key) {
$setting->where('key', $key)->value = $request->$key; // putting all request's input values to corresponding key
}
$setting->save(); // saving the DB.
As you can see, this won't work and will throw an Exception, like Call to undefined method ...\Eloquent\Builder::save(). I tried the same code with an update, but the difficult part here is to update it multiple times (since the if section should have the update as well, for the logo), as well as binding the key to value.
So, a little bit of your help would be appreciated - what the logic should be here? How can I update a DB rows with corresponding column's value? I mean - like this (update where key = 'general_app_name' set value, 'some_setting_value'), but using the optimized and clear way?
Working solution
As #miken32 stated in his answer, I used hid version of code, but with slight changes:
// Changed the $request->settings->keys() to PHP native method array_keys():
$settings = Settings::whereIn('key', array_keys($request->settings))->get()->groupBy('id');
// Also, here I changed the `whereIn('id', ...)` to `whereIn('key', ...)`, since it was my primary index.
foreach ($request->settings as $k=>$v) {
if ($k === self::GENERAL_APP_LOGO_ID) {
// not sure about this one, but I think this is
// how you'd access a file input in an array
$image = $request->file('settings')[$k];
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name);
$v = self::LOGO_IMAGE_PATH . $name;
}
// take the Setting object out of the list we pulled
// Here I added the ->first() to get the first element from the retrieved collection;
$setting = $settings->get($k)->first();
$setting->value = $v;
$setting->save();
}
Since I was fetching the configuration values via helper, that only returns the value of the current key (and no id column), I changed the id to key and made the key as my PK in a model. Works like a charm!
With each setting in a separate row, there's no way to avoid multiple database queries – one to get the current values for all settings, and other to update each one. Looking up items by primary key is more efficient, so I'd recommend putting the contents of the id column in your blade view, like this:
<label for="setting_{{$setting->id}}">{{$setting->key}}</label>
<input name="settings[{{$setting->id}}]" id="setting_{{$setting->id}}" value="{{$setting->value}}"/>
Now in your controller, $request->settings will be an array you can loop through. You can continue treating your file upload separately, but now you've got the id column to look up, so change your constant to that.
$settings = Settings::whereIn('id', $request->settings->keys())->get()->groupBy('id');
foreach ($request->settings as $k=>$v) {
if ($k === self::GENERAL_APP_LOGO_ID) {
// not sure about this one, but I think this is
// how you'd access a file input in an array
$image = $request->file('settings')[$k];
$this->uploadLogo($image, self::LOGO_IMAGE_PATH, $name);
$v = self::LOGO_IMAGE_PATH . $name;
}
// take the Setting object out of the list we pulled
$setting = $settings->get($k);
$setting->value = $v;
$setting->save();
}
Note that Laravel does offer methods to bulk-update multiple models at once, but they are doing separate queries to the database in the background. IIRC, the save() method doesn't do anything if the value hasn't changed, which will spare you some hits.
You could try creating a text field, or a json field if your database supports it, and storing all of your settings as a JSON string in that field.
id
settings
1
{ "logo_path" : "img/logo.png", "foo" : "bar", "thing_count" : 17 }
2
{ "logo_path" : "img/logo2.png", "foo" : "baz", "thing_count" : 4 }
In your Laravel model, you can cast it as an array
protected $casts = ["settings" => "array"];
and then use it from the model
echo $theModel->settings['logo'];
echo $theModel->settings['foo'];
or you can cast it as a fully fledged object if you need to using value object casting.
One gotcha that can be confusing for people is the setting of the values in the array to update it. This will not work:
$theModel->settings['foo'] = "boz";
The reason is due to the way the Laravel mutators work. Instead, you make a value copy of the settings, change that, and reassign it to the model:
$settings = $theModel->settings;
$settings['foo'] = "boz";
$theModel->settings = $settings;
This approach has the capacity to infinitely expandable in the future as you just add new keys to your json. Be sure to do checks on the settings array to ensure fields you are looking for are set (which is why value objects can be very handy to do validation).
It also solves your database query problem - it's only ever one.
You don't need to put
$setting->where('key', $logo)->value = ...;
Just call
$setting->where('key', $logo)->update($request->toArray());
$setting->save(); called when you instantiated setting class like :
$setting = new Setting();
Or
$setting = Setting::whereIn('key', $keys)->get()->first();
Then
$setting->val = ...;
$setting->save(); // then it work's

How do I create a single-value field in Solr/Solarium?

I've done a vanilla install of Solr and Solarium for PHP
Solarium 5.x
PHP 7.1
Solr 8.5.1
I can create and query documents. But all fields I create are returned as arrays - except for "id". obviously there is some schema somewhere that specifies that id is a single-value field. how can I create my own single-value fields? All fields I create are multi-value arrays.
The multi-value field feature is useful but there are only a few cases where I will need it.
It seems I should be able to define the field types and specify whether they are multi-value or not but instead all fields I create are multi-value arrays and I can't appear to change that. the solarium documentation has a section on multi-value fields but not single-value fields.
https://solarium.readthedocs.io/en/stable/documents/#multivalue-fields
I don't see any documentation in solarium for defining the documents and their field types. possibly something is wrong with my installation.
here is my code example:
$client = new Solarium\Client($config);
// get an update query instance
$update = $client->createUpdate();
// create a new document for the data
$doc1 = $update->createDocument();
// add data to the document
$doc1->id = 123; // single value by default
$doc1->name = 'document name'; // always results in an array
$doc1->price = 5; // always results in an array
// add the documents and a commit command to the update query
$update->addDocuments(array($doc1));
$update->addCommit();
// this executes the query and returns the result
$result = $client->update($update);
// then query the documents
$query = $client->createSelect();
$query->setQuery('*:*');
$resultSet = $client->select($query);
echo 'NumFound: '.$resultSet->getNumFound().'<br>';
foreach ($resultSet as $document) {
foreach ($document as $field => $value) {
// I can test to see if $value is an array but my point is that I don't want
// to do so for every single field. how can I define fields that are single-value by default?
echo '<div'> . $field . ' : ' . $value . '</div>';
}
}
this outputs:
NumFound: 1
id : 123
name : Array
price : Array
yes I know how to get those values out of the array but I know there must be some way to get single-value fields by default.
Thanks in advance.
I have discovered that I can define multiValued="false", and many other detailed properties of a field, by manually editing managed-schema (schema.xml) in the conf directory for the core.
however it says at the top of this file:
So now the real question is, how do you edit the SOLR schema?

Laravel - can't find() attached object in many-to-many relationship

in my POST form users are able to add other users to a room.
I put a unique constraint on the link (no duplicate entry in the link between users and rooms).
However when I refresh my page (f5) after submitting the form, Laravel complains about duplicate entries, although I do check if the objects are attached before.
Here's the code:
$roomUsers = Room::find($request->room_id)->users();
if ($request->add != null) {
foreach ($request->add as $uId)
// if null, user hasnt been attach yet
if (!$roomUsers->find($uId)) {
Log::debug($roomUsers->find($uId) == null ? 'null' : 'not null');
// then we can attach him
$roomUsers->attach($uId);
}
}
The line !$roomUsers->find($uId) returns true yet the object has been attached in the previous iteration. How is that possible ? Thanks
The reason you're above code isn't working is because you're not creating a new instance of BelongsToMany for each check. This means that every time you call find you're not actually creating a new query you're just adding to the existing one e.g.
say you the ids to add are [1, 2, 3] by the last check your query would effectively be:
SELECT * FROM users WHERE id = 1 AND id = 2 AND id = 3
To keep with the above logic you could do:
$room = Room::find($request->room_id);
if ($request->add != null) {
foreach ($request->add as $uId)
// if null, user hasnt been attach yet
if (!$room->users()->find($uId)) {
// then we can attach him
$room->users()->attach($uId);
}
}
Or a much simpler way to go about this would be to syncWithoutDetaching.
Your code could then look something like:
$roomUsers = Room::find($request->room_id);
if ($request->has('add')) {
$roomUsers->users()->syncWithoutDetaching($request->add);
}
Hope this helps!

Laravel 5 eloquent model won't let me update

I do have the invitations table set up and in the database. I use it for other purpose such as adding more...
my goal: update the first record with a new value to a field:
I tried:
$invitation = new MemberInvitation();
$invitation1 = $invitation->find(1);
$invitation1->status = 'clicked';
$invitation1->save();
And also:
$invitation1 = \App\Model\MemberInvitation::find(1);
$invitation1->status = 'clicked';
$invitation1->save();
both ended with:
Creating default object from empty value
EDIT:
This piece of code worked and updated my records correctly -- I just can't do it via Eloquent's model:
\DB::table('member_invitations')->where('id', '=', $member_invitation->id)
->update(array('status' => 'clicked', 'member_id' => $member->id));
what am I missing?
Try this.
$invitation = MemberInvitation::findOrFail(1);
$invitation->status = 'clicked';
$invitation->save();
If that doesnt work, please show your model
find(1) doesn't mean "give me the first record", it means "give me the first record where id = 1". If you don't have a record with an id of 1, find(1) is going to return null. When you try and set an attribute on null, you get a PHP warning message "Creating default object from empty value".
If you really just want to get the first record, you would use the first() method:
$invitation = \App\Model\MemberInvitation::first();
If you need to get a record with a specific id, you can use the find() method. For example, to translate your working DB code into Eloquent, it would look like:
$invitation = \App\Model\MemberInvitation::find($member_invitation->id);
$invitation->status = 'clicked';
$invitation->member_id = $member->id;
$invitation->save();
The answer is obvious based on your reply from May 15th.
Your "fillable" attribute in the model is empty. Make sure you put "status" in that array. Attributes that are not in this array cannot up modified.

Magento how to detect changed fields only in admin section?

I need to insert some values to custom database table based on the values of changed custom field, if the specific custom field value (in a custom shipping method) had changed.I need to check this in my Observer.php event that I'm firing is admin_system_config_changed_section_carriers for getting values from the field and insert values to the table
is there any possible way to do this ?
EDIT:
here is my observer function
public function handle_adminSystemConfigChangedSection($observer){
$post = Mage::app()->getRequest()->getPost();
$firstBarcodeFlatrate = $post['groups']['flatrate']['fields']['barcode_start']['value'];
$lastBarcodeFlatrate = $post['groups']['flatrate']['fields']['barcode_end']['value'];
$flatrateRange = range($firstBarcodeFlatrate,$lastBarcodeFlatrate);
$shippingMethod = 'flatrate';
foreach($flatrateRange as $barcodes){
$insertData = array(
'barcode' => $barcodes,'shipping_method' => $shippingMethod,'status' => 1,
);
$model = Mage::getModel('customshippingmethods/customshippingmethods')->setData($insertData);
try {
$model->save();
} catch (Exception $e){
echo $e->getMessage();
}
}
as you can see above database query will update each time I save the configuration but I just need to run the query iff $firstBarcodeFlatrate value had changed
I would probably go with two options:
1. Cache the last value of $firstBarcodeFlatrate
$cacheId = 'first_barcode_flatrate_last_value';
$cache = Mage::app()->getCache();
$lastValue = $cache->load($cacheId);
if (!$lastValue) {
//We don't have cached last value, we need to run the query and cache it:
//Cache the value:
$cache->save($firstBarcodeFlatrate, $cacheId, array('first_barcode_flatrate_last_value'), null);
//Run the query here
} else {
//We have last value, we need to check if it has been changed:
if($lastValue != $firstBarcodeFlatrate) {
//Update the cached value:
$cache->save($firstBarcodeFlatrate, $cacheId, array('first_barcode_flatrate_last_value'), null);
//Run the query here.
}
}
Option 2 is to create another table with a single row and two fields or add another system config field that will store the last used value. Then before the running the query, you will check this value if it's different than $firstBarcodeFlatrate you will run the query, otherwise you won't, though I think the caching will do the job for you.

Categories