Browse documentationOpen

byrcsc/laravel-comments · 1.x

Attachments.

Record metadata about files your application stored, attach processed images in one call, and clean up disks from the removal event.

An attachment is a row of metadata about a file your application stored: a disk name, a path on it, and what the application said the file is called, is, and weighs.

The package never opens the file, never checks that it is there, and never deletes it. Serving it, authorizing the download, and cleaning it up stay on your side of the line.

Record a file

$path = $request->file('receipt')->store('receipts', 'uploads');

$attachment = $comment->attach(
    path: $path,
    disk: 'uploads',
    name: $request->file('receipt')->getClientOriginalName(),
    mimeType: $request->file('receipt')->getMimeType(),
    size: $request->file('receipt')->getSize(),
);

Only path is required:

ArgumentFalls back to
diskcomments.attachments.disk, then the application's default disk
nameThe path's basename
mimeTypeStays null
sizeStays null

Size and MIME type stay null when you did not measure them. A guess recorded as fact is worse than an honest absence.

The metadata is recorded as given and never verified against the disk. Attaching a path that holds nothing succeeds, because the package has no business reading the file to find out.

Read them

$comment->attachments;   // oldest first

foreach ($comment->attachments as $attachment) {
    $attachment->disk;
    $attachment->path;
    $attachment->name;
    $attachment->mime_type;
    $attachment->size;
}

Rendering them is ordinary Eloquent, and so is eager-loading a whole thread's with with('attachments').

Remove one

$comment->detach($attachment);  // returns whether there was one to remove

The file on disk is untouched, here as everywhere. Passing an attachment that belongs to a different comment throws an InvalidAttachmentException rather than removing it.

Delete the file

Deleting bytes belongs in a listener on AttachmentRemoved, where the row is still in hand and its disk and path are still readable:

use ByRcsc\LaravelComments\Events\AttachmentRemoved;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Storage;

Event::listen(AttachmentRemoved::class, function (AttachmentRemoved $event): void {
    Storage::disk($event->attachment->disk)->delete($event->attachment->path);
});

That listener covers both removal paths. detach() fires it once, and a force delete fires it once per attachment across the whole subtree the cascade takes — replies and their tombstones included, because a listener that never heard about a reply's files would leave them on the disk forever.

A soft delete fires nothing, because nothing was removed.

Attach an image

attachImage() processes an uploaded image, stores it, and records it in one call:

use Illuminate\Support\Facades\Image;

$comment->attachImage(Image::fromUpload($request->file('screenshot')));

Everything about the processing is the framework's — hand it whatever Image::fromUpload() and friends give you, configured however you like:

$comment->attachImage(
    Image::fromUpload($request->file('screenshot'))->resize(1600, 900),
    name: 'Screenshot.png',
    disk: 'uploads',
    directory: 'comments/screenshots',
);

The disk and directory fall back to comments.attachments.disk and comments.attachments.directory. The name falls back to the uploaded file's own name carrying the extension of what was actually stored — a screenshot.png optimized to WebP is recorded as screenshot.webp, because a row whose name disagrees with its own bytes is metadata that lies.

This is the one path where the package writes bytes to a disk, and it writes only the ones it was handed. Nothing is read back afterwards.

The optimize flag

$optimize defaults to true and applies the framework's own optimize step — WebP at its default quality — which is what makes the common screenshot case one line.

Pass optimize: false when the pipeline you handed over already says what the output should be:

$comment->attachImage(
    Image::fromUpload($file)->toPng(),
    optimize: false,
);

Image keeps its pipeline private, so the package cannot ask whether you configured one and would otherwise overwrite your format. The flag is that question, asked of the caller.

Requirements

attachImage() needs the framework's Image facade, which arrived in Laravel 13, and intervention/image, which the package suggests rather than requires:

composer require intervention/image

Without it the call throws an ImageSupportMissingException naming the missing dependency, rather than a driver error three frames away. Nothing else in the package needs it — attach() works without an image library at all.

Validation

The package checks presence and types, and nothing further:

  • A blank path or a blank name throws an InvalidAttachmentException.
  • A negative size throws the same. Zero is accepted — an empty file is a real file.
  • Attaching to an unsaved comment throws a LogicException.

Whether the bytes are really on that disk is your application's to know: it is what put them there.

Deleted comments

A tombstone keeps the attachments it already had and takes no new ones, for the same reason its reactions are frozen: a moderator reading what happened needs the record to have stopped changing. Attaching to or detaching from a soft-deleted comment throws a CommentTrashedException. Restoring it makes both work again.

Force deleting removes the rows through the cascade, for the comment and its whole subtree — and fires AttachmentRemoved for each one first.

Reading is never gated.

Events

EventFires when
AttachmentAddedattach() or attachImage() records a row
AttachmentRemoveddetach() removes one, or a force delete takes one

Both extend CommentAttachmentChanged and carry the comment and the attachment model. On the force-delete sweep, the comment the event carries is the one that held the attachment — not necessarily the one the caller deleted.

Authorization

attach is an ability on the shipped CommentPolicy and allows any authenticated actor by default, including on somebody else's comment: a moderator adding evidence to a reported comment is as ordinary as an author adding a screenshot to their own. Narrow it to authors by overriding. See authorization.