https://d226lax1qjow5r.cloudfront.net/blog/blogposts/befriending-service-with-symfony-and-vonage/Blog_Befriending_Symfony_Voice-Verify_1200x600.png

Servicio de amistad con Symfony y Vonage

Publicado el April 19, 2021

Tiempo de lectura: 65 minutos

Con la actual situación mundial, la mayoría de los países se encuentran en algún tipo de bloqueo. El distanciamiento social es fundamental en estos momentos para reducir el impacto de Covid-19. Mientras que algunos de nosotros tenemos calendarios repletos de reuniones virtuales, otros no disponen de un grupo tan grande de personas a las que llamar o con las que conectar para pasar el tiempo. Mi abuela, por ejemplo, no es muy técnica, así que depende de las llamadas telefónicas. Aunque tiene la suerte de tener trece nietos, a menudo dice que algunos días no sabe nada de nadie y que le gustaría hablar con alguien a diario. En Vonage, tenemos oportunidades periódicas de crear algo para nuestro aprendizaje. En esta oportunidad, elegí crear un servicio de amistad, que presentaría a los usuarios que son vulnerables, se sienten solos o quieren una persona diferente con la que hablar a diario. La idea es que las personas puedan hacer nuevos amigos mientras están encerradas o en cualquier momento.

Requisitos previos

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.

Primeros pasos

A través de este tutorial, usted construirá un nuevo proyecto PHP usando Symfony. Utilizarás Vonage Verify, Vonage Voice y MapQuest API. Una vez completado, este proyecto ofrecerá un servicio que permitirá a los usuarios registrarse con su número de teléfono, nombre, ciudad y provincia/estado. Al registrarse, el usuario recibirá un código de verificación que deberá introducir en el sitio web. Una vez verificado, el usuario pasa a engrosar una lista de personas que cada día recibirán una llamada telefónica, conectadas a otro usuario con el que conversar. Por último, al final de cada día, cada usuario recibirá de nuevo una llamada automatizada en la que se le pedirá opinión sobre la llamada que haya tenido ese día.

Clonar el repositorio

El proyecto utiliza el módulo Docker contenedores Nginx, MySQL, PHPy Ngrok para cambios mínimos en la configuración del servidor.

Comience este tutorial clonando el repositorio existente. La clonación se puede hacer copiando el siguiente comando en su Terminal, y luego cambiando el directorio al directorio del proyecto:

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

Su Terminal mostrará dos directorios:

  • docker donde se encuentran sus configuraciones Docker (No es necesario cambiar nada, a menos que esté estableciendo diferentes credenciales de base de datos)

  • project que es donde su nuevo Symfony se almacena

Ejecutar Docker

En tu Terminal, cambia de directorio al directorio docker/ y ejecute el siguiente comando para crear e iniciar sus contenedores docker:

docker-compose up -d

El comando anterior descarga todos los contenedores preconfigurados Docker preconfigurados, añadiendo cualquier cambio personalizado definido en los archivos que se encuentran en el directorio docker/ directorio. A continuación, Build compila estos contenedores y los ejecuta como servicios para que puedas ejecutar tu aplicación web.

Una vez completado el comando, verá una confirmación de que los Docker se están ejecutando, como se muestra en el siguiente ejemplo:

Successfully Running Docker

Los contenedores Docker y los servidores ya están listos para comenzar el desarrollo.

La aplicación

Registro y verificación de usuarios

Instalar Symfony

Si miras en el directorio project verás que la estructura de archivos de una instalación vacía de Symfony es la siguiente:

Fresh Symfony Installation

Aún dentro del directorio docker/, ejecute el siguiente comando para instalar todas las bibliotecas PHH descritas en el archivo composer.json:

docker-compose exec php composer install

La configuración de la base de datos para este proyecto Symfony necesita ser almacenada en algún lugar. Así que en tu IDE, bajo el directorio project/ crea un nuevo archivo llamado .env.local. Una vez creado, añade la siguiente línea a este nuevo archivo:

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

A continuación se describe un desglose de la línea anterior:

  • Nombre de la base de datos: befriending

  • Usuario de la base de datos: user

  • Contraseña de la base de datos: password

  • Host y puerto de la base de datos: mysql:3306

Se trata de valores preestablecidos dentro del docker/docker-compose.yml archivo. El nombre de usuario y la contraseña actuales no son seguros, por motivos de seguridad, si desea cambiarlos, hágalo tanto en project/.env.local y docker/docker-compose.ymly luego ejecute:

docker-compose build

Crear una entidad de usuario

A User entidad, que es la clase que define correctamente la estructura de la user tabla de la base de datos. Esta tabla es la tabla donde se almacenan los registros de nuevos usuarios.

Para ello, Symfony ha lanzado una librería make que permite a los usuarios utilizar la interfaz de línea de comandos (CLI) para crear ciertas clases.

Ejecute el siguiente comando y siga las instrucciones para crear las propiedades de la entidad User entidad:

docker-compose exec php bin/console make:entity
  • Nombre de clase de la entidad a crear o actualizar (por ejemplo, DeliciousGnome):

    • User

  • Inmueble 1

    • Nombre: phoneNumber

    • Tipo: string

    • Longitud del campo: 255

    • ¿Puede ser nulo? no

  • Inmueble 2

    • Nombre: name

    • Tipo: string

    • Longitud del campo: 255

    • ¿Puede ser nulo? no

  • Inmueble 3

    • Nombre: town

    • Tipo: string

    • Longitud del campo: 255

    • ¿Puede ser nulo? no

  • Inmueble 4

    • Nombre: county

    • Tipo: string

    • Longitud del campo: 255

    • ¿Puede ser nulo? no

  • Inmueble 5

    • Nombre: countryCode

    • Tipo: string

    • Longitud del campo: 2

    • ¿Puede ser nulo? no

  • Inmueble 6

    • Nombre: verificationRequestId

    • Tipo: string

    • Longitud del campo: 255

    • ¿Puede ser nulo? yes

  • Inmueble 7

    • Nombre: verified

    • Tipo: boolean

    • ¿Puede ser nulo? no

  • Inmueble 8

    • Nombre: active

    • Tipo: boolean

    • ¿Puede ser nulo? no

