Observer not running when creating in database - laravel

Hello I have this method that mass create student sections
Enrollment controller method this code works fine but it doesn't get in my studentSectionObserver. Although it's getting saved one by one with for loop.
public function setStudentsSection(Request $request)
{
$enrollments = Enrollment::whereIn('student_id', $request->students)->where('session_id', $request->session_id)->get();
$program_section = ProgramSection::withCount('students')->find($request->program_section_id);
if(($program_section->students_count + count($enrollments)) <= $program_section->max_students) {
$new_student_sections = array();
foreach($enrollments as $enrollment) {
$data = [
'student_id' => $enrollment->student_id,
'enrollment_id' => $enrollment->id,
'section_id' => $request->program_section_id,
'created_at' => Carbon::now()
];
array_push($new_student_sections, $data);
}
return StudentSection::insert($new_student_sections);
}
return response()->json(['errors' => ['message' => 'Selected Section is full.']], 405);
}
Then i output this activity with studentSectionObserver and added log::info but it doesn't log anything
public function created(StudentSection $student_section)
{
Log::info('test');
$student = $student_section->student()->get()->first();
$section = $student_section->section()->get()->first();
Logger::createLog("Assigned " . $student->first_name . " " . $student->last_name . " '" . $section->section->name . "'");
}
I know this observer gets triggered cause i tested it with this method whenever i add section the studentSectionObserver triggers Logger.
public function enrollStudent(EnrollmentRequest $request)
{
$check_if_exist = Enrollment::where('student_id', $request->student_id)->where('session_id', $request->session_id)->first();
if (!$check_if_exist) {
$program_section = ProgramSection::withCount('students')->find($request->section_id);
if($program_section) {
if($program_section->students_count < $program_section->max_students) {
$enrollment = Enrollment::create($request->all());
$section_data = ['student_id' => $request->student_id, 'section_id' => $request->section_id, 'enrollment_id' => $enrollment->id];
$section = StudentSection::create($section_data);
return response()->json($enrollment, 200);
}
return response()->json(['errors' => ['message' => 'Selected Section is full.']], 405);
}
$enrollment = Enrollment::create($request->all());
return response()->json($enrollment, 200);
}
return response()->json(['errors' => ['message' => 'Student is already enrolled in this session.']], 405);
}
Any help would be greatly appreciated.

As you have figured out the answer on your own.
The reason for using create method is because it triggers the event on the model. Same goes for update method.
Following is the update method under the hood:
/**
* Update the model in the database.
*
* #param array $attributes
* #param array $options
* #return bool
*/
public function update(array $attributes = [], array $options = [])
{
if (! $this->exists) {
return false;
}
return $this->fill($attributes)->save($options);
}
And the save method has these lines of code:
if ($this->fireModelEvent('saving') === false) {
return false;
}
Similarly, create method works. That's why insert doesn't trigger the event on model and you had to use create method.

Turns out i just need to use create method.
public function setStudentsSection(Request $request)
{
$enrollments = Enrollment::whereIn('student_id', $request->students)->where('session_id', $request->session_id)->get();
$program_section = ProgramSection::withCount('students')->find($request->program_section_id);
if(($program_section->students_count + count($enrollments)) <= $program_section->max_students) {
foreach($enrollments as $enrollment) {
$response = StudentSection::create([
'student_id' => $enrollment->student_id,
'enrollment_id' => $enrollment->id,
'section_id' => $request->program_section_id,
'created_at' => Carbon::now()
]);
return $response;
}
}
return response()->json(['errors' => ['message' => 'Selected Section is full.']], 405);
}

Laravel observer doesn't work on bulk objects, it only works with single object.
So when you use create() function it will trigger the observer.

Related

Create flash message in class request laravel

