xiifactors / azure-functions-bundle
简化使用 Azure Functions 运行 Symfony 应用
Requires
- php: >=8.1
- ext-ctype: *
- ext-iconv: *
- phpdocumentor/reflection-docblock: ^5.3
- phpstan/phpdoc-parser: ^1.23
- symfony/console: 6.3.*
- symfony/dotenv: 6.3.*
- symfony/flex: ^2
- symfony/framework-bundle: 6.3.*
- symfony/property-access: 6.3.*
- symfony/property-info: 6.3.*
- symfony/runtime: 6.3.*
- symfony/serializer: 6.3.*
- symfony/twig-bundle: 6.3.*
- symfony/validator: 6.3.*
- symfony/yaml: 6.3.*
Requires (Dev)
- dg/bypass-finals: ^1.4
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.2
- squizlabs/php_codesniffer: ^3.7
README
-
如果你使用 Homebrew,可以运行:
brew install azure-functions-core-tools@4
安装
步骤 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.json
和 local.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}
几分钟后,镜像应该已更新并部署了新的函数。