Weave 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

Viewer Laravel Package

stevebauman/viewer

Viewer is a lightweight Laravel package by Steve Bauman that provides a clean, customizable way to view and inspect content within your application, with simple configuration and a framework-friendly API that drops neatly into any Laravel project.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require stevebauman/viewer

The package is deliberately small — one base class and one trait — so the fastest path to understanding it is to read two files after installing: src/Viewer.php (the base presenter) and src/Traits/Viewable.php (the model-side accessor). The README is short; the source is the documentation.

Step 1 — attach the trait to a model:

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Stevebauman\Viewer\Traits\Viewable;
use App\Viewers\UserViewer;

class User extends Model
{
    use Viewable;

    protected $viewer = UserViewer::class;
}

Step 2 — create the viewer:

namespace App\Viewers;

use Stevebauman\Viewer\Viewer;

class UserViewer extends Viewer
{
    public function badge()
    {
        return $this->view('viewers.user.badge', [
            'user' => $this->model,
        ]);
    }
}

Step 3 — use it in Blade:

{!! $user->viewer->badge() !!}

First real use case: pick a partial you already repeat — an avatar block, a status pill, an author byline — and move it out of resources/views/partials into a viewer method. That immediately removes the @include('partials.user.badge', ['user' => $user]) boilerplate from every loop and gives you one obvious place to change markup.

Implementation Patterns

Mirror your model namespace. App\Models\User → App\Viewers\UserViewer. When the number of viewers grows this convention saves you from hunting, and it makes a viewer resolver on your base model trivial if you want to infer the class instead of declaring $viewer manually.

One method = one fragment. Keep viewer methods single-purpose and named for what they render (badge(), byline(), meta()), not for the entity. Composite views then read like a table of contents:

<div class="user-card">
    {!! $user->viewer->avatar() !!}
    {!! $user->viewer->byline() !!}
    {!! $user->viewer->badge() !!}
</div>

Viewers compose, they don't compute. A viewer method may call another viewer method, but it should never hit the database, dispatch events, or contain conditional business rules. If a method needs a query, the relationship should be eager-loaded at the call site (User::with('roles')->paginate()) and passed in — otherwise a listing page quietly turns into N+1.

Return type discipline matters. Depending on version, a viewer method may hand back an Illuminate\View\View or an already-rendered string. Standardise on one in your codebase (calling ->render() inside the viewer keeps Blade clean but kills @include-style data injection later) and be consistent. If you return View objects, {!! !!} is required — {{ }} will print the object's string cast in some cases and escape the markup in others.

Where it pays off beyond Blade: transactional email templates, PDF invoices (dompdf/snappy), and Livewire components all need HTML assembled from a model. A viewer gives you a testable unit instead of a chain of view()->make() calls sprinkled through job classes.

Test at the seam. Viewers are cheap to test because they produce a named view with known data:

$view = $user->viewer->badge();

$this->assertSame('viewers.user.badge', $view->name());
$this->assertTrue($view->getData()['user']->is($user));

That assertion set catches the most common regression in this pattern — a renamed partial that nobody notices until a 500 fires in production.

Multiple viewers per model is legitimate for genuinely different surfaces (public profile vs. admin row). Rather than bloating one class, add a second viewer and expose it via a distinct accessor or a small factory, e.g. $user->adminViewer.

Gotchas and Tips

  • Zero dependents means you are the QA team. With no downstream users and a low score, treat the package as unmaintained-by-default: pin the version in composer.json, read src/ end to end (it's small enough), and be prepared to copy the two classes into app/Support/ if the upstream goes quiet. The pattern is more valuable than the package.
  • Trait collisions. If your model already has a viewer attribute, a viewer() relationship, or a Viewable-adjacent trait, PHP will fatal on the method clash. Alias on import (use Viewable as ViewableTrait;) or rename via insteadof. Check existing accessors before adopting.
  • Serialization leakage. If the viewer is exposed as an accessor and you add it to $appends, it will appear in toArray() / JSON responses — dumping HTML into your API payloads and possibly blowing up on models with circular relations. Keep viewers out of API resources entirely.
  • Missing views throw late, not early. view()->make() raises an InvalidArgumentException only at render time, so a broken viewer path shows up as a 500 on a specific page rather than at boot. A cheap guard in CI: a test that renders every viewer method for a factory-made model.
  • Model binding in the viewer constructor. Confirm how $this->model is populated in Viewer::__construct() before writing helpers around it; some versions expect the record under a different property name. Get this right once and copy the pattern.
  • Debugging tip: dd($user->viewer->badge()->name()) before render() isolates "wrong view" from "wrong data" in one step. If the name is right and the output is empty, it's a data key mismatch, not a view-path problem.
  • Extension point: the base Viewer class is the intended hook — subclass it with shared helpers (formatDate(), link(), a viewPath prefix) so every viewer inherits them, rather than duplicating utility methods across ten classes.
  • Don't over-apply it. A viewer for a model rendered in exactly one place is indirection for its own sake — a plain @include is clearer. Adopt it for fragments that recur across three or more templates.
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.
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log
hsluv/hsluv
seampact/codegen