https://a.storyblok.com/f/270183/48135/99eff8075e/blog_befriending_symfony_voice-verify_1200x600.png

Serviço de amizade com Symfony e Vonage

Publicado em April 19, 2021

Tempo de leitura: 64 minutos

Com a atual situação global, a maioria dos países está sob algum tipo de confinamento. O distanciamento social é fundamental neste momento para reduzir o impacto da Covid-19. Enquanto alguns de nós podem ter agendas lotadas de encontros virtuais, outros talvez não tenham um círculo tão grande de pessoas para ligar ou com quem se conectar para passar o tempo. Minha avó, por exemplo, não tem muita familiaridade com a tecnologia, por isso depende das ligações telefônicas. Embora tenha a sorte de ter treze netos, ela costuma dizer que, em alguns dias, não tem notícias de ninguém e gostaria de conversar com alguém diariamente. Na Vonage, temos oportunidades regulares de desenvolver projetos para nosso aprendizado. Nesta oportunidade, optei por criar um serviço de amizade, que apresentaria usuários que estão em situação de vulnerabilidade, se sentem solitários ou querem conversar com uma pessoa diferente todos os dias. A ideia por trás disso é permitir que as pessoas façam novos amigos durante o confinamento ou a qualquer momento.

Pré-requisitos

Introdução

Ao longo deste tutorial, você criará um novo projeto em PHP usando Symfony. Você utilizará o o Vonage Verify, Vonage Voice e MapQuest . Este projeto, uma vez concluído, oferecerá um serviço que permitirá aos usuários se cadastrarem com seu número de telefone, nome, cidade e município/estado. Ao se cadastrar, o usuário receberá um código de verificação, que deverá ser inserido no site. Após a verificação, os usuários são adicionados a uma lista de pessoas que, todos os dias, receberão uma ligação telefônica, conectadas a outro usuário para conversar. Por fim, ao final de cada dia, cada usuário receberá novamente uma ligação automática solicitando feedback sobre a ligação que recebeu naquele dia.

Clonar o repositório

O projeto utiliza o contêineres contêineres Nginx, MySQL, PHPe contêineres Ngrok para alterações mínimas na configuração do servidor.

Comece este tutorial clonando o repositório existente. Para cloná-lo, basta copiar o comando a seguir no Terminal e, em seguida, acessar o diretório do projeto:

git clone git@github.com:nexmo-community/befriending-service-with-symfony.git cd befriending-service-with-symfony

Seu Terminal exibirá dois diretórios:

  • docker onde estão suas configurações do Docker (não é preciso alterar nada, a menos que você esteja definindo credenciais diferentes para o banco de dados)

  • project e é aí que entra o seu novo projeto Symfony está armazenado

Executar o Docker

No Terminal, mude para o diretório docker/ e execute o seguinte comando para compilar e iniciar seus contêineres do Docker:

docker-compose up -d

O comando acima baixa todos os Docker , adicionando quaisquer alterações personalizadas definidas nos arquivos encontrados no docker/ diretório. O `build` compila esses contêineres e os executa como serviços para que você possa rodar seu aplicativo web.

Assim que o comando for concluído, você verá uma confirmação de que os Docker contêineres estão em execução, conforme mostrado no exemplo abaixo:

Successfully Running Docker

Seus contêineres Docker e servidores já estão prontos para o início do desenvolvimento!

The Applications

Cadastro e verificação de usuários

Instalar o Symfony

Se você acessar o project diretório, você verá a estrutura de arquivos de uma instalação vazia do Symfony, igual à que aparece abaixo:

Fresh Symfony Installation

Ainda no diretório docker/, execute o comando abaixo para instalar todas as bibliotecas PHH descritas no arquivo composer.json:

docker-compose exec php composer install

As configurações do banco de dados para este projeto Symfony precisam ser armazenadas em algum lugar. Portanto, no seu IDE, na pasta project/ diretório, crie um novo arquivo chamado .env.local. Depois de criado, adicione a seguinte linha a esse novo arquivo:

DATABASE_URL=mysql://user:password@mysql:3306/befriending?serverVersion=8.0.17&charset=utf8

Segue abaixo uma descrição detalhada da falha na linha mencionada acima:

  • Nome do banco de dados: befriending

  • Usuário do banco de dados: user

  • Senha do banco de dados: password

  • Servidor e porta do banco de dados: mysql:3306

Esses valores estão predefinidos no docker/docker-compose.yml arquivo. O nome de usuário e a senha atuais não são seguros; por motivos de segurança, caso deseje alterá-los, faça-o em ambos project/.env.local e docker/docker-compose.yml, e, em seguida, execute:

docker-compose build

Criar uma entidade de usuário

É User é necessária uma entidade, que é a classe que define corretamente a estrutura da user tabela do banco de dados. Essa tabela é onde os novos cadastros de usuários são armazenados.

Para isso, o Symfony lançou uma make biblioteca que permite aos usuários utilizar a Interface de Linha de Comando (CLI) para criar determinadas classes.

Execute o comando abaixo e siga as instruções fornecidas para criar as propriedades da User entidade:

docker-compose exec php bin/console make:entity
  • Nome da classe da entidade a ser criada ou atualizada (por exemplo, DeliciousGnome):

    • User

  • Imóvel 1

    • Nome: phoneNumber

    • Tipo: string

    • Comprimento do campo: 255

    • Pode ser nulo?: no

  • Imóvel 2

    • Nome: name

    • Tipo: string

    • Comprimento do campo: 255

    • Pode ser nulo?: no

  • Imóvel 3

    • Nome: town

    • Tipo: string

    • Comprimento do campo: 255

    • Pode ser nulo?: no

  • Imóvel 4

    • Nome: county

    • Tipo: string

    • Comprimento do campo: 255

    • Pode ser nulo?: no

  • Imóvel 5

    • Nome: countryCode

    • Tipo: string

    • Comprimento do campo: 2

    • Pode ser nulo?: no

  • Imóvel 6

    • Nome: verificationRequestId

    • Tipo: string

    • Comprimento do campo: 255

    • Pode ser nulo?: yes

  • Imóvel 7

    • Nome: verified

    • Tipo: boolean

    • Pode ser nulo?: no

  • Imóvel 8

    • Nome: active

    • Tipo: boolean

    • Pode ser nulo?: no

Após a execução deste comando, são criados dois novos arquivos:

  • project/src/Entity/User.php

  • project/src/Repository/UserRepository.php

A UserRepository é uma classe de repositório onde os desenvolvedores encontrarão consultas personalizadas para a User entidade. Ignore essa classe por enquanto.

A entidade criada não reflete, no momento, o que está no banco de dados. Para fazer com que o banco de dados reflita o que você definiu na User entidade, execute o comando abaixo para gerar um novo arquivo de migração:

docker-compose exec php bin/console make:migration

Se você quiser ver as próximas alterações no banco de dados, os arquivos de migração gerados são salvos em project/src/Migrations/.

Desde que você esteja satisfeito com essas alterações, para salvá-las no banco de dados, execute o comando abaixo:

docker-compose exec php bin/console doctrine:migrations:migrate

A User entidade ainda precisa de algumas alterações adicionais que não puderam ser feitas com a maker CLI. Para fazer essas alterações, abra esta classe, que você encontra em project/src/Entity/User.php.

Primeiro, uma classe UniqueEntity é usada como uma anotação para garantir que uma das propriedades da sua classe `User` seja única (ou seja, haja apenas uma entrada com esse valor). Adicione esta classe no início:

use Doctrine\ORM\Mapping as ORM;
+ use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

Adicione as duas linhas a seguir:

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 + * @ORM\Table(name="`user`")
 + * @UniqueEntity("phoneNumber")
 */

A primeira alteração que você fez foi adicionar uma anotação codificada diretamente para o nome da tabela. O motivo é que a palavra user é uma palavra-chave no MySQL. Portanto, o sistema precisa que o user nome da tabela entre aspas para garantir que, sempre que uma consulta for feita, essa alteração impeça que o código confunda o nome da tabela com a palavra-chave.

A segunda alteração consiste em adicionar a propriedade phoneNumber como um UniqueEntity. O uso de UniqueEntity evitará a possibilidade de vários usuários terem o mesmo número de telefone.

Agora, atualize a definição da phoneNumber propriedade para que seja um campo exclusivo:

/**
-  * @ORM\Column(type="string", length=255)
+  * @ORM\Column(type="string", length=255, unique=true)
 */
private $phoneNumber;

A alteração final na User entidade é garantir activee verified os campos sejam preenchidos por padrão com “false”; portanto, crie uma __construct() função para fazer isso:

public function __construct()
{
    $this->setActive(false);
    $this->setVerified(false);
}

Você acabou de criar uma nova entidade “Usuário”, que é como o sistema cria novos usuários, além de salvar e editar usuários no banco de dados.

Instalar o Webpack Encore

O Bootstrap é utilizado neste tutorial para melhorar o design e o layout das páginas. O Symfony lançou recentemente o Webpack Encore, que é uma maneira muito mais simples de integrar o Webpack à sua aplicação Symfony ou PHP. Para instalar o Webpack Encore, execute os seguintes comandos:

docker-compose exec php composer require symfony/webpack-encore-bundle cd ../project/ yarn install

Ao executar o yarn install comando configurará o projeto para lidar também com arquivos JavaScript. Esse comando cria um assets diretório e, dentro dele, há um js/app.js arquivo e um css/app.css arquivo.

