В данной статье, я расскажу, как можно осуществить проверку указанного посетителем (покупателем и тд.) E-mail адреса. Это полезно при регистрации пользователей или при создании подписки на обновления вашего сайта.
Последнее как раз меня и интересовало, т.к. когда мой блог стал достаточно посещаемым, стало «подписываться» много ботов и скрипт стал пытаться рассылать (используя планировщик хостинга) письма на несуществующие адреса.
Поэтому пришлось внедрить функционал подтверждения e-mail. Т.к. делал я его для данного блога, созданного с помощью php-фреймворка Yii2, описывать буду на базе Yii2. Хотя, на самом деле, его легко приспособить к чему угодно, включая чистый PHP.

Кратко о процессе реализации.
После того, как посетитель вашего сайта или интернет-магазина заполнит форму (регистрации/подписки и др.) ему присваивается специальный код, который с другими данными сохраняется в базу данных, а человеку показывается сообщение, что для окончания регистрации было отправлено письмо на указанный им e-mail со ссылкой подтверждения.
Человек, получив данное письмо, переходит по ссылке которая в нем указана. Она ведет на специальную страницу вашего сайта, где скрипт проверяет код (используя GET-параметр адресной строки) и, если такой код присутствует в БД, активирует данную запись (пользователя).

Вот, вкратце и все. Сам код, уже позже, можно использовать, для удаления аккаунта или отписки от сайта. Так же, человек переходит по ссылка на специальную страницу, где скрипт, по данному коду, находит пользователя в БД и удаляет (или деактивирует) его запись.


Реализация.

Прежде всего нужно в таблицу, в которой у вас хранятся данные по подписчикам (или зарегистрированным пользователям), например в subscription добавить 2 поля:
activation VARHAR 255, по умолчанию = null
status tinyint, длина 1, по умолчанию = 0

Для изменения структуры базы данных в Yii2, обычно стоит использовать миграции. Но если вы работаете над собственным сайтом, то можно создать данные поля вручную или выполнив SQL запрос:
ALTER TABLE `subscription` ADD `activation` VARCHAR(255) NULL DEFAULT NULL, ADD `status` TINYINT(1) NOT NULL DEFAULT '0';

В activation будет храниться код, сформированный при регистрации пользователя.
В status будет храниться отметка о том, подтвердил ли пользователь свой e-mail (1) или нет (0).
При этом, так же, сделаем, чтобы пользователи и боты не подтвердившие регистрацию удалялись автоматически. На это дадим им 5 дней.

Сама форма подписки, лично у меня, вынесена в отдельный виджет и использует функционал Pjax (сохраняет и обновляет данные без перезагрузки страницы). Про форму можно почитать в другой моей статье.

При отправке формы, данные POST-запросом отсылаются на один из контроллеров. У меня это frontend\controllers\SiteController.php который принимает данные и отправляет в модель на обработку. Вот его код (для примера):
public function actionSubscription(){
    $model = new \common\models\Subscription();

    if ($model->load(Yii::$app->request->post()) && $model->validate()){

        $model->activation();

        if ($model->save()) {
            Yii::$app->response->refresh(); //очистка данных из формы
            echo "<p style='color:green'>На Ваш e-mail отправлено письмо со ссылкой.<br><br>"
            . "Перейдите по ней для активации подписки!</p>";
            exit;
        } 
    } else {
        echo "<p style='color:red'>Ошибка оформления подписки.</p>";
        //Проверяем наличие фразы в массиве ошибки
        if( isset($model->errors['email']) && strpos($model->errors['email'][0], 'уже занято') !== false) {
            echo "<p style='color:red'>Вы уже подписаны!</p>";
        }  
    }
    exit;
}
Тут стоит обратить внимание на строку
$model->activation();
которая вызывает метод activation() в котором генерируется код для данного пользователя и сохраняется в БД вместе с другими данными:
public function activation(){
    $this->load(Yii::$app->request->post());

    $email = Html::encode($this->email);
    $this->email = $email;
    $this->addtime = (string) time();
    $this->activation = md5($email.time()); // Encrypted email+timestamp
    //отправка письма для подтверждения email
    MailKsl::mail_subscription_activation($this->email, $this->activation);
}

Данный метод стоит разместить в папке с моделями, а именно в модели обрабатывающей таблицу, в которую мы добавляли поля. У меня это common\models\Subscription.php
Если функционал по сохранению данных формы вы не выносили в модель, то просто добавьте себе строку по формированию кода в действие контроллера:
$this->activation = md5($email.time()); 
тут генерируется индивидуальный код используя e-mail пользователя и текущее время сервера.

В следующей же строке вызывается класс и метод отправки сообщения пользователю. Куда передается его e-mail и код для формирования ссылки.



Пример метода отправки (у меня за отправку отвечает отдельный класс MailKsl):
public static function mail_subscription_activation ($email, $cod){

    $absoluteHomeUrl = Url::home(true); //http://ваш сайт
    $serverName = Yii::$app->request->serverName; //ваш сайт без http
    $url = $absoluteHomeUrl.'activation/'.$cod;

    $msg = "Здравствуйте! Спасибо за оформление подписки на сайте $serverName!  Вам осталось только подтвердить свой e-mail. Для этого перейдите по ссылке $url";

    $msg_html  = "<html><body style='font-family:Arial,sans-serif;'>";
    $msg_html .= "<h2 style='font-weight:bold;border-bottom:1px dotted #ccc;'>Здравствуйте! Спасибо за оформление подписки на сайте <a href='". $absoluteHomeUrl ."'>$serverName</a></h2>\r\n";
    $msg_html .= "<p><strong>Вам осталось только подтвердить свой e-mail.</strong></p>\r\n";
    $msg_html .= "<p><strong>Для этого перейдите по ссылке </strong><a href='". $url."'>$url</a></p>\r\n";
    $msg_html .= "</body></html>";

    Yii::$app->mailer->compose()
        //->setFrom('admin@klisl.com') //не надо указывать если указано в common\config\main-local.php
        ->setTo($email) // кому отправляем - реальный адрес куда придёт письмо формата asdf @asdf.com
        ->setSubject('Подтверждение подписки.') // тема письма
        ->setTextBody($msg) // текст письма без HTML
        ->setHtmlBody($msg_html) // текст письма с HTML
        ->send();
}
не забываем подключать используемые классы в начале файла используя «use».

Тут стоит обратить внимание на строку
$url = $absoluteHomeUrl.'activation/'.$cod;
в ней мы к URL сайта добавляем строку 'activation/', которая будет обозначать действие для контроллера, обрабатывающего полученный код, ну и далее сам код.
Пример такой строки:
http://site.com/activation/107fcc8a246d4f70c5c846ed7f4ae9e1

Если вы и так отсылали письма при регистрации новых пользователей, то просто вставьте в нее ссылку для активации (и код ее формирования), а остальной код данного метода вам не нужен.


На данном этапе все данные пользователя сохранены в БД, а ему самому выслано письмо со ссылкой, перейдя по которой он сможет завершить процедуру регистрации (подписки).

Теперь нужно создать страницу, ссылку на которую мы отправляем в письме.
Добавляем указанное действие в контроллер frontend\controllers\SiteController.php:
/*
 * Подтверждение подписки.
 * В качестве GET-параметра принимается код, который сравнивается с тем, что в таблице subscription
 * в ячейке activation. При успехе - ставится true в ячейку status.
 */
public function actionActivation(){
    $code = Yii::$app->request->get('code');
    $code = Html::encode($code);
    //ищем код подтверждения в БД
    $find = \common\models\Subscription::find()->where(['activation'=>$code])->one();
    if($find){
        $find->status = 1;
        if ($find->save()) {
            $text = '<p>Поздравляю!</p>
            <p>Ваш e-mail подтвержден и Вы оформили подписку на обновления.</p>';
            //страница подтверждения
            return $this->render('activation', [
                'text' => $text
            ]);
        }
    }
    $absoluteHomeUrl = Url::home(true);
    return $this->redirect($absoluteHomeUrl, 303); //на главную
}

Строкой
$code = Yii::$app->request->get('code');
получаем переданный код. Далее проверяем есть ли такой в БД. Если да – ставим true в поле status и на этом пользователь считается полностью зарегистрированным (или подписанным и тд.) о чем ему выводим соответствующее сообщение. Если же такого кода в БД нет – делаем переадресацию на главную страницу.

Для того, что бы по ссылке
http://site.com/activation/107fcc8a246d4f70c5c846ed7f4ae9e1
пользователь попадал в контроллер site и метод activation, нужно добавить правило маршрутизации. Для это в файл frontend\config\main.php в массив urlManager, подмассив rules нужно добавить
'activation/<code:.+>' => 'site/activation',
в начало или по крайней мере до строки
'<action:\w+>' => 'site/<action>',

Таким образом мы получим GET параметр с ключом code и значением в качестве переданного кода, который обработает контроллер.

Ну и осталось сделать файл-представление frontend\views\site\activation.php.
Пример такого файла:
<?php
use yii\helpers\Html;

$this->title = 'Подписка';
$this->registerMetaTag([
    'name' => 'robots',
    'content' => 'noindex,nofollow'
]);

$this->params['breadcrumbs'][] = $this->title;
?>

  <h1><?= Html::encode($this->title) ?></h1>
    <div class="top_block white">
        <div class="content">
            <?=$text?>
        </div>
    </div>    

Вызывая метод registerMetaTag() формируем метатег, указывающий поисковым службам, что это служебная страница и ее не нужно индексировать.
У меня на сайте, на этой странице, так же используются блоки и стили Bootstrap + виджеты боковой колонки. Вы сможете настроить вид индивидуально.


На этом процесс регистрации/подписки завершен, а именно – пользователь подтвердил свой e-mail о чем свидетельствует значение true (единица) в поле status. Далее, при выборка пользователей, вы просто проверяете наличие данного значения и если в поле status находится значение false – игнорируете таких пользователей.

Что еще не мешало бы, так это сделать автоматическое удаление пользователей (и ботов) которые не подтвердили свой электронный адрес в течении определенного периода, чтобы не накапливать их в таблице. Ниже я приведу такой скрипт.

У меня на блоге, я реализовал отправку уведомлений о новых статьях подписчикам с помощью планировщика (cron), который запускается в определенное время и делает рассылку. Поэтому такое удаление неподтвержденных пользователей я добавил в скрипт рассылки, так же для автоматического выполнения по расписанию. Вы же, если не используете рассылку и планировщик, можете делать удаление таких пользователей при наступлении какого-то события, например при регистрации нового пользователя.
//Удаление подписчиков, которые не подтвердили свой e-mail в течении 7-и дней.
public function deleteSubActivation(){
    $today = time();
    $old_time = $today - (86400*7);

    $oldSub = Subscription::find()
        ->where(['status' => '0'])
        ->andWhere(['<','addtime', $old_time])
        ->all();

    foreach($oldSub as $sub){
        $sub->delete();
    }
}
В поле addtime таблицы subscription у меня хранится метка времени (в формате Unix) когда текущий пользователь был добавлен.