NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

The facade is the recommended default, not the only way in. There are three equivalent entry points:

  • The Advertisements facade — shortest, and what the rest of these docs use.
  • The manager, RoundlyConsulting\Advertisements\AdvertisementManager — the facade root. Inject it through the constructor for the same API with an explicit dependency and no static calls.
  • Actions — single-purpose classes in RoundlyConsulting\Advertisements\Actions with one execute() method, for composing into your own actions, jobs and commands.

Injecting the manager

use RoundlyConsulting\Advertisements\AdvertisementManager;
use RoundlyConsulting\Advertisements\Models\Advertisement;

final class LaunchCampaign
{
    public function __construct(private AdvertisementManager $ads) {}

    public function __invoke(Advertisement $ad): void
    {
        $this->ads->for($ad)->placements()->sync(['sidebar', 'header']);
        $this->ads->publish($ad);
    }
}

AdvertisementManager is deliberately not final: Advertisements::fake() swaps in AdvertisementsFake, a subtype, behind the facade and in the container — so constructor-injected managers get the fake too.

Calling an action

Resolve an action from the container, or type-hint it in a controller, job or action of your own:

use RoundlyConsulting\Advertisements\Actions\PublishAdvertisement;

// The raw use case:
app(PublishAdvertisement::class)->execute($ad, now()->addDay());
use Illuminate\Http\RedirectResponse;
use RoundlyConsulting\Advertisements\Actions\PublishAdvertisement;
use RoundlyConsulting\Advertisements\Models\Advertisement;

final class ScheduleAdvertisementController
{
    public function __invoke(Advertisement $advertisement, PublishAdvertisement $publish): RedirectResponse
    {
        $publish->execute($advertisement, now()->addDay());   // scheduled for tomorrow

        return back();
    }
}

The manager resolves each action from the container per call, so binding your own implementation changes the facade’s behaviour as well. The fake replaces the manager, not the actions — an action you call directly still writes rows.

Facade method → action

Facade methodActionSignature
create()CreateAdvertisementexecute(AdvertisementData $data): Advertisement
update()UpdateAdvertisementexecute(Advertisement $ad, AdvertisementData $data): Advertisement
publish()PublishAdvertisementexecute(Advertisement $ad, ?CarbonInterface $at = null): Advertisement
unpublish()UnpublishAdvertisementexecute(Advertisement $ad): Advertisement
expire()ExpireAdvertisementexecute(Advertisement $ad, ?CarbonInterface $at = null): Advertisement
archive()ArchiveAdvertisementexecute(Advertisement $ad): Advertisement
delete()DeleteAdvertisementexecute(Advertisement $ad): bool
for($ad)->placements()->attach()AttachPlacementsexecute(Advertisement $ad, iterable $placements): Advertisement
for($ad)->placements()->detach()DetachPlacementsexecute(Advertisement $ad, iterable $placements): Advertisement
for($ad)->placements()->sync()SyncPlacementsexecute(Advertisement $ad, iterable $placements): Advertisement
for($ad)->track()->impression()RecordImpressionexecute(Advertisement $ad, $placement = null, ?ImpressionData $data = null): ?AdvertisementEvent
for($ad)->track()->click()RecordClickexecute(Advertisement $ad, $placement = null, ?ImpressionData $data = null): ?AdvertisementEvent

create() and update() take an AdvertisementData; the tracking actions take an optional ImpressionData (see Creating & updating ads and Impression & click tracking). The raw tracking actions record what they are given — the live-ad and placement checks live in for($ad)->track($placement):

use RoundlyConsulting\Advertisements\Actions\RecordImpression;
use RoundlyConsulting\Advertisements\DataTransferObjects\ImpressionData;

// Records what it is given — the live-ad and placement checks live in for($ad)->track($placement).
app(RecordImpression::class)->execute($ad, 'sidebar', new ImpressionData(ip: $request->ip()));

RecordAdvertisementEvent is @internal — the single code path behind RecordImpression, RecordClick and the buffered job. Record through the facade or those two actions instead.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.