shammaa/laravel-smart-glide

Advanced image processing toolkit for Laravel with unified /img routing, responsive components, and intelligent caching.

Installs: 28

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/shammaa/laravel-smart-glide

2.0.0 2025-12-12 09:03 UTC

This package is auto-updated.

Last update: 2025-12-12 09:06:14 UTC


README

Advanced image processing toolkit for Laravel with unified /img routing, responsive components, and intelligent caching.

Features

  • 🔐 Signed URLs & Validation — Protect image transformations with HMAC signatures, strict parameter validation, and extension whitelisting.
  • 🖼️ Responsive Components — Blade components generate srcset, sizes, and background media queries automatically.
  • ⚙️ Smart Profiles — Define reusable WebP/AVIF compression presets and override them from an external PHP profile file.
  • 📦 Unified Delivery Path — All image responses flow through /img/..., simplifying CDNs, caching rules, and observability.
  • 🧠 Intelligent Caching — LRU-inspired cache manifest with size budgets, warmup metadata, and background eviction.
  • 🧾 SEO-Ready — Configurable attributes (loading, fetchpriority, aria, title, etc.) plus optional JSON-LD ImageObject snippets.
  • 🛡️ Security Rules — Block remote sources (optional), enforce max width/height/quality ranges, and stricter MIME filters.
  • 🧰 DX Friendly — Publishable config, auto-discovered components, and clean service provider wiring.

Requirements

  • PHP 8.3+
  • Laravel 11.x or 12.x
  • GD or Imagick PHP extension
  • League Glide 3.x (installed automatically)

Installation

composer require shammaa/laravel-smart-glide

The service provider and components are auto-discovered. If discovery is disabled, register the provider manually in bootstrap/app.php (Laravel 11+):

return Application::configure(basePath: dirname(__DIR__))
    ->withProviders([
        Shammaa\SmartGlide\SmartGlideServiceProvider::class,
    ])
    ->create();

Publish the configuration file when you want to customize paths, caching, or SEO defaults:

php artisan vendor:publish --tag=smart-glide-config

This creates config/smart-glide.php in your application.

Environment Setup

  1. Signing keys
    APP_KEY=base64:...
    # Optional dedicated key
    SMART_GLIDE_SIGNATURE_KEY=base64:...
    After modifying the .env file, always clear cached configuration:
    php artisan config:clear
    php artisan cache:clear
  2. Filesystem
    • Originals default to storage/app/public.
    • Processed images live in storage/app/smart-glide-cache.
    • Run php artisan storage:link once if your front-end needs to serve the originals.
  3. Permissions Make sure the cache directory is writable by your web server user.

Quick Start

  1. Prepare assets
    Place an image inside storage/app/public (default source path). If you need public access to the originals, create the symlink once via php artisan storage:link.
  2. Drop the component
    <x-smart-glide-img src="team/photo.jpg" alt="Team portrait" width="960" height="640" />
  3. Load the page
    The rendered <img> tag now contains a fully signed src, responsive srcset, sizes, and fixed width/height for layout stability. All requests resolve to /img/team/photo.jpg?....
  4. Need a different source path?
    Edit config/smart-glide.php ('source' => ...) and clear configuration cache:
    php artisan config:clear
    php artisan cache:clear

Verify in Tinker

php artisan tinker
>>> app(Shammaa\SmartGlide\Support\SmartGlideManager::class)->deliveryUrl('team/photo.jpg', ['w' => 640])

Opening the returned URL in a browser should yield the processed image (HTTP 200). A 403 usually indicates an invalid or missing signature—double‑check your APP_KEY/SMART_GLIDE_SIGNATURE_KEY and clear cached config.

Facade Usage

Resolve URLs anywhere in your Laravel app through the SmartGlide facade. This is convenient for tables, JSON APIs, or Livewire components where you need pre-signed image URLs without rendering Blade components.

use Shammaa\SmartGlide\Facades\SmartGlide;

$url = SmartGlide::croppedUrl(
    path: 'team/photo.jpg',
    width: 160,
    height: 160,
    parameters: [
        'profile' => 'thumbnail', // merges preset params (quality, format, etc.)
        'focus' => 'faces',       // any additional Glide parameters
    ],
);

In a DataTable JSON feed:

return TeamMember::query()
    ->select(['id', 'name', 'photo_path'])
    ->get()
    ->map(fn ($member) => [
        'id' => $member->id,
        'name' => $member->name,
        'photo' => SmartGlide::croppedUrl($member->photo_path, 96, 96, [
            'profile' => 'thumbnail',
        ]),
    ]);

Then render it in JavaScript:

$('#team-table').DataTable({
    columns: [
        { data: 'name' },
        {
            data: 'photo',
            render: (data, type, row) => type === 'display'
                ? `<img src="${data}" alt="${row.name}" class="h-12 w-12 rounded-full object-cover" />`
                : data,
            orderable: false,
            searchable: false,
        },
    ],
});

Configuration Overview

The config file (config/smart-glide.php) exposes several groups:

Key Description
source, cache, delivery_path Control filesystem paths and the unified /img endpoint.
security Toggle URL signing, allowed formats, max dimensions, quality ranges, and remote sources.
profiles & profile_file Define reusable transformation presets; merge an external PHP file at runtime.
breakpoints Global widths for automatic srcset generation.
responsive_sets Named breakpoint aliases usable from the components.
cache_strategy Enforce cache size (MB) and LRU time window.
cache_headers Configure browser cache days and enable ETag responses.
logging Enable processing logs and choose the log channel.
seo Default attributes for <img> / background components and structured data behaviour.

Tip: set SMART_GLIDE_SCHEMA_ENABLED=true to emit JSON-LD ImageObject snippets for every component by default.

Default Profiles & Responsive Sets

Profiles (config('smart-glide.profiles'))

Name Description
default Outputs WebP at quality 82.
thumbnail 320×320 crop for avatars or cards.
hero 1600×900 centered crop for hero banners.
portrait 800×1200 crop prioritising faces.
square 600×600 crop for grids.
profile_photo 400×400 crop focused on faces (avatars).
cover 2048×1152 crop suitable for cover art.
background 2560×1440 max-fit background-friendly rendition.

Use them via profile="hero" or override/add more in your config file.

Responsive sets (config('smart-glide.responsive_sets'))

Name Widths
hero 640, 960, 1280, 1600, 1920
thumbnails 240, 320, 480
square 320, 480, 640
portrait 480, 768, 1024
hd 960, 1280, 1600
fhd 1280, 1600, 1920, 2560
retina 640, 960, 1280, 1920, 2560

Reference them with responsive="retina" or specify an inline array.

Blade Components

<x-smart-glide-img>

Render responsive <img> tags with minimal boilerplate.

<x-smart-glide-img
    src="portfolio/hero.jpg"
    profile="hero"
    alt="Product hero image"
    class="rounded-xl shadow-lg"
    style="display:block"
    aspect-ratio="16:9"
    :params="['fit' => 'crop', 'focus' => 'center']"
    :seo="[
        'fetchpriority' => 'high',
        'title' => 'Hero Image',
        'data-license' => 'CC-BY-4.0',
    ]"
    schema
/>

Common properties

Prop Type Description
src string Relative path (inside smart-glide.source).
profile string Apply a config profile (profiles.hero).
params array Additional Glide parameters (width, height, etc.).
style string Inline styles applied to <img>.
aspect-ratio string Aspect ratio hint (e.g. 16:9, 1.5).
width / height int Native dimension attributes for layout stability.
seo array Extra HTML attributes (fetchpriority, title, itemprop, …).
schema bool Force JSON-LD emission for this image only.

Structured data respects component-level overrides and merges extra fields from config('smart-glide.seo.structured_data.fields').

View Overrides & Fallback Rendering

When you publish the package views, make sure resources/views/vendor/smart-glide/components/img.blade.php is kept in sync with the version shipped by the package. If it is removed or left empty, the component now falls back to rendering the <img> tag inline, so URLs remain signed even with an incomplete override. You can safely delete the override to restore the default template.

How srcset Is Generated

  • Smart Glide reads breakpoint widths from config('smart-glide.breakpoints') by default.
  • For each width, the component clones your parameters, overrides w, and calls the delivery URL to produce entries like /img/photo.jpg?w=640&... 640w.
  • The generated HTML will look similar to:
    <img
        src="/img/photo.jpg?w=960&fm=webp&q=82&s=..."
        srcset="/img/photo.jpg?w=360&... 360w, /img/photo.jpg?w=640&... 640w, ..."
        sizes="(max-width: 360px) 100vw, (max-width: 640px) 100vw, ..."
    >
  • Update the breakpoints globally by editing config('smart-glide.breakpoints'), or define named presets in responsive_sets for reuse (responsive="hero").
  • Override widths without touching the config:
    <x-smart-glide-img src="gallery/piece.jpg" :responsive="[320, 640, 960]" />
  • Use a named preset from config('smart-glide.responsive_sets'):
    <x-smart-glide-img src="hero.jpg" responsive="hero" />
  • Disable srcset entirely when you want a single rendition:
    <x-smart-glide-img src="logo.png" :responsive="false" />
  • If you need full control, set the attributes manually via :seo="['sizes' => '...']" or ['srcset' => '...'].

<x-smart-glide-bg>

Responsive background helper with media queries and placeholders.

<x-smart-glide-bg
    src="banners/summit.jpg"
    profile="hero"
    class="hero-banner"
    alt="Annual summit banner"
    lazy="true"
    :seo="['data-license' => 'Internal use only']"
    responsive="hero"
/>

The component outputs a wrapper <div> with inline styles plus an optional <style> block for breakpoint-specific backgrounds. A blurred placeholder overlay is included when lazy is true.

<x-smart-glide-picture>

Responsive <picture> helper that lets you define multiple <source> breakpoints and a Smart Glide powered fallback.

<x-smart-glide-picture
    src="gallery/feature.jpg"
    alt="Feature image"
    class="feature-picture"
    aspect-ratio="1:1"
    :sources="[
        ['media' => '(min-width: 1200px)', 'widths' => [1200], 'params' => ['fit' => 'crop', 'w' => 1200, 'h' => 675]],
        ['media' => '(min-width: 768px)', 'widths' => [900], 'params' => ['w' => 900, 'h' => 506]],
    ]"
