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: Modify your model’s migration to include the publishable() macro:
    Schema::create('posts', function (Blueprint $table) {
        $table->id();
        $table->string('title');
        $table->text('body');
        $table->timestamps();
        $table->publishable(); // Adds status, published_at, published_first_at
    });
    
  3. Apply Migration:
    php artisan migrate
    
  4. Use the Trait: Attach the Publishable trait to your Eloquent model:
    use Novius\Publishable\PublishableTrait;
    
    class Post extends Model
    {
        use PublishableTrait;
    }
    
  5. First Query: Fetch published posts with the built-in scope:
    $publishedPosts = Post::published()->get();
    

First Use Case

Publish a Draft Post:

$post = Post::create([
    'title' => 'Hello World',
    'body' => 'This is a draft.',
    // Status defaults to 'draft'
]);

// Publish immediately
$post->publish();

// Or schedule for later
$post->schedule(Publishable::STATUS_PUBLISHED, now()->addDay());

Implementation Patterns

Core Workflows

1. State Transitions

Use the trait’s methods to transition between states:

$post = Post::find(1);

// Draft → Published
$post->publish();

// Published → Unpublished
$post->unpublish();

// Draft → Scheduled
$post->schedule(Publishable::STATUS_PUBLISHED, now()->addHours(2));

// Scheduled → Published (auto-triggered by package logic)

2. Query Filtering

Leverage scopes for common queries:

// All published posts
$published = Post::published()->get();

// Only drafts
$drafts = Post::onlyDrafted()->get();

// Posts that will publish in the future
$scheduled = Post::onlyWillBePublished()->get();

// Posts that were published but are now expired (if using TTL)
$expired = Post::onlyExpired()->get();

3. Model Events

Extend the trait to hook into state changes:

use Novius\Publishable\PublishableTrait;

class Post extends Model
{
    use PublishableTrait;

    protected static function booted()
    {
        static::published(function (Post $post) {
            // Send notification, update cache, etc.
            event(new PostPublished($post));
        });
    }
}

4. Admin Panel Integration (Nova)

If using Laravel Nova, extend the resource:

use Novius\Publishable\PublishableTrait;
use Laravel\Nova\Fields\Select;

class Post extends Resource
{
    public function fields(Request $request)
    {
        return [
            // ...
            Select::make('Status')
                ->options([
                    Publishable::STATUS_DRAFT => 'Draft',
                    Publishable::STATUS_PUBLISHED => 'Published',
                    Publishable::STATUS_UNPUBLISHED => 'Unpublished',
                    Publishable::STATUS_SCHEDULED => 'Scheduled',
                ])
                ->onlyOnDetail(),
        ];
    }
}

Integration Tips

  1. Custom Validation: Override the trait’s publish() method to add validation:

    public function publish()
    {
        $this->validatePublishRules();
        parent::publish();
    }
    
    protected function validatePublishRules()
    {
        if ($this->body === null) {
            throw new \Exception('Body cannot be empty.');
        }
    }
    
  2. Soft Deletes Compatibility: Combine with SoftDeletes trait:

    use Illuminate\Database\Eloquent\SoftDeletes;
    use Novius\Publishable\PublishableTrait;
    
    class Post extends Model
    {
        use SoftDeletes, PublishableTrait;
    }
    

    Note: A soft-deleted record cannot be published/unpublished (logic must be handled manually).

  3. API Responses: Filter responses by state in controllers:

    public function index(Request $request)
    {
        $posts = Post::when($request->status, function ($query, $status) {
            return $query->where('status', $status);
        })->published()->get();
    
        return PostResource::collection($posts);
    }
    
  4. Scheduled Jobs: Use Laravel’s scheduler to auto-publish posts:

    // In app/Console/Kernel.php
    protected function schedule(Schedule $schedule)
    {
        $schedule->command('publish:scheduled-posts')->everyMinute();
    }
    

    Create a command:

    php artisan make:command PublishScheduledPosts
    
    // app/Console/Commands/PublishScheduledPosts.php
    public function handle()
    {
        Post::where('status', Publishable::STATUS_SCHEDULED)
            ->where('published_at', '<=', now())
            ->each->publish();
    }
    
  5. Testing: Use factories to create test data:

    // database/factories/PostFactory.php
    return [
        'status' => Publishable::STATUS_DRAFT,
        'published_at' => null,
        'published_first_at' => null,
    ];
    

    Test state transitions:

    /** @test */
    public function it_can_publish_a_post()
    {
        $post = Post::factory()->create(['status' => Publishable::STATUS_DRAFT]);
        $post->publish();
        $this->assertEquals(Publishable::STATUS_PUBLISHED, $post->status);
    }
    

Gotchas and Tips

Pitfalls

  1. Missing Database Indexes: The published_first_at and status fields should be indexed for performance:

    Schema::table('posts', function (Blueprint $table) {
        $table->index('status');
        $table->index('published_first_at');
    });
    
  2. Timezone Handling: The published_at field uses the application’s timezone (config('app.timezone')). Ensure this is set correctly to avoid scheduled post delays.

  3. State Transition Logic: The package does not enforce business rules (e.g., "only admins can publish"). Implement these in your application logic:

    public function publish()
    {
        if (!auth()->user()->isAdmin()) {
            throw new \AuthorizationException('Unauthorized.');
        }
        parent::publish();
    }
    
  4. Soft Deletes Conflict: A soft-deleted model cannot be published/unpublished by default. Handle this explicitly:

    public function publish()
    {
        if ($this->deleted_at) {
            throw new \Exception('Cannot publish a deleted post.');
        }
        parent::publish();
    }
    
  5. Scheduled Posts in Development: Scheduled posts may not trigger immediately in development due to Laravel’s queue workers. Use:

    php artisan queue:work
    

    Or disable scheduling in tests:

    $post->schedule(..., now()); // Force immediate publishing
    
  6. Backfilling published_first_at: When migrating existing data, ensure published_first_at is set correctly for published posts:

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

Debugging Tips

  1. Check Status Values: The package uses constants like Publishable::STATUS_PUBLISHED. Verify these in your code:

    dd(Publishable::STATUS_DRAFT, Publishable::STATUS_PUBLISHED, Publishable::STATUS_UNPUBLISHED, Publishable::STATUS_SCHEDULED);
    
  2. Query Logs: Enable query logging to debug scope issues:

    DB::enableQueryLog();
    Post::published()->get();
    dd(DB::getQueryLog());
    
  3. Event Debugging: Listen for events to trace state changes:

    Post::published(function ($post) {
        \Log::info('Post published', ['id' => $post->id]);
    });
    
  4. Migration Issues: If migrations fail, ensure the publishable() macro is called after the timestamps() macro:

    $table->timestamps();
    $table->publishable(); // Correct order
    

Extension Points

  1. Custom States: Extend the trait to support additional states:
    class Post extends Model
    {
        use PublishableTrait;
    
        const STATUS_ARCHIVED = 'archived';
    
        protected $casts = [
            'status' => 'string',
        ];
    
        public function
    
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.
nexmo/api-specification
capell-app/block-library
axium/identity
cetria/laravel-dummy-models
cetria/reflection-helper
agropredict/sso-auth-bundle
evolvestudio/spam-protection
datacore/hub-sdk
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
ecotone/kafka
22h/doctrine-garbage-collection-bundle
agtp/agtp-php
agtp/mod-php
splash/sonata-admin
splash/metadata