multi-tenant
multi-tenant copied to clipboard
URL Not Generating Properly
Description
When running an artisan job that loops through all web sites - the URL within the results (an email) is set to localhost. I do have the 'update-app-url' set to true.
$repository = app(WebsiteRepository::class);
$websites = $repository->all();
//dd( $websites );
foreach( $websites as $siter ) {
$environment = \App::make(\Hyn\Tenancy\Environment::class);
$environment->tenant($siter);
// Processes the message etc
//LOOPS FOR USERS
\Mail::to($recipient->email)->send(new DailyEmail($objDailyMail));
// END LOOP
}
It is using a Laravel Mailer to generate an email and then 'jdavidbakr/mail-tracker' (2.1) to replace links and add a tracking pixel to the email.
Actual behavior
Sets the tracking URL to http://localhost/ throughout the entire email as well as the tracking pixel and 'view' event on that email never fires as a result
Expected behavior
Should use the app.url to the current active tenant
Information
- hyn/multi-tenant version: 5.3.0
- laravel version: 5.6
- database driver and version: MySQL
- webserver software and version: Apache
- php version: 7
tenancy.php config
<?php
/*
* This file is part of the hyn/multi-tenant package.
*
* (c) Daniël Klabbers <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @see https://laravel-tenancy.com
* @see https://github.com/hyn/multi-tenant
*/
use Hyn\Tenancy\Database\Connection;
return [
'models' => [
/**
* Specify different models to be used for the global, system database
* connection. These are also used in their relationships. Models
* used have to implement their respective contracts and
* either extend the SystemModel or use the trait
* UsesSystemConnection.
*/
// Must implement \Hyn\Tenancy\Contracts\Customer
'customer' => \Hyn\Tenancy\Models\Customer::class,
// Must implement \Hyn\Tenancy\Contracts\Hostname
'hostname' => \Hyn\Tenancy\Models\Hostname::class,
// Must implement \Hyn\Tenancy\Contracts\Website
'website' => \Hyn\Tenancy\Models\Website::class
],
/**
* The package middleware. Removing a middleware here will disable it.
* You can of course extend/replace them or add your own.
*/
'middleware' => [
// The eager identification middleware.
\Hyn\Tenancy\Middleware\EagerIdentification::class,
// The hostname actions middleware (redirects, https, maintenance).
\Hyn\Tenancy\Middleware\HostnameActions::class,
],
'website' => [
/**
* Each website has a short random hash that identifies this entity
* to the application. By default this id is randomized and fully
* auto-generated. In case you want to force your own logic for
* when you need to have a better overview of the complete
* tenant folder structure, disable this and implement
* your own id generation logic.
*/
'disable-random-id' => false,
/**
* The random Id generator is responsible for creating the hash as mentioned
* above. You can override what generator to use by modifying this value
* in the configuration.
*
* @warn This won't work if disable-random-id is true.
*/
'random-id-generator' => Hyn\Tenancy\Generators\Uuid\ShaGenerator::class,
/**
* Enable this flag in case you're using a driver that does not support
* database username or database name with a length of more than 32 characters.
*
* This should be enabled for MySQL, but not for MariaDB and PostgreSQL.
*/
'uuid-limit-length-to-32' => env('LIMIT_UUID_LENGTH_32', true),
/**
* Specify the disk you configured in the filesystems.php file where to store
* the tenant specific files, including media, packages, routes and other
* files for this particular website.
*
* @info If not set, will revert to the default filesystem.
* @info If set to false will disable all tenant specific filesystem auto magic
* like the config, vendor overrides.
*/
'disk' => null,
/**
* Automatically generate a tenant directory based on the random id of the
* website. Uses the above disk to store files to override system-wide
* files.
*
* @info set to false to disable.
*/
'auto-create-tenant-directory' => true,
/**
* Automatically rename the tenant directory when the random id of the
* website changes. This should not be too common, but in case it happens
* we automatically want to move files accordingly.
*
* @info set to false to disable.
*/
'auto-rename-tenant-directory' => true,
/**
* Automatically deletes the tenant specific directory and all files
* contained within.
*
* @see
* @info set to true to enable.
*/
//'auto-delete-tenant-directory' => false,
'auto-delete-tenant-directory' => env('AUTO_DELETE_TENANT_DIRECTORY', false),
/**
* Time to cache websites in minutes. Set to false to disable.
*/
'cache' => 10,
],
'hostname' => [
/**
* If you want the multi tenant application to fall back to a default
* hostname/website in case the requested hostname was not found
* in the database, complete in detail the default hostname.
*
* @warn this must be a FQDN, these have no protocol or path!
*/
'default' => env('TENANCY_DEFAULT_HOSTNAME'),
/**
* The package is able to identify the requested hostname by itself,
* disable to get full control (and responsibility) over hostname
* identification. The hostname identification is needed to
* set a specific website as currently active.
*
* @see src/Jobs/HostnameIdentification.php
*/
'auto-identification' => env('TENANCY_AUTO_HOSTNAME_IDENTIFICATION', true),
/**
* In case you want to have the tenancy environment set up early,
* enable this flag. This will run the tenant identification
* inside a middleware. This will eager load tenancy.
*
* A good use case is when you have set "tenant" as the default
* database connection.
*/
'early-identification' => env('TENANCY_EARLY_IDENTIFICATION', true),
/**
* Abort application execution in case no hostname was identified. This will throw a
* 404 not found in case the tenant hostname was not resolved.
*/
'abort-without-identified-hostname' => true,
/**
* Time to cache hostnames in minutes. Set to false to disable.
*/
'cache' => 10,
/**
* Automatically update the app.url configured inside Laravel to match
* the tenant FQDN whenever a hostname/tenant was identified.
*
* This will resolve issues with password reset mails etc using the
* correct domain.
*/
'update-app-url' => true,
],
'db' => [
/**
* The default connection to use; this overrules the Laravel database.default
* configuration setting. In Laravel this is normally configured to 'mysql'.
* You can set a environment variable to override the default database
* connection to - for instance - the tenant connection 'tenant'.
*/
'default' => env('TENANCY_DEFAULT_CONNECTION'),
/**
* Used to give names to the system and tenant database connections. By
* default we configure 'system' and 'tenant'. The tenant connection
* is set up automatically by this package.
*
* @see src/Database/Connection.php
* @var system-connection-name The database connection name to use for the global/system database.
* @var tenant-connection-name The database connection name to use for the tenant database.
*/
'system-connection-name' => env('TENANCY_SYSTEM_CONNECTION_NAME', Connection::DEFAULT_SYSTEM_NAME),
'tenant-connection-name' => env('TENANCY_TENANT_CONNECTION_NAME', Connection::DEFAULT_TENANT_NAME),
/**
* The tenant division mode specifies to what database websites will be
* connecting. The default setup is to use a new database per tenant.
* In case you prefer to use the same database with a table prefix,
* set the mode to 'prefix'.
*
* @see src/Database/Connection.php
*/
'tenant-division-mode' => env('TENANCY_DATABASE_DIVISION_MODE', 'database'),
/**
* The database password generator takes care of creating a valid hashed
* string used for tenants to connect to the specific database. Do
* note that this will only work in 'division modes' that set up
* a connection to a separate database.
*/
'password-generator' => Hyn\Tenancy\Generators\Database\DefaultPasswordGenerator::class,
/**
* The tenant migrations to be run during creation of a tenant. Specify a directory
* to run the migrations from. If specified these migrations will be executed
* whenever a new tenant is created.
*
* @info set to false to disable auto migrating.
*
* @warn this has to be an absolute path, feel free to use helper methods like
* base_path() or database_path() to set this up.
*/
'tenant-migrations-path' => database_path('migrations/tenant'),
/**
* The default Seeder class used on newly created databases and while
* running artisan commands that fire seeding.
*
* @info requires tenant-migrations-path in order to seed newly created websites.
* @info seeds stored in `database/seeds/tenants` need to be configured in your composer.json classmap.
*
* @warn specify a valid fully qualified class name.
* @example App\Seeders\AdminSeeder::class
*/
//'tenant-seed-class' => false,
'tenant-seed-class' => App\Seeder\TenantSeeder::class,
/**
* Automatically generate a tenant database based on the random id of the
* website.
*
* @info set to false to disable.
*/
'auto-create-tenant-database' => true,
/**
* Automatically generate the user needed to access the database.
*
* @info Useful in case you use root or another predefined user to access the
* tenant database.
* @info Only creates in case tenant databases are set to be created.
*
* @info set to false to disable.
*/
'auto-create-tenant-database-user' => true,
/**
* Automatically rename the tenant database when the random id of the
* website changes. This should not be too common, but in case it happens
* we automatically want to move databases accordingly.
*
* @info set to false to disable.
*/
'auto-rename-tenant-database' => true,
/**
* Automatically deletes the tenant specific database and all data
* contained within.
*
* @info set to true to enable.
*/
'auto-delete-tenant-database' => false, //env('TENANCY_DATABASE_AUTO_DELETE', false),
/**
* Automatically delete the user needed to access the tenant database.
*
* @info Set to false to disable.
* @info Only deletes in case tenant database is set to be deleted.
*/
'auto-delete-tenant-database-user' => env('TENANCY_DATABASE_AUTO_DELETE_USER', false),
/**
* Define a list of classes that you wish to force onto the tenant or system connection.
* The connection will be forced when the Model has booted.
*
* @info Useful for overriding the connection of third party packages.
*/
'force-tenant-connection-of-models' => [
// \App\User::class
],
'force-system-connection-of-models' => [
// \App\User::class
],
],
/**
* Global tenant specific routes.
* Making it easier to distinguish between landing and tenant routing.
*
* @info only works with `tenancy.hostname.auto-identification` or identification happening
* before the application is booted (eg inside middleware or the register method of
* service providers).
*/
'routes' => [
/**
* Routes file to load whenever a tenant was identified.
*
* @info Set to false or null to disable.
*/
'path' => base_path('routes/tenants.php'),
/**
* Set to true to flush all global routes before setting the routes from the
* tenants.php routes file.
*/
'replace-global' => false,
],
/**
* Folders configuration specific per tenant.
* The following section relates to configuration to files inside the tenancy/<uuid>
* tenant directory.
*/
'folders' => [
'config' => [
/**
* Merge configuration files from the config directory
* inside the tenant directory with the global configuration files.
*/
'enabled' => true,
/**
* List of configuration files to ignore, preventing override of crucial
* application configurations.
*/
'blacklist' => ['database', 'tenancy', 'webserver'],
],
'routes' => [
/**
* Allows adding and overriding URL routes inside the tenant directory.
*/
'enabled' => true,
/**
* Prefix all tenant routes.
*/
'prefix' => null,
],
'trans' => [
/**
* Allows reading translation files from a trans directory inside
* the tenant directory.
*/
'enabled' => true,
/**
* Will override the global translations with the tenant translations.
* This is done by overriding the laravel default translator with the new path.
*/
'override-global' => true,
/**
* In case you disabled global override, specify a namespace here to load the
* tenant translation files with.
*/
'namespace' => 'tenant',
],
'vendor' => [
/**
* Allows using a custom vendor (composer driven) folder inside
* the tenant directory.
*/
'enabled' => true,
],
'media' => [
/**
* Mounts the assets directory with (static) files for public use.
*/
'enabled' => true,
],
'views' => [
/**
* Adds the vendor directory of the tenant inside the application.
*/
'enabled' => true,
/**
* Specify a namespace to use with which to load the views.
*
* @eg setting `tenant` will allow you to use `tenant::some.blade.php`
* @info set to null to add to the global namespace.
*/
'namespace' => null,
/**
* If `namespace` is set to null (thus using the global namespace)
* make it override the global views. Disable by setting to false.
*/
'override-global' => true,
]
]
];
webserver.php config
Error log
DailyEmail most likely needs to implement the TenantAwareJob trait, see: https://laravel-tenancy.com/docs/hyn/5.3/queues
I tried a few things - including your recommendation. Not luck yet.
Curious - I replaced the method that I am using with the following - just to see if the url('/') is working properly up to that point.
$repository = app(WebsiteRepository::class);
$websites = $repository->all();
//dd( $websites ); // THIS WORKS I am given the first web site details
foreach( $websites as $siter ) {
$environment = \App::make(\Hyn\Tenancy\Environment::class);
$environment->tenant($siter);
dd( url('/'), 'There');
}
Which very strangely returns:
"http://localhost"
"There"
From what I understand I should be getting the URL to the current (first iteration) web site's url.
What am I doing wrong here?
Thanks, Daniel
Humor me and try using config('app.url')
instead of url
Hi @bkintanar thanks for chiming in here..
I gave that a try and am returned the same 'localhost' value.
D
Ok. what does your TENANCY_AUTO_HOSTNAME_IDENTIFICATION
in your .env
looks like?
What makes it a little more confusing is that we are using the tenant.database at that point - when I dump a value from the db - it is using the first web sites database properly.
Problem just seems to be the URL.
To your other question - I do not have that set in .env rather setting the default in the config/tenancy.php
'auto-identification' => env('TENANCY_AUTO_HOSTNAME_IDENTIFICATION', true),
D
Well the thing is that the setting of the update-app-url
happens in the HostnameActions
middleware, and only if auto-identification is enabled. Which you claim it's set to true.
For argument's sake I set it in .env here are the related entries
TENANCY_DATABASE_AUTO_DELETE=true
TENANCY_DATABASE_AUTO_DELETE_USER=true
TENANCY_DEFAULT_CONNECTION=system
AUTO_DELETE_TENANT_DIRECTORY=true
TENANCY_EARLY_IDENTIFICATION=true
TENANCY_AUTO_HOSTNAME_IDENTIFICATION=true
Still returning localhost...
Looks like this is a bug then. HostnameActions is a middleware so it's only executed when an http request is made.
@luceos ?
Could it be the loop? I hacked the src (recommended on an issue from some time ago - to be able to loop through all sites (in order to run a job on each site)
$repository = app(WebsiteRepository::class);
$websites = $repository->all();
Which does work to loop all sites. Then using the following to set the environment etc
foreach( $websites as $siter ) {
$environment = \App::make(\Hyn\Tenancy\Environment::class);
$environment->tenant($siter);
// This works to give me the database (and Storage I believe)
}
I don't believe it's the loop since you're able to get the correct hostname out of it. I believe it's (app.url) not being set at all.
Just to further help any debug here -- I made this change
$repository = app(WebsiteRepository::class);
$websites = $repository->all();
//dd( $websites );
foreach( $websites as $siter ) {
$environment = \App::make(\Hyn\Tenancy\Environment::class);
$environment->tenant($siter);
// Get current Hostname
$hostname = app(\Hyn\Tenancy\Environment::class)->hostname();
// Get FQDN (Fully-Qualified Domain Name) by current hostname
$fqdn = $hostname->fqdn;
dd( config('app.url'), $fqdn);
}
Strangely I am not getting the FQDN either. This is what is dumped
"http://localhost"
null
You cant get the hostname that way.
$env = app(Environment::class);
$env->tenant($tenant);
$hostname = $env->hostname();
Can you try this code above?
No change..
$repository = app(WebsiteRepository::class);
$websites = $repository->all();
//dd( $websites );
foreach( $websites as $siter ) {
//$environment = \App::make(\Hyn\Tenancy\Environment::class);
//$environment->tenant($siter);
$env = \App(\Hyn\Tenancy\Environment::class);
$env->tenant($siter);
$hostname = $env->hostname();
// Get current Hostname
dd( $hostname, 'bkintanar Host' );
// Get FQDN (Fully-Qualified Domain Name) by current hostname
$fqdn = $hostname->fqdn;
dd( config('app.url'), $fqdn);
}
Here is the dump
null
"bkintanar Host"
When I dd($siter) in the above loop...
Hyn\Tenancy\Models\Website {#8910
#connection: "system"
#table: null
#primaryKey: "id"
#keyType: "int"
+incrementing: true
#with: []
#withCount: []
#perPage: 15
+exists: true
+wasRecentlyCreated: false
#attributes: array:7 [
"id" => 22
"uuid" => "e2b798f1a01e43f1832d3bfb051298aa"
"customer_id" => 22
"created_at" => "2018-05-29 16:55:19"
"updated_at" => "2018-05-29 16:55:19"
"deleted_at" => null
"managed_by_database_connection" => null
]
#original: array:7 [
"id" => 22
"uuid" => "e2b798f1a01e43f1832d3bfb051298aa"
"customer_id" => 22
"created_at" => "2018-05-29 16:55:19"
"updated_at" => "2018-05-29 16:55:19"
"deleted_at" => null
"managed_by_database_connection" => null
]
#changes: []
#casts: []
#dates: []
#dateFormat: null
#appends: []
#dispatchesEvents: []
#observables: []
#relations: []
#touches: []
+timestamps: true
#hidden: []
#visible: []
#fillable: []
#guarded: array:1 [
0 => "*"
]
#forceDeleting: false
}
Is this normal?
D
For clarity sake - I only made the one change to the multi-tenant core.. (Hyn\Tenancy\Repositories\WebsiteRepository)
// Hack to enable looping through all websites (used to perform a job on all sites)
public function all() {
return $this->website->all();
}
I'm using version 5.1 in my project and I have the same problem. url('/')
returns always the value stored inside the .env
file under the APP_URL
key, even though I'm successfully switching tenants.
I finally managed to solve it by forcing Laravel's UrlGenerator
class to use a custom root url:
app(UrlGenerator::class)->forceRootUrl(request()->getScheme() . '://' . $hostname->fqdn);
@mauricius Thanks -- but I am not even able to access the $hostname - digging into that at the moment.
D
@mauricius interesting method call, we might be able to use that in the package as well.. Feel free to PR that if you like.
@luceos you’re right the comment on the other issue is related. Is this a bug or still a support problem or something I’m doing wrong?
Curious: what you get on command line interface when trying:
$ php artisan tenancy:run --tenant=1 route:list
I am having also problems with generating urls. Indeed, with 'URL::forceRootUrl()` the url can be set on command line interface by first finding the first() hostname of the tenant.
But generating an url for a named route doesn't work on cli, in a Command e.g.:
echo route('my.custom-route');
In a Controller the code does work perfectly!
Could you tell me if you can list your tenant routes on the command line?
Sorry, maybe I hijacked your issue a little with my question... That wasn't my intention. I will open a new issue myself.
+1