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 Publishable Laravel Package

novius/laravel-publishable

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps

  1. Installation:

    composer require novius/laravel-publishable
    php artisan vendor:publish --provider="Novius\Publishable\LaravelPublishableServiceProvider" --tag=lang
    
  2. Add Migration Macro: Update your model migration to include the publishable() macro:

    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->text('content');
        $table->timestamps();
        $table->publishable(); // Adds status, published_at, and published_first_at fields
    });
    
  3. Apply Migration:

    php artisan migrate
    
  4. Use the Trait: Add the Publishable trait to your Eloquent model:

    namespace App\Models;
    
    use Illuminate\Database\Eloquent\Model;
    use Novius\Publishable\Publishable;
    
    class Post extends Model
    {
        use Publishable;
    }
    
  5. First Use Case: Publish a draft post:

    $post = Post::create(['title' => 'Hello World', 'content' => '...']);
    $post->publish(); // Transitions from 'draft' to 'published'
    

Where to Look First

  • Package Documentation: Focus on the Publishable trait methods and query scopes in the README.
  • Migration Macro: Understand the publishable() macro’s generated columns (status, published_at, published_first_at).
  • Query Scopes: Test the built-in scopes (published(), notPublished(), onlyDrafted(), etc.) for filtering.

Implementation Patterns

Usage Patterns

  1. State Transitions: Use the trait’s methods to manage state transitions:

    $post = Post::find(1);
    $post->draft();      // Set to 'draft'
    $post->publish();    // Set to 'published'
    $post->unpublish();  // Set to 'unpublished'
    $post->schedule($date); // Set to 'scheduled' with a future date
    
  2. Query Filtering: Leverage query scopes for consistent filtering:

    // Get all published posts
    $publishedPosts = Post::published()->get();
    
    // Get only drafted posts
    $drafts = Post::onlyDrafted()->get();
    
    // Get posts that will be published in the future
    $scheduledPosts = Post::onlyWillBePublished()->get();
    
  3. Scheduled Publishing: Schedule a post to publish at a future date:

    $post = Post::create(['title' => 'Event Announcement', 'content' => '...']);
    $post->schedule(now()->addDays(7)); // Publish in 7 days
    
  4. Conditional Logic: Check a post’s state in business logic:

    if ($post->isPublished()) {
        // Show to public
    } elseif ($post->isDraft()) {
        // Show in admin panel only
    }
    

Workflows

  1. Content Moderation Workflow:

    • Draft: Create and save as draft ($post->draft()).
    • Review: Admin reviews draft in Nova/Livewire.
    • Publish: Admin publishes ($post->publish()).
    • Unpublish: Admin unpublishes ($post->unpublish()) if needed.
    • Schedule: Set future publish date ($post->schedule($date)).
  2. API Endpoints:

    • GET /posts/published: Return only published posts.
    • POST /posts/{id}/publish: Transition a post to published.
    • GET /admin/posts/drafts: Show drafts in admin panel.
  3. Caching: Cache published content with tags for invalidation:

    Cache::tags(['posts'])->put('post-' . $post->id, $post, now()->addHours(1));
    event(new PostPublished($post)); // Trigger cache invalidation
    

Integration Tips

  1. Nova Integration: Extend the package with laravel-nova-publishable for Nova toolbars and filters:

    composer require novius/laravel-nova-publishable
    

    Configure Nova resources to use the publishable fields.

  2. Validation: Add validation rules for state transitions:

    use Illuminate\Validation\Rule;
    
    $validator = Validator::make($request->all(), [
        'status' => [
            Rule::in(['draft', 'published', 'unpublished', 'scheduled']),
        ],
    ]);
    
  3. Observers: Use model observers to log state changes:

    class PostObserver
    {
        public function saved(Post $post)
        {
            if ($post->wasRecentlyCreated && $post->isPublished()) {
                Log::info("New post published: {$post->title}");
            }
        }
    }
    
  4. Testing: Test state transitions and query scopes:

    public function test_publish_post()
    {
        $post = Post::factory()->create(['status' => 'draft']);
        $post->publish();
        $post->refresh();
        $this->assertTrue($post->isPublished());
    }
    
  5. Seeding: Seed test data with different states:

    Post::factory()->create(['status' => 'published']);
    Post::factory()->create(['status' => 'draft']);
    Post::factory()->create(['status' => 'scheduled', 'published_at' => now()->addDay()]);
    

Gotchas and Tips

Pitfalls

  1. Global Scope Removal: The package removed the global scope in v2.0, meaning queries default to all states unless filtered. Always explicitly use scopes like published():

    // ❌ Avoid: Assumes global scope (deprecated)
    $posts = Post::all();
    
    // ✅ Correct: Explicitly filter
    $posts = Post::published()->get();
    
  2. Timezone Handling: Scheduled posts use Laravel’s timezone (config('app.timezone')). Ensure consistency across environments:

    $post->schedule(now()->timezone('UTC')->addDays(7));
    
  3. Database Indexes: For large datasets, add indexes to status and published_at columns to optimize queries:

    Schema::table('posts', function (Blueprint $table) {
        $table->index('status');
        $table->index('published_at');
    });
    
  4. Soft Deletes Conflict: If using SoftDeletes, ensure logic handles both deleted_at and status:

    // Example: Check if a post is truly "visible"
    public function isVisible()
    {
        return !$this->isDeleted && $this->isPublished();
    }
    
  5. Backfilling published_first_at: When migrating existing data, populate published_first_at for published posts:

    DB::table('posts')
        ->where('status', 'published')
        ->whereNull('published_first_at')
        ->update(['published_first_at' => DB::raw('created_at')]);
    

Debugging

  1. State Transition Issues: If a post isn’t transitioning states, check:

    • The status column is being updated (use dd($post->fresh()->status)).
    • No custom accessors/mutators are interfering.
  2. Query Scope Not Working: Verify the scope is called on the query builder:

    // ❌ Wrong: Calls scope on model instance
    Post::published()->get(); // Correct
    $post = Post::first();
    $post->published(); // ❌ Returns boolean, not query builder
    
  3. Scheduled Posts Not Triggering: Ensure published_at is set and the current time is checked correctly:

    $post = Post::where('status', 'scheduled')
        ->where('published_at', '<=', now())
        ->first();
    

Config Quirks

  1. Custom States: The package enforces 4 states (draft, published, unpublished, scheduled). To extend:

    // Override the trait's getStatusAttribute or use a custom trait
    protected function getStatusAttribute()
    {
        return $this->attributes['status'] ?? 'draft';
    }
    
  2. Published First At Logic: published_first_at defaults to created_at for published posts. Override in a model observer:

    public function saved(Post $post)
    {
        if ($post->isPublished() && $post->wasRecentlyCreated) {
            $post->update(['published_first_at' => now()]);
        }
    }
    
  3. Nova Toolbar Integration: If using Nova, ensure the laravel-nova-publishable package is configured to display the correct toolbars for each state.

Extension Points

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