tenancy / multi-tenant

Run multiple websites using the same Laravel installation while keeping tenant specific data separated for fully independent multi-domain setups, previously github.com/hyn/multi-tenant
https://tenancy.dev
MIT License
2.55k stars 392 forks source link

Scheduler job: route not defined #849

Open RaazPuspa opened 4 years ago

RaazPuspa commented 4 years ago

Description

I have been implementing cron jobs through Laravel scheduler to send some reminder emails for the clients with unsubscription options (link) attached. The job fetches every tenants and for each one select a list of contacts eligible for reminder and send them emails via queue. I have switched the tenant with the following command and can get expected set of contacts from each tenant database, but while using route() it pops an error.

app(Environment::class)->tenant($this->website)

Actual behavior

InvalidArgumentException: Route [profile.unsubscribe.show] not defined.

Expected behavior

route() should prepare url as requested as it is defined on the routes/tenants.php


Information


routes/tenants.php (portion of it)

$this->router
    ->name('profile.')
    ->prefix('profile')
    ->namespace('Profile')
    ->group(function () {
        $this->router->get('unsubscribe', 'UnsubscriptionController@show')
            ->name('unsubscribe.show');
        $this->router->patch('unsubscribe', 'UnsubscriptionController@update')
            ->name('unsubscribe.update');
    });

Error log

[2019-10-02 12:21:01] local.ERROR: InvalidArgumentException: Route [profile.unsubscribe.show] not defined. in /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Routing/UrlGenerator.php:388
Stack trace:
#0 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php(782): Illuminate\Routing\UrlGenerator->route('profile.unsubsc...', Array, true)
#1 /Users/pusparaj/sources/andatech/calibration/app/Calibration/Helpers/helpers.php(278): route('profile.unsubsc...', Array)
#2 /Users/pusparaj/sources/andatech/calibration/app/CronJobs/Tenant/CalibrationDueReminder.php(148): prepareUnsubscribeUrl(Object(App\Entities\Models\User), 'calibration_due', 2)
#3 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Support/Collection.php(475): App\CronJobs\Tenant\CalibrationDueReminder->App\CronJobs\Tenant\{closure}(Object(App\Entities\Models\Product), 0)
#4 /Users/pusparaj/sources/andatech/calibration/app/CronJobs/Tenant/CalibrationDueReminder.php(178): Illuminate\Support\Collection->each(Object(Closure))
#5 [internal function]: App\CronJobs\Tenant\CalibrationDueReminder->__invoke()
#6 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(32): call_user_func_array(Array, Array)
#7 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(90): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}()
#8 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(34): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#9 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Container/Container.php(576): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array, NULL)
#10 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Console/Scheduling/CallbackEvent.php(75): Illuminate\Container\Container->call(Array, Array)
#11 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php(111): Illuminate\Console\Scheduling\CallbackEvent->run(Object(Illuminate\Foundation\Application))
#12 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php(75): Illuminate\Console\Scheduling\ScheduleRunCommand->runEvent(Object(Illuminate\Console\Scheduling\CallbackEvent))
#13 [internal function]: Illuminate\Console\Scheduling\ScheduleRunCommand->handle()
#14 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(32): call_user_func_array(Array, Array)
#15 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(90): Illuminate\Container\BoundMethod::Illuminate\Container\{closure}()
#16 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php(34): Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Array, Object(Closure))
#17 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Container/Container.php(576): Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), Array, Array, NULL)
#18 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Console/Command.php(183): Illuminate\Container\Container->call(Array)
#19 /Users/pusparaj/sources/andatech/calibration/vendor/symfony/console/Command/Command.php(255): Illuminate\Console\Command->execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Illuminate\Console\OutputStyle))
#20 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Console/Command.php(170): Symfony\Component\Console\Command\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Illuminate\Console\OutputStyle))
#21 /Users/pusparaj/sources/andatech/calibration/vendor/symfony/console/Application.php(915): Illuminate\Console\Command->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#22 /Users/pusparaj/sources/andatech/calibration/vendor/symfony/console/Application.php(272): Symfony\Component\Console\Application->doRunCommand(Object(Illuminate\Console\Scheduling\ScheduleRunCommand), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#23 /Users/pusparaj/sources/andatech/calibration/vendor/symfony/console/Application.php(148): Symfony\Component\Console\Application->doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#24 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Console/Application.php(90): Symfony\Component\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#25 /Users/pusparaj/sources/andatech/calibration/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php(133): Illuminate\Console\Application->run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#26 /Users/pusparaj/sources/andatech/calibration/artisan(37): Illuminate\Foundation\Console\Kernel->handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
#27 {main}  
RaazPuspa commented 4 years ago