i have a problem in my project. How to create a flash session in class request / validation ? I haven't found a way.
This my Request class code
class UserRequest extends FormRequest {
public function authorize()
{
return TRUE;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
$rules = [
"user_fullname" => "required|alpha_spaces",
"user_email" => "required|email|unique,list_user,user_email",
"user_phone" => "nullable|numeric",
"access_id" => "required|alpha_num_spaces",
"user_password" => "required|min:6|regex:/^.*(?=.{3,})(?=.*[a-zA-Z])(?=.*[0-9]).*$/|confirmed",
"warehouse_id" => "alpha_num_spaces|nullable",
];
if ($this->method() == "POST") {
$rules["province_id"] = "alpha_num_spaces|nullable";
$rules["district_id"] = "alpha_num_spaces|nullable";
$rules["subdistrict_id"] = "alpha_num_spaces|nullable";
$rules["kode_pos"] = "alpha_num_spaces|nullable";
$rules["user_address"] = "alpha_num_spaces|nullable";
}
return $rules;
}
public function messages()
{
return [
"user_password.regex" => "Password wajib terdiri dari huruf & angka!"
];
}
}
This my controller
public function process_user_add(UserRequest $user_request)
{
$user_request->validated();
$request = \request();
$input = (object) \request()->all();
$check = User::add_user_from_owner($input);
if ($check->success) {
return \redirect()->to("administrator/user/" . \encrypt_url($check->id))->with("message", "<script>sweet('success', 'Success!', '$check->message')</script>");
} else {
return \redirect()->back()->with("message", "<script>sweet(\"error\", \"Failed!\", \"$check->message\")</script>")->withInput($request->all());
}
}
How to i check if validation failed, i create a flash message? To my knowledge, class request is auto redirect goback url if validation error
You can use die Validator Facade. Validator::make() After then you can rescieve the fails with the fails() Method. Take a look in the code below:
$rules = array(
"user_fullname" => "required|alpha_spaces",
"user_email" => "required|email|unique,list_user,user_email",
"user_phone" => "nullable|numeric",
"access_id" => "required|alpha_num_spaces",
"user_password" => "required|min:6|regex:/^.*(?=.{3,})(?=.*[a-zA-Z])(?=.*[0-9]).*$/|confirmed",
"warehouse_id" => "alpha_num_spaces|nullable",
);
$validator = Validator::make($request->all(), $rules);
if ($validator->fails())
{
return Redirect::to('/your_url')->withInput()->withErrors($validator);
}
This section from the documentation will point you to the right direction

Laravel - How to update Input Array without deleting Sales Detail

