Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Readonly Laravel Package

michaelachrisco/readonly

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:
    composer require michaelachrisco/readonly
    
  2. Apply Trait: Add ReadOnlyTrait to any Eloquent model:
    use MichaelAChrisco\ReadOnly\ReadOnlyTrait;
    
    class LegacyUser extends Model
    {
        use ReadOnlyTrait;
    }
    
  3. First Use Case: Attempting to save a LegacyUser will immediately throw a ReadOnlyException:
    $user = new LegacyUser(['name' => 'John']);
    $user->save(); // Throws ReadOnlyException
    

Where to Look First

  • Exceptions: Check ReadOnlyException for method-specific errors.
  • Model Events: Override boot() to apply the trait dynamically:
    protected static function boot()
    {
        static::addGlobalScope(new ReadOnlyScope());
    }
    
  • Testing: Use expectException(ReadOnlyException::class) in PHPUnit tests.

Implementation Patterns

Core Workflows

  1. Legacy System Integration:

    • Apply ReadOnlyTrait to models representing read-only data sources (e.g., LegacyInvoice, AuditLog).
    • Pair with database-level permissions (e.g., SELECT only) for defense in depth.
  2. Dynamic Application:

    • Use middleware to conditionally apply the trait:
      public function handle($request, Closure $next)
      {
          if ($request->isLegacyEndpoint()) {
              Model::addGlobalScope(new ReadOnlyScope());
          }
          return $next($request);
      }
      
  3. API Responses:

    • Return 403 Forbidden for write attempts:
      try {
          $user->save();
      } catch (ReadOnlyException $e) {
          return response()->json(['error' => 'Resource is read-only'], 403);
      }
      

Integration Tips

  • Query Builder: Extend DB::table() to enforce read-only behavior:
    DB::macro('readonly', function ($query) {
        return $query->getReadOnlyQuery();
    });
    
  • Relationships: Mark related models as read-only:
    class Order extends Model
    {
        use ReadOnlyTrait;
    
        public function items()
        {
            return $this->hasMany(OrderItem::class)->readonly();
        }
    }
    
  • Caching: Cache read-only queries aggressively (e.g., Cache::remember()).

Gotchas and Tips

Pitfalls

  1. Overridden Methods:

    • Custom save() or update() methods bypass the trait. Use parent::save() to trigger checks.
    • Example:
      public function save(array $options = [])
      {
          throw new ReadOnlyException('Custom save blocked');
          // parent::save($options); // Would throw the trait's exception
      }
      
  2. Mass Assignment:

    • fill() and create() may silently fail. Explicitly check for ReadOnlyException:
      $user->fill(['name' => 'Alice']); // No exception, but save() will throw.
      
  3. Soft Deletes:

    • restore() and forceDelete() are blocked, but deleted_at may still be set via raw queries.
  4. Model Events:

    • saving, updating, or deleting events fire before the trait’s checks. Use saved, updated, etc., for post-validation logic.

Debugging

  • Exception Handling:
    • Catch ReadOnlyException globally in App\Exceptions\Handler:
      public function render($request, Throwable $exception)
      {
          if ($exception instanceof ReadOnlyException) {
              return response()->json(['error' => 'Read-only violation'], 403);
          }
          return parent::render($request, $exception);
      }
      
  • Logging:
    • Log read-only attempts for auditing:
      catch (ReadOnlyException $e) {
          \Log::warning("Read-only violation on {$this->class}: {$e->getMessage()}");
      }
      

Extension Points

  1. Custom Exceptions:

    • Extend ReadOnlyException for granular error messages:
      class LegacyReadOnlyException extends ReadOnlyException {}
      
    • Override the trait’s throwException() method.
  2. Whitelisting:

    • Allow specific methods (e.g., touchOwnedTimestamps) by modifying the trait:
      protected $allowedMethods = ['touch'];
      
  3. Conditional Read-Only:

    • Use a scope to toggle behavior:
      class ConditionalReadOnlyScope implements Scope
      {
          public function apply(Builder $builder, Model $model)
          {
              if ($model->isReadOnly()) {
                  $builder->getQuery()->setReadOnly(true);
              }
          }
      }
      
  4. Testing:

    • Mock the trait for tests:
      $this->partialMock(User::class, ['save'])
           ->shouldThrow(ReadOnlyException::class)
           ->during('save');
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope