Блог Андрея

 
 

Каптча на форме FOSUser. Добавляем google re-captcha

В предыдущей статье я перегрузил (декорировал) контроллер FOSUserBundle ResettingController, теперь осталось только добавить гугл каптчу, ради чего всё и затеялось.

Само по себе добавление не вызвало проблем, установив пакет

composer require google/recaptcha

я получил практически всё и сразу. В описании пакета есть пример, который хорошо работает. Вам надо только получить у гугла ключи, секретный ключ и ключ сайта, ссылка на страницу их получения есть всё в той же документации пакета.

Единственная проблема возникла с получением параметров конфигурации в контроллере.

Я изменил тип последнего аргумента в конфигурации перегруженного контроллера на @service_container:

AppControllerResettingController:
        decorates: fos_user.resetting.controller
        arguments:
            - '@AppControllerResettingController.inner'
            - '@event_dispatcher'
            - '@fos_user.resetting.form.factory'
            - '@fos_user.user_manager'
            - '@fos_user.util.token_generator'
            - '@fos_user.mailer'
            - '%fos_user.resetting.retry_ttl%'
            - '@service_container'

, добавил соотвтествующий use в начало контроллера:

use SymfonyComponentDependencyInjectionContainerInterface;

и добавил аргумент конструктора:

    /**
     * @param EventDispatcherInterface $eventDispatcher
     * @param FactoryInterface         $formFactory
     * @param UserManagerInterface     $userManager
     * @param TokenGeneratorInterface  $tokenGenerator
     * @param MailerInterface          $mailer
     * @param int                      $retryTtl
     * @param ContainerInterface		   $container
     */
    public function __construct(BaseResettingController $baseController, EventDispatcherInterface $eventDispatcher, FactoryInterface $formFactory, UserManagerInterface $userManager, TokenGeneratorInterface $tokenGenerator, MailerInterface $mailer, $retryTtl, ContainerInterface $container);

Также я объявил приватное поле класса $container и присвоил ему новый аргумент в конструкторе.

    public function __construct(/** ... */ContainerInterface $container)
{
    //...
    $this->container = $container;
}

Однако, попытка получить параметр

    /**
     * Request reset user password: submit form and send email.
     * @param Request $request
     * @return Response
     */
    public function sendEmailAction(Request $oRequest)
    {	
		$sGRecaptchaResponse = $oRequest->get('g-recaptcha-response');
		$sPhone = $oRequest->get('username');
		$secret = $this->container->getParameter('app.google_recaptcha_secret_key');
}

привела к ошибке.

Решение оказалось простым, вместо защищённого поля $container я добавил приватное $oContainer и всё заработало.

Полный код перегруженного метода:

    
/*
 * This is OVERRIDE  file  of the FOSUserBundle package.
 *
*/

namespace AppController;

//...
use ReCaptchaReCaptcha;
use SymfonyComponentDependencyInjectionContainerInterface;
/**
 *
 */
class ResettingController extends AbstractController
{
//...
    /**
     * Request reset user password: submit form and send email.
     *
     * @param Request $request
     *
     * @return Response
     */
    public function sendEmailAction(Request $oRequest)
    {	
       //получаем идентификатор "пройденной" каптчи
		$sGRecaptchaResponse = $oRequest->get('g-recaptcha-response');
       //получаем имя пользователя (в моём случае телефонный номер)
		$sPhone = $oRequest->get('username');
       //секретный ключ для google
		$secret = $this->oContainer->getParameter('app.google_recaptcha_secret_key');
       //ip клиента
		$sRemoteIp = $oRequest->server->get('REMOTE_ADDR');
       //домен, как он указан в google
		$sDomain = $this->oContainer->getParameter('app.domain');
		
		//check user by phone
		$aUsers = $this->getDoctrine()->getRepository('App:Users')->findBy(['username' => $sPhone]);
		$oUser = $aUsers[0] ?? null;
		if (!$oUser) {
           //напишем, что не найден такой товарищ
			return $this->_redirectToFailRoute('User with phone not found');
		}
		if (!$sGRecaptchaResponse) {
           //напишем, что надо поставить галочку в гуглокаптче
			return $this->_redirectToFailRoute('missing-input-response');
		}
		//Фактически пример из документации пакета composer google/recaptcha
		$oRecaptcha = new ReCaptcha($secret);
		$oResponse = $oRecaptcha->setExpectedHostname($sDomain)
						  ->verify($sGRecaptchaResponse, $sRemoteIp);
		if ($oResponse->isSuccess()) {
			// Verified! Запускаем стандартный механизм FOSUser
			return $this->resettingController->sendEmailAction($oRequest);
		}
           //если что-то пошло не так, получаем у google список ошибок
		$aErrors = $oResponse->getErrorCodes();
        //конец примера из документации пакета composer google/recaptcha
           //локализуем их
		$oTranslator = $this->oContainer->get('translator');
		$aErrors = array_map(function($sMessage, $oTranslator){
			return $oTranslator->trans($sMessage);
		}, $aErrors, [$oTranslator]);
           //и покажем на странице ввода каптчи
		return $this->_redirectToFailRoute( '

' . join('

', $aErrors) . '

'); } /** * Redirect in to resetting_request and set flash with error text * Здесь просто сделаем редирект на страницу с формой и добавим flash с сообщением об ошибке * @param string $sMessage - with error text * * @return Redirect */ private function _redirectToFailRoute(string $sMessage) { $sFailRoute = 'fos_user_resetting_request'; $sMessage = $this->oContainer->get('translator')->trans($sMessage); $this->addFlash('notice', $sMessage); return $this->redirectToRoute($sFailRoute); } }