Skip to content

Commit e84467f

Browse files
committed
refactor: implement application-centric architecture for runtime initialization and handler management
1 parent 6cd3c40 commit e84467f

35 files changed

Lines changed: 2237 additions & 1193 deletions
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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\Application;
15+
16+
use Ymir\Runtime\Aws\LambdaClient;
17+
use Ymir\Runtime\Lambda\Handler\ConsoleCommandLambdaEventHandler;
18+
use Ymir\Runtime\Lambda\Handler\LambdaEventHandlerCollection;
19+
use Ymir\Runtime\Lambda\Handler\PingLambdaEventHandler;
20+
use Ymir\Runtime\Lambda\Handler\WarmUpEventHandler;
21+
use Ymir\Runtime\RuntimeContext;
22+
23+
/**
24+
* Base application executed by the runtime.
25+
*/
26+
abstract class AbstractApplication implements ApplicationInterface
27+
{
28+
/**
29+
* The Lambda runtime context.
30+
*
31+
* @var RuntimeContext
32+
*/
33+
protected $context;
34+
35+
/**
36+
* Constructor.
37+
*/
38+
public function __construct(RuntimeContext $context)
39+
{
40+
$this->context = $context;
41+
}
42+
43+
/**
44+
* {@inheritdoc}
45+
*/
46+
public function getConsoleHandlers(): LambdaEventHandlerCollection
47+
{
48+
return $this->getEventHandlerCollection([
49+
new ConsoleCommandLambdaEventHandler($this->context->getLogger()),
50+
]);
51+
}
52+
53+
/**
54+
* {@inheritDoc}
55+
*/
56+
public function getContext(): RuntimeContext
57+
{
58+
return $this->context;
59+
}
60+
61+
/**
62+
* {@inheritdoc}
63+
*/
64+
public function getQueueHandlers(): LambdaEventHandlerCollection
65+
{
66+
return $this->getEventHandlerCollection();
67+
}
68+
69+
/**
70+
* {@inheritdoc}
71+
*/
72+
public function initialize(): void
73+
{
74+
}
75+
76+
/**
77+
* Get the LambdaEventHandlerCollection with the given Lambda event handlers.
78+
*/
79+
protected function getEventHandlerCollection(array $handlers = []): LambdaEventHandlerCollection
80+
{
81+
$logger = $this->context->getLogger();
82+
83+
return new LambdaEventHandlerCollection($logger, array_merge([
84+
new PingLambdaEventHandler(),
85+
new WarmUpEventHandler(LambdaClient::createFromContext($this->context), $logger),
86+
], $handlers));
87+
}
88+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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\Application;
15+
16+
use Ymir\Runtime\Exception\ApplicationInitializationException;
17+
use Ymir\Runtime\RuntimeContext;
18+
19+
/**
20+
* Runtime application factory.
21+
*/
22+
class ApplicationFactory
23+
{
24+
/**
25+
* Applications that the factory can create.
26+
*
27+
* ORDER MATTERS: Most specific applications must be first.
28+
*/
29+
private const APPLICATIONS = [
30+
// WordPress
31+
RadicleApplication::class,
32+
BedrockApplication::class,
33+
WordPressApplication::class,
34+
35+
// Laravel
36+
LaravelApplication::class,
37+
];
38+
39+
/**
40+
* Create the runtime application for the given runtime context.
41+
*/
42+
public static function createFromContext(RuntimeContext $context): ApplicationInterface
43+
{
44+
$application = collect(self::APPLICATIONS)
45+
->first(function (string $application) use ($context): bool {
46+
return is_a($application, ApplicationInterface::class, true) && $application::present($context->getRootDirectory());
47+
});
48+
49+
if (!is_string($application) || !is_a($application, ApplicationInterface::class, true)) {
50+
throw new ApplicationInitializationException('Unable to create runtime application');
51+
}
52+
53+
return new $application($context);
54+
}
55+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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\Application;
15+
16+
use Ymir\Runtime\Lambda\Handler\LambdaEventHandlerCollection;
17+
use Ymir\Runtime\RuntimeContext;
18+
19+
/**
20+
* An application executed by the runtime.
21+
*/
22+
interface ApplicationInterface
23+
{
24+
/**
25+
* Check if the application is present in the given directory.
26+
*/
27+
public static function present(string $directory): bool;
28+
29+
/**
30+
* Get the application handlers for "console" function.
31+
*/
32+
public function getConsoleHandlers(): LambdaEventHandlerCollection;
33+
34+
/**
35+
* Get the application Lambda runtime context.
36+
*/
37+
public function getContext(): RuntimeContext;
38+
39+
/**
40+
* Get the application handlers for "queue" function.
41+
*/
42+
public function getQueueHandlers(): LambdaEventHandlerCollection;
43+
44+
/**
45+
* Get the application handlers for "website" function.
46+
*/
47+
public function getWebsiteHandlers(): LambdaEventHandlerCollection;
48+
49+
/**
50+
* Initialize the application.
51+
*/
52+
public function initialize(): void;
53+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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\Application;
15+
16+
use Ymir\Runtime\Lambda\Handler\Http\BedrockHttpEventHandler;
17+
use Ymir\Runtime\Lambda\Handler\LambdaEventHandlerCollection;
18+
19+
/**
20+
* WordPress Bedrock runtime application.
21+
*/
22+
class BedrockApplication extends AbstractApplication
23+
{
24+
/**
25+
* {@inheritDoc}
26+
*/
27+
public static function present(string $directory): bool
28+
{
29+
return file_exists($directory.'/web/app/mu-plugins/bedrock-autoloader.php')
30+
|| (is_dir($directory.'/web/app/') && file_exists($directory.'/web/wp-config.php') && file_exists($directory.'/config/application.php'));
31+
}
32+
33+
/**
34+
* {@inheritDoc}
35+
*/
36+
public function getWebsiteHandlers(): LambdaEventHandlerCollection
37+
{
38+
return $this->getEventHandlerCollection([
39+
new BedrockHttpEventHandler($this->context->getLogger(), $this->context->getPhpFpmProcess(), $this->context->getRootDirectory()),
40+
]);
41+
}
42+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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\Application;
15+
16+
use Ymir\Runtime\Exception\ApplicationInitializationException;
17+
use Ymir\Runtime\Logger;
18+
19+
/**
20+
* Creates Laravel storage directories.
21+
*/
22+
trait CreatesLaravelStorageDirectoriesTrait
23+
{
24+
/**
25+
* Create the necessary Laravel storage directories.
26+
*/
27+
private function createStorageDirectories(Logger $logger): void
28+
{
29+
$logger->debug('Creating Laravel storage directories');
30+
31+
collect(['/bootstrap/cache', '/framework/cache', '/framework/views'])
32+
->map(function (string $path): string {
33+
return '/tmp/storage'.$path;
34+
})
35+
->filter(function (string $directory): bool {
36+
return !is_dir($directory);
37+
})
38+
->each(function (string $directory) use ($logger): void {
39+
if (!mkdir($directory, 0755, true) && !is_dir($directory)) {
40+
throw new ApplicationInitializationException(sprintf('Failed to create "%s" directory', $directory));
41+
}
42+
43+
$logger->debug(sprintf('"%s" directory created', $directory));
44+
});
45+
}
46+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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\Application;
15+
16+
use Symfony\Component\Process\Process;
17+
use Ymir\Runtime\Exception\ApplicationInitializationException;
18+
use Ymir\Runtime\Lambda\Handler\Http\LaravelHttpEventHandler;
19+
use Ymir\Runtime\Lambda\Handler\LambdaEventHandlerCollection;
20+
use Ymir\Runtime\Lambda\Handler\Sqs\LaravelSqsHandler;
21+
22+
/**
23+
* Laravel runtime application.
24+
*/
25+
class LaravelApplication extends AbstractApplication
26+
{
27+
use CreatesLaravelStorageDirectoriesTrait;
28+
29+
/**
30+
* {@inheritDoc}
31+
*/
32+
public static function present(string $directory): bool
33+
{
34+
return file_exists($directory.'/public/index.php')
35+
&& file_exists($directory.'/artisan');
36+
}
37+
38+
/**
39+
* {@inheritDoc}
40+
*/
41+
public function getQueueHandlers(): LambdaEventHandlerCollection
42+
{
43+
return $this->getEventHandlerCollection([
44+
new LaravelSqsHandler($this->context->getLogger(), $this->context->getRootDirectory()),
45+
]);
46+
}
47+
48+
/**
49+
* {@inheritDoc}
50+
*/
51+
public function getWebsiteHandlers(): LambdaEventHandlerCollection
52+
{
53+
return $this->getEventHandlerCollection([
54+
new LaravelHttpEventHandler($this->context->getLogger(), $this->context->getPhpFpmProcess(), $this->context->getRootDirectory()),
55+
]);
56+
}
57+
58+
/**
59+
* {@inheritDoc}
60+
*/
61+
public function initialize(): void
62+
{
63+
$logger = $this->context->getLogger();
64+
65+
$this->createStorageDirectories($logger);
66+
67+
$logger->debug('Creating Laravel cache');
68+
69+
$process = new Process(['/opt/bin/php', $this->context->getRootDirectory().'/artisan', 'config:cache', '--no-ansi']);
70+
$process->run();
71+
72+
if (!$process->isSuccessful()) {
73+
throw new ApplicationInitializationException(sprintf('Failed to create Laravel cache: %s', $process->getOutput()));
74+
}
75+
76+
$logger->debug('Laravel cache created');
77+
}
78+
}

0 commit comments

Comments
 (0)