Al finalizar este comando, se crean dos nuevos archivos:

  • project/src/Entity/User.php

  • project/src/Repository/UserRepository.php

La página UserRepository es una clase de repositorio donde los desarrolladores encontrarán consultas personalizadas para la User entidad. Ignore esta clase por ahora.

La entidad creada, no refleja actualmente lo que hay en la base de datos. Para que la base de datos refleje lo que ha definido en la User entidad, ejecute el siguiente comando para generar un nuevo archivo de migración:

docker-compose exec php bin/console make:migration

Si desea ver los próximos cambios en la base de datos, los archivos de migración generados se guardan en project/src/Migrations/.

Si estás satisfecho con estos cambios, ejecuta el siguiente comando para guardarlos en la base de datos:

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

La entidad User entidad aún necesita algunos cambios adicionales que no podrían realizarse con la maker CLI. Para hacer estos cambios abre esta clase, que puedes encontrar en project/src/Entity/User.php.

En primer lugar, una clase UniqueEntity se utiliza como una anotación para asegurar que una de las propiedades en su clase de Usuario es Única (por lo que sólo una entrada con ese valor). Añade esta clase en la parte superior:

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

Añade las dos líneas siguientes:

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

La primera alteración que has hecho es añadir una anotación hardcode para el nombre de la tabla. La razón es que la palabra user es una palabra clave en MySQL. Así que el sistema necesita el user nombre de la tabla entre comillas para asegurar que cuando se hace una consulta, este cambio evita que el código confunda el nombre de la tabla con la palabra clave.

El segundo cambio consiste en añadir la propiedad phoneNumber como UniqueEntity. El uso de UniqueEntity evitará que varios usuarios tengan el mismo número de teléfono.

Ahora, actualice la definición de la propiedad phoneNumber para que sea un campo único:

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

El último cambio en la User entidad es garantizar activey verified sean falsos por defecto, así que cree una función __construct() para ello:

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

Ahora ha creado una nueva entidad de Usuario, que es como el sistema crea nuevos usuarios, así como guardar y editar usuarios en la base de datos.

Instalar Webpack Encore

En este tutorial se utiliza Bootstrap para mejorar el diseño de las páginas. Symfony ha lanzado recientemente Webpack Encore, que es una forma mucho más sencilla de integrar Webpack en tu aplicación Symfony o PHP. Para instalar Webpack Encore, ejecuta los siguientes comandos:

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

Ejecutando el comando yarn install se configurará el proyecto para manejar también archivos Javascript. Este comando crea un directorio assets directorio, y dentro de este directorio, hay un archivo js/app.js y un archivo css/app.css archivo.

Ahora Bootstrap está instalado con el siguiente comando:

yarn add bootstrap --dev

La aplicación Symfony no sabe que Bootstrap existe todavía. Para incluirlo en el proyecto, abre el archivo project/assets/css/app.scss y añade la siguiente línea:

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

Bootstrap JS requiere jQuery y PopperJs, así que instálalos con el siguiente comando:

yarn add jquery popper.js --dev

De nuevo, la aplicación Symfony no sabe lo que son jQuery y PopperJs. Así que inclúyelos en el proyecto abriendo el archivo project/assets/js/app.js y añadiendo las siguientes actualizaciones:

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

+ require('bootstrap');

Estos archivos CSS y JS necesitan ser compilados para poder acceder a ellos en las plantillas. Para ello ejecute el siguiente comando:

yarn run dev

Las partes necesarias del frontend están instaladas y configuradas.

Instala Vonage SDK y libphonenumber-for-php

Este tutorial requiere dos bibliotecas PHP más:

  • el SDK de Vonage,

    • para enviar solicitudes de verificación,

    • Verificar usuarios

    • llamar por teléfono

    • manejar la entrada de llamadas telefónicas DTMF.

  • libphonenumber-for-php de Giggsey, que manipula números de teléfono en formatos internacionalizados o nacionalizados.

Para instalar el SDK PHP de Vonage, ejecuta:

# 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 las variables de entorno del archivo que crearás en el siguiente paso, se necesita el componente DotEnv de Symfony. Para instalar este componente ejecuta el siguiente comando desde tu Terminal:

docker-compose exec php composer require symfony/dotenv

En el Panel para desarrolladores de Vonageencontrarás "Tus credenciales de API", anótalas.

Dentro del directorio project/ añada las tres líneas siguientes al archivo .env.local (sustituyendo api_key y api_secret por su clave y su secreto):

La dirección VONAGE_BRAND_NAME es el nombre de la empresa/marca a la que representa para la verificación:

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

Se necesita una forma de verificar que los números de teléfono son válidos antes de hacer la comprobación de verificación. También hay que asegurarse de que el número de teléfono tiene el formato correcto para que la Verify API sepa a qué región (código de país) pertenece el número). Vas a utilizar puerto PHP de Giggsey de Google libphonenumber para asegurarte de que los números de teléfono tienen el formato correcto.

Ejecute el siguiente comando para instalar libphonenumber-for-php:

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

