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

Befriending-Dienst mit Symfony und Vonage

Zuletzt aktualisiert am April 19, 2021

Lesedauer: 64 Minuten

Angesichts der aktuellen globalen Situation sind die meisten Länder in irgendeiner Form abgeriegelt. Soziale Distanzierung ist gerade jetzt wichtig, um die Auswirkungen von Covid-19 zu verringern. Während einige von uns einen vollen Kalender mit virtuellen Treffen haben, haben andere vielleicht nicht so viele Menschen, die sie anrufen oder mit denen sie sich die Zeit vertreiben können. Meine Großmutter zum Beispiel ist technisch nicht sehr versiert und daher auf Telefongespräche angewiesen. Obwohl sie das Glück hat, dreizehn Enkelkinder zu haben, sagt sie oft, dass sie an manchen Tagen von niemandem etwas gehört hat und gerne täglich mit jemandem sprechen würde. Bei Vonage haben wir regelmäßig die Möglichkeit, etwas für unser Lernen zu entwickeln. Bei dieser Gelegenheit habe ich mich dafür entschieden, einen Freundschaftsdienst einzurichten, der Nutzer vorstellt, die verletzlich oder einsam sind oder täglich mit einer anderen Person sprechen möchten. Die Idee dahinter ist, dass die Menschen neue Freunde finden können, während sie eingesperrt sind, oder jederzeit.

Voraussetzungen

Vonage API-Konto

Um dieses Tutorial durchzuführen, benötigen Sie ein Vonage API-Konto. Wenn Sie noch keines haben, können Sie sich noch heute anmelden und mit einem kostenlosen Guthaben beginnen. Sobald Sie ein Konto haben, finden Sie Ihren API-Schlüssel und Ihr API-Geheimnis oben auf dem Vonage-API-Dashboard.

In diesem Lernprogramm wird auch eine virtuelle Telefonnummer verwendet. Um eine zu erwerben, gehen Sie zu Rufnummern > Rufnummern kaufen und suchen Sie nach einer Nummer, die Ihren Anforderungen entspricht.

Erste Schritte

In diesem Tutorial werden Sie ein neues PHP-Projekt mit Symfony. Sie verwenden Vonage Verify, Vonage Voice und MapQuest APIs. Dieses Projekt wird nach seiner Fertigstellung einen Dienst bereitstellen, der es den Nutzern ermöglicht, sich mit ihrer Telefonnummer, ihrem Namen, ihrer Stadt und ihrem Bezirk/Staat zu registrieren. Bei der Registrierung erhält der Nutzer einen Verifizierungscode, den er auf der Website eingeben muss. Nach der Verifizierung wird der Nutzer in eine Liste von Personen aufgenommen, die jeden Tag einen Anruf erhalten und mit einem anderen Nutzer verbunden werden, mit dem sie sich unterhalten können. Am Ende eines jeden Tages erhält jeder Nutzer erneut einen automatischen Anruf, in dem er um eine Rückmeldung zu dem Gespräch gebeten wird, das er an diesem Tag geführt hat.

Klonen des Repositorys

Das Projekt verwendet die Docker Container Nginx, MySQL, PHPund Ngrok Container für minimale Änderungen der Serverkonfiguration.

Beginnen Sie dieses Tutorial mit dem Klonen des bestehenden Repositorys. Das Klonen kann durch Kopieren des folgenden Befehls in Ihr Terminal und anschließendes Wechseln des Projektverzeichnisses durchgeführt werden:

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

Ihr Terminal wird zwei Verzeichnisse ausgeben:

  • docker wo Ihre Docker-Konfigurationen zu finden sind (Sie brauchen nichts zu ändern, es sei denn, Sie legen andere Datenbankanmeldeinformationen fest)

  • project wo Ihr frisches Symfony Projekt gespeichert ist

Docker ausführen

Wechseln Sie in Ihrem Terminal in das Verzeichnis docker/ und führen Sie den folgenden Befehl aus, um Ihre Docker-Container zu erstellen und zu starten:

docker-compose up -d

Der obige Befehl lädt alle vorkonfigurierten Docker Container herunter und fügt alle benutzerdefinierten Änderungen hinzu, die in den Dateien im Verzeichnis docker/ Verzeichnis. Build kompiliert dann diese Container und führt sie als Dienste aus, damit Sie Ihre Webanwendung ausführen können.

Sobald der Befehl abgeschlossen ist, sehen Sie eine Bestätigung, dass die Docker Container ausgeführt werden, wie im folgenden Beispiel gezeigt:

Successfully Running Docker

Ihre Docker-Container und Server sind nun bereit für die Entwicklung!

Die Anwendung

Benutzerregistrierung und -überprüfung

Symfony installieren

Wenn Sie sich das Verzeichnis project Verzeichnis schauen, werden Sie die Dateistruktur einer leeren Symfony-Installation wie unten sehen:

Fresh Symfony Installation

Führen Sie im Verzeichnis docker/ den folgenden Befehl aus, um alle in der composer.json-Datei beschriebenen PHH-Bibliotheken zu installieren:

docker-compose exec php composer install

Die Datenbankkonfigurationen für dieses Symfony-Projekt müssen irgendwo gespeichert werden. Also erstellen Sie in Ihrer IDE unter dem project/ Verzeichnis, eine neue Datei namens .env.local. Fügen Sie nach der Erstellung die folgende Zeile in diese neue Datei ein:

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

Eine Beschreibung der Aufschlüsselung der oben genannten Linie ist unten aufgeführt:

  • Name der Datenbank: befriending

  • Datenbank-Benutzer: user

  • Datenbank-Passwort: password

  • Datenbank-Host und -Port: mysql:3306

Dies sind Werte, die in der docker/docker-compose.yml Datei. Der aktuelle Benutzername und das Passwort sind aus Sicherheitsgründen nicht sicher. Wenn Sie diese ändern möchten, tun Sie dies bitte sowohl in project/.env.local und docker/docker-compose.ymlund führen Sie dann aus:

docker-compose build

Eine Benutzerentität erstellen

A User Entität wird benötigt, d.h. die Klasse, die die Struktur der Datenbanktabelle korrekt definiert. user Datenbanktabelle definiert. Diese Tabelle ist die Tabelle, in der neue Benutzerregistrierungen gespeichert werden.

Um dies zu erreichen, hat Symfony eine make Bibliothek veröffentlicht, die es den Benutzern ermöglicht, die Befehlszeilenschnittstelle (CLI) zur Erstellung bestimmter Klassen zu verwenden.

Führen Sie den folgenden Befehl aus und folgen Sie den Anweisungen, um die Eigenschaften für die User Entität zu erstellen:

docker-compose exec php bin/console make:entity
  • Klassenname der Entität, die erstellt oder aktualisiert werden soll (z. B. DeliciousGnome):

    • User

  • Eigenschaft 1

    • Name: phoneNumber

    • Art: string

    • Feldlänge: 255

    • Kann Null sein: no

  • Eigenschaft 2

    • Name: name

    • Art: string

    • Feldlänge: 255

    • Kann Null sein: no

  • Eigenschaft 3

    • Name: town

    • Art: string

    • Feldlänge: 255

    • Kann Null sein: no

  • Eigenschaft 4

    • Name: county

    • Art: string

    • Feldlänge: 255

    • Kann Null sein: no

  • Eigenschaft 5

    • Name: countryCode

    • Art: string

    • Feldlänge: 2

    • Kann Null sein: no

  • Eigenschaft 6

    • Name: verificationRequestId

    • Art: string

    • Feld Länge: 255

    • Kann Null sein: yes

  • Eigenschaft 7

    • Name: verified

    • Art: boolean

    • Kann Null sein: no

  • Eigenschaft 8

    • Name: active

    • Art: boolean

    • Kann Null sein: no

Nach Beendigung dieses Befehls werden zwei neue Dateien erstellt:

  • project/src/Entity/User.php

  • project/src/Repository/UserRepository.php

Die UserRepository ist eine Repository-Klasse, in der die Entwickler benutzerdefinierte Abfragen für die User Entität finden. Ignorieren Sie diese Klasse vorerst.

Die erstellte Entität entspricht nicht dem, was sich in der Datenbank befindet. Damit die Datenbank widerspiegelt, was Sie in der Entität definiert haben User Entität definiert haben, führen Sie den folgenden Befehl aus, um eine neue Migrationsdatei zu erzeugen:

docker-compose exec php bin/console make:migration

Wenn Sie die anstehenden Datenbankänderungen sehen möchten, werden die erzeugten Migrationsdateien in project/src/Migrations/.

Wenn Sie mit diesen Änderungen zufrieden sind, führen Sie den folgenden Befehl aus, um sie in der Datenbank zu speichern:

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

Die User Entität benötigt noch einige weitere Änderungen, die nicht mit dem maker CLI. Um diese Änderungen vorzunehmen, öffnen Sie diese Klasse, die Sie in project/src/Entity/User.php.

Zunächst wird eine Klasse UniqueEntity als Anmerkung verwendet, um sicherzustellen, dass eine der Eigenschaften in Ihrer User-Klasse eindeutig ist (also nur ein Eintrag mit diesem Wert). Fügen Sie diese Klasse oben hinzu:

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

Fügen Sie die folgenden zwei Zeilen hinzu:

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

Die erste Änderung, die Sie vorgenommen haben, ist das Hinzufügen einer fest kodierten Anmerkung für den Tabellennamen. Der Grund dafür ist, dass das Wort user ein Schlüsselwort in MySQL ist. Daher muss das System den user Tabellenname in Anführungszeichen gesetzt werden, um sicherzustellen, dass der Code bei einer Abfrage den Tabellennamen nicht mit dem Schlüsselwort verwechselt.

Die zweite Änderung ist das Hinzufügen der Eigenschaft phoneNumber als eine UniqueEntity. Die Verwendung von UniqueEntity wird verhindert, dass mehrere Benutzer dieselbe Telefonnummer haben können.

Aktualisieren Sie nun die Definition der Eigenschaft phoneNumber Eigenschaft, damit sie ein eindeutiges Feld ist:

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

Die letzte Änderung an der User Entität ist es, sicherzustellen active, und verified Felder standardmäßig auf false gesetzt werden, also erstellen Sie eine __construct() Funktion, die dies tut:

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

Sie haben nun eine neue Entität "Benutzer" erstellt, mit der das System neue Benutzer anlegt sowie Benutzer in der Datenbank speichert und bearbeitet.

Webpack Encore installieren

Bootstrap wird in diesem Tutorial verwendet, um das Design der Seiten besser zu gestalten und zu layouten. Symfony hat vor kurzem Webpack Encore veröffentlicht, das einen viel einfacheren Weg darstellt, Webpack in Ihre Symfony- oder PHP-Anwendung zu integrieren. Um Webpack Encore zu installieren, führen Sie die folgenden Befehle aus:

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

Die Ausführung des yarn install Befehls wird das Projekt so eingerichtet, dass es auch Javascript-Dateien verarbeiten kann. Dieser Befehl erstellt ein assets Verzeichnis, und innerhalb dieses Verzeichnisses gibt es eine js/app.js Datei und eine css/app.css Datei.

Jetzt wird Bootstrap mit dem folgenden Befehl installiert:

yarn add bootstrap --dev

Die Symfony-Anwendung weiß noch nicht, dass es Bootstrap gibt. Um dies in das Projekt einzubinden, öffnen Sie die project/assets/css/app.scss Datei und fügen Sie die folgende Zeile hinzu:

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

Bootstrap JS benötigt jQuery und PopperJs, so installieren Sie diese mit dem Befehl unten:

yarn add jquery popper.js --dev

Auch hier weiß die Symfony-Anwendung nicht, was jQuery und PopperJs sind. Fügen Sie sie also in das Projekt ein, indem Sie die Datei project/assets/js/app.js Datei öffnen und ihr die folgenden Aktualisierungen hinzufügen:

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

+ require('bootstrap');

Diese CSS- und JS-Dateien müssen kompiliert werden, damit sie in den Vorlagen zugänglich sind. Führen Sie dazu den folgenden Befehl aus:

yarn run dev

Die erforderlichen Frontend-Teile sind installiert und konfiguriert!

Vonage SDK und libphonenumber-for-php installieren

Dieses Tutorial benötigt zwei weitere PHP-Bibliotheken:

  • das Vonage SDK,

    • um Verifizierungsanfragen zu senden,

    • Benutzer verifizieren

    • Telefonate führen

    • DTMF-Telefonanrufe verarbeiten.

  • Giggseys libphonenumber-for-php, das Telefonnummern in internationalisierte oder nationalisierte Formate umwandelt.

Um das Vonage PHP SDK zu installieren, führen Sie aus:

# 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

Um Umgebungsvariablen aus der Datei zu verwenden, die Sie im nächsten Schritt erstellen werden, wird die Symfony DotEnv Komponente benötigt. Um diese Komponente zu installieren, führen Sie den folgenden Befehl in Ihrem Terminal aus:

docker-compose exec php composer require symfony/dotenv

Auf dem Vonage Entwickler Dashboardfinden Sie "Ihre API-Zugangsdaten", notieren Sie sich diese.

Innerhalb des Verzeichnisses project/ Verzeichnis fügen Sie die folgenden drei Zeilen in die Datei .env.local (ersetzen Sie api_key und api_secret durch Ihren Schlüssel und Ihr Geheimnis):

Die VONAGE_BRAND_NAME ist der Name des Unternehmens/der Marke, das/die Sie bei der Überprüfung vertreten:

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

Es ist eine Möglichkeit erforderlich, die Gültigkeit von Numbers zu überprüfen, bevor die Überprüfung durchgeführt wird. Sie müssen auch sicherstellen, dass die Telefonnummer das richtige Format hat, damit die Verify API weiß, zu welcher Region (Landesvorwahl) die Nummer gehört.) Sie werden Folgendes verwenden Giggseys PHP-Portierung von Google libphonenumber um sicherzustellen, dass die Telefonnummern richtig formatiert sind.

Führen Sie den folgenden Befehl zur Installation aus libphonenumber-for-php:

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

