Why is my Laravel Livewire event emission not working? - laravel

I have a simple Livewire component that lists users:
class UserListComponent extends Component
{
use WithPagination;
protected $listeners = ['refreshComponent' => '$refresh'];
public $search = '';
public $orderBy = 'id';
public $orderAsc = true;
public $perPage = 10;
public function render()
{
return view('livewire.admin.user-list-component',[
'users' => User::search($this->search)
->orderBy($this->orderBy, $this->orderAsc ? 'ASC' : 'DESC')
->simplePaginate($this->perPage)
]);
}
}
and a component that adds users:
public function addUser()
{
// validate data
$validatedData = $this->validate();
// generate random password
$bytes = openssl_random_pseudo_bytes(4);
$password = bin2hex($bytes);
// create user
$user = User::create([
'name' => $validatedData['name'],
'email' => $validatedData['email'],
'password' => Hash::make($password),
]);
event(new Registered($user));
// assign user role
$user->attachRole('user');
$this->emitTo('UserListComponent', 'refreshComponent');
}
As you can see, at the end of the addUser() function I emit an event to the UserListComponent and have a listener set up in that component to refresh it, so that when a user is added the list of users automatically updates. However, it doesn't work. If I refresh manually I can see that the user is added to the database and display just fine, but the component refreshing does not happen, and no error is thrown.
Any ideas?

From what I see the best way is to use full class name so instead of:
$this->emitTo('UserListComponent', 'refreshComponent');
you should use:
$this->emitTo(\App\Http\Livewire\UserListComponent:class, 'refreshComponent');
(of course update namespace if you have UserListComponent in other namespace)

Related

Lavarel & Vue e-commerce: how to post an order as array of products instead of single product

