← Laracrate 16 / 17

Reference

Reference

Artisan commands

Laracrate ships three commands for the maintenance work that keeps storage costs and usage counters honest. None of them run on their own: register the ones you need in your scheduler.

use Illuminate\Support\Facades\Schedule;

Schedule::command('laracrate:abort-stale-multipart')->hourly();
Schedule::command('laracrate:purge-expired')->hourly();
Schedule::command('laracrate:recompute-usage')->daily();

laracrate:abort-stale-multipart

Aborts multipart upload sessions that passed their expires_at without completing. This is the important one for production: until a multipart session is aborted, the parts already uploaded to S3/R2 keep occupying storage and billing you indefinitely. The command loads stale sessions via the MultipartUpload::stale() scope and runs AbortMultipartUploadAction on each, marking them MultipartUploadStatus::EXPIRED.

php artisan laracrate:abort-stale-multipart
php artisan laracrate:abort-stale-multipart --dry-run --limit=200
Option Default Effect
--dry-run off List the stale sessions, abort nothing.
--limit= 500 Maximum sessions to process per run.

See the Multipart uploads coverage in the Upload modes section for how these sessions are created.

laracrate:purge-expired

Deletes files from collections that declare a TTL and have outlived it. A collection opts in by setting ttl_hours in its config/laracrate.php entry. The command walks every configured collection, computes a cutoff of now()->subHours($ttlHours), and force-deletes top-level files (parent_id null) created before it. It uses forceDelete() on purpose so the FileObserver purges the binary and its sidecars from the backend and cascades to variants.

php artisan laracrate:purge-expired
php artisan laracrate:purge-expired --collection=temp_uploads --dry-run
Option Default Effect
--collection= all Restrict to one collection.
--dry-run off List expired files, delete nothing.
--limit= 1000 Maximum files to process per collection.

Collections without a positive ttl_hours are skipped, so this command is safe to schedule even if only some collections are temporary. See the Configuration section for ttl_hours.

laracrate:recompute-usage

Recomputes the laracrate_folderables usage counters (files_count, total_size_bytes) from laracrate_files. The per-row counters are kept up to date by the FileObserver, but they can drift if the observer fails, you import rows by hand, or you restore from a backup. This command re-aggregates the truth, updates last_recomputed_at, and resets orphaned rows to 0/0. It is idempotent, so running it repeatedly always converges to the correct state.

It only touches collections with track_usage enabled in config, unless you name one explicitly.

php artisan laracrate:recompute-usage
php artisan laracrate:recompute-usage drive
php artisan laracrate:recompute-usage --dry-run
Argument / Option Default Effect
collection (argument) all tracked Restrict to one collection.
--dry-run off Print the per-row deltas without persisting.

See the Multi-tenancy, buckets and usage section for how track_usage and the usage counters work.

Events

Laracrate fires plain Laravel events around the processing pipeline so your app can react without coupling to the internals: refresh UI, invalidate caches, notify users, or index vectors into your search engine. Every event uses the Illuminate\Foundation\Events\Dispatchable trait, lives under the EduLazaro\Laracrate\Events namespace, and exposes its payload as public constructor-promoted properties.

use EduLazaro\Laracrate\Events\FileProcessed;
use Illuminate\Support\Facades\Event;

Event::listen(function (FileProcessed $event) {
    $event->file->fileable->notify(new FileReady($event->file));
});
Event When it fires Payload
FileProcessingStarted Just before the pipeline iterates its steps. The file is already marked ProcessingStatus::PROCESSING. File $file
FileProcessed The pipeline finished successfully. The file is already marked ProcessingStatus::COMPLETED. File $file
FileProcessingFailed A step threw. The file is marked ProcessingStatus::FAILED and processing_error holds the message. The queue retries if the job has tries left. File $file, Throwable $exception
VariantGenerated A child file (parent_id set) is persisted: thumbnail, preview, transcoded copy, watermarked variant, and so on. Fired centrally from the observer, independent of which action created it. File $variant, ?File $parent
EmbeddingsReady The embedding step finished and produced at least one new vector. File $file, int $count (chunks that ended up with an embedding in this pass)

A common use of EmbeddingsReady is to mirror the generated vectors into your search backend (pgvector, Meilisearch, Qdrant) from a queued listener:

namespace App\Listeners;

use EduLazaro\Laracrate\Events\EmbeddingsReady;
use Illuminate\Contracts\Queue\ShouldQueue;

class IndexFileVectors implements ShouldQueue
{
    public function handle(EmbeddingsReady $event): void
    {
        // $event->count chunks now carry an embedding
        MyVectorIndex::sync($event->file);
    }
}

Register listeners the usual way (an Event::listen call in a service provider, an auto-discovered handle method, or a #[AsEventListener] attribute). See the Processing pipeline section for what runs between FileProcessingStarted and FileProcessed, and the Text extraction, embeddings and search (RAG) section for the embedding step behind EmbeddingsReady.

API reference

A compact, signature-level reference for the public surface of Laracrate. Every entry below is copied from source. For narrative usage of each piece, see the section it belongs to (working with files from your models, displaying files, processing pipeline, and so on).

HasFiles trait

Add use EduLazaro\Laracrate\Concerns\HasFiles; to any model. See the Working with files from your models section for usage.

// Relations and lookups
public function files(?string $collection = null): MorphMany;       // top-level only, ordered by position
public function file(string $collection): ?File;                    // latest, default-first
public function getFile(string $collection): ?File;                 // alias of file()
public function defaultFile(string $collection): ?File;
public function images(?string $collection = null): MorphMany;

// Mutations
public function addFile(
    UploadedFile|\EduLazaro\Laracrate\Support\Binary|FileUpload|string $file,
    string $collection,
    array $data = [],
    array $slots = [],
    ?Model $creator = null,
    ?Model $owner = null,
    ?Folder $folder = null,
): ?File;
public function setFile(
    string $collection,
    UploadedFile|\EduLazaro\Laracrate\Support\Binary|FileUpload|string|null $file,
    array $data = [],
    ?Model $creator = null,
    ?Model $owner = null,
): ?File;                                                            // replaces (force-deletes existing)
public function setDefaultFile(File $file): File;
public function deleteFile(File $file, bool $forceDelete = false): bool;
public function reorderFiles(string $collection, array $orderedIds): void;

// Rendering helpers
public function fileLink(string $collection, ?string $variant = null, ?string $forceType = null): ?string;
public function fileRender(string $collection, ?string $variant = null, array $attrs = []): HtmlString;

// Config and tenant resolution
public function getCollectionConfig(string $collection): array;
public function getDiskFor(string $collection): string;
public function resolveFileTenant(): ?Model;                        // override per app to point at your tenant model

File model

EduLazaro\Laracrate\Models\File. Table laracrate_files. Route key is slug.

// Relations
public function fileable(): MorphTo;
public function creator(): MorphTo;
public function owner(): MorphTo;
public function tenant(): MorphTo;
public function parent(): BelongsTo;
public function children(): HasMany;
public function folder(): BelongsTo;
public function chunks(): HasMany;                  // ordered by chunk_index
public function chunk(): HasOne;                    // chunk_index = 0
public function slots(): BelongsToMany;
public function contents(): HasMany;                // @deprecated alias of chunks()
public function content(): HasOne;                  // @deprecated alias of chunk()
public function effectiveOwner(): ?Model;           // explicit owner, else creator

// Variant navigation (dot notation)
public function variant(string $path): self;        // falls back to nearest ancestor, never null
public function variantOrFail(string $path): self;  // throws RuntimeException if a link is missing
public function createVariant(string $variantName, array $overrides): self;

// Storage key helpers (path stores the full object key)
public function getKeyAttribute(): string;          // $file->key
public function siblingKey(string $newName): string;
public function variantKey(string $newName): string;

// URLs and rendering
public function url(?string $forceType = null): ?string;            // public/signed/stream per access
public function placeholderFor(string $type): string;
public function getLinkAttribute(): ?string;        // $file->link, alias of url()
public function getPreviewLinkAttribute(): string;  // $file->preview_link, thumbnail or placeholder
public function streamUrl(): string;                // signed package route, TTL config laracrate.urls.route_signed_ttl
public function downloadUrl(): string;
public function previewUrl(): string;

// Folder
public function moveToFolder(?Folder $folder): void;

// State helpers
public function publish(): self;
public function unpublish(): self;
public function makeDefault(): self;

// Type and state predicates
public function isVariant(): bool;
public function isTopLevel(): bool;
public function isSensitive(): bool;
public function createdByUser(): bool;
public function createdByAgent(): bool;
public function createdAutomatically(): bool;
public function isMultiTenant(): bool;
public function isImage(): bool;
public function isVideo(): bool;
public function isAudio(): bool;
public function isDocument(): bool;
public function isPdf(): bool;

// Extracted text and chunks (sidecar artifacts on the disk)
public function extractedContent(): ?ExtractedContent;
public function extractedText(): ?string;
public function chunkText(int $chunkIndex): ?string;
public function chunksJsonl(): array;
public function hasEmbeddings(): bool;

// Authorization (delegates to PolicyRegistry)
public function canView(?Model $user): bool;
public function canEdit(?Model $user): bool;
public function canDelete(?Model $user): bool;

Query scopes:

File::topLevel();                  // whereNull('parent_id')
File::withDescendants($depth = 2); // eager-load children to depth
File::withVariants($depth = 3);    // eager-load the variant tree
File::forTenant($tenant);
File::ordered();                   // position, then id
File::published();
File::unpublished();
File::default();

Scopes, and the state helpers that return self, chain like any Eloquent call:

File::published()->ordered()->forTenant($org)->get();
$file->makeDefault()->publish();

StorageManager service

EduLazaro\Laracrate\Services\StorageManager. The package facade over Storage::disk(). Resolve it with app(StorageManager::class). See the Upload modes and Multi-tenancy, buckets and usage sections for context.

public function urlFor(File $file): ?string;        // public/signed/stream based on $file->access
public function diskFor(File $file): \Illuminate\Contracts\Filesystem\Filesystem;
public function resolveDisk(string $disk): \Illuminate\Contracts\Filesystem\Filesystem;   // honors 'tb:{id}'
public function configFor(string $disk): array;
public function readBinary(File $file): string;
public function writeBinary(string $disk, string $key, string $content, ?string $mime = null): bool;
public function deleteFromBackend(string $disk, string $key): bool;
public function moveServerSide(string $disk, string $fromKey, string $toKey): bool;       // S3 copyObject, no PHP round-trip
public function batchDelete(string $disk, array $keys): int;
public function presignedUpload(string $disk, string $key, string $mime, ?int $maxSize = null, int $minutes = 15): array;
public function withLocalCopy(File $file, callable $fn): mixed;                            // temp file for ffmpeg/Imagick
public function getCollectionConfig(string $collection, array $modelOverride = [], ?string $morphAlias = null): array;
public function getTypeConfig(string $collection, string $type, ?string $morphAlias = null): array;
public function acceptsType(string $collection, string $type, ?string $morphAlias = null): bool;
public function s3ClientOf(string $disk): ?\Aws\S3\S3Client;
public function driverOf(string $disk): string;

UsageReporter service

EduLazaro\Laracrate\Services\UsageReporter. Storage usage aggregation. Resolve it with app(UsageReporter::class). Each method returns a UsageStats DTO. See the Multi-tenancy, buckets and usage section.

public function forTenant(Model $tenant, bool $excludeTrashed = false): UsageStats;
public function forCreator(Model $creator, bool $excludeTrashed = false): UsageStats;
public function forCollection(string $collection, ?Model $tenant = null, bool $excludeTrashed = false): UsageStats;
public function global(bool $excludeTrashed = false): UsageStats;

Contracts

Bind your own implementation in a service provider to swap any of these. The defaults are documented in their feature sections.

// EduLazaro\Laracrate\Contracts\ChunkStore
public function store(File $file, array $chunks): int;
public function getByFile(File $file): \Illuminate\Support\Collection;
public function search(string $query, array $filters = [], array $options = []): \Illuminate\Support\Collection;
public function deleteByFile(File $file): void;
public function driverName(): string;
// EduLazaro\Laracrate\Contracts\EmbeddingProvider
public function embed(array $texts): array;          // one vector per text, same order
public function dimensions(): int;
public function model(): string;                     // e.g. "text-embedding-3-small"
public function name(): string;                      // e.g. "openai"
// EduLazaro\Laracrate\Contracts\TextExtractor
public function supports(File $file): bool;
public function extract(File $file): \EduLazaro\Laracrate\Support\ExtractedContent;
// EduLazaro\Laracrate\Contracts\FileActionInterface
public function handle(File $file): void;
public function priority(): int;
// optional: public function supports(File $file): bool;  // assumed true if absent