In my Laravel-8 project, I have this controller for Input Field Array Update.
Controller:
public function update(UpdateSaleRequest $request, $id)
{
try {
$sale = Sale::find($id);
$data = $request->all();
$update['date'] = date('Y-m-d', strtotime($data['date']));
$update['company_id'] = $data['company_id'];
$update['name'] = $data['name'];
$update['remarks'] = $data['remarks'];
$sale->update($update);
SaleDetail::where('sale_id', $sale->id)->delete();
foreach ($data['invoiceItems'] as $item) {
$details = [
'sale_id' => $sale->id,
'item_id' => $item['item_id'],
'employee_id' => $item['employee_id'],
'quantity' => $item['qty'],
'price' => $item['cost'],
'total_price' => $item['cost'] * $item['qty'],
'sale_type_id'=>$item['sale_type_id']
];
$saleDetail = new SaleDetail($details );
$saleDetail->save();
}
} catch (JWTException $e) {
throw new HttpException(500);
}
return response()->json($sale);
}
In the form, the user can add more Sales Detail or remove.
Some of the SaleDetail fields are being used somewhere else.
Is there a way to update the input field array without deleting the SaleDetail as shown in what I did here:
SaleDetail::where('sale_id', $sale->id)->delete();
Thanks
I've tried to restructure your code so that's easier to edit. I've left some comments. I can really recommend refactoring.guru. There you will find many ways to improve your code so that it is more extensible, maintainable and testable. If you have any questions, please feel free to ask.
class Sale extends Model
{
// Use a relationship instead of building your own query
public function details() {
return $this->hasMany(SaleDetail::class);
}
}
class SaleDetail extends Model
{
// Use a computed property instead of manually calculating total price
// You can access it with $saleDetail->totalPrice
public function getTotalPriceAttribute() {
return $this->price * $this->quantity;
}
}
class UpdateSaleRequest extends Request
{
public function authorize() {
return true;
}
protected function prepareForValidation() {
$this->merge([
// Create a Carbon instance by string
'date' => Carbon::make($this->date)
]);
}
public function rules() {
// Your validation rules
// Please also validate your invoice items!
// See https://laravel.com/docs/8.x/validation#validating-arrays
}
}
// We let Laravel solve the sale by dependency injection
// You have to rename the variable name in ihr web.php
public function update(UpdateSaleRequest $request, Sale $sale)
{
// At this point, all inputs are validated!
// See https://laravel.com/docs/8.x/validation#creating-form-requests
$sale->update($request->validated());
// Please ensure, that all properties have the same name
// In your current implementation you have price = cost, be consistent!
foreach($request->input('invoiceItems') as $invoiceItem) {
// How we can consider that a detail is already created?
// I assume that each item_id will only occur once, otherwise you'll
// place the id of each detail in your update form (e.g. in a hidden input)
$candidate = $sale->details()
->where('item_id', $properties['item_id'])
->first();
if($candidate) {
$candidate->update($properties);
} else {
$sale->details()->create($properties);
}
}
// A JWT-Exception should not be necessary, since your authentication
// will be handled by a middleware.
return response()->json($sale);
}
I have not tested the code, few adjustments may be needed.
Laravel has a method called updateOrCreate as follow
/**
* Create or update a record matching the attributes, and fill it with values.
*
* #param array $attributes
* #param array $values
* #return \Illuminate\Database\Eloquent\Model|static
*/
public function updateOrCreate(array $attributes, array $values = [])
{
return tap($this->firstOrNew($attributes), function ($instance) use ($values) {
$instance->fill($values)->save();
});
}
That means you could do some thing like
public function update(UpdateSaleRequest $request, $id)
{
try {
$sale = Sale::find($id);
$data = $request->all();
$update['date'] = date('Y-m-d', strtotime($data['date']));
$update['company_id'] = $data['company_id'];
$update['name'] = $data['name'];
$update['remarks'] = $data['remarks'];
$sale->update($update);
foreach ($data['invoiceItems'] as $item) {
$details = [
'item_id' => $item['item_id'],
'employee_id' => $item['employee_id'],
'quantity' => $item['qty'],
'price' => $item['cost'],
'total_price' => $item['cost'] * $item['qty'],
'sale_type_id'=>$item['sale_type_id']
];
$sale->saleDetail()->updateOrCreate([
'sale_id' => $sale->id
], $details);
}
} catch (JWTException $e) {
throw new HttpException(500);
}
return response()->json($sale);
}
I would encourage you to refactor and clean up your code.You can also read more about it here https://laravel.com/docs/8.x/eloquent#upserts

updating record in yii2 with condition but not working

<?php
namespace frontend\controllers;
use Yii;
use common\models\Subscriber;
use common\models\SubscriberSearch;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
use yii\filters\VerbFilter;
/**
* SubscriberController implements the CRUD actions for Subscriber model.
*/
class SubscriberController extends Controller
{
/**
* Creates a new Subscriber model.
* If creation is successful, the browser will be redirected to the 'view' page.
* #return mixed
*/
public function actionSubscribe()
{
$model = new Subscriber();
if ($model->load(Yii::$app->request->post()) && $model->validate()) {
if ($model->sendEmail()) {
Yii::$app->session->setFlash('success', 'You have successfully subscribed My-Blog. You will get notification whenever New post is published');
return $this->goHome();
} else {
Yii::$app->session->setFlash('error', 'Sorry, we are unable to subscribe for the provided email address.');
}
}
return $this->render('create', [
'model' => $model,
]);
}
/**
* Finds the Subscriber model based on its primary key value.
* If the model is not found, a 404 HTTP exception will be thrown.
* #param integer $id
* #return Subscriber the loaded model
* #throws NotFoundHttpException if the model cannot be found
*/`enter code here`
}
using following model :
<?php
namespace common\models;
use Yii;
use yii\behaviors\TimestampBehavior;
use yii\db\ActiveRecord;
use yii\db\Expression;
/**
* This is the model class for table "subscriber".
*
* #property int $id
* #property string $email
* #property string $token
* #property int $status
* #property int $created_at
* #property int $updated_at
*/
class Subscriber extends \yii\db\ActiveRecord
{
const STATUS_DEACTIVE = 0;
const STATUS_ACTIVE = 1;
/**
* #inheritdoc
*/
public static function tableName()
{
return 'subscriber';
}
public function behaviors()
{
return [
'timestamp' => [
'class' => TimestampBehavior::className(),
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
],
'value' => new Expression('NOW()'),
],
];
}
/**
* #inheritdoc
*/
public function rules()
{
return [
[['email'], 'required'],
[['status', 'created_at', 'updated_at'], 'integer'],
[['email'], 'string', 'max' => 60],
[['token'], 'string', 'max' => 255],
[['token'], 'unique'],
[['email'], 'unique', 'targetClass' => '\common\models\Subscriber', 'message' => 'This email has already subscribed our blog.','filter' => ['!=','status' ,0]],
];
}
/**
* #inheritdoc
*/
public function attributeLabels()
{
return [
'id' => 'ID',
'email' => 'Email',
'token' => 'Token',
'status' => 'Status',
'created_at' => 'Created At',
'updated_at' => 'Updated At',
];
}
/**
* Generates subscriber token
*/
public function generateSubscriberToken()
{
return $this->token = Yii::$app->security->generateRandomString() . '_' . time();
}
/**
* Send Email when successfully subscribe
*/
public function sendEmail()
{
$subscribers = Subscriber::find()->where(['email' => $this->email, 'status' => 0,])->one();
if(!$subscribers)
{
$this->generateSubscriberToken();
if(!$this->save())
{
return false;
}
return Yii::$app->mailer
->compose()
->setFrom(['noreply#my-blog.com' => Yii::$app->name . ' robot'])
->setTo('piyush#localhost')
->setSubject('Subscription : ' . Yii::$app->name)
->setHtmlBody('Thank you '.$this->email.' for subscribing '.Yii::$app->name.'<br /><br /> You will receive notification whenever new trick or post is published to website')
->send();
}
$subscribers->generateSubscriberToken();
$subscribers->status = 1;
if(!$subscribers->save())
{
return false;
}
return Yii::$app->mailer
->compose()
->setFrom(['noreply#my-blog.com' => Yii::$app->name . ' robot'])
->setTo('piyush#localhost')
->setSubject('Subscription : ' . Yii::$app->name)
->setHtmlBody('Welcome back '.$this->email.'Thank you for subscribing '.Yii::$app->name.'<br /><br /> You will receive notification whenever new trick or post is published to website')
->send();
}
}
This controller and model are being used to make subscribe activity using email. I want that IF a user has unsubscribed and after some time again want to subscribe then update status = 1 and regenerate token. Above sendEmail is working fine if it's a new subscriber but if it is an old subscriber with status 0 then not working.
Above all, you need to replace the lines
$subscribers->generateSubscriberToken();
$subscribers->status = 1;
with
$subscriber->token =$this->generateSubscriberToken();
$subscribers->status = 1;
as in your function you are setting $this->token and returning it and to update the record you need to set the $subcribers->token filed with the value.
And you should not search the table for the email with status 0 just query the email and check in PHP if status ==0 because a new record should only be entered if the email does not exist, without caring what the status field has so in your case if the email exists but with status =1 your query won't fetch the record and it will try to insert a record instead of doing nothing.
To understand you can try using var_dump(!$subscribers) in both cases an see what it returns.
Moreover, you are repeating things like sending email and token generation you should change your function to the below.
public function sendEmail()
{
$subscribers = self::find()->where(['email' => $this->email])->one();
//set flag for sending email
$sendMail = false;
//email subject
$subject = '';
//generate token
$token = $this->generateSubscriberToken();
//if email found in subscribers
if ($subscribers !== null) {
//check if inactive
if ($subscribers->status !== self::STATUS_ACTIVE) {
//assign token
$subscribers->token = $token;
//set status to active
$subscribers->status = self::STATUS_ACTIVE;
//update the recrod
if (!$subscribers->save()) {
return false;
}
//set subject
$subject = 'Welcome back ' . $this->email . 'Thank you for subscribing ' . Yii::$app->name . '<br /><br /> You will receive notification whenever new trick or post is published to website';
$sendMail = true;
}
} else { //if email does not exist only then insert a new record
$this->status = 1;
if (!$this->save()) {
return false;
}
$subject = 'Thank you ' . $this->email . ' for subscribing ' . Yii::$app->name . '<br /><br /> You will receive notification whenever new trick or post is published to website';
$sendMail = true;
}
//check if send mail flag set
if ($sendMail) {
return Yii::$app->mailer
->compose()
->setFrom(['noreply#my-blog.com' => Yii::$app->name . ' robot'])
->setTo('piyush#localhost')
->setSubject('Subscription : ' . Yii::$app->name)
->setHtmlBody($subject)
->send();
}
}

