Closed ametad closed 5 years ago
Forgot to include something.
This is where I cannot get the helper route()
to generate correct tenant urls:
<?php
namespace App\Mail;
use Hyn\Tenancy\Contracts\Website;
use Hyn\Tenancy\Queue\TenantAwareJob;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\URL;
class InvitationMade extends Mailable implements ShouldQueue
{
use Queueable,TenantAwareJob;
/**
* @var Website
*/
public $website;
/**
* @var string
*/
public $fqdn;
/**
* @var string
*/
private $token;
/**
* @var string
*/
private $email;
/**
* Create a new message instance.
*
* @param Website $website
* @param string $email
* @param string $token
*/
public function __construct(Website $website, string $email, string $token)
{
$this->website = $website;
$this->token = $token;
$this->email = $email;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->markdown('emails.invitation')
//->from($this->website->company->email) // todo
->subject('Uitnodiging ' . $this->website->name)
->with([
'invitee_register_url' => $this->registerUrl(),
'invitee_login_url' => $this->loginUrl(),
]);
}
protected function registerUrl(): string
{
if (! $this->configTenantUrl()) {
return null;
}
return "{$this->fqdn}/invitee/register/{$this->email}/{$this->token}";
// return route('invitee.register', ['email' => $this->email, 'token' => $this->token]);
}
protected function loginUrl(): string
{
if (! $this->configTenantUrl()) {
return null;
}
return "{$this->fqdn}/invitee/login/{$this->email}/{$this->token}";
// return route('invitee.login', ['email' => $this->email, 'token' => $this->token]);
}
protected function configTenantUrl(): bool
{
if ($hostname = $this->findHostname()) {
$prefix = $hostname->force_https ? 'https://' : 'http://';
$fqdn = "{$prefix}{$hostname->fqdn}";
URL::forceRootUrl($fqdn);
config(['app.url' => $fqdn]);
$this->fqdn = $fqdn;
return true;
}
return false;
}
/**
* @return mixed
*/
protected function findHostname()
{
return $this->website
->hostnames()
->first();
}
}
You are assuming because you're sending a Website model into the job that the TenantAwareJob trait automatically picks that up. That's not the case, what happens is that the trait will check whether a Tenant Website is currently active, if that is the case that Tenant is used when the Job is deserialised. When dispatching the job you will have to force the job to use the Website you wanted to sent as argument, so:
dispatch(new InvitationMade($website, 'foo@bar.com', '123abc'));
becomes:
dispatch((new InvitationMade($website, 'foo@bar.com', '123abc'))->onTenant($website));
as per docs: https://laravel-tenancy.com/docs/hyn/5.3/queues
I hope this helps.
Also there are some issues with the root URL being forced which will be resolved in a future version.
Thank you for your time @luceos,
The Website $website
in the constructor of InvitationMade
is there for another reason, it is needed in the view of the email to display some info.
Because this is an email, I use this syntax to send it:
Mail::to($email)->send(new InvitationMade($website, $email, $token));
And InvitationMade
implements Illuminate\Contracts\Queue\ShouldQueue
so it is send trough the queue.
Sending the e-mail is initiated trough an Artisan Command, therefore I run it within Tenancy context with: artisan tenancy:run --tenant=1 invite:user --argument="email=foo@example.com"
Should the TenantAwareJob
trait in the mailable not pick up the context of the now 'active' tenant?
Have you tried what I suggested?
I misread your suggestion perhaps..? Because I already use the Hyn\Tenancy\Queue\TenantAwareJob
trait the active tenant is automatically set. The tenant is set on the command line with artisan tenancy:run --tenant=1
.
But to answer your question: no I havent tried setting the tenant manually with ->onTenant($website)
.
I will try to set it manually and report back here.
Also manually call onTenant($website)
does NOT do the trick...
I have been trying a lot of things and it keeps boiling down to:
within http context the route('route.name')
does work perfectly for routes defined in 'routes/tenants.php'
within command line interface context the routes defined in 'routes/tenants.php' are not resolved/found ("InvalidArgumentException: Route [route.name] not defined.")
Perhaps this has something to do with #686 because there the routes (defined 'routes/tenants.php') are also not resolved on cli.
Mail app/Mail/InvitationMade.php
:
<?php
namespace App\Mail;
use App\Contracts\CanBeInvited;
use Hyn\Tenancy\Queue\TenantAwareJob;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Support\Facades\URL;
use Hyn\Tenancy\Contracts\Website.php;
class InvitationMade extends Mailable
{
use Queueable,
TenantAwareJob;
/**
* @var Website
*/
public $website;
/**
* @var string
*/
public $url;
/**
* @var CanBeInvited
*/
public $data;
/**
* @var string
*/
private $token;
/**
* @var string
*/
private $email;
/**
* @var int
*/
private $invitation_id;
/**
* Create a new message instance.
*
* @param Website $website
* @param int $invitation_id
* @param string $email
* @param string $token
* @param CanBeInvited|null $data
*/
public function __construct(Website $website, int $invitation_id, string $email, string $token, CanBeInvited $data = null)
{
$this->website = $website;
$this->token = $token;
$this->email = $email;
$this->invitation_id = $invitation_id;
$this->data = $data;
$this->onTenant($website);
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->markdown('emails.invitation')
//->from($this->website->company->email) // todo
->subject('Uitnodiging ' . $this->website->name)
->with([
'invitee_register_url' => $this->registerUrl(),
'invitee_login_url' => $this->loginUrl(),
]);
}
protected function registerUrl(): string
{
if (!$this->configTenantUrl()) {
return null;
}
return route('invitee.register', ['invitation_id' => $this->invitation_id, 'email' => $this->email, 'token' => $this->token]);
}
protected function configTenantUrl(): bool
{
if ($this->url) {
return true;
}
if ($hostname = $this->findHostname()) {
$prefix = $hostname->force_https ? 'https://' : 'http://';
$url = "{$prefix}{$hostname->fqdn}";
URL::forceRootUrl($url);
config(['app.url' => $url]);
$this->url = $url;
return true;
}
return false;
}
/**
* @return mixed
*/
protected function findHostname()
{
return $this->website
->hostnames()
->first();
}
protected function loginUrl(): string
{
if (!$this->configTenantUrl()) {
return null;
}
return route('invitee.login', ['invitation_id' => $this->invitation_id, 'email' => $this->email, 'token' => $this->token]);
}
}
(Notice: ShouldQueue
is removed. This mail will not automatically send trough a queue)
Called directly in HTTP context:
routes/tenants.php
use App\Mail\InvitationMade;
Route::middleware('web')->namespace('App\Http\Controllers')->group(function () {
// todo remove testing
Route::get('mail-direct', function () {
$website = Tenancy::website();
// SEND
Mail::to('test@example.com')->send(new InvitationMade($website, 1, 'test@example.com', 'secret-code-123', null));
});
Route::get('mail-queued', function () {
$website = Tenancy::website();
//QUEUE
Mail::to('test@example.com')->queue(new InvitationMade($website, 1, 'test@example.com', 'secret-code-123', null));
});
Auth::routes(['verify' => true]);
// Invitee register
Route::get('invitee/register/{invitation_id}/{email}/{token}', 'Auth\\InviteeRegisterController@showRegistrationForm')->name('invitee.register');
Route::post('invitee/register/{invitation_id}/{email}/{token}', 'Auth\\InviteeRegisterController@register');
// Invitee login
Route::get('invitee/login/{invitation_id}/{email}/{token}', 'Auth\\InviteeLoginController@showLoginForm')->name('invitee.login');
Route::post('invitee/login/{invitation_id}/{email}/{token}', 'Auth\\InviteeLoginController@login');
In browser hit /mail-direct
: works perfectly, routes are resolved correctly!
In browser hit /mail-queued
: Error!
From the logs:
InvalidArgumentException: Route [invitee.register] not defined
@luceos Would you be so kind to look over a demo I've made to reproduce my problem?
https://github.com/ametad/laravel-app-multi-tenant-demo
Please let me know if you if you want to know something I can assist you with.
Regards.
Let me know whether this problem persist with 5.4.0-beta.2; it makes all Queue jobs tenant aware.
I will make some time in the next days. Thank you for letting me know!
@luceos Yes it works! This is very nice. Thank you for digging into this.
Any estimate on the 5.4 release? Succes.
Description
A queued e-mail is handled on the command line interface by the Laravel worker
php artisan queue:work
. I'd like the helperroute('name.of.route')
generate urls that are defined for the tenants inroutes/tenants.php
. These urls are needed for buttons in the mail.Because of Tenancy, the context of where the urls are generated needs to be tenant-aware.
Actual behavior
Routes generated with the 'route()' helper are not generated and an error appears saying the named route is not found.
Expected behavior
Defined tenant routes (as described here https://laravel-tenancy.com/docs/hyn/5.3/fallback#tenant-routes-override) should be generated in a tenant-aware context. E.g. with the
Hyn\Tenancy\Queue\TenantAwareJob
or by running a command within thephp artisan tenancy:run <custom-command>
structure.Information
tenancy.php config
webserver.php config
.env
routes/web.php
routes/tenants.php
Tenant screenshot
Screenshot of a correct working tenant auth route, trough http protocol: