PHP code example of edulazaro / laracrate

1. Go to this page and download the library: Download edulazaro/laracrate library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

edulazaro / laracrate example snippets


'media' => [
    'driver' => 's3',
    'bucket' => env('R2_BUCKET_MEDIA'),
    'endpoint' => env('R2_ENDPOINT'),
    'use_path_style_endpoint' => true,
    // ...
],
'documents' => [
    'driver' => 's3',
    'bucket' => env('R2_BUCKET_DOCUMENTS'),
    // ...
],

'default_collection' => 'default',
'default_context'    => 'default',

'defaults' => [
    'image' => [
        'accepted_mime_types' => ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
        'accepted_extensions' => ['jpeg', 'jpg', 'png', 'gif', 'webp'],
        'max_file_size'       => 10240,
        'format'              => 'webp',
        'quality'             => 90,
        'variant_quality'     => 85,
        'max_width'           => 1920,
        'max_height'          => 1080,
        'variants' => [
            'thumbnail' => ['width' => 300,  'height' => 300],
            'medium'    => ['width' => 800,  'height' => 800],
            'large'     => ['width' => 1600, 'height' => 1600],
        ],
    ],
    'document' => [
        'accepted_mime_types' => ['application/pdf', 'application/msword', /* ... */],
        'accepted_extensions' => ['pdf', 'doc', 'docx', 'xls', 'xlsx'],
        'max_file_size'       => 20480,
    ],
    'audio' => [/* ... */],
    'video' => [/* ... */],
],

'collections' => [

    'avatar' => [
        'disk'      => 'media',
        'access'    => 'public',
        'single'    => true,                   // only 1 file per owner
        'component' => 'user-avatar',          // default blade component (optional)
        'types'     => [
            'image' => [
                'variants' => [
                    'small'  => ['width' => 64,  'height' => 64,  'fit' => true],
                    'medium' => ['width' => 128, 'height' => 128, 'fit' => true],
                ],
            ],
        ],
    ],

    'identity' => [
        'disk'      => 'documents',
        'access'    => 'stream',
        'sensitive' => true,                   // bind URL to the user
        'encrypt'   => true,                   // encrypt binary at rest
        'types'     => [
            'image' => [
                'variants' => [
                    'thumbnail' => ['width' => 300, 'height' => 300],            // no watermark
                    'display'   => ['width' => 1200, 'watermark' => true],       // watermarked
                ],
            ],
            'document' => [
                'preview' => ['page' => 1, 'width' => 2000],
            ],
        ],
    ],

    'temp_uploads' => [
        'disk'      => 'media',
        'access'    => 'public',
        'ttl_hours' => 24,                     // purged via command
    ],

],

'documents' => [
    // base — shared by every model that uses this collection
    'disk'   => 'documents',
    'access' => 'signed',
    'types'  => [
        'document' => ['preview' => ['page' => 1, 'width' => 1600]],
    ],

    'models' => [
        'case' => [
            // inherits everything above, plus:
            'path' => 'cases/{slug}/documents',
        ],
        'organization' => [
            'path' => 'orgs/{handle}/documents',
            // override puntual: en orgs no queremos preview
            'types' => [
                'document' => ['preview' => false],
            ],
        ],
    ],
],

'placeholders' => [
    'default'  => '/img/laracrate/file.svg',
    'image'    => '/img/laracrate/image.svg',
    'video'    => '/img/laracrate/video.svg',
    'audio'    => '/img/laracrate/audio.svg',
    'document' => '/img/laracrate/document.svg',
],

'image' => fn ($collection, $type, $model) => "/api/avatars/{$model->id}.svg",

'urls' => [
    'signed_ttl'             => 5,    // signed URL TTL in minutes (R2)
    'signed_cache_ttl'       => 4,    // server-side cache TTL of the signed URL
    'sensitive_redirect_ttl' => 10,   // ultra-short TTL after validation (seconds)
    'route_signed_ttl'       => 15,   // HMAC TTL for /files/{slug}/stream (minutes)
    'bind_to_user'           => true, // tie the URL to the current viewer when sensitive
],

'policies' => [
    'register_gate' => true,
],

@can('view', $file)
$user->can('update', $file)
$this->authorize('delete', $file)
Route::middleware('can:view,file')

'stream' => [
    'route_prefix'        => 'files',
    'route_name_prefix'   => 'laracrate.files',
    'middleware'          => ['web', 'auth'],
    'increment_downloads' => true,
    'log_access'          => true,
],

'status' => [
    'route_prefix' => 'laracrate/files',
    'middleware'   => ['web', 'auth'],
],

