I am running a Laravel 10 with passport 11 hyn multitenant application. It's a legacy application that is a few years old and has worked very well until I upgraded my Laravel from 9 to 10 and passport from 10 to 11. I when I try to create a tenancy, everything works until the "$company = Tenant::create($request);" which also works quite well up to a point. It creates an entry into the tency.hostnames and tenancy.websites tables respectively and also creates a custom database for the new tenancy. How ever, It attempts to recreate all the system tables that resides in the tenant database. These migration files are used by the entire app and are located in migrations folder whiles the others that make up each tenncies's database are in migrations/tenant folder. I do'nt really undersatand why creating a new tenancy is trying to recreate the tables in the tenancy database which obviously already exists. I get this error when this happens
SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'websites' already exists (Connection: system, SQL: create table
websites
(id
bigint unsigned not null auto_increment primary key,uuid
varchar(191) not null,created_at
timestamp null,updated_at
timestamp null,deleted_at
timestamp null) default character set utf8mb4 collate 'utf8mb4_unicode_ci')
this is my register function
public function register(Request $request)
{
$facility_name = Hostname::where('tenant_facility_name', $request->tenant_facility_name)->first();
$checkEmail = Hostname::where('email', $request->email)-`>`first();
$fqdn = Hostname::where('subdomain', $request->fqdn)->first();
if($facility_name){
return response(['message' => 'A facility with this name already exist'], 409);
}
if ($checkEmail) {
return response(['message' => 'A facility with this email already exist'], 409);
}
if ($fqdn) {
return response(['message' => 'A facility with this name already exist'], 409);
}
// Validate the incoming request
$this->validator($request->all())->validate();
try {
// create Tenant Account
$company = Tenant::create($request);
} catch(\Exception $e) {
return response($e->getMessage(), 405);
}
event(new Registered($user = $this->create($request->all())));
// set trial period for new account without actual subscription for 30 days
// $host = HostnameModel::where('fqdn', $company->hostname->fqdn)->first();
// $host->trial_ends_at = now()->addDays(30);
// $host->save();
$this->createTrialPeriod($request->email);
// Function to send email
$name = $request->othernames . ' ' . $request->surname;
$email = $request->email;
$facilityName = $request->tenant_facility_name;
// Send email to new account admin
try {
Mail::to($email)->send(new TenantAccountCreation($name, $email, $facilityName));
} catch(\Exception $e) {
// sending 200 so that the registration continues without queing email to new account user
return response($e->getMessage(), 200);
}
return response()->json(['message' => 'Account Has Been Created Successfully'], 200);
}
and this is my Tenant.php file
<?php
namespace App\Models\General;
use Illuminate\Support\Facades\Artisan;
use Hyn\Tenancy\Environment;
use Hyn\Tenancy\Models\Hostname;
use Hyn\Tenancy\Models\Website;
use Illuminate\Support\Str;
use Hyn\Tenancy\Contracts\Repositories\HostnameRepository;
use Hyn\Tenancy\Contracts\Repositories\WebsiteRepository;
class Tenant
{
public function __construct(Website $website = null, Hostname $hostname = null)
{
$this->website = $website ?? $sub->website;
$this->hostname = $hostname ?? $sub->websites->hostnames->first();
}
public function delete()
{
app(HostnameRepository::class)->delete($this->hostname, true);
app(WebsiteRepository::class)->delete($this->website, true);
}
public static function create($request): Tenant
{
// Create New Website
$website = new Website;
// Attached the fqdn to a random string of 5
$website->uuid = $request->fqdn.'_'.Str::random(5);
app(WebsiteRepository::class)->create($website);
// associate the website with a hostname
$hostname = new Hostname;
$hostname->subdomain = $request->fqdn;
$hostname->email = $request->email;
$hostname->currency = $request->currency;
// Add the facility name to hostname table
$hostname->tenant_facility_name = $request->tenant_facility_name;
// merge
// $request->merge(['fqdn' => $request->fqdn . '.' . env('APP_URL_BASE')]);
$fqdn = $request->fqdn . '.' .config('services.environment');
$hostname->fqdn = $fqdn;
// $hostname->fqdn = $request->fqdn;
app(HostnameRepository::class)->attach($hostname, $website);
// make hostname current
app(Environment::class)->tenant($website);
Artisan::call('passport:install');
return new Tenant($website, $hostname);
}
public static function tenantExists($name)
{
return Hostname::where('fqdn', $name)->exists();
}
}
and this is my database.php file
<?php
use Illuminate\Support\Str;
return [
/*
|--------------------------------------------------------------------------
| Default Database Connection Name
|--------------------------------------------------------------------------
|
| Here you may specify which of the database connections below you wish
| to use as your default connection for all database work. Of course
| you may use many connections at once using the Database library.
|
*/
'default' => env('DB_CONNECTION', 'mysql'),
/*
|--------------------------------------------------------------------------
| Database Connections
|--------------------------------------------------------------------------
|
| Here are each of the database connections setup for your application.
| Of course, examples of configuring each database platform that is
| supported by Laravel is shown below to make development simple.
|
|
| All database work in Laravel is done through the PHP PDO facilities
| so make sure you have the driver for your particular database of
| choice installed on your machine before you begin development.
|
*/
'connections' => [
'system' => [
'driver' => 'mysql',
'host' => env('TENANCY_HOST', '127.0.0.1'),
'port' => env('TENANCY_PORT', '3306'),
'database' => env('TENANCY_DATABASE', 'tenancy'),
'username' => env('TENANCY_USERNAME', 'User1'),
'password' => env('TENANCY_PASSWORD', 'mypassword'),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'strict' => true,
'engine' => null,
],
'sqlite' => [
'driver' => 'sqlite',
'url' => env('DATABASE_URL'),
'database' => env('DB_DATABASE', database_path('database.sqlite')),
'prefix' => '',
'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true),
],
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
'pgsql' => [
'driver' => 'pgsql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '5432'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'schema' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [
'driver' => 'sqlsrv',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', 'localhost'),
'port' => env('DB_PORT', '1433'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
],
],
/*
|--------------------------------------------------------------------------
| Migration Repository Table
|--------------------------------------------------------------------------
|
| This table keeps track of all the migrations that have already run for
| your application. Using this information, we can determine which of
| the migrations on disk haven't actually been run in the database.
|
*/
'migrations' => 'migrations',
/*
|--------------------------------------------------------------------------
| Redis Databases
|--------------------------------------------------------------------------
|
| Redis is an open source, fast, and advanced key-value store that also
| provides a richer body of commands than a typical key-value system
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
*/
'redis' => [
'client' => env('REDIS_CLIENT', 'predis'),
'options' => [
'cluster' => env('REDIS_CLUSTER', 'predis'),
'prefix' => Str::slug(env('APP_NAME', 'laravel'), '_').'_database_',
],
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_DB', 0),
],
'cache' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => env('REDIS_CACHE_DB', 1),
],
],
];
updating the create function in my Tenant.php to:
public static function create($request): Tenant
{
try {
// Create New Website
\Log::info('creating new website info');
$website = new Website;
// Attached the fqdn to a random string of 5
$website->uuid = $request->fqdn . '_' . Str::random(5);
app(WebsiteRepository::class)->create($website);
\Log::info('website created');
// Associate the website with a hostname
$hostname = new Hostname;
$hostname->subdomain = $request->fqdn;
$hostname->email = $request->email;
$hostname->currency = $request->currency;
$hostname->tenant_facility_name = $request->tenant_facility_name;
$fqdn = $request->fqdn . '.' . config('services.environment');
$hostname->fqdn = $fqdn;
app(HostnameRepository::class)->attach($hostname, $website);
\Log::info('creating new hostname');
// Make hostname current
app(Environment::class)->tenant($website);
\Log::info('make tenancy current');
// Run Passport install
\Log::info('starting passport install');
Artisan::call('passport:install');
\Log::info('passport installed');
return new Tenant($website, $hostname);
} catch (\Exception $e) {
\Log::error('Error creating tenant: ' . $e->getMessage());
throw $e;
}
}
this is what is logged in the laravel log file
[2024-06-21 00:16:01] local.INFO: creating new website info [2024-06-21 00:17:15] local.INFO: website created [2024-06-21 00:17:15] local.INFO: creating new hostname [2024-06-21 00:17:15] local.INFO: make tenancy current [2024-06-21 00:17:15] local.INFO: starting passport install [2024-06-21 00:17:16] local.ERROR: Error creating tenant: SQLSTATE[42S01]: Base table or view already exists: 1050 Table 'websites' already exists (Connection: system, SQL: create table websit
which clearly proves that the line
Artisan::call('passport:install')
is responsible for the error.
Question is : Why is this,
Artisan::call('passport:install')
command trying to run all migration files in the application all over again?
I was able to resolve my issues after reinstalling laravel/passport all over again.