/>

Each source entry accepts:

Key Description
media Optional media query for the <source> tag.
src Override image path for this source (defaults to component src).
params Glide parameters merged with the base parameters.
profile Profile name to merge with parameters.
widths / responsive Array or preset used to generate the srcset.
sizes Custom sizes attribute.
type MIME type hint for the source.
srcset Custom array of descriptors if you need full control.
img-class / img-style Styling for the fallback <img>.
img-width / img-height Native width/height attributes on fallback <img>.
width / height Set attributes on the wrapping <picture>.
aspect-ratio Sets CSS aspect-ratio on fallback <img>.

The internal <img> fallback inherits Smart Glide features (SEO attributes, structured data, signed URLs) and can be styled separately via img-class.

Picture Presets

Skip repeating :sources="[]" arrays in every template by defining presets in config('smart-glide.picture_presets'):

'picture_presets' => [
    'feature' => [
        ['media' => '(min-width: 1200px)', 'widths' => [1200], 'params' => ['fit' => 'crop', 'w' => 1200, 'h' => 675]],
        ['media' => '(min-width: 768px)', 'widths' => [900], 'params' => ['fit' => 'crop', 'w' => 900, 'h' => 506]],
    ],
],

Use the preset in Blade:

<x-smart-glide-picture
    preset="feature"
    src="{{ $article->featured_image }}"
    alt="{{ $article->title }}"
    aspect-ratio="16:9"
/>

You can still pass :sources="[]" along with a preset; they will be appended for page-specific overrides.

Signing & Security

  • URLs are signed with HMAC using SMART_GLIDE_SIGNATURE_KEY or APP_KEY when security.secure_urls is enabled.
  • Parameters are sanitized and validated against maximum width/height and quality limits.
  • Remote image sources are rejected by default; enable them per environment with SMART_GLIDE_ALLOW_REMOTE=true.
  • Allowed output formats (fm) and file extensions are configurable to avoid unsafe file types.

Requests with invalid signatures or disallowed parameters return 403/400 responses automatically.

Smart Cache

Smart Glide stores processed images in the configured cache path and tracks metadata in a manifest stored in your cache store.

  • max_size_mb limits total cache footprint.
  • lru_window determines how far back "least recently used" entries remain before eviction.
  • Metadata includes cached_at, last_accessed, and cache_file references to support background cleanup.

Responses include browser cache headers (Cache-Control, Expires) and optional ETag. HTTP clients respecting If-None-Match will receive 304 Not Modified when appropriate.

External Profiles

To allow non-developers to tweak compression parameters, create a PHP file that returns an array:

<?php

return [
    'hero' => ['w' => 1920, 'h' => 1080, 'fm' => 'webp', 'q' => 80],
    'thumbnail' => ['w' => 320, 'h' => 320, 'fit' => 'crop', 'q' => 72],
];

Point SMART_GLIDE_PROFILE_FILE to this file. Its definitions merge on top of the inline profiles config.

CLI Helpers

Add these convenience commands to your application (optional suggestions):

// app/Console/Commands/ClearSmartGlideCache.php

Then run:

php artisan smart-glide:clear-cache

(Command scaffolding is not bundled yet; feel free to implement it following your workflow.)

Scheduled Cache Purge

The package now ships with a built-in Artisan command and scheduler hook:

  • Configure the daily purge time via SMART_GLIDE_CACHE_PURGE_TIME (default 03:00).
  • Smart Glide registers smart-glide:clear-cache automatically; when the scheduler is enabled (php artisan schedule:run), cached renditions are cleared daily at the configured time.
  • Run manually with php artisan smart-glide:clear-cache --force.

Testing

composer test          # PHPUnit (requires Laravel testing harness)
composer test-coverage # Coverage (if configured)
composer analyse       # Static analysis (PHPStan/Psalm if added)

Because this is a Laravel package, use Orchestra Testbench for isolated package tests.

Roadmap Ideas

  • Remote URL whitelisting & SSRF hardening helpers.
  • Middleware abstractions for header customization.
  • First-class Artisan commands (cache clear, warmup, manifest stats).
  • Vue/React components for hybrid stacks.

Contributions and issues are welcome once the package is public.

Troubleshooting

Symptom Likely Cause Resolution
403 Smart Glide signature missing/invalid APP_KEY or SMART_GLIDE_SIGNATURE_KEY changed, or URL copied without the signed query string. Restore the original key, run php artisan config:clear & php artisan cache:clear, and always use URLs generated by Smart Glide.
Image returns 404 Asset not found inside config('smart-glide.source'). Move the file into the source directory (default storage/app/public) or update the config accordingly.
Links must be absolute APP_URL missing or incorrect. Set APP_URL in .env; deliveryUrl() honours it when producing URLs.
Cached variant stale Old rendition remains in storage/app/smart-glide-cache. Delete the cached file or add a lightweight Artisan command to purge Smart Glide cache entries.

License

Released under the MIT License.