Sie verfügen nun über die beiden wichtigsten Bibliotheken, die Sie benötigen:

  • Telefonnummern formatieren,

  • Stellen Sie sicher, dass die Numbers gültig sind,

  • Verify mit Vonage Verify API

  • Telefonate führen

Bauen Sie das Verify Util

Erstellen Sie ein neues Verzeichnis innerhalb von project/src/ namens Utilund erstellen Sie innerhalb dieses neuen Verzeichnisses eine neue Datei mit dem Namen VonageVerifyUtil.php. Diese neue Datei ist die Dienstleistungsklasse, die die Funktionalität zur Verifizierung von Benutzern bei der Registrierung enthält. Kopieren Sie in diese neue Datei den unten stehenden Code:

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

Fügen Sie zwei neue Methoden zu dieser Klasse hinzu. Der Zweck dieser beiden Methoden ist es,:

  • die Telefonnummer in ein internationalisiertes Format umwandeln.

  • die Rufnummer in ein nationales Nummernformat umwandeln.

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

Die nächste Funktion, die das System benötigt, ist der Antrag auf Verifizierung für neue Registrierungen. Fügen Sie die nachstehende Methode hinzu, um die Erstellung eines Verifizierungsantrags zu verarbeiten:

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

Hinweis Wenn Sie an einem anderen Arbeitsablauf für den Verifizierungsprozess interessiert sind, sehen Sie sich bitte die Vonage-Dokumentation.

Der nächste Schritt zur Verifizierung ist die Eingabe des Verifizierungscodes durch den Benutzer und das Senden einer Anfrage zur Verifizierung mit dem Verifizierungscode:

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

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

Die letzte Funktion wird in dieser Utility-Klasse nicht verwendet, kommt aber an mehreren Stellen im Projekt vor. Diese Funktion extrahiert die requestId aus einer API-Antwort. Fügen Sie das Folgende hinzu:

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

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

    return $responseData['request_id'];
}

Einen neuen Benutzer anlegen

So erstellen Sie einen neuen Register Controller mit der Symfony make Bibliothek zu erstellen, führen Sie den folgenden Befehl aus:

docker-compose exec php bin/console make:controller

Wo es heißt: Choose a name for your controller class (e.g. AgreeableGnomeController): eingeben RegisterController ein und drücken Sie die Eingabetaste.

In Ihrem Terminal sehen Sie die Ausgabe für zwei neu erzeugte Dateien:

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

Die VonageVerifyUtil muss als Dienst in die neu erstellte RegisterController öffnen Sie also die Datei src/Controller/RegisterController.php und injizieren Sie sie in die __construct() Funktion.

+ use App\Util\VonageVerifyUtil;

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

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

Öffnen Sie in Ihrem Browser die register Seite mit der URL http://localhost:8081/register. Sie werden mit "begrüßt":

The view in a web page showing the default RegisterController output

Es ist an der Zeit, die Registerseite zu erstellen!

Die Symfony make Bibliothek erweist sich als sehr nützlich, bisher hat sie eine Entity, a Controllererstellt, und jetzt wird sie eine Form. Zum Erstellen des UserType Formular zu erstellen, führen Sie den folgenden Befehl aus:

docker-compose exec php bin/console make:form

Wenn es nach einem Namen fragt, geben Sie UserTypeein, und wenn es nach einem Entity eingeben: User.

Sie finden die neu erstellten UserType unter . project/src/Form/UserType.php. Öffnen Sie es, suchen Sie die buildForm() Funktion und ersetzen Sie deren Inhalt durch:

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

An der Spitze der Klasse müssen mehrere neue Klassen eingefügt werden, ChoiceType, SubmitType, Length, NotBlank, TelType, TextType. Fügen Sie diese am Anfang der Datei ein, wie unten gezeigt:

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;

Registrierungsvorlage erstellen

Die Basisvorlage muss Bootstrap enthalten, damit unsere Bootstrap-CSS-Klassen erkannt und angezeigt werden können.

Öffnen Sie project/templates/base.html.twigdie Basisvorlage, die von allen anderen Vorlagen im Projekt übernommen wird, und ersetzen Sie den Inhalt durch:

<!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>

Da die Vorlage nun Bootstrap enthält, ist es an der Zeit, die index.html.twig Vorlage zu aktualisieren, um unser Formular und alle anderen Informationen anzuzeigen. Öffnen Sie project/templates/register/index.html.twig und ersetzen Sie alles zwischen {% block body %} und {% endblock %} mit:

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

Vollständiger Registrierungsprozess

Sie haben nun eine Vorlage und eine Formularklasse erstellt, aber das Projekt verwendet derzeit keine dieser Dateien. Sie müssen diese in Ihre RegisterController Methode namens index(). Gehen Sie zurück zu project/src/Controller/RegisterController.php.

Aktualisieren Sie den Inhalt der Methode index() wie unten gezeigt:

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

Die Änderungen im obigen Beispiel bewirken Folgendes:

  • Erzeugt ein neues leeres User Objekt,

  • Erzeugt eine neue Instanz der UserType Formulars, wobei das neue User-Objekt als Datenklasse verwendet wird, mit der das Formular strukturiert werden soll,

  • Verarbeitet die Formularanforderung, um festzustellen, ob das Formular:

    • wurde vorgelegt,

    • der Inhalt des Formulars gültig ist (zum Beispiel, ob phoneNumber leer ist, nicht eindeutig ist)

  • Wenn das Formular abgeschickt wird und gültig ist, dann wird die Funktion:

    • speichert den neuen Benutzer in der Datenbank

    • spült diese Änderungen

    • Sendet eine neue Anfrage an die Verify API,

    • speichert die Kennung des Verifizierungsantrags im Benutzerdatensatz in der Datenbank

  • Wenn das Formular nicht übermittelt wurde oder ungültig ist, wird dem Benutzer die Vorlage angezeigt, wobei das Formular in die Vorlage integriert wird.

Mehrere Klassen müssen in die aktuelle Klasse importiert werden, wie z. B. User und UserTypesind die Klassen an der Spitze, wie unten gezeigt:

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

Zusammengefasst: Sie haben gerade eine neue RegisterController, a User Entität und UserType Formular erstellt. Sie haben Ihre Vorlage für die index() Methode innerhalb der RegisterController um die Formularfelder anzuzeigen, die der Benutzer sehen soll.

Sie können diese Änderungen sehen auf http://localhost:8081/registeroder wie im folgenden Beispiel gezeigt:

Template of registration page being displayed in a web browser

Verify-Formular erstellen

Erstellen Sie das VerifyType Formular mit dem folgenden Befehl:

docker-compose exec php bin/console make:form

Für die Eingabe des Namens: VerifyType Und für die zu verwendende Entität, wählen Sie User

Nach dem Absenden sehen Sie etwas wie in der folgenden Abbildung dargestellt:

Terminal output confirming creation of VerifyForm class

Das Erstellen des VerifyType Formular auf diese Weise zu erstellen, bedeutet, dass das System automatisch alle User Entitätseigenschaften in dieses Formular eingefügt. Keines dieser Felder ist erforderlich, also aktualisieren Sie den Code mit dem Folgenden:

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

Der obige Code definiert das Formular, das für die Übermittlung eines Prüfcodes erwartet wird. Dieses Formular enthält zwei Felder, die verificationCode und submit. Jedes Feld hat seine eigenen Attribute für die Validierung und Anzeige im Formular oder die Übermittlung des Formulars.

Am Anfang der Klasse müssen einige Klassenimporte importiert werden. Kopieren Sie also den Code oben in der Klasse, wie unten gezeigt:

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;

Handle Verifizierung

Sie haben also Ihre Registrierungsseite erstellt, aber was nun? Sie müssen eine Seite erstellen, auf der der Benutzer den Verifizierungscode eingeben kann, den er von der Verify API ERHÄLT. Innerhalb der RegisterController erstellen Sie eine neue Methode unterhalb der index() eine mit dem Namen verify(). Das folgende Beispiel zeigt dies und den Inhalt dieser Methode:

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

Fügen Sie am Anfang der Klasse die Response, VerifyType, ParamConverter, und Verification Klassen wie unten gezeigt ein:

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;

Die Vorlage für die Anzeige dieses Formulars wird nun benötigt. Erstellen Sie eine neue Datei in project/templates/register/ namens verify.html und kopieren Sie den Inhalt des unten stehenden Codeblocks in diese Datei:

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

Handhabung einer erfolgreichen Verifizierung

Innerhalb des RegisterController braucht das Projekt nun eine Erfolgsseite, wenn der Benutzer erfolgreich verifiziert wurde. Es wird eine neue registerSucess() Methode, die eine Vorlage mit der Meldung anzeigt, dass die Registrierung erfolgreich war:

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

Erstellen Sie nun eine neue Datei in project/templates/register/ mit dem Namen success.html.twig und geben Sie das Folgende aus:

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

Es sind noch ein paar kleine Änderungen erforderlich, um den Verifizierungsteil zu vervollständigen. Umleitungen sind erforderlich, wenn der Benutzer bei jeder Formularübermittlung erfolgreich auf die nächste Seite weitergeleitet wurde. Also, in RegisterControllerunter public function index()finden Sie das Folgende und fügen es hinzu:

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

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

Und innerhalb der Methode public function verify(Request $request, User $user)finden Sie folgendes und fügen es hinzu:

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

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

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

Breitengrad und Längengrad

Um registrierte Nutzer in geografischer Nähe zu anderen registrierten Nutzern finden zu können, benötigt das System bei der Registrierung den Breiten- und Längengrad jedes Nutzers. Für das Projekt wird ein Dienst namens MapQuestverwendet, der die Stadt und den Landkreis/Bundesstaat des Nutzers in den erforderlichen Breiten- und Längengrad umwandelt.

Registrieren Sie sich für einen Account auf: MapQuest.

Öffnen Sie .env.local und fügen Sie die folgenden neuen Einträge hinzu (ersetzen Sie <api_key> durch Ihren MapQuest-API-Schlüssel):

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

Die Entität User Entität benötigt zwei Eigenschaften, um den Breitengrad und den Längengrad zu speichern. Um diese Entität zu aktualisieren, führen Sie den Symfony make wie unten gezeigt aus und folgen Sie den Anweisungen:

docker-compose exec php bin/console make:entity
  • Klassenname der Entität, die erstellt oder aktualisiert werden soll (z. B. DeliciousGnome):

    • User

  • Eigenschaft 1

    • Name: latitude

    • Art: decimal

    • Genauigkeit: 20

    • Maßstab: 16

    • Kann Null sein: yes

  • Eigenschaft 2

    • Name: longtitude

    • Art: decimal

    • Genauigkeit: 20

    • Maßstab: 16

    • Kann Null sein: yes

Wenn Sie den folgenden Befehl ausführen, wird eine neue Migrationsdatei mit diesen Datenbankänderungen erstellt:

docker-compose exec php bin/console make:migration

Wenn Sie die anstehenden Datenbankänderungen sehen möchten, werden die erzeugten Migrationsdateien in project/src/Migrations/.

Wenn Sie mit diesen Änderungen zufrieden sind, führen Sie den folgenden Befehl aus, um sie in der Datenbank zu speichern:

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

Guzzle ist ein PHP-HTTP-Client, der es Projekten ermöglicht, Anfragen an andere Webdienste zu stellen, wie z.B. POST- und GET-Anfragen an APIs. Guzzle macht es einfach, eine GET-Anfrage an den API-Endpunkt von Map Quest zu stellen, um Breiten- und Längengrade des Nutzerstandorts zu erhalten.

Der nächste Schritt ist die Erstellung einer MapQuestUtil zu erstellen, um diese GET-Anfrage zu stellen und die Antwort so zu verarbeiten, dass nur die erforderlichen Felder abgerufen werden. Erstellen Sie eine neue Datei in project/src/Util/ mit dem Namen MapQuestUtil.php und kopieren Sie den folgenden Code in die neu erstellte Datei:

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

Der obige Code ist eine neue Methode, die ein User Objekt nimmt und eine Anfrage an die API von MapQuest stellt, die den Benutzern location die ihre Town und ihre County. Nach dieser Anfrage folgen mehrere Schritte, um sicherzustellen, dass ein Standort zurückgegeben wird und dass die erwarteten Felder für den Breiten- und Längengrad in die Antwort aufgenommen werden.

Zeit für den Aufruf dieser MapQuestUtil Klasse und getLatLongByAddress() Methode. Öffnen Sie die RegisterController.

Die neue Utility-Klasse muss als Dienst injiziert werden. In der __construct() Methode wird sie wie unten gezeigt aktualisiert:

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

Nun, da die MapQuestUtil injiziert wurde, müssen Sie die Funktion aufrufen und dann die zurückgegebenen Details in der User. In der index() Funktion, suchen Sie die Zeile if ($form->isSubmitted() && $form->isValid()) { und fügen Sie eine neue Zeile darunter ein. Fügen Sie das Folgende hinzu:

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

An diesem Punkt des Tutorials haben Sie ein neues Symfony-Projekt erstellt und dieses Projekt mit Ihrer Datenbank konfiguriert. Sie haben auch eine neue User Entität erstellt, die mit Ihrer user Datenbank-Tabelle abbildet. Sie haben zwei neue Formulare erstellt, ein UserType und VerifyTypeerstellt, die zur Überprüfung neuer Benutzer und der Gültigkeit des eingegebenen Verifizierungscodes verwendet werden. Sie haben auch mehrere Seiten erstellt, auf die der Benutzer zugreifen kann, a /register, /verifyund /success.

Sie befinden sich jetzt an einem natürlichen Haltepunkt in der Mitte dieses Tutorials! Sie können nun den Registrierungsprozess Ihres Projekts testen.

Gehen Sie zur die Registrierungsseite und folgen Sie den Anweisungen auf den nächsten beiden Seiten. Bitte geben Sie eine gültige Telefonnummer ein. Andernfalls können Sie Ihren Account nicht verifizieren.

Befriending

Match-Benutzer

Match-Entität erstellen

Es wird eine neue Entität benötigt, um zwei Benutzer zu verbinden, ihr Feedback zu sammeln und Datensätze von Benutzern zu speichern, die verbunden werden. Diese neue Entität speichert eine Beziehung zu den beiden Anrufern im abgeglichenen Objekt und den Namen der Telefonkonferenz (die Namen der beiden Benutzer durch '-' verkettet). Um dies zu tun, verwenden Sie die make Bibliothek wie unten gezeigt und folgen Sie den Anweisungen für die drei neuen Eigenschaften:

docker-compose exec php bin/console make:entity
  • Name: Spiel

  • 1 - callerOne - ManyToOne - Benutzer - nicht löschbar - Rest Standardwerte

  • 2 - callerTwo - ManyToOne - Benutzer - nicht löschbar - Rest Standardwerte

  • 3 - conferenceName - string - 255 - nicht löschbar

Bevor Sie die Migration durchführen, ist eine weitere erforderlich. Es gibt ein Erweiterungsbündel für Doctrine das es dem Code erlaubt, Doctrine-Erweiterungen zu nutzen, wie z.B. Timestampablezu verwenden, um zwei Zeilen in unseren Entitäten zu haben createdAt und updatedAt Felder. Um dies zu tun, installieren Sie stof/doctrine-extensions-bundle:

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

Die Konfiguration wird für dieses neue Erweiterungsbündel benötigt. öffnen stof_doctrine_extensions.yaml gefunden in: project/config/packages/ und aktualisieren Sie sie auf mirror:

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

Sie müssen einige weitere Änderungen an der neu erstellten Match Entität vornehmen. Öffnen Sie diese Klasse, die Sie unter project/src/Entity/Match.php.

Binden Sie zunächst die TimestampableEntity in das Projekt ein, indem Sie den folgenden Import hinzufügen:

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

Verwenden Sie nun die TimestampableTrait in der Match Klasse, wie unten gezeigt:

class Match
 {
+     use TimestampableEntity;

Wenn Sie den folgenden Befehl ausführen, wird eine neue Migrationsdatei mit diesen Datenbankänderungen erstellt:

docker-compose exec php bin/console make:migration

Wenn Sie die anstehenden Datenbankänderungen sehen möchten, werden die erzeugten Migrationsdateien in project/src/Migrations/.

Wenn Sie mit diesen Änderungen zufrieden sind, führen Sie den folgenden Befehl aus, um sie in der Datenbank zu speichern:

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

Ngrok konfigurieren

Ngrok ist eine plattformübergreifende Anwendung, die es Benutzern ermöglicht, ihren Entwicklungsserver dem Internet auszusetzen, ohne ihren Router konfigurieren zu müssen. Ngrok tut dies, indem es einen Tunnel zwischen Ihrem Server und der Ngrok-Subdomain erstellt.

Es ist an der Zeit, Ngrok so zu konfigurieren, dass die Vonage-Anwendung (die im nächsten Schritt erstellt wird) eine Ngrok-Subdomain hat, um ihre Webhook-Anfragen zu stellen.

Im Verzeichnis docker/ngrok/ benennen Sie die Datei ngrok.conf.local in ngrok.confund ersetzen Sie <auth_token> mit Ihrem Ngrok-Authentifizierungstoken:

Hinweis Sie finden Ihre auth token in Ihrem Dashboard auf Ngrok

authtoken: <auth_token>

Um Ihre Docker-Container mit der richtigen Ngrok-Subdomain-URL zu aktualisieren, muss Docker neu gestartet werden. Sie können Docker mit dem folgenden Befehl neu starten:

docker-compose restart

Sobald Sie Docker neu gestartet haben, gehen Sie zu https://0.0.0.0:4040und kopieren Sie die Ngrok-Subdomain, die auf der Statusseite angezeigt wird.

Ngrok Status Page

Wenn Sie nun diese Ngrok-Subdomäne nehmen und darauf zugreifen: ngroksubdomain/registerund ersetzen Sie ngroksubdomain durch Ihre Ngrok-Subdomain ersetzen, sehen Sie Ihre Registrierungsseite. Für mich war das Beispiel: https://a551f0297ed8.ngrok.io/register.

Bitte notieren Sie sich diese Ngrok-Subdomain, denn sie wird als nächstes für die Konfiguration Ihrer Vonage-Anwendung benötigt.

Erstellen einer Vonage-Anwendung

Melden Sie sich bei Ihrem Vonage Dashboard. Links auf der Seite finden Sie einen Link Your Applicationsklicken Sie auf diesen. Sobald die Seite geladen ist, klicken Sie auf die Create a new Application Schaltfläche.

Legen Sie den Application NameFür dieses Tutorial habe ich den Namen auf Befriending Servicefestgelegt, aber Sie können ihn beliebig ändern.

Klicken Sie auf die Schaltfläche Generate public and private key. Ihr Browser wird einen Download für die private.key Datei. Speichern Sie diese in Ihrem project/ Verzeichnis.

Unter Capabilitiesschalten Sie ein Voice.

Aktualisieren Sie alle drei Eingabefelder, die Sie jetzt sehen, mit den Webhook-URLs, die von Vonage für Rückrufe bei Telefonaten benötigt werden. Zum Beispiel für das Textfeld Ereignisse: https://a551f0297ed8.ngrok.io/webhooks/eventsErsetzen Sie a551f0297ed8 durch Ihre Subdomain, die Sie im obigen Schritt erstellt haben. Achten Sie darauf, dies für alle drei Eingabefelder zu tun:

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

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

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

Klicken Sie schließlich auf Generate new application.

Die Seite, die nun geladen wird, zeigt Ihre Application ID. Notieren Sie sich dies!

In Ihrem project/ Verzeichnis, öffnen Sie .env.local und fügen Sie die folgenden zwei Zeilen ein, wobei Sie darauf achten, dass Sie <application id> durch Ihre Anwendungs-ID und <ngrok_url> durch Ihre Ngrok-URL:

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>

Sie haben nun eine Vonage-Anwendung erstellt und die Anmeldedaten in Ihrem Projekt gespeichert. Durch das Speichern dieser Anmeldeinformationen kann das Projekt nun Anrufe tätigen und entgegennehmen.

Vonage Call Util

Erstellen Sie eine neue Datei in project/src/Util/ mit dem Namen 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;
    }
}

Diese Dienstleistungsklasse tut derzeit nichts anderes als zwei neue Dienste bei der Erstellung zu injizieren. Diese Dienste sind die VonageClientder den Teil darstellt, der die Vonage Voice API nutzt, um Anrufe zu tätigen und zu empfangen. Der andere Dienst, der injiziert wird, ist der VonageVerifyUtilder aufgerufen wird, wenn das Projekt bestimmte Methoden innerhalb dieser Utility benötigt.

Die Utility-Klasse wird verwendet, um den Anruf zwischen zwei Benutzern zu initiieren. Die erste Methode, die benötigt wird, ist also 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;
}

Der obige Code ist eine Methode, die zwei Benutzer als Argumente dieser Methode annimmt. Die Methode erzeugt dann ein neues Objekt der Match Entität als Verbindung zwischen den beiden Benutzern. Es wird ein Nexmo Call Control Object (NCCO) Array erstellt, das eine Reihe von Anweisungen für Vonage Voice enthält, um zu wissen, was zu erwarten ist und welche Schritte für die Durchführung des Anrufs erforderlich sind.

