Support to reload .env when using hyperf/watcher. (#6936)

Co-authored-by: 李铭昕 <715557344@qq.com>
This commit is contained in:
Albert Chen 2024-07-08 11:29:54 +08:00 committed by GitHub
parent 2bacaf3233
commit 3bca85b652
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 243 additions and 5 deletions

1
.gitignore vendored
View File

@ -18,3 +18,4 @@ node_modules/*
.bashrc .bashrc
/phpstan.neon /phpstan.neon
/phpunit.xml /phpunit.xml
!/src/support/tests/envs/**/*.env

View File

@ -1,5 +1,9 @@
# v3.1.31 - TBD # v3.1.31 - TBD
## Added
- [#6936](https://github.com/hyperf/hyperf/pull/6936) Support to reload `.env` when using `hyperf/watcher`.
# v3.1.30 - 2024-07-05 # v3.1.30 - 2024-07-05
## Fixed ## Fixed

View File

@ -21,6 +21,7 @@ use Hyperf\Di\LazyLoader\LazyLoader;
use Hyperf\Di\ScanHandler\PcntlScanHandler; use Hyperf\Di\ScanHandler\PcntlScanHandler;
use Hyperf\Di\ScanHandler\ScanHandlerInterface; use Hyperf\Di\ScanHandler\ScanHandlerInterface;
use Hyperf\Support\Composer; use Hyperf\Support\Composer;
use Hyperf\Support\DotenvManager;
class ClassLoader class ClassLoader
{ {
@ -43,7 +44,7 @@ class ClassLoader
$composerLoader = Composer::getLoader(); $composerLoader = Composer::getLoader();
if (file_exists(BASE_PATH . '/.env')) { if (file_exists(BASE_PATH . '/.env')) {
static::loadDotenv(); DotenvManager::load([BASE_PATH]);
} }
// Scan by ScanConfig to generate the reflection class map // Scan by ScanConfig to generate the reflection class map
@ -59,6 +60,10 @@ class ClassLoader
LazyLoader::bootstrap($configDir); LazyLoader::bootstrap($configDir);
} }
/**
* @see DotenvManager::load()
* @deprecated use DotenvManager instead
*/
protected static function loadDotenv(): void protected static function loadDotenv(): void
{ {
$repository = RepositoryBuilder::createWithNoAdapters() $repository = RepositoryBuilder::createWithNoAdapters()

View File

@ -24,10 +24,6 @@ use function Hyperf\Support\env;
* @coversNothing * @coversNothing
*/ */
#[CoversNothing] #[CoversNothing]
/**
* @internal
* @coversNothing
*/
class ClassLoaderTest extends TestCase class ClassLoaderTest extends TestCase
{ {
public function testDotEnv() public function testDotEnv()

View File

@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Support;
use Dotenv\Dotenv;
use Dotenv\Repository\Adapter\AdapterInterface;
use Dotenv\Repository\Adapter\PutenvAdapter;
use Dotenv\Repository\RepositoryBuilder;
class DotenvManager
{
protected static AdapterInterface $adapter;
protected static Dotenv $dotenv;
protected static array $cachedValues;
public static function load(array $paths): void
{
if (isset(static::$cachedValues)) {
return;
}
static::$cachedValues = static::getDotenv($paths)->load();
}
public static function reload(array $paths, bool $force = false): void
{
if (! isset(static::$cachedValues)) {
static::load($paths);
return;
}
foreach (static::$cachedValues as $deletedEntry => $value) {
static::getAdapter()->delete($deletedEntry);
}
static::$cachedValues = static::getDotenv($paths, $force)->load();
}
protected static function getDotenv(array $paths, bool $force = false): Dotenv
{
if (isset(static::$dotenv) && ! $force) {
return static::$dotenv;
}
return static::$dotenv = Dotenv::create(
RepositoryBuilder::createWithNoAdapters()
->addAdapter(static::getAdapter($force))
->immutable()
->make(),
$paths
);
}
protected static function getAdapter(bool $force = false): AdapterInterface
{
if (isset(static::$adapter) && ! $force) {
return static::$adapter;
}
return static::$adapter = PutenvAdapter::create()->get();
}
}

View File

@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace HyperfTest\Support;
use Hyperf\Support\DotenvManager;
use PHPUnit\Framework\Attributes\CoversNothing;
use PHPUnit\Framework\TestCase;
use function Hyperf\Support\env;
/**
* @internal
* @coversNothing
*/
#[CoversNothing]
class DotenvManagerTest extends TestCase
{
public function testLoad()
{
DotenvManager::load([__DIR__ . '/envs/oldEnv']);
$this->assertEquals('1.0', env('TEST_VERSION'));
$this->assertTrue(env('OLD_FLAG'));
}
public function testReload()
{
DotenvManager::load([__DIR__ . '/envs/oldEnv']);
$this->assertEquals('1.0', env('TEST_VERSION'));
$this->assertTrue(env('OLD_FLAG'));
DotenvManager::reload([__DIR__ . '/envs/newEnv'], true);
$this->assertEquals('2.0', env('TEST_VERSION'));
$this->assertNull(env('OLD_FLAG'));
$this->assertTrue(env('NEW_FLAG'));
}
public function testGetEnvFromEnvironment()
{
DotenvManager::load([__DIR__ . '/envs/oldEnv']);
$this->assertNotEquals('0.0.0', env('SW_VERSION'));
}
}

View File

@ -0,0 +1,3 @@
TEST_VERSION=2.0
NEW_FLAG=true
SW_VERSION="0.0.0"

View File

@ -0,0 +1,3 @@
TEST_VERSION=1.0
OLD_FLAG=true
SW_VERSION="0.0.0"

View File

@ -13,6 +13,7 @@ declare(strict_types=1);
namespace Hyperf\Watcher; namespace Hyperf\Watcher;
use Hyperf\Watcher\Command\WatchCommand; use Hyperf\Watcher\Command\WatchCommand;
use Hyperf\Watcher\Listener\ReloadDotenvAndConfigListener;
class ConfigProvider class ConfigProvider
{ {
@ -24,6 +25,9 @@ class ConfigProvider
'commands' => [ 'commands' => [
WatchCommand::class, WatchCommand::class,
], ],
'listeners' => [
ReloadDotenvAndConfigListener::class,
],
'publish' => [ 'publish' => [
[ [
'id' => 'config', 'id' => 'config',

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Watcher\Event;
class BeforeServerRestart
{
public function __construct(public string $pid)
{
}
}

View File

@ -0,0 +1,69 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace Hyperf\Watcher\Listener;
use Hyperf\Contract\ConfigInterface;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Framework\Event\BeforeWorkerStart;
use Hyperf\Support\DotenvManager;
use Hyperf\Watcher\Event\BeforeServerRestart;
use Psr\Container\ContainerInterface;
use Swoole\Atomic;
class ReloadDotenvAndConfigListener implements ListenerInterface
{
protected static Atomic $restartCounter;
public function __construct(protected ContainerInterface $container)
{
static::$restartCounter = new Atomic(0);
}
public function listen(): array
{
return [
BeforeWorkerStart::class,
BeforeServerRestart::class,
];
}
public function process(object $event): void
{
if ($event instanceof BeforeWorkerStart
&& $event->workerId === 0
&& static::$restartCounter->get() === 0
) {
static::$restartCounter->add();
return;
}
static::$restartCounter->add();
$this->reloadDotenv();
$this->reloadConfig();
}
protected function reloadConfig(): void
{
if (! method_exists($this->container, 'unbind')) {
return;
}
$this->container->unbind(ConfigInterface::class);
}
protected function reloadDotenv(): void
{
DotenvManager::reload([BASE_PATH]);
}
}

View File

@ -20,8 +20,10 @@ use Hyperf\Support\Exception\InvalidArgumentException;
use Hyperf\Support\Filesystem\FileNotFoundException; use Hyperf\Support\Filesystem\FileNotFoundException;
use Hyperf\Support\Filesystem\Filesystem; use Hyperf\Support\Filesystem\Filesystem;
use Hyperf\Watcher\Driver\DriverInterface; use Hyperf\Watcher\Driver\DriverInterface;
use Hyperf\Watcher\Event\BeforeServerRestart;
use PhpParser\PrettyPrinter\Standard; use PhpParser\PrettyPrinter\Standard;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Throwable; use Throwable;
@ -109,6 +111,8 @@ class Watcher
$pid = $this->filesystem->get($file); $pid = $this->filesystem->get($file);
try { try {
$this->output->writeln('Stop server...'); $this->output->writeln('Stop server...');
$this->container->get(EventDispatcherInterface::class)
->dispatch(new BeforeServerRestart($pid));
if (posix_kill((int) $pid, 0)) { if (posix_kill((int) $pid, 0)) {
posix_kill((int) $pid, SIGTERM); posix_kill((int) $pid, SIGTERM);
} }