Magento how to detect changed fields only in admin section? - php

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.

Related

Laravel/Mysql: unable to set field to null

I am working on a laravel project and trying to duplicate a record in mysql db.
after replication I want to set a field value to null(appointment_status).
everything is working except the new record's value (appointment_status) is the same as the original record even tho I set it to null.
$newAppointment = $appointment->replicate();
//push to get the id for the cloned record
$newAppointment->push();
$newAppointment->duplicated_from_id = $appointment->id;
$newAppointment->appointment_status = null;
$newAppointment->save();
//updating the old appointment
$appointment->duplicated_to_id = $newAppointment->id;
$appointment->save();
Instead of whole code-block, try something like this:
// replicate as the new record with some different fields
$newAppointment = $appointment->replicate()->fill([
'duplicated_from_id' => $appointment->id,
'appointment_status' => null,
])->save();
// update some fields of initial original record
$appointment->update([
'duplicated_to_id' => $newAppointment->id,
]);
For more check out this.

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

Database data field check before Data insertion

I have a data coming from the HTML Page. And i want to check whether the date and the place values already exists. If they exists, it should throw an error saying Data is already present, if those date and place data is not there it should allow the user to save it.
Here is the code which i have written to save it,
public function StoreSampling(Request $request)
{
$date = Carbon::createFromFormat('d-m-Y', $request->input('date'))->format('Y-m-d');
$doctorname = Input::get('doctorselected');
$product = Input::get('product');
$product= implode(',', $product);
$quantity = Input::get('qty');
$quantity =implode(',',$quantity);
$representativeid = Input::get('representativeid');
//Store all the parameters.
$samplingOrder = new SamplingOrder();
$samplingOrder->date = $date;
$samplingOrder->doctorselected = $doctorname;
$samplingOrder->products = $product;
$samplingOrder->quantity = $quantity;
$samplingOrder->representativeid = $representativeid;
$samplingOrder->save();
return redirect()->back()->with('success',true);
}
I searched some of the Stack over flow pages. And came across finding the existence through the ID And here is the sample,
$count = DB::table('teammembersall')
->where('TeamId', $teamNameSelectBoxInTeamMembers)
->where('UserId', $userNameSelectBoxInTeamMembers)
->count();
if ($count > 0){
// This user already in a team
//send error message
} else {
DB::table('teammembersall')->insert($data);
}
But i want to compare the date and the place. And if they are not present, i want to let the user to save it. Basically trying to stop the duplicate entries.
Please help me with this.
There are very good helper functions for this called firstOrNew and firstOrCreate, the latter will directly create it, while the first one you will need to explicitly call save. So I would go with the following:
$order = SamplingOrder::firstOrNew([
'date' => $date,
'place' => $place
], [
'doctorname' => Input::get('doctorselected'),
'product' => implode(',', Input::get('product')),
'quantity' => implode(',',Input::get('qty')),
'representativeid' => Input::get('representativeid')
]);
if($order->exists()) {
// throw error
return;
}
$order->save();
// success
You need to modify your query to something like this:
$userAlreadyInTeam = SamplingOrder::where('date', $date)
->where('place', $place) // I'm not sure what the attribute name is for this as not mentioned in question
// any other conditions
->exists();
if (userAlreadyInTeam) {
// Handle error
} else {
// Create
}
You do not need to use count() as your only trying to determine existence.
Also consider adding a multi column unique attribute to your database, to guarantee that you don't have a member with the same data and place.
The best way is to use the laravel unique validation on multiple columns. Take a look at this.
I'm presuming that id is your primary key and in the sampling_orders table. The validation rule looks like this:
'date' => ['unique:sampling_orders,date,'.$date.',NULL,id,place,'.$place]
p.s: I do not see any place input in your StoreSampling()

How do I reduce repetitive code when inserting the same metadata into identical columns over multiple tables?

