xiifactors/azure-functions-bundle

简化使用 Azure Functions 运行 Symfony 应用

0.1.6 2023-07-28 16:25 UTC

This package is auto-updated.

Last update: 2024-09-28 18:58:49 UTC


README

安装

步骤 1:下载包

打开命令行,进入项目目录,并执行以下命令以下载此包的最新稳定版本

$ composer require xiifactors/azure-functions-bundle

步骤 2:启用包

然后,将包添加到项目 config/bundles.php 文件中注册的包列表中,以启用包

// config/bundles.php

return [
    // ...
    XIIFactors\AzureFunctions\AzureFunctionsBundle::class => ['all' => true],
];

步骤 3:导入路由

然后,在项目 config/routes.yaml 文件中添加以下内容以导入路由

// config/routes.yaml

app_annotations:
    resource: '@AzureFunctionsBundle/src/Controller/'
    type: annotation

步骤 4:导入服务

然后,在项目 config/services.yaml 文件中添加以下内容以导入服务

// config/services.yaml

imports:
    - { resource: '@AzureFunctionsBundle/config/services.yaml' }

步骤 5:复制所需文件

此仓库包括示例 host.jsonlocal.settings.json 文件(以 .example 结尾),以及一个 bash 脚本(run.sh),该脚本包含一行代码,用于使用正确的环境变量执行 PHP 网络服务器。

在项目根目录下打开终端并运行以下命令

cp vendor/xiifactors/azure-functions-bundle/host.json.example host.json
cp vendor/xiifactors/azure-functions-bundle/local.settings.json.example local.settings.json
cp vendor/xiifactors/azure-functions-bundle/run.sh .

步骤 6:创建 Azure Function HTTP 入口点

在项目根目录下创建一个名为 HttpEntrypoint 的目录。

在此目录中创建一个包含以下内容的 function.json 文件

// HttpEntrypoint/functions.json

{
    "disabled": false,
    "bindings": [
        {
            "name": "req",
            "authLevel": "anonymous",
            "type": "httpTrigger",
            "direction": "in",
            "route": "{path}",
            "methods": ["GET", "POST", "PUT", "PATCH", "DELETE"]
        },
        {
            "name": "$return",
            "type": "http",
            "direction": "out"
        }
    ]
}

步骤 7:创建第一个控制器

您必须使用 ResponseDto 来帮助格式化响应

// src/Controller/HealthController.php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use XIIFactors\AzureFunctions\Dto\ResponseDto;

#[
    Route(
        path: '/api/health',
        name: 'myapp.health',
        defaults: ['_format' => 'json'],
        methods: ['GET'],
    )
]
class HealthController extends AbstractController
{
    public function __invoke(): Response
    {
        return new JsonResponse(new ResponseDto(
            ReturnValue: json_encode([
                'success' => true,
            ])
        ));
    }
}

步骤 8:启动函数

在您的终端中运行以下命令

func start

现在您应该能够在本地运行以下 curl 请求

curl -vvv https://:7071/api/health

并收到

{
    "success": true
}

enableForwardingHttpRequest

这是在自定义处理程序的 host.json 文件中设置的标志。我们的 host.json.example 默认已启用。

如果此标志为 false,则表示 Azure Function 主机将向 URI /{NameOfFunction}(例如 /HttpEntrypoint)发送 POST 请求,包括原始请求的所有详细信息(如 URI、方法、参数等)在正文。在这种情况下,HttpEntrypointController 的任务是处理该请求,然后向所需的路由发送内部请求。

但是,如果标志设置为 true,则表示函数主机将简单地转发原始请求到我们的应用程序。此包包括一个 ConvertResponseListener(当您导入服务时启用 - 如步骤 4 中所述),它可以无缝处理这两种情况。

注意:当此标志为 true 时,它将提高性能,但请注意,只有当函数使用具有 HTTP 触发器 "in" 绑定和 HTTP "out" 绑定的 HTTP 触发器定义时,才会发生转发。如果存在任何其他绑定,则请求不会转发,并且行为将回退到与标志为 false 时的行为一样。 - 请参阅官方文档

输出绑定

以下示例显示了一个接收 HTTP 请求并使用输出绑定将消息写入队列的函数。在此示例中,您需要设置 ResponseDto 对象的 Outputs 属性。

注意:这将使 enableForwardingHttpRequest 变为 null,即使它设置为 true,因为我们已定义了一个额外的绑定。

function.json

// ./Example/function.json

