Download the PHP package splitstack/laravel-rome without Composer
On this page you can find all versions of the php package splitstack/laravel-rome. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package laravel-rome
Laravel Rome
Make database views first-class citizens in your Laravel app.
Laravel Rome gives you three complementary tools:
HasReadOnlyModetrait — add to any existing Eloquent model to get areadonly()guard on instances and afromView()fluent builder that queries directly from a dedicated DB view, wrapping every result in a write-protected proxy.ReadOnlyModel— a purpose-built Eloquent base class for models that live entirely on a view. Blocks direct writes, and optionally proxies mutations through a separate writable model. Works fantastically with Livewire components and anywhere else you want to hold view state in memory and write back through it without juggling two separate models.- Tooling — scaffold views with
make:dbview, regenerate them across connections withdbview:regen(multi-tenant aware), refresh materialized views via a queued job, and catch misuse at build time with bundled PHPStan rules.
Works with PostgreSQL and MySQL, with optional multi-tenant support.
Requirements
- PHP 8.2+
- Laravel 11+
- Database: PostgreSQL 9.3+ or MySQL 5.7+
Installation
Publishing
Publish the Laravel config:
Publish the PHPStan extension (optional — see PHPStan rules):
This copies extension.neon to phpstan-rome.neon at your project root, where you can customise it (e.g. set a non-standard db_views_path). Then include it in your phpstan.neon:
If you don't need to customise anything, you can skip publishing and include the extension directly from the vendor path:
Configuration
ReadOnlyModel
ReadOnlyModel is the Eloquent model you point at a database view. Reading from it works exactly like any other model. Writing directly with save() or delete() is intentionally blocked.
However, we provide a fluent way to proxy the underlying writable model for updates, and to access the underlying model instance for event dispatch or method calls.
In most well-architected apps you won't need the write-proxy at all. If you already know the record's ID — which is typical in a standard controller — reach for the writable model directly:
Product::find($id)->update(...). The proxy system pays off in situations where you're already holding aReadOnlyModelinstance and want to get a writable model from it without an extra query: a Livewire component whose state is the view model, a shared action/service class that expects a writable Eloquent model, or any place where splitting your state across two models would be awkward. If neither of those applies, skip$proxyToentirely.
Enabling proxy operations
Proxy operations (update, underlying, proxied) are off by default.
To use them, you should:
- Turn on the global switch — set
rome.proxy_enabled => trueinconfig/rome.phpor, if you don't need to publish, set the environment variableROME_PROXY_ENABLED=true. - Define
protected static $proxyTo— the writable Eloquent model to proxy to.
Calls to proxy operations throw a ProxiedModelException if either of these conditions is not met.
Create your first read-only view model
| Property | Type | Purpose |
|---|---|---|
$table |
string |
The view name in the database |
$proxyTo |
class-string\|null |
Writable model that owns the underlying table. Required to enable proxy operations |
$exclude |
string[] |
Columns stripped when hydrating via proxied() / underlying(false). See computed column warning |
$primaryKey |
string (default: id) |
The primary key column name. Override if your view's primary key is different. |
Primary Key configuration
ReadOnlyModel declares a non-incrementing primary key named id but makes no assumption about key type. Set $keyType, $incrementing, and any $casts on your model to match your actual key type. The model set in $proxyTo must use the same primary key name and type, since all proxy lookups use $this->getKey() to locate the record in the proxied table.
Make sure to override protected $primaryKey if your view's "primary key" is not id.
⚠ Warning - if your view does not have a unique column or set of columns that can serve as a primary key, you will not be able to use proxy operations. You can still use the model for querying and read operations, but updates through the proxy are not possible without a reliable way to identify the underlying record.
Proxy operations
update(array $attributes)
Looks up the matching record in the proxied model by primary key, updates it, then re-fetches and returns the view record so your computed columns are up to date.
Throws if no matching record exists in the proxied table.
save() and delete()
They always throw regardless of proxy configuration. This is a safety measure to prevent accidental overwrites through the view model. Use update() for updates, and call underlying()->delete() for deletions.
Accessing the underlying model
underlying(bool $forceFetch = true)
Returns a proxied model instance. The default is forceFetch: true — it queries the proxied model's table directly so all attributes are present and reflect the real stored values.
Pass forceFetch: false to hydrate in-memory from the view's attributes intersected with the proxied model's $fillable. No database query is made, but attributes not in $fillable are absent, and computed column values are taken from the view — see the warning below.
proxied()
Alias for underlying(forceFetch: false). Intended for cases where you need a writable model instance for event dispatch, method calls, or other non-persistence uses and can accept the in-memory hydration trade-offs.
Danger: computed columns that share a name with the underlying table column
If your view computes a value under the same column name that exists in the proxied model's table,
proxied()andunderlying(forceFetch: false)will silently hydrate the proxied instance with the computed value from the view, not the raw stored value. Callingsave()orupdate()on that instance can then write the computed value back to the table, corrupting data.Example: a view computes
total_priceasquantity * unit_price. Theorderstable also has a storedtotal_pricecolumn. Callingproxied()populates$order->total_pricewith the view-computed figure. If that instance is then updated, the computed figure overwrites the stored one.We provide a
php artisan rome:checkcommand that scans your view SQL for computed columns that share names with the proxied model's table columns, and reports any dangerous collisions it finds. Be aware that this command is not perfect — it looks for simple patterns in the SQL and may miss complex cases or produce false positives. Always review the view SQL and your$excludelist carefully to ensure all computed columns are accounted for.The safest fix is to rename computed columns in the view SQL so they cannot collide:
When renaming is not possible (e.g. the view is shared or generated), use
$excludeto strip the dangerous attributes before hydration:
$excludehas no effect onunderlying(forceFetch: true), which always reads from the database. UseforceFetch: true(the default) whenever you intend to write back through the proxied model. Only useproxied()orunderlying(false)when you explicitly do not need the stored values and have audited both your column aliases and your$excludelist.
HasReadOnlyMode
HasReadOnlyMode is a trait you can add to any Eloquent model — writable or not — to expose a read-only interface to its table or a dedicated read view. It is a lighter alternative to ReadOnlyModel for situations where you want to keep a single model class but need a guarded, view-backed query path alongside it.
readonly()
Called on a model instance, returns a ReadOnlyProxy that passes attribute reads and non-mutating method calls through to the wrapped model but throws ReadOnlyModelException on save(), delete(), or update().
fromView()
Static method. Returns a ReadOnlyBuilder — a custom Eloquent builder that:
- queries from
$readOnlyViewwhen set, otherwise from the model's own table - wraps every result (
get,first,sole,find) in aReadOnlyProxy - throws
ReadOnlyModelExceptionimmediately onupdate(),delete(),create(), orfirstOrCreate()
ReadOnlyProxy
ReadOnlyProxy is the wrapper returned by readonly() and by all ReadOnlyBuilder query methods. It is not specific to HasReadOnlyMode — you can also construct it directly when you need to prevent accidental mutation of any model instance:
Nesting a ReadOnlyProxy inside another ReadOnlyProxy is safe — the constructor always unwraps to the underlying Model.
Scaffolding a view
The command prompts for the view name if omitted, then offers an interactive picklist of Eloquent models in your app/Models directory (and any paths listed in rome.model_scan_paths). Selecting a model seeds the SELECT column list and the view model's $fillable from that model's $fillable. Choose (none) to start with a blank template.
You can bypass the prompt in scripts:
This creates three files:
| File | Purpose |
|---|---|
database/views/order_summary.sql |
SQL definition — edit this |
database/migrations/{timestamp}_create_order_summary_view.php |
Runs the SQL on migrate |
app/Models/Views/OrderSummaryView.php |
Eloquent model backed by the view |
The output path for view models is controlled by rome.readonly_model_path.
Regenerating views
Re-runs all .sql files in db_views_path against each configured connection, handling drop-and-recreate and view dependencies.
If some views depend on others existing first, declare them in priority_views in the config — they are created in the listed order before all remaining views (which are sorted alphabetically):
Multi-tenant mode
When tenant_model is configured, --multi-tenant iterates over all active tenants using eachCurrent (compatible with spatie/laravel-multitenancy):
Refreshing materialized views (PostgreSQL only)
Via the job
The job includes a distributed lock so concurrent dispatches for the same view/tenant are deduplicated rather than stacked.
Job defaults: 3 tries, 5-minute timeout, 60-second backoff.
Directly
RefreshableMaterializedView trait
Add to any model backed by a materialized view for convenience dispatch methods:
ViewDialect
Driver-aware SQL builder. Used internally but available if you need to generate view DDL yourself:
Database support
| Feature | PostgreSQL | MySQL |
|---|---|---|
| Regular views | ✓ | ✓ |
| Materialized views | ✓ | — (skipped with warning) |
DROP VIEW … CASCADE |
✓ | ✓ (omitted) |
| Unique index check | ✓ | ✓ |
PHPStan rules
Laravel Rome ships two PHPStan rules that catch misuse of ReadOnlyModel at static-analysis time — before a test or request ever hits the line.
Setup
Require PHPStan if you haven't already:
Then include the extension — either the published file or directly from vendor (see Publishing).
Rules
NoDirectWriteOnReadOnlyModelRule
Flags any call to save() or delete() on a ReadOnlyModel subclass. Both methods always throw ReadOnlyModelException at runtime; this surfaces the mistake at build time instead.
ProxiedWriteAfterProxyCallRule
Flags save() or delete() chained directly onto proxied() or underlying(false). Both return an in-memory instance hydrated from the view's attributes, which may contain computed column values that don't exist in the backing table. Writing through such an instance can silently corrupt data.
The rule catches chained calls only. Assigning the result to a variable first (
$p = $view->proxied(); $p->save()) is not currently detected.
License
MIT
All versions of laravel-rome with dependencies
illuminate/contracts Version ^11.0|^12.0|^13.0
illuminate/database Version ^11.0|^12.0|^13.0
splitstack/readonly-models Version ^0.1