*/ protected $fillable = [ 'name', 'token', 'expires_at', ]; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'last_used_at' => 'datetime', 'expires_at' => 'datetime', ]; /** * The attributes that should be hidden for serialization. * * @var array */ protected $hidden = [ 'token', ]; /** * Find a token by its plain-text value. * * Tokens are stored as SHA-256 hashes, so we hash the input * before querying the database. * * @param string $token Plain-text token value */ public static function findToken(string $token): ?UserToken { return static::where('token', hash('sha256', $token))->first(); } /** * Get the user that owns the token. * * @return BelongsTo */ public function user(): BelongsTo { return $this->belongsTo(User::class); } /** * Determine if the token has expired. */ public function isExpired(): bool { return $this->expires_at && $this->expires_at->isPast(); } /** * Determine if the token is valid (not expired). */ public function isValid(): bool { return ! $this->isExpired(); } /** * Update the last used timestamp. * * Preserves the hasModifiedRecords state to avoid triggering * model events when only updating usage tracking. */ public function recordUsage(): void { $connection = $this->getConnection(); // Preserve modification state if the connection supports it if (method_exists($connection, 'hasModifiedRecords') && method_exists($connection, 'setRecordModificationState')) { $hasModifiedRecords = $connection->hasModifiedRecords(); $this->forceFill(['last_used_at' => now()])->save(); $connection->setRecordModificationState($hasModifiedRecords); } else { // Fallback for connections that don't support modification state $this->forceFill(['last_used_at' => now()])->save(); } } }