acme/crm against a fictional “Acme CRM”.
The design
Listen to user events
src/Plugin.php
The listener
src/Listeners/PushContact.php
Map the fields
Keep mapping in one class, so the listener and the backfill can’t drift apart:src/ContactMapper.php
The backfill
Existing users don’t fire events, so give administrators a sync they can start. Keep its state in an option: a status, a cursor and counters.src/Listeners/SyncContacts.php
sync leaves the API key and the field mapping untouched.
The admin area
Split the pages so the flow guides an administrator through setup:
Redirect to the keys page until a key exists:
{% set active_menu = '/admin/plugins' %} in these templates, and point extra.default_url at the overview.
Sync controls
Nothing runs until the next cron tick. Say so on the page, and show the counters so administrators can see progress instead of wondering whether it worked.
The HTTP client
src/Client.php
Rate limits and daily jobs
For work that should run at most once a day, store a timestamp and return early:Testing
Creating a user pushes exactly one contact, not two.
Updating a user’s name updates the contact.
With auto-sync off, nothing is pushed.
A backfill processes in batches across ticks, and resumes after a pause.
Stopping a sync clears its state, and starting again begins from the top.
With the API unreachable, signup still works and the failure is logged.