{
  "disabled": false,
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "route": "example"
    },
    {
      "type": "http",
      "direction": "out",
      "name": "$return"
    },
    {
      "type": "queue",
      "direction": "out",
      "name": "exampleItem",
      "queueName": "example-queue",
      "connection": "AzureWebJobsStorage"
    }
  ]
}

控制器

// src/Controller/ExampleController.php

namespace App\Controller;

use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use XIIFactors\AzureFunctions\Dto\ResponseDto;

#[
    Route(
        path: '/api/example',
        name: 'myapp.output_example',
        defaults: ['_format' => 'json'],
        methods: ['POST'],
    )
]
class ExampleController extends AbstractController
{
    public function __invoke(): Response
    {
        return new JsonResponse(new ResponseDto(
            // Sends the message to the "example" queue via the "exampleItem" output binding
            Outputs: ['exampleItem' => json_encode(['subject' => 'example'])],
            ReturnValue: json_encode([
                'success' => true,
            ])
        ));
    }
}

输入绑定

如果您只处理HTTP,那么您只需创建像上面的HealthController这样的控制器。

如果您需要处理其他类型的输入,那么它将类似,但请注意,函数宿主将向函数的名称发送POST请求,输入的详细信息将包含在请求体中。如果您想,可以使用RequestDto来映射请求数据。

function.json

// ./QueueFunction/function.json

{
    "disabled": false,
    "bindings": [
        {
            "type": "queueTrigger",
            "direction": "in",
            "name": "exampleItem",
            "queueName": "example"
        },
        {
            "type": "blob",
            "direction": "out",
            "name": "outputBlob",
            "path": "example/{rand-guid}"
        }
    ]
}

控制器

// src/Controller/QueueFunctionController.php

namespace App\Controller;

use RuntimeException;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
use Symfony\Component\Routing\Annotation\Route;
use XIIFactors\AzureFunctions\Dto\RequestDto;
use XIIFactors\AzureFunctions\Dto\ResponseDto;

#[
    Route(
        path: '/QueueFunction',
        name: 'myapp.input_example',
        defaults: ['_format' => 'json'],
        methods: ['POST'],
    )
]
class QueueFunctionController extends AbstractController
{
    public function __invoke(#[MapRequestPayload] RequestDto $rd): Response
    {
        // Grab the queue item
        $queueItem = $rd->Data['exampleItem'] ?? throw new RuntimeException('Queue item is missing');

        // Do something with queue item...
        $decoded = json_decode($queueItem, true);

        // Write queue item to blob storage
        return new JsonResponse(new ResponseDto(
            Outputs: ['outputBlob' => $queueItem]
        ));
    }
}

将函数部署到Azure

部署PHP函数的方法不止一种,这里提供的方法是创建一个Docker镜像,然后更新函数应用以使用该镜像而不是默认的镜像。

1. 创建Dockerfile

# ./Dockerfile

FROM mcr.microsoft.com/azure-functions/dotnet:4-appservice 
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
    AzureFunctionsJobHost__Logging__Console__IsEnabled=true

# Install PHP 8.1
RUN apt -y install lsb-release apt-transport-https ca-certificates 
RUN wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
RUN echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | tee /etc/apt/sources.list.d/php.list
RUN apt update && apt install php8.1 php8.1-xml -y

# Get Composer
COPY --from=composer:latest /usr/bin/composer /usr/local/bin/composer

# Copy codebase into web root
COPY . /home/site/wwwroot

# Install composer deps and the bundle public/bundles/azurefunctions/index.php (see run.sh)
RUN cd /home/site/wwwroot && \
    composer install -o --no-scripts && \
    bin/console assets:install

2. 构建并将镜像推送到您的ACR(Azure容器注册库)

az login --identity
az acr login --name {YOUR_ACR_NAME}

az acr build \
  --registry {YOUR_REGISTRY_ID} \
  --image {YOUR_REGISTRY_ID}.azurecr.io/examplefunction:{YOUR_IMAGE_TAG}

3. 将必要的环境变量添加到函数应用中

az login --identity

az functionapp config appsettings set \
  --resource-group {YOUR_RESOURCE_GROUP} \
  --name {YOUR_FUNCTION_NAME} \
  --settings APP_ENV=${APP_ENV} APP_DEBUG=${APP_DEBUG}

4. 部署新镜像

az login --identity

az functionapp config container set \
  --resource-group {YOUR_RESOURCE_GROUP} \
  --name {YOUR_FUNCTION_NAME} \
  --image {YOUR_REGISTRY_ID}.azurecr.io/examplefunction:{YOUR_IMAGE_TAG}

几分钟后,镜像应该已更新并部署了新的函数。