Recording Activities
Introduction
An activity records a verb and its participants. Publish it from the code that handles the action, such as a controller, observer, or event listener.
Publishing Activities
Assign the activity's roles and call the publish method:
<?php
namespace App\Http\Controllers;
use App\Http\Requests\PlaceOrderRequest;
use App\Models\Shop;
use Illuminate\Http\RedirectResponse;
use Storyfeed\Facades\Storyfeed;
class OrderController extends Controller
{
public function store(
PlaceOrderRequest $request,
Shop $shop,
): RedirectResponse {
$order = $shop->orders()->create($request->validated());
Storyfeed::activity()
->by($request->user())
->action('place', $order)
->to($shop)
->publish();
return to_route('orders.show', $order);
}
}<?php
namespace App\Http\Controllers;
use App\Http\Requests\PlaceOrderRequest;
use App\Models\Shop;
use Illuminate\Http\RedirectResponse;
use Storyfeed\Facades\Storyfeed;
class OrderController extends Controller
{
public function store(
PlaceOrderRequest $request,
Shop $shop,
): RedirectResponse {
$order = $shop->orders()->create($request->validated());
Storyfeed::record(
verb: 'place',
object: $order,
actor: $request->user(),
target: $shop,
);
return to_route('orders.show', $order);
}
}The first argument to the action method is the verb, a string describing the action. This example records place. Define its headline in The Feed File.
You may also call the record method on the Storyfeed facade, passing each role as a named argument.
Assigning Roles
| Role | Meaning | Example |
|---|---|---|
actor | who performed the action | the customer |
object | the entity acted on | the order |
target | the entity the action was directed at | the shop |
context | the containing entity | the shop where the action occurred |
origin | the source | the source of an accepted invitation |
result | the entity produced | a receipt or generated file |
instrument | the tool or service used | the device used to take an order |
Choose the role based on the entity's involvement. A tablet is a target when an order is sent to it, or an instrument when used to take the order.
These roles come from W3C Activity Streams 2.0.
Role Aliases
Each role has a method with the same name, such as actor or object. The verb method sets the verb. You may also use these aliases:
| Alias | Sets | Meaning |
|---|---|---|
->by() | actor | who performed the action |
->action() | verb and object | the action and affected entity |
->using() | instrument | the tool or service used |
->resulting() | result | the entity produced |
->to() ->for() ->on() ->with() ->into() ->in() ->from() | target | the entity the action was directed at |
These aliases let you compose activities expressively, like a natural-language sentence.
Assigning the Actor
The actor is the user or model that performed the activity. You may specify the actor using the by method:
Storyfeed::activity()
->by($order->customer)
->action('place', $order)
->to($order->shop)
->publish();Storyfeed::record(
verb: 'place',
object: $order,
actor: $order->customer,
target: $order->shop,
);With the default configuration and no scoped or verb-specific actor, omitting by records the authenticated user, or no actor when nobody is signed in. Scopes, carried job identity, verb actors, custom resolvers, and a configured fallback party can change that selection. See Role Precedence.
Use by(null) to bypass default actor selection explicitly. An anonymous activity has no recorded actor.
Adding Activity Data
Define each new verb and its headline before publishing. By default, local and testing environments throw UnknownVerb for an unregistered verb and UnauthoredActivity for an object-type/verb pair without a headline. A concrete Story definition satisfies both checks:
use App\Models\MenuItem;
use Storyfeed\Facades\Story;
Story::for(MenuItem::class)->verb('reprice')
->headline(':actor changed the price of :object');Here $product is a MenuItem.
Use the data method to store arbitrary values on an activity. Storyfeed returns them in the activity's data field:
$from = $product->price;
$product->update(['price' => $request->integer('price')]);
Storyfeed::activity()
->by($request->user())
->action('reprice', $product)
->data(['from' => $from, 'to' => $product->price])
->publish();$from = $product->price;
$product->update(['price' => $request->integer('price')]);
Storyfeed::record(
verb: 'reprice',
object: $product,
actor: $request->user(),
data: ['from' => $from, 'to' => $product->price],
);[
{
"kind": "activity",
"id": "a-price",
"verb": "reprice",
"published_at": "1985-07-02T11:00:00.000000Z",
"headline_template": ":actor changed the price of :object",
"headline": null,
"glyph": "tag",
"glyph_intent": null,
"actor": {
"type": "user",
"id": "101",
"label": "Steve Harrington",
"url": "/users/101",
"attributes": {},
"modal": false,
"data": {},
"media": null,
"body": null,
"tombstone": null
},
"object": {
"type": "menu_item",
"id": "101",
"label": "USS Butterscotch",
"url": "/menu/butterscotch",
"attributes": {},
"modal": false,
"data": {},
"media": null,
"body": null,
"tombstone": null
},
"target": {
"type": "venue",
"id": "101",
"label": "Scoops Ahoy",
"url": "/venues/scoops",
"attributes": {},
"modal": false,
"data": {},
"media": {
"icon": null,
"image": null,
"files": [],
"preview": {
"src": "/media/worlds/stranger-things/parlour.jpg",
"mediaType": "image/jpeg",
"width": 960,
"height": 720,
"alt": "An ice cream counter"
},
"url": null
},
"body": [
{
"$body": "Storyfeed/Body/Image",
"$v": 1,
"caption": "Scoops Ahoy",
"alt": "Scoops Ahoy",
"width": null,
"height": null,
"image": "preview"
}
],
"tombstone": null
},
"context": null,
"origin": null,
"result": null,
"instrument": null,
"data": {
"from": 275,
"to": 295
},
"tombstoned": [],
"redundant": false,
"missing_headline_template": null,
"missing_headline": null
}
]Setting the Publication Time
To set an earlier publication time, such as when importing records, call the publishedAt method:
use App\Models\MenuItem;
use App\Models\User;
use Storyfeed\Facades\Storyfeed;
foreach ($rows as $row) {
Storyfeed::activity()
->by(User::findOrFail($row['user_id']))
->action('reprice', MenuItem::findOrFail($row['menu_item_id']))
->data(['from' => $row['from'], 'to' => $row['to']])
->publishedAt($row['changed_at'])
->publish();
}use App\Models\MenuItem;
use App\Models\User;
use Storyfeed\Facades\Storyfeed;
foreach ($rows as $row) {
Storyfeed::record(
verb: 'reprice',
object: MenuItem::findOrFail($row['menu_item_id']),
actor: User::findOrFail($row['user_id']),
data: ['from' => $row['from'], 'to' => $row['to']],
publishedAt: $row['changed_at'],
);
}Recording Multiple Objects
To record an activity involving multiple objects, call the objects method. Storyfeed stores a parent activity and one activity per object. Define the upload verb before recording the photos. This verb-wide definition also covers the parent activity:
use Storyfeed\Facades\Story;
Story::verb('upload')->headline(':actor uploaded :object');Then publish the photos:
Storyfeed::activity()
->by($request->user())
->verb('upload')
->objects($photos)
->publish();Storyfeed::record(
verb: 'upload',
objects: $photos,
actor: $request->user(),
);See Composites for how these activities appear in the feed and how to define their headlines.