Import a subscriber with their original join date
A migration importing an established audience arrives holding a join date per subscriber. Without carrying it across, every imported token is stamped at import time and TenureBadgeCalculator — which derives tenure from the earliest created_at across a user’s active tokens — badges a five-year supporter as a New Member. That is worse than showing nothing: it is confidently wrong, in front of the subscriber it is wrong about.
TokenManager::generate() and TokenRepository::insert() both take an optional trailing ?string $created_at. Leave it null (every non-migration caller) and the column’s DEFAULT CURRENT_TIMESTAMP applies. Pass a Y-m-d H:i:s or bare Y-m-d string in site-local time and it is written instead — the bare form widens to midnight, which is the shape a CSV export actually has.
⚠ Validate the whole file before you write a single row. generate() throws InvalidArgumentException out of the guard on a bad date, and the plaintext token generated for that attempt is discarded with the exception. BackdateGuard::reason() is the non-throwing half and exists precisely so an importer can collect every bad row and show the operator one list, rather than aborting on row 7 of 400.
Code
<?php
use Benecaster\Token\BackdateGuard;
use Benecaster\Token\TokenManager;
/**
* @param array<int, array{user_id: int, join_date: string}> $rows Parsed CSV.
* @return array{imported: int, rejected: array<int, array{user_id: int, join_date: string, reason: string}>}
*/
function my_addon_import_with_tenure( TokenManager $manager, array $rows, int $show_id, string $tier_slug ): array {
$guard = new BackdateGuard();
$good = [];
$rejected = [];
// Pass one: judge every row before writing any of them. reason() returns
// null when the date is fine, otherwise a stable code — 'unparseable',
// 'future' or 'before_floor' — that you can group and translate yourself.
foreach ( $rows as $row ) {
$reason = $guard->reason( $row['join_date'] );
if ( null === $reason ) {
$good[] = $row;
continue;
}
$rejected[] = $row + [ 'reason' => $reason ];
}
// Show the operator the rejects BEFORE importing. A half-finished
// migration is much harder to reason about than one that has not started.
if ( $rejected !== [] ) {
my_addon_report_bad_rows( $rejected );
}
$imported = 0;
foreach ( $good as $row ) {
$token = $manager->generate(
$row['user_id'],
$show_id,
$tier_slug,
'subscriber',
$row['join_date'] // already validated above
);
my_addon_email_feed_url( $row['user_id'], $token );
$imported++;
}
return [ 'imported' => $imported, 'rejected' => $rejected ];
}
Need this built rather than just documented? See our services →