Yii2 how to resave the modal in afterSave - php

I have a model ProductOffer inside of it I use afterSave to generate the coupon.
Right now the status is null and in aftersave I want to update it.
public function afterSave($insert, $changedAttributes) {
if (floatval($this->offer) >= floatval($this->product->threshold_price)) {
$coupon = false;
$createCoupon = "";
$ctr = 1;
while ($coupon == false) {
$createCoupon = $this->createCoupon(
"Offer for " . $this->customer_name . ' #' . $this->id,
$this->product->sale_price - $this->offer,
$this->product_id
);
if ($createCoupon || $ctr > 3) {
$coupon = true;
}
$ctr++;
}
$this->status = self::STATUS_ACCEPTED_COUPON_GENERATED;
$this->coupon_code = $createCoupon->code;
// todo this
// echo "Accepted automatically then send email to customer as the same time to merchant email";
} else {
$this->status = self::STATUS_REJECTED;
}
return parent::afterSave($insert, $changedAttributes);
}
So here at afterSave I want to update the status of record and save the coupon code.
What I wan't to do is simply like this.
public function afterSave($insert, $changedAttributes) {
// So basically I want to update the status in afterSave
$this->status = "What ever value rejected or accepted it depends of the outcome of generating coupon";
$this->coupon = "AddTheCoupon";
// Save or Update
$this->save();
return parent::afterSave($insert, $changedAttributes);
}
But It seems not working for me and if you going to analyze it, it seems to do endless updating of the data since every save() it will pass through the afterSave().
Is there other way to do it?
Thanks!

You should use the updateAttributes method, which skips all the events.
See reference updateAttributes(['some_field']).
/** After record is saved
*/
public function afterSave($insert, $changedAttributes)
{
parent::afterSave($insert, $changedAttributes);
$this->some_field = 'new_value';
$this->updateAttributes(['some_field']);
}

Related

Botman yii2. Properties of the conversation lost

BotMan Version: 2.1
PHP Version:7.3
Messaging Service(s):
Cache Driver: SymfonyCache
Description:
I trying to have conversation. In every next method I lost data from conversation properties, that was saved in properties before!
class GetAlertDataConversation extends AppConversation
{
public $bot;
public $alertTitle;
public $alertDescription;
public $alertLatitude;
public $alertLongitude;
public $alertAuthorId;
public $alertAuthorName;
public function __construct($bot)
{
$this->bot = $bot;
}
private function askTitle()
{
$this->ask('Что случилось? (кратко)', function (Answer $answer) {
$this->alertTitle = $this->askSomethingWithLettersCounting($answer->getText(), 'Слишком коротко!', 'askDescription');
\Yii::warning($this->alertTitle);
});
}
public function askDescription()
{
$this->ask('Расскажи подробней!', function (Answer $answer) {
$this->alertDescription = $this->askSomethingWithLettersCounting($answer->getText(), 'Слишком коротко!', 'askLocation');
\Yii::warning($this->alertTitle);
});
}
private function askLocation()
{
\Yii::warning($this->alertTitle);
$this->askForLocation('Локация?', function (Location $location) {
// $location is a Location object with the latitude / longitude.
$this->alertLatitude = $location->getLatitude();
$this->alertLongitude = $location->getLongitude();
$this->endConversation();
return true;
});
}
private function endConversation()
{
\Yii::warning($this->alertTitle);
$alertId = $this->saveAlertData();
if ($alertId)
$this->say("Событие номер {$alertId} зарегистрировано!");
else
$this->say("Ошибка при сохранении события, обратитесь к администратору!");
}
private function saveAlertData()
{
$user = $this->bot->getUser();
$this->alertAuthorId = $user->getId();
$this->alertAuthorName = $user->getFirstName() . ' ' . $user->getLastName();
$alert = new Alert();
\Yii::warning($this->alertTitle);
$alert->name = $this->alertTitle;
$alert->description = $this->alertDescription;
$alert->latitude = $this->alertLatitude;
$alert->longitude = $this->alertLongitude;
$alert->author_id = $this->alertAuthorId;
$alert->author_name = $this->alertAuthorName;
$alert->chat_link = '';
$alert->additional = '';
if ($alert->validate()) {
$alert->save();
return $alert->id;
} else {
\Yii::warning($alert->errors);
\Yii::warning($alert);
return false;
}
}
}
There is user's text answer in the first \Yii::warning($this->alertTitle); in the askTitle() function.
But all other \Yii::warning($this->alertTitle); returns NULL!!!!
As the result, saving of Alert object not working!
Please, help me. Some ideas?
I think, that it can be by some caching + serialise problem.
I was trying to change cache method to Redis. Same result.
Problem was in this function call and caching:
$this->askSomethingWithLettersCounting($answer->getText(), 'Слишком коротко!', 'askDescription');
If you will read other conversation botman issues in github, you wil see, that most of issues in conversation cache and serialise.
Not any PHP code of conversation can be cached right.
In this case, not direct call of functions askDescription() and askLocation() broken conversation caching.
I fixed that by removing askSomethingWithLettersCounting() function.