Dieses Array enthält zwei weitere Array-Elemente. Das erste ist die Verwendung der Persona Amy zu verwenden, um den Benutzer in den Anruf einzuführen und zu erklären, worum es in dem Telefonat geht. Amy fragt den Benutzer, ob er an einer Telefonkonferenz teilnehmen möchte oder nicht und fordert ihn auf, 1 für ja oder 2 für nein einzugeben. Das zweite Array leitet den Benutzer zum nächsten Webhook-Endpunkt weiter, der der Konferenz beitreten soll.

Sie haben vielleicht bemerkt, dass es zwei Aufrufe zu einer makeCall() Methode gibt, die Sie noch nicht erstellt haben. Erstellen Sie diese neue Methode, indem Sie den folgenden Code in Ihre 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) {

    }
}

Diese Methode ruft zunächst eine andere Methode auf, getInternationalizedNumber()auf, die sich in der Datei vonageVerifyUtil. Sie nimmt die $caller die eine Instanz von User ist, und wandelt die Landesvorwahl und die Telefonnummer in eine Internationalized Nummer, die Vonage anrufen kann.

Der nächste Schritt in der obigen Methode ist die Initialisierung des Aufrufs durch Setzen von a to, from und Einfügen des ncco Objekts.

Schließlich müssen in diese VonageCallUtil müssen mehrere Klassen eingefügt werden, da sie in den obigen Beispielen verwendet werden. Kopieren Sie die im folgenden Beispiel gezeigten Ergänzungen in Ihre Datei:

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;

Zusammenfassend lässt sich sagen, dass die obige Funktion noch nicht aufgerufen wird, aber sie erstellt ein neues Match-Objekt, speichert zwei Benutzer darin und führt dann zwei Anrufe für jeden Benutzer durch. Es wird noch keine Konferenz gebildet oder eine Möglichkeit, die beiden Anrufe miteinander zu verbinden. Die Konferenzschaltung wird als nächstes durchgeführt!

Passende Benutzer

Da das Projekt tägliche Aufrufe haben soll, wird ein Befehl benötigt, der als Cronjob ausgeführt wird. In Ihrem Terminal, innerhalb des docker/ Verzeichnis mit Hilfe der make Bibliothek, um einen neuen Befehl zu erstellen:

docker-compose exec php bin/console make:command

Wenn die Bibliothek nach einem Namen für Ihren neuen Befehl fragt, geben Sie ein app:match-users.

Wenn Sie diesen Befehl ausführen, wird eine neue Datei mit dem Namen MatchUsersCommand.php innerhalb: project/src/Command/MatchUsersCommand.php.

Öffnen Sie die neu erstellte MatchUsersCommand.php Datei.

Der von Ihnen erstellte Befehl enthält einige Standardeinstellungen, von denen Sie die meisten für dieses Projekt nicht benötigen. Suchen Sie protected function configure() und ersetzen Sie den Inhalt dieser Funktion durch:

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

Zwei Dienste müssen in die Klasse über die __construct() Methode. Die beiden erforderlichen Dienste sind die EntityManagerInterfaceder es dem Code ermöglicht, Datenbankabfragen durchzuführen oder auf die Repository-Methoden für Ihre Entitäten zuzugreifen. Der zweite benötigte Dienst ist der VonageCallUtil um unsere zuvor erstellte Funktionalität aufzurufen, die zwei Benutzer aufruft und sie miteinander verbindet.

Aktualisieren Sie den folgenden Code innerhalb der MatchUsersCommand.php Datei so, dass er mit dem unten gezeigten übereinstimmt:

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

Jetzt ist es an der Zeit, mit dem Aufbau der Befehlsfunktionalität zu beginnen!

Damit die Abfrage funktioniert, benötigt das Projekt einige Doctrine Extensions, die Sie im nächsten Schritt schreiben werden. Diese Erweiterungen stammen aus einer Drittanbieter-Bibliothek von Benjamin Eberlei. Um diese Bibliothek zu installieren, führen Sie in Ihrem Terminal den folgenden Befehl aus:

docker-compose exec php composer require beberlei/doctrineextensions

Aktivieren Sie nun die erforderlichen Erweiterungen. Öffnen Sie project/config/packages/doctrine.yamlund mit der gleichen Einrückung von auto_generate_proxy_classes, naming_strategy, auto_mapping, und mappings hinzufügen:

Anmerkung: Bitte achten Sie auf die Einrückung, um sicherzustellen, dass das YAML-Format korrekt ist.

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

Zurück in Ihrem MatchUsersCommand finden Sie die execute() Funktion. Diese Funktion ist der Ort, wo die ganze Arbeit für den Befehl passiert.

Löschen Sie zunächst den gesamten Inhalt dieser Methode.

Der erste Teil, der in dieser Methode benötigt wird, ist das Vorhandensein einer Instanz der Match und User Repositories in Variablen einzutragen und alle als aktiv gekennzeichneten Benutzer zu ermitteln:

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

Danach ist es an der Zeit, eine Schleife durch alle aktiven Benutzer zu ziehen, um Anrufe zu initiieren und sie mit einem anderen Benutzer zu verbinden. Fügen Sie die folgende Schleife hinzu:

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

}

Nun muss das System Benutzer in der Nähe des aktuellen Standortes finden $callerOne um sie zu verbinden. Die Abfrage ist recht umfangreich und gehört in die UserRepository. Öffnen Sie die Datei project/src/Repository/UserRepository.php

Unterhalb der __construct Methode fügen Sie die folgende neue Methode hinzu:

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

Die Methode im obigen Code-Block nimmt ein Benutzerobjekt, ein Array der aktuell aktiven Benutzer und eine Integer-Variable mit einem vorgegebenen Abstand.

Diese Methode erstellt eine neue Abfrage, die die Entfernung aller Benutzer vom angegebenen Benutzer berechnet und sicherstellt, dass sie alle aktiv und verifiziert sind. Die letzte Prüfung besteht darin, sicherzustellen, dass die zurückgegebenen Benutzer innerhalb der angegebenen Entfernung des übergebenen Benutzers liegen.

Zurück im Inneren der MatchUsersCommand finden Sie die Zeile: foreach ($activeUsers as $activeUser) { und fügen Sie Folgendes hinzu:

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

Der Befehl wird erstellt, um Benutzer zu finden, die zusammengeführt werden sollen, und den Anruf einzuleiten. Es werden jedoch Webhook-URLs benötigt, um den Anruf von der automatischen Einleitung bis zur Verbindung der Benutzer zu einer Telefonkonferenz zu führen.

Webhaken

Sie benötigen nun ein WebhooksControlleralso verwenden Sie in Ihrem Terminal die make Bibliothek, um den folgenden Befehl auszuführen:

docker-compose exec php bin/console make:controller

Wo es heißt: Choose a name for your controller class (e.g. AgreeableGnomeController): eingeben WebhooksController ein und drücken Sie die Eingabetaste.

Sie haben nun zwei neue Dateien erstellt:

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

Öffnen Sie das neu erstellte WebhooksController.php gefundene Datei in project/src/Controller/

Dieser Controller wird drei Dienste in Anspruch nehmen müssen, VonageVerifyUtil, VonageCallUtil, und EntityManagerInterface. Beginnen Sie also damit, sie in das Konstrukt des Controllers zu injizieren, wie unten gezeigt:

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

Die beiden folgenden Methoden tun das, was ihr Name sagt. Die erste findet einen Benutzer anhand seiner Telefonnummer, während die zweite eine Match Instanz anhand einer User die Sie heute erstellt haben. Fügen Sie diese zu Ihrer 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()));
}