My goals:
Follow database-normalization.
Ability to track changes in my tables.
Ability to restore the state of my database from a given
point in time, e.g. last month.
Separate the code that is processing the data so that the
input can come either from a HTML-form or a script in another language (Ruby/perl).
To accomplish this, I've opted for a database design like the one described in this answer:
StackOverflow: Is there a MySQL option/feature to track history of changes to records?
However, when a user updates several fields, the same metadata has to be inserted into multiple tables that contain identical columns, and my code becomes repetitive.
Example:
A user submits data through a HTML-form.
PHP processes the data like below, with the help of Propel ORM.
function insertEvent($name, $venueId, $user, $date, $id = 0) {
//validation and formatting..
//if id == 0, new event,
//else, this is an update, archive the old database row on successful processing and maintain a common id as a parent
//...
$event = new Event();
$event->setName($name);
$event->setVenueId($venueId);
$event->setUser($user); //1
$event->setValidFrom($date); //1
$event->setValidUntil(null); //1
$event->save();
// ...
}
function insertEventPhonenumber($phonenumber, $pid, $user, $date, $id = 0) {
//...
$event_phonenumber = new EventPhonenumber();
$event_phonenumber->setPid($pid); //2
$event_phonenumber->setPhonenumber($phonenumber);
$event_phonenumber->setUser($user); //2
$event_phonenumber->setValidFrom($date); //2
$event_phonenumber->setValidUntil(null); //2
$event_phonenumber->save();
// ...
}
function insertEventArtistId($artistId, $pid, $user, $date, $id = 0) {
//...
$event_artistId = new EventArtistId();
$event_artistId->setPid($pid); //3
$event_artistId->setArtistId($artistId);
$event_artistId->setUser($user); //3
$event_artistId->setValidFrom($date); //3
$event_artistId->setValidUntil(null); //3
$event_artistId->save();
// ...
}
My problem:
In my full code there are more tables affected than the three in the example.
Marked with //1, //2 and //3, you see data input that is often going to be identical.
In my stomach, I don't like this. I've been trying search engines with queries like 'common columns in SQL insert queries over multiple tables' and variations of the wording, without finding anything directly related to my problem.
Is this bad practice like it feels to me?
How can I minimize the repetition in my code?
Are you trying to get rid of all of those functions? If so you can use the call_user_func to eliminate most of your code.
<?php
class MyApp {
static function insert($name, $params) {
$ob = new $name();
foreach($params as $k=>$v) {
$op = array($ob,"set".ucfirst($k));
//if (is_callable($op)) {
call_user_func($op,$v);
//}
}
$ob->save();
}
}
MyApp::insert(
"EventArtistId",
array(
"pid"=>$_REQUEST['pid'],
"artistId"=>$_REQUEST['artistId'],
"user" => $_REQUEST['user'],
"validFrom" => $_REQUEST['date'], // may need to convert date
"validUntil" => isset($_REQUEST['validUntil']) ? $_REQUEST['validUntil'] : null,
)
);
// More cool
// MyApp::insert($_REQUEST['action'],$_REQUEST['params']);
?>

How to update record array using cakephp and mongodb

I have a problem. I have this structure of db in mongodb:
id:"xxx",
is_validated: "xxx",
validation_code:"xxx",
profile:[
{
profile_pic:"xxx",
firstname:"xxx",
lastname:"xxx",
}
]
I am using cakephp. When I update the record, I use this:
$this->User->set('id', "xxx");
$this->User->set('profile', array('firstname' => 'Benedict'));
$this->User->save()
When I save the record, the whole array of profile is deleted and only saves the "firstname":
id:"xxx",
is_validated: "xxx",
validation_code:"xxx",
profile:[
{
firstname:"xxx"
}
]
I need to be able to save the firstname without deleting the other array records of mongodb using cakephp
Do you not follow the standard convention when using MongoDB? (documentation):
// where '1' is the id of your user
$this->User->read(null, 1);
// set the new value for the field
$this->User->set('profile', array('firstname' => 'Benedict'));
// commit the changes to the database
$this->User->save();
Update
If the above doesn't work, try reading the whole record and modifying accordingly:
// set the active record
$this->User->id = 1;
// read the entire record
$user = $this->User->read();
// modify the field
$user['User']['profile']['firstname'] = 'Benedict';
// save the record
$this->User->save($user);
The reason this is happening is because you are asking for the profile field to be updated with the array that you pass. This then duly replaces the current profile array with yours.
To get round this you will have to pass the complete array in i.e. with the keys you want to keep and their values as well as the keys you want to change.

Categories