public function confirm(): void
    {
        if ($this->isConfirmed()) {
            throw new OptInTokenAlreadyConfirmedException();
        }

        if (!$this->isValid()) {
            throw new OptInTokenNoLongerValidException();
        }

        $this->model->tstamp = time();
        $this->model->confirmedOn = time();
        $this->model->removeOn = strtotime('+3 years');
        $this->model->save();

        $related = $this->model->getRelatedRecords();

        if (empty($related)) {
            return;
        }

        /** @var OptInModel $adapter */
        $adapter = $this->framework->getAdapter(OptInModel::class);
        $prefix = strtok($this->getIdentifier(), '-');

        // Invalidate other tokens that relate to the same records
        foreach ($related as $table => $ids) {
            if (!$models = $adapter->findByRelatedTableAndIds($table, $ids)) {
                continue;
            }

            foreach ($models as $model) {
                if (
                    $model->confirmedOn > 0
                    || $model->invalidatedThrough
                    || $model->token === $this->getIdentifier()
                    || 0 !== strncmp($model->token, $prefix.'-', \strlen($prefix) + 1)
                ) {
                    continue;
                }

                $token = new self($model, $this->framework);

                // The related records must match exactly
                if ($token->getRelatedRecords() !== $related) {
                    continue;
                }

                $model->invalidatedThrough = $this->model->token;
                $model->save();
            }
        }
    }
public function confirm(): void
    {
        if ($this->isConfirmed()) {
            throw new OptInTokenAlreadyConfirmedException();
        }

        if (!$this->isValid()) {
            throw new OptInTokenNoLongerValidException();
        }

        $this->model->tstamp = time();
        $this->model->confirmedOn = time();
        $this->model->removeOn = strtotime('+3 years');
        $this->model->save();

        $related = $this->model->getRelatedRecords();

        if (empty($related)) {
            return;
        }

        /** @var OptInModel $adapter */
        $adapter = $this->framework->getAdapter(OptInModel::class);
        $prefix = strtok($this->getIdentifier(), '-');

        // Invalidate other tokens that relate to the same records
        foreach ($related as $table => $ids) {
            if (!$models = $adapter->findByRelatedTableAndIds($table, $ids)) {
                continue;
            }

            foreach ($models as $model) {
                if (
                    $model->confirmedOn > 0
                    || $model->invalidatedThrough
                    || $model->token === $this->getIdentifier()
                    || 0 !== strncmp($model->token, $prefix.'-', \strlen($prefix) + 1)
                ) {
                    continue;
                }

                $token = new self($model, $this->framework);

                // The related records must match exactly
                if ($token->getRelatedRecords() !== $related) {
                    continue;
                }

                $model->invalidatedThrough = $this->model->token;
                $model->save();
            }
        }
    }
{@inheritdoc}