How to set additional custom attribute in Eloquent Laravel while using Accessor

I'm trying to set additional custom attribute while using Accessor in Laravel model.
Example:
I'm calculating promotion price and setting this new attribute, but in additional want to set "$promo = 1 || $promo = 0" using the same logic.
The very cut example just with logic. The real logic is far deeper, that's why I don't want to duplicate the accessor:
public function getFinalPriceAttribute($value)
{
if($this->promotion == true) {
$final_price = $this->price * 100;
//here I want to add new attribute (promo = 1)
//Something like using another method here to setAttribute. Example: setPromoAttribute(1)
} else {
$final_price = $price;
//here I want to add new attribute (promo = 0)
//Something like using another method here to setAttribute. Example: setPromoAttribute(0)
}
return $final_price;
}
protected $appends = ['final_price', 'on_sale'];
}
I can easily duplicate the whole getFinalPriceAttribute(), but make no sense to have exactly the same code in two getAttribute() accessors. Any idea?
I don't know whether this is the correct implementation of what you want to achieve but here are two hacks:
1.
Create a virtual relation for this row in accessor with a new Illuminate\Database\Eloquent\Collection and then push the item promo value into it. But THIS WILL BE AN ARRAY.
public function getFinalPriceAttribute($value)
{
$this->setRelation('promo', new Collection());
if($this->promotion == true) {
$final_price = $this->price * 100;
$this->promo->push(1);
} else {
$final_price = $price;
$this->promo->push(1);
}
return $final_price;
}
So in response, you'll get promo as array like:
{
"promotion": true,
"promo": [
1
]
}
or
{
"promotion": false,
"promo": [
0
]
}
2.
Create two accessors for promo. One as promo0 & other is promo1 and append the attribute dynamically(and conditionally) with append(). But this way you'll get two different attributes in response conditionally.
public function getPromo1Attribute()
{
return true;
}
public function getPromo0Attribute()
{
return true;
}
public function getFinalPriceAttribute($value)
{
if($this->promotion == true) {
$final_price = $this->price * 100;
$this->append('promo1');
} else {
$final_price = $price;
$this->append('promo0');
}
return $final_price;
}
And this will return the response like:
{
"promotion": true,
"promo1": true
}
or
{
"promotion": false,
"promo0": true
}
I hope this will help at least someone in the future.

Eloquent Model Saves in DB but not found in code