Agora, o Bootstrap está instalado com o seguinte comando:

yarn add bootstrap --dev

A aplicação Symfony ainda não reconhece a existência do Bootstrap. Para incluí-lo no projeto, abra o project/assets/css/app.scss arquivo e adicione a seguinte linha:

@import "../../node_modules/bootstrap";

O Bootstrap JS requer o jQuery e o PopperJs; portanto, instale-os com o comando abaixo:

yarn add jquery popper.js --dev

Mais uma vez, o aplicativo Symfony não reconhece o jQuery e o PopperJs. Portanto, inclua-os no projeto abrindo o project/assets/js/app.js arquivo e adicionando as seguintes atualizações a ele:

import '../css/app.css';
+ import $ from 'jquery';

+ require('bootstrap');

Esses arquivos CSS e JS precisam ser compilados para que possam ser acessados nos modelos. Para isso, execute o seguinte comando:

yarn run dev

Os componentes do front-end necessários estão instalados e configurados!

Instale o SDK do Vonage e a biblioteca libphonenumber-for-php

Este tutorial requer mais duas bibliotecas PHP:

  • o SDK da Vonage,

    • para enviar solicitações de verificação,

    • verificar usuários

    • fazer ligações

    • processar entradas de chamadas telefônicas DTMF.

  • O libphonenumber-for-php, de Giggsey, que converte números de telefone para formatos internacionalizados ou nacionais.

Para instalar o SDK do Vonage para PHP, execute:

# Note, if you're not inside the `docker` directory in your terminal run this command first: # cd ../docker/ docker-compose exec php composer require nexmo/client

Para utilizar as variáveis de ambiente do arquivo que você criará na próxima etapa, é necessário o componente DotEnv . Para instalar esse componente, execute o seguinte comando no Terminal:

docker-compose exec php composer require symfony/dotenv

No Painel do Desenvolvedor da Vonage, você encontrará “Suas credenciais de API”; anote-as.

No project/ diretório, adicione as três linhas a seguir ao .env.local arquivo (substituindo api_key e api_secret pela sua chave e senha):

O VONAGE_BRAND_NAME é o nome da empresa/marca que você está representando para a verificação:

VONAGE_API_KEY=<api_key>
VONAGE_API_SECRET=<api_secret>
VONAGE_BRAND_NAME=Befriending

É necessário encontrar uma maneira de verificar se os números de telefone são válidos antes de realizar a verificação. Você também precisa garantir que o número de telefone esteja no formato correto para que a Verify API saiba a qual região (código do país) o número pertence. Você vai usar a versão em PHP da biblioteca libphonenumber do Google, desenvolvida por Giggsey para garantir que os números de telefone estejam formatados corretamente.

Execute o comando abaixo para instalar libphonenumber-for-php:

docker-compose exec php composer require giggsey/libphonenumber-for-php

Agora você tem as duas bibliotecas essenciais necessárias para:

  • Formatar números de telefone,

  • Verifique se os números de telefone são válidos,

  • Verificar com a Verify API da Vonage

  • Fazer ligações

Compilar o utilitário Verify

Crie um novo diretório dentro de project/src/ chamado Util, e dentro desse novo diretório, crie um novo arquivo chamado VonageVerifyUtil.php. Esse novo arquivo é a classe utilitária que contém a funcionalidade para verificar os usuários quando eles se cadastram. Dentro desse novo arquivo, copie o código abaixo:

<?php

namespace App\Util;

use App\Entity\User;
use Nexmo\Client as VonageClient;
use Nexmo\Client\Credentials\Basic;
use Nexmo\Verify\Verification;

class VonageVerifyUtil
{
    /** @var VonageClient */
    protected $client;

    public function __construct()
    {
        $this->client = new VonageClient(
            new Basic(
                $_ENV['VONAGE_API_KEY'],
                $_ENV['VONAGE_API_SECRET']
            )
        );     
    }
}

Adicione dois novos métodos a esta classe. O objetivo desses dois métodos é:

  • converter o número de telefone para um formato internacionalizado.

  • converter o número de telefone para um formato nacional.

public function getInternationalizedNumber(User $user): ?string
{
    $phoneNumberUtil = \libphonenumber\PhoneNumberUtil::getInstance();

    $phoneNumberObject = $phoneNumberUtil->parse(
        $user->getPhoneNumber(),
        $user->getCountryCode()
    );

    if (!$phoneNumberUtil->isValidNumberForRegion(
        $phoneNumberObject,
        $user->getCountryCode())
    ) {
        return null;
    }

    return $phoneNumberUtil->format(
        $phoneNumberObject,
        \libphonenumber\PhoneNumberFormat::INTERNATIONAL
    );
}

public function getNationalizedNumber(string $phoneNumber)
{
    $phoneNumberUtil = \libphonenumber\PhoneNumberUtil::getInstance();
    $phoneNumberObject = $phoneNumberUtil->parse($phoneNumber);

    return '0' . $phoneNumberObject->getNationalNumber();
}

A próxima função de que o sistema precisa é a de gerar a solicitação de verificação para novos registros. Adicione o método abaixo para processar a criação de uma solicitação de verificação:

public function sendVerification(User $user)
{
    $internationalizedNumber = $this->getInternationalizedNumber($user);

    if (!$internationalizedNumber) {
        return null;
    }

    $verification = new Verification(
        $internationalizedNumber,
        $_ENV['VONAGE_BRAND_NAME'],
        ['workflow_id' => 2]
    );

    return $this->client->verify()->start($verification);
}

observação Se você estiver interessado em um fluxo de trabalho diferente para o processo de verificação, consulte a documentação da Vonage.

O próximo passo na verificação é receber o código de verificação inserido pelo usuário e enviar uma solicitação de verificação com esse código:

public function verify(string $requestId, string $verificationCode)
{
    $verification = new Verification($requestId);

    return $this->client->verify()->check($verification, $verificationCode);
}

A última função não é utilizada nesta classe utilitária, mas será usada em vários pontos do projeto. Essa função extrai o requestId de uma resposta da API. Adicione o seguinte:

public function getRequestId(Verification $verification): ?string
{
    $responseData = $verification->getResponseData();

    if (empty($responseData)) {
        return null;
    }

    return $responseData['request_id'];
}

Criar um novo usuário

Para criar um novo Register controlador com a biblioteca do Symfony make , execute o comando abaixo:

docker-compose exec php bin/console make:controller

Onde está escrito: Choose a name for your controller class (e.g. AgreeableGnomeController): digite RegisterController e pressione Enter.

Você verá no Terminal a saída correspondente a dois novos arquivos gerados:

created: src/Controller/RegisterController.php
created: templates/registration/index.html.twig

O VonageVerifyUtil precisa ser injetado como um serviço no recém-criado RegisterController ; então, abra o arquivo src/Controller/RegisterController.php e insira-o na __construct() função.

+ use App\Util\VonageVerifyUtil;

class RegisterController extends AbstractController
{
+     /** @var VonageVerifyUtil */
+     protected $vonageVerifyUtil;

+     public function __construct(VonageVerifyUtil $vonageVerifyUtil)
+     {
+         $this->vonageVerifyUtil = $vonageVerifyUtil;
+     }

No seu navegador, abra a register página com a URL http://localhost:8081/register. Você será recebido com:

The view in a web page showing the default RegisterController output

É hora de criar a página de cadastro!

A biblioteca do Symfony make está se mostrando muito útil; até agora, ela criou uma Entity, um Controller, e agora vai criar um Form. Para criar o UserType formulário, execute o seguinte comando:

docker-compose exec php bin/console make:form

Quando for solicitado um nome, digite UserType, e quando for solicitado um Entity tipo: User.

Você encontrará o recém-criado UserType em project/src/Form/UserType.php. Abra-o, localize a buildForm() função e substitua seu conteúdo por:

$builder
    ->add('phoneNumber', TelType::class, [
        'attr' => [
            'class' => 'form-control form-control-lg'
        ]
    ])
    ->add('name', TextType::class, [
        'constraints' => [
            new NotBlank(),
            new Length(['min' => 3]),
        ],
        'attr' => [
            'class' => 'form-control form-control-lg'
        ],
    ])
    ->add('town', TextType::class, [
        'constraints' => [
            new NotBlank(),
            new Length(['min' => 3]),
        ],
        'attr' => [
            'class' => 'form-control form-control-lg'
        ],
    ])
    ->add('county', TextType::class, [
        'constraints' => [
            new NotBlank(),
            new Length(['min' => 3]),
        ],
        'attr' => [
            'class' => 'form-control form-control-lg'
        ],
    ])
    ->add('countryCode', ChoiceType::class, [
        'label' => false,
        'attr' => [
            'class' => 'form-control form-control-lg'
        ],
        'choices' => [
            "United Kingdom" => "GB",
            "United States" => "US"
        ]
    ])
    ->add('submit', SubmitType::class, [
        'label' => 'Sign up',
        'attr' => [
            'class' => 'btn btn-info btn-lg btn-block'
        ],
    ])
;

No início da classe, será necessário incluir várias novas classes, ChoiceType, SubmitType, Length, NotBlank, TelType, TextType. Adicione-as no início do arquivo, conforme mostrado abaixo:

use App\Entity\User;
use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\Form\Extension\Core\Type\TelType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Validator\Constraints\Length;
+use Symfony\Component\Validator\Constraints\NotBlank;

Criar modelo de inscrição

O modelo base precisa incluir o Bootstrap para que nossas classes CSS do Bootstrap sejam reconhecidas e exibidas.

Abra project/templates/base.html.twig, o modelo base que será incluído por todos os outros modelos do projeto, e substitua o conteúdo por:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}
            {{ encore_entry_link_tags('app') }}
        {% endblock %}
    </head>
    <body>
        <nav class="navbar navbar-dark bg-dark navbar-expand-lg">
            <a class="navbar-brand" href="#">Welcome</a>
            <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
        </nav>
        <div class="container-fluid h-100">
            {% block body %}{% endblock %}
        </div>
        {% block javascripts %}
            {{ encore_entry_script_tags('app') }}
        {% endblock %}
    </body>