'multipart' => [
    'threshold'       => 100 * 1024 * 1024,  // 100 MB; the frontend decides when to use multipart
    'part_size'       => 10  * 1024 * 1024,  // 10 MB per part (S3 minimum is 5 MB)
    'expire_minutes'  => 60,                 // multipart session TTL
    'url_ttl_minutes' => 60,                 // presigned URL TTL per part
    'route_prefix'    => 'laracrate/multipart',
    'middleware'      => null,               // null inherits from uploads
],

'image' => [
    'driver'             => 'imagick',  // 'imagick' (recommended) or 'gd'
    'optimize_originals' => false,      // re-encode the original to webp with max dims
    'max_width'          => 1920,
    'max_height'         => 1920,
    'quality'            => 85,
],

'video' => [
    'max_width'    => 1920,
    'max_height'   => 1920,
    'bitrate_kbps' => 2500,
],

'encryption' => [
    'driver' => 'laravel',
],

'embeddings' => [
    'enabled'           => false,
    'provider'          => 'openai',
    'api_key'           => env('LARACRATE_EMBEDDINGS_API_KEY'),
    'model'             => env('LARACRATE_EMBEDDINGS_MODEL', 'text-embedding-3-small'),
    'dimensions'        => 1536,
    'chunk_size'        => 1000,
    'chunk_overlap'     => 100,
    'batch_size'        => 16,

    // Fallback chain of text extractors. Run in order; if one returns less
    // than `min_text_per_file` chars, the next is tried. Empty = built-in
    // defaults (PdfTextExtractor + PlainTextExtractor).
    'extractors' => [
        // \EduLazaro\Laracrate\Extractors\PdfTextExtractor::class,
        // \EduLazaro\Laracrate\Extractors\OcrPdfTextExtractor::class,
        // \EduLazaro\Laracrate\Extractors\PlainTextExtractor::class,
    ],
    'min_text_per_file' => 100,
],

'collections' => [
    'documents' => [
        'extract_text' => true,
        'embed'        => true,
        // ...
    ],
],

// AppServiceProvider::register()
$this->app->bind(
    \EduLazaro\Laracrate\Contracts\EmbeddingProvider::class,
    \App\Embeddings\MyCustomProvider::class
);

// Option A: declarative, via config (recommended).
'embeddings' => [
    'extractors' => [
        \EduLazaro\Laracrate\Extractors\PdfTextExtractor::class,
        \App\Extractors\MyOcrExtractor::class,
    ],
],

// Option B: imperative, registered at boot.
$registry = app(\EduLazaro\Laracrate\Support\TextExtractorRegistry::class);
$registry->add(new \App\Extractors\MyOcrExtractor());

'chunks' => [
    'driver' => env('LARACRATE_CHUNKS_DRIVER', 'mysql'),
],

'meilisearch' => [
    'index'    => env('LARACRATE_MEILISEARCH_INDEX', 'laracrate_file_chunks'),
    'embedder' => env('LARACRATE_MEILISEARCH_EMBEDDER', 'default'),
],

// AppServiceProvider::register()
$this->app->singleton(\Meilisearch\Client::class, fn () =>
    new \Meilisearch\Client(config('scout.meilisearch.host'), config('scout.meilisearch.key'))
);

// .env
LARACRATE_CHUNKS_DRIVER=meilisearch
LARACRATE_MEILISEARCH_INDEX=laracrate_file_chunks
LARACRATE_MEILISEARCH_EMBEDDER=default

// AppServiceProvider::register()
$this->app->bind(
    \EduLazaro\Laracrate\Contracts\ChunkStore::class,
    \App\Search\MyQdrantChunkStore::class
);

'ocr' => [
    'provider' => env('LARACRATE_OCR_PROVIDER', 'anthropic'),  // 'anthropic' | 'openai'

    'anthropic' => [
        'api_key' => env('LARACRATE_ANTHROPIC_API_KEY') ?: env('ANTHROPIC_API_KEY'),
        'model'   => env('LARACRATE_OCR_ANTHROPIC_MODEL', env('LARACRATE_OCR_MODEL', 'claude-haiku-4-5')),
    ],

    'openai' => [
        'api_key' => env('LARACRATE_OPENAI_API_KEY') ?: env('OPENAI_API_KEY'),
        'model'   => env('LARACRATE_OCR_OPENAI_MODEL', env('LARACRATE_OCR_MODEL', 'gpt-4o-mini')),
    ],
],

'embeddings' => [
    'extractors' => [
        \EduLazaro\Laracrate\Extractors\PdfTextExtractor::class,     // 1. smalot, free, instant
        \EduLazaro\Laracrate\Extractors\OcrPdfTextExtractor::class,  // 2. OCR fallback for scanned PDFs
        \EduLazaro\Laracrate\Extractors\PlainTextExtractor::class,   // 3. text/*
    ],
    'min_text_per_file' => 100,  // if smalot returns < 100 chars, fall back to OCR
],

