A Suprnova application can run on a database a Laravel 13 application created, and it can run next to that Laravel application on the same database. This chapter lists the tables Suprnova shares with Laravel and the layout each one takes, explains how an existing Suprnova application upgrades its own tables to those layouts, and describes the one setting an application turns on when a Laravel application uses the database at the same time.
The tables Suprnova shares with Laravel
Every table the framework or a new scaffold creates under a name that
Laravel, a first-party Laravel package or spatie/laravel-permission also
uses takes that table's layout: the same columns, types, nullability, keys
and indexes that Laravel's or the package's migration creates. There is one
difference. On MySQL, a table Suprnova creates holds its time columns as
DATETIME where Laravel's migration says TIMESTAMP, because TIMESTAMP
refuses any time after 2038-01-19.
| Table | Layout | Created by |
|---|---|---|
jobs |
Laravel's jobs |
queue::migrations::CreateJobsTable |
job_batches |
Laravel's job_batches |
queue::migrations::CreateJobBatchesTable |
failed_jobs |
Laravel's failed_jobs |
queue::migrations::CreateFailedJobsTable |
sessions |
the Laravel 13 skeleton's sessions |
session::migrations::CreateSessionsTable |
notifications |
Laravel's notifications |
notifications::migrations::CreateNotificationsTable |
users |
the Laravel 13 skeleton's users |
the scaffold's create_users_table |
features |
laravel/pennant's features |
features::migrations::CreateFeaturesTable |
roles, permissions |
spatie/laravel-permission's | rbac::migrations::CreateRbacTables |
model_has_roles, model_has_permissions, role_has_permissions |
spatie/laravel-permission's | rbac::migrations::CreateRbacTables |
Each migration creates its table when it is missing, and leaves a table Laravel or a package already created exactly as it is. It never alters or indexes Laravel's table.
Some columns follow the key type of your models, as Laravel's do:
notifications.notifiable_idfollowsNOTIFICATIONS_MORPH_KEY:int(the default) for Laravel'smorphs,uuidforuuidMorphs, orulidforulidMorphs.sessions.user_idfollows the key of the default guard's user model. PassSessionUserKey::Integer(Laravel'sforeignId),SessionUserKey::Uuid(foreignUuid) orSessionUserKey::Ulid(foreignUlid) toCreateSessionsTable::new.model_has_roles.model_idandmodel_has_permissions.model_idfollowRBAC_MODEL_KEY:int,uuidorulid.
Data the framework keeps that Laravel's layout has no column for lives in tables of its own, under names Laravel does not use:
| Framework table | Holds |
|---|---|
suprnova_jobs_reservations |
the token and deadline of each job a worker reserved |
job_batch_settlements |
which jobs of a batch settled, so a redelivered job is not counted twice |
suprnova_feature_details |
a flag's description and the actor who last changed it |
suprnova_role_details, suprnova_permission_details |
the display names an earlier release kept |
cache, cache_locks, password_reset_tokens and the tables of the other
first-party packages share no name with a framework table, and Suprnova
leaves them alone.
What each table holds
jobs: a Suprnova job'spayloadis its envelope with Laravel'sdisplayNameanduuidadded. Suprnova's workers only reserve rows Suprnova wrote, so a Suprnova worker never runs a Laravel job.failed_jobs: each failed job gets a freshuuid. Itsconnectionissuprnova:followed by the Suprnova connection name, a form no connection in Laravel's defaultconfig/queue.phptakes.job_batches: the counters are kept in Laravel's columns, andoptionsholds a PHP-serialized array that Laravel's batch repository reads.sessions: thepayloadis base64-encoded JSON, as the Laravel 13 skeleton serializes it. Session garbage collection only deletes rows Suprnova wrote.features: a flag is stored as Pennant stores it. The global scope is__laravel_null, and a user's scope isApp\Models\User|<id>.FEATURES_USER_SCOPEnames another class or morph alias. A stored value other thanfalsereads as enabled, as Pennant'sactive()decides.users: Suprnova never writesremember_token, so Laravel's own remember-me keeps working. A password reset may clear it, as Laravel's documented reset does.
Registering the migrations
List the framework's migrations in your Migrator, after your own:
use ;
use ;
use ;
use ;
use ;
use ;
;
Then run suprnova migrate. On a database Laravel created, the migrations
add only the framework's own tables listed above.
Upgrading an existing Suprnova application
An application an earlier release created has its own layouts under these
names. The same migrations upgrade it in place when suprnova migrate
runs:
failed_jobsis reshaped into Laravel's layout. Each failed job keeps its id as itsuuid, soqueue:failed,queue:retry <id>andqueue:forget <id>still find it.jobsis moved into Laravel's layout. A queued job stays queued, a delayed job stays delayed, and a reserved job stays reserved by the same token until the same deadline, so the worker that holds it settles it and no job runs twice.job_batchesis moved into Laravel's layout, and each batch keeps its total, pending and failed counts.sessionsis moved into the skeleton's layout. Every session keeps its id, its user, its data and its CSRF token, so nobody is signed out.notifications,features,rolesandpermissionsare reshaped into the layouts above, with every notification, flag, role, permission and assignment. The earliermodel_roles,model_permissionsandrole_permissionsare moved into spatie's tables and dropped.
Each table is copied aside as suprnova_earlier_<table> before it is
reshaped, and its rows move in chunks. A migrate that stops part way
starts again where it stopped on the next run, and moves no row twice.
Every other table an earlier release created keeps working unchanged.
The framework never reshapes users: it is your application's own
migration. This migration moves an earlier scaffold's users into the
Laravel 13 skeleton's layout, keeping every row and id. If your users has
columns of your own, add them to Schema::create and to both column lists.
use *;
use DbBackend;
use Schema;
/// Moves the `users` table an earlier scaffold created into the Laravel 13
/// skeleton's layout. Every row keeps its id. A run that stops part way
/// starts again from `users_earlier` on the next `migrate`.
;
After it runs, the scaffold's User model reads id into a u64, and its
timestamps into Option<DateTime<Utc>>.
Sharing the database with a running Laravel application
When a Laravel application uses the database at the same time, turn on one setting:
LARAVEL_SHARED_DATABASE=true
or call LaravelDatabase::share(true) in your bootstrap. With the setting
on:
- Every password hash the framework and Magnetar write is a
$2y$bcrypt hash, which Laravel's hasher accepts withHASH_VERIFY=true. A valid sign-in rewrites a$2b$or Argon2id hash as$2y$, at the configured bcrypt cost or at the stored hash's cost when that is higher, and Magnetar no longer upgrades bcrypt hashes to Argon2id. So Laravel signs in every user Suprnova signed in, registered or reset. If Suprnova can't write the$2y$hash, the sign-in fails with that error, because Laravel couldn't sign that user in. The stored hash stays as it was, and the next sign-in tries the rewrite again. - A job queued with default settings is stored on the queue
suprnovainstead ofdefault. Laravel'sdatabaseconnection readsdefault, so Laravel's worker never reserves a Suprnova job. Inside Suprnova the queue keeps its name: a job routed todefaultand a worker started with--queue=defaultboth use the storedsuprnova.
The setting gives up two things the framework does by default:
- Argon2id. Without the setting, Magnetar upgrades a bcrypt hash to Argon2id on a valid sign-in. With it, every hash stays bcrypt, which judges a password by its first 72 bytes.
- The stored queue name
default. A tool that reads thejobstable directly, Laravel'squeue:monitorincluded, seessuprnovafor Suprnova's unrouted jobs.
Without the setting, $2y$ hashes Laravel wrote still verify through
Auth::attempt and Magnetar, passwords of 72 bytes and longer included.
Laravel's queue:retry and Suprnova's failed jobs
Laravel's queue:retry cannot retry a failed job Suprnova wrote, because
Laravel has no queue connection named suprnova:<connection>. So
queue:retry all stops at the first failed job Suprnova wrote. Run
queue:retry --queue=<queue> with a queue only Laravel uses, and Laravel
retries its own failed jobs. Retry Suprnova's failed jobs with Suprnova's
queue:retry.
Polymorphic types and morph aliases
Laravel stores a PHP class name, such as App\Models\Post, in a
polymorphic *_type column. Declare it as the model's morph_type. A
Laravel application that adopted Relation::morphMap late holds both the
class name and the alias, such as post, for one model. Register the
alias with morph_aliases:
use model;
Reads accept every name: a MorphTo loads the post a row names by either
form, and a MorphOne or MorphMany, direct or eager, matches rows that
hold either. Writes store the morph_type. The RBAC stores a model's
morph_type as spatie's model_type, so declare morph_type = "App\\Models\\User" on your user model when spatie's tables hold Laravel's
assignments.
Unsigned keys on MySQL
Your new tables use unsigned IDs on MySQL by default. You use id() and
foreign_id() to match Laravel's keys, and timestamps() to create
nullable timestamps. A new scaffold's users.id is already unsigned.
Declare unsigned keys as u64 in your models.
If your existing tables use signed IDs, keep that shape in new migrations with the opt-out:
[package.metadata.suprnova.schema]
unsigned_ids = false
#[suprnova::main] installs it before migrations. Without that entry point,
you call suprnova::schema::Schema::set_unsigned_ids(false) before your first
migration. You keep signed columns on Postgres and SQLite with either
setting. See Migrations.
What Suprnova does not support
- Sanctum tokens Laravel issued. Suprnova does not read
personal_access_tokens. - Columns Laravel's encrypter wrote, such as Fortify's two-factor secrets
and recovery codes, and any column with an
encryptedcast. Suprnova cannot decrypt them. - Remember-me cookies Laravel issued. A user signed in to Laravel signs in to Suprnova separately.
- A Laravel application or a database server whose time zone is not UTC. Suprnova reads every stored time as UTC.
Why Suprnova diverges
Laravel owns each of these tables in its own application, and a second
application writing to them is not a case Laravel plans for. Suprnova takes
Laravel's layouts so both applications read the same rows, and it keeps
what Laravel's layouts cannot hold in tables of its own instead of adding
columns to Laravel's tables. A failed job's connection names a connection
Laravel does not have, so Laravel's queue:retry refuses it instead of
running a job Laravel cannot decode. The setting is off by default because
$2b$, Argon2id and the queue name default are the better choices for an
application that does not share its database.
Next
- Database - connections, transactions and the query builder
- Migrations - the schema builder and
unsigned_ids - Queues - workers, failed jobs and batches
- From Laravel - how Laravel habits map to Suprnova
