›
byrcsc/laravel-comments · 1.x
Record attachment metadata while your application manages the stored files.
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.
$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:
| Argument | Falls back to |
|---|---|
disk | comments.attachments.disk, then the application's default disk |
name | The path's basename |
mimeType | Stays null |
size | Stays 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.
$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').
$comment->detach($attachment); // returns whether there was one to removeThe file on disk is untouched, here as everywhere. Passing an attachment that
belongs to a different comment throws an InvalidAttachmentException rather
than removing it.
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. A force
delete fires it once per attachment across the complete reply subtree,
including tombstones. The listener can therefore remove every stored file.
A soft delete fires nothing, because nothing was removed.
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.
$optimize defaults to true and applies the framework's own optimize step.
It writes WebP at the framework's default quality.
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.
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/imageWithout 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.
The package checks presence and types, and nothing further:
InvalidAttachmentException.LogicException.Whether the bytes are really on that disk is your application's to know: it is what put them there.
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.
| Event | Fires when |
|---|---|
AttachmentAdded | attach() or attachImage() records a row |
AttachmentRemoved | detach() 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.
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.