I am kinda new to both Laravel and Vue and I am working on a school project. I have been following a guide and trying to develop the product but I have the following problem: in the guide was only possible to do an order with a single product. Using LocalStorage a created a Cart component where you can add several products instead. How do I use axios.post to correctly post the order in the database now?
app/Http/Controllers/OrderController.php:
<?php
namespace App\Http\Controllers;
use App\Models\Order;
use Auth;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function index()
{
return response()->json(Order::with(['product'])->get(),200);
}
public function store(Request $request)
{
$order = Order::create([
'product_id' => $request->product_id,
'user_id' => Auth::id(),
'quantity' => $request->quantity,
'address' => $request->address
]);
return response()->json([
'status' => (bool) $order,
'data' => $order,
'message' => $order ? 'Order Created!' : 'Error Creating Order'
]);
}
public function show(Order $order)
{
return response()->json($order,200);
}
Resources/JS/views/Checkout.vue (between < script > tag):
placeOrder(e) {
e.preventDefault()
let address = this.address
let product_id = this.product.id
let quantity = this.quantity
axios.post('api/orders/', {address, quantity, product_id})
.then(response => this.$router.push('/confirmation'))
},
App/Http/Models/Order.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Order extends Model
{
use SoftDeletes;
protected $fillable = [
'product_id', 'user_id', 'quantity', 'address'
];
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
public function product()
{
return $this->belongsTo(Product::class, 'product_id');
}
}
Actually, You can achieve your goal by changing many lines of code instead of using your current code at backend (laravel Model-Controller) and frontend (Vue). I will show you how to do by adding hasMany relationship in your User model structure, then changing saving method at controller, and axios request payload. This method has limitation, you have to post an array of products of the same user ID.
Add hasMany relationship in your User Model. Read this
class User extends Model
{
//add this line
public function order()
{
return $this->hasMany(Order::class);
}
Use createMany function to save multiple rows in your controller. Read this
public function store(Request $request)
{
//use this lines to store array of orders
$user = Auth::user();
$orderStored = $user->order()->createMany($request->data);
//return your response after this line
}
Change your axios payload from vue method
data(){
return {
//add new key data to store array of order
arrayOfOrders:[];
};
},
methods:{
placeOrder(e) {
e.preventDefault()
let address = this.address
let product_id = this.product.id
let quantity = this.quantity
//remark these lines, change with storing to arrayOfOrders data instead of doing post request
//axios.post('api/orders/', {address, quantity, product_id})
//.then(response => this.$router.push('/confirmation'))
this.arrayOfOrders.push({
product_id:product_id,
quantity:quantity,
address:address
});
},
//create new function to make post request and call it from your button
postData(){
let instance = this;
axios.post('api/orders/', {
data:instance.arrayOfOrders
}).then(response => this.$router.push('/confirmation'))
}
}
Thank you for your answer! Just one thing is not so clear.. in my OrderController.php should the final code look something like this?
public function store(Request $request)
{
$user = Auth::user();
$order = $user->order()->createMany([
'product_id' => $request->product_id,
'user_id' => Auth::id(),
'quantity' => $request->quantity,
'address' => $request->address
]);
return response()->json([
'status' => (bool) $order,``
'data' => $order,
'message' => $order ? 'Order Created!' : 'Error Creating Order'
]);
}

Larave 6 - pivot table sync - fire created event only if the attached user is new

I have following code:
$clinic->users()->sync($sync);
Which will go to this class (sync is working):
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class ClinicUser extends Pivot
{
protected $table = 'clinic_user';
static function boot()
{
parent::boot();
static::created(function($item) {
$user = \App\User::find($item->users_id);
$clinic = \App\Models\Clinic::find($item->clinics_id);
if($user->userData->notification_email == 1)
\Mail::to($user->email)->send(new \App\Mail\ClinicManagerAdded(
$user,
$clinic));
if($user->userData->notification_app == 1)
\App\Notification::create([
'title' => "message",
'body' => "message",
'user_id' => $user->id,
]);
});
}
}
Is it possible to fire created method only to the new users (does which weren't detached)?
What i was suggesting is not that robust, infact you need to do
$clinic->users()->detach($sync->pluck('id'));
$clinic->users()->sync($sync);
Every time, and you need to remember it (and so is not robust).
What i feel to suggest you to do is something like this:
Delete the notification in the Model
Create a Service for this operation, let's call it NotyfyUsersNewClinicService (maybe you will find a better name):
<?php
namespace App;
use ...;
class NotyfyUsersNewClinicService{
public __constructor(){}
public updateUsers(Clinic& $clinic, Collection& $newUsers){
$clinic->users->diff($newUsers)->each(function(User $users){
$user->userData->notification_email = true;
\Mail::to($user->email)->send(new \App\Mail\ClinicManagerAdded(
$user,
$clinic));
});
$clinic->users()->sync($sync);
}
}
then you will only need to use this:
(new NotyfyUsersNewClinicService())->updateUsers($clinic, $users);
Note: better if you move the email to a job and send it using queue:work
If someone has a similar problem, I have managed to resolve this by creating the static variable, and fill this variable in the deleted event, like this:
<?php
namespace App;
use Illuminate\Database\Eloquent\Relations\Pivot;
class ClinicUser extends Pivot
{
protected $table = 'clinic_user';
static $ids = [];
static function boot()
{
parent::boot();
static::deleted(function($item){
self::$ids[] = $item->users_id;
});
static::created(function($item){
if(!\in_array($item->users_id, self::$ids)){
$user = \App\User::find($item->users_id);
$clinic = \App\Models\Clinic::find($item->clinics_id);
if($user->userData->notification_email == 1)
\Mail::to($user->email)->send(new \App\Mail\ClinicManagerAdded(
$user,
$clinic));
if($user->userData->notification_app == 1)
\App\Notification::create([
'title' => "new message",
'body' => "<p>body</p>",
'user_id' => $user->id,
]);
}
});
}
}

Static model class is null in feature test laravel

I have this test in my feature folder and I've imported model on top of the class but it keeps failing and I think $event is null!
namespace Tests\Feature\Events;
use App\Models\Event;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class EventManagementTest extends TestCase
{
use RefreshDatabase;
/**
* #test
* #group event
* A basic feature test to check event registration
*
*/
public function an_event_can_be_registered()
{
$this->withoutExceptionHandling();
$response = $this->post('/events',$this->data());
$event = Event::first();
$this->assertCount(1,Event::all());
$response->assertRedirect('/events/' . $event->event_id);
}
private function data()
{
return[
'event_title' => 'Internet Businesses',
'event_location' => 'Milad Tower',
'event_description' => 'In this event Amin will present you the most recent methods in Internet Businesses',
'event_start_date' => '2020-06-01',
'event_end_date' => '2020-06-05',
];
}
...
}
And this is the results:
FAIL Tests\Feature\Events\EventManagementTest ✕ an event can be registered
Tests: 1 failed
Failed asserting that two strings are equal.
....
--- Expected
+++ Actual
## ##
-'http://localhost/events/1'
+'http://localhost/events'
these two URIs are different and I think that's because $event is null and I don't know why?!
UPDATE: I've added the Route and the controller:
Route::post('/events','Web\EventsController#store');
and the controller is:
public function store(){
$event = Event::create($this->validateRequest());
return redirect('/events/'.$event->event_id);
}
protected function validateRequest(){
return request()->validate([
'event_title' => 'required',
'event_location' => 'required',
'event_description' => 'required',
'event_start_date' => 'required',
'event_end_date' => 'required',
]);
}
Your do not use the standard primary id column, therefor you need to define it in your model. If it is not defined, it will not set it on create().
class Event extends Model {
protected $primaryKey = 'event_id';
}

how to add extra data into create method with laravel

public function store(Request $request) {
$user = Book::create([
'user_id' => auth()->id(),
'name => $request->name,
'year => $request->year
)];
}
The above code is able to store into Database.
I want to know how to add below extra data TOGETHER.
I found out that merge was not working as it is not collection.
Tried to chain but was not working.
public function data() {
$array = [
'amount' => 30,
'source' => 'abcdef',
];
return $array;
}
You can catch create Event in Model.
This code may can help you.
/**
* to set default value in database when new record insert
*
*/
public static function bootSystemTrait()
{
static::creating(function ($model) {
$model->amount = 30;
});
}
You can write this code into your model. It will execute every time when you create record using Laravel model.
If you want to modify it you can use property into Model class. Something like this:
class TestClass extends Model
{
public static $amount = 0;
static::creating(function ($model) {
$model->amount = self::$amount;
});
}
Good Luck.

Customise Reset Password Email and pass User Data in Laravel 5.3

I am using Laravel 5.3 and customizing the Password Reset Email Template. I have done the following changes to create my own html email for the notification using a custom Mailable class. This is my progress so far:
ForgotPasswordController:
public function postEmail(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
$response = Password::sendResetLink($request->only('email'), function (Message $message) {
$message->subject($this->getEmailSubject());
});
switch ($response) {
case Password::RESET_LINK_SENT:
return Response::json(['status' => trans($response)], 200);
case Password::INVALID_USER:
return Response::json(['email' => trans($response)], 400);
}
}
User Model:
public function sendPasswordResetNotification($token)
{
Mail::queue(new ResetPassword($token));
}
ResetPassword Mailable Class:
protected $token;
public function __construct($token)
{
$this->token = $token;
}
public function build()
{
$userEmail = 'something'; // How to add User Email??
$userName = 'Donald Trump'; // How to find out User's Name??
$subject = 'Password Reset';
return $this->view('emails.password')
->to($userEmail)
->subject($subject)
->with([
'token' => $this->token
'userEmail' => $userEmail,
'userName' => $userName
]);
}
If you noticed above, I am not sure how do I pass the user's name and find out the user's email address. Do I need to send this data from the User Model or do I query it from the Mailable class? Can someone show me how I can do that please?
Usually you ask for the user email in order to send a reset password email, that email should come as a request parameter to your route controller.
By default, L5.3 uses post('password/email) route to handle a reset password request. This route execute sendResetLinkEmail method which is defined in the 'SendsPasswordResetEmails' trait used by the App\Http\Controllers\Auth\ForgotPasswordController.
From here you can take one of 2 options:
1st: You could overwrite the route to call another function in the same controller (or any other controller, in this case could be your postEmail function) which search for the user model by the email you received, then you can pass the user model as function parameter to the method which execute the queue mail action (this may or may not require to overwrite the SendsPasswordResetEmails, depends on how you handle your reset password method).
This solution would looks something like this:
In routes/web.php
post('password/email', 'Auth\ForgotPasswordController#postEmail')
in app/Mail/passwordNotification.php (for instance)
protected $token;
protected $userModel;
public function __construct($token, User $userModel)
{
$this->token = $token;
$this->userModel = $userModel;
}
public function build()
{
$userEmail = $this->userModel->email;
$userName = $this->userModel->email
$subject = 'Password Reset';
return $this->view('emails.password')
->to($userEmail)
->subject($subject)
->with([
'token' => $this->token
'userEmail' => $userEmail,
'userName' => $userName
]);
}
in app/Http/Controllers/Auth/ForgotPasswordController
public function postEmail(Request $request)
{
$this->validate($request, ['email' => 'required|email']);
$userModel = User::where('email', $request->only('email'))->first();
Mail::queue(new ResetPassword($token));
//Manage here your response
}
2nd: You could just overwirte the trait SendsPasswordResetEmails to search for the user model by the email and use your customized function in sendResetLinkEmail function. There you could use your function but notice that you still have to handle somehow an status to create a response as you already have it on ForgotPasswordController.
I hope it helps!

Resources