Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sluggable Laravel Package

bpocallaghan/sluggable

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require bpocallaghan/sluggable
    

    No additional configuration is required beyond adding the trait to your model.

  2. Basic Usage: Add the HasSlug trait to any Eloquent model:

    use Bpocallaghan\Sluggable\HasSlug;
    
    class Post extends Model
    {
        use HasSlug;
    }
    

    The slug will auto-generate from the name attribute (default) and save to a slug column (default).

  3. First Use Case:

    $post = new Post(['name' => 'My Awesome Post']);
    $post->save();
    // $post->slug now contains "my-awesome-post"
    

Where to Look First

  • Trait Documentation: Focus on HasSlug and SlugOptions in the README.
  • Default Behavior: Understand that the package uses str_slug() (Laravel’s built-in helper) for generation and defaults to nameslug.
  • Migration: Ensure your database table has a slug column (e.g., string or text).

Implementation Patterns

Core Workflows

  1. Default Slug Generation:

    // Auto-generates slug from 'title' and saves to 'slug_field'
    class Article extends Model
    {
        use HasSlug;
    
        protected function getSlugOptions()
        {
            return SlugOptions::create()
                ->generateSlugFrom('title')
                ->saveSlugTo('slug_field');
        }
    }
    
  2. Dynamic Slug Sources: Use computed attributes or relationships for slugs:

    class Product extends Model
    {
        use HasSlug;
    
        public function getSlugSourceAttribute()
        {
            return "{$this->name} ({$this->category->name})";
        }
    
        protected function getSlugOptions()
        {
            return SlugOptions::create()
                ->generateSlugFrom('slug_source');
        }
    }
    
  3. Custom Separators/Transformations: Override str_slug behavior via SlugOptions:

    protected function getSlugOptions()
    {
        return SlugOptions::create()
            ->slugSeparator('_')
            ->generateSlugFrom('name')
            ->lowercaseSlug(true); // Optional: Force lowercase
    }
    
  4. Manual Slug Updates: Trigger slug regeneration without saving:

    $post->updateSlug(); // Manually regenerate slug
    

Integration Tips

  • Form Handling: Use updateSlug() in form submissions to refresh slugs if the source field changes:

    public function update(StorePostRequest $request)
    {
        $post->fill($request->validated());
        $post->updateSlug(); // Ensure slug is updated
        $post->save();
    }
    
  • API Responses: Include slugs in API resources:

    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'slug' => $this->slug, // Auto-included
            'title' => $this->title,
        ];
    }
    
  • Scopes/Queries: Use slugs for SEO-friendly routes or scopes:

    public function scopeActive($query)
    {
        return $query->where('is_active', true)
                    ->where('slug', 'like', '%active%');
    }
    
  • Testing: Mock slug generation in tests:

    $post = new Post(['name' => 'Test Post']);
    $post->shouldReceive('generateSlug')->andReturn('test-post');
    $post->save();
    

Gotchas and Tips

Pitfalls

  1. Column Mismatch:

    • Error: Column 'slug' does not exist.
    • Fix: Ensure your migration includes the slug column (or override saveSlugTo()).
    • Tip: Use Schema::string('slug') or Schema::text('slug') for the column.
  2. Duplicate Slugs:

    • Behavior: The package appends a hyphen and number to duplicates (e.g., my-post-2).
    • Customization: Override SlugOptions::duplicateSlugSeparator() if needed:
      ->duplicateSlugSeparator('-v') // Generates "my-post-v2"
      
  3. Case Sensitivity:

    • Issue: Str::slug() is case-insensitive by default, but some databases (e.g., MySQL with utf8mb4_bin) treat slugs as case-sensitive.
    • Fix: Use ->lowercaseSlug(true) in SlugOptions or enforce case in migrations:
      $table->string('slug')->collation('utf8mb4_general_ci');
      
  4. Relationship Delays:

    • Problem: Slugs generated from relationships may fail if the relationship isn’t loaded.
    • Solution: Eager-load relationships or use with():
      $post->load('category'); // Ensure relationship is loaded
      $post->save();
      
  5. Mass Assignment:

    • Risk: Slugs can be overwritten if not protected in $fillable.
    • Fix: Exclude slug from $fillable or use $guarded:
      protected $fillable = ['name', 'content']; // Exclude 'slug'
      

Debugging

  • Log Slug Generation: Temporarily add a generatingSlug event listener to debug:

    Sluggable::generatingSlug(function ($model, $slug) {
        \Log::debug("Generating slug for {$model->class}: {$slug}");
    });
    
  • Check for Overrides: Ensure no other package or model method is interfering with save() or generateSlug().

Extension Points

  1. Custom Slug Logic: Override the generateSlug() method in your model:

    protected function generateSlug($value)
    {
        return parent::generateSlug($value) . '-custom';
    }
    
  2. Event Hooks: Listen for sluggable.slug.generated events:

    Sluggable::slugGenerated(function ($model, $slug) {
        // Post-process the slug (e.g., cache it)
    });
    
  3. Database-Level Uniqueness: Add a unique index to the slug column in migrations:

    $table->string('slug')->unique();
    
    • Note: The package handles duplicates via appending numbers, but a unique index enforces this at the database level.
  4. Localization: Generate language-specific slugs by overriding getSlugOptions() dynamically:

    protected function getSlugOptions()
    {
        return Sluggable::create()
            ->generateSlugFrom("title_{$this->locale}");
    }
    

Performance Tips

  • Avoid Regeneration: Skip slug generation if the source attribute hasn’t changed:

    public function save(array $options = [])
    {
        if (!$this->isDirty('name')) {
            $options['skipSlugGeneration'] = true;
        }
        return parent::save($options);
    }
    
  • Batch Processing: For bulk operations, regenerate slugs in a queue job to avoid timeouts:

    Post::chunk(100, function ($posts) {
        foreach ($posts as $post) {
            $post->updateSlug();
            $post->save();
        }
    });
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope