Could you show us your code please. And the exact error that Laravel spits out.
How do you instantiate variables in an event based markdown mail?
Hi,
I've been banging up against a wall.
When reviewing this section of the docs: https://laravel.com/docs/5.8/mail#view-data
It appears you can import the class, set a public property, call $this->order = $order and voila! now you have the event based order available in the markdown mail.
https://laracasts.com/series/laravel-from-scratch-2018/episodes/32
That video shows a great technique of tucking the mail::to function in the notificationhandle().
however, no matter what I do, if I try to add job to the mailable (in addition to $order) I get these errors saying in the notification handle(), we expected 2 but got 1.
job is not a column in the orders table, it holds the user_id which then goes to a pivot table with job_id, order_id.
Thank you ~
good morning, thank you for checking this out. Just been driving me crazy...
here's the error:
Too few arguments to function App\Listeners\SendOrderPlacedNotification::handle(), 1 passed and exactly 2 expected
//event
class OrderPlaced
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* The order instance.
*
* @var Order $order
*/
public $order;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
class OrderPlacedMail extends Mailable
{
use Queueable, SerializesModels;
/**
* The job instance.
*
* @var Job
*/
public $job;
/**
* The order instance.
*
* @var Order $order
*/
public $order;
/**
* Create a new message instance.
*
* @param Job $job
* @param object $order
* @return void
*/
public function __construct($order, $job)
{
$this->order = $order;
$this->job = $job;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->markdown('emails.order-confirmation-email')
->subject("Your BidBird Order")
->with([
'jobTitle' => $this->job->jobtitle,
'orderConfirmation_number' => $this->order->confirmation_number
]);
}
class SendOrderPlacedNotification implements ShouldQueue
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param Job $jobTitle
* @param object $event
* @return void
*/
public function handle(OrderPlaced $event, $jobTitle)
{
Mail::to($event->order->user->email)->send(
new OrderPlacedMail($event->order, $jobTitle)
);
}
class Order extends Model
{
// private $job;
// private $confirmation_number;
protected $guarded = [];
protected $dispatchesEvents = [
'created' => OrderPlaced::class
];
// belongsTo
public function job()
{
return $this->belongsTo(Job::class);
}
your listener receives the event. Your event should contain the public properties that you want your listener to use
Ahh man. Criminy. So, just add the public properties in the event!
Do you personally add them in both places (event and mailable)? I was referring to the docs where it's shown in the mailable with no reference to the event itself.
Do you find that confusing?
I guess most mailables are not created from within event listeners?
Problem is, your event receives the instance of the model through its constructor - and nothing else.
Not sure where you are getting $job / $jobTitle from?
public function handle(OrderPlaced $event, $jobTitle)
^^^^^^^^^
Yea, that's partly why it's been so frustrating.
The docs say here using the with() method you can assign e.g. $jobTitle. At least, how I read it and then it's available in the view. Order is available and effective, passes the the tests, etc, if I comment out the job.
https://laravel.com/docs/5.8/mail#view-data
Isn't the constructor for instantiating? when you say nothing else what would you do in a situation like that?
Get the sequence.
Model created --> Event fires --> Listener catches --> mailable generated
As you are observing the model being created, you are relying on how eloquent generates the event. It does not know about anything else other than the model concerned.
Yes, of course, you can pass data to the mailable to use in the markdown... but only data that is available to the thing creating the mailable, or that the mailable can create itself.
Your listener says
public function handle(OrderPlaced $event, $jobTitle)
Yes, it gets an instance of OrderPlaced event, but where do you think $jobTitle comes from?
You are asking for data in the listener that would simply have to come out of thin air?
After much trial and error I've settled on using OrderPlaced event as I think it more accurately describes what happened. A BidReserve or a Bid make up orders. So I think having a Bid or BidReserve event is not really accurate.
Does that seem legit?
So, I've finally come across a major issue, which your quote explains
Model created --> Event fires --> Listener catches --> mailable generated
My issue is not instantiating variables for a markdown mail but accessing a pivot table from the order.
class CreateOrdersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('orders', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('user_id');
$table->string('confirmation_number');
$table->integer('amount');
$table->string('card_last_four');
$table->timestamps();
});
}
class CreateBidReservesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('bidreserves', function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger('job_id');
$table->unsignedBigInteger('order_id')->nullable();
$table->foreign('job_id')->references('id')->on('jobs');
$table->bigInteger('amount');
$table->timestamps();
});
}
It seems like $order->bidReserve->job->jobtitle would work, but it does not. It gives:
Trying to get property 'job' of non-object
I gave this a go in the Order.php model. Where's this going wrong?
public function bidReserve()
{
return $this->belongsTo(BidReserve::class)->wherePivot('job_id', 'order_id');
}
If bidReserve carries the foreign key to the Order then
BidReserve belongsTo Order / Order hasOne BidReserve
or
BidReserve belongsTo Order / Order hasMany BidReserve
Please check your relationships, and try it out in tinker so that you can be sure all your relationships work as expected.
Hi @snapey
My test keeps saying: ErrorException: Trying to get property of non-object
This mailable action only fails if trying to access data through the pivot table. e.g. dd($order->bidReserve->job->jobtitle);
id job_id order_id amount created_at updated_at
333 3 NULL 500 2019-10-06 21:08:34 2019-10-06 21:08:34
If it's changed to dd($order); It gives the proper data in my test:
#original: array:7 [
"amount" => 500
"user_id" => 1
"confirmation_number" => "ORDERCONFIRMATION1234"
"card_last_four" => "1234"
"updated_at" => "2019-10-07 01:03:55"
"created_at" => "2019-10-07 01:03:55"
"id" => 1
]
I've tried all your concepts above, even with hasOneThrough, hasManyThrough to no avail.
It seems like the pivot table bidreserves is causing issues. Is it a naming issue? e.g. it's not jobs_orders There is this in the BidReserve model: public $table = "bidreserves";
Basically, the orders table tallies amount and is linked to a user. The BidReserves table has the order_id. Should these relations be hasMany? to utilize pivot tables?
This is in the docs: return $this->belongsToMany('App\Role')->withPivot('column1', 'column2'); was not sure if this applies to this situation.
Any ideas?
Is this bidreserves? (you don't say)
id job_id order_id amount created_at updated_at
333 3 NULL 500 2019-10-06 21:08:34 2019-10-06 21:08:34
order_id is null so this bidreserve is never going to relate to an order
Sorry, just saw you responded.
Yea, that's the bidreserve. All I can think is that the update does not occur in time for the mailable to catch it and utilize the relationship.
How would you delay the update so the mailable can use it? Can't update the bidreserve till the order is created...
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store($jobId)
{
$job = Job::Incomplete()->findOrFail($jobId);
$this->validate(request(), [
'amount' => ['required', 'integer', 'min:1'],
'stripeToken' => ['required']
]);
// Charging the customer
try {
$bidReserve = $job->bidReserves()->create([
'amount' => request('amount'),
'job_id' => $jobId,
]);
$order = $bidReserve->complete($this->paymentGateway, request('stripeToken'), auth()->user()->id);
DB::transaction(function () use($order, $job, $bidReserve) {
DB::table('bidreserves')
->where([
['job_id', $job->id],
['created_at', $bidReserve->created_at]
])
->update(array('order_id' => $order->id));
});
return back()
->setStatusCode(201);
} catch (PaymentFailedException $e) {
return back()
->setStatusCode(422);
}
}
depends what bidreserve->complete() does. That function should be attaching the order.
Ok, well, at least I got this far! @snapey
here's the bidreserve->complete():
// as posted...
public function complete($paymentGateway, $stripeToken)
{
$charge = $paymentGateway->charge($this->bidReserveAmount(), $stripeToken);
return Order::forBidReserves($this->job->bidReserves(), $this->userId(), $charge);
}
I'd like the controller to say something like $order = $bidReserve->complete($this->paymentGateway, request('stripeToken'), auth()->user()->id)->updateBidReservesTable();
Would you recommend that type of approach? I'm not sure how to get those variables which are setup in the controller into this method...
// BidReserve.php
...
public function updateBidReservesTable()
{
DB::transaction(function () use($order, $job, $bidReserve) {
DB::table('bidreserves')
->where([
['job_id', $job->id],
['created_at', $bidReserve->created_at]
])
->update(array('order_id' => $order->id));
});
}
gave the above a shot, but it seems like the $job, $bidReserve should be available via the container somehow.
public function complete($paymentGateway, $stripeToken)
{
$charge = $paymentGateway->charge($this->bidReserveAmount(), $stripeToken);
$orderSetup = Order::forBidReserves($this->job->bidReserves(), $this->userId(), $charge);
$order = DB::transaction(function () use($orderSetup, $this->job, $this->bidReserve) {
DB::table('bidreserves')
->where([
['job_id', $job->id],
['created_at', $bidReserve->created_at]
])
->update(array('order_id' => $order->id));
});
return $order;
}
The above isn't working but I feel it may be a bit better (overall) than the previous approach.
Hi @snapey , getting back to this. Got 44 tests together, but this one is not passing, still.
I've been trying to figure out the way to make the setup in the controller available in the complete() method. Can you simply add more arguments? Like so? Maybe I'm missing something.
$order = $bidReserve->complete($this->paymentGateway, request('stripeToken'), auth()->user()->id, $job, $bidReserve);
The $job->id gives this error: Trying to get property 'id' of non-object. So this seems like the job is not being passed.
public function complete($paymentGateway, $stripeToken, $job, $bidReserve)
{
$charge = $paymentGateway->charge($this->bidReserveAmount(), $stripeToken);
$orderSetup = Order::forBidReserves($this->job->bidReserves(), $this->userId(), $charge);
$order = DB::transaction(function () use($orderSetup, $job, $bidReserve) {
DB::table('bidreserves')
->where([
['job_id', $job->id],
['created_at', $bidReserve->created_at]
])
->update(array('order_id' => $orderSetup->id));
});
return $order;
}
Well you have a mixture of $job and $this->job Are they the same? should you be using one and not the other?
Have you checked that $job you are passing to the complete method is a model instance and not null or a collection of models?
ok, thank you. That's one area I'm still foggy on. $this
dd($job) in the complete() method; gives 8 so the id of the current bidder (in the local dev context). No bueno.
I realized, the complete method was missing $user!
you are amazing. Thank you again! Going to update a few of these posts which are related to this...nightmare I've been having.
Please or to participate in this conversation.