Pre-1.0. Not intended for public use yet. The API is subject to undocumented change, and these pages may already be wrong about it.

Skip to content

Parties & Anonymous Actors ​

Introduction ​

An activity's actor can be a user or a party, such as a payment provider. An anonymous activity has no recorded actor.

MeansIn the Payload
anonymousno recorded actoractor: null; the headline uses the anonymous headline
partya named participant with no model in your appan entity with type: "storyfeed.party", a label, and url: null

Recording a Party ​

When Starcourt Mall closes for the night, a scheduled Artisan command cancels any Scoops Ahoy order left unpaid. The command runs from the console, where no user is signed in, so it needs a named actor to identify who cancelled the orders. Pass a string to the by method to use a party. Strings can name parties in any role:

app/Console/Commands/CancelUnpaidOrders.php
php
<?php

namespace App\Console\Commands;

use App\Models\Order;
use Illuminate\Console\Command;
use Storyfeed\Facades\Storyfeed;

class CancelUnpaidOrders extends Command
{
    protected $signature = 'orders:cancel-unpaid';

    protected $description = 'Cancel the orders left unpaid at closing time';

    public function handle(): void
    {
        Order::whereNull('paid_at')->whereNull('cancelled_at')->each(function (Order $order) {
            $order->update(['cancelled_at' => now()]);

            Storyfeed::activity()
                ->by('Scoops Register') 
                ->action('cancel', $order)
                ->publish();
        });
    }
}
php
<?php

namespace App\Console\Commands;

use App\Models\Order;
use Illuminate\Console\Command;
use Storyfeed\Facades\Storyfeed;

class CancelUnpaidOrders extends Command
{
    protected $signature = 'orders:cancel-unpaid';

    protected $description = 'Cancel the orders left unpaid at closing time';

    public function handle(): void
    {
        Order::whereNull('paid_at')->whereNull('cancelled_at')->each(function (Order $order) {
            $order->update(['cancelled_at' => now()]);

            Storyfeed::record(
                verb: 'cancel',
                object: $order,
                actor: 'Scoops Register', 
            );
        });
    }
}
SR
Scoops Register cancelled Order #2071

Storyfeed creates the party when its name is first used and reuses it for later activities.

Without by('Scoops Register') or another actor default, this command records an anonymous activity.

Using Parties in Other Roles ​

A party can fill any role, not only the actor:

app/Http/Controllers/DispatchOrderController.php
php
<?php

namespace App\Http\Controllers;

use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Storyfeed\Facades\Storyfeed;

class DispatchOrderController extends Controller
{
    public function __invoke(Order $order): RedirectResponse
    {
        $order->update(['dispatched_at' => now()]);

        Storyfeed::activity()
            ->action('dispatch', $order)
            ->to('Front desk')
            ->publish();

        return back();
    }
}
php
<?php

namespace App\Http\Controllers;

use App\Models\Order;
use Illuminate\Http\RedirectResponse;
use Storyfeed\Facades\Storyfeed;

class DispatchOrderController extends Controller
{
    public function __invoke(Order $order): RedirectResponse
    {
        $order->update(['dispatched_at' => now()]);

        Storyfeed::record(
            verb: 'dispatch',
            object: $order,
            target: 'Front desk',
        );

        return back();
    }
}

To retrieve a party model by name, call Storyfeed::party('Front desk'). Storyfeed creates it if it does not exist.

Declaring Party Names ​

A misspelling such as 'Strpie' creates a separate party from 'Stripe'. Declare allowed actor names to detect these mistakes:

app/Providers/AppServiceProvider.phpboot()
php
use Storyfeed\Facades\Storyfeed;

Storyfeed::parties(['Stripe', 'Scoops Register']);

Undeclared names are handled according to the environment:

EnvironmentAn Undeclared Name
local, testingthrows UndeclaredParty with the call and allowed names
everywhere elseis ignored; the activity retains its default actor and storyfeed:doctor reports the name

The list applies to Storyfeed::actor() and a verb's actor method. The by method does not check it. Without a list, any name is allowed. Names match by slug, so 'Stripe' and 'stripe' identify the same party.

Set parties.strict in config/storyfeed.php to control whether undeclared names throw an exception. The default, null, throws only in local and testing.

Setting a Default Actor ​

config/storyfeed.php
php
'parties' => [
    // e.g. 'System' — a name for otherwise-anonymous publishes
    'fallback' => null,
],

Without a fallback or another resolved actor, the activity is anonymous.

Resolving the Default Actor ​

By default, Storyfeed records the authenticated user when you omit the actor. To use another authentication guard, set actor_resolver to an invokable class that returns the actor:

app/Support/ResolveFeedActor.php
php
<?php

namespace App\Support;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Auth;

class ResolveFeedActor
{
    public function __invoke(): ?Model
    {
        return Auth::guard('admin')->user() ?? Auth::user();
    }
}
config/storyfeed.php
php
'actor_resolver' => App\Support\ResolveFeedActor::class,

When the resolver returns null, the fallback party applies.

Recording Anonymous Activities ​

If you omit the actor, Storyfeed uses the authenticated user or a configured default. To record no actor, even during an authenticated request, pass null to the by method:

php
Storyfeed::activity()
    ->by(null) 
    ->action('place', $order)
    ->to($shop)
    ->publish();
Someone placed Order #1035 with Scoops Ahoy
MethodActor
omit by()resolved from the request or configured defaults
->by(null) or ->actor(null)anonymous
->anonymously()anonymous, on an existing builder
Storyfeed::anonymous()anonymous, on a new builder
Storyfeed::record(..., anonymous: true)anonymous; also supplying a non-null actor: throws an exception

Passing actor: null to Storyfeed::record() still allows the default actor to apply. Use anonymous: true to record no actor with named arguments.

Anonymous Headlines ​

routes/feed.php
php
use App\Models\Order;
use Storyfeed\Facades\Story;

Story::for(Order::class)->verb('confirm')
    ->headline(':actor confirmed :object')
    ->anonymousHeadline(':object was confirmed');

An anonymous activity uses the anonymous headline. A party uses the ordinary headline. Anonymous templates cannot contain :actor. You may also use a closure, as described in The Feed File.

If a verb never records an actor, you may omit :actor from its headline:

routes/feed.php
php
use App\Models\Order;
use Storyfeed\Facades\Story;

Story::for(Order::class)->verb('expire')
    ->headline(':object expired at :target');

Omitting :actor from a headline does not remove the recorded actor.

Released under the MIT License.