Alexa und PHP – ein Dream-Team

Umsetzung des Skill-Codes mit PHP
14
Mai

Alexa und PHP – ein Dream-Team? Teil 2: Umsetzung des Skill-Codes mit PHP

Im zweiten Teil der Artikelserie über die Entwicklung von Alexa Skills mit PHP geht es nun ans Eingemachte. Auf Basis der Planung und Konfiguration aus dem ersten Teil wird der Skill-Code implementiert. Dabei kommt eine PHP Library zum Einsatz, die sich um die Details bei der Kommunikation zwischen der Amazon Alexa Service Platform und Ihrem HTTPS-Endpunktserver kümmert. Somit können Sie sich ganz auf die Entwicklung der Logik für Ihren Skill konzentrieren.

Im ersten Teil dieser zweiteiligen Artikelserie haben Sie die Grundlagen sowie einige wichtige Begriffe bei der Entwicklung von Alexa Skills für Amazon Echo kennengelernt. Sie haben Ihren ersten Skill geplant und im Alexa Skills Kit konfiguriert. Zudem haben Sie einige Tipps erhalten, wie Sie den Freigabeprozess für Ihren Skill möglichst fehlerfrei überstehen können.

Lesen Sie den ersten Teil dieser Artikelserie „Alexa und PHP – ein Dream-Team“

 

In diesem zweiten Teil werden Sie zuerst die Beispielanwendung installieren und genauer anschauen. Sie werden den Skill-Code für die beiden geplanten Intents implementieren und zudem erfahren, wie Sie mit Session-Attributen und Slot Types arbeiten können. Zu guter Letzt werden Sie Ihren Skill online bringen und ausführlich testen, bevor Sie den Freigabeprozess starten können.

Beispielanwendung installieren

Nachdem das Frontend im Alexa Skills Kit soweit vorbereitet ist, können Sie sich nun um den Skill-Code kümmern. Doch keine Angst, Sie müssen nun nicht einen eigenen Parser schreiben, der die JSON Service Requests von Alexa verarbeiten und JSON Service Responses erstellen kann. Dafür gibt es bereits eine entsprechende PHP Library sowie eine Skeleton Application, die auf dieser Library und Expressive aufsetzt. Durch die Library sind Sie aber nicht an das Zend Framework und Expressive gebunden. Sie können auch gerne eine Anwendung mit einem anderen Framework aufsetzen und darin die PHP Library nutzen. Sie können die Skeleton Application mit $ composer create-project travello-gmbh/amazon-alexa-skill-skeleton mein-zoo installieren.

Doch statt mit der Skeleton Application sollten Sie mit der Beispielanwendung beginnen. Die Beispielanwendung für den Mein Zoo Alexa Skill setzt auf dieser Skeleton Application auf, damit Sie sich ganz auf die Skill-Entwicklung konzentrieren können. Darin wurden bereits einige Vorbereitungen für Sie getroffen, damit Sie gleich loslegen können. Sie installieren die Beispielanwendung wie folgt:

$ git clone https://github.com/RalfEggert/phpmagazin.alexa
$ cd phpmagazin.alexa
$ composer install
$ sudo chmod -R 777 data/cache/

 

Danach sollten Sie einen Virtual-Host phpmagazin.alexa einrichten, der auf das Verzeichnis /html/ in diesem Projekt verweist. Um die Funktion der Anwendung nach der Installation und den Virtual-Host zu testen, rufen Sie im Browser den URL http://phpmagazin.alexa/ auf. Sie sollten eine JSON-Ausgabe mit einer kurzen Begrüßung erhalten.
Außerdem empfiehlt es sich, zum Prüfen der Anfragen ein Tool wie Postman [4] zu verwenden. Wenn Sie Postman installiert haben, können Sie die Datei /data/postman/collection.json importieren. Sie sollten in dieser Datei aber vorher alle Stellen für die Application-ID anpassen. Suchen Sie einfach nach amzn1.ask.skill.place-your-skill-id-here und tauschen diese Fundstellen durch Ihre eigene Application-ID aus. Prüfen Sie dann die Abfrage Zoo LaunchRequest. Wenn die Rückgabe in etwa so aussieht wie in Abbildung 1, war die Installation erfolgreich.

Abb. 1: Erfolgreiche Installation

Damit Sie sich in der Anwendung etwas orientieren können, ein paar Anmerkungen dazu. Bei der Anwendung handelt es sich wie erwähnt um eine Expressive-Applikation. Wenn Sie noch keinerlei Erfahrungen mit Expressive und dem Zend Framework gemacht haben, sollten Sie zumindest einmal die Dokumentation dazu genauer anschauen [5]. Im Verzeichnis /config/autoload/ finden Sie die beiden Konfigurationsdateien travello-alexa.config.global.php und travello-alexa.config.development.php. Darin können Sie die Signaturvalidierung sowie das Logging der Requests an- und abschalten. Auf dem Liveserver sollten Sie die Signaturvalidierung nur für noch nicht veröffentlichte Skills deaktivieren.

In der Datei /data/texts/zoo.common.texts.php finden Sie alle Texte für den neuen Skill, die Sie mindestens brauchen. Weitere Texte sollten Sie dort einfügen. Wenn Sie Ihren Skill später einmal internationalisieren möchten, dann können Sie dies einfach tun, da alle Texte hier ausgelagert sind. Die Datei /data/zoo/animals.php enthält eine Liste mit Tieren, die nach Tierart unterteilt sind. In den Verzeichnissen /html/cards/ und /html/images/ liegen einige Vorlagen für die Icons, die Sie für Ihren Skill brauchen. Die Größen sollten passen, zudem ist das /html/cards/ durch die .htaccess-Datei für CORS-Zugriffe fertig konfiguriert. Der eigentliche Skill-Code soll im Verzeichnis /module/Zoo/ liegen. Darin finden Sie schon einige Klassen sowie ein Template. In der Klasse /module/Zoo/src/ConfigProvider.php finden Sie die Konfiguration des Skill-Moduls für unseren Zoo. In Listing 1 sehen Sie einen Auszug aus der Konfigurationsdatei, der die Konfiguration des Skills an sich beinhaltet. Dort werden unter anderem die Application-ID des Skills, die Session-Attribute, die Textdateien und die Intents konfiguriert. Zu diesen einzelnen Punkten kommen wir später.

<?php
namespace Zoo;
use TravelloAlexaLibrary\Application\AlexaApplication;
use TravelloAlexaLibrary\TextHelper\TextHelper;
use Zend\Expressive\Application;
use Zoo\Config\RouterDelegatorFactory;
class ConfigProvider
{
  const NAME = 'zoo-skill';
  /** ... */
  public function getSkills(): array
  {
    return [
      self::NAME => [
        'applicationId'    => 'amzn1.ask.skill.place-your-skill-id-here',
        'applicationClass' => AlexaApplication::class,
        'textHelperClass'  => TextHelper::class,
        'sessionDefaults'  => [
          'animals' => [],
        ],
        'smallImageUrl'    => 'https://www.travello.audio/cards/zoo-480x480.png',
        'largeImageUrl'    => 'https://www.travello.audio/cards/zoo-800x800.png',
        'intents'          => [
          'aliases'   => [],
          'factories' => [],
        ],
        'texts'       => [
          'de-DE' => include PROJECT_ROOT . '/data/texts/zoo.common.texts.de-DE.php',
          'en-US' => include PROJECT_ROOT . '/data/texts/zoo.common.texts.en-US.php',
        ],
      ]
    ];
  }
}

 

Skill-Code mit PHP entwickeln

Bevor Sie mit der Entwicklung der Skill-Application beginnen, sollten Sie noch die erforderlichen Texte erstellen. Alle Standardtexte für die Begrüßung oder das Ende des Skills sind bereits in der Datei /data/texts/zoo.common.texts.de-DE.php vorhanden. Wenn Sie nun noch einmal die Beispieldialoge zu den Antworten von Alexa anschauen, finden Sie noch mindestens zwei Texte, die Sie noch implementieren müssen: Eine Giraffe lebt in deinem Zoo! und In deinem Zoo leben 42 Tiere!. Dabei fällt auf, dass diese Texte mit einem Platzhalter arbeiten müssen.

Als Erstes legen Sie die neuen Texte an. Sie brauchen jeweils eine Nachricht sowie einen Titel, der später in der Alexa-App auf dem Smartphone angezeigt werden soll. Rufen Sie die Datei /data/texts/zoo.common.texts.de-DE.php auf und fügen Sie die neuen Texte ein:

<?php
return [
  /* ... */
  'animalTitle'     => 'Ein Tier',
  'animalMessage'   => '%1$s lebt in deinem Zoo!',
  'countTitle'      => 'Tierzählung',
  'countMessage'    => 'In deinem Zoo leben %1$s Tiere!',
];

 

Wiederholen Sie dann den Schritt mit der englischsprachigen Datei. Damit sind die Vorbereitungen für die Texte abgeschlossen.

Nun ist die eigentliche Anwendung an der Reihe. Legen Sie das Verzeichnis /module/Zoo/src/Intent und darin die Klasse AnimalIntent an. Schauen Sie dazu in Listing 2. In der Regel müssen Sie in einer Intent-Klasse in der Response mindestens einen Ausgabetext festlegen. Dafür können Sie die sogenannte OutputSpeech festlegen. Dabei haben Sie die Wahl zwischen normalem Text (PlainText) und einer SSML-Definition. Mit der Speech Synthesis Markup Language können Sie die Texte, die Alexa sagt, betonen, bestimmte Effekte verwenden oder MP3-Dateien abspielen lassen. Details finden Sie in der Dokumentation [6]. Zusätzlich zum Ausgabetext können Sie auch eine Card festlegen, die in der Smartphone-App angezeigt wird. Hier haben Sie die Wahl zwischen Simple Cards ohne Bilder und Standard Cards mit Bildern. Details zu den Cards finden Sie ebenfalls in der Dokumentation [7]. Die genannten Varianten für die OutputSpeech sowie für die Cards werden von der Travello Alexa Library unterstützt. Vielleicht fällt Ihnen dabei auf, dass das aktuelle Tier noch festgelegt ist. Um die Varianz kümmern Sie sich aber erst später.

