
Teilen Sie:
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.
Wie man eine Rufbereitschaftsanwendung mit React Native und Symfony erstellt
Lesedauer: 35 Minuten
Sind Sie ein Entwickler? Waren Sie schon einmal auf Abruf und mussten eine dieser lästigen Apps installieren, die Sie benachrichtigen, wenn etwas nicht in Ordnung ist? Der Schwellenwert für Fehler ist überschritten, oder der Server braucht zu lange, um zu antworten? Wenn ja, haben Sie schon einmal gedacht: "So einen Dienst würde ich gerne selbst entwickeln"? In diesem Tutorial lernen Sie die Grundlagen für die Entwicklung einer solchen Anwendung und die Nutzung von Vonage für die Kommunikation.
Dieses Tutorial wird Ihnen helfen, den Anfang einer API in PHP mit Symfony und die mobile Anwendung mit React Native.
Den kompletten Code für dieses Tutorial finden Sie in unserem: Community Repository. Checken Sie unbedingt in den end-tutorial Zweig.
Voraussetzungen
Für die Durchführung dieses Tutorials benötigen Sie Folgendes:
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.
Klonen des Repositorys
Erstellung der API
JWT-Schlüsselpaar generieren
In diesem Projekt wird eine in React Native erstellte mobile Anwendung verwendet.
Sie müssen den Benutzer zwischen der mobilen Anwendung und der API authentifizieren. Dieses Projekt verwendet JWT, um die Authentifizierung zu handhaben, daher müssen Zertifikate generiert werden, um die JWT-Tokens zu erstellen.
Führen Sie im Stammverzeichnis Ihres Projekts die folgenden drei Befehle aus:
Ihre Anwendung für das Internet freigeben
Um mit Vonage zu telefonieren, benötigen Sie eine virtuelle Rufnummer. Außerdem müssen Sie einen Webhook einrichten, um die Ereignisse zu protokollieren, die auftreten, wenn ein Telefonanruf getätigt, angenommen, abgewiesen oder beendet wird.
Für dieses Lernprogramm, ngrok der Dienst der Wahl, um die Anwendung mit dem Internet zu verbinden. Installieren Sie ngrok, und führen Sie den folgenden Befehl in einem neuen Terminalfenster aus:
Stellen Sie sicher, dass Sie Ihre ngrok-HTTPS-URL kopieren, da Sie diese später für die Konfiguration des Projekts benötigen.
Umgebungsvariablen
Innerhalb des Verzeichnisses Docker Verzeichnis befindet sich eine Datei namens .env.dist; kopieren Sie diese Datei oder benennen Sie sie um in .env.
Die ersten Felder, die Sie aktualisieren müssen, sind Ihre Datenbankanmeldedaten. Das folgende Beispiel zeigt die Anmeldedaten, die ich für dieses Tutorial verwendet habe, aber bitte verwenden Sie sicherere.
Aktualisieren Sie die Werte sowohl für VONAGE_API_KEY= und VONAGE_API_SECRET=, die Sie im Vonage Entwickler Dashboard.
Navigieren Sie dann im Dashboard zu "Your Applications". Erstellen Sie eine neue Applikation und stellen Sie sicher, dass Sie die private.key Datei in das Stammverzeichnis des Projekts herunterzuladen und sicherzustellen, dass Ihre Anwendung über Sprachfunktionen verfügt.
Wenn Sie die Voice API verwenden, müssen Sie die Ereignis-Webhook-URL festlegen. Setzen Sie diese auf die ngrok-HTTPS-URL, die Sie im letzten Abschnitt kopiert haben.
Aktualisieren Sie die folgenden zwei:
Als Nächstes verknüpfen Sie Ihre zuvor erworbene virtuelle Vonage-Nummer mit Ihrer Anwendung. Aktualisieren Sie dann in Ihrem Code das Folgende innerhalb der .env Datei innerhalb Docker:
Suchen Sie schließlich ON_CALL_NUMBER= in derselben Datei, und fügen Sie Ihre Telefonnummer zu diesem Wert hinzu. Es muss sich um eine echte Nummer handeln, die SMS-Nachrichten und Sprachanrufe empfangen kann.
Docker starten
Führen Sie die folgenden fünf Befehle aus - die Kommentare auf der rechten Seite beschreiben, was sie bewirken:
Zeit für die Erstellung der API!
Datenbank-Entitäten erstellen
Es gibt drei neue Datenbanktabellen für dieses Projekt. Alerts, OnCallund eine Tabelle zur Verknüpfung von Warnungen und Benutzern, UserAlerts.
Um zu beginnen, führen Sie den unten stehenden Befehl aus und befolgen Sie die unten stehenden Anweisungen zur Eingabe:
Bitte fügen Sie für jedes Feld Folgendes hinzu:
Name der Klasse: Alert
Eigenschaftsname: title (String, 255, Not null)
Eigenschaftsname: Beschreibung (String, 255, Not null)
Eigenschaftsname: status (String, 255, Not null)
Wenn der Befehl abgeschlossen ist, öffnen Sie die neue Datei: src/Entity/Alert.php
Es gibt drei weitere Klassen, die in dieser neuen Entity-Datei verwendet werden. Fügen Sie diese Importe am Anfang der Datei hinzu:
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Gedmo\Timestampable\Traits\TimestampableEntity;Eine dieser neuen Klassen ist die TimestampableEntity, die die created_at und updated_at Felder zur Datenbank hinzufügt. Fügen Sie use TimestampableEntity; an den Anfang der Klasse, wie unten gezeigt:
class Alert
{
use TimestampableEntity;Wir müssen der Klasse einige Standardwerte hinzufügen. Erstellen Sie also ein neues Konstrukt und geben Sie die Werte wie unten gezeigt vor:
public function __construct()
{
$this->status = 'raised';
$this->createdAt = new \DateTime();
$this->updatedAt = new \DateTime();
}Wenn wir schon in dieser Klasse sind, fügen Sie die beiden folgenden Funktionen hinzu.
Die Funktion getUserAssigned() ermittelt den Benutzer, der aktuell für die Meldung verantwortlich ist. Die zweite Funktion, toArray()wandelt die Werte der Klasse in ein Array um, das für die API-Antworten bereit ist.
public function getUserAssigned(): ?User
{
if ($this->getUserAlerts()->isEmpty()) {
return null;
}
return $this
->getUserAlerts()
->first()
->getUser();
}
public function toArray()
{
return [
'id' => $this->getId(),
'title' => $this->getTitle(),
'description' => $this->getDescription(),
'status' => $this->getStatus(),
'dateRaised' => $this->getCreatedAt()->format('Y-m-d H:i:s'),
'assigned' => $this->getUserAssigned()->getName(),
'incidentId' => $this->getId()
];
}Um die Entität OnCall Entität zu erstellen, die wir verwenden, um zu speichern, welche Person jede Woche Bereitschaft hat, führen Sie den folgenden Befehl aus und folgen Sie den Anweisungen für die Eingabe wie aufgeführt:
Bitte fügen Sie für jedes Feld Folgendes hinzu:
Klassenname: OnCall
Eigenschaftsname: user (relation, User, ManyToOne, Not null, Add Property to User Yes)
Eigenschaftsname: startDate (datetime, Not null)
Eigenschaftsname: endDate (datetime, Not null)
Wenn der Befehl abgeschlossen ist, öffnen Sie die neue Datei: src/Entity/OnCall.php
Es gibt eine weitere Klasse, die in dieser neuen Entitätsdatei verwendet wird. Fügen Sie diesen Import am Anfang der Datei hinzu:
use Gedmo\Timestampable\Traits\TimestampableEntity;Eine dieser neuen Klassen ist die TimestampableEntity, die die created_at und updated_at Felder zur Datenbank hinzufügt, fügen Sie use TimestampableEntity; am Anfang der Klasse wie unten gezeigt:
class OnCall
{
use TimestampableEntity;Wir müssen der Klasse einige Standardwerte hinzufügen. Erstellen Sie also ein neues Konstrukt und geben Sie die Werte wie unten gezeigt vor:
public function __construct()
{
$this->createdAt = new \DateTime();
$this->updatedAt = new \DateTime();
}Um die Entitäten User und Alert miteinander zu verknüpfen, müssen Sie eine neue Entität namens UserAlert. Folgen Sie den nachstehenden Anweisungen:
Klassenname: UserAlert
Eigenschaftsname: user (relation, User, ManyToOne, Not null, Add Property to User Yes)
Eigenschaftsname: alert (relation, Alert, ManyToOne, Not null, Add Property to Alert yes)
Eigenschaftsname: smsSentAt (datetime, null)
Eigenschaftsname: voiceSentAt (datetime, null)
Wenn der Befehl abgeschlossen ist, öffnen Sie die neue Datei: src/Entity/UserAlert.php
Es gibt eine weitere Klasse, die in dieser neuen Entitätsdatei verwendet wird. Fügen Sie diesen Import am Anfang der Datei hinzu:
use Gedmo\Timestampable\Traits\TimestampableEntity;Eine dieser neuen Klassen ist die TimestampableEntity, die die created_at und updated_at Felder zur Datenbank hinzufügt, fügen Sie use TimestampableEntity; am Anfang der Klasse wie unten gezeigt:
class UserAlert
{
use TimestampableEntity;Wir müssen der Klasse einige Standardwerte hinzufügen. Erstellen Sie also ein neues Konstrukt und geben Sie die Werte wie unten gezeigt vor:
public function __construct()
{
$this->createdAt = new \DateTime();
$this->updatedAt = new \DateTime();
} Führen Sie die Migrationen durch!
Nun ist es an der Zeit, die Migrationen durchzuführen und neue Tabellen und Spalten in Ihrer Datenbank anzulegen, um die neu erstellten Entitäten zu berücksichtigen.
Führen Sie in Ihrem Terminal aus:
DataFixtures erstellen
Wir müssen einige vordefinierte Vorrichtungen für die OnCall Datenbanktabelle erstellen, um festzustellen, wer zu einer bestimmten Zeit Bereitschaft hat. Führen Sie dazu den folgenden Befehl aus und befolgen Sie die aufgeführten Anweisungen:
Die Eingabe des Namens OnCallFixtures wird eine Datei innerhalb von API/src/DataFixtures mit dem Namen OnCallFixtures.php. Ersetzen Sie den Inhalt dieser Datei durch den folgenden Text:
<?php
namespace App\DataFixtures;
use App\Entity\OnCall;
use Carbon\CarbonImmutable;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Persistence\ObjectManager;
class OnCallFixtures extends Fixture implements DependentFixtureInterface
{
public function load(ObjectManager $manager)
{
$currentWeek = CarbonImmutable::now();
$onCall = new OnCall();
$onCall
->setUser($this->getReference('user_1'))
->setStartDate($currentWeek->startOfWeek())
->setEndDate($currentWeek->endOfWeek());
$manager->persist($onCall);
$manager->flush();
}
public function getDependencies(): array
{
return [
UserFixtures::class,
];
}
}Führen Sie Ihre Vorrichtungen aus, damit wir einen Benutzer und einen Datensatz für den Bereitschaftsdienst haben! Führen Sie in Ihrem Terminal aus:
Formular erstellen
Bei der Bearbeitung einer API-Anfrage zum Auslösen eines Alarms müssen wir die Eingabe validieren, um sicherzustellen, dass sie den Erwartungen entspricht. Mit Symfony ist es am einfachsten, dies mit einem Formular zu tun. Mit einem Formular können wir definieren, welche Werte wir erwarten und welche Beschränkungen für diese Werte gelten. Beginnen Sie mit der Ausführung des folgenden Befehls:
Folgen Sie den Anweisungen, wie in der Abbildung gezeigt:

Öffnen Sie nun die neu erstellte AlertType.php Datei, die sich in src/Form/ und ersetzen Sie den Inhalt der Datei durch:
<?php
namespace App\Form;
use App\Entity\Alert;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\Length;
class AlertType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class, [
'required' => true,
'constraints' => [
new Length(['min' => 5]),
new NotBlank()
]
])
->add('description', TextType::class, [
'required' => true,
'constraints' => [
new Length(['min' => 5]),
new NotBlank()
]
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Alert::class,
'csrf_protection' => false,
]);
}
}Der neue Code, den Sie der Klasse AlertType Klasse hinzugefügt haben, fügt weitere Einschränkungen und Anforderungen für die beiden Felder in diesem Formular hinzu, title und descriptionum sicherzustellen, dass sie eine Mindestlänge haben und nicht leer sind.
Bauen Sie eine Vonage Util
Eine Utility-Klasse wird benötigt, um Vonage-API-Anfragen beim Senden von SMS-Nachrichten und beim Tätigen von Sprachanrufen zu bearbeiten.
Unter API/srcerstellen Sie ein neues Verzeichnis namens Utilsowie eine neue Datei in diesem neuen Verzeichnis mit dem Namen VonageUtil.php
Sie haben Ihre Vonage-Anmeldedaten bereits in der Datei .env Datei gespeichert, und Sie werden diese in dieser neuen PHP-Klasse verwenden.
Fügen Sie in der neuen Datei den folgenden Code ein:
<?php
namespace App\Util;
use Vonage\Client;
use Vonage\SMS\Message\SMS;
use Vonage\Voice\Endpoint\Phone;
use Vonage\Voice\NCCO\NCCO;
use Vonage\Voice\NCCO\Action\Talk;
use Vonage\Voice\OutboundCall;
class VonageUtil
{
/**
* @var Client
*/
protected $client;
public function __construct(Client $client)
{
$this->client = $client;
}
}Im Moment initialisiert dieser Code eine neue PHP-Klasse und erstellt einen neuen Client für die Vonage API, wobei der Vonage Symfony Wrapper für das PHP SDK verwendet wird.
Als Nächstes fügen Sie in dieser Klasse zwei neue Funktionen hinzu, die die Anfrage an die API zum Senden einer SMS oder zum Tätigen eines Sprachanrufs bearbeiten. Fügen Sie die folgenden beiden hinzu:
public function sendSms(string $to, string $from, string $text): bool
{
$response = $this->client->sms()->send(
new SMS($to, $from, $text)
);
$message = $response->current();
if ($message->getStatus() == 0) {
return true;
}
return false;
}
public function makePhoneCall(string $to, string $from, string $text)
{
$outboundCall = new OutboundCall(
new Phone($to),
new Phone($from)
);
$ncco = new NCCO();
$ncco->addAction(new Talk($text));
$outboundCall->setNCCO($ncco);
$this->client->voice()->createOutboundCall($outboundCall);
} Erstellen Sie den Webhook-Controller
Bevor wir den Controller erstellen, benötigen wir eine Repository-Funktion, um bestimmte Daten aus der Datenbank abzurufen. Öffnen Sie die OnCallRepository.php gefunden in src/Repository. Innerhalb der Klasse unterhalb der __construct() Funktion, fügen Sie die neue Funktion findCurrentOnCall ein, die beim Aufruf den aktuellen Benutzer findet.
public function findCurrentOnCall(\Carbon\Carbon $date)
{
return $this->createQueryBuilder('o')
->andWhere('o.startDate <= :date')
->andWhere('o.endDate >= :date')
->setParameter('date', $date->format('Y-m-d H:i:s'))
->getQuery()
->getOneOrNullResult();
}Wir haben die Funktionalität zum Abrufen der Daten erstellt. Als Nächstes müssen wir einen Controller erstellen, der alle Anfragen bearbeitet und die Daten abruft.
Führen Sie zunächst in Ihrem Terminal den folgenden Befehl aus:
Wenn Sie nach dem Namen Ihres Controllers gefragt werden, geben Sie ein WebhookController.
Öffnen Sie die neu erstellte Datei: API/src/Controller/WebhookController.php.
Wir werden alle folgenden Klassen verwenden, also sollten wir sie von Anfang an einbeziehen. Am Anfang der Datei, direkt unter namespace App\Controller; fügen Sie das Folgende ein:
use App\Entity\Alert;
use App\Entity\OnCall;
use App\Entity\UserAlert;
use App\Form\AlertType;
use App\Util\VonageUtil;
use Carbon\Carbon;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;Ihre Klasse braucht ein Konstrukt, mit dem Symfony die Klassen EntityManager und VonageUtil injizieren kann. Fügen Sie am Anfang Ihrer Klasse hinzu:
/** @var VonageUtil */
protected $vonageUtil;
/** @var EntityManagerInterface */
private $entityManager;
public function __construct(
VonageUtil $vonageUtil,
EntityManagerInterface $entityManager
) {
$this->vonageUtil = $vonageUtil;
$this->entityManager = $entityManager;
}Ersetzen Sie nun die index() Funktion durch den nachstehenden Code, um neue Warnmeldungen zu erstellen. Diese neue Funktion verarbeitet den POST-Anforderungstext, erstellt diese Daten als eine neue Alertund übergibt diese Meldung an das Formular, um die Werte zu überprüfen. Wenn alles wie erwartet ist, wird eine neue UserAlertmit der Person, die gerade angerufen wird, als die Person, die die Meldung erhält.
/**
* @Route("/webhooks/raise_alert", name="raise_alert", methods={"POST"})
*/
public function index(Request $request): JsonResponse
{
$data = json_decode($request->getContent(), true);
// Create an alert.
$alert = (new Alert())
->setStatus('raised');
$form = $this->createForm(AlertType::class, $alert);
$form->submit($data);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($alert);
$entityManager->flush();
// Get the on call user
$onCall = $this->entityManager
->getRepository(OnCall::class)
->findCurrentOnCall(Carbon::now());
if (!$onCall) {
return new JsonResponse(['message' => 'No Alerts found.'], 400);
}
// Create a UserAlert
$userAlert = (new UserAlert())
->setUser($onCall->getUser())
->setAlert($alert);
$entityManager->persist($userAlert);
// Notify the on call user
$this->vonageUtil->sendSms(
$onCall->getUser()->getPhoneNumber(),
getenv('VONAGE_BRAND'),
'A new alert has been raised, please log into the mobile app to investigate.'
);
// Save this update to the user alert
$userAlert->setSmsSentAt(Carbon::now());
$entityManager->flush();
return new JsonResponse([], 201);
}
return new JsonResponse($this->getErrorMessages($form), 400);
}Sie haben vielleicht bemerkt, dass die Funktion $this->getErrorMessages() am Ende aufgerufen wird, aber Ihre Klasse hat sie noch nicht. Sie müssen diese Funktion als nächstes hinzufügen. Sie wird alle Formularfehler abrufen, die gefunden werden, wenn der Endpunkt ausgelöst wird, aber einige Daten fehlen. Unterhalb Ihrer index() Methode fügen Sie das Folgende hinzu:
private function getErrorMessages(Form $form): array
{
$errors = [];
foreach ($form->getErrors() as $key => $error) {
if ($form->isRoot()) {
$errors['#'][] = $error->getMessage();
} else {
$errors[] = $error->getMessage();
}
}
foreach ($form->all() as $child) {
if (!$child->isValid()) {
$errors[$child->getName()] = $this->getErrorMessages($child);
}
}
return $errors;
}Wir sind an einem Punkt angelangt, an dem wir das jetzt testen können!
Testen Sie die Authentifizierung
In diesem Teil des Tutorials gibt es zwei Endpunkte, die wir mit unserer API testen können. Wenn Docker also noch im Hintergrund läuft, machen Sie eine POST Anfrage an http://localhost:8080/api/login_check mit dem JSON-Körper von:
{
"username": "dev+1@company.com",
"password": "test_pass"
}Die Antwort ist ein JSON-Objekt mit einem Schlüssel tokenund der Wert ist ein JWT-Token.
Die folgende Abbildung zeigt ein Beispiel für diese Vorgehensweise mit Postman:

Test Auslösung eines Alarms
In diesem Beispiel müssen Sie nicht authentifiziert sein, um einen Alarm auszulösen. Sie brauchen also das JWT aus dem vorherigen Beispiel nicht zu verwenden.
Um einen Alarm auszulösen, aktualisieren Sie das URL-Feld: http://localhost:8080/webhooks/raise_alert, behalten Sie die Methode als POST Anfrage und den JSON-Körper von:
{
"title": "ERRORRRRRR ASAP FIX NOW ITS BORKED",
"description": "THE PAGE AINT LOADING TOP PRIORITY FIX ASAP."
}Die Antwort ist ein leeres Array und der HTTP-Statuscode 201 (erstellt). Ein Beispiel für diese Anfrage in Postman sehen Sie in der folgenden Abbildung:

Umgang mit einer Warnung
Die Workflow-Komponente von Symfony ermöglicht es Ihnen, einen Lebenszyklus zu definieren, den Ihr Objekt mit seinen Zuständen durchlaufen kann. Jeder Schritt, den Ihr Objekt durchlaufen kann, wird als Ort bezeichnet, wobei die Übergänge die Aktion definieren, die das Objekt durchführen muss, um von einem Ort zum anderen zu gelangen.
Mit Workflows können Sie festlegen, an welchen Stellen Ihr Alert stehen kann, um vom Zustand raised bis zum letzten Schritt, der entweder cancelled oder completed.
Öffnen Sie die workflow.yaml Datei, die sich in config/packages/ und ersetzen Sie den Inhalt durch das unten stehende Beispiel:
framework:
workflows:
alerts:
type: 'state_machine'
supports:
- App\Entity\Alert
marking_store:
type: 'method'
property: 'status'
initial_marking: new
places:
- new
- raised
- accepted
- cancelled
- completed
transitions:
raise:
from: [new]
to: raised
accept:
from: [raised]
to: accepted
cancel:
from: [raised, accepted]
to: cancelled
complete:
from: [accepted]
to: completedEin Controller wird nun benötigt, um alle API-Anfragen zu bearbeiten Alerts. Führen Sie also den folgenden Befehl aus, um unseren neuen AlertsApiController zu erstellen:
Wenn Sie nach einem Controller-Namen gefragt werden, geben Sie AlertsApiController. Dieser Befehl erstellt eine neue AlertsApiController.php Datei innerhalb von src/Controllers. Öffnen Sie also diese neue Datei.
Wir werden alle folgenden Klassen verwenden, also sollten wir sie von Anfang an einbeziehen. Am Anfang der Datei, direkt unter namespace App\Controller; fügen Sie das Folgende ein:
use App\Entity\Alert;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Workflow\Registry;Fügen Sie Ihr klassenbasiertes Routing wie im Beispiel unten gezeigt ein, so dass alle Routen innerhalb dieser Klasse mit einem Präfix versehen sind /api/alerts/:
/**
* @Route("/api/alerts")
*/
class AlertsApiController extends AbstractController
{Dieser Controller verwendet die $workflowRegistry und $entityManager an mehreren Stellen dieser Klasse verwenden. Um zu vermeiden, dass der Code an mehreren Stellen umgeschrieben wird, platzieren wir sie innerhalb des Konstrukts. Fügen Sie den folgenden Code oben in Ihrer Klasse ein:
/** Registry */
private $workflowRegistry;
/** EntityManagerInterface */
private $entityManager;
public function __construct(Registry $workflowRegistry, EntityManagerInterface $entityManager)
{
$this->workflowRegistry = $workflowRegistry;
$this->entityManager = $entityManager;
}Bei der Erstellung des Controllers wurde eine Funktion namens index() automatisch hinzugefügt. Wir brauchen sie für dieses Projekt nicht, also löschen Sie diese Funktion.
Jetzt erstellen wir unsere listAction() die alle Warnmeldungen aus der Datenbank abruft und als JSON-Antwort zurückgibt. Füge die listAction() zu Ihrem Controller wie unten gezeigt:
/**
* @Route("", methods={"GET"})
*/
public function listAction(): JsonResponse
{
$data = $this->entityManager
->getRepository(Alert::class)
->findAll();
$alerts = [];
foreach ($data as $alert) {
$alerts[] = $alert->toArray();
}
return new JsonResponse(
$alerts,
JsonResponse::HTTP_OK
);
}Als nächstes erstellen wir readAction() um eine Meldung nach ID aus der Datenbank abzurufen und sie als JSON-Antwort zurückzugeben. Füge readAction() zu Ihrem Controller wie unten gezeigt:
/**
* @Route("/{id}", methods={"GET"})
*/
public function readAction(int $id): JsonResponse
{
$alert = $this->entityManager
->getRepository(Alert::class)
->findOneById($id);
if (!$alert) {
return new JsonResponse(
null,
JsonResponse::HTTP_NOT_FOUND
);
}
return new JsonResponse(
$alert->toArray(),
JsonResponse::HTTP_OK
);
}Wir erstellen unsere acceptAction()die eine Meldung anhand ihrer ID in der Datenbank sucht; wenn eine gefunden wird, versucht sie, den Status dieser Meldung von pending zu accepted. Die Antwort wird eine leere JSON-Antwort mit dem HTTP-Statuscode 200 sein.
Fügen Sie die acceptAction() zu Ihrem Controller wie unten gezeigt:
/**
* @Route("/{id}/accept", methods={"POST"})
*/
public function acceptAction(int $id): JsonResponse
{
$alert = $this->entityManager
->getRepository(Alert::class)
->findOneById($id);
if (!$alert) {
return new JsonResponse(null, JsonResponse::HTTP_NOT_FOUND);
}
$workflow = $this->workflowRegistry->get($alert);
try {
$workflow->apply($alert, 'accept');
$this->entityManager->flush();
} catch (LogicException $exception) {
return new JsonResponse(['message' => $exception->getMessage()], 400);
}
return new JsonResponse([], 200);
}Als nächstes erstellen wir unsere completeAction(), das eine Meldung anhand ihrer ID in der Datenbank sucht; wird eine gefunden, versucht es, den Status dieser Meldung von accepted zu completed. Die Antwort wird eine leere JSON-Antwort mit dem HTTP-Statuscode 200 sein.
Fügen Sie die completeAction() zu Ihrem Controller wie unten gezeigt:
/**
* @Route("/{id}/complete", methods={"POST"})
*/
public function completeAction(int $id): JsonResponse
{
$alert = $this->entityManager
->getRepository(Alert::class)
->findOneById($id);
if (!$alert) {
return new JsonResponse(null, JsonResponse::HTTP_NOT_FOUND);
}
$workflow = $this->workflowRegistry->get($alert);
try {
$workflow->apply($alert, 'complete');
$this->entityManager->flush();
} catch (LogicException $exception) {
return new JsonResponse(['message' => $exception->getMessage()], 400);
}
return new JsonResponse([], 200);
}Schließlich erstellen wir unsere cancelAction(), das eine Meldung anhand ihrer ID in der Datenbank sucht; wird eine gefunden, versucht es, den Status dieser Meldung von accepted oder pending zu cancelled. Die Antwort wird eine leere JSON-Antwort mit dem HTTP-Statuscode 200 sein.
Fügen Sie die cancelAction() zu Ihrem Controller wie unten gezeigt:
/**
* @Route("/{id}/cancel", methods={"POST"})
*/
public function cancelAction(int $id): JsonResponse
{
$alert = $this->entityManager
->getRepository(Alert::class)
->findOneById($id);
if (!$alert) {
return new JsonResponse(null, JsonResponse::HTTP_NOT_FOUND);
}
$workflow = $this->workflowRegistry->get($alert);
try {
$workflow->apply($alert, 'cancel');
$this->entityManager->flush();
} catch (LogicException $exception) {
return new JsonResponse(['message' => $exception->getMessage()], 400);
}
return new JsonResponse([], 200);
}Zusammenfassend lässt sich sagen, dass wir eine Konfiguration zu unserem Projekt hinzugefügt haben, die den Lebenszyklus unserer Warnmeldungen steuert. Dann haben wir einen API-Controller erstellt, der es uns ermöglicht, eine Liste unserer Warnungen abzurufen, eine bestimmte Warnung abzurufen, die Warnungen je nach ihrem Status anzunehmen, abzulehnen, abzubrechen oder abzuschließen.
Erstellen Sie den Eskalationsbefehl
Was ist, wenn die SMS nicht empfangen wurde? Oder wird sie ignoriert? Nun, keine Sorge! Der nächste Schritt ist die Implementierung eines Symfony-Befehls, der als zeitbasierter Job-Scheduler (Cron-Job) läuft und alle Alarme eskaliert, die älter als 10 Minuten sind.
Bevor wir diesen Befehl erstellen, müssen wir eine Repository-Methode zum Abrufen von Alarmen hinzufügen, die eine Eskalation erfordern. Öffnen Sie Ihre UserAlertRepository.php Datei innerhalb von API/src/Repository/.
Fügen Sie oben in dieser Datei weitere Bibliotheken von Drittanbietern zum Importieren hinzu:
use App\Entity\Alert;
use App\Entity\UserAlert;
use Carbon\Carbon;Als Nächstes fügen Sie die Repository-Methode hinzu, um alle Warnungen abzurufen, für die vor mehr als 10 Minuten eine SMS gesendet wurde, die sich aber immer noch im Status von raised:
public function findRaisedUserAlerts()
{
$queryBuilder = $this->createQueryBuilder('ua');
$lastAlertSent = (Carbon::now())
->sub('10 minutes');
return $queryBuilder
->join(Alert::class, 'a', Join::WITH, $queryBuilder->expr()->andX(
$queryBuilder->expr()->eq('a', 'ua.alert'),
$queryBuilder->expr()->eq('a.status', ':alertStatus')
))
->where($queryBuilder->expr()->isNull('ua.voiceSentAt'))
->andWhere($queryBuilder->expr()->lte('ua.smsSentAt', ':smsSentAt'))
->setParameter('alertStatus', 'raised')
->setParameter('smsSentAt', $lastAlertSent->format('Y-m-d H:i:s'))
->getQuery()
->getResult();
}Dieser neue Symfony-Befehl wird alle abgerufenen Alarme eskalieren. Um ihn zu erstellen, führen Sie den folgenden Befehl in Ihrem Terminal aus:
Wenn Sie nach dem Befehlsnamen gefragt werden, geben Sie app:escalate-alertein, wodurch eine neue Datei namens EscalateAlertCommand.php innerhalb von API/src/Command. Öffnen Sie diese neue Datei.
Wir werden alle folgenden Klassen verwenden, also sollten wir sie von Anfang an einbeziehen. Am Anfang der Datei, direkt unter namespace App\Command; fügen Sie das Folgende ein:
use App\Entity\UserAlert;
use App\Util\VonageUtil;
use Carbon\Carbon;
use Doctrine\ORM\EntityManagerInterface;Die Klasse benötigt zwei Objekte, die in sie injiziert werden, die VonageUtil und EntityManagerInterface. Mit Symfony ist es am einfachsten, dies über den Konstruktor zu tun. Fügen Sie am Anfang Ihrer Klasse die folgende Funktionalität hinzu:
/** @var VonageUtil */
protected $vonageUtil;
/** @var EntityManagerInterface */
private $entityManager;
public function __construct(
VonageUtil $vonageUtil,
EntityManagerInterface $entityManager
) {
$this->vonageUtil = $vonageUtil;
$this->entityManager = $entityManager;
parent::__construct();
}Jetzt ist es an der Zeit, die Funktionalität für diesen Befehl zu schreiben. Er wird alle Alerts abrufen, deren SMS vor mehr als 10 Minuten gesendet wurde, aber noch den raised Status. In diesem Fall wird der Benutzer, der der Meldung zugeordnet ist, ermittelt und ihm eine Text-To-Speech-Sprachanrufbenachrichtigung gesendet. Ersetzen Sie die derzeitige Funktionalität in protected function execute() durch:
$io = new SymfonyStyle($input, $output);
$userAlertRepository = $this->entityManager->getRepository(UserAlert::class);
$userAlerts = $userAlertRepository->findRaiseduserAlerts();
if (!$userAlerts) {
$io->warning('There are no alerts needing to be raised.');
}
/** @var UserAlert $userAlert */
foreach ($userAlerts as $userAlert) {
$this->vonageUtil->makePhoneCall(
$userAlert->getUser()->getPhoneNumber(),
getenv('VONAGE_NUMBER'),
'A new alert has been raised, please log into the mobile app to investigate.'
);
$userAlert->setVoiceSentAt(Carbon::now());
$this->entityManager->flush();
}
return Command::SUCCESS; Testen Sie die API
Ihr Benutzer muss sich authentifizieren, um diese neuen Endpunkte zu testen.
Stellen Sie zunächst sicher, dass Sie Ihr JWT-Token erhalten, indem Sie eine POST Anfrage an http://localhost:8080/api/login_check mit den Anmeldedaten Ihres festen Benutzers senden.
Sobald Sie Ihr JWT kopiert haben, aktualisieren Sie den Typ zu einer GET Anfrage und die URL zu http://localhost:8080/api/alerts. Sie müssen eine Kopfzeile mit dem Schlüssel Authorisation und dem Wert als Bearer <JWT> Ersetzen Sie <JWT> durch Ihr Token ersetzen.
Der Endpunkt list Alerts gibt ein JSON-Array zurück, das Sie im folgenden Postman-Beispiel sehen können:

Behalten wir diese Meldung in ihrem aktuellen Zustand und verwenden sie später beim Testen der mobilen Anwendung.
Sie haben eine API entwickelt; jetzt ist es an der Zeit, die mobile Anwendung zu erstellen.
Erstellen Sie die mobile App
Aktualisierung config.json wobei der Wert des Feldes APIURL die ngrok-URL ist, die Sie zuvor gespeichert haben.
Öffnen Sie ein neues Terminalfenster und führen Sie die folgenden Befehle aus:
Nach einer kurzen Weile öffnet sich ein Webbrowser. Auf der linken Seite gibt es mehrere Optionen, um die Anwendung auf Ihrem Mobilgerät, iOS-Simulator oder Android-Simulator auszuführen. Wählen Sie die für Sie passende Option aus. Wenn die Anwendung hochfährt, sehen Sie als erstes den Anmeldebildschirm.
Die Anmeldedaten des angemeldeten Benutzers in der Datenbank lauten:
username: dev+1@company.com
password: test_passWie in der Abbildung unten dargestellt:

Eine erfolgreiche Anmeldung bewirkt derzeit noch nichts! Wir müssen zuerst mehr Bildschirme implementieren, aber um zu überprüfen, ob Ihre Anmeldung korrekt war, überprüfen Sie Ihr Terminal, wo Sie expo start. Sie sollten die Zeile sehen: You Successfully logged in!.
Warnungen API
Anzeigen einer Liste von Ausschreibungen
Innerhalb des Verzeichnisses API Verzeichnis eine neue Datei mit dem Namen alerts.js.
Fügen Sie das folgende Beispiel hinzu, das die Datei client.js Datei importiert, um die Funktionalität von getClient().
Diese neue Funktion namens getAlerts() stellt eine Anfrage an die API über den Endpunkt /api/alerts. Wir können die anderen API-Aufrufe, die Annahme, das Abschließen und das Abbrechen von Alarmen hinzufügen, während wir hier sind.
import { getClient } from "./client.js";
export function getAlerts() {
return getClient()
.then(function(client) {
return client.get("/api/alerts");
});
};
export function acceptAlert(alertId) {
return getClient()
.then(function(client) {
return client.post(`/api/alerts/${alertId}/accept`);
});
};
export function cancelAlert(alertId) {
return getClient()
.then(function(client) {
return client.post(`/api/alerts/${alertId}/cancel`);
});
};
export function completeAlert(alertId) {
return getClient()
.then(function(client) {
return client.post(`/api/alerts/${alertId}/complete`);
})
};Nun, da wir die Funktionalität haben, um die Warnungen zu erhalten, erstellen Sie die Komponente AlertsScreen. Erstellen Sie eine neue Datei in components/ mit dem Namen AlertsScreen.js.
import React, { Component } from 'react'
import { FlatList, Text, View, StyleSheet, StatusBar } from 'react-native'
import { TouchableOpacity } from 'react-native-gesture-handler';
import { getAlerts } from '../api/alerts.js'
class AlertsScreen extends Component {
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
header: {
backgroundColor: '#03A5C9',
padding: 10,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
},
body: {
padding: 10,
borderBottomLeftRadius: 20,
borderBottomRightRadius: 20,
},
item: {
marginVertical: 8,
marginHorizontal: 16,
paddingBottom: 10,
borderWidth: 1,
borderRadius: 20
},
title: {
fontSize: 24,
},
incidentId: {
textAlign: 'right'
}
});
export default AlertsScreen;Wir haben jetzt eine leere AlertsScreen Klasse und etwas Styling. Fügen wir zu dieser Klasse etwas hinzu, um etwas zu zeigen:
state = {
alerts: []
}
renderItem = ({ item }) => (
<View style={styles.item}>
<TouchableOpacity onPress={() => this.onPress(item)}>
<View style={styles.header}>
<Text style={styles.title}>
{item.title}
</Text>
</View>
<View style={styles.body}>
<Text>
{item.dateRaised}
</Text>
<Text>
{item.assigned !== '' ? item.assigned : 'Unassigned'}
</Text>
<Text style={styles.incidentId}>
#{item.incidentId}
</Text>
</View>
</TouchableOpacity>
</View>
);
render() {
return (
<View>
<FlatList
data={this.state.alerts}
renderItem={this.renderItem}
keyExtractor={item => item.id}
/>
</View>
);
}Ok, das zeigt uns unsere Seite. Aber es ruft keine Informationen ab und sagt uns nicht, was wir als nächstes tun sollen!
Oberhalb Ihrer renderItem() Methode fügen Sie Folgendes hinzu:
componentDidMount() {
getAlerts()
.then(response => {
return response.data.map(alert => ({
id: `${alert.id}`,
title: `${alert.title}`,
description: `${alert.description}`,
dateRaised: `${alert.dateRaised}`,
assigned: `${alert.assigned}`,
incidentId: `${alert.incidentId}`,
status: `${alert.status}`
}))
})
.then(alerts => {
this.setState({ alerts: alerts });
})
.catch((err) => console.log(err));
}
onPress = (item) => {
return this.props.navigation.navigate('Alert', {
alert: item,
})
} Anzeigen eines bestimmten Alarms
Erstellen Sie eine neue Datei in components namens AlertScreen.js, die die spezifische Ausschreibung nach ID anzeigt.
import React, { Component } from 'react'
import { Text, View, ScrollView, StyleSheet, StatusBar, TouchableOpacity } from 'react-native'
import { acceptAlert, cancelAlert, completeAlert } from '../api/alerts.js'
class AlertScreen extends Component {
state = {
alert: {}
}
const = this.state.alert = this.props.route.params.alert;
onPressComplete = () => {
completeAlert(this.state.alert.id)
.then(() => {
this.setState({ alert: { ...this.state.alert, status: 'completed'} });
})
.catch((err) => console.log(err));
}
onPressCancel = () => {
cancelAlert(this.state.alert.id)
.then(() => {
this.setState({ alert: { ...this.state.alert, status: 'cancelled'} });
})
.catch((err) => console.log(err));
}
onPressAccept = () => {
acceptAlert(this.state.alert.id)
.then(() => {
this.setState({ alert: { ...this.state.alert, status: 'accepted'} });
})
.catch((err) => console.log(err));
}
render() {
let buttons;
if (this.state.alert.status === 'raised') {
buttons = <View style={styles.buttonContainer}>
<View style={styles.buttonView}>
<TouchableOpacity
style={styles.button}
onPress={() => this.onPressAccept()}
underlayColor='#fff'>
<Text style={styles.actionText}>Accept</Text>
</TouchableOpacity>
</View>
<View style={styles.buttonView}>
<TouchableOpacity
style={styles.button}
onPress={() => this.onPressCancel()}
underlayColor='#fff'>
<Text style={styles.actionText}>Cancel</Text>
</TouchableOpacity>
</View>
</View>
} else if (this.state.alert.status === 'accepted') {
buttons = <View style={styles.buttonContainer}>
<View style={styles.buttonView}>
<TouchableOpacity
style={styles.button}
onPress={() => this.onPressComplete()}
underlayColor='#fff'>
<Text style={styles.actionText}>Complete</Text>
</TouchableOpacity>
</View>
<View style={styles.buttonView}>
<TouchableOpacity
style={styles.button}
onPress={() => this.onPressCancel()}
underlayColor='#fff'>
<Text style={styles.actionText}>Cancel</Text>
</TouchableOpacity>
</View>
</View>
}
return (
<View style={styles.item}>
<View style={styles.header}>
<Text style={styles.title}>
{this.state.alert.title}
</Text>
</View>
<View style={styles.body}>
<Text>
Date Raised: {this.state.alert.raisedDate}
</Text>
<Text>
Assignee: {this.state.alert.assigned !== '' ? this.state.alert.assigned : 'Unassigned'}
</Text>
<Text style={styles.incidentId}>
Incident ID: #{this.state.alert.incidentId}
</Text>
<Text style={styles.status}>
Status: {this.state.alert.status}
</Text>
</View>
{buttons}
<View style={styles.scrollView}>
<ScrollView>
<Text style={styles.text}>
{this.state.alert.description}
</Text>
</ScrollView>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
header: {
backgroundColor: '#03A5C9',
padding: 10,
},
body: {
padding: 10,
},
item: {
paddingBottom: 10,
},
title: {
fontSize: 24,
},
buttonContainer: {
flex: 1,
flexDirection: "row",
alignItems: 'center',
justifyContent: 'center',
paddingBottom: 30
},
buttonView: {
flex: 1,
height: 10
},
button: {
marginRight: 40,
marginLeft: 40,
marginTop: 10,
paddingTop: 10,
paddingBottom: 10,
backgroundColor: '#1E6738',
borderRadius: 10,
borderWidth: 1,
borderColor: '#fff'
},
actionText: {
color: '#fff',
textAlign: 'center',
paddingLeft: 10,
paddingRight: 10
},
text: {
fontSize: 20,
},
});
export default AlertScreen;Ihre Anwendung hat derzeit keine Anweisung, wie die beiden neuen Bildschirme, die Sie erstellt haben, angezeigt werden sollen. Unter navigation/MainStackNavigator.js unten import Loginfügen Sie die folgenden zwei Zeilen ein:
import Alert from '../components/AlertScreen';
import Alerts from '../components/AlertsScreen';Dann fügen Sie unterhalb des Login Stack.Screenzwei neue Bildschirme ein:
<Stack.Screen
name='Alerts'
component={Alerts}
options={{ title: 'Alerts Screen' }}
/>
<Stack.Screen
name='Alert'
component={Alert}
options={({route, navigation}) => (
{headerTitle: 'Alert Screen',
route: {route},
navigation: {navigation}}
)}
/>Zurück in Ihrer LoginScreen.js Datei, finden Sie die Zeile mit: console.log('You Successfully logged in!'); und fügen Sie das folgende Snippet ein, um den Benutzer bei erfolgreicher Anmeldung umzuleiten.
return this.props.navigation.navigate('Alerts'); Prüfung
Um diese Anwendung in Ihrem Terminal zu testen, stellen Sie sicher, dass Sie in das MobileApp Verzeichnis und führen Sie den folgenden Befehl aus:
Nach einer kurzen Weile sollte sich ein Webbrowser öffnen. Auf der linken Seite gibt es mehrere Optionen, um die Anwendung auf Ihrem Mobilgerät, iOS-Simulator oder Android-Simulator auszuführen. Wählen Sie die für Sie passende Option. Wenn die Anwendung gestartet wird, sehen Sie als erstes den Anmeldebildschirm.
Die Anmeldedaten des angemeldeten Benutzers in der Datenbank lauten:
username: dev+1@company.com
password: test_passNach erfolgreicher Anmeldung sehen Sie als Nächstes den Bildschirm "Alerts". Dieser ist jedoch im Moment noch leer, da in der Datenbank noch keine Alarme vorhanden sind.

Versuchen Sie nun erneut, sich bei Ihrer mobilen Anwendung anzumelden. Sie sehen die neue Meldung und können auf diese Meldung klicken, um zu einem Bildschirm mit weiteren Informationen zu gelangen.
Sie können diese Ausschreibung auch umwandeln, um sie zu akzeptieren oder zu löschen.
Schlussfolgerung
In diesem Tutorial haben wir gelernt, wie man eine API mit einem PHP-Framework namens Symfony erstellt. Außerdem haben wir eine mobile Anwendung mit React Native erstellt. Mit den APIs von Vonage konnten wir Benachrichtigungen per SMS und Text-To-Speech-Sprachanrufe versenden. Indem wir all dies zusammen angewendet haben, haben wir uns eine funktionale Bereitschaftsanwendung für Entwickler oder Systemadministratoren gebaut, die benachrichtigt werden, wenn etwas schief läuft. Mit einem Webhook können wir unser Bereitschaftssystem in mehrere Dienste integrieren, um so viele wie möglich abzudecken.
Im Folgenden finden Sie einige weitere Tutorials, die wir zur Implementierung der Vonage Voice API in Projekte geschrieben haben:
Wie immer, wenn Sie Fragen, Ratschläge oder Ideen haben, die Sie mit der Community teilen möchten, dann können Sie sich gerne an unseren Slack-Arbeitsbereich der Community. Ich würde gerne hören, wie ihr mit diesem Tutorial zurechtgekommen seid und wie euer Projekt funktioniert.