Can any one suggest me some sort of solutions here?

Plytas commented 4 years ago

@RaazPuspa are the routes working when you visit them manually in your browser? If so then this might be an issue of routes being registered once on boot. Try calling (new \Hyn\Tenancy\Providers\Tenants\RouteProvider(app()))->boot(); after you identify your tenant.

RaazPuspa commented 4 years ago

I found a "hack" to make it work by defining the copy of route inside default routes/web.php file. I don't know if this is how it is expected to work, but at this moment its working fine for us.

Regarding your suggestion, I added (new RouteProvider(app()))->boot(); after app(Environment::class)->tenant($this->website);. Still the issue is same, if I remove route definition from default Laravel path.

ICYMI: I am trying to prepare route from a class method invoked by a cron job with Laravel scheduler (once a day).

Following is a screenshot of the route in working. The message is as expected for some random token string.

Screen Shot 2020-01-31 at 17 14 14

Plytas commented 4 years ago

Would you be able to share the cron job and how you are calling it?

RaazPuspa commented 4 years ago

This is the content of schedule() inside App\Console\Kernel

app(Website::class)->whereHas('activeCustomer')
    ->each(function ($website) use ($schedule) {
        $schedule
            ->call(new SendCalibrationDueNotification($website))
            ->name('SendCalibrationDueNotification')
            ->timezone($this->scheduleTimezone())
            ->withoutOverlapping()
            ->runInBackground()
            ->dailyAt('03:00');

        $schedule
            ->call(new SendCalibrationOverDueNotification($website))
            ->name('SendCalibrationOverDueNotification')
            ->timezone($this->scheduleTimezone())
            ->withoutOverlapping()
            ->runInBackground()
            ->dailyAt('03:30');
    });

This is how the __invoke method inside each of the two classes start:

public function __invoke()
{
    app(Environment::class)->tenant($this->website);
    // method logic
}
Plytas commented 4 years ago

Just to confirm it, could you try booting the route provider after identifying the tenant and then immediately try to resolve the route?

RaazPuspa commented 4 years ago

Added the following contents just below app(Environment::class)->tenant($this->website);

(new \Hyn\Tenancy\Providers\Tenants\RouteProvider(app()))->boot();
logger(route('profile.unsubscribe.show', ['t' => str_random(64)]));

My crontab entry for the project:

* * * * * cd $HOME/Documents/projects/andatech/calibration && php artisan schedule:run >> /dev/null 2>&1

In the image below, the successful logs are when routes are defined at routes/web.php and the exception is caught after it had been removed (keeping a single copy in routes/tenants.php.

Screen Shot 2020-01-31 at 19 23 31
ArlonAntonius commented 4 years ago

Could you dd the ->website() from the environment as well? Think that's actually empty and that should be the issue.

Also, please double-check, triggering that function might fix your issue.

RaazPuspa commented 4 years ago

@ArlonAntonius, yes you got it right. The website from the environment is null. But, triggering the function call does not help me.

As in the image below, the dd method throws and exception Route [profile.unsubscribe.show] not defined. when route definition is set only within tenants/routes.php.

Screen Shot 2020-03-05 at 11 51 37
ArlonAntonius commented 4 years ago

Try setting the hostname, I expect that should solve the problem :)

RaazPuspa commented 4 years ago

Hey @ArlonAntonius, I tried to set the environment hostname using ->hostname() API method from Environment class. Is that the correct way? if yes, that's not helping me at all. if not, what is the way to set the hostname?

app(Environment::class)->tenant($this->website);
app(Environment::class)->hostname(
    $this->website->hostnames()->latest()->first()
);

Using that approach, there are no any issues logged but the intended task did nothing. The cron job is assumed to send 48 emails (today, Mon Aug 31, 2020). When I didn't set the hostname, there are 48 error log entries for route not found issue but after setting it nothing happened at all.