diff --git a/README.md b/README.md index 56e0ab9..24bbd80 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,76 @@ -# Amazon alexa php library -This library is a helper for google actions with php. \ No newline at end of file +# Google actions php library +This library is a helper for google actions with php. + +## Install via composer +Require the package with composer: +``` +composer require maxbeckers/google-actions-php +``` + +## Usage +Handle the request: +- map request data to request object +- validate request +- handle request data +- create response +- send response + +### Map request data to request object +Map needed request headers and request body to `Request`. +```php +use MaxBeckers\GoogleActions\Request\Request; +... +$requestBody = file_get_contents('php://input'); +$googleRequest = Request::fromGoogleRequest($requestBody); +``` +### Validate request +The `RequestValidator` will handle the google request validation. +```php +use MaxBeckers\GoogleActions\Validation\RequestValidator; +... +$validator = new RequestValidator(); +$validator->validate($googleRequest); +``` +### Register request handlers +For different requests it's helpful to create different RequestHandlers. +```php +use MaxBeckers\GoogleActions\RequestHandler\RequestHandlerRegistry; +... +$requestHandlerRegistry = new RequestHandlerRegistry(); +$requestHandlerRegistry->addHandler($myRequestHandler); +``` +### Use registry to handle request +```php +use MaxBeckers\GoogleActions\RequestHandler\RequestHandlerRegistry; +... +$requestHandler = $requestHandlerRegistry->getSupportingHandler($googleRequest); +$response = $requestHandler->handleRequest($googleRequest); +``` +### Render response +```php +header('Content-Type: application/json'); +echo json_encode($response); +exit(); +``` +### Create a new request handler +The new request handler must extend `AbstractRequestHandler`. +First implement the abstract `supportsRequest`-method. +```php +public function supportsRequest(Request $request): bool +{ + return true; // check request data +} +``` +Then implement the `handleRequest`-method. For simple responses there is a `ResponseHelper`. +```php +use MaxBeckers\GoogleActions\Helper\ResponseHelper; +... +/** @var ResponseHelper */ +private $responseHelper; +... +public function handleRequest(Request $request): Response +{ + // todo set needed response data + return $responseHelper->respond('Success :)'); +} +``` diff --git a/composer.json b/composer.json index f9b4758..7a3b41c 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,7 @@ "keywords": [ "google", "actions", + "home", "php" ], "license": "MIT", diff --git a/examples/Handlers/SimpleRequestHandler.php b/examples/Handlers/SimpleRequestHandler.php new file mode 100644 index 0000000..9cd6bdd --- /dev/null +++ b/examples/Handlers/SimpleRequestHandler.php @@ -0,0 +1,36 @@ + + */ +class SimpleRequestHandler extends AbstractRequestHandler +{ + /** + * @var ResponseHelper + */ + private $responseHelper; + + /** + * {@inheritdoc} + */ + public function supportsRequest(Request $request): bool + { + // support all requests, should not be done. + return true; + } + + /** + * {@inheritdoc} + */ + public function handleRequest(Request $request): Response + { + return $this->responseHelper->respond('Success :)'); + } +} \ No newline at end of file diff --git a/examples/simple-request.php b/examples/simple-request.php index dff94b4..e98e69e 100644 --- a/examples/simple-request.php +++ b/examples/simple-request.php @@ -1,16 +1,26 @@ validate($googleRequest); + + // add handlers to registry + $mySimpleRequestHandler = new SimpleRequestHandler(); + $requestHandlerRegistry = new RequestHandlerRegistry(); + $requestHandlerRegistry->addHandler($mySimpleRequestHandler); + + $response = $requestHandlerRegistry->getSupportingHandler($googleRequest)->handleRequest($googleRequest); // render response header('Content-Type: application/json'); echo json_encode($response); diff --git a/src/Exception/InvalidRequestException.php b/src/Exception/InvalidRequestException.php new file mode 100644 index 0000000..a833895 --- /dev/null +++ b/src/Exception/InvalidRequestException.php @@ -0,0 +1,10 @@ + + */ +class InvalidRequestException extends \Exception +{ +} diff --git a/src/Exception/MissingRequestHandlerException.php b/src/Exception/MissingRequestHandlerException.php new file mode 100644 index 0000000..4a634d8 --- /dev/null +++ b/src/Exception/MissingRequestHandlerException.php @@ -0,0 +1,10 @@ + + */ +class MissingRequestHandlerException extends \Exception +{ +} diff --git a/src/Helper/ResponseHelper.php b/src/Helper/ResponseHelper.php new file mode 100644 index 0000000..5f9b881 --- /dev/null +++ b/src/Helper/ResponseHelper.php @@ -0,0 +1,60 @@ + + */ +class ResponseHelper +{ + + /** + * @var Response + */ + public $response; + + /** + * ResponseHelper constructor creates a new response object. + */ + public function __construct() + { + $this->response = new Response(); + } + + /** + * @param string $text + * + * @return Response + */ + public function respond(string $text): Response + { + $simpleResponse = new SimpleResponse(); + $simpleResponse->textToSpeech = $text; + $simpleResponse->displayText = $text; + $item = new Item(); + $item->simpleResponse = $simpleResponse; + $richInitialPrompt = new RichResponse(); + $richInitialPrompt->items[] = $item; + $inputPrompt = new InputPrompt(); + $inputPrompt->richInitialPrompt = $richInitialPrompt; + + $intent = new ExpectedIntent(); + $intent->intent = ExpectedIntent::TYPE_TEXT; + + $expectedInput = new ExpectedInput(); + $expectedInput->inputPrompt = $inputPrompt; + $expectedInput->possibleIntents[] = $intent; + + $this->response->expectedInputs[] = $expectedInput; + + return $this->response; + } +} diff --git a/src/RequestHandler/AbstractRequestHandler.php b/src/RequestHandler/AbstractRequestHandler.php new file mode 100644 index 0000000..46bf765 --- /dev/null +++ b/src/RequestHandler/AbstractRequestHandler.php @@ -0,0 +1,30 @@ + + */ +abstract class AbstractRequestHandler +{ + /** + * Check if the request handler can handle given request. + * + * @param Request $request + * + * @return bool + */ + public abstract function supportsRequest(Request $request): bool; + + /** + * Handle the given request end return a response object. + * + * @param Request $request + * + * @return Response + */ + public abstract function handleRequest(Request $request): Response; +} diff --git a/src/RequestHandler/RequestHandlerRegistry.php b/src/RequestHandler/RequestHandlerRegistry.php new file mode 100644 index 0000000..58c3ac6 --- /dev/null +++ b/src/RequestHandler/RequestHandlerRegistry.php @@ -0,0 +1,43 @@ + + */ +class RequestHandlerRegistry +{ + /** + * @var AbstractRequestHandler[] + */ + private $requestHandlers = []; + + /** + * @param Request $request + * + * @throws MissingRequestHandlerException + * + * @return AbstractRequestHandler + */ + public function getSupportingHandler(Request $request): AbstractRequestHandler + { + foreach ($this->requestHandlers as $requestHandler) { + if ($requestHandler->supportsRequest($request)) { + return $requestHandler; + } + } + + throw new MissingRequestHandlerException(); + } + + /** + * @param AbstractRequestHandler $handler + */ + public function addHandler(AbstractRequestHandler $handler) + { + $this->requestHandlers[] = $handler; + } +} diff --git a/src/Response/Action.php b/src/Response/Action.php new file mode 100644 index 0000000..20b4b29 --- /dev/null +++ b/src/Response/Action.php @@ -0,0 +1,31 @@ + + */ +class Action +{ + const TYPE_UNKNOWN = 'UNKNOWN'; + const TYPE_VIEW_DETAILS = 'VIEW_DETAILS'; + const TYPE_MODIFY = 'MODIFY'; + const TYPE_CANCEL = 'CANCEL'; + const TYPE_RETURN = 'RETURN'; + const TYPE_EXCHANGE = 'EXCHANGE'; + const TYPE_EMAIL = 'EMAIL'; + const TYPE_CALL = 'CALL'; + const TYPE_REORDER = 'REORDER'; + const TYPE_REVIEW = 'REVIEW'; + const TYPE_CUSTOMER_SERVICE = 'CUSTOMER_SERVICE'; + + /** + * @var string|null + */ + public $type; + + /** + * @var Button|null + */ + public $button; +} diff --git a/src/Response/BasicCard.php b/src/Response/BasicCard.php new file mode 100644 index 0000000..a7fe974 --- /dev/null +++ b/src/Response/BasicCard.php @@ -0,0 +1,34 @@ + + */ +class BasicCard +{ + /** + * @var string|null + */ + public $title; + + /** + * @var string|null + */ + public $subtitle; + + /** + * @var string|null + */ + public $formattedText; + + /** + * @var Image|null + */ + public $image; + + /** + * @var Button[] + */ + public $buttons = []; +} diff --git a/src/Response/Button.php b/src/Response/Button.php new file mode 100644 index 0000000..7cd2c73 --- /dev/null +++ b/src/Response/Button.php @@ -0,0 +1,19 @@ + + */ +class Button +{ + /** + * @var string|null + */ + public $title; + + /** + * @var OpenUrlAction|null + */ + public $openUrlAction; +} diff --git a/src/Response/CancellationInfo.php b/src/Response/CancellationInfo.php new file mode 100644 index 0000000..82c890e --- /dev/null +++ b/src/Response/CancellationInfo.php @@ -0,0 +1,14 @@ + + */ +class CancellationInfo +{ + /** + * @var string|null + */ + public $reason; +} diff --git a/src/Response/CustomPushMessage.php b/src/Response/CustomPushMessage.php new file mode 100644 index 0000000..afe4920 --- /dev/null +++ b/src/Response/CustomPushMessage.php @@ -0,0 +1,14 @@ + + */ +class CustomPushMessage +{ + /** + * @var OrderUpdate|null + */ + public $orderUpdate; +} diff --git a/src/Response/ExpectedInput.php b/src/Response/ExpectedInput.php new file mode 100644 index 0000000..66c058c --- /dev/null +++ b/src/Response/ExpectedInput.php @@ -0,0 +1,24 @@ + + */ +class ExpectedInput +{ + /** + * @var InputPrompt|null + */ + public $inputPrompt; + + /** + * @var ExpectedIntent[] + */ + public $possibleIntents = []; + + /** + * @var string[] + */ + public $speechBiasingHints = []; +} diff --git a/src/Response/ExpectedIntent.php b/src/Response/ExpectedIntent.php new file mode 100644 index 0000000..4c9bffb --- /dev/null +++ b/src/Response/ExpectedIntent.php @@ -0,0 +1,23 @@ + + */ +class ExpectedIntent +{ + const TYPE_TEXT = 'actions.intent.TEXT'; + + /** + * @var string|null + */ + public $intent; + + /** + * Todo + * + * @var array + */ + public $inputValueData = []; +} diff --git a/src/Response/FinalResponse.php b/src/Response/FinalResponse.php new file mode 100644 index 0000000..643c25c --- /dev/null +++ b/src/Response/FinalResponse.php @@ -0,0 +1,14 @@ + + */ +class FinalResponse +{ + /** + * @var RichResponse|null + */ + public $richResponse; +} diff --git a/src/Response/FulfillmentInfo.php b/src/Response/FulfillmentInfo.php new file mode 100644 index 0000000..ca5f78d --- /dev/null +++ b/src/Response/FulfillmentInfo.php @@ -0,0 +1,16 @@ + + */ +class FulfillmentInfo +{ + /** + * A timestamp in RFC3339 UTC "Zulu" format, accurate to nanoseconds. Example: "2014-10-02T15:01:23.045123456Z". + * + * @var string|null + */ + public $deliveryTime; +} diff --git a/src/Response/Image.php b/src/Response/Image.php new file mode 100644 index 0000000..3912a60 --- /dev/null +++ b/src/Response/Image.php @@ -0,0 +1,29 @@ + + */ +class Image +{ + /** + * @var string|null + */ + public $url; + + /** + * @var string|null + */ + public $accessibilityText; + + /** + * @var int|null + */ + public $height; + + /** + * @var int|null + */ + public $width; +} diff --git a/src/Response/InTransitInfo.php b/src/Response/InTransitInfo.php new file mode 100644 index 0000000..646a589 --- /dev/null +++ b/src/Response/InTransitInfo.php @@ -0,0 +1,16 @@ + + */ +class InTransitInfo +{ + /** + * A timestamp in RFC3339 UTC "Zulu" format, accurate to nanoseconds. Example: "2014-10-02T15:01:23.045123456Z". + * + * @var string|null + */ + public $updatedTime; +} diff --git a/src/Response/InputPrompt.php b/src/Response/InputPrompt.php new file mode 100644 index 0000000..9ad856a --- /dev/null +++ b/src/Response/InputPrompt.php @@ -0,0 +1,19 @@ + + */ +class InputPrompt +{ + /** + * @var RichResponse|null + */ + public $richInitialPrompt; + + /** + * @var SimpleResponse[] + */ + public $noInputPrompts = []; +} diff --git a/src/Response/Item.php b/src/Response/Item.php new file mode 100644 index 0000000..c79dedf --- /dev/null +++ b/src/Response/Item.php @@ -0,0 +1,24 @@ + + */ +class Item +{ + /** + * @var SimpleResponse|null + */ + public $simpleResponse; + + /** + * @var BasicCard|null + */ + public $basicCard; + + /** + * @var StructuredResponse|null + */ + public $structuredResponse; +} diff --git a/src/Response/LineItemUpdate.php b/src/Response/LineItemUpdate.php new file mode 100644 index 0000000..13f96be --- /dev/null +++ b/src/Response/LineItemUpdate.php @@ -0,0 +1,24 @@ + + */ +class LineItemUpdate +{ + /** + * @var OrderState|null + */ + public $orderState; + + /** + * @var Price|null + */ + public $price; + + /** + * @var string|null + */ + public $reason; +} diff --git a/src/Response/LinkOutSuggestion.php b/src/Response/LinkOutSuggestion.php new file mode 100644 index 0000000..84b8821 --- /dev/null +++ b/src/Response/LinkOutSuggestion.php @@ -0,0 +1,19 @@ + + */ +class LinkOutSuggestion +{ + /** + * @var string|null + */ + public $destinationName; + + /** + * @var string|null + */ + public $url; +} diff --git a/src/Response/Money.php b/src/Response/Money.php new file mode 100644 index 0000000..6398f4a --- /dev/null +++ b/src/Response/Money.php @@ -0,0 +1,24 @@ + + */ +class Money +{ + /** + * @var string|null + */ + public $currencyCode; + + /** + * @var string|null + */ + public $units; + + /** + * @var int|null + */ + public $nanos; +} diff --git a/src/Response/OpenUrlAction.php b/src/Response/OpenUrlAction.php new file mode 100644 index 0000000..c85b761 --- /dev/null +++ b/src/Response/OpenUrlAction.php @@ -0,0 +1,14 @@ + + */ +class OpenUrlAction +{ + /** + * @var string|null + */ + public $url; +} diff --git a/src/Response/OrderState.php b/src/Response/OrderState.php new file mode 100644 index 0000000..03945a3 --- /dev/null +++ b/src/Response/OrderState.php @@ -0,0 +1,19 @@ + + */ +class OrderState +{ + /** + * @var string|null + */ + public $state; + + /** + * @var string|null + */ + public $label; +} diff --git a/src/Response/OrderUpdate.php b/src/Response/OrderUpdate.php new file mode 100644 index 0000000..0d18301 --- /dev/null +++ b/src/Response/OrderUpdate.php @@ -0,0 +1,95 @@ + + */ +class OrderUpdate +{ + /** + * @var OrderState|null + */ + public $orderState; + + /** + * @var Action[] + */ + public $orderManagementActions = []; + + /** + * @var string|null + */ + public $updateTime; + + /** + * @var Price|null + */ + public $totalPrice; + + /** + * @var LineItemUpdate|[] + */ + public $lineItemUpdates = []; + + /** + * @var UserNotification|null + */ + public $userNotification; + + /** + * Todo + * + * @var array + */ + public $infoExtension = []; + + /** + * @var string|null + */ + public $googleOrderId; + + /** + * @var string|null + */ + public $actionOrderId; + + /** + * @var RejectionInfo|null + */ + public $rejectionInfo; + + /** + * @var Receipt|null + */ + public $receipt; + + /** + * @var CancellationInfo|null + */ + public $cancellationInfo; + + /** + * @var InTransitInfo|null + */ + public $inTransitInfo; + + /** + * @var FulfillmentInfo|null + */ + public $fulfillmentInfo; + + /** + * @var ReturnInfo|null + */ + public $returnInfo; + + /** + * @param string $key + * @param LineItemUpdate $lineItemUpdate + */ + public function addLineItemUpdate(string $key, LineItemUpdate $lineItemUpdate) + { + $this->lineItemUpdates[$key] = $lineItemUpdate; + } +} diff --git a/src/Response/Price.php b/src/Response/Price.php new file mode 100644 index 0000000..f9f625a --- /dev/null +++ b/src/Response/Price.php @@ -0,0 +1,23 @@ + + */ +class Price +{ + const TYPE_UNKNOWN = 'UNKNOWN'; + const TYPE_ESTIMATE = 'ESTIMATE'; + const TYPE_ACTUAL = 'ACTUAL'; + + /** + * @var string|null + */ + public $type; + + /** + * @var Money|null + */ + public $amount; +} diff --git a/src/Response/Receipt.php b/src/Response/Receipt.php new file mode 100644 index 0000000..4e9da48 --- /dev/null +++ b/src/Response/Receipt.php @@ -0,0 +1,14 @@ + + */ +class Receipt +{ + /** + * @var string|null + */ + public $confirmedActionOrderId; +} diff --git a/src/Response/RejectionInfo.php b/src/Response/RejectionInfo.php new file mode 100644 index 0000000..ab004e9 --- /dev/null +++ b/src/Response/RejectionInfo.php @@ -0,0 +1,22 @@ + + */ +class RejectionInfo +{ + const TYPE_UNKNOWN = 'UNKNOWN'; + const TYPE_PAYMENT_DECLINED = 'PAYMENT_DECLINED'; + + /** + * @var string|null + */ + public $type; + + /** + * @var string|null + */ + public $reason; +} diff --git a/src/Response/Response.php b/src/Response/Response.php index b03ec0e..6cf8baa 100644 --- a/src/Response/Response.php +++ b/src/Response/Response.php @@ -7,4 +7,38 @@ */ class Response { + /** + * @var string|null + */ + public $conversationToken; + + /** + * @var bool + */ + public $expectUserResponse = false; + + /** + * @var ExpectedInput[] + */ + public $expectedInputs = []; + + /** + * @var FinalResponse|null + */ + public $finalResponse; + + /** + * @var CustomPushMessage|null + */ + public $customPushMessage; + + /** + * @var ResponseMetadata|null + */ + public $responseMetadata; + + /** + * @var bool + */ + public $isInSandbox = false; } diff --git a/src/Response/ResponseMetadata.php b/src/Response/ResponseMetadata.php new file mode 100644 index 0000000..10037a0 --- /dev/null +++ b/src/Response/ResponseMetadata.php @@ -0,0 +1,14 @@ + + */ +class ResponseMetadata +{ + /** + * @var Status|null + */ + public $status; +} diff --git a/src/Response/ReturnInfo.php b/src/Response/ReturnInfo.php new file mode 100644 index 0000000..fcb58dc --- /dev/null +++ b/src/Response/ReturnInfo.php @@ -0,0 +1,14 @@ + + */ +class ReturnInfo +{ + /** + * @var string|null + */ + public $reason; +} diff --git a/src/Response/RichResponse.php b/src/Response/RichResponse.php new file mode 100644 index 0000000..c3d3a4b --- /dev/null +++ b/src/Response/RichResponse.php @@ -0,0 +1,24 @@ + + */ +class RichResponse +{ + /** + * @var Item|[] + */ + public $items = []; + + /** + * @var Suggestion[] + */ + public $suggestions = []; + + /** + * @var LinkOutSuggestion|null + */ + public $linkOutSuggestion; +} diff --git a/src/Response/SimpleResponse.php b/src/Response/SimpleResponse.php new file mode 100644 index 0000000..475ca2c --- /dev/null +++ b/src/Response/SimpleResponse.php @@ -0,0 +1,24 @@ + + */ +class SimpleResponse +{ + /** + * @var string|null + */ + public $textToSpeech; + + /** + * @var string|null + */ + public $ssml; + + /** + * @var string|null + */ + public $displayText; +} diff --git a/src/Response/Status.php b/src/Response/Status.php new file mode 100644 index 0000000..176a977 --- /dev/null +++ b/src/Response/Status.php @@ -0,0 +1,26 @@ + + */ +class Status +{ + /** + * @var int|null + */ + public $code; + + /** + * @var string|null + */ + public $message; + + /** + * todo + * + * @var array + */ + public $details = []; +} diff --git a/src/Response/StructuredResponse.php b/src/Response/StructuredResponse.php new file mode 100644 index 0000000..e013e7d --- /dev/null +++ b/src/Response/StructuredResponse.php @@ -0,0 +1,14 @@ + + */ +class StructuredResponse +{ + /** + * @var OrderUpdate|null + */ + public $orderUpdate; +} diff --git a/src/Response/Suggestion.php b/src/Response/Suggestion.php new file mode 100644 index 0000000..3024b46 --- /dev/null +++ b/src/Response/Suggestion.php @@ -0,0 +1,14 @@ + + */ +class Suggestion +{ + /** + * @var string|null + */ + public $title; +} diff --git a/src/Response/UserNotification.php b/src/Response/UserNotification.php new file mode 100644 index 0000000..5637f98 --- /dev/null +++ b/src/Response/UserNotification.php @@ -0,0 +1,19 @@ + + */ +class UserNotification +{ + /** + * @var string|null + */ + public $title; + + /** + * @var string|null + */ + public $text; +} diff --git a/src/Validation/RequestValidator.php b/src/Validation/RequestValidator.php new file mode 100644 index 0000000..06195ed --- /dev/null +++ b/src/Validation/RequestValidator.php @@ -0,0 +1,24 @@ + + */ +class RequestValidator +{ + /** + * Validate request data. + * + * @throws InvalidRequestException + * + * @param Request $request + */ + public function validate(Request $request) + { + // Todo validate request, throw error on invalid + } +}