Installation:
composer require baks-dev/posters
Publish the package configuration and migrations:
php artisan vendor:publish --provider="BaksDev\Posters\PostersServiceProvider" --tag="config"
php artisan vendor:publish --provider="BaksDev\Posters\PostersServiceProvider" --tag="migrations"
php artisan migrate
Configure Ad Slots:
Define ad slots in config/posters.php:
'slots' => [
'header' => [
'positions' => ['top', 'bottom'],
'weight' => 70,
],
'sidebar' => [
'positions' => ['left', 'right'],
'weight' => 30,
],
],
Create an Ad Campaign: Use Tinker or a seed class to create a campaign:
php artisan tinker
$campaign = \BaksDev\Posters\Models\AdCampaign::create([
'name' => 'Summer Sale',
'impression_limit' => 1000,
'click_limit' => 200,
]);
$campaign->ads()->create([
'url' => 'https://example.com/sale',
'image_url' => 'https://example.com/banner.jpg',
'alt_text' => 'Summer Sale Banner',
]);
Render an Ad Slot in Blade:
Add the Blade directive to your view (e.g., resources/views/layouts/app.blade.php):
@adSlot('header')
Test Locally: Visit a page with the ad slot to verify the banner renders. Check Laravel logs for tracking events:
tail -f storage/logs/laravel.log
@adSlot('slot_name')) or a helper method (Posters::render('slot_name')) to insert ads into views.config/posters.php to control ad rotation order (e.g., ['top', 'bottom']).header slot with weight: 70 appears 70% of the time).// database/seeders/AdCampaignSeeder.php
public function run()
{
\BaksDev\Posters\Models\AdCampaign::factory()->count(5)->create()->each(function ($campaign) {
$campaign->ads()->saveMany(\BaksDev\Posters\Models\Ad::factory()->count(3)->make());
});
}
// config/posters.php
'slot_campaigns' => [
'header' => [1, 2], // Campaign IDs
'sidebar' => [3],
],
AdImpression and AdClick events:
// app/Listeners/LogAdEvents.php
public function handle(AdEvent $event)
{
Log::channel('ad_tracking')->info($event->toArray());
}
Register the listener in EventServiceProvider:
protected $listen = [
\BaksDev\Posters\Events\AdImpression::class => [
\App\Listeners\LogAdEvents::class,
],
];
AdService:
// app/Services/ExtendedAdService.php
public function logImpression(Ad $ad, Request $request)
{
parent::logImpression($ad, $request);
// Custom logic (e.g., GA4 event)
Analytics::event('ad_impression', [
'ad_id' => $ad->id,
'slot' => $request->route('slot'),
]);
}
// app/Providers/PostersServiceProvider.php
public function boot()
{
Posters::resolver(function ($slot, $request) {
if ($request->user()->role === 'premium') {
return Posters::slot('premium_header')->getAd();
}
return Posters::slot('header')->getAd();
});
}
@adSlot('sidebar', ['category' => $article->category])
// routes/api.php
Route::get('/ads/{slot}', [AdController::class, 'serve']);
// app/Http/Controllers/AdController.php
public function serve($slot, Request $request)
{
$ad = Posters::slot($slot)->getAd();
return response()->json([
'url' => $ad->url,
'image' => $ad->image_url,
]);
}
// app/Services/HybridAdService.php
public function getAd($slot)
{
$localAd = Posters::slot($slot)->getAd();
if ($localAd) return $localAd;
return $this->fetchFromAdSense($slot);
}
Queue Workers for Tracking: Configure a queue worker to process ad events asynchronously:
php artisan queue:work --queue=ad_tracking
Ensure the PostersServiceProvider binds the queue connection:
$this->app->bind(\Illuminate\Contracts\Queue\Queue::class, function ($app) {
return Queue::connection('ad_tracking');
});
Caching Ad Slots: Cache frequently accessed slots to reduce database load:
// app/Providers/AppServiceProvider.php
public function boot()
{
Posters::extend(function ($app) {
$app->singleton(\BaksDev\Posters\Contracts\SlotRepository::class, function ($app) {
return new CachedSlotRepository(
new EloquentSlotRepository,
Cache::store('redis')
);
});
});
}
Blade Component Upgrade: Replace Blade directives with modern Laravel components for better maintainability:
<x-ad-slot name="header" />
Register the component in AppServiceProvider:
Blade::component('ad-slot', \BaksDev\Posters\View\Components\AdSlot::class);
Testing Strategy:
SlotRepository and AdService to test ad selection logic:
$repository = Mockery::mock(\BaksDev\Posters\Contracts\SlotRepository::class);
$repository->shouldReceive('findByName')->andReturn($slot);
$this->app->instance(\BaksDev\Posters\Contracts\SlotRepository::class, $repository);
$response = $this->get('/');
$response->assertSeeInOrder([
'Ad Slot: header',
'https://example.com/banner.jpg',
]);
Deployment Checklist:
php artisan migrate --seed
php artisan config:cache
php artisan queue:work --daemon
php artisan posters:stats
ad_campaigns, ad_slots). If you’ve customized your schema, override the Eloquent models:
// app/Models/CustomAdCampaign.php
class CustomAdCampaign extends \BaksDev\Posters\Models\AdCampaign
{
protected $table = 'custom_ad_campaigns';
}
How can I help you explore Laravel packages today?