7

There are some tables in our system which are being fed using 3rd party APIs and our system is supposed only read data from them and never Insert or Update anything.

Is there any feature in Laravel/Lumen, where we can mention in the Model to disallow/disable insert/update queries like we have the option public $timestamps = false; to disable the timestamps?

The tables are in same database else we would have restricted based on MySQL user.

Deepanshu Goyal
  • 2,738
  • 3
  • 34
  • 61

8 Answers8

9

There are a few ways.
OPTION 1: Probably the quickest is this "read-only" model trait. https://github.com/michaelachrisco/ReadOnlyTraitLaravel

It protects you from...

  • create
  • forceCreate
  • save
  • update
  • firstOrCreate
  • firstOrNew
  • delete
  • destroy
  • restore
  • forceDelete
  • performDeleteOnModel
  • push
  • finishSave
  • performUpdate
  • touch
  • insert
  • truncate

OPTION 2: A completely different way to do it is on the db config and model connection. So, this has 2 parts.

project/config/database.php Duplicate and tweak the db connection.

'mysql' => [
            'driver'         => 'mysql',
            'host'           => env('DB_HOST', '127.0.0.1'),
    ...
'readonly' => [
            'driver'         => 'mysql',
            'read' => [
                'host' => env('DB_HOST', '127.0.0.1')
            ],
            'write' => [
                'host' => 'this.will.404'
            ],
    ...

project/app/MyReadOnlyModel.php

class MyReadOnlyModel extends Model
{
    protected $connection  = 'readonly';
...
}

If you are caught in the trap of wanting the Model to be writable sometimes... I would suggest having 2 models on the same table.

app/Normal/MyModel.php

app/ReadOnly/MyModel.php

Tarek Adam
  • 3,387
  • 3
  • 27
  • 52
  • 1
    Option 2 doesn't work for all cases, as if you use the "fresh" or "refresh" on the model, it will fail on the DB read. This means the model can't be serialized in jobs. – Nick Feb 28 '23 at 00:42
  • @Nick that's an interesting observation. At least the data is protected as intended, and you could use the app/Normal/MyModel.php for jobbing and/or remove the serialize models from the trait. In any case, good observation. A problem for every solution. – Tarek Adam Mar 10 '23 at 19:50
3

The most secure way will be to create a second MySQL user with the readonly only on the tables.
Then in Laravel you can create a specific connection with the restricted MySQL user in your config/database.php.
Then in the Model specify the wanted connection through the protected $connection property.

ml59
  • 1,495
  • 1
  • 9
  • 10
3

yes,

as a secure way:

as you can restricting some actions on Database.

ReadOnly Model

but you can disable the eloquent models too.

laravel models are extended from Illuminate\Database\Eloquent\Model

you can extend an ReadOnlyModel from Model.

then extend any model you want from that class.

this class should Override any method which writes data in db,

so i follow the source code:

Update and updateOrFail, push and etc was using used Model->save() method.

While create,creteOrFail , delete and etc were places in Builder which uses Model->save() method

The save() method used performUpdate or performInsert with someevent triggering ...

so the simplest thing you can do to stop model from touching databases is to implement:

   <?php

namespace App\ReadOnlyDB;

use Illuminate\Database\Eloquent\Model;
/**
* Just Extend all of Your Models from This Class
*
*/
class ReadOnlyModel extends Model
{
        public function save(){
            // do nothing
        }
}

save() methods in Relation and BelongsTo classes would use Model->save() method too.

Abilogos
  • 4,777
  • 2
  • 19
  • 39
  • 1
    You'll want to override the update() method too, and probably others depending on your needs. Here's a bunch of possibilities: https://github.com/michaelachrisco/ReadOnlyTraitLaravel/blob/main/src/ReadOnlyTrait.php – Joel Mellon Aug 25 '21 at 20:58
  • @joelMellon the update method uses [`save()` method itself](https://github.com/laravel/framework/blob/7134356108eb8bbe003ef7f71bfff8d1b01356cd/src/Illuminate/Database/Eloquent/Model.php#L851). – Abilogos Aug 26 '21 at 08:55
  • IDK, override them all! – Joel Mellon Aug 26 '21 at 20:58
1

I think the only proper way to manage this is to have access restrictions at the MySQL user side. Here's why:

If you are stubbing (disabling) save/update methods from the Eloquent class, you only disable save/updates for your own code. This is analogous to locking a door while hanging the key on the door handle. Nothing prevents you from doing the saves/updates since you are not bound to use the Eloquent class.

In addition, 3rd party packages may persist data which does not depend on your code. You basically have to check every single line of code of theirs to make sure your database is not updated.

Therefore, apply these restrictions at the database user level. Then, when for some reason your (or anyone elses) code calls $myModel->save(), you should face an exception from the ORM that states you do not have permission. Then, handle it accordingly (like returning a particular HTTP status code).

Flame
  • 6,663
  • 3
  • 33
  • 53
0
  1. Create an additional mySql user with read-only privileges.

  2. in .env file add the following

    DB_CONNECTION_SECOND=mysql

    DB_HOST_SECOND=127.0.0.1

    DB_PORT_SECOND=3306

    DB_DATABASE_SECOND=database

    DB_USERNAME_SECOND=user_2

    DB_PASSWORD_SECOND=secret

  3. in config/database.php add the following

    'mysql2' => [
    'driver'    => env('DB_CONNECTION_SECOND'),
    'host'      => env('DB_HOST_SECOND'),
    'port'      => env('DB_PORT_SECOND'),
    'database'  => env('DB_DATABASE_SECOND'),
    'username'  => env('DB_USERNAME_SECOND'),
    'password'  => env('DB_PASSWORD_SECOND'),],
    
  4. in your controller specify the connection name..

    DB::connection('mysql2')->select(...);

Rejneesh
  • 1,574
  • 16
  • 16
0

Maybe using an empty fillable attribute in your model resolve your problem!

protected $fillable = [];
0

Set a model accessor to throw an exception when touching an attribute.

But this is more of a read-only attribute instead of a read-only model since it requires one accessor per attribute.

use Exception;
use Illuminate\Database\Eloquent\Casts\Attribute;

    protected function value(): Attribute
    {
        return Attribute::make(
            set: fn () => throw new Exception('Model is readonly'),
        );
    }
medilies
  • 1,811
  • 1
  • 8
  • 32
0

You can set all the fields of the models as guarded:

protected $guarded = ['*'];

This will not prevent to delete model.

Juan Lago
  • 960
  • 1
  • 10
  • 20