<?php
namespace Zoo\Intent;

use TravelloAlexaLibrary\Intent\AbstractIntent;
use TravelloAlexaLibrary\Response\AlexaResponse;
use TravelloAlexaLibrary\Response\Card\Standard;
use TravelloAlexaLibrary\Response\OutputSpeech\SSML;

class AnimalIntent extends AbstractIntent
{
  const NAME = 'AnimalIntent';

  public function handle(string $smallImageUrl, string $largeImageUrl): AlexaResponse
  {
    $zooMessage = $this->getTextHelper()->getAnimalMessage('Ein Elefant');

    $this->getAlexaResponse()->setOutputSpeech(
      new SSML($zooMessage)
    );

    $this->getAlexaResponse()->setCard(
      new Standard(
        $this->getTextHelper()->getAnimalTitle(),
        $zooMessage,
        $smallImageUrl,
        $largeImageUrl
      )
    );

    return $this->getAlexaResponse();
  }
}

 

Jetzt geht es um die Konfiguration des Skills. Öffnen Sie die Konfigurationsklasse Zoo\ConfigProvider und scrollen Sie zur Methode getSkills(). Ändern Sie als Erstes den Wert für die applicationId. Erstellen Sie auch die Bilder in den Größen 480 x 480 Pixel sowie 800 x 800 Pixel und laden Sie sie auf den eingerichteten HTTPS-Endpunktserver hoch. Passen Sie die URLs zu den Dateien an, sodass Sie während der Entwicklung schon mit den Bildern arbeiten können.

Nun können Sie den neuen Intent aufnehmen. Damit teilen Sie dem Intent-Manager mit, dass es eine neue Intent-Klasse gibt. Sie müssen dafür einmal die Factory für den Intent definieren und noch einen Alias festlegen, damit der Intent-Manager diesen anhand der NAME-Konstante finden kann. Das Ergebnis sehen Sie in Listing 3.

<?php
namespace Zoo;

use TravelloAlexaZf\Intent\AbstractIntentFactory;
use Zoo\Intent\AnimalIntent;

class ConfigProvider
{
  /** ... */

  public function getSkills(): array
  {
    return [
      self::NAME => [
        'applicationId'    => 'amzn1.ask.skill.place-your-skill-id-here',
        'applicationClass' => AlexaApplication::class,
        'textHelperClass'  => TextHelper::class,
        'sessionDefaults'  => [
          'animals' => [],
        ],
        'smallImageUrl'    => 'https://www.travello.audio/cards/zoo-480x480.png',
        'largeImageUrl'    => 'https://www.travello.audio/cards/zoo-800x800.png',
        'intents'          => [
          'aliases' => [
            AnimalIntent::NAME => AnimalIntent::class,
          ],

           'factories' => [
             AnimalIntent::class => AbstractIntentFactory::class,
            ],
          ],
          'texts'            => [
            'de-DE' => include PROJECT_ROOT . '/data/texts/zoo.common.texts.de-DE.php',
            'en-US' => include PROJECT_ROOT . '/data/texts/zoo.common.texts.en-US.php',
          ],
        ]
      ];
    }
}

 

Jetzt geht es darum, den AnimalIntent zu testen. Rufen Sie dafür im Postman den Request Zoo AnimalIntent auf und betrachten Sie die Rückgabe. Sollten Sie die Meldung Application Id invalid erhalten, dann müssen Sie im Postman in den Body des Requests noch Ihre eigene Application-ID eintragen. Abbildung 2 zeigt die mögliche Ausgabe.

Abb. 2: Mögliche Ausgabe von „AnimalIntent“

Wenn Sie nicht alle Änderungen erfolgreich umsetzen konnten, wechseln Sie einfach mit $ git checkout step1 auf den step1-Branch im Projekt.

Mit Session-Attributen arbeiten

Nun sollen natürlich nicht immer nur Elefanten genannt werden. Es gibt ja eine Liste mit Tieren unterteilt nach Tierarten. Diese muss nun in die AnimalIntent injiziert werden, damit ein zufälliges Tier anhand der Liste ausgewählt werden kann. Rufen Sie dafür die Klasse Zoo\Intent\AnimalIntent auf und fügen Sie die Änderungen aus Listing 4 ein. Diese gehen wir nun Schritt für Schritt durch.

