PHPackages                             splitstack/readonly-models - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. splitstack/readonly-models

ActiveLibrary

splitstack/readonly-models
==========================

Block Eloquent persistence on read-only / value-object models.

v0.1.0(today)00MITPHPPHP ^8.2

Since Aug 1Pushed todayCompare

[ Source](https://github.com/EmilienKopp/readonly-models)[ Packagist](https://packagist.org/packages/splitstack/readonly-models)[ RSS](/packages/splitstack-readonly-models/feed)WikiDiscussions main Synced today

READMEChangelog (1)Dependencies (9)Versions (2)Used By (0)

readonly-models
===============

[](#readonly-models)

Block persistence on Eloquent models. Drop the `PreventsWrites` trait onto a model and every write path (`save`, `update`, `delete`, `forceDelete`) throws a `ReadOnlyException` instead of touching the database.

Useful for value objects, in-memory entities, database views, and any model that should never be written back.

Installation
------------

[](#installation)

```
composer require splitstack/readonly-models
```

Requires PHP 8.2+ and `illuminate/database` 10 through 13. It only needs the database component, not the full framework, so it works inside a Laravel app or standalone with the Eloquent Capsule.

Usage
-----

[](#usage)

```
use Illuminate\Database\Eloquent\Model;
use Splitstack\ReadonlyModels\Concerns\PreventsWrites;

class ExchangeRate extends Model
{
    use PreventsWrites;
}
```

```
$rate = ExchangeRate::find(1); // reads work as normal
$rate->rate = 1.23;
$rate->save();                 // throws ReadOnlyException
```

Because `create()`, `saveQuietly()` and `push()` all route through `save()`, and the static `destroy()` routes through `delete()`, those four overrides cover the whole write surface.

Catching the exception
----------------------

[](#catching-the-exception)

`ReadOnlyException` extends `BadMethodCallException` (itself a `LogicException`), so you can catch it at whichever level fits:

```
use Splitstack\ReadonlyModels\Exceptions\ReadOnlyException;

try {
    $model->save();
} catch (ReadOnlyException $e) {
    // ...
}
```

Customising the behaviour
-------------------------

[](#customising-the-behaviour)

**Keep one write method working.** A method declared directly on the class always wins over the trait's version, so you can carve out an exception without any trait conflict. For example, route `update()` through another model:

```
class ViewModel extends Model
{
    use PreventsWrites;

    public function update(array $attributes = [], array $options = [])
    {
        // your own logic — save/delete/forceDelete stay blocked
    }
}
```

**Change the exception or message.** Override the protected `denyWrite()` hook. It receives the blocked method name and returns the exception to throw:

```
use Splitstack\ReadonlyModels\Exceptions\ReadOnlyException;

protected function denyWrite(string $method): ReadOnlyException
{
    return new MyReadOnlyException(match ($method) {
        'save'   => 'This record is immutable.',
        'delete' => 'This record cannot be removed.',
        default  => "Cannot {$method} this record.",
    });
}
```

`MyReadOnlyException` just needs to extend `ReadOnlyException`.

License
-------

[](#license)

MIT. See [LICENSE](LICENSE).

###  Health Score

36

—

LowBetter than 79% of packages

Maintenance100

Actively maintained with recent releases

Popularity0

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity35

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

0d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/b9e77e55bb8341a1fa99f8348597070b4de230b5fc457e866e4f196dd56843f1?d=identicon)[EmilienKopp](/maintainers/EmilienKopp)

---

Top Contributors

[![EmilienKopp](https://avatars.githubusercontent.com/u/91975560?v=4)](https://github.com/EmilienKopp "EmilienKopp (3 commits)")

###  Code Quality

TestsPest

Static AnalysisPHPStan, Rector

Type Coverage Yes

### Embed Badge

![Health badge](/badges/splitstack-readonly-models/health.svg)

```
[![Health](https://phpackages.com/badges/splitstack-readonly-models/health.svg)](https://phpackages.com/packages/splitstack-readonly-models)
```

###  Alternatives

[spatie/laravel-permission

Permission handling for Laravel 12 and up

12.9k107.5M1.5k](/packages/spatie-laravel-permission)[psalm/plugin-laravel

Psalm plugin for Laravel

3355.4M350](/packages/psalm-plugin-laravel)[pressbooks/pressbooks

Pressbooks is an open source book publishing tool built on a WordPress multisite platform. Pressbooks outputs books in multiple formats, including PDF, EPUB, web, and a variety of XML flavours, using a theming/templating system, driven by CSS.

45844.8k1](/packages/pressbooks-pressbooks)[api-platform/laravel

API Platform support for Laravel

58174.6k18](/packages/api-platform-laravel)[forjedio/inertia-table

Backend-driven dynamic tables for Laravel + Inertia.js

272.0k](/packages/forjedio-inertia-table)[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1239.7k25](/packages/fleetbase-core-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
