diff --git a/docs/environment/php.md b/docs/environment/php.md
index 5423658ad..4133c3358 100644
--- a/docs/environment/php.md
+++ b/docs/environment/php.md
@@ -48,6 +48,7 @@ Bref strives to include the most common PHP extensions. If a major PHP extension
- Core
+ - bcmath
- ctype
- curl
- date
@@ -66,6 +67,7 @@ Bref strives to include the most common PHP extensions. If a major PHP extension
- libxml
- mbstring
+ - mysqli
- mysqlnd
- opcache
- openssl
@@ -84,6 +86,7 @@ Bref strives to include the most common PHP extensions. If a major PHP extension
- session
- SimpleXML
- sodium
+ - SOAP
- SPL
- sqlite3
- standard
diff --git a/docs/frameworks/laravel.md b/docs/frameworks/laravel.md
index 9bd47647b..423848fa8 100644
--- a/docs/frameworks/laravel.md
+++ b/docs/frameworks/laravel.md
@@ -39,7 +39,7 @@ Resources:
Timeout: 30 # in seconds (API Gateway has a timeout of 30 seconds)
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-1:209497400698:layer:php-72-fpm:1'
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:php-72-fpm:4'
Events:
# The function will match all HTTP URLs
HttpRoot:
@@ -62,9 +62,9 @@ Resources:
Runtime: provided
Layers:
# PHP runtime
- - 'arn:aws:lambda:us-east-1:209497400698:layer:php-72:1'
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:php-72:4'
# Console layer
- - 'arn:aws:lambda:us-east-1:209497400698:layer:console:1'
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:console:4'
Outputs:
DemoHttpApi:
diff --git a/docs/frameworks/symfony.md b/docs/frameworks/symfony.md
index 2a6f388b4..aec3d3e45 100644
--- a/docs/frameworks/symfony.md
+++ b/docs/frameworks/symfony.md
@@ -39,7 +39,7 @@ Resources:
MemorySize: 1024
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fpm:1'
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fpm:4'
Events:
HttpRoot:
Type: Api
@@ -61,8 +61,8 @@ Resources:
Timeout: 120 # in seconds
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73:1' # PHP
- - 'arn:aws:lambda:us-east-1:209497400698:layer:console:1' # The "console" layer
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73:4' # PHP
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:console:4' # The "console" layer
Outputs:
DemoApi:
diff --git a/runtime/php/layers/fpm/php.ini b/runtime/php/layers/fpm/php.ini
index ecc6d7cc8..c75564852 100644
--- a/runtime/php/layers/fpm/php.ini
+++ b/runtime/php/layers/fpm/php.ini
@@ -19,6 +19,13 @@ opcache.max_accelerated_files=10000
zend_extension=opcache.so
+; This directive determines which super global arrays are registered when PHP
+; starts up. G,P,C,E & S are abbreviations for the following respective super
+; globals: GET, POST, COOKIE, ENV and SERVER.
+; We explicitly populate all variables else ENV is not populated by default.
+; See https://github.com/mnapoli/bref/pull/291
+variables_order="EGPCS"
+
; The lambda environment is not compatible with fastcgi_finish_request
; See https://github.com/mnapoli/bref/issues/214
disable_functions=fastcgi_finish_request
diff --git a/runtime/php/layers/function/php.ini b/runtime/php/layers/function/php.ini
index 3cdd86a73..70597b409 100644
--- a/runtime/php/layers/function/php.ini
+++ b/runtime/php/layers/function/php.ini
@@ -24,3 +24,10 @@ opcache.memory_consumption=128
opcache.max_accelerated_files=10000
zend_extension=opcache.so
+
+; This directive determines which super global arrays are registered when PHP
+; starts up. G,P,C,E & S are abbreviations for the following respective super
+; globals: GET, POST, COOKIE, ENV and SERVER.
+; We explicitly populate all variables else ENV is not populated by default.
+; See https://github.com/mnapoli/bref/pull/291
+variables_order="EGPCS"
diff --git a/runtime/php/php.Dockerfile b/runtime/php/php.Dockerfile
index 28a13a4cd..74e6d4ef1 100644
--- a/runtime/php/php.Dockerfile
+++ b/runtime/php/php.Dockerfile
@@ -408,8 +408,10 @@ RUN set -xe \
--with-gettext \
--enable-mbstring \
--with-pdo-mysql=shared,mysqlnd \
+ --with-mysqli \
--enable-pcntl \
--enable-zip \
+ --enable-bcmath \
--with-pdo-pgsql=shared,${INSTALL_DIR} \
--enable-intl=shared \
--enable-opcache-file \
diff --git a/src/Runtime/LambdaRuntime.php b/src/Runtime/LambdaRuntime.php
index cd95474a6..9cbd0d78d 100644
--- a/src/Runtime/LambdaRuntime.php
+++ b/src/Runtime/LambdaRuntime.php
@@ -22,6 +22,12 @@
*/
class LambdaRuntime
{
+ /** @var resource|null */
+ private $handler;
+
+ /** @var resource|null */
+ private $returnHandler;
+
/** @var string */
private $apiUrl;
@@ -39,6 +45,27 @@ public function __construct(string $apiUrl)
$this->apiUrl = $apiUrl;
}
+ public function __destruct()
+ {
+ $this->closeHandler();
+ $this->closeReturnHandler();
+ }
+
+ private function closeHandler(): void
+ {
+ if ($this->handler !== null) {
+ curl_close($this->handler);
+ $this->handler = null;
+ }
+ }
+ private function closeReturnHandler(): void
+ {
+ if ($this->returnHandler !== null) {
+ curl_close($this->returnHandler);
+ $this->returnHandler = null;
+ }
+ }
+
/**
* Process the next event.
*
@@ -70,13 +97,15 @@ public function processNextEvent(callable $handler): void
*/
private function waitNextInvocation(): array
{
- $handler = curl_init("http://{$this->apiUrl}/2018-06-01/runtime/invocation/next");
- curl_setopt($handler, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($handler, CURLOPT_FAILONERROR, true);
+ if ($this->handler === null) {
+ $this->handler = curl_init("http://{$this->apiUrl}/2018-06-01/runtime/invocation/next");
+ curl_setopt($this->handler, CURLOPT_FOLLOWLOCATION, true);
+ curl_setopt($this->handler, CURLOPT_FAILONERROR, true);
+ }
// Retrieve invocation ID
$invocationId = '';
- curl_setopt($handler, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$invocationId) {
+ curl_setopt($this->handler, CURLOPT_HEADERFUNCTION, function ($ch, $header) use (&$invocationId) {
if (! preg_match('/:\s*/', $header)) {
return strlen($header);
}
@@ -90,15 +119,17 @@ private function waitNextInvocation(): array
// Retrieve body
$body = '';
- curl_setopt($handler, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$body) {
+ curl_setopt($this->handler, CURLOPT_WRITEFUNCTION, function ($ch, $chunk) use (&$body) {
$body .= $chunk;
return strlen($chunk);
});
- curl_exec($handler);
- if (curl_error($handler)) {
- throw new \Exception('Failed to fetch next Lambda invocation: ' . curl_error($handler));
+ curl_exec($this->handler);
+ if (curl_errno($this->handler) > 0) {
+ $message = curl_error($this->handler);
+ $this->closeHandler();
+ throw new \Exception('Failed to fetch next Lambda invocation: ' . $message);
}
if ($invocationId === '') {
throw new \Exception('Failed to determine the Lambda invocation ID');
@@ -106,7 +137,6 @@ private function waitNextInvocation(): array
if ($body === '') {
throw new \Exception('Empty Lambda runtime API response');
}
- curl_close($handler);
$event = json_decode($body, true);
@@ -160,27 +190,28 @@ private function signalFailure(string $invocationId, \Throwable $error): void
*/
public function failInitialization(string $message, ?\Throwable $error = null): void
{
- if ($error instanceof \Exception) {
- $errorMessage = get_class($error) . ': ' . $error->getMessage();
- } else {
- $errorMessage = $error->getMessage();
- }
-
// Log the exception in CloudWatch
echo "$message\n";
- printf(
- "Fatal error: %s in %s:%d\nStack trace:\n%s",
- $errorMessage,
- $error->getFile(),
- $error->getLine(),
- $error->getTraceAsString()
- );
+ if ($error) {
+ if ($error instanceof \Exception) {
+ $errorMessage = get_class($error) . ': ' . $error->getMessage();
+ } else {
+ $errorMessage = $error->getMessage();
+ }
+ printf(
+ "Fatal error: %s in %s:%d\nStack trace:\n%s",
+ $errorMessage,
+ $error->getFile(),
+ $error->getLine(),
+ $error->getTraceAsString()
+ );
+ }
$url = "http://{$this->apiUrl}/2018-06-01/runtime/init/error";
$this->postJson($url, [
'errorMessage' => $message . ' ' . $error->getMessage(),
- 'errorType' => get_class($error),
- 'stackTrace' => explode(PHP_EOL, $error->getTraceAsString()),
+ 'errorType' => $error ? get_class($error) : 'Internal',
+ 'stackTrace' => $error ? explode(PHP_EOL, $error->getTraceAsString()) : [],
]);
exit(1);
@@ -196,21 +227,24 @@ private function postJson(string $url, $data): void
throw new \Exception('Failed encoding Lambda JSON response: ' . json_last_error_msg());
}
- $handler = curl_init($url);
- curl_setopt($handler, CURLOPT_CUSTOMREQUEST, 'POST');
- curl_setopt($handler, CURLOPT_RETURNTRANSFER, true);
- curl_setopt($handler, CURLOPT_FAILONERROR, true);
- curl_setopt($handler, CURLOPT_POSTFIELDS, $jsonData);
- curl_setopt($handler, CURLOPT_HTTPHEADER, [
+ if ($this->returnHandler === null) {
+ $this->returnHandler = curl_init();
+ curl_setopt($this->returnHandler, CURLOPT_CUSTOMREQUEST, 'POST');
+ curl_setopt($this->returnHandler, CURLOPT_RETURNTRANSFER, true);
+ curl_setopt($this->returnHandler, CURLOPT_FAILONERROR, true);
+ }
+
+ curl_setopt($this->returnHandler, CURLOPT_URL, $url);
+ curl_setopt($this->returnHandler, CURLOPT_POSTFIELDS, $jsonData);
+ curl_setopt($this->returnHandler, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen($jsonData),
]);
- curl_exec($handler);
- if (curl_error($handler)) {
- $errorMessage = curl_error($handler);
- curl_close($handler);
+ curl_exec($this->returnHandler);
+ if (curl_errno($this->returnHandler) > 0) {
+ $errorMessage = curl_error($this->returnHandler);
+ $this->closeReturnHandler();
throw new \Exception('Error while calling the Lambda runtime API: ' . $errorMessage);
}
- curl_close($handler);
}
}
diff --git a/template.yaml b/template.yaml
index 26b0ff9c3..ccb5e4100 100644
--- a/template.yaml
+++ b/template.yaml
@@ -16,7 +16,7 @@ Resources:
Handler: demo/function.php
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-2:209497400698:layer:php-72:3'
+ - 'arn:aws:lambda:us-east-2:209497400698:layer:php-72:4'
HttpFunction:
Type: AWS::Serverless::Function
@@ -29,7 +29,7 @@ Resources:
MemorySize: 1024 # The memory size is related to the pricing and CPU power
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-2:209497400698:layer:php-72-fpm:3'
+ - 'arn:aws:lambda:us-east-2:209497400698:layer:php-72-fpm:4'
Events:
HttpRoot:
Type: Api
@@ -51,8 +51,8 @@ Resources:
Handler: demo/console.php
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-2:209497400698:layer:php-72:3'
- - 'arn:aws:lambda:us-east-2:209497400698:layer:console:3'
+ - 'arn:aws:lambda:us-east-2:209497400698:layer:php-72:4'
+ - 'arn:aws:lambda:us-east-2:209497400698:layer:console:4'
Outputs:
DemoHttpApi:
diff --git a/template/console/template.yaml b/template/console/template.yaml
index c95b36c2c..7e31942f8 100644
--- a/template/console/template.yaml
+++ b/template/console/template.yaml
@@ -14,5 +14,5 @@ Resources:
MemorySize: 1024 # The memory size is related to the pricing and CPU power
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73:1' # PHP
- - 'arn:aws:lambda:us-east-1:209497400698:layer:console:1' # The "console" layer
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73:4' # PHP
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:console:4' # The "console" layer
diff --git a/template/default/template.yaml b/template/default/template.yaml
index e73d0687a..387445127 100644
--- a/template/default/template.yaml
+++ b/template/default/template.yaml
@@ -14,4 +14,4 @@ Resources:
MemorySize: 1024 # The memory size is related to the pricing and CPU power
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73:1'
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73:4'
diff --git a/template/http/template.yaml b/template/http/template.yaml
index 15d1e02f4..5efda1f3e 100644
--- a/template/http/template.yaml
+++ b/template/http/template.yaml
@@ -14,7 +14,7 @@ Resources:
MemorySize: 1024 # The memory size is related to the pricing and CPU power
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fpm:1'
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fpm:4'
Events:
# The function will match all HTTP URLs
HttpRoot:
diff --git a/tests/Sam/Php/function.php b/tests/Sam/Php/function.php
index 202e8e32f..113c39f39 100644
--- a/tests/Sam/Php/function.php
+++ b/tests/Sam/Php/function.php
@@ -11,6 +11,14 @@
return ini_get_all(null, false);
}
+ if ($event['env'] ?? false) {
+ return [
+ '$_ENV' => $_ENV['FOO'] ?? null,
+ '$_SERVER' => $_SERVER['FOO'] ?? null,
+ 'getenv' => getenv('FOO'),
+ ];
+ }
+
if ($event['stdout'] ?? false) {
echo 'This is a test log by writing to stdout';
}
diff --git a/tests/Sam/PhpFpm/index.php b/tests/Sam/PhpFpm/index.php
index d13ed91c2..aed110125 100644
--- a/tests/Sam/PhpFpm/index.php
+++ b/tests/Sam/PhpFpm/index.php
@@ -12,6 +12,16 @@
return;
}
+if ($_GET['env'] ?? false) {
+ header('Content-Type: application/json');
+ echo json_encode([
+ '$_ENV' => $_ENV['FOO'] ?? null,
+ '$_SERVER' => $_SERVER['FOO'] ?? null,
+ 'getenv' => getenv('FOO'),
+ ], JSON_PRETTY_PRINT);
+ return;
+}
+
if ($_GET['stderr'] ?? false) {
$stderr = fopen('php://stderr', 'a');
fwrite($stderr, 'This is a test log into stderr');
diff --git a/tests/Sam/PhpFpmRuntimeTest.php b/tests/Sam/PhpFpmRuntimeTest.php
index 2a7f5ec8e..ac0710f55 100644
--- a/tests/Sam/PhpFpmRuntimeTest.php
+++ b/tests/Sam/PhpFpmRuntimeTest.php
@@ -93,48 +93,53 @@ public function test warnings are logged()
public function test php extensions()
{
$response = $this->invoke('/?extensions=1');
+ $extensions = $this->getJsonBody($response);
+ sort($extensions);
self::assertEquals([
'Core',
- 'date',
- 'libxml',
- 'openssl',
- 'pcre',
- 'sqlite3',
- 'zlib',
+ 'PDO',
+ 'Phar',
+ 'Reflection',
+ 'SPL',
+ 'SimpleXML',
+ 'Zend OPcache',
+ 'bcmath',
+ 'cgi-fcgi',
'ctype',
'curl',
+ 'date',
'dom',
- 'hash',
+ 'exif',
'fileinfo',
'filter',
'ftp',
'gettext',
- 'SPL',
+ 'hash',
'iconv',
'json',
+ 'libxml',
'mbstring',
+ 'mysqli',
+ 'mysqlnd',
+ 'openssl',
'pcntl',
- 'session',
- 'PDO',
+ 'pcre',
'pdo_sqlite',
- 'standard',
'posix',
'readline',
- 'Reflection',
- 'Phar',
- 'SimpleXML',
+ 'session',
+ 'soap',
'sodium',
- 'exif',
+ 'sqlite3',
+ 'standard',
'tokenizer',
'xml',
'xmlreader',
'xmlwriter',
'zip',
- 'mysqlnd',
- 'cgi-fcgi',
- 'Zend OPcache',
- ], $this->getJsonBody($response), $this->logs);
+ 'zlib',
+ ], $extensions, $this->logs);
}
/**
@@ -172,6 +177,17 @@ public function test php config()
], $this->getJsonBody($response), false, $this->logs);
}
+ public function test environment variables()
+ {
+ $response = $this->invoke('/?env=1');
+
+ self::assertEquals([
+ '$_ENV' => 'bar',
+ '$_SERVER' => 'bar',
+ 'getenv' => 'bar',
+ ], $this->getJsonBody($response), $this->logs);
+ }
+
/**
* Check some PHP config values
*/
diff --git a/tests/Sam/PhpRuntimeTest.php b/tests/Sam/PhpRuntimeTest.php
index 6fc09cb3b..5d9f81d2b 100644
--- a/tests/Sam/PhpRuntimeTest.php
+++ b/tests/Sam/PhpRuntimeTest.php
@@ -61,7 +61,7 @@ public function test uncaught exception appears in logs and is reported
// We don't assert on complete exception traces because they will change over time
$expectedLogs = <<invokeLambda([
'extensions' => true,
]);
+ sort($result);
self::assertEquals([
'Core',
- 'date',
- 'libxml',
- 'openssl',
- 'pcre',
- 'sqlite3',
- 'zlib',
+ 'PDO',
+ 'Phar',
+ 'Reflection',
+ 'SPL',
+ 'SimpleXML',
+ 'Zend OPcache',
+ 'bcmath',
'ctype',
'curl',
+ 'date',
'dom',
- 'hash',
+ 'exif',
'fileinfo',
'filter',
'ftp',
'gettext',
- 'SPL',
+ 'hash',
'iconv',
'json',
+ 'libxml',
'mbstring',
+ 'mysqli',
+ 'mysqlnd',
+ 'openssl',
'pcntl',
- 'session',
- 'PDO',
+ 'pcre',
'pdo_sqlite',
- 'standard',
'posix',
'readline',
- 'Reflection',
- 'Phar',
- 'SimpleXML',
+ 'session',
+ 'soap',
'sodium',
- 'exif',
+ 'sqlite3',
+ 'standard',
'tokenizer',
'xml',
'xmlreader',
'xmlwriter',
'zip',
- 'mysqlnd',
- 'Zend OPcache',
+ 'zlib',
], $result, $logs);
}
@@ -223,6 +227,19 @@ public function test php config()
], $result, $logs);
}
+ public function test environment variables()
+ {
+ [$result, $logs] = $this->invokeLambda([
+ 'env' => true,
+ ]);
+
+ self::assertEquals([
+ '$_ENV' => 'bar',
+ '$_SERVER' => 'bar',
+ 'getenv' => 'bar',
+ ], $result, $logs);
+ }
+
/**
* @param mixed $event
*/
diff --git a/tests/Sam/template.yaml b/tests/Sam/template.yaml
index 908050b03..f54348509 100644
--- a/tests/Sam/template.yaml
+++ b/tests/Sam/template.yaml
@@ -10,7 +10,10 @@ Resources:
Handler: tests/Sam/Php/function.php
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73:2'
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73:4'
+ Environment:
+ Variables:
+ FOO: bar
HttpFunction:
Type: AWS::Serverless::Function
@@ -20,13 +23,16 @@ Resources:
Handler: tests/Sam/PhpFpm/index.php
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fpm:2'
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fpm:4'
Events:
HttpRoot:
Type: Api
Properties:
Path: /
Method: ANY
+ Environment:
+ Variables:
+ FOO: bar
MissingHandler:
Type: AWS::Serverless::Function
@@ -36,7 +42,7 @@ Resources:
Handler: tests/Sam/PhpFpm/UNKNOWN.php
Runtime: provided
Layers:
- - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fpm:2'
+ - 'arn:aws:lambda:us-east-1:209497400698:layer:php-73-fpm:4'
Events:
HttpRoot:
Type: Api
|