Skip to content

Commit 92b6db3

Browse files
committed
refactor: create a RuntimeContext to store all the runtime context
1 parent 91cc015 commit 92b6db3

5 files changed

Lines changed: 409 additions & 20 deletions

File tree

src/Aws/LambdaClient.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Ymir PHP Runtime.
7+
*
8+
* (c) Carl Alexander <support@ymirapp.com>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Ymir\Runtime\Aws;
15+
16+
use AsyncAws\Lambda\LambdaClient as BaseLambdaClient;
17+
use Ymir\Runtime\RuntimeContext;
18+
19+
class LambdaClient extends BaseLambdaClient
20+
{
21+
/**
22+
* Create a new LambdaClient from the given runtime context.
23+
*/
24+
public static function createFromContext(RuntimeContext $context): self
25+
{
26+
return new self(['region' => $context->getRegion()], null, null, $context->getLogger());
27+
}
28+
}

src/Aws/SsmClient.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Ymir PHP Runtime.
7+
*
8+
* (c) Carl Alexander <support@ymirapp.com>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Ymir\Runtime\Aws;
15+
16+
use AsyncAws\Ssm\SsmClient as BaseSsmClient;
17+
use Ymir\Runtime\RuntimeContext;
18+
19+
class SsmClient extends BaseSsmClient
20+
{
21+
/**
22+
* Create a new LambdaClient from the given runtime context.
23+
*/
24+
public static function createFromContext(RuntimeContext $context): self
25+
{
26+
return new self(['region' => $context->getRegion()], null, null, $context->getLogger());
27+
}
28+
}

src/Runtime.php

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@
1313

1414
namespace Ymir\Runtime;
1515

16-
use AsyncAws\Lambda\LambdaClient;
1716
use AsyncAws\Ssm\Input\GetParametersByPathRequest;
18-
use AsyncAws\Ssm\SsmClient;
1917
use AsyncAws\Ssm\ValueObject\Parameter;
2018
use Tightenco\Collect\Support\Arr;
19+
use Ymir\Runtime\Aws\LambdaClient;
20+
use Ymir\Runtime\Aws\SsmClient;
2121
use Ymir\Runtime\Exception\InvalidConfigurationException;
22-
use Ymir\Runtime\FastCgi\PhpFpmProcess;
2322
use Ymir\Runtime\Lambda\Handler\ConsoleCommandLambdaEventHandler;
2423
use Ymir\Runtime\Lambda\Handler\Http as HttpHandler;
2524
use Ymir\Runtime\Lambda\Handler\LambdaEventHandlerCollection;
@@ -38,27 +37,24 @@ class Runtime
3837
public static function create(): RuntimeInterface
3938
{
4039
$coldStart = microtime(true);
41-
$logger = new Logger(getenv('YMIR_RUNTIME_LOG_LEVEL') ?: Logger::INFO);
42-
$runtimeApiClient = new RuntimeApiClient((string) getenv('AWS_LAMBDA_RUNTIME_API'), $logger);
40+
$context = RuntimeContext::createFromEnvironment();
41+
42+
$logger = $context->getLogger();
43+
$rootDirectory = $context->getRootDirectory();
44+
$runtimeApiClient = $context->getRuntimeApiClient();
4345

4446
try {
4547
$functionType = getenv('YMIR_FUNCTION_TYPE');
46-
$region = getenv('AWS_REGION');
47-
$rootDirectory = getenv('LAMBDA_TASK_ROOT');
4848

4949
if (!is_string($functionType)) {
5050
throw new InvalidConfigurationException('The "YMIR_FUNCTION_TYPE" environment variable is missing');
51-
} elseif (!is_string($rootDirectory)) {
52-
throw new InvalidConfigurationException('The "LAMBDA_TASK_ROOT" environment variable is missing');
53-
} elseif (!is_string($region)) {
54-
throw new InvalidConfigurationException('The "AWS_REGION" environment variable is missing');
5551
}
5652

57-
self::injectSecretEnvironmentVariables($logger, $region);
53+
self::injectSecretEnvironmentVariables($context);
5854

5955
$handlers = [
6056
new PingLambdaEventHandler(),
61-
new WarmUpEventHandler(new LambdaClient(['region' => $region], null, null, $logger), $logger),
57+
new WarmUpEventHandler(LambdaClient::createFromContext($context), $logger),
6258
];
6359

6460
switch ($functionType) {
@@ -76,8 +72,7 @@ public static function create(): RuntimeInterface
7672

7773
break;
7874
case WebsiteRuntime::TYPE:
79-
$maxInvocations = ((int) getenv('YMIR_RUNTIME_MAX_INVOCATIONS')) ?: null;
80-
$phpFpmProcess = PhpFpmProcess::createForConfig($logger);
75+
$phpFpmProcess = $context->getPhpFpmProcess();
8176

8277
$runtime = new WebsiteRuntime($runtimeApiClient, new LambdaEventHandlerCollection($logger, array_merge($handlers, [
8378
// Application/Framework specific handlers
@@ -88,7 +83,7 @@ public static function create(): RuntimeInterface
8883

8984
// Fallback handlers
9085
new HttpHandler\PhpScriptHttpEventHandler($logger, $phpFpmProcess, $rootDirectory, getenv('_HANDLER') ?: 'index.php'),
91-
])), $logger, $phpFpmProcess, $maxInvocations);
86+
])), $logger, $phpFpmProcess, $context->getMaxInvocations());
9287

9388
$runtime->start();
9489

@@ -111,7 +106,7 @@ public static function create(): RuntimeInterface
111106
/**
112107
* Inject the secret environment variables into the runtime.
113108
*/
114-
private static function injectSecretEnvironmentVariables(Logger $logger, string $region): void
109+
private static function injectSecretEnvironmentVariables(RuntimeContext $context): void
115110
{
116111
$secretsPath = getenv('YMIR_SECRETS_PATH');
117112

@@ -124,13 +119,13 @@ private static function injectSecretEnvironmentVariables(Logger $logger, string
124119
// results because they use a numbered index.
125120
//
126121
// @see https://stackoverflow.com/questions/70536304/why-does-iterator-to-array-give-different-results-than-foreach
127-
collect(iterator_to_array((new SsmClient(['region' => $region], null, null, $logger))->getParametersByPath(new GetParametersByPathRequest([
122+
collect(iterator_to_array(SsmClient::createFromContext($context)->getParametersByPath(new GetParametersByPathRequest([
128123
'Path' => $secretsPath,
129124
'WithDecryption' => true,
130125
])), false))->mapWithKeys(function (Parameter $parameter) {
131126
return [Arr::last(explode('/', (string) $parameter->getName())) => (string) $parameter->getValue()];
132-
})->filter()->each(function ($value, $name) use ($logger): void {
133-
$logger->debug(sprintf('Injecting [%s] secret environment variable into runtime', $name));
127+
})->filter()->each(function ($value, $name) use ($context): void {
128+
$context->getLogger()->debug(sprintf('Injecting [%s] secret environment variable into runtime', $name));
134129
$_ENV[$name] = $value;
135130
});
136131
}

src/RuntimeContext.php

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Ymir PHP Runtime.
7+
*
8+
* (c) Carl Alexander <support@ymirapp.com>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Ymir\Runtime;
15+
16+
use Ymir\Runtime\Exception\InvalidConfigurationException;
17+
use Ymir\Runtime\FastCgi\PhpFpmProcess;
18+
19+
/**
20+
* The Lambda runtime context.
21+
*/
22+
class RuntimeContext
23+
{
24+
/**
25+
* The Lambda root directory.
26+
*
27+
* @var string
28+
*/
29+
protected $rootDirectory;
30+
31+
/**
32+
* The Lambda runtime API client.
33+
*
34+
* @var RuntimeApiClient
35+
*/
36+
protected $runtimeApiClient;
37+
/**
38+
* The logger that sends logs to CloudWatch.
39+
*
40+
* @var Logger
41+
*/
42+
private $logger;
43+
44+
/**
45+
* The maximum number of invocations.
46+
*
47+
* @var int|null
48+
*/
49+
private $maxInvocations;
50+
51+
/**
52+
* The PHP-FPM process used by the Lambda runtime.
53+
*
54+
* @var PhpFpmProcess|null
55+
*/
56+
private $phpFpmProcess;
57+
58+
/**
59+
* The AWS region that the Lambda runtime is in.
60+
*
61+
* @var string
62+
*/
63+
private $region;
64+
65+
/**
66+
* Constructor.
67+
*/
68+
public function __construct(Logger $logger, RuntimeApiClient $runtimeApiClient, string $region, string $rootDirectory, ?int $maxInvocations = null, ?PhpFpmProcess $phpFpmProcess = null)
69+
{
70+
$this->logger = $logger;
71+
$this->maxInvocations = $maxInvocations;
72+
$this->phpFpmProcess = $phpFpmProcess;
73+
$this->region = $region;
74+
$this->rootDirectory = $rootDirectory;
75+
$this->runtimeApiClient = $runtimeApiClient;
76+
}
77+
78+
/**
79+
* Create new runtime context from the Lambda environment variable.
80+
*/
81+
public static function createFromEnvironment(): self
82+
{
83+
$logger = new Logger(getenv('YMIR_RUNTIME_LOG_LEVEL') ?: Logger::INFO);
84+
$maxInvocations = ((int) getenv('YMIR_RUNTIME_MAX_INVOCATIONS')) ?: null;
85+
$region = getenv('AWS_REGION');
86+
$rootDirectory = getenv('LAMBDA_TASK_ROOT');
87+
$runtimeApiUrl = getenv('AWS_LAMBDA_RUNTIME_API');
88+
89+
if (!is_string($region)) {
90+
throw new InvalidConfigurationException('The "AWS_REGION" environment variable is missing');
91+
} elseif (!is_string($rootDirectory)) {
92+
throw new InvalidConfigurationException('The "LAMBDA_TASK_ROOT" environment variable is missing');
93+
} elseif (!is_string($runtimeApiUrl)) {
94+
throw new InvalidConfigurationException('The "AWS_LAMBDA_RUNTIME_API" environment variable is missing');
95+
}
96+
97+
return new self($logger, new RuntimeApiClient($runtimeApiUrl, $logger), $region, $rootDirectory, $maxInvocations);
98+
}
99+
100+
/**
101+
* Get the logger that sends logs to CloudWatch.
102+
*/
103+
public function getLogger(): Logger
104+
{
105+
return $this->logger;
106+
}
107+
108+
/**
109+
* Get the maximum number of invocations.
110+
*/
111+
public function getMaxInvocations(): ?int
112+
{
113+
return $this->maxInvocations;
114+
}
115+
116+
/**
117+
* Get the PHP-FPM process used by the Lambda runtime.
118+
*/
119+
public function getPhpFpmProcess(): PhpFpmProcess
120+
{
121+
if (!$this->phpFpmProcess instanceof PhpFpmProcess) {
122+
$this->phpFpmProcess = PhpFpmProcess::createForConfig($this->logger);
123+
}
124+
125+
return $this->phpFpmProcess;
126+
}
127+
128+
/**
129+
* Get the AWS region that the Lambda runtime is in.
130+
*/
131+
public function getRegion(): string
132+
{
133+
return $this->region;
134+
}
135+
136+
/**
137+
* Get the Lambda root directory.
138+
*/
139+
public function getRootDirectory(): string
140+
{
141+
return $this->rootDirectory;
142+
}
143+
144+
/**
145+
* Get the Lambda runtime API client.
146+
*/
147+
public function getRuntimeApiClient(): RuntimeApiClient
148+
{
149+
return $this->runtimeApiClient;
150+
}
151+
}

0 commit comments

Comments
 (0)