Extending¶
Replacing pieces¶
Every piece of the core is a definition of the container (config/di.php of the package), keyed by its interface.
A definition with the same key in your application's di/ wins, and the package builds every dependent piece over
yours:
// config/common/di/indexnow.php
use IndexNowKit\Dispatch\DispatcherInterface;
use IndexNowKit\Http\TransportInterface;
return [
TransportInterface::class => App\IndexNow\RecordingTransport::class, // a proxy, a recorder in tests
DispatcherInterface::class => App\IndexNow\QueueDispatcher::class, // your queue: see below
];
| Definition | Default | Replace it for |
|---|---|---|
IndexNowKit\Http\TransportInterface |
http.client through the core's lazy transport, else PSR-18 discovery |
a proxy, a recording transport |
IndexNowKit\Debounce\DebounceStoreInterface |
debounce.store (the container's PSR-16 cache) |
a store the cache cannot express |
IndexNowKit\Dispatch\DispatcherInterface |
sync / none of dispatch |
a queue (see below) |
IndexNowKit\Url\UrlResolverInterface |
the attribute resolver over the router bridge | replacing the whole "object → URLs" step |
IndexNowKit\Url\RouteUrlResolverInterface |
Url\YiiRouteUrlResolver over UrlGeneratorInterface |
another URL generator |
IndexNowKit\Submission\SubmissionStoreInterface |
the store of history.store, else the null store |
your own submission log |
IndexNowKit\Attribute\ParamExtractor |
new ParamExtractor(new ActiveRecordSubjectReader()) |
more SubjectReaderInterfaces (->with()) |
IndexNowKit\Attribute\AttributeReaderInterface |
RuleRegistry over AttributeReader |
rules from another source |
Psr\Clock\ClockInterface |
the system clock | FrozenClock in tests |
IndexNowKit\Key\KeyProviderInterface, UrlNormalizerInterface, ThrottleInterface, ClientInterface, SubmitterInterface, CollectorInterface, ResolverLocatorInterface, ObjectChangeHandler |
the core's factories | rarely |
How it works: Wiring describes the graph with the core's Adapter\ServicesBuilder. The graph the application
runs on (Adapter\Services, also a definition) reads every node from the container; the default of each node
(Wiring::transport(), Wiring::client(), …) is the core's factory for that node over a graph whose other nodes
come from the container. Replace nothing and you get the graph IndexNowKit::create() builds; replace one thing and
it reaches every dependent piece, the checker and the command submitters included.
Injectable as they are: IndexNowKit\IndexNowKit (the core facade), IndexNowKit\Yii3\IndexNow (the service with
submitRecord() and the package accessors), Config, RuleRegistry, GuardedUrlResolver, KeyFileResponder,
CheckerInterface, SubmitterFactoryInterface, VerifyingStaging, IndexNowObserver, SubjectLoaderInterface,
Vocabulary, ResultFormatterInterface, the runners of indexnowkit/console (di-console.php).
A queue¶
yiisoft/queue has no stable release, so the package ships no queue mode. A dispatcher over the queue you run is
one class and one definition:
final class QueueDispatcher implements IndexNowKit\Dispatch\DispatcherInterface
{
public function __construct(private readonly MyQueue $queue) {}
/** @param list<string> $urls */
public function dispatch(array $urls): void
{
$this->queue->push(new SubmitUrlsJob($urls, attempt: 1)); // never throw into the request
}
}
The worker is the recipe of retries-and-queues.md:
SubmitterInterface::submit() (inject it, it is the graph's submitter with the pre-flight of verify when enabled),
then Retry\WorkerOutcome decides what to re-enqueue and with what delay. Keep dispatch: sync in the params (it is
what check reports; the line names your class) and set base_url: a worker has no request to take the host from.
Custom resolvers¶
#[IndexNow(resolver: ProductUrlResolver::class)] // a class the container can build (autowired)
#[IndexNow(resolver: 'app.product_urls')] // or a container id
The class implements IndexNowKit\Url\UrlResolverInterface; constructor dependencies come from the container.
Rules at runtime¶
$indexNow->observe(Product::class, [new IndexNow(route: 'product/view', params: ['id' => 'self'])], new IndexNowDefaults(when: 'active'));
$indexNow->rules()->registerFor(Page::class, fn (Page $page): ?RuleSet => ...); // decided per object
observe() appends the observer to the class's event dispatcher of yiisoft/active-record (Event\ObservedDispatcher:
the class's own attribute handlers run first), the same mechanism as the active_record.models list. The class
still needs EventsTrait.
Manual submissions¶
| Method | Returns |
|---|---|
IndexNow::submit(iterable $urls), submitRecord($record, Event $event), submitRecords(iterable $records) (one request for many) |
list<Result> — what the engines answered |
urlsFor($record, Event $event), urlsForAll(iterable $records, Event $event) |
list<string> — the URLs the rules yield, nothing sent |
explain($record, Event $event) |
list<ResolvedUrl> — the same URLs with the rule that produced each |
collect(iterable $urls), flush() |
nothing: collect() parks URLs in the request collector, flush() verifies the staged changes and sends now |
Every Result is also dispatched to the container's PSR-14 EventDispatcherInterface: listen to
IndexNowKit\Result in your events.php.
Checks¶
'checks' => [App\IndexNow\CdnPurgeCheck::class] in the params appends your Check\CheckInterface lines to
indexnow:check; the ids are resolved through the container.
Console¶
SubjectLoaderInterface (how submit-record and explain find records: tenant scoping, another id format),
ResultFormatterInterface (your JSON envelope) and SubmitterFactoryInterface are definitions; the command bodies
are the IndexNowKit\Console\*Runner of indexnowkit/console (di-console.php), so a tenant loop over
SubmitSubjectsRunner is a ten-line command of your own.
The commands themselves are classes of the packages, not of this one: IndexNowKit\Console\Command\*
(indexnowkit/console), IndexNowKit\Sitemap\Console\SitemapCommand, IndexNowKit\History\Console\HistoryCommand and
StatusCommand — params-console.php maps the names to them, di-console.php gives them their runners, the
ConfigSourceInterface (Console\ConfigSource over the facade) and the .env of key:generate. To change what a
command does, replace its runner definition in your di/ (SubmitSubjectsRunner::class => MyTenantLoop::class); to
replace a command outright, map the name to your class in your own params-console.php (the application's params
win) and give it the same inputs with Definitions::submit()->applyTo($this). The key file route is the core's
Key\KeyFileRequestHandler behind Http\KeyFileHandler (di-web.php): replace the former to serve the file from
another key source, the route and the {key} pattern stay.
What is the core's¶
The observer keeps only what is Yii3's: the change set from the old-value snapshot of BeforeUpdate, the previous
state for renamed pages, the verify-on-commit staging keyed by the connection. Guarding, logging and the URLs of a
row about to be deleted are the core's Hook\ObserverHelper; the inputs of every command come from
Console\Definitions of indexnowkit/console and the definitions of the optional packages, so ./yii indexnow:submit-record --help
matches the bundle and artisan.