'watermark' => [
    'image_path' => env('LARACRATE_WATERMARK_IMAGE', null),  // PNG to overlay
    'size'       => 0.40,                                    // 40% of the variant's width
    'opacity'    => 30,                                      // 0 to 100
    'position'   => 'center',

    'text' => [
        'content'         => null,                           // null, fixed string, or closure(File): ?string
        'font_size_ratio' => 0.0195,
        'color'           => 'rgba(255, 255, 255, 0.60)',
        'position'        => 'bottom-left',
        'padding'         => 20,
        'font_path'       => null,
    ],
],

'collections' => [
    'identity' => [
        'types' => [
            'image' => [
                'variants' => [
                    'thumbnail' => ['width' => 300, 'height' => 300],            // no watermark
                    'display'   => ['width' => 1200, 'watermark' => true],       // with watermark
                ],
            ],
        ],
    ],
],

'ui' => [
    'default_theme' => env('LARACRATE_THEME', 'default'),
],

'queue' => [
    'connection' => env('LARACRATE_QUEUE_CONNECTION', null),  // null uses Laravel's default
    'name'       => env('LARACRATE_QUEUE_NAME', 'default'),
],

use EduLazaro\Laracrate\Concerns\HasFiles;

class Property extends Model
{
    use HasFiles;

    // Per-model override (optional). Recursively merged with the global collection.
    protected array $fileCollections = [
        'gallery' => [
            'types' => [
                'image' => [
                    'variants' => [
                        'og' => ['width' => 1200, 'height' => 630, 'fit' => true, 'format' => 'jpg'],
                    ],
                ],
            ],
        ],
    ];
}

$property->addFile($request->file('image'), 'gallery', [
    'title' => 'Front facade',
    'label' => 'facade',
]);

use EduLazaro\Laracrate\Support\FileUpload;