Ahora tienes las dos bibliotecas clave necesarias para:

  • Formatea números de teléfono,

  • Asegúrese de que los Numbers de teléfono son válidos,

  • Verificar con Vonage Verify API

  • Llamar por teléfono

Construir la utilidad Verify

Cree un nuevo directorio dentro de project/src/ llamado Utily dentro de este nuevo directorio, cree un nuevo archivo llamado VonageVerifyUtil.php. Este nuevo archivo es la clase de utilidad que contiene la funcionalidad para verificar los usuarios cuando se registran. Dentro de este nuevo archivo copia el código de abajo:

<?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']
            )
        );     
    }
}

Añade dos nuevos métodos a esta clase. El propósito de estos dos métodos es:

  • convertir el número de teléfono a un formato internacionalizado.

  • convertir el número de teléfono a un formato de número nacionalizado.

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();
}

La siguiente función que necesita el sistema es realizar la solicitud de verificación para nuevos registros. Añade el siguiente método para procesar la creación de una solicitud de verificación:

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);
}

nota si está interesado en un flujo de trabajo diferente para el proceso de verificación, consulte la documentación de Vonage.

El siguiente paso para verificar es tomar la entrada de los usuarios del código de verificación y enviar una solicitud para verificar con el código de verificación:

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

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

La última función no se utiliza en esta clase de utilidad, pero estará en varios lugares del proyecto. Esta función extrae el requestId de una respuesta de la API. Añade lo siguiente:

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

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

    return $responseData['request_id'];
}

Crear un nuevo usuario

Para crear un nuevo Register con la librería make ejecuta el siguiente comando:

docker-compose exec php bin/console make:controller

Donde dice: Choose a name for your controller class (e.g. AgreeableGnomeController): escriba RegisterController y pulse Intro.

Verá la salida en su Terminal para dos nuevos archivos generados:

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

El sitio VonageVerifyUtil necesita inyectarse como servicio en el recién creado RegisterController así que abre el archivo src/Controller/RegisterController.php e inyéctalo en la función __construct() función.

+ use App\Util\VonageVerifyUtil;

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

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

En su navegador, abra la página register página con la URL http://localhost:8081/register. Aparecerá:

The view in a web page showing the default RegisterController output

Es hora de crear la página de registro.

La biblioteca make está demostrando ser muy útil, hasta ahora ha creado un Entity, a Controllery ahora va a crear un Form. Para crear el UserType ejecute el siguiente comando:

docker-compose exec php bin/console make:form

Cuando te pida un nombre, introduce UserTypey cuando te pida un Entity escriba : User.

Encontrará el recién creado UserType en project/src/Form/UserType.php. Ábralo, localice la función buildForm() y sustituye su contenido 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'
        ],
    ])
;

En la parte superior de la clase, habrá que incluir varias clases nuevas, ChoiceType, SubmitType, Length, NotBlank, TelType, TextType. Añádelos al principio del archivo, como se muestra a continuación:

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;

Crear plantilla de registro

La plantilla base necesita incluir Bootstrap para que nuestras clases CSS de Bootstrap sean reconocidas y mostradas.

Abra project/templates/base.html.twigla plantilla base que incluirán todas las demás plantillas del proyecto, y sustituya el contenido 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>