</html>

Agora que o modelo já inclui o Bootstrap, é hora de atualizar o index.html.twig modelo para exibir nosso formulário e quaisquer outras informações. Abra project/templates/register/index.html.twig e substitua todo o conteúdo entre {% block body %} e {% endblock %} por:

<div class="row justify-content-center align-items-center h-100">
    <div class="col col-sm-6 col-md-6 col-lg-4 col-xl-3">
        {{ form_start(form) }}
            <h1 class="h3 mb-3 font-weight-normal">Sign up</h1>
        {{ form_end(form) }}
    </div>
</div>

Concluir o processo de inscrição

Você já criou um modelo e uma classe de formulário, mas, no momento, o projeto não utiliza nenhum desses arquivos. É preciso incluí-los na sua RegisterController método chamado index(). Volte para project/src/Controller/RegisterController.php.

Atualize o conteúdo do método index() conforme mostrado abaixo:

/**
 * @Route("/register", name="register")
 */
-public function index()
+public function index(Request $request)
{
+ $user = new User();
+
+ $form = $this->createForm(
+     UserType::class,
+     $user
+ );
+ $form->handleRequest($request);
+
+ if ($form->isSubmitted() && $form->isValid()) {
+    $entityManager = $this->getDoctrine()->getManager();
+    $entityManager->persist($user);
+    $entityManager->flush();
+
+    $verification = $this->vonageVerifyUtil->sendVerification($user);
+    $requestId = $this->vonageVerifyUtil->getRequestId($verification);
+
+    if ($requestId) {
+        $user->setVerificationRequestId($requestId);
+        $entityManager->flush();
+    }
+ }
+
  return $this->render('register/index.html.twig', [
+    'controller_name' => 'RegisterController',
+    'form' => $form->createView(),
  ]);
}

As alterações no exemplo acima têm os seguintes efeitos:

  • Cria um novo objeto vazio User objeto vazio,

  • Cria uma nova instância da UserType classe do formulário usando o novo objeto User como a classe de dados com a qual estruturar o formulário,

  • Processa a solicitação do formulário para determinar se o formulário:

    • foi enviado,

    • o conteúdo do formulário é válido (por exemplo, se phoneNumber está vazio ou não é único)

  • Se o formulário for enviado e estiver válido, então a função:

    • o novo usuário é gravado no banco de dados

    • aplica essas alterações

    • Envia uma nova solicitação à Verify API,

    • armazena o ID da solicitação de verificação no registro do usuário no banco de dados

  • Se o formulário não tiver sido enviado ou não for válido, a função exibe o modelo para o usuário, com o formulário incluído no modelo.

É necessário importar várias classes para a classe atual, tais como User e UserType, inclua-as no início, conforme mostrado abaixo:

+use App\Entity\User;
+use App\Form\UserType;
use App\Util\VonageVerifyUtil;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;

Resumindo, você acabou de criar um novo RegisterController, uma User entidade e UserType formulário. Você atualizou seu modelo para o index() método dentro do RegisterController para exibir os campos do formulário que você espera que o usuário veja.

Você pode ver essas alterações em http://localhost:8081/register, ou conforme mostrado no exemplo abaixo:

Template of registration page being displayed in a web browser

Criar formulário de verificação

Crie o VerifyType formulário com o seguinte comando:

docker-compose exec php bin/console make:form

Para o campo de nome: VerifyType E, quanto à entidade a ser usada, escolha User

Ao enviar, você verá algo semelhante ao mostrado na imagem abaixo:

Terminal output confirming creation of VerifyForm class

A criação do VerifyType formulário dessa maneira significa que o sistema inseriu automaticamente todas as User propriedades da entidade nesse formulário. Nenhum desses campos é obrigatório; portanto, atualize o código com o seguinte:

$builder
-    ->add('phoneNumber')
-    ->add('name')
-    ->add('town')
-    ->add('county')
-    ->add('countryCode')
-    ->add('verificationRequestId')
-    ->add('verified')
-    ->add('active')
+->add('verificationCode', TextType::class, [
+    'mapped' => false,
+    'attr' => [
+        'class' => 'form-control form-control-lg'
+    ],
+    'constraints' => [
+        new NotBlank([
+            'message' => 'Please enter a verification code',
+        ]),
+        new Length([
+            'min' => 4,
+            'max' => 4,
+            'minMessage' => 'The verification code is a 4 digit number.',
+        ]),
+    ],
+])
+->add('submit', SubmitType::class, [
+    'label' => 'Verify',
+    'attr' => [
+        'class' => 'btn btn-info btn-lg btn-block'
+    ],
+])
;

O código acima define o formulário que deve ser preenchido para o envio de um código de verificação. Esse formulário contém dois campos: o verificationCode e submit. Cada campo possui seus próprios atributos para fins de validação e exibição no formulário ou no momento do envio do mesmo.

No início da classe, algumas importações precisam ser incluídas. Portanto, copie o código no início da classe, conforme mostrado abaixo:

use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\Form\Extension\Core\Type\TextType;
+use Symfony\Component\Validator\Constraints\Length;
+use Symfony\Component\Validator\Constraints\NotBlank;

Verificação do identificador

Então, você criou sua página de cadastro, mas e agora? Você precisa criar uma página para que o usuário insira o código de verificação que recebe da Verify API. Dentro do RegisterController , crie um novo método abaixo do index() chamado verify(). O exemplo abaixo mostra isso e o conteúdo desse método:

/**
 * @Route("/register/verify/{user}", name="app_register_verify")
 * @ParamConverter("user", class="App:User")
 */
public function verify(Request $request, User $user): Response
{
    $form = $this->createForm(VerifyType::class, $user);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $verify = $this->vonageVerifyUtil->verify(
            $user->getVerificationRequestId(),
            $form->get('verificationCode')->getData()
        );

        if ($verify instanceof Verification) {
            $user->setVerificationRequestId(null);
            $user->setVerified(true);
            $user->setActive(true);

            $entityManager = $this->getDoctrine()->getManager();
            $entityManager->flush();
        }
    }

    return $this->render('register/verify.html.twig', [
        'form' => $form->createView(),
    ]);
}

No início da classe, inclua o Response, VerifyType, ParamConvertere Verification classes, conforme mostrado abaixo:

use App\Entity\User;
use App\Form\UserType;
use App\Util\VonageVerifyUtil;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
+use Nexmo\Verify\Verification;
+use Symfony\Component\HttpFoundation\Response;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
+use App\Form\VerifyType;

Agora é necessário o modelo para exibir esse formulário. Crie um novo arquivo em project/templates/register/ chamado verify.html e copie o conteúdo do bloco de código abaixo para esse arquivo:

{% extends 'base.html.twig' %}

{% block title %}Verify{% endblock %}

{% block body %}
    <div class="row justify-content-center align-items-center h-100">
        <div class="col col-sm-6 col-md-6 col-lg-4 col-xl-3">
            <h1 class="h3 mb-3 font-weight-normal">Verify</h1>

            {{ form_start(form) }}
                <div class="form-group">
                    {{ form_row(form.verificationCode) }}
                </div>

            {{ form_end(form) }}
        </div>
    <div>
{% endblock %}

Como conduzir uma verificação bem-sucedida

No âmbito do RegisterController projeto, agora é necessária uma página de sucesso para quando o usuário tiver concluído a verificação com sucesso. Criar um novo registerSucess() método, que exibirá um modelo com a mensagem informando que o cadastro foi bem-sucedido:

/**
 * @Route("/register/success", name="app_register_success")
 */
public function registerSuccess(): Response
{
    return $this->render('register/success.html.twig');
}

Agora crie um novo arquivo em project/templates/register/ chamado success.html.twig e exiba o seguinte:

{% extends 'base.html.twig' %}

{% block title %}Registration Successful!{% endblock %}

{% block body %}
    <div class="row justify-content-center align-items-center h-100">
        <div class="col col-sm-6 col-md-6 col-lg-4 col-xl-3">
            <h1 class="h3 mb-3 font-weight-normal">Sign up Successful!</h1>

            <p>Thank you for verifying your phone number.</p>

            <p>Your details have been added to the database to participate in the Befriending Service.</p>

            <p>You will be automatically contacted at 2pm every day and connected with another user in the system.</p>
        </div>
    </div>
{% endblock %}

São necessárias algumas pequenas alterações para finalizar a parte de verificação. São necessários redirecionamentos para que, sempre que o envio de um formulário for bem-sucedido, o usuário seja direcionado para a próxima página. Portanto, em RegisterController, em public function index(), localize e adicione o seguinte:

if ($requestId) {
    $user->setVerificationRequestId($requestId);
    $entityManager->flush();

+    return $this->redirectToRoute('app_register_verify', ['user' => $user->getId()]);
}