Thanks in advance for any help. I have an Invoice Model which has a one-to-many relationship with the payment model and when I loop through an invoice's payments to add all the $payment->net and subtract it from the $invoice->cost to see the balance that is left. The payment that was just made in the same call doesn't appear in $invoice->payments, it feels like its cached.
Invoice.php
public function payments()
{
return $this->hasMany('App\Payment');
}
public function net() :float
{
$payments = $this->payments;
$net = $this->cost;
foreach ($payments as $payment) {
$net += $payment->net;
}
return $net;
}
public function balance() :float
{
return ($this->cost - $this->net());
}
Payment.php
public function invoice()
{
return $this->belongsTo('App\Invoice');
}
PaymentController.php
$invoice = Invoice::findOrFail($id);
// Check ownership
if(!$this->getCurrentUser()->isSuperuser() && $this->getCurrentUser()->id === $invoice->user_id) {
throw new ModelNotFoundException();
}
$payment = new Payment($request->all());
$payment->ref = Payment::generateRef($invoice->id, $request->input('type'));
// Check for overpayment
if($invoice->balance() < $payment->net) {
throw new BadInputException('Payment exceeds balance.');
}
if($payment = $invoice->payments()->save($payment)) {
if($invoice->balance() == 0) {
$invoice->status = Invoice::CLOSED;
$invoice->save();
}
}
return $payment;
This is not the solution I was looking for but I don't like to expend too much time in little problems like this. I'll try to understand why is the model caching later.
The first time I run $invoice->balance() the value gets cached and the function won't take into consideration the new payment made when evaluating the new balance.
so the next time I ran $invoice->balance() i just manually subtract the new net payment.
if($invoice->balance() - $payment->net == 0) {
$invoice->status = Invoice::CLOSED;
$invoice->save();
}

How can I use callback functions in groceryCrud for the view record page?

I do not know how to set a callback function for the view record page in codeigniter.
I use the callback_column function and it does what I need in the grid view, but on the view record page it does not work.
I searched their site and forum and did not found anything that could help me.
My code looks like:
$zeus = new grocery_CRUD();
$zeus->set_theme('bootstrap');
// $zeus->set_language('romanian');
$zeus->set_table('programari');
$zeus->columns(array('id_client', 'id_sala', 'denumire', 'numar_persoane', 'observatii'));
$zeus->callback_column('id_sala',array($this,'_test_function'));
$cod = $zeus->render();
$this->_afiseaza_panou($cod);
public function _test_function($row, $value)
{
return '0';
}
write this lines in \libraries\Grocery_CRUD.php
at line number 3530
protected $callback_read_field = array();
than put this function after constructor call
public function callback_read_field($field, $callback = null)
{
$this->callback_read_field[$field] = $callback;
return $this;
}
//Now update this function to manage the field outputs using callbacks if they are defined for the same
protected function get_read_input_fields($field_values = null)
{
$read_fields = $this->get_read_fields();
$this->field_types = null;
$this->required_fields = null;
$read_inputs = array();
foreach ($read_fields as $field) {
if (!empty($this->change_field_type)
&& isset($this->change_field_type[$field->field_name])
&& $this->change_field_type[$field->field_name]->type == 'hidden') {
continue;
}
$this->field_type($field->field_name, 'readonly');
}
$fields = $this->get_read_fields();
$types = $this->get_field_types();
$input_fields = array();
foreach($fields as $field_num => $field)
{
$field_info = $types[$field->field_name];
if(isset($field_info->db_type) && ($field_info->db_type == 'tinyint' || ($field_info->db_type == 'int' && $field_info->db_max_length == 1))) {
$field_value = $this->get_true_false_readonly_input($field_info, $field_values->{$field->field_name});
} else {
$field_value = !empty($field_values) && isset($field_values->{$field->field_name}) ? $field_values->{$field->field_name} : null;
}
if(!isset($this->callback_read_field[$field->field_name]))
{
$field_input = $this->get_field_input($field_info, $field_value);
}
else
{
$primary_key = $this->getStateInfo()->primary_key;
$field_input = $field_info;
$field_input->input = call_user_func($this->callback_read_field[$field->field_name], $field_value, $primary_key, $field_info, $field_values);
}
switch ($field_info->crud_type) {
case 'invisible':
unset($this->read_fields[$field_num]);
unset($fields[$field_num]);
continue;
break;
case 'hidden':
$this->read_hidden_fields[] = $field_input;
unset($this->read_fields[$field_num]);
unset($fields[$field_num]);
continue;
break;
}
$input_fields[$field->field_name] = $field_input;
}
return $input_fields;
}
than call same as other callback functions
As far as I'm aware GroceryCRUD doesn't provide callbacks or another means of overriding the default output in the view state.
The solution to customising this would be to create a custom view to which you will insert the data from your record. This way you can customise the layout and other presentation.
What you would then do is unset the default read view with:
$crud->unset_read();
And add a new action where there are details on how to do this here.
What to do with the new action is point it to a URL that you map in routes.php if necessary and handle it with a new function in your controller. You'll either have to write a model function to retrieve the data since this isn't passed from GC or you can use the action to target a callback and feed $row to it via POST or something so that the data for the record is accessible in the view. (Look at the example in the link above).