How To Create Conditional for Unique Validation (UPDATE/PATCH) on Form Request

I'm trying to get my controller cleaner by moving 'validation request' into a form request called 'BookRequest'.
The problem is on the update process, I try to create a condition to check, if it PATCH or POST with the following codes
MyRequest.php
public function rules()
{
// Check Create or Update
if ($this->method() == 'PATCH')
{
$a_rules = 'required|string|size:6|unique:books,column2,' .$this->get('id');
$b_rules = 'sometimes|string|size:10|unique:books,column3,' .$this->get('id');
}
else
{
$a_rules = 'required|string|size:6|unique:books,column2';
$b_rules = 'sometimes|string|size:10|unique:books,column3';
}
return [
'column1' => 'required|string|max:100',
'column2' => $a_rules,
'column3' => $b_rules,
'column4' => 'required|date',
'column5' => 'required|in:foo,bar',
'column6' => 'required',
'column7' => 'required',
'column8' => 'required',
];
}
.$this->get('id') it failed, the form still treat the unique on the update.
Controller#update
public function update($id, BookRequest $request)
{
$book = Book::findOrFail($id);
$input = $request->all();
$book->update($request->all());
return view('dashboards.book');
}
Controller#edit
public function edit($id)
{
$book = Book::findOrFail($id);
return view('dashboards.edit', compact('book'));
}
Controller#create
public function create()
{
return view('dashboards.create');
}
Controller#store
public function store(BookRequest $request)
{
$input = $request->all();
$book = Book::create($input);
return redirect('dashboards/book/index');
}
I try the alternative .$book->id, and it throw me an ErrorException Undefined variable: book
Any suggestion? I'm using Laravel 5.2 by the way
You are using book as your route parameter but trying to get with id. try this-
if ($this->method() == 'PATCH')
{
$a_rules = 'required|string|size:6|unique:books,column2,' .$this->route()->parameter('book');
$b_rules = 'sometimes|string|size:10|unique:books,column3,' .$this->route()->parameter('book');
}
Hope it helps :)

Figure what fields `save()` acted on if any (detecting changes)

Doing ->save() and ->update() only updates when changes made, I think, is this true?
Here's the relevant part of code in Illuminate\Database\Eloquent\Model#performUpdate:
protected function performUpdate(Builder $query, array $options = [])
{
$dirty = $this->getDirty();
if (count($dirty) > 0)
{
// runs update query
}
return true;
}
I typically update like this:
public function update(Requests\UpdatePetRequest $request, Pet $pet)
{
$pet->update($request->all());
return $pet;
}
I want to figure from ->save and from ->update if what field names were updated? Along with what was the old value and the new value.
I currently manually do this like this:
public function update(Requests\UpdatePetRequest $request, Pet $pet)
{
$changes = [];
if ($request->exists('name') && $request->name != $pet->name) {
$changes['name'] = array([
'old' => $pet->name,
'new' => $request->name
]);
}
if ($request->exists('avatar') && $request->avatar != $pet->avatar) {
$changes['avatar'] = array([
'old' => $pet->avatar,
'new' => $request->avatar
]);
}
if (!count($changes)) {
return response()->json(['error'=>'No properties changed'], 422);
}
$pet->update($request->all());
$body = json_encode($changes);
$message = new Message(['body' => $body, 'kind' => 'PET_UPDATE']);
}
Is there an automated way to do this?
You can't do that with update(), but you can use getDirty() before save():
$model = Model::find($id);
$model->fill($request->all());
$cahnges = $model->getDirty();
$model->save();
getDirty() will return you an array with changed columns only, for example:
['name' => 'New Name', 'address' => 'New Street, 12']

Resources