Arguments
Arguments are what the origin needs in order to ask: an id, a code, a language. They are resolved before the origin sees anything, so the client never reaches into your model.
The map
protected function arguments(): array { return [ 'city_id' => 'external_id', // $city->external_id 'contact_id' => 'office.provider_contact_id', // through a relation ]; } // the origin receives ['city_id' => 'city-42', 'contact_id' => 71]
The value is read when the arguments are resolved, so it is never a stale copy, and a dotted path walks relations. The map is per class: every row of that source resolves the same way.
There is also an $arguments property with the same shape, which sits below the method in precedence. It exists so a subclass can add to a map without rewriting it:
class SandboxListingSource extends ListingSource { protected array $arguments = ['environment' => 'sandbox_flag']; }
At call time
$city->source('weather', ['city_id' => 'custom_id'])->fetch(); $city->source('weather')->setVariantArguments(['city_id' => 'custom_id'])->fetch();
These win over everything else, which is what you want for a one-off call: a reconciliation command walking a provider's ids, a debugging tinker session, a job that already knows the id it is working with.
Pinned to one row
A row can carry its own arguments in the arguments JSON column, and they beat the map. That is for pinning something to a single row when the map cannot express it, because the map is per class:
$source->record()->update(['arguments' => ['feed_code' => 'B-114']]);
persist() never overwrites them, so a write does not wipe what you pinned.
Pointing at something instead
An argument does not have to be a value. It can point at a model, or at another source:
$scraped = $article->source('scraped')->fetch(); $article->source('translation') ->useArgument('scraped', $scraped) // a model, a source, or its record ->fetch();
This is a pointer, not a copy. The second origin receives the first one's payload as it is when it reads it:
// inside TranslationOrigin public function fetch(array $arguments = []): array { return ['content' => $this->translate($arguments['scraped']['content'])]; }
The payload arrives uncast, because casts belong to the source that owns those fields.
Which is what a pipeline is
Scrape into one source, derive from it in another, and nothing copies data from one row to the next:
scraped (row) -> translation (row) -> the Article
Each row keeps its own signature, so you know whether the scrape changed before paying for the translation again. And because the link is a pointer, re-scraping the first row is immediately visible to the second: there is no stale copy to invalidate.
The pointer is stored in source_arguments, so it is still there on a later read, and a name points at one thing only. Pointing the same name somewhere else replaces it:
$source->forgetArgument('scraped');
Pointing at a model
$property->source('listing')->useArgument('office', $property->office)->fetch(); // in the origin: $arguments['office']->provider_contact_id
Powerful, and worth thinking twice about. An origin that receives your Office knows your domain, when its job is to speak HTTP. Prefer a value through the map, and keep the pointer for when the origin genuinely needs several things from the same record, or when which record it is varies per row.
Precedence
From lowest to highest:
- The
$argumentsproperty. - The
arguments()method. - The arguments stored on the row, including its pointers.
- The pointers declared on this instance with
useArgument(). - The arguments passed to
source()orsetVariantArguments().
$source->resolveArguments(); // exactly what the origin will receive
That is the first thing to print when an origin is asking the wrong question.