Implementing not automatic badges with PHP and MYSQL

I have users' table users, where I store information like post_count and so on. I want to have ~50 badges and it is going to be even more than that in future.
So, I want to have a page where member of website could go and take the badge, not automatically give him it like in SO. And after he clicks a button called smth like "Take 'Made 10 posts' badge" the system checks if he has posted 10 posts and doesn't have this badge already, and if it's ok, give him the badge and insert into the new table the badge's id and user_id that member couldn't take it twice.
But I have so many badges, so do I really need to put so many if's to check for all badges? What would be your suggestion on this? How can I make it more optimal if it's even possible?
Thank you.
optimal would be IMHO the the following:
have an object for the user with functions that return user specific attributes/metrics that you initialise with the proper user id (you probably wanna make this a singleton/static for some elements...):
<?
class User {
public function initUser($id) {
/* initialise the user. maby load all metrics now, or if they
are intensive on demand when the functions are called.
you can cache them in a class variable*/
}
public function getPostCount() {
// return number of posts
}
public function getRegisterDate() {
// return register date
}
public function getNumberOfLogins() {
// return the number of logins the user has made over time
}
}
?>
have a badge object that is initialised with an id/key and loads dependencies from your database:
<?
class Badge {
protected $dependencies = array();
public function initBadge($id) {
$this->loadDependencies($id);
}
protected function loadDependencies() {
// load data from mysql and store it into dependencies like so:
$dependencies = array(array(
'value' => 300,
'type' => 'PostCount',
'compare => 'greater',
),...);
$this->dependencies = $dependencies;
}
public function getDependencies() {
return $this->dependencies;
}
}
?>
then you could have a class that controls the awarding of batches (you can also do it inside user...)
and checks dependencies and prints failed dependencies etc...
<?
class BadgeAwarder {
protected $badge = null;
protected $user = null;
public function awardBadge($userid,$badge) {
if(is_null($this->badge)) {
$this->badge = new Badge; // or something else for strange freaky badges, passed by $badge
}
$this->badge->initBadge($badge);
if(is_null($this->user)) {
$this->user = new User;
$this->user->initUser($userid);
}
$allowed = $this->checkDependencies();
if($allowed === true) {
// grant badge, print congratulations
} else if(is_array($failed)) {
// sorry, you failed tu full fill thef ollowing dependencies: print_r($failed);
} else {
echo "error?";
}
}
protected function checkDependencies() {
$failed = array();
foreach($this->badge->getDependencies() as $depdency) {
$value = call_user_func(array($this->badge, 'get'.$depdency['type']));
if(!$this->compare($value,$depdency['value'],$dependency['compare'])) {
$failed[] = $dependency;
}
}
if(count($failed) > 0) {
return $failed;
} else {
return true;
}
}
protected function compare($val1,$val2,$operator) {
if($operator == 'greater') {
return ($val1 > $val2);
}
}
}
?>
you can extend to this class if you have very custom batches that require weird calculations.
hope i brought you on the right track.
untested andp robably full of syntax errors.
welcome to the world of object oriented programming. still wanna do this?
Maybe throw the information into a table and check against that? If it's based on the number of posts, have fields for badge_name and post_count and check that way?

Categories