Eine Methode, die im obigen Beispiel aufgerufen wird, existiert noch nicht, nämlich die Suche nach Übereinstimmungen mit dem Benutzer und dem aktuellen Datum. Öffnen Sie project/src/Repository/MatchRepository.php und fügen Sie innerhalb dieser Klasse die unten gezeigte Methode hinzu:

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

Fügen Sie am Anfang der Datei den Import für die Entität User:

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

Die obige Abfrage findet einen Match Eintrag, bei dem der Benutzer entweder callerOne oder callerTwo ist und der Match heute erstellt wurde. Hier gibt es eine Abfragefunktion, die Doctrine nicht kennt. Date(m.createdAt). Bei der Berechnung der Entfernung zwischen Personen haben Sie beberlei/doctrineextensions. Diese Bibliothek hat die Funktionalität, um die Date Erweiterung. Öffnen Sie also project/config/packages/doctrine.yaml und fügen Sie die folgenden zwei Zeilen ein:

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

Zuvor haben Sie eine Methode namens createConferenceBetween()erstellt, innerhalb dieser Methode war ein NCCO Array, das auf eine URL von /webhooks/joinConferenceWenn Sie das Beispiel jetzt ausführen würden, würde nichts passieren, da die URL nicht existiert. Erstellen Sie eine neue joinConference() Methode innerhalb Ihrer WebhooksController mit der Annotation, die diese URL enthält.

Die Methode hat zwei Aufgaben. Der erste besteht darin, die Eingaben des Benutzers zu überprüfen und sicherzustellen, dass er nur gültige Optionen eingibt, bevor er fortfährt. Diese Eingaben sind 1 für Ja und 2 für Nein.

Der zweite Zweck dieser Methode besteht darin, die Eingaben des Benutzers zu berücksichtigen. Wenn er sich für die Teilnahme an dem Anruf entscheidet, wird er zur nächsten Aktion weitergeleitet, die ihn mit der Telefonkonferenz verbindet. Wählt der Benutzer die Option "2", um nicht an einem Gespräch teilzunehmen, bestätigt das System den Wunsch, nicht an einem Gespräch teilzunehmen, und beendet das Gespräch.

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

Hier werden vier Klassen verwendet, die wir nicht in unsere Datei aufgenommen haben, Request, Match, User, und JsonResponse. Fügen Sie diese oberhalb der Klasse ein:

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

An dieser Stelle haben Sie nun ein Benutzerregistrierungsformular, das den Benutzer durch den Verifizierungsprozess führt. Sie haben auch einen Befehl, der, wenn er ausgeführt wird, eine Schleife durch alle aktiven und verifizierten Benutzer durchläuft und sie mit anderen aktiven und verifizierten Benutzern innerhalb eines Radius von maximal 100 Meilen verbindet.

Sie können dies jetzt testen! Wenn Sie zwei Telefonnummern zur Verfügung haben, können Sie sich auf Ihrer Registrierungsseite. Vergewissern Sie sich, dass der Standort, den Sie für beide Benutzer eingegeben haben, nicht weiter als 30 Meilen voneinander entfernt ist.

Sobald Sie die Benutzer registriert haben, führen Sie in Ihrem Terminal im Verzeichnis docker/ Verzeichnis den folgenden Befehl aus:

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

Beide Numbers erhalten einen Anruf und werden gefragt, ob sie ein Gespräch mit einer anderen Person führen möchten!

Feedback einholen

Ein zweiter Befehl ist erforderlich, um die Rückmeldungen der einzelnen Benutzer abzurufen. Mit diesem Befehl werden die Rückmeldungen zu den Anrufen des aktuellen Tages von jedem Benutzer gesammelt. Dieser Befehl wird auch ein Cronjob sein, der einige Stunden nach Beendigung des entsprechenden Anrufs ausgeführt wird.

Bevor Sie jedoch diesen neuen Befehl ausführen, muss die Match Entität aktualisiert werden, da Sie bei der Erstellung der Match Entität mit der make Bibliothek erstellt haben, können Sie sie auch mit neuen Eigenschaften aktualisieren. Führen Sie den folgenden Befehl in Ihrem Terminal aus und folgen Sie den Anweisungen der einzelnen Schritte unten:

docker-compose exec php bin/console make:entity
  • Klassenname der Entität, die erstellt oder aktualisiert werden soll (z. B. DeliciousGnome):

    • Match

  • Eigenschaft 1

    • Name: callerOneFeedbackAccepted

    • Art: boolean

    • Kann Null sein: yes

  • Eigenschaft 2

    • Name: callerTwoFeedbackAccepted

    • Art: boolean

    • Kann Null sein: yes

  • Eigenschaft 3

    • Name: callerOneCallSuccessful

    • Art: boolean

    • Kann Null sein: yes

  • Eigenschaft 4

    • Name: callerTwoCallSuccessful

    • Art: boolean

    • Kann Null sein: yes

Die aktualisierte Entität spiegelt nicht den aktuellen Stand in der Datenbank wider. Damit die Datenbank widerspiegelt, was Sie in der Match Entität definiert haben, führen Sie den folgenden Befehl aus, um eine neue Migrationsdatei zu erzeugen:

docker-compose exec php bin/console make:migration

Wenn Sie die anstehenden Datenbankänderungen sehen möchten, werden die erzeugten Migrationsdateien in project/src/Migrations/.

Wenn Sie mit diesen Änderungen zufrieden sind, führen Sie den folgenden Befehl aus, um sie in der Datenbank zu speichern:

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

Zunächst müssen Sie eine Liste der heutigen Spiele erstellen. Also fügen Sie in Ihrer MatchRepository eine neue Funktion ein, die die Datenbank abfragt, um alle Übereinstimmungen für den aktuellen Tag zu finden. Diese Abfrage findet nur übereinstimmende Benutzer, bei denen das callerOneCallSuccessful Flagge leer ist:

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

Nun ist es an der Zeit, den neuen Befehl, der die neuen Änderungen nutzt, in Ihrem Terminal einzugeben:

docker-compose exec php bin/console make:command

Wenn es nach einem Befehlsnamen fragt, geben Sie ein: app:get-feedback. Dieser Befehl erzeugt eine neue Befehlsdatei in project/src/command/ mit dem Namen . GetFeedbackCommand.php Öffnen Sie die neu erstellte Datei.

Dieser Controller benötigt zwei Dienste, die über die __construct() Methode. Die gleichen zwei Dienste, die in die Methode MatchUsersCommand. Kopieren Sie die unten gezeigten Anpassungen in Ihre 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();
+    }