Ahora que la plantilla tiene Bootstrap incluido, es hora de actualizar el index.html.twig para mostrar nuestro formulario y cualquier otra información. Abra project/templates/register/index.html.twig y reemplaza todo entre {% block body %} y {% 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>

Proceso de registro completo

Ha creado una plantilla y una clase de formulario, pero el proyecto no utiliza ninguno de estos archivos. Necesita incluirlos en su método RegisterController llamado index(). Vuelve a project/src/Controller/RegisterController.php.

Actualice el contenido del método index() como se muestra a continuación:

/**
 * @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(),
  ]);
}

Los cambios en el ejemplo anterior hacen lo siguiente:

  • Crea un nuevo objeto User vacío,

  • Crea una nueva instancia de la clase UserType utilizando el nuevo objeto Usuario como clase de datos con la que estructurar el formulario,

  • Maneja la solicitud de formulario para determinar si el formulario:

    • se ha presentado,

    • es válido (por ejemplo, si phoneNumber está vacío, no es único)

  • Si el formulario se envía y es válido, la función:

    • persiste el nuevo usuario en la base de datos

    • elimina estos cambios

    • Envía una nueva solicitud a la Verify API,

    • guarda el identificador de la solicitud de verificación en el registro del usuario en la base de datos

  • Si el formulario no se ha enviado o no es válido, la función muestra la plantilla al usuario, con el formulario incluido en la plantilla.

Es necesario importar varias clases a la clase actual, como por ejemplo User y UserTypeincluyen las de la parte superior, como se muestra a continuación:

+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;

En resumen, acabas de crear un nuevo RegisterController, a User entidad y un UserType formulario. Has actualizado tu plantilla para el método index() dentro del método RegisterController para mostrar los campos de formulario que esperas que vea el usuario.

Puede ver estos cambios en http://localhost:8081/registero como se muestra en el siguiente ejemplo:

Template of registration page being displayed in a web browser

Crear formulario Verify

Cree el VerifyType formulario con el siguiente comando:

docker-compose exec php bin/console make:form

Para la entrada del nombre: VerifyType Y para qué Entidad utilizar, elija User

Al enviarlo, verá algo como lo que se muestra en la imagen siguiente:

Terminal output confirming creation of VerifyForm class

Al crear el VerifyType formulario de esta manera significa que el sistema ha insertado automáticamente todas las User propiedades de la entidad en este formulario. Ninguno de estos campos es obligatorio, así que actualiza el código con lo siguiente:

$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'
+    ],
+])
;

El código anterior define el formulario que se espera para enviar un código de verificación. Este formulario contiene dos campos, el verificationCode y submit. Cada campo tiene sus propios atributos para fines de validación y visualización en el formulario o envío del formulario.

En la parte superior de la clase, es necesario importar algunas importaciones de clase. Así que copia el código en la parte superior de la clase, como se muestra a continuación:

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;

Verificación de la manija

Ya ha creado su página de registro, pero ¿y ahora qué? Necesita crear una página para que el usuario introduzca el código de verificación que recibe de la Verify API. Dentro de la página RegisterController crea un nuevo método debajo del método index() llamado verify(). El siguiente ejemplo muestra esto y el contenido de ese 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(),
    ]);
}

En la parte superior de la clase, incluya Response, VerifyType, ParamConvertery Verification como se muestra a continuación:

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;

Ahora se necesita la plantilla para mostrar este formulario. Cree un nuevo archivo en project/templates/register/ llamado verify.html y copie el contenido del bloque de código siguiente en este archivo:

{% 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 %}

Gestión de una verificación con éxito

Dentro del RegisterController el proyecto necesita ahora una página de éxito para cuando el usuario se haya verificado correctamente. Creando un nuevo registerSucess() que mostrará una plantilla con el mensaje de que el registro se ha realizado correctamente:

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

Ahora cree un nuevo archivo en project/templates/register/ llamado success.html.twig e imprime lo siguiente:

{% 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 %}

Hay algunos pequeños cambios necesarios para finalizar la parte de verificación. Las redirecciones son necesarias cuando cada envío de formulario tiene éxito en mover al usuario a la siguiente página. Así, en RegisterControlleren public function index()encuentre y añada lo siguiente:

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

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

Y dentro del método public function verify(Request $request, User $user)encuentra y añade lo siguiente:

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

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

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

Latitud y longitud

Para poder encontrar a los usuarios registrados geográficamente cerca de otros usuarios registrados, el sistema necesita disponer de la latitud y longitud de cada usuario, en el momento del registro. El proyecto utilizará un servicio llamado MapQuestque toma la ciudad y el condado/estado del usuario y los convierte en la latitud y longitud necesarias.

Regístrese para obtener una Account en: MapQuest.

Abra .env.local y añada las siguientes entradas nuevas (sustituyendo <api_key> por su clave API de MapQuest):

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

La entidad User necesita dos propiedades para almacenar la latitud y la longitud. Para actualizar esta entidad, ejecuta el comando de Symfony make como se muestra a continuación, y siga las instrucciones:

docker-compose exec php bin/console make:entity
  • Nombre de clase de la entidad a crear o actualizar (por ejemplo, DeliciousGnome):

    • User

  • Inmueble 1

    • Nombre: latitude

    • Tipo: decimal

    • Precisión: 20

    • Escala: 16

    • ¿Puede ser nulo? yes

  • Inmueble 2

    • Nombre: longtitude

    • Tipo: decimal

    • Precisión: 20

    • Escala: 16

    • ¿Puede ser nulo? yes

Ejecutando el siguiente comando se generará un nuevo archivo de migración con estos cambios en la base de datos:

docker-compose exec php bin/console make:migration

Si desea ver los próximos cambios en la base de datos, los archivos de migración generados se guardan en project/src/Migrations/.

Si estás satisfecho con estos cambios, ejecuta el siguiente comando para guardarlos en la base de datos:

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

Guzzle es un cliente PHP HTTP que permite a los proyectos hacer peticiones a otros servicios web, como peticiones POST y GET a APIs. Guzzle facilita hacer una solicitud GET al punto final de la API de Map Quest para recibir la latitud y longitud de la ubicación del usuario.

El siguiente paso es crear un MapQuestUtil para hacer esta petición GET y manejar la respuesta para recuperar sólo los campos requeridos. Cree un nuevo archivo en project/src/Util/ llamado MapQuestUtil.php y copia el siguiente código en el archivo recién creado:

<?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'];
    }
}

El código anterior es un nuevo método que toma un objeto User y hace una petición a la API de MapQuest proporcionando a los usuarios location que es su Town y su County. Hay varios pasos que siguen a esta solicitud para asegurarse de que se devuelve una ubicación, y si los campos esperados para la latitud y la longitud se incluyen en la respuesta.

Es hora de llamar a esta MapQuestUtil clase y getLatLongByAddress() método. Abra la clase RegisterController.

La nueva clase Utility debe inyectarse como servicio. En el método __construct() actualízalo como se muestra a continuación:

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;
    }

Ahora que la función MapQuestUtil se ha inyectado, es necesario llamar a la función y luego almacenar los detalles devueltos en el archivo User. En la función index() busque la línea if ($form->isSubmitted() && $form->isValid()) { y añada una nueva línea debajo. Añadir lo siguiente:

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

En este punto del tutorial, has creado un nuevo proyecto Symfony, configurado este proyecto con tu base de datos. También has creado una nueva User entidad, que se asigna con su user tabla de base de datos. Has creado dos nuevos formularios, un UserType y VerifyTypeque se utilizan para validar a los nuevos usuarios y comprobar si el código de verificación introducido es válido. También has creado varias páginas para que el usuario acceda, a /register, /verifyy /success.

Ahora se encuentra en un punto de interrupción natural, ¡a mitad de este tutorial! Ahora puede probar el proceso de registro de su proyecto.

Ir a la la página de inscripción y siga las instrucciones de las dos páginas siguientes. Introduzca un número de teléfono válido. De lo contrario, no podrá verificar su Account.

Amistad

Usuarios de Match

Crear entidad coincidente

Se necesita una nueva entidad para conectar a dos usuarios: recoger sus reacciones y almacenar los registros de los usuarios que se conectan. Esta nueva entidad almacenará una relación con los dos llamantes en el objeto emparejado y el nombre de la multiconferencia (los nombres de ambos usuarios concatenados por '-'). Para ello, utilice la biblioteca make como se muestra a continuación, y siga sus instrucciones para las tres nuevas propiedades:

docker-compose exec php bin/console make:entity
  • Nombre: Partido

  • 1 - callerOne - ManyToOne - Usuario - no anulable - resto por defecto

  • 2 - callerTwo - ManyToOne - Usuario - no anulable - resto por defecto

  • 3 - conferenceName - cadena - 255 - no anulable

Antes de realizar la migración, se necesita otro. Existe un paquete de extensiones para Doctrine que permite que el código haga uso de extensiones de Doctrine como Timestampablepara utilizar dos líneas en nuestras entidades para tener createdAt y updatedAt campos. Para ello instale stof/doctrine-extensions-bundle:

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

La configuración es necesaria para este nuevo paquete de extensiones. Abrir stof_doctrine_extensions.yaml que se encuentra en project/config/packages/ y actualícelo para reflejarlo:

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

Es necesario realizar algunos cambios más en la recién creada Match recién creada. Abra esta clase, que puede encontrar en project/src/Entity/Match.php.

En primer lugar, incluya el archivo TimestampableEntity en el proyecto añadiendo el siguiente import:

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

Ahora utiliza el TimestampableTrait en la clase Match como se muestra a continuación:

class Match
 {
+     use TimestampableEntity;

Ejecutando el siguiente comando se generará un nuevo archivo de migración con estos cambios en la base de datos:

docker-compose exec php bin/console make:migration

Si desea ver los próximos cambios en la base de datos, los archivos de migración generados se guardan en project/src/Migrations/.

Si estás satisfecho con estos cambios, ejecuta el siguiente comando para guardarlos en la base de datos:

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

Configurar Ngrok

Ngrok es una aplicación multiplataforma que permite a los usuarios exponer su servidor de desarrollo a Internet sin tener que configurar su router. Ngrok hace esto mediante la creación de un túnel entre su servidor y el subdominio Ngrok.

Es hora de configurar Ngrok para permitir que la aplicación de Vonage (que se crea en el siguiente paso), tenga un subdominio Ngrok para hacer sus peticiones webhook.

En el directorio docker/ngrok/ cambie el nombre del archivo ngrok.conf.local a ngrok.confy sustituya <auth_token> por tu auth token Ngrok:

Nota Puede encontrar su auth token en su salpicadero en Ngrok

authtoken: <auth_token>

Ahora, para actualizar sus contenedores Docker con la URL correcta del subdominio Ngrok, es necesario reiniciar Docker. Puedes reiniciar Docker con el siguiente comando:

docker-compose restart

Una vez que haya reiniciado Docker, vaya a https://0.0.0.0:4040y copie el subdominio Ngrok que aparece en la página de estado.

Ngrok Status Page

Si ahora tomas ese subdominio Ngrok y accedes a él: ngroksubdomain/registersustituyendo ngroksubdomain por tu subdominio Ngrok, verás tu página de registro. Para mí, el ejemplo fue: https://a551f0297ed8.ngrok.io/register.

Toma nota de este subdominio de Ngrok porque será necesario a continuación para configurar tu aplicación de Vonage.

Crear una aplicación de Vonage

Inicia sesión en tu Panel de Vonage. A la izquierda de la página hay un enlace Your Applicationshaz clic en él. Una vez cargado, haz clic en el botón Create a new Application botón .

Establecer el Application NamePara el tutorial, he puesto el nombre de Befriending Servicepero puede ser el que quieras.

Haga clic en el botón para Generate public and private key. Su navegador tendrá una descarga para el private.key archivo. Guárdelo en su project/ directorio.

En CapabilitiesActivar Voice.

Actualiza los tres cuadros de entrada que ves ahora con las URL de webhook necesarias para que Vonage realice devoluciones de llamadas durante las llamadas telefónicas. Por ejemplo, para el cuadro de texto de eventos https://a551f0297ed8.ngrok.io/webhooks/eventsrecuerda reemplazar a551f0297ed8 por el subdominio que creaste en el paso anterior. Asegúrate de hacer esto para los tres cuadros de entrada:

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

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

  • Fallback - https://<ngrok url>/webhooks/fallback

Por último, haga clic en Generate new application.

La página que se carga ahora mostrará su Application ID. Toma nota.

En su project/ abra .env.local y añada las dos líneas siguientes, asegurándose de sustituir <application id> por el id de tu aplicación, y sustituir <ngrok_url> por la URL de 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>

Ahora has creado una aplicación de Vonage y almacenado las credenciales dentro de tu proyecto. Al almacenar estas credenciales, el proyecto ahora puede realizar y recibir llamadas telefónicas.

Utilidad de llamadas de Vonage

Cree un nuevo archivo en project/src/Util/ llamado 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;
    }
}

Esta clase Utility actualmente no hace otra cosa que inyectar dos nuevos servicios al crearse. Estos servicios son VonageClientque es la parte que utiliza la Voice API de Vonage para realizar y recibir llamadas. El otro servicio inyectado es el VonageVerifyUtilque se llama cuando el proyecto necesita métodos específicos de esta clase Util.

La clase Utility se utiliza para iniciar la llamada entre dos usuarios. Así que el primer método necesario 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;
}

El código anterior es un método que toma dos usuarios como argumentos del mismo. A continuación, el método genera un nuevo objeto de la Match como conexión entre los dos usuarios. Se crea una matriz de objetos de control de llamadas Nexmo (NCCO), que es un conjunto de instrucciones para que Vonage Voice sepa qué esperar y los pasos esperados para realizar la llamada.

Esta matriz contiene otros dos elementos. El primero es utilizar la persona Amy para introducir al usuario en la llamada y explicarle de qué se trata. Amy pregunta al usuario si desea unirse a una conferencia telefónica o no, pidiéndole que introduzca 1 para sí o 2 para no. La segunda matriz es para dirigir al usuario al siguiente punto final del webhook, que es unirse a la conferencia.

Te habrás dado cuenta de que hay dos llamadas a un método makeCall() que aún no has creado. Cree este nuevo método copiando el siguiente código en su fichero 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) {

    }
}

Este método llama primero a otro método, getInternationalizedNumber()que se encuentra en el directorio vonageVerifyUtil. Toma el $caller que es una instancia de User y convierte el código de país y el número de teléfono en un número Internationalized número al que Vonage puede llamar.

El siguiente paso en el método anterior es inicializar la llamada estableciendo a to, from e insertando el objeto ncco objeto.

Por último, dentro de este VonageCallUtil es necesario incluir varias clases, ya que se utilizan en los ejemplos anteriores. Copie las adiciones que se muestran en el siguiente ejemplo en su archivo:

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;

En resumen, la funcionalidad anterior aún no se ejecuta, pero crea un nuevo objeto Match, guarda dos usuarios en él y, a continuación, realiza dos llamadas para cada usuario. Todavía no se realiza ninguna conferencia ni se enlazan las dos llamadas. La conferencia se realiza a continuación.

Usuarios coincidentes

Debido a que el proyecto va a tener llamadas diarias, se necesita un comando, que se ejecutará como un cronjob. En su Terminal, dentro del directorio docker/ utilice la biblioteca make para crear un nuevo comando:

docker-compose exec php bin/console make:command

Cuando la biblioteca le pida un nombre para su nuevo comando introduzca app:match-users.

Al ejecutar este comando se creará un nuevo archivo llamado MatchUsersCommand.php dentro de: project/src/Command/MatchUsersCommand.php.

Abra el archivo MatchUsersCommand.php recién creado.

El comando que has generado tiene algunos ajustes por defecto, la mayoría de los cuales no necesitarás para este proyecto. Busque protected function configure() y sustituye el contenido de esta función por

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

Es necesario inyectar dos servicios en la clase mediante el método __construct() de la clase. Los dos servicios necesarios son el EntityManagerInterfaceque permite al código realizar consultas a la base de datos o tener acceso a los métodos del repositorio de sus entidades. El segundo servicio necesario es el VonageCallUtil para llamar a nuestra funcionalidad previamente creada que llama a dos usuarios y los conecta.

Actualice el siguiente código dentro del archivo MatchUsersCommand.php para que coincida con el que se muestra a continuación:

+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();
+    }

Ahora es el momento de empezar a construir la funcionalidad del mando.

Para que la consulta funcione, el proyecto necesita algunas Extensiones Doctrine que vas a escribir en el siguiente paso. Estas extensiones provienen de una biblioteca de terceros creada por Benjamin Eberlei. Para instalar esta librería, dentro de tu Terminal ejecuta el siguiente comando:

docker-compose exec php composer require beberlei/doctrineextensions

Ahora, para habilitar las extensiones requeridas. Abra project/config/packages/doctrine.yamly con la misma sangría de auto_generate_proxy_classes, naming_strategy, auto_mappingy mappings añada:

Nota: Preste atención a la sangría para asegurarse de que el formato YAML es correcto.

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

De vuelta a su MatchUsersCommand encuentra la función execute() función. Esta función es el lugar donde todo el trabajo ocurre para el comando.

Para empezar, elimine todo el contenido de este método.

La primera parte que se necesita en este método es tener una instancia del método Match y User Repositorios en variables, y obtener todos los usuarios que están marcados como activos:

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

Después de esto, es hora de hacer un bucle a través de cada usuario activo, para iniciar llamadas y conectarlos con otro usuario. Añade el siguiente bucle:

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

}

Ahora el sistema necesita encontrar usuarios geográficamente cercanos a la corriente $callerOne para conectarlos. La consulta es bastante larga y pertenecerá a la categoría UserRepository. Abra el archivo project/src/Repository/UserRepository.php

Debajo del método __construct añada el siguiente método nuevo:

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();
}

El método en el bloque de código anterior, toma un objeto usuario, un array de los usuarios activos actuales y una variable entera de una distancia predeterminada.

Este método crea una nueva consulta que calcula la distancia de todos los usuarios desde el usuario dado y se asegura de que todos están activos y verificados. La última comprobación es asegurarse de que los usuarios devueltos están dentro de la distancia dada del usuario introducido.

De vuelta al MatchUsersCommand encuentre la línea foreach ($activeUsers as $activeUser) { y añade lo siguiente:

// 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();
}

El comando se crea para encontrar usuarios con los que hacer coincidir e iniciar la llamada. Sin embargo, se necesitan URL de webhooks para que la llamada progrese desde la introducción automatizada hasta la conexión de los usuarios en una multiconferencia.

Webhooks

Ahora necesitas un WebhooksControllerasí que, en su Terminal, utilice la biblioteca make para ejecutar el siguiente comando:

docker-compose exec php bin/console make:controller

Donde dice: Choose a name for your controller class (e.g. AgreeableGnomeController): escriba WebhooksController y pulse Intro.

Ahora ha creado dos nuevos archivos:

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

Abra el archivo WebhooksController.php que se encuentra en project/src/Controller/

Este controlador tendrá que hacer uso de tres servicios, VonageVerifyUtil, VonageCallUtily EntityManagerInterface. Así que empieza por inyectarlos en la construcción del Controlador, como se muestra a continuación:

+
+ 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;
+    }
}

Los dos métodos siguientes hacen lo que su nombre indica. El primero busca a un usuario por su número de teléfono, mientras que el segundo busca una instancia de Match instancia por un User que has creado hoy. Añádelos a tu fichero 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()));
}

Un método llamado en el ejemplo anterior todavía no existe, se trata de encontrar coincidencias por el usuario y la fecha actual. Abra project/src/Repository/MatchRepository.php y dentro de esta clase, añade el método que se muestra a continuación:

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();
}

En la parte superior del fichero añada la importación para la entidad User:

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

La consulta anterior encuentra una Match entrada en la que el usuario es callerOne o callerTwo y la entrada Match fue creada hoy. Aquí hay una función de consulta que Doctrine no conoce. Date(m.createdAt). Al calcular la distancia entre las personas que instaló beberlei/doctrineextensions. Esta librería tiene funcionalidad para incluir la Date extensión. Así que abre project/config/packages/doctrine.yaml y añade las dos líneas siguientes:

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, usted creó un método llamado createConferenceBetween()dentro de este método había un NCCO que hacía referencia a una URL de /webhooks/joinConferenceSi ejecutas el ejemplo ahora, no ocurrirá nada ya que la URL no existe. Cree un nuevo método joinConference() dentro de su método WebhooksController con la anotación que contiene esta URL.

El método tendrá dos finalidades. El primero es validar la entrada del usuario, asegurándose de que sólo introduce opciones válidas antes de continuar. Estas entradas son 1 para sí, y 2 para no.

El segundo propósito de este método es respetar la entrada del usuario. Si opta por continuar con la llamada, se le redirige a la siguiente acción, que le conecta a la multiconferencia. Si el usuario selecciona "2", para no unirse a una llamada con alguien, el sistema responde confirmando su petición de no unirse a una llamada y finaliza la llamada.

/**
 * @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);
}

Aquí se utilizan cuatro clases que no hemos incluido en nuestro archivo, Request, Match, Usery JsonResponse. Inclúyalas encima de la clase:

+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;

En este punto, ahora tienes un formulario de registro de usuario, que lleva al usuario a través del proceso de verificación. También tienes un comando que, cuando se ejecuta, hará un bucle a través de todos los usuarios activos y verificados conectándolos con otros usuarios activos y verificados en un radio máximo de 100 millas entre sí.

Ya puedes probarlo. Si dispone de dos números de teléfono, siga adelante y regístrese en su página de registro. Asegúrate de seguir el proceso de verificación y de que la ubicación que has introducido para ambos usuarios se encuentra dentro de un radio de 30 millas.

Una vez registrados los usuarios, en tu Terminal, dentro del directorio docker/ ejecute el siguiente comando:

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

Ambos Numbers recibirán una llamada y se les preguntará si desean conectarse a una llamada con otra persona.

Recibir comentarios

Se necesita un segundo comando para recuperar los comentarios de cada usuario. Este comando recopilará los comentarios de las llamadas del día actual de cada usuario. Este comando también será un cronjob programado para ejecutarse varias horas después de que haya finalizado la llamada correspondiente.

Pero antes de hacer este nuevo comando, la Match entidad necesita actualizarse, ya que cuando creó la Match entidad con la make biblioteca, también puedes actualizarla con nuevas propiedades. Ejecute el siguiente comando en su Terminal y siga las instrucciones de cada paso a continuación:

docker-compose exec php bin/console make:entity
  • Nombre de clase de la entidad a crear o actualizar (por ejemplo, DeliciousGnome):

    • Match

  • Inmueble 1

    • Nombre: callerOneFeedbackAccepted

    • Tipo: boolean

    • ¿Puede ser nulo? yes

  • Inmueble 2

    • Nombre: callerTwoFeedbackAccepted

    • Tipo: boolean

    • ¿Puede ser nulo? yes

  • Inmueble 3

    • Nombre: callerOneCallSuccessful

    • Tipo: boolean

    • ¿Puede ser nulo? yes

  • Inmueble 4

    • Nombre: callerTwoCallSuccessful

    • Tipo: boolean

    • ¿Puede ser nulo? yes

La entidad actualizada no refleja actualmente lo que hay en la base de datos. Para que la base de datos refleje lo que ha definido en la entidad Match ejecute el siguiente comando para generar un nuevo archivo de migración:

docker-compose exec php bin/console make:migration

Si desea ver los próximos cambios en la base de datos, los archivos de migración generados se guardan en project/src/Migrations/.

Si estás satisfecho con estos cambios, ejecuta el siguiente comando para guardarlos en la base de datos:

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

En primer lugar, debe obtener una lista de los partidos de hoy. Así que en su MatchRepository añade una nueva función que consulte la base de datos para encontrar todas las coincidencias del día actual. Esta consulta sólo encontrará los usuarios coincidentes en los que el indicador callerOneCallSuccessful está vacía:

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();
}

Ahora es el momento de crear el nuevo comando que hace uso de los nuevos cambios, en su tipo de terminal:

docker-compose exec php bin/console make:command

Cuando te pida un nombre de comando introduce: app:get-feedback. Este comando generará un nuevo archivo de comandos dentro de project/src/command/ llamado GetFeedbackCommand.php Abra el archivo recién creado.

Este controlador necesita que se le inyecten dos servicios a través del método __construct() . Los mismos dos servicios inyectados en el controlador MatchUsersCommand. Copie los ajustes que se muestran a continuación en su 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();
+    }

Al realizar los cambios anteriores, su comando ahora tiene acceso a los archivos VonageCallUtil y EntityManagerInterface.

Actualice el contenido de configure() con lo que se muestra en el siguiente ejemplo. Este ejemplo establece la descripción del comando:

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

Es hora de implementar la funcionalidad para iniciar la recogida de comentarios de los usuarios emparejados. Sustituye el contenido del 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;

La función anterior recopila las coincidencias del día y, a continuación, realiza un bucle con cada coincidencia iniciando una llamada con el llamante uno y el llamante dos solicitando información de cada uno sobre su llamada anterior.

Como puede ver, ha hecho una llamada a makeFeedbackCall() desde el método VonageCallUtilpero el método aún no existe. Así que abre el método VonageCallUtil dentro de project/src/util/VonageCallUtil.php y añade un nuevo 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);
}

El ejemplo anterior hace referencia a una eventUrl que es su URL Ngrok y /webhooks/userFeedback. Este endpoint no existe en nuestro WebhooksController todavía.

El método tendrá dos finalidades. El primero es validar la entrada del usuario, asegurándose de que sólo introduce opciones válidas antes de continuar. Estas entradas son 1 para sí, y 2 para no.

El segundo propósito de este método es respetar la opinión del usuario. Si opta por dar su opinión, se le redirige a la siguiente acción, que es formular la primera pregunta de opinión. Si el usuario selecciona "2", no dar feedback, el sistema responde confirmando su petición de no dar feedback y finaliza la llamada.

Así que en la sección WebhooksController creamos un nuevo método llamado getUserFeedback() como se muestra a continuación:

/**
 * @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);
}

El ejemplo anterior hace referencia a una eventUrl que es su URL Ngrok y /webhooks/userFeedbackCallSuccess. Este endpoint no existe en nuestro WebhooksController todavía.

El método tendrá dos finalidades. El primero es validar la entrada del usuario, asegurándose de que sólo introduce opciones válidas antes de continuar. Estas entradas son 1 para sí, y 2 para no.

El segundo propósito de este método es respetar la entrada del usuario. Si elige '1' o '2', entonces guarda la entrada del usuario en la propiedad setCallerOneCallSuccessful o la propiedad setCallerTwoCallSuccessful como verdadero o falso (tanto si han disfrutado de la llamada como si no).

Una vez introducida con éxito la opción, el código redirige al usuario a la siguiente acción de la llamada, que es el punto final /webhooks/userFeedbackContinue

Así que en la sección WebhooksController creamos un nuevo método llamado getUserFeedbackCallSuccess() como se muestra a continuación:

/**
 * @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);
}

El ejemplo anterior hace referencia a una eventUrl que es su URL Ngrok y webhooks/userFeedbackContinue. Este endpoint no existe en nuestro WebhooksController todavía.

El método tendrá dos finalidades. El primero es validar la entrada del usuario, asegurándose de que sólo introduce opciones válidas antes de continuar. Estas entradas son 1 para sí, y 2 para no.

El segundo objetivo de este método es respetar la entrada del usuario. Si elige "1", seguirá suscrito al servicio para recibir una llamada al día siguiente.

Si el usuario elige "2", entonces se establecen como inactivos y ya no se les llama.

La llamada finaliza tras la introducción correcta de una opción.

Así que en la sección WebhooksController creamos un nuevo método llamado getUserFeedbackContinue() como se muestra a continuación:

/**
 * @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);
}

La última adición necesaria para el proyecto es una función que usted puede haber notado que se llama isFirstOrSecondCaller() pero que es necesario crear. Este método toma una instancia de Match y un único objeto User y determina si el usuario es callerOne o callerTwo del Match. Añade esto al final de tu clase:

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

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

    return null;
}

Conclusión

Si has seguido este tutorial de principio a fin, ya has creado un proyecto a partir de una nueva instalación de Symfony.

Este nuevo proyecto tiene una página de registro, que al ser enviada envía una solicitud de verificación a Vonage Verify. Esta solicitud activa un SMS para los propietarios del número de teléfono registrado. El código de verificación del SMS debe ingresarse en la siguiente página que ve el usuario. Si la verificación se realiza correctamente, el usuario es redirigido a una página de éxito que le informa a qué hora debe esperar las llamadas telefónicas.

La segunda parte de este proyecto es la función Voice de Vonage. Mediante comandos, se empareja a los usuarios, se les llama y se les une en una multiconferencia. Más tarde, ese mismo día, se ejecuta un segundo comando para solicitar la opinión de cada persona emparejada en el día en curso.

El código terminado para este tutorial se puede encontrar en la rama final-tutorial en este repositorio GitHub.

A continuación encontrarás otros tutoriales que hemos escrito para implementar Vonage Verify o Voice en proyectos:

No olvides que si tienes alguna pregunta, consejo o idea que quieras compartir con la comunidad, no dudes en entrar en nuestro espacio de trabajo espacio de trabajo comunitario Slack. Me encantaría saber si alguien ha implementado este tutorial y cómo funciona su proyecto.

Compartir:

https://a.storyblok.com/f/270183/250x250/b052219541/greg-holmes.png
Greg HolmesAntiguos alumnos de Vonage

Antiguo educador de desarrolladores @Vonage. Procedente de PHP, pero no limitado a un solo lenguaje. Un ávido jugador y un entusiasta de Raspberry pi. A menudo se le encuentra practicando escalada en rocódromo.