Route::post('/properties/{property}/files', function (Request $request, Property $property) {
    $upload = FileUpload::fromArray($request->validate([
        'key'           => 'ery'));
});

$user->fileLink('avatar')                          // URL or configured placeholder
$user->fileLink('avatar', 'medium')                // medium variant
$user->fileLink('cover', 'preview.thumbnail')      // dot notation
$user->fileLink('cover', 'preview.small', 'image') // force a type

$user->fileRender('avatar', 'medium', ['class' => 'w-12 h-12'])
// produces <x-{component} :model="$user" :url="..." class="w-12 h-12" />

'collections' => [
    'avatar' => [
        'component' => 'user-avatar',
        // ...
    ],
],

$property->deleteFile($file);
$property->reorderFiles('gallery', $request->input('ids'));
$file->makeDefault();
$file->publish();
$file->unpublish();

use EduLazaro\Laracrate\Support\PolicyRegistry;

// AppServiceProvider::boot()
app(PolicyRegistry::class)
    ->viewable('property',   fn ($file, $user) => $user && $file->fileable->isOwnedBy($user))
    ->editable('property',   fn ($file, $user) => $user && $file->fileable->canEdit($user))
    ->deletable('property',  fn ($file, $user) => $user && $file->fileable->canEdit($user));

// AppServiceProvider::boot()
$registry = app(\EduLazaro\Laracrate\Support\FileActionRegistry::class);

// Add your own step
$registry->add(new \App\Files\Pipeline\VirusScanStep());

// Remove a default
$registry->remove(\EduLazaro\Laracrate\Pipeline\Steps\Image\OptimizeImageStep::class);

'collections' => [
    'documents' => [
        // ...
        'actions' => [
            \App\Pipeline\Steps\ClassifyDocumentStep::class,
        ],
        'models' => [
            // Optional: extra steps that only apply when the fileable
            // is a specific morph type. Cumulative with the top-level
            // 'actions' above — both run.
            'case'    => ['actions' => [\App\Pipeline\Steps\DetectDeadlinesStep::class]],
            'lawsuit' => ['actions' => [\App\Pipeline\Steps\AutofillLawsuitStep::class]],
        ],
    ],
],

namespace App\Files\Pipeline;

use App\Files\Actions\VirusScanAction;
use EduLazaro\Laracrate\Contracts\FileActionInterface;
use EduLazaro\Laracrate\Models\File;
use EduLazaro\Laractions\Action;

class VirusScanStep extends Action implements FileActionInterface
{
    public function supports(File $file): bool
    {
        return $file->creator_type === 'user'
            && in_array($file->collection, ['documents', 'attachments'], true);
    }

    public function priority(): int
    {
        return 5;
    }

    public function handle(File $file): void
    {
        VirusScanAction::create()->run(['file' => $file]);
    }
}

$file->key                                  // ltrim($file->path, '/'), the full key
$file->variantKey($newName)                 // build the key for a variant (variants/ subdir)
$file->siblingKey($newName)                 // build the key for a sibling (same dir)
$file->createVariant($name, $overrides)     // create a variant row inheriting parent scope

$schedule->command('laracrate:abort-stale-multipart')->hourly();
$schedule->command('laracrate:purge-expired')->hourly();

$model->files(?$collection = null)              // MorphMany (top-level only)
$model->file($collection)                       // first file ordered by default → latest
$model->images($collection)                     // shortcut of files($collection)->where(type, image)
$model->getFile($collection)                    // first file (alias)
$model->defaultFile($collection)                // file with default=true

$model->addFile($upload, $collection, $metadata = [])
$model->setFile($collection, $upload, $metadata = [])  // single, replaces the existing
$model->deleteFile($file, $forceDelete = false)
$model->reorderFiles($collection, $orderedIds)
$model->setDefaultFile($file)

$model->fileLink($collection, $variant = null, $forceType = null): ?string
$model->fileRender($collection, $variant = null, $attrs = []): HtmlString

$model->getCollectionConfig($collection): array
$model->getDiskFor($collection): string
$model->resolveFileTenant(): ?Model

// Relations
$file->parent
$file->children
$file->fileable
$file->creator
$file->tenant
$file->contents                                  // chunks from laracrate_file_contents

// Variants
$file->variant('preview.thumbnail')              // dot notation, falls back to ancestor
$file->variantOrFail('preview.thumbnail')        // throws if the chain breaks

// URLs
$file->url($forceType = null)                    // real URL or placeholder
$file->link                                      // accessor: alias of url()
$file->preview_link                              // accessor: variant('preview.thumbnail')->url('image')
$file->streamUrl()
$file->downloadUrl()
$file->placeholderFor('image')

// Storage
$file->key                                       // accessor: ltrim(path, '/')
$file->variantKey($newName)
$file->siblingKey($newName)
$file->createVariant($variantName, $overrides)

// State
$file->makeDefault()
$file->publish() / unpublish()
$file->isVariant() / isTopLevel() / isSensitive()
$file->isImage() / isVideo() / isAudio() / isDocument()
$file->createdByUser() / createdByAgent() / createdAutomatically()

// Extracted text (when embed)
$file->extractedText(): ?string                  // joins all chunks
$file->hasEmbeddings(): bool

// Authorization (delegates to PolicyRegistry)
$file->canView($user)
$file->canEdit($user)
$file->canDelete($user)

// Scopes
File::published()
File::unpublished()
File::default()
File::ordered()
File::topLevel()
File::withDescendants(2)
File::forTenant($tenant)

$manager = app(\EduLazaro\Laracrate\Services\StorageManager::class);

$manager->urlFor($file)                                       // delegates to GeneratePublicUrl/Signed/Stream
$manager->diskFor($file)                                      // Storage::disk for the File
$manager->readBinary($file)                                   // full binary contents
$manager->writeBinary($disk, $key, $content, $mime)
$manager->deleteFromBackend($disk, $key)
$manager->moveServerSide($disk, $fromKey, $toKey)             // S3 copyObject
$manager->batchDelete($disk, $keys)                           // up to 1000 keys per request
$manager->presignedUpload($disk, $key, $mime, $maxSize, $minutes = 15)
$manager->withLocalCopy($file, $callback)                     // safe temporary download

$manager->getCollectionConfig($collection): array
$manager->getTypeConfig($collection, $type): array
$manager->acceptsType($collection, $type): bool
$manager->driverOf($disk): string
$manager->s3ClientOf($disk): ?S3Client

$usage = app(\EduLazaro\Laracrate\Services\UsageReporter::class);

$stats = $usage->forTenant($organization);     // total bytes used by an org/tenant
$stats = $usage->forCreator($user);            // total bytes uploaded by a user
$stats = $usage->forCollection('documents');   // total bytes in one collection

$stats->bytes              // raw byte count (int)
$stats->files              // number of files (int)
$stats->byCollection       // ['avatar' => 12345, 'documents' => 9876543, ...]
$stats->human()            // "1.42 GB"
$stats->exceeds($limit)    // bool, useful for quota checks
bash
composer vendor:publish --tag=laracrate-config
php artisan migrate
blade
{{-- resources/views/components/user-avatar.blade.php --}}
@props(['model', 'url'])

@if($url)
    <img src="{{ $url }}" {{ $attributes->merge(['class' => 'rounded-full']) }} alt="{{ $model->name }}">
@else
    <div {{ $attributes->merge(['class' => 'rounded-full bg-gray-300 flex items-center justify-center text-white']) }}>
        {{ strtoupper(mb_substr($model->name, 0, 1)) }}
    </div>
@endif
bash
php artisan vendor:publish --tag=laracrate-views