Durch die oben genannten Änderungen hat Ihr Befehl nun Zugriff auf die VonageCallUtil und EntityManagerInterface.

Aktualisieren Sie den Inhalt von configure() mit dem im folgenden Beispiel gezeigten Inhalt. In diesem Beispiel wird die Beschreibung des Befehls festgelegt:

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

Nun ist es an der Zeit, die Funktion zu implementieren, mit der das Sammeln von Rückmeldungen von angepassten Benutzern eingeleitet wird. Ersetzen Sie den Inhalt der Methode execute() mit:

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

Die obige Funktion sammelt die Übereinstimmungen für den Tag, geht dann in einer Schleife durch jede Übereinstimmung und initiiert einen Anruf mit Anrufer eins und Anrufer zwei, um von beiden ein Feedback zu ihrem vorherigen Anruf zu erhalten.

Wie Sie sehen können, haben Sie einen Aufruf an makeFeedbackCall() aus der VonageCallUtilaufgerufen, aber die Methode existiert noch nicht. Öffnen Sie also die VonageCallUtil innerhalb von project/src/util/VonageCallUtil.php und fügen Sie eine neue Methode hinzu:

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

Das obige Beispiel verweist auf eine eventUrl die Ihre Ngrok-URL ist und /webhooks/userFeedback. Dieser Endpunkt existiert nicht in unserer WebhooksController noch nicht.

Die Methode hat zwei Aufgaben. Der erste besteht darin, die Eingaben des Benutzers zu überprüfen und sicherzustellen, dass er nur gültige Optionen eingibt, bevor er fortfährt. Diese Eingaben sind 1 für Ja und 2 für Nein.

Der zweite Zweck dieser Methode besteht darin, die Eingaben des Benutzers zu respektieren. Wenn er sich dafür entscheidet, Feedback zu geben, wird er zur nächsten Aktion weitergeleitet, d. h. zur ersten Feedback-Frage. Wählt der Benutzer "2", also "kein Feedback geben", bestätigt das System die Bitte, kein Feedback zu geben, und beendet den Anruf.

Also in der WebhooksController eine neue Methode namens getUserFeedback() wie unten gezeigt:

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

Das obige Beispiel verweist auf eine eventUrl die Ihre Ngrok-URL ist und /webhooks/userFeedbackCallSuccess. Dieser Endpunkt existiert nicht in unserer WebhooksController noch nicht.

Die Methode hat zwei Aufgaben. Der erste besteht darin, die Eingaben des Benutzers zu überprüfen und sicherzustellen, dass er nur gültige Optionen eingibt, bevor er fortfährt. Diese Eingaben sind 1 für Ja und 2 für Nein.

Der zweite Zweck dieser Methode besteht darin, die Eingaben des Benutzers zu berücksichtigen. Wenn der Benutzer entweder "1" oder "2" wählt, wird die Eingabe des Benutzers entweder in der Eigenschaft setCallerOneCallSuccessful oder der Eigenschaft setCallerTwoCallSuccessful als "true" oder "false" (unabhängig davon, ob der Benutzer den Aufruf genossen hat oder nicht).

Nach erfolgreicher Optionseingabe leitet der Code den Benutzer zur nächsten Aktion des Aufrufs weiter, nämlich zum Endpunkt /webhooks/userFeedbackContinue

Also in der WebhooksController eine neue Methode namens getUserFeedbackCallSuccess() wie unten gezeigt:

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

Das obige Beispiel verweist auf eine eventUrl die Ihre Ngrok-URL ist und webhooks/userFeedbackContinue. Dieser Endpunkt existiert nicht in unserer WebhooksController noch nicht.

Die Methode hat zwei Aufgaben. Der erste besteht darin, die Eingaben des Benutzers zu überprüfen und sicherzustellen, dass er nur gültige Optionen eingibt, bevor er fortfährt. Diese Eingaben sind 1 für Ja und 2 für Nein.

Der zweite Zweck dieser Methode besteht darin, die Eingaben des Benutzers zu berücksichtigen. Wenn er "1" wählt, bleibt er für einen Anruf am nächsten Tag für den Dienst angemeldet.

Wählt der Benutzer '2', so werden sie auf inaktiv gesetzt und nicht mehr aufgerufen.

Der Anruf endet nach erfolgreicher Eingabe der Option.

Also in der WebhooksController eine neue Methode namens getUserFeedbackContinue() wie unten gezeigt:

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

Die letzte Ergänzung, die für das Projekt erforderlich ist, ist eine Funktion, deren Aufruf Sie vielleicht schon bemerkt haben isFirstOrSecondCaller() aber erstellt werden muss. Diese Methode nimmt eine Instanz von Match und ein einzelnes User Objekt; sie bestimmt dann, ob der angegebene Benutzer callerOne oder callerTwo des Match. Fügen Sie dies am Ende Ihrer Klasse hinzu:

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

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

    return null;
}

Schlussfolgerung

Wenn Sie dieses Tutorial von Anfang bis Ende verfolgt haben, haben Sie nun ein Projekt aus einer frischen Symfony-Installation erstellt.

Dieses neue Projekt verfügt über eine Registrierungsseite, die nach dem Absenden eine Verifizierungsanfrage an Vonage Verify sendet. Diese Anfrage löst eine SMS für die Besitzer der Telefonnummer bei der Registrierung aus. Der in der SMS enthaltene Verifizierungscode muss auf der nächsten Seite, die der Benutzer sieht, eingegeben werden. Nach erfolgreicher Verifizierung wird der Nutzer auf eine Erfolgsseite weitergeleitet, auf der er erfährt, wann er mit Anrufen rechnen kann.

Der zweite Teil dieses Projekts ist die Voice-Funktion von Vonage. Mit Hilfe von Befehlen werden die Benutzer zusammengeführt, angerufen und zu einer Telefonkonferenz verbunden. Später am Tag wird ein zweiter Befehl ausgeführt, um ein Feedback von jeder Person anzufordern, die am aktuellen Tag gefunden wurde.

Der fertige Code für dieses Tutorial befindet sich im end-tutorial-Zweig auf diesem GitHub-Repository.

Im Folgenden finden Sie einige weitere Tutorials, die wir zur Implementierung von Vonage Verify oder Voice in Projekte geschrieben haben:

Vergessen Sie nicht, wenn Sie Fragen, Ratschläge oder Ideen haben, die Sie mit der Community teilen möchten, dann können Sie sich gerne in unserem Slack-Arbeitsbereich der Gemeinschaft. Ich würde mich freuen, von allen zu hören, die dieses Tutorial umgesetzt haben und wie ihr Projekt funktioniert.

Teilen Sie:

https://a.storyblok.com/f/270183/250x250/b052219541/greg-holmes.png
Greg HolmesVonage Ehemalige

Ehemaliger Ausbilder für Entwickler @Vonage. Kommt von einem PHP-Hintergrund, ist aber nicht auf eine Sprache beschränkt. Ein begeisterter Gamer und Raspberry-Pi-Enthusiast. Oft beim Bouldern in Kletterhallen anzutreffen.