How an import runs
The job tracks its own progress, so each tick continues where the last one stopped. Batches are bounded, which keeps a large archive from exhausting memory or time.The interface
Yield conversations
Each yielded conversation must carry the source’s own identifier in its metadata as_original_id. The runner uses it to build a stable external ID, which is how re-running an import skips what’s already there.
src/NotesAdapter.php
Check the exact constructor and factory signatures of
ConversationEntity and MessageEntity in the source before you build them: they take value objects, and the message factories set the role for you.Rules
- Respect
$offsetand$limit. Yielding the whole archive on the first call defeats the batching, and large imports will time out. - Yield, don’t persist. The runner persists, deduplicates and counts.
- Be restartable.
prepare()andparse()run again on the next tick, so make both safe to repeat. - Set
_original_idon every conversation. Without it, the conversation is skipped. - Keep memory flat. Stream the archive rather than decoding it all into an array where you can.
Register the adapter
The runner finds an adapter by matching the job’s source against the key you registered under, so the key must equal the source value your upload endpoint sets:src/Plugin.php
Create jobs
Give users a page to upload their archive, then create the file and the job:FileSystemInterface under var/, not the CDN: it’s a working file, not something to serve. Poll the job from your page to show progress, and remember that nothing happens until a cron tick runs.
Testing
A small archive imports completely, and conversations appear in the library.
A large archive imports across several cron ticks without timing out.
Re-importing the same archive skips existing conversations instead of duplicating them.
A corrupt archive fails the job with a readable error, rather than looping forever.
Temporary files are removed when the job finishes.