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

Laravel Sluggable Trait Laravel Package

martinbean/laravel-sluggable-trait

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require martinbean/laravel-sluggable-trait
    

    No additional configuration is required for basic usage.

  2. Apply the Trait: Add the Sluggable trait to your Eloquent model:

    use MartinBean\Database\Eloquent\Sluggable;
    
    class Post extends Model
    {
        use Sluggable;
    }
    

    This automatically generates a slug from the name column and stores it in the slug column.

  3. First Use Case: Save a model instance with a name attribute:

    $post = new Post(['name' => 'How to Build a Laravel App']);
    $post->save(); // Automatically generates slug: "how-to-build-a-laravel-app"
    

Where to Look First

  • README: Focus on the Usage section for basic setup.
  • Source Code: Review MartinBean\Database\Eloquent\Sluggable for default behavior.
  • Tests: Check the test cases for edge cases (e.g., duplicate slugs, custom logic).

Implementation Patterns

Basic Workflow

  1. Default Behavior:

    • Slugs are generated from the name column and stored in the slug column.
    • Uses Laravel’s built-in Str::slug() for conversion (e.g., "Hello World""hello-world").
  2. Custom Slug Sources: Override getSluggableString() to derive slugs from multiple fields:

    protected function getSluggableString()
    {
        return "{$this->category->name} - {$this->title}";
    }
    
  3. Custom Slug Columns: Override getSlugColumnName() to use a non-standard column:

    protected function getSlugColumnName()
    {
        return 'url_friendly_name';
    }
    
  4. Manual Slug Updates: Force a slug update without saving the model:

    $post->updateSlug(); // Manually trigger slug generation.
    

Integration Tips

  • Form Handling: Use the trait with Laravel’s Form Request validation to ensure slug uniqueness:

    public function rules()
    {
        return [
            'name' => 'required|string',
            'slug' => 'required|unique:posts,slug,' . $this->post->id,
        ];
    }
    
  • API Responses: Include slugs in API responses via App\Http\Resources\PostResource:

    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'title' => $this->name,
            'slug' => $this->slug, // Automatically populated.
        ];
    }
    
  • SEO Meta Tags: Dynamically generate meta titles/descriptions using the slug:

    public function getMetaTitle()
    {
        return "{$this->name} | {$this->slug}";
    }
    
  • Scoped Routes: Use slugs in route definitions:

    Route::get('/posts/{post:slug}', [PostController::class, 'show']);
    
  • Observers/Events: Listen for saved events to log slug changes:

    public function saved(Model $model)
    {
        if ($model instanceof Post) {
            Log::info("Slug updated to: {$model->slug}");
        }
    }
    

Gotchas and Tips

Pitfalls

  1. Visibility Change (v0.4.0+):

    • Older code using public methods (getSlugColumnName(), getSluggableString()) will fail.
    • Fix: Update to protected or alias the methods:
      public function getSlugColumnName()
      {
          return $this->getSlugColumnNameProtected();
      }
      protected function getSlugColumnNameProtected() { ... }
      
  2. Duplicate Slugs:

    • The trait appends a hyphen and number to duplicates (e.g., post-2).
    • Debugging: Check for collisions in MartinBean\Database\Eloquent\Sluggable::generateSlug().
  3. Case Sensitivity:

    • Slugs are lowercase by default. Override Str::slug() behavior by extending the trait:
      use MartinBean\Database\Eloquent\Sluggable as BaseSluggable;
      
      class CustomSluggable extends BaseSluggable {
          protected function generateSlug($string)
          {
              return Str::slug($string, '-', ['å' => 'a']); // Custom replacements.
          }
      }
      
  4. Mass Assignment:

    • Slugs are not mass-assignable by default. Explicitly add to $fillable if needed:
      protected $fillable = ['name', 'slug'];
      
  5. Database Migrations:

    • Ensure the slug column exists and is indexed for performance:
      $table->string('slug')->unique()->index();
      

Debugging Tips

  • Log Slug Generation: Temporarily override generateSlug() to log input/output:

    protected function generateSlug($string)
    {
        \Log::debug("Generating slug from: {$string}");
        return parent::generateSlug($string);
    }
    
  • Check for Overrides: Use method_exists() to verify trait methods are not being overridden incorrectly:

    if (method_exists($model, 'getSlugColumnName')) {
        \Log::info("Custom slug column: {$model->getSlugColumnName()}");
    }
    

Extension Points

  1. Custom Slug Logic: Extend the trait to add validation or transformations:

    class ExtendedSluggable extends BaseSluggable {
        protected function getSluggableString()
        {
            $string = parent::getSluggableString();
            return preg_replace('/[^a-z0-9\s-]/', '', $string); // Sanitize.
        }
    }
    
  2. Soft Deletes: Handle slug uniqueness for soft-deleted models by excluding them from checks:

    protected function getSlugColumnName()
    {
        return 'slug';
    }
    
    protected function getUniqueSlugConstraints()
    {
        return ['slug', $this->getKey(), 'deleted_at', null];
    }
    
  3. Localization: Generate language-specific slugs by overriding generateSlug():

    protected function generateSlug($string)
    {
        return Str::slug(app()->getLocale() === 'es' ? $string : __($string));
    }
    
  4. Caching: Cache slugs to avoid regeneration on every save:

    public function save(array $options = [])
    {
        if (!$this->exists || $this->isDirty('name')) {
            $this->slug = $this->generateSlug($this->getSluggableString());
        }
        return parent::save($options);
    }
    
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.
nasirkhan/laravel-sharekit
directorytree/privacy-filter-classifier
directorytree/privacy-filter
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata
splash/openapi
splash/scopes
splash/toolkit
testo/output-teamcity
testo/bridge-symfony