E dentro do método public function verify(Request $request, User $user), localize e adicione o seguinte:

if ($verify instanceof Verification) {
    $user->setVerificationRequestId(null);
    $user->setVerified(true);

    $entityManager = $this->getDoctrine()->getManager();
    $entityManager->flush();

+    return $this->redirectToRoute('app_register_success');
}

Latitude e longitude

Para poder localizar usuários cadastrados geograficamente próximos a outros usuários cadastrados, o sistema precisa dispor da latitude e longitude de cada usuário no momento do cadastro. O projeto utilizará um serviço chamado MapQuest, que recebe a cidade e o município/estado do usuário e os converte nas coordenadas de latitude e longitude necessárias.

Crie um Account em: MapQuest.

Abrir .env.local e adicione as seguintes novas entradas (substituindo <api_key> pela sua chave da API do MapQuest):

MAP_QUEST_API_KEY=<api_key>
MAP_QUEST_API_URL=https://www.mapquestapi.com/geocoding/v1/

A User entidade precisa de duas propriedades para armazenar a latitude e a longitude. Para atualizar essa entidade, execute o comando do Symfony make conforme mostrado abaixo e siga as instruções:

docker-compose exec php bin/console make:entity
  • Nome da classe da entidade a ser criada ou atualizada (por exemplo, DeliciousGnome):

    • User

  • Imóvel 1

    • Nome: latitude

    • Tipo: decimal

    • Precisão: 20

    • Escala: 16

    • Pode ser nulo?: yes

  • Imóvel 2

    • Nome: longtitude

    • Tipo: decimal

    • Precisão: 20

    • Escala: 16

    • Pode ser nulo?: yes

Ao executar o comando abaixo, será gerado um novo arquivo de migração com essas alterações no banco de dados:

docker-compose exec php bin/console make:migration

Se você quiser ver as próximas alterações no banco de dados, os arquivos de migração gerados são salvos em project/src/Migrations/.

Desde que você esteja satisfeito com essas alterações, para salvá-las no banco de dados, execute o comando abaixo:

docker-compose exec php bin/console doctrine:migrations:migrate

O Guzzle é um cliente HTTP em PHP que permite que projetos enviem solicitações a outros serviços da web, como solicitações POST e GET a APIs. O Guzzle facilita o envio de uma solicitação GET ao endpoint da API do Map Quest para obter a latitude e a longitude da localização do usuário.

O próximo passo é criar um MapQuestUtil para fazer essa solicitação GET e processar a resposta de modo a recuperar apenas os campos necessários. Crie um novo arquivo em project/src/Util/ chamado MapQuestUtil.php e copie o código a seguir para o arquivo recém-criado:

<?php

namespace App\Util;

use App\Entity\User;
use App\Util\MapQuestUtil;
use GuzzleHttp\Client as GuzzleClient;

class MapQuestUtil
{
    public function getLatLongByAddress(User $user): ?array
    {
        $client = new GuzzleClient(
          ['base_uri' => $_ENV['MAP_QUEST_API_URL']]
        );

        $response = $client->request(
            'GET',
            'address', [
                'query' => [
                    'key' => $_ENV['MAP_QUEST_API_KEY'],
                    'inFormat' => 'kvp',
                    'outFormat' => 'json',
                    'location' => $user->getTown() . ',' . $user->getCounty(),
                    'thumbMaps' => 'false'
                ]
            ]
        );

        if ($response->getStatusCode() !== 200) {
            return null;
        }

        $body = json_decode($response->getBody()->getContents(), true);

        if (!is_array($body) || empty($body)) {
            return null;
        }

        if (!array_key_exists('results', $body) || empty($body['results'])) {
            return null;
        }

        return $body['results'][0]['locations'][0]['latLng'];
    }
}

O código acima é um novo método que recebe um User objeto e faz uma solicitação à API do MapQuest, fornecendo aos usuários location qual é a Town e sua County. Em seguida, há várias etapas após essa solicitação para garantir que uma localização seja retornada e que os campos esperados para a latitude e a longitude sejam incluídos na resposta.

É hora de encerrar esta MapQuestUtil classe e getLatLongByAddress() método. Abra o RegisterController.

A nova classe Utility precisa ser injetada como um serviço. No __construct() método, atualize-o conforme mostrado abaixo:

use App\Form\VerifyType;
+use App\Util\MapQuestUtil;

class RegisterController extends AbstractController
{
    /** @var VonageVerifyUtil */
    protected $vonageVerifyUtil;

+    /** @var MapQuestUtil */
+    protected $mapQuestUtil;

-    public function __construct(VonageVerifyUtil $vonageVerifyUtil)
-    {
+    public function __construct(
+        VonageVerifyUtil $vonageVerifyUtil,
+        MapQuestUtil $mapQuestUtil
+    ) {
        $this->vonageVerifyUtil = $vonageVerifyUtil;
+        $this->mapQuestUtil = $mapQuestUtil;
    }

Agora que a MapQuestUtil foi injetado, você precisa chamar a função e, em seguida, armazenar os detalhes retornados no User. Na index() função, localize a linha if ($form->isSubmitted() && $form->isValid()) { e adicione uma nova linha abaixo dela. Adicione o seguinte:

if ($form->isSubmitted() && $form->isValid()) {
+    $latLng = $this->mapQuestUtil->getLatLongByAddress($user);
+
+    if (null !== $latLng) {
+        $user
+          ->setLatitude($latLng['lat'])
+          ->setLongitude($latLng['lng']);
+    }

Nesta etapa do tutorial, você criou um novo projeto Symfony e o configurou com seu banco de dados. Você também criou uma nova User entidade, que se mapeia à sua user tabela do banco de dados. Você criou dois novos formulários, um UserType e VerifyType, que são usados para validar seus novos usuários e verificar se o código de verificação inserido está correto. Você também criou várias páginas para o usuário acessar, uma /register, /verify, e /success.

Você está agora em um ponto de pausa natural, na metade deste tutorial! Agora você pode testar o processo de cadastro do seu projeto.

Acesse página de cadastro e siga as instruções nas próximas duas páginas. Insira um número de telefone válido. Caso contrário, você não conseguirá verificar sua Account.

Fazer amizade

Corresponder usuários

Criar entidade de correspondência

É necessária uma nova entidade para conectar dois usuários: coletar seus comentários e armazenar registros dos usuários que se conectam. Essa nova entidade armazenará uma relação com os dois participantes no objeto correspondente e o nome da teleconferência (os nomes dos dois usuários concatenados por “-”). Para fazer isso, utilize a make biblioteca conforme mostrado abaixo e siga as instruções para as três novas propriedades:

docker-compose exec php bin/console make:entity
  • Nome: Match

  • 1 - callerOne - Muitos para um - Usuário - não nulo - padrões restantes

  • 2 - callerTwo - Muitos para um - Usuário - não nulo - valores padrão para o restante

  • 3 - conferenceName - string - 255 - não pode ser nulo

Antes de realizar a migração, é necessário realizar outra ação. Existe um pacote de extensões que Doctrine que permite que o código utilize extensões do Doctrine, como Timestampable, para usar duas linhas em nossas entidades para ter createdAt e updatedAt campos. Para fazer isso, instale stof/doctrine-extensions-bundle:

docker-compose exec php composer require stof/doctrine-extensions-bundle

A configuração é necessária para este novo pacote de extensões. Abra stof_doctrine_extensions.yaml em: project/config/packages/ e atualize-o para:

stof_doctrine_extensions:
    default_locale: en_GB
    orm:
        default:
            timestampable: false

Você precisa fazer mais algumas alterações na entidade recém-criada Match . Abra essa classe, que você encontra em project/src/Entity/Match.php.

Primeiro, inclua o TimestampableEntity no projeto, adicionando a seguinte importação:

use Doctrine\ORM\Mapping as ORM;
+use Gedmo\Timestampable\Traits\TimestampableEntity;

Agora use o TimestampableTrait na Match classe, conforme mostrado abaixo:

class Match
 {
+     use TimestampableEntity;

Ao executar o comando abaixo, será gerado um novo arquivo de migração com essas alterações no banco de dados:

docker-compose exec php bin/console make:migration

Se você quiser ver as próximas alterações no banco de dados, os arquivos de migração gerados são salvos em project/src/Migrations/.

Desde que você esteja satisfeito com essas alterações, para salvá-las no banco de dados, execute o comando abaixo:

docker-compose exec php bin/console doctrine:migrations:migrate

Configurar o Ngrok

O Ngrok é uma aplicação multiplataforma que permite aos usuários expor seu servidor de desenvolvimento à Internet sem precisar configurar o roteador. O Ngrok faz isso criando um túnel entre o seu servidor e o subdomínio do Ngrok.

É hora de configurar o Ngrok para permitir que o aplicativo da Vonage (que será criado na próxima etapa) tenha um subdomínio do Ngrok para realizar suas solicitações de webhook.

No diretório docker/ngrok/ renomeie o arquivo ngrok.conf.local para ngrok.confe substitua <auth_token> pelo seu token de autenticação do Ngrok:

Observação Você pode encontrar o seu auth token no seu painel do Ngrok

authtoken: <auth_token>

Agora, para atualizar seus contêineres do Docker com a URL correta do subdomínio do Ngrok, é preciso reiniciar o Docker. Você pode reiniciar o Docker com o seguinte comando:

docker-compose restart

Depois de reiniciar o Docker, acesse https://0.0.0.0:4040e copie o subdomínio do Ngrok exibido na página de status.

Ngrok Status Page

Se você pegar agora esse subdomínio do Ngrok e acessá-lo: ngroksubdomain/register, substituindo ngroksubdomain pelo seu subdomínio do Ngrok, você verá sua página de cadastro. No meu caso, o exemplo foi: https://a551f0297ed8.ngrok.io/register.

Anote este subdomínio do Ngrok, pois ele será necessário na próxima etapa para configurar seu aplicativo Vonage.

Criar uma aplicação da Vonage

Faça login no seu Painel da Vonage. À esquerda da página há um link Your Applications; clique nele. Assim que a página carregar, clique no Create a new Application botão.

Defina o Application Name; para o tutorial, defini o nome como Befriending Service, mas pode ser o que você quiser.

Clique no botão para Generate public and private key. Seu navegador iniciará o download do private.key arquivo. Salve-o no seu project/ diretório.

Em Capabilities, ative Voice.

Atualize os três campos de entrada que você vê agora com as URLs de webhook necessárias para que a Vonage faça chamadas de retorno durante as ligações. Por exemplo, no campo de texto “eventos”: https://a551f0297ed8.ngrok.io/webhooks/events, lembre-se de substituir a551f0297ed8 pelo seu subdomínio criado na etapa acima. Certifique-se de fazer isso nos três campos de entrada:

  • Evento - https://<ngrok url>/webhooks/event

  • Resposta - https://<ngrok url>/webhooks/answer

  • Solução alternativa - https://<ngrok url>/webhooks/fallback

Por fim, clique em Generate new application.

A página que será carregada agora exibirá o seu Application ID. Anote isso!

No seu project/ diretório, abra .env.local e adicione as duas linhas a seguir, certificando-se de substituir <application id> pelo ID do seu aplicativo e substitua <ngrok_url> pela sua URL do Ngrok:

VONAGE_APPLICATION_ID=<application id>
VONAGE_APPLICATION_PRIVATE_KEY_PATH=/var/www/symfony/private.key
VONAGE_NUMBER=<your virtual Vonage number>

NGROK_URL=<ngrok_url>

Você já criou uma aplicação da Vonage e armazenou as credenciais no seu projeto. O armazenamento dessas credenciais permite que o projeto agora faça e receba chamadas telefônicas.

Utilizador de Chamadas da Vonage

Crie um novo arquivo em project/src/Util/ chamado VonageCallUtil.php.

<?php

namespace App\Util;

use App\Util\VonageVerifyUtil;
use Nexmo\Client as VonageClient;
use Nexmo\Client\Credentials\Keypair;

class VonageCallUtil
{
    /** @var VonageClient */
    protected $client;

    /** @var VonageVerifyUtil */
    protected $vonageVerifyUtil;

    public function __construct(VonageVerifyUtil $vonageVerifyUtil)
    {
        $keypair = new Keypair(
            file_get_contents($_ENV['VONAGE_APPLICATION_PRIVATE_KEY_PATH']),
            $_ENV['VONAGE_APPLICATION_ID']
        );

        $this->client = new VonageClient($keypair);
        $this->vonageVerifyUtil = $vonageVerifyUtil;
    }
}

Atualmente, essa classe Utility não faz nada além de injetar dois novos serviços no momento da criação. Esses serviços são o VonageClient, que é a parte que utiliza a Voice API da Vonage para fazer e receber chamadas. O outro serviço injetado é o VonageVerifyUtil, que é chamado quando o projeto precisa de métodos específicos desta classe Util.

A classe Utility é utilizada para iniciar a chamada entre dois usuários. Portanto, o primeiro método necessário será createConferenceBetween()

public function createConferenceBetween(User $callerOne, User $callerTwo): Match
{
    $conferenceName = $callerOne->getName() . '_' . $callerTwo->getName();

    // Save conference name to the database, along with the callerOne and callerTwo.
    $match = (new Match())
        ->setCallerOne($callerOne)
        ->setCallerTwo($callerTwo)
        ->setConferenceName($conferenceName)
        ->setCreatedAt((new \DateTime()))
        ->setUpdatedAt((new \DateTime()));

    $ncco = [
        [
            'action' => 'talk',
            'voiceName' => 'Amy',
            'text' => 'You are on a Befriending service call, I will connect you to someone random from my database that shouldn\'t be far from your town. Please enjoy your call. If you would like to join now, enter 1 for yes, or 2 for no.'
        ],
        [
            'action' => 'input',
            'maxDigits' => 1,
            'eventUrl' => [
                $_ENV['NGROK_URL'] . '/webhooks/joinConference'
            ],
            'timeOut' => 10
        ]
    ];

    $this->makeCall($callerOne, $ncco);
    $this->makeCall($callerTwo, $ncco);

    return $match;
}

O código acima é um método que recebe dois usuários como argumentos. Em seguida, o método gera um novo objeto da Match entidade como uma conexão entre os dois usuários. É criada uma matriz de Objetos de Controle de Chamada da Nexmo (NCCO), que é um conjunto de instruções para que o Vonage Voice saiba o que esperar e as etapas necessárias para realizar a chamada.

Essa matriz contém mais dois elementos. O primeiro serve para utilizar a persona Amy para apresentar o usuário à chamada e explicar o motivo da ligação. Amy pergunta ao usuário se ele gostaria de participar de uma teleconferência ou não, solicitando que digite 1 para sim ou 2 para não. O segundo array serve para direcionar o usuário ao próximo endpoint do webhook, que é para participar da teleconferência.

Você deve ter notado que há duas chamadas a um makeCall() método, que você ainda não criou. Crie esse novo método copiando o código a seguir para o seu VonageCallUtil:

public function makeCall(User $caller, array $ncco)
{
    try {
        $number = $this->vonageVerifyUtil->getInternationalizedNumber($caller);

        $call = $this->client->calls()->create([
            'to' => [[
                'type' => 'phone',
                'number' => preg_replace('/\s+/', '', $number)
            ]],
            'from' => [
                'type' => 'phone',
                'number' => $_ENV['VONAGE_NUMBER']
            ],
            'ncco' => $ncco
        ]);
    } catch (\Exception $e) {

    }
}

Esse método primeiro chama outro método, getInternationalizedNumber(), localizado dentro do vonageVerifyUtil. Ele recebe o $caller que é uma instância de User e converte o código do país e o número de telefone em um Internationalized para que a Vonage possa ligar.

O próximo passo no método acima é inicializar a chamada definindo um to, from e inserindo o ncco objeto.

Por fim, neste VonageCallUtil é preciso incluir várias classes, pois elas são utilizadas nos exemplos acima. Copie as adições mostradas no exemplo abaixo para o seu arquivo:

namespace App\Util;

+use App\Entity\Match;
+use App\Entity\User;
use App\Util\VonageVerifyUtil;
use Nexmo\Client as VonageClient;
use Nexmo\Client\Credentials\Keypair;

Resumindo, a funcionalidade acima ainda não é chamada, mas cria um novo objeto `Match`, salva dois usuários nele e, em seguida, realiza duas chamadas para cada usuário. Ainda não é estabelecida nenhuma conferência, nem existe uma maneira de conectar as duas chamadas. A chamada em conferência será estabelecida a seguir!

Correspondência de usuários

Como o projeto prevê chamadas diárias, é necessário um comando que será executado como uma tarefa cron. No Terminal, dentro do docker/ diretório, use a make biblioteca para criar um novo comando:

docker-compose exec php bin/console make:command

Quando a biblioteca solicitar um nome para o seu novo comando, digite app:match-users.

Ao executar este comando, será criado um novo arquivo chamado MatchUsersCommand.php dentro: project/src/Command/MatchUsersCommand.php.

Abra este arquivo recém-criado MatchUsersCommand.php .

O comando que você gerou possui algumas configurações padrão, a maioria das quais você não precisará para este projeto. Encontre protected function configure() e substitua o conteúdo dessa função por:

$this
    ->setDescription('Match users together for a phone call')
;

É necessário injetar dois serviços na classe por meio do __construct() método. Os dois serviços necessários são o EntityManagerInterface, que permite que o código faça consultas ao banco de dados ou tenha acesso aos métodos do repositório para suas entidades. O segundo serviço necessário é o VonageCallUtil para chamar nossa funcionalidade criada anteriormente, que chama dois usuários e os conecta.

Atualize o código a seguir dentro do MatchUsersCommand.php arquivo para que fique conforme mostrado abaixo:

+use App\Entity\Match;
+use App\Entity\User;
+use App\Util\VonageCallUtil;
+use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MatchUsersCommand extends Command
{
    protected static $defaultName = 'app:match-users';

+    /** @var VonageCallUtil */
+    protected $vonageCallUtil;
+
+    /** @var EntityManagerInterface */
+    protected $entityManager;
+
+    public function __construct(
+        VonageCallUtil $vonageCallUtil,
+        EntityManagerInterface $entityManager
+    ) {
+        $this->vonageCallUtil = $vonageCallUtil;
+        $this->entityManager = $entityManager;
+
+        parent::__construct();
+    }

Agora é hora de começar a desenvolver a funcionalidade de comandos!

Para que a consulta funcione, o projeto precisa de algumas extensões do Doctrine que você irá escrever na próxima etapa. Essas extensões vêm de uma biblioteca de terceiros criada por Benjamin Eberlei. Para instalar essa biblioteca, execute o seguinte comando no Terminal:

docker-compose exec php composer require beberlei/doctrineextensions

Agora, para habilitar as extensões necessárias, abra project/config/packages/doctrine.yamle, mantendo o mesmo recuo de auto_generate_proxy_classes, naming_strategy, auto_mapping, e mappings adicione:

Observação: Preste atenção ao recuo para garantir que o formato YAML esteja correto.

dql:
    numeric_functions:
        acos: DoctrineExtensions\Query\Mysql\Acos
        cos: DoctrineExtensions\Query\Mysql\Cos
        radians: DoctrineExtensions\Query\Mysql\Radians
        sin: DoctrineExtensions\Query\Mysql\Sin

De volta ao interior do seu MatchUsersCommand , encontre a execute() função. Essa função é o local onde todo o trabalho do comando é realizado.

Para começar, apague todo o conteúdo desse método.

O primeiro passo necessário nesse método é ter uma instância do Match e User Repositories em variáveis e obter todos os usuários marcados como ativos:

$matchRepository = $this->entityManager->getRepository(Match::class);
$userRepository = $this->entityManager->getRepository(User::class);
$activeUsers = $userRepository->findByActive(true);

Em seguida, é hora de percorrer cada usuário ativo, para iniciar chamadas e conectá-los a outro usuário. Adicione o seguinte loop:

foreach ($activeUsers as $key => $callerOne) {

}

Agora, o sistema precisa encontrar usuários geograficamente próximos ao atual $callerOne para conectá-los. A consulta é bastante longa e deve ser colocada dentro do UserRepository. Abra o arquivo project/src/Repository/UserRepository.php

Abaixo do __construct método, adicione o seguinte novo método:

public function findPossibleMatchesByDistance(User $user, array $activeUsers, int $distance)
{
    $queryBuilder = $this->createQueryBuilder('u');

    return $this->createQueryBuilder('u')
        ->addSelect(
            '( 3959 * acos(cos(radians(:latitude))' .
                '* cos(radians(u.latitude))' .
                '* cos(radians(u.longitude)' .
                '- radians(:longitude))' .
                '+ sin(radians(:latitude))' .
                '* sin(radians(u.latitude)))) as distance'
        )
        ->andWhere($queryBuilder->expr()->eq('u.active', ':isActive'))
        ->andWhere($queryBuilder->expr()->eq('u.verified', ':isVerified'))
        ->andWhere($queryBuilder->expr()->neq('u', ':user'))
        ->having($queryBuilder->expr()->lt('distance', ':distance'))
        ->setParameters([
            'latitude' => $user->getLatitude(),
            'longitude' => $user->getLongitude(),
            'user' => $user,
            'distance' => $distance,
            'isActive' => true,
            'isVerified' => true
        ])
        ->getQuery()
        ->getResult();
}

O método no bloco de código acima recebe um objeto de usuário, uma matriz com os usuários ativos no momento e uma variável inteira que representa uma distância predeterminada.

Este método cria uma nova consulta que calcula a distância de todos os usuários em relação ao usuário especificado e garante que todos estejam ativos e Verifyados. A última verificação é para garantir que os usuários retornados estejam dentro da distância especificada em relação ao usuário passado como parâmetro.

De volta ao interior do MatchUsersCommand , localize a linha: foreach ($activeUsers as $activeUser) { e adicione o seguinte:

// Retrieve all active users within a 30 mile radius of current callerOne
$matches = $userRepository->findPossibleMatchesByDistance($callerOne, $activeUsers, 30);

// If there are less than 5 users returned, increase the search to 100 mile radius.
if (count($matches) < 5) {
    $matches = $userRepository->findPossibleMatchesByDistance($callerOne, $activeUsers, 100);
}

// If there are no users within a 100 mile radius return 0
if (count($matches) === 0) {
    unset($activeUsers[$key]);

    continue;
}

// Shuffle returned matches.
shuffle($matches);
// Remove callerOne from list of active users
unset($activeUsers[$key]);

$callerTwo = $matches[0][0];

// Remove callerTwo from list of active users
$matchKey = array_search($callerTwo, $activeUsers);
unset($activeUsers[$matchKey]);

// Make a call to createConferenceBetween() inside VonageCallUtil to connect the two users via phone call.
$match = $this->vonageCallUtil->createConferenceBetween($callerOne, $callerTwo);

// If successful, save the Match to the database.
if ($match instanceof Match) {
    $this->entityManager->persist($match);
    $this->entityManager->flush();
}

O comando é criado para identificar usuários compatíveis e iniciar a chamada. No entanto, são necessárias URLs de webhook para que a chamada avance da apresentação automatizada até a conexão dos usuários em uma teleconferência.

Webhooks

Agora você precisa de um WebhooksController, então, no Terminal, use a make biblioteca para executar o seguinte comando:

docker-compose exec php bin/console make:controller

Onde está escrito: Choose a name for your controller class (e.g. AgreeableGnomeController): digite WebhooksController e pressione Enter.

Você acabou de criar dois novos arquivos:

created: src/Controller/WebhooksController.php
created: templates/webhooks/index.html.twig

Abra o arquivo recém-criado WebhooksController.php que se encontra em project/src/Controller/

Este controlador precisará utilizar três serviços, VonageVerifyUtil, VonageCallUtil, e EntityManagerInterface. Portanto, comece injetando-os na construção do controlador, conforme mostrado abaixo:

+
+ use App\Util\VonageVerifyUtil;
+ use App\Util\VonageCallUtil;
+ use Doctrine\ORM\EntityManagerInterface;

class WebhooksController extends AbstractController
{
+    /** @var VonageVerifyUtil */
+    protected $vonageVerifyUtil;
+
+    /** @var VonageCallUtil */
+    protected $vonageCallUtil;
+
+    /** @var EntityManagerInterface */
+    protected $entityManager;
+
+    public function __construct(
+        VonageCallUtil $vonageCallUtil,
+        VonageVerifyUtil $vonageVerifyUtil,
+        EntityManagerInterface $entityManager
+    ) {
+        $this->vonageCallUtil = $vonageCallUtil;
+        $this->vonageVerifyUtil = $vonageVerifyUtil;
+        $this->entityManager = $entityManager;
+    }
}

Os dois métodos abaixo fazem exatamente o que seus nomes indicam. O primeiro serve para localizar um usuário pelo número de telefone, enquanto o segundo localiza um Match instância por meio de uma User que você criou hoje. Adicione-os ao seu WebhooksController.

private function findUserByNumber(string $phoneNumber)
{
    return $this->entityManager->getRepository(User::class)->findOneByPhoneNumber(
        $this->vonageVerifyUtil->getNationalizedNumber('+' . $phoneNumber)
    );
}

private function findMatchByUser(User $user)
{
    return $this->entityManager
        ->getRepository(Match::class)
        ->findByDateUser($user, (new \DateTime()));
}

Um método chamado no exemplo acima ainda não existe; ele serve para encontrar correspondências com base no usuário e na data atual. Abra project/src/Repository/MatchRepository.php e, dentro dessa classe, adicione o método mostrado abaixo:

public function findByDateUser(User $user, DateTime $date)
{
    $queryBuilder = $this->createQueryBuilder('m');

    return $this->createQueryBuilder('m')
        ->andWhere(
            $queryBuilder->expr()->orX(
                $queryBuilder->expr()->eq('m.callerOne', ':user'),
                $queryBuilder->expr()->eq('m.callerTwo', ':user')
            )
        )
        ->andWhere(
            $queryBuilder->expr()->like('Date(m.createdAt)', ':date')
        )
        ->setParameters([
            'user' => $user,
            'date' => $date->format('Y-m-d') . '%'
        ])
        ->getQuery()
        ->getResult();
}

No início do arquivo, adicione a importação da entidade User:

use App\Entity\Match;
+use App\Entity\User;

A consulta acima localiza um Match entrada em que o usuário seja callerOne ou callerTwo e a Match foi criada hoje. Há uma função de consulta aqui que o Doctrine não reconhece. Date(m.createdAt). Ao calcular a distância entre as pessoas, você instalou beberlei/doctrineextensions. Essa biblioteca possui funcionalidade para incluir a Date extensão. Portanto, abra project/config/packages/doctrine.yaml e adicione as duas linhas a seguir:

dql:
    numeric_functions:
        acos: DoctrineExtensions\Query\Mysql\Acos
        cos: DoctrineExtensions\Query\Mysql\Cos
        radians: DoctrineExtensions\Query\Mysql\Radians
        sin: DoctrineExtensions\Query\Mysql\Sin
+    datetime_functions:
+        Date: DoctrineExtensions\Query\Mysql\Date

Anteriormente, você criou um método chamado createConferenceBetween(); dentro desse método havia uma NCCO array que fazia referência a uma URL de /webhooks/joinConference. Se você executasse o exemplo agora, nada aconteceria, pois a URL não existe. Crie um novo joinConference() método dentro do seu WebhooksController com a anotação contendo essa URL.

O método terá dois objetivos. O primeiro é validar as entradas do usuário, garantindo que ele insira apenas opções válidas antes de prosseguir. Essas entradas são 1 para “sim” e 2 para “não”.

O segundo objetivo desse método é respeitar a escolha do usuário. Se ele optar por prosseguir com a chamada, ele será redirecionado para a próxima ação, que o conectará à teleconferência. Se o usuário selecionar o “2” para não participar de uma chamada com alguém, o sistema responde confirmando sua solicitação de não participar e encerra a chamada.

/**
 * @Route("/webhooks/joinConference", name="match_join")
 */
public function joinConference(Request $request)
{
    $content = json_decode($request->getContent(), true);

    if (!in_array($content['dtmf'], ['1', '2'])) {
        $ncco = [
            [
                'action' => 'talk',
                'text' => 'Please only enter 1 or 2. Would you like to join a call with someone?',
                'voiceName' => 'Amy',
            ],
            [
                'action' => 'input',
                'maxDigits' => 1,
                'eventUrl' => [
                    $_ENV['NGROK_URL'] . '/webhooks/joinConference'
                ],
                'timeOut' => 10
            ]
        ];
    } elseif ($content['dtmf'] === '1') {
        // Find user by number.
        $user = $this->findUserByNumber($content['to']);
        // Find match by user and today.
        $match = $this->findMatchByUser($user)[0];

        // Make next request.
        $ncco = [
            [
                'action' => 'talk',
                'text' => 'Thank you. I will now connect you.',
                'voiceName' => 'Amy',
            ],
            [
                'action' => 'conversation',
                'name' => $match->getConferenceName()
            ]
        ];
    } else {
        $ncco = [
            [
                'action' => 'talk',
                'text' => 'Ok, we will not put you in a call with someone at this time. Goodbye.',
                'voiceName' => 'Amy',
            ]
        ];
    }

    return new JsonResponse($ncco);
}

Aqui são utilizadas quatro classes que não incluímos em nosso arquivo, Request, Match, User, e JsonResponse. Inclua-as acima da classe:

+use App\Entity\Match;
+use App\Entity\User;
use App\Util\VonageCallUtil;
use App\Util\VonageVerifyUtil;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
use Doctrine\ORM\EntityManagerInterface;

Neste momento, você já dispõe de um formulário de cadastro de usuário, que conduz o usuário pelo processo de verificação. Você também possui um comando que, quando executado, percorre todos os usuários ativos e verificados, conectando-os a outros usuários ativos e verificados em um raio máximo de 100 milhas uns dos outros.

Agora você pode testar isso! Se tiver dois números de telefone disponíveis, vá em frente e cadastre-se na sua página de cadastro. Certifique-se de seguir o processo de verificação e verifique se os locais que você inseriu para ambos os usuários estão a menos de 30 milhas um do outro.

Depois de registrar os usuários, no Terminal, dentro do docker/ diretório, execute o seguinte comando:

docker-compose exec php bin/console app:match-users

Ambos os números de telefone receberão uma ligação e serão questionados se gostariam de participar de uma chamada com outra pessoa!

Receba comentários

É necessário um segundo comando para coletar o feedback de cada usuário. Esse comando coletará o feedback das chamadas do dia atual de cada usuário. Esse comando também será uma tarefa cron programada para ser executada várias horas após o término da chamada correspondente.

Mas, antes de criar esse novo comando, a Match entidade precisa ser atualizada, assim como quando você criou a Match entidade com a make biblioteca, você também pode atualizá-la com novas propriedades. Execute o comando a seguir no Terminal e siga as instruções de cada etapa abaixo:

docker-compose exec php bin/console make:entity
  • Nome da classe da entidade a ser criada ou atualizada (por exemplo, DeliciousGnome):

    • Match

  • Imóvel 1

    • Nome: callerOneFeedbackAccepted

    • Tipo: boolean

    • Pode ser nulo?: yes

  • Imóvel 2

    • Nome: callerTwoFeedbackAccepted

    • Tipo: boolean

    • Pode ser nulo?: yes

  • Imóvel 3

    • Nome: callerOneCallSuccessful

    • Tipo: boolean

    • Pode ser nulo?: yes

  • Imóvel 4

    • Nome: callerTwoCallSuccessful

    • Tipo: boolean

    • Pode ser nulo?: yes

Atualmente, a entidade atualizada não reflete o que está no banco de dados. Para fazer com que o banco de dados reflita o que você definiu na Match entidade, execute o comando abaixo para gerar um novo arquivo de migração:

docker-compose exec php bin/console make:migration

Se você quiser ver as próximas alterações no banco de dados, os arquivos de migração gerados são salvos em project/src/Migrations/.

Desde que você esteja satisfeito com essas alterações, para salvá-las no banco de dados, execute o comando abaixo:

docker-compose exec php bin/console doctrine:migrations:migrate

Primeiro, você precisa obter uma lista dos pares de hoje. Portanto, no seu MatchRepository , adicione uma nova função que consulte o banco de dados para encontrar todas as correspondências do dia atual. Essa consulta encontrará apenas usuários compatíveis cujo callerOneCallSuccessful sinalizador estiver vazio:

public function getTodaysMatches()
{
    $queryBuilder = $this->createQueryBuilder('m');

    return $this->createQueryBuilder('m')
        ->andWhere(
            $queryBuilder->expr()->isNull('m.callerOneCallSuccessful'),
            $queryBuilder->expr()->like('Date(m.createdAt)', ':date')
        )
        ->setParameter('date', (new \DateTime())->format('Y-m-d') . '%')
        ->getQuery()
        ->getResult();
}

Agora é hora de criar o novo comando que aproveita as novas alterações. No terminal, digite:

docker-compose exec php bin/console make:command

Quando for solicitado o nome de um comando, digite: app:get-feedback. Esse comando irá gerar um novo arquivo de comando dentro de project/src/command/ chamado GetFeedbackCommand.php Abra o arquivo recém-criado.

Esse controlador precisa que dois serviços sejam injetados nele por meio do __construct() método. Os mesmos dois serviços injetados no MatchUsersCommand. Copie os ajustes mostrados abaixo para o seu GetFeedbackCommand:

+use App\Entity\Match;
+use App\Util\VonageCallUtil;
+use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class GetFeedbackCommand extends Command
{
    protected static $defaultName = 'app:get-feedback';

+    /** @var VonageCallUtil */
+    protected $vonageCallUtil;
+
+    /** @var EntityManagerInterface */
+    protected $entityManager;
+
+    public function __construct(
+        VonageCallUtil $vonageCallUtil,
+        EntityManagerInterface $entityManager
+    ) {
+        $this->vonageCallUtil = $vonageCallUtil;
+        $this->entityManager = $entityManager;
+
+        parent::__construct();
+    }

Ao realizar as alterações acima, seu comando agora tem acesso ao VonageCallUtil e EntityManagerInterface.

Atualize o conteúdo de configure() com o que é mostrado no exemplo abaixo. Este exemplo define a descrição do comando:

$this
    ->setDescription('Contact all previous matches to get their feedback.');

É hora de implementar a funcionalidade para iniciar a coleta de feedback dos usuários emparelhados. Substitua o conteúdo do método execute() por:

$matchRepository = $this->entityManager->getRepository(Match::class);

// Get matches that haven't had any feedback.
$matches = $matchRepository->getTodaysMatches();

if (empty($matches)) {
    return 0;
}

// Loop through all retrieved matches
foreach ($matches as $match) {
    if (null === $match->getCallerOneCallSuccessful()) {
        // Call callerOne as we do not have feedback from them.
        $this->vonageCallUtil->makeFeedbackCall(
            $match->getCallerOne()
        );
    }

    if (null === $match->getCallerTwoCallSuccessful()) {
        // Call callerTwo as we do not have feedback from them.
        $this->vonageCallUtil->makeFeedbackCall(
            $match->getCallerTwo()
        );
    }

    // Save changes to the database.
    $this->entityManager->flush();
    $this->entityManager->clear();
}

return 0;

A funcionalidade acima reúne as partidas do dia; em seguida, percorre cada partida, iniciando uma ligação com o interlocutor 1 e o interlocutor 2 para solicitar o feedback de cada um sobre a ligação anterior.

Como você pode ver, você fez uma chamada para makeFeedbackCall() a partir do VonageCallUtil, mas o método ainda não existe. Então, abra o VonageCallUtil dentro project/src/util/VonageCallUtil.php e adicione um novo método:

public function makeFeedbackCall(User $user)
{
    $ncco = [
        [
            'action' => 'talk',
            'text' => 'Thank you for using the Befriending service. Could you please provide feedback for your call today? Enter 1 for yes, or two for no.',
            'voiceName' => 'Amy',
        ],
        [
            'action' => 'input',
            'maxDigits' => 1,
            'eventUrl' => [
                $_ENV['NGROK_URL'] . '/webhooks/userFeedback'
            ],
            'timeOut' => 10
        ]
    ];

    $this->makeCall($user, $ncco);
}

O exemplo acima faz referência a um eventUrl que é a sua URL do Ngrok e /webhooks/userFeedback. Esse endpoint ainda não existe em nosso WebhooksController ainda.

O método terá dois objetivos. O primeiro é validar as entradas do usuário, garantindo que ele insira apenas opções válidas antes de prosseguir. Essas entradas são 1 para “sim” e 2 para “não”.

O segundo objetivo desse método é respeitar a escolha do usuário. Se ele optar por prosseguir para dar feedback, redirecione-o para a próxima ação, que é fazer a primeira pergunta de feedback. Se o usuário selecionar “2”, não forneça feedback; o sistema responde confirmando o pedido de não fornecer feedback e encerra a chamada.

Então, no WebhooksController crie um novo método chamado getUserFeedback() conforme mostrado abaixo:

/**
 * @Route("/webhooks/userFeedback", name="match_feedback")
 */
public function getUserFeedback(Request $request)
{
    $content = json_decode($request->getContent(), true);

    if (!in_array($content['dtmf'], ['1', '2'])) {
        $ncco = [
            [
                'action' => 'talk',
                'text' => 'Please only enter 1 or 2. Would you like to provide feedback for the service?',
                'voiceName' => 'Amy',
            ],
            [
                'action' => 'input',
                'maxDigits' => 1,
                'eventUrl' => [
                    $request->getScheme().'://'.$request->getHost().'/webhooks/userFeedback'
                ],
                'timeOut' => 10
            ]
        ];
    } else {
        // Find user by number.
        $user = $this->findUserByNumber($content['to']);
        // Find match by user and today.
        $match = $this->findMatchByUser($user)[0];
        // Determine if user is first or second caller.
        $isFirstOrSecond = $this->isFirstOrSecondCaller($match, $user);

        // Save entry to database.
        if ($isFirstOrSecond === 1) {
            $method = 'setCallerOneFeedbackAccepted';
        } elseif ($isFirstOrSecond === 2) {
            $method = 'setCallerTwoFeedbackAccepted';
        } else {
            return new JsonResponse([]);
        }

        $mapResponse = [
            '1' => true,
            '2' => false
        ];

        $match->$method($mapResponse[$content['dtmf']]);
        $this->entityManager->flush();

        // Make next request.
        $ncco = [
            [
                'action' => 'talk',
                'text' => 'Thank you. Was the call successful? Please enter 1 for yes, or 2 for no.',
                'voiceName' => 'Amy',
            ],
            [
                'action' => 'input',
                'maxDigits' => 1,
                'eventUrl' => [
                    $_ENV['NGROK_URL'] . '/webhooks/userFeedbackCallSuccess'
                ],
                'timeOut' => 10
            ]
        ];
    }

    return new JsonResponse($ncco);
}

O exemplo acima faz referência a um eventUrl que é a sua URL do Ngrok e /webhooks/userFeedbackCallSuccess. Esse endpoint ainda não existe em nosso WebhooksController ainda.

O método terá dois objetivos. O primeiro é validar as entradas do usuário, garantindo que ele insira apenas opções válidas antes de prosseguir. Essas entradas são 1 para “sim” e 2 para “não”.

O segundo objetivo desse método é respeitar a entrada do usuário. Se ele escolher “1” ou “2”, salve a entrada do usuário na propriedade setCallerOneCallSuccessful ou na propriedade setCallerTwoCallSuccessful como verdadeiro ou falso (dependendo se ele gostou ou não da ligação).

Após a seleção bem-sucedida de uma opção, o código redireciona o usuário para a próxima ação da chamada, que é o endpoint /webhooks/userFeedbackContinue

Então, no WebhooksController crie um novo método chamado getUserFeedbackCallSuccess() conforme mostrado abaixo:

/**
 * @Route("/webhooks/userFeedbackCallSuccess", name="match_call_success")
 */
public function getUserFeedbackCallSuccess(Request $request)
{
    $content = json_decode($request->getContent(), true);

    if (!in_array($content['dtmf'], ["1", "2"])) {
        $ncco = [
            [
                'action' => 'talk',
                'text' => 'Please only enter 1 or 2. Was the call successful? Please enter 1 for yes or 2 for no.',
                'voiceName' => 'Amy',
            ],
            [
                'action' => 'input',
                'maxDigits' => 1,
                'eventUrl' => [
                    $request->getScheme().'://'.$request->getHost().'/webhooks/userFeedbackCallSuccess'
                ],
                'timeOut' => 10
            ]
        ];
    } else {
        // Find user by number.
        $user = $this->findUserByNumber($content['to']);
        // Find match by user and today.
        $match = $this->findMatchByUser($user)[0];
        // Determine if user is first or second caller.
        $isFirstOrSecond = $this->isFirstOrSecondCaller($match, $user);

        // Save entry to database.
        if ($isFirstOrSecond === 1) {
            $method = 'setCallerOneCallSuccessful';
        } elseif ($isFirstOrSecond === 2) {
            $method = 'setCallerTwoCallSuccessful';
        } else {
            return new JsonResponse([]);
        }

        $mapResponse = [
            '1' => true,
            '2' => false
        ];

        $match->$method($mapResponse[$content['dtmf']]);
        $this->entityManager->flush();

        $ncco = [
            [
                'action' => 'talk',
                'text' => 'Would you like to have another call tomorrow? Please enter 1 for yes or 2 for no.',
                'voiceName' => 'Amy',
            ],
            [
                'action' => 'input',
                'maxDigits' => 1,
                'eventUrl' => [
                    $_ENV['NGROK_URL'] . '/webhooks/userFeedbackContinue'
                ],
                'timeOut' => 10
            ]
        ];
    }

    return new JsonResponse($ncco);
}

O exemplo acima faz referência a um eventUrl que é a sua URL do Ngrok e webhooks/userFeedbackContinue. Esse endpoint ainda não existe em nosso WebhooksController ainda.

O método terá dois objetivos. O primeiro é validar as entradas do usuário, garantindo que ele insira apenas opções válidas antes de prosseguir. Essas entradas são 1 para “sim” e 2 para “não”.

O segundo objetivo desse método é respeitar a escolha do usuário. Se ele selecionar “1”, continuará inscrito no serviço para receber uma ligação no dia seguinte.

Se o usuário escolher o “2”, ele será marcado como inativo e não receberá mais chamadas.

A chamada é encerrada após a inserção bem-sucedida da opção.

Então, no WebhooksController crie um novo método chamado getUserFeedbackContinue() conforme mostrado abaixo:

/**
 * @Route("/webhooks/userFeedbackContinue", name="match_user_continue")
 */
public function getUserFeedbackContinue(Request $request)
{
    $content = json_decode($request->getContent(), true);

    if (!in_array($content['dtmf'], ["1", "2"])) {
        $ncco = [
            [
                'action' => 'talk',
                'text' => 'Please only enter 1 or 2. Would you like to have another call tomorrow? Please enter 1 for yes or 2 for no.',
                'voiceName' => 'Amy',
            ],
            [
                'action' => 'input',
                'maxDigits' => 1,
                'eventUrl' => [
                    $_ENV['NGROK_URL'] . '/webhooks/userFeedbackContinue'
                ],
                'timeOut' => 10
            ]
        ];
    } else {
        // Find user by number.
        $user = $this->findUserByNumber($content['to']);
        // Find match by user and today.
        $match = $this->findMatchByUser($user);

        if (!$match) {
            return new JsonResponse([]);
        }

        $mapResponse = [
            '1' => true,
            '2' => false
        ];

        if ($content['dtmf'] === "2") {
            $user->setActive(false);
            $this->entityManager->flush();

            $ncco = [
                [
                    'action' => 'talk',
                    'text' => 'Thank you for your feedback. Your number has been removed from the list.',
                    'voiceName' => 'Amy',
                ]
            ];
        } else {
            $ncco = [
                [
                    'action' => 'talk',
                    'text' => 'Thank you for your feedback. Goodbye.',
                    'voiceName' => 'Amy'
                ],
            ];
        }
    }

    return new JsonResponse($ncco);
}

A última adição necessária ao projeto é uma função que você talvez tenha notado que está sendo chamada isFirstOrSecondCaller() , mas que ainda precisa ser criada. Esse método recebe uma instância de Match e um único User objeto; em seguida, determina se o usuário fornecido é callerOne ou callerTwo do Match. Adicione isso ao final da sua classe:

private function isFirstOrSecondCaller(Match $match, User $user): ?int
{
    if ($match->getCallerOne() === $user) {
        return 1;
    }

    if ($match->getCallerTwo() === $user) {
        return 2;
    }

    return null;
}

Conclusão

Se você seguiu este tutorial do início ao fim, já criou um projeto a partir de uma instalação nova do Symfony.

Este novo projeto possui uma página de cadastro que, ao ser enviada, gera uma solicitação de verificação para o Vonage Verify. Essa solicitação aciona o envio de um SMS para os titulares do número de telefone cadastrado. O código de verificação contido na mensagem SMS deve ser inserido na próxima página exibida ao usuário. Após a verificação bem-sucedida, o usuário é redirecionado para uma página de confirmação, informando a que horas poderá receber as ligações.

A segunda parte deste projeto é o recurso Vonage Voice. Por meio de comandos, os usuários são emparelhados, ligados e incluídos em uma teleconferência. Mais tarde, naquele mesmo dia, um segundo comando é executado para solicitar feedback de cada pessoa emparelhada naquele dia.

O código final deste tutorial pode ser encontrado no branch “end-tutorial” neste repositório do GitHub.

A seguir, apresentamos alguns outros tutoriais que elaboramos sobre a implementação do Vonage Verify ou do Voice em projetos:

Não se esqueça: se tiver alguma dúvida, sugestão ou ideia que gostaria de compartilhar com a comunidade, sinta-se à vontade para participar do nosso espaço de trabalho do Slack da Comunidade. Adoraria receber feedback de quem já colocou este tutorial em prática e saber como está o seu projeto.

Compartilhar:

https://a.storyblok.com/f/270183/250x250/b052219541/greg-holmes.png
Greg HolmesEx-funcionários da Vonage

Ex-educador de desenvolvedores na @Vonage. Tenho formação em PHP, mas não me limito a uma única linguagem. Sou um jogador ávido e entusiasta do Raspberry Pi. Costumo praticar escalada em rocha em centros de escalada indoor.