Sie müssen eine neue Eigenschaft animalList für das Array mit den Tieren schaffen und eine öffentliche Methode setAnimalList() für das Setzen der Tierliste erstellen. Danach müssen Sie die handle()-Methode anpassen, damit ein zufälliges Tier ausgewählt wird. Listing 4 zeigt eine mögliche Implementation. Dabei wird das Ermitteln des Tiers in eine eigene kleine Hilfsmethode getRandomAnimal() ausgelagert. Wenn Sie im Postman den Request Zoo AnimalIntent nun erneut aufrufen, sollte Ihnen jedes Mal ein anderes Tier genannt werden.

<?php
namespace Zoo\Intent;

use TravelloAlexaLibrary\Intent\AbstractIntent;
use TravelloAlexaLibrary\Response\AlexaResponse;
use TravelloAlexaLibrary\Response\Card\Standard;
use TravelloAlexaLibrary\Response\OutputSpeech\SSML;

class AnimalIntent extends AbstractIntent
{
  const NAME = 'AnimalIntent';

  private $animalList = [];

  public function setAnimalList(array $animalList)
  {
    $this->animalList = $animalList;
  }

  protected function getAnimalList(): array
  {
    return $this->animalList;
  }

  public function handle(string $smallImageUrl, string $largeImageUrl): AlexaResponse
  {
    $randomAnimal = $this->getRandomAnimal();

    $zooMessage = $this->getTextHelper()->getAnimalMessage($randomAnimal);

    $this->getAlexaResponse()->setOutputSpeech(
      new SSML($zooMessage)
    );

    $this->getAlexaResponse()->setCard(
      new Standard(
        $this->getTextHelper()->getAnimalTitle(),
        $zooMessage,
        $smallImageUrl,
        $largeImageUrl
      )
    );

    return $this->getAlexaResponse();
  }

  private function getRandomAnimal()
  {
    $locale = $this->getAlexaRequest()->getRequest()->getLocale();

    $randomType      = array_rand($this->getAnimalList()[$locale]);
    $randomAnimalKey = array_rand($this->getAnimalList()[$locale][$randomType]);
    $randomAnimal    = $this->getAnimalList()[$locale][$randomType][$randomAnimalKey];

    return $randomAnimal;
  }
}

 

Momentan kann es aber passieren, dass Sie ein Tier, das gerade erst genannt wurde, beim nächsten Aufruf erneut genannt bekommen. Das wäre auf Dauer etwas langweilig. Um zumindest zu verhindern, dass ein Tier kurz hintereinander erneut genannt wird, kommen nun die Session-Attribute zum Einsatz. Hierbei handelt es sich nicht um klassische PHP Sessions. Das Alexa Skill Kit erlaubt den Einsatz von Session-Attributen, die Sie in eine Service Response aufnehmen können und die dann beim nächsten Service Request unverändert zurückgeschickt werden.

Die Travello Alexa Library bietet Ihnen dafür schon alle Funktionen, die Sie für die Nutzung von Session-Attributen brauchen. Sie können die Session-Attribute initialisieren und an die AlexaResponse anhängen. Dafür stehen entsprechende Methoden bereit. Listing 5 zeigt alle erforderlichen Änderungen an der Zoo\Intent\AnimalIntent-Klasse:

  • Sie müssen eine neue private Eigenschaft $sessionAnimals zum Ablegen der letzten Tiere aus den Session-Attributen erstellen.
  • In der Methode handle() rufen Sie zuerst den aktuellen Wert der Tierliste aus der Session ab, bevor das zufällige Tier ermittelt wird. Bevor die Methode verlassen wird, wird der Wert der Session-Variable auch in der Response vermerkt. Damit wird sichergestellt, dass der Wert beim nächsten Request wieder mitgeliefert wird.
  • Die größten Änderungen betreffen die getRandomAnimal()-Methode. Hier wurde die Ermittlung eines zufälligen Tiers in einer do-while-Schleife integriert, die nur verlassen wird, wenn das neue zufällige Tier nicht in der Liste der bisherigen Tiere enthalten ist. Um sicherzustellen, dass die Liste der bisherigen Tiere nicht irgendwann alle Tiere enthält und der AnimalIntent dann nicht mehr funktioniert, wird das Array auf maximal fünf Einträge begrenzt. Danach wird das neue zufällige Tier der Eigenschaft $sessionAnimals hinzugefügt, bevor die Methode es wieder zurückgibt.
<?php
namespace Zoo\Intent;

use TravelloAlexaLibrary\Intent\AbstractIntent;
use TravelloAlexaLibrary\Response\AlexaResponse;
use TravelloAlexaLibrary\Response\Card\Standard;
use TravelloAlexaLibrary\Response\OutputSpeech\SSML;

class AnimalIntent extends AbstractIntent
{
  /** ... */

  /** @var array */
  private $sessionAnimals = [];

  public function handle(string $smallImageUrl, string $largeImageUrl): AlexaResponse
  {
    $this->sessionAnimals = $this->getAlexaResponse()->getSessionContainer()->getAttribute('animals');

    $randomAnimal = $this->getRandomAnimal();

    // ...

    $this->getAlexaResponse()->getSessionContainer()->setAttribute('animals', $this->sessionAnimals);

    return $this->getAlexaResponse();
  }

  private function getRandomAnimal()
  {
    $locale = $this->getAlexaRequest()->getRequest()->getLocale();

    do {
      $randomType      = array_rand($this->getAnimalList()[$locale]);
      $randomAnimalKey = array_rand($this->getAnimalList()[$locale][$randomType]);
      $randomAnimal    = $this->getAnimalList()[$locale][$randomType][$randomAnimalKey];
    } while (in_array($randomAnimal, $this->sessionAnimals));

    if (count($this->sessionAnimals) >= 5) {
      array_shift($this->sessionAnimals);
    }

    $this->sessionAnimals[] = $randomAnimal;

    return $randomAnimal;
  }
}

 

Als Letztes brauchen wir noch eine Factory-Klasse, die die Liste aller möglichen Tiere in den AnimalIntent injiziert. Legen Sie dafür die Klasse Zoo\Intent\AnimalIntentFactory an und fügen den Inhalt aus Listing 6 ein. Darin wird der AnimalIntent entsprechend konfiguriert und bekommt alle erforderlichen Abhängigkeiten sowie die Daten der Tiere injiziert. Zudem müssen Sie noch die Konfiguration der Factory im ConfigProvider anpassen.

<?php
namespace Zoo\Intent;

use Interop\Container\ContainerInterface;
use TravelloAlexaLibrary\Request\AlexaRequest;
use TravelloAlexaLibrary\Response\AlexaResponse;
use TravelloAlexaLibrary\TextHelper\TextHelper;
use Zend\ServiceManager\Factory\FactoryInterface;

class AnimalIntentFactory implements FactoryInterface
{
  public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
  {
    $alexaRequest  = $container->get(AlexaRequest::class);
    $alexaResponse = $container->get(AlexaResponse::class);
    $textHelper    = $container->get(TextHelper::class);

    $animalList = include PROJECT_ROOT . '/data/zoo/animals.php';

    /** @var AnimalIntent $intent */
    $intent = new $requestedName($alexaRequest, $alexaResponse, $textHelper);
    $intent->setAnimalList($animalList);

    return $intent;
  }
}

 

Probieren Sie im Postman den Request Zoo AnimalIntent nun erneut aus. In der Response sollte nun unter sessionAttributes auch das zuletzt genannte Tier auftauchen. Probieren Sie nun im Postman auch mal den Request Zoo AnimalIntent (5 last animals) aus. Schauen Sie sich den Body des Requests an und vergleichen Sie ihn mit dem Body der Response. Im Request waren bereits fünf Tiere in den Session-Attributen gespeichert; in der Response sollte das älteste Tier dann herausgeflogen sein.

Sie können den AnimalIntent natürlich auch so umschreiben, dass die Liste nicht nach fünf Tieren gekürzt, sondern so lange fortgeführt wird, bis alle Tiere einmal genannt worden sind. Dann muss Ihr AnimalIntent aber beim nächsten Aufruf eine entsprechende Meldung ausgeben, dass alle Tiere genannt worden sind. Sie sind da vollkommen frei, wie Sie das umsetzen.

Die letzten Änderungen können Sie sich auch einfach durch den Wechsel auf den step2 Branch anschauen, falls Sie diese nicht im Detail selber implementiert haben. Der Befehl dafür lautet $ git checkout step2.

Zweiten Intent implementieren

Sie erinnern sich vielleicht, dass Sie neben dem implementierten AnimalIntent auch einen zweiten Intent im Alexa Skills Kit definiert haben: den CountIntent zum Zählen aller Tiere im Zoo. Mit Ihren bisher erworbenen Kenntnissen sollte Ihnen dies jedoch nicht schwerfallen. Dafür wird eine neue Intent-Klasse CountIntent erstellt. Die entsprechenden Texte für OutputSpeech und Card haben Sie bereits angelegt, sodass dies auch schnell vonstattengehen sollte. Nur für die Berechnung der Anzahl der Tiere würde sich eine eigene Methode anbieten.

In Listing 7 finden Sie eine beispielhafte Implementation. Hierbei wurde die Berechnung für die Tierzählung in die Methode getAnimalCount() ausgelagert. Bitte beachten Sie, dass eine einfache Zählung per count($this->animalList, COUNT_RECURSIVE) auch die Tierarten mitzählen und somit ein falsches Ergebnis liefern würde. Deshalb wurde eine foreach()-Schleife gewählt. Zusätzlich wäre es auch denkbar, das Ergebnis der Tierzählung für spätere Abfragen zu cachen. Für die Demonstration sollte dies aber ausreichend sein. Der Aufbau der handle()-Methode ist im Wesentlichen ähnlich zum AnimalIntent.

<?php
namespace Zoo\Intent;

use TravelloAlexaLibrary\Response\AlexaResponse;
use TravelloAlexaLibrary\Response\Card\Standard;
use TravelloAlexaLibrary\Response\OutputSpeech\SSML;

class CountIntent extends AbstractIntent
{
  const NAME = 'CountIntent';

  public function handle(string $smallImageUrl, string $largeImageUrl): AlexaResponse
  {
    $count = $this->getAnimalCount();

    $zooMessage = $this->getTextHelper()->getCountMessage($count);

    $this->getAlexaResponse()->setOutputSpeech(
      new SSML($zooMessage)
    );

    $this->getAlexaResponse()->setCard(
      new Standard(
        $this->getTextHelper()->getCountTitle(),
        $zooMessage,
        $smallImageUrl,
        $largeImageUrl
      )
    );

      return $this->getAlexaResponse();
    }

    private function getAnimalCount()
    {
      $locale = $this->getAlexaRequest()->getRequest()->getLocale();

      $count = 0;

      foreach ($this->getAnimalList()[$locale] as $type => $typeList) {
      $count += count($typeList);
    }

    return $count;
  }
}

 

Vielleicht ist Ihnen aufgefallen, dass die CountIntent-Klasse die Klasse Zoo\Intent\AbstractIntent erweitert. In diese abstrakte Klasse wurde die Verarbeitung der Tierliste inklusive Setter und Getter ausgelagert. Eine andere Variante wäre auch ein Trait gewesen. Sie finden diese abstrakte Klasse in Listing 8. Als Ergänzung müssen Sie die bisherige Zoo\Intent\AnimalIntentFactory-Klasse in Zoo\Intent\AbstractIntentFactory umbenennen, um sie für beide Intents verwenden zu können. Beachten Sie auch Listing 9 mit der geänderten Konfiguration für den Intent-Manager.

<?php
namespace Zoo\Intent;

use TravelloAlexaLibrary\Intent\AbstractIntent as BaseAbstractIntent;

abstract class AbstractIntent extends BaseAbstractIntent
{
  private $animalList = [];

  public function setAnimalList(array $animalList)
  {
    $this->animalList = $animalList;
  }

  protected function getAnimalList(): array
  {
    return $this->animalList;
  }
}

 

<?php
namespace Zoo;

use Zoo\Intent\AbstractIntentFactory;
use Zoo\Intent\AnimalIntent;
use Zoo\Intent\CountIntent;

class ConfigProvider
{
  /** ... */

  public function getSkills(): array
  {
    return [
      self::NAME => [
        /** ... */

        'intents'          => [
          'aliases' => [
            AnimalIntent::NAME => AnimalIntent::class,
            CountIntent::NAME  => CountIntent::class,
          ],

          'factories' => [
            AnimalIntent::class => AbstractIntentFactory::class,
            CountIntent::class  => AbstractIntentFactory::class,
          ],
        ],

        /** ... */
      ]
    ];
  }
}

 

Sie können diesen neuen Intent im Postman durch den Request Zoo CountIntent ausführen lassen. Im Body der Response sollten Sie dann erfahren, wie viele Tiere in Ihrem Zoo leben.

Nachdem Sie die Grundlagen geschaffen haben, war die Implementation des zweiten Intents für Sie hoffentlich einfach umzusetzen. Durch den Wechsel auf den Branch step3 mithilfe von $ git checkout step3 können Sie auch diese Änderungen leicht nachvollziehen.

Mit Slot Types arbeiten

Bisher haben Sie die beiden Intents noch ohne Slot Types verwendet. Im nächsten Schritt definieren Sie nun einen eigenen Slot Type und setzen ihn auch ein. Loggen Sie sich dafür wieder im Alexa Skills Kit ein und wählen Sie Ihren Skill aus. Wechseln Sie dann auf den Bereich Interaction Model und starten Sie den Skill Builder. Fügen Sie nun einen neuen Slot Type hinzu, den Sie SPECIES nennen. Als Werte tragen Sie Vögel, Säugetiere, Fische und Tiere ein. Tragen Sie für jeden Wert als Synonym auch die Einzahl ein, also entsprechend Vogel, Säugetier, Fisch und Tier. Abbildung 3 zeigt, wie die Einrichtung des Slot Types aussehen sollte.

Abb. 3: Einrichtung des Slot Types

Nun wählen Sie links in der Liste den AnimalIntent aus und bearbeiten ihn. Zuerst fügen Sie auf der rechten Seite den neuen Slot species hinzu und verknüpfen ihn mit Ihrem Slot Type SPECIES. Nun klicken Sie in der Mitte eine Utterance an, z. B. Welches Tier gibt es noch in meinem Zoo, und tauschen das Wort Tier durch {species} aus. Damit verknüpfen Sie die Utterance mit dem Slot. Dies führen Sie mit allen Utterances durch. Um die grammatikalische Korrektheit der Utterances zu wahren, können Sie auch noch die Utterances Nenne mir einen {species} aus meinem Zoo und Welchen {species} gibt es noch in meinem Zoo hinzufügen. Am Ende sollte der Intent so aussehen wie in Abbildung 4 gezeigt. Danach klicken Sie erst auf Save Model und danach auf Build Model und warten ab, bis der Prozess abgeschlossen ist.

Abb.4: Der fertige Intent

Um sicherzustellen, dass die Änderungen am Interaction Model korrekt waren, können Sie in den Testbereich des Alexa Skills Kit wechseln und die neuen Utterances ausprobieren. Geben Sie im Service Simulator Welches Säugetier gibt es in meinem Zoo ein; im JSON Service Request sollte dann ein Slot mit dem Wert Säugetier angezeigt werden. Im Postman können Sie den AnimalIntent mit Slot testen, indem Sie den Request Zoo AnimalIntent (with slot) ausführen. Ihnen sollten aber immer noch auch Vögel und Fische ausgegeben werden, auch wenn Sie explizit nach einem Säugetier gefragt haben.

Wenn diese Tests erfolgreich waren, können Sie die Umsetzung im Skill-Code beginnen. Dabei ist der zusätzlich zu entwickelnde Code recht überschaubar, da Ihnen der bisherige Aufbau des AnimalIntent bei der Erweiterung sehr entgegenkommt. Listing 10 zeigt alle erforderlichen Änderungen:

  • Die neue Methode getSpeciesSlot() hat die Aufgabe, den Wert für den Tierart-Slot-Type zu ermitteln. Dabei greift sie auf den aktuellen Wert aus dem Alexa Request zu. Da sowohl Einzahl als auch Mehrzahl sowie Kleinschrift als auch Großschrift und zudem die Sprache beachtet werden müssen, wird der Schlüssel für die einzelnen Tierarten anhand einer switch-Anweisung ermittelt und zurückgegeben. Wenn keine bekannte Tierart übergeben wurde, wird null zurückgegeben. Zugegeben, das lässt sich sicherlich sauberer implementieren, aber für unsere Zwecke ist die Methode ausreichend.
  • Die vorhandene Methode getRandomAnimal() wird um einen Parameter erweitert, der die aktuell gewünschte Tierart enthält. Wird kein Wert für die Tierart übergeben, wird die Tierart zufällig ausgewählt. Ansonsten wird die gewünschte Tierart genommen, um dann ein Tier zufällig auszuwählen. Der Rest der Methode bleibt unverändert.
  • In der vorhandenen animalIntent()-Methode wird zu Beginn zuerst die getSpeciesSlot()-Methode aufgerufen und der ermittelte Wert danach an die getRandomAnimal()-Methode übergeben. Der Rest dieser Methode kann ebenfalls unverändert bleiben.
<?php
namespace Zoo\Intent;

use TravelloAlexaLibrary\Request\RequestType\IntentRequestType;
use TravelloAlexaLibrary\Response\AlexaResponse;
use TravelloAlexaLibrary\Response\Card\Standard;
use TravelloAlexaLibrary\Response\OutputSpeech\SSML;

class AnimalIntent extends AbstractIntent
{
  /** ... */

  public function handle(string $smallImageUrl, string $largeImageUrl): AlexaResponse
  {
    $this->sessionAnimals = $this->getAlexaResponse()->getSessionContainer()->getAttribute('animals');

    $speciesSlot = $this->getSpeciesSlot();

    $randomAnimal = $this->getRandomAnimal($speciesSlot);

    /** ... */
  }

  private function getSpeciesSlot()
  {
    /** @var IntentRequestType $intentRequest */
    $intentRequest = $this->getAlexaRequest()->getRequest();

    $speciesSlot = $intentRequest->getIntent()->getSlotValue('species');

    switch ($speciesSlot) {
      case 'Vogel':
      case 'vogel':
      case 'Vögel':
      case 'vögel':
      case 'Bird':
      case 'bird':
      case 'Birds':
      case 'birds':
      return 'V';

      case 'Säugetier':
      case 'säugetier':
      case 'Säugetiere':
      case 'säugetiere':
      case 'Mammal':
      case 'mammal':
      case 'Mammals':
      case 'mammals':
      return 'S';

      case 'Fisch':
      case 'fisch':
      case 'Fische':
      case 'fische':
      case 'Fish':
      case 'fish':
      case 'Fishes':
      case 'fishes':
      return 'F';

      default:
      return null;
    }
  }

  private function getRandomAnimal(string $speciesSlot = null)
  {
    $locale = $this->getAlexaRequest()->getRequest()->getLocale();

    do {
      $randomType = is_null($speciesSlot) ? array_rand($this->getAnimalList()[$locale]) : $speciesSlot;
      $randomAnimalKey = array_rand($this->getAnimalList()[$locale][$randomType]);
      $randomAnimal = $this->getAnimalList()[$locale][$randomType][$randomAnimalKey];
    } while (in_array($randomAnimal, $this->sessionAnimals));

    /** ... */
  }
}

 

Nun führen Sie im Postman den Request Zoo AnimalIntent (with slot) erneut aus. Jetzt sollten Ihnen auf jeden Fall nur noch Säugetiere zurückgegeben werden. Sie können den Wert für den species-Slot auch anpassen oder sogar einen nicht unterstützten Wert ausprobieren. Versuchen Sie es einfach.

Das Arbeiten mit eigenen Slot Types in Ihrem Skill-Code ist durch die PHP Library Travello Alexa Library recht einfach umzusetzen. Sie sollten sich auf die Werte im Alexa Service Request jedoch nicht komplett verlassen und diese Werte immer in Ihrem Skill-Code aufbereiten. Eine 1:1-Übernahme könnte sonst zu unerwarteten Fehlern führen. Auch diesen Implementierungsschritt können Sie nachstellen, wenn Sie mit $ git checkout step4 direkt den Branch step4 auschecken.

Skill online bringen und testen

Nachdem der Skill-Code nun erst einmal abgeschlossen ist und die Konfiguration im Alexa Skills Kit auch soweit fertig sein sollte, ist es an der Zeit, den Skill-Code auf Ihren HTTPS-Endpunktserver zu deployen. Welchen Weg Sie dafür einschlagen, bleibt Ihnen überlassen. Da gibt es bei der Skill-Entwicklung keine Besonderheiten zu berücksichtigen. Sie sollten nur darauf achten, dass der Skill genau über den URL erreichbar ist, den Sie als URL für Endpunkt unter Configuration im Alexa Skills Kit festgelegt haben.

Wenn Sie z. B. den URL https://www.mein-skillserver.de/zoo/ verwenden möchten, wundern Sie sich nicht, dass der Aufruf des URL im Browser zu einem Fehler oder sogar einer weißen Seite führen kann. Das Routing für die Anwendung ist so eingerichtet, dass nur auf einen POST Request reagiert werden soll. Rufen Sie stattdessen direkt die Datenschutzerklärung mit https://www.mein-skillserver.de/zoo/privacy auf, dann sollte Ihnen diese Seite angezeigt werden.

Alternativ können Sie auch die Aufrufe im Postman so abändern, dass Sie nun den neuen URL verwenden, statt den lokalen Virtual Host zu nehmen. Sollte es hier zu Fehlern kommen, war das Deployment nicht erfolgreich. Vielleicht fehlen PHP Extensions oder Sie haben das Vendor-Verzeichnis nicht mit übertragen – es gibt da unzählige Fehlerquellen. Wenn Sie auch diese Hürde genommen haben, können Sie Ihren ersten Test mit einem Amazon Echo oder Echo Dot machen.

Der Servicesimulator für die Tests muss mit Vorsicht genossen werden. Beim Schreiben dieses Artikels stand nämlich ein deutlicher Fehlerhinweis direkt beim Servicesimulator. Dennoch sollte dies die nächste Anlaufstelle für Ihre Tests sein. Sie müssen im Simulator den Skill mit Alexa starte mein Zoo starten und dann können Sie den Service Request und die Service Response näher betrachten. Danach können Sie einmal Nenne mir ein Tier aus meinem Zoo schreiben. Schauen Sie sich auch hier Request und Response an. Wenn Sie die Phrase wiederholen, sollten nach und nach die letzten genannten Tiere auch in den Attributen bei dem Request auftauchen. Durch Abbrechen können Sie den Skill beenden.

Der nächste wichtige Test sollte dann direkt auf Ihrem Amazon Echo oder Echo Dot erfolgen. Starten Sie den Skill durch Ihre Sprache mit denselben Phrasen, die Sie eben beim Servicesimulator ausprobiert haben. Wenn alles glattgegangen ist, sollte Ihr Test nun lauffähig sein. Nun sollten Sie weitere Tests ausführen, die Funktionalität erweitern und auch mal unerwartet antworten und schauen, wie sinnvoll Ihr Skill dabei reagiert. Auch den im ersten Teil dieser Artikelserie angesprochenen Betatest sollten Sie ausführlich nutzen. Erst dann, wenn alles zufriedenstellend läuft, sollten Sie die Freigabe beantragen.

Fazit

Sie haben in dieser zweiteiligen Artikelserie alle Grundlagen vermittelt bekommen, mit denen Sie eigene Alexa Skills auf Basis von PHP und Expressive umsetzen können. Der Teufel liegt bei der Konfiguration und Implementation aber durchaus im Detail. Ich drücke Ihnen aber dennoch die Daumen, dass Sie bald Ihren ersten eigenen Alexa Skill erfolgreich auf die Beine stellen.

AUF DEM LAUFENDEN BLEIBEN!

BEHIND THE TRACKS

Web Design & Development
Design thinking out of the box

Online Marketing
Die neuesten Marketing Trends

User Experience Design
User Experiences im Fokus

Ideation & Design Thinking
Von Kleinstunternehmen bis Enterprise-Level