Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple/Parallel PHP Version Support for Valet #1192

Merged
merged 11 commits into from
Mar 14, 2022
19 changes: 1 addition & 18 deletions cli/Valet/Nginx.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public function installServer()
str_replace(
['VALET_HOME_PATH', 'VALET_SERVER_PATH', 'VALET_STATIC_PREFIX'],
[VALET_HOME_PATH, VALET_SERVER_PATH, VALET_STATIC_PREFIX],
$this->replaceLoopback($this->files->get(__DIR__.'/../stubs/valet.conf'))
$this->site->replaceLoopback($this->files->get(__DIR__.'/../stubs/valet.conf'))
)
);

Expand All @@ -90,23 +90,6 @@ public function installServer()
);
}

public function replaceLoopback($siteConf)
{
$loopback = $this->configuration->read()['loopback'];

if ($loopback === VALET_LOOPBACK) {
return $siteConf;
}

$str = '#listen VALET_LOOPBACK:80; # valet loopback';

return str_replace(
$str,
substr(str_replace('VALET_LOOPBACK', $loopback, $str), 1),
$siteConf
);
}

/**
* Install the Nginx configuration directory to the ~/.config/valet directory.
*
Expand Down
157 changes: 130 additions & 27 deletions cli/Valet/PhpFpm.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,20 @@ public function __construct(Brew $brew, CommandLine $cli, Filesystem $files)
/**
* Install and configure PhpFpm.
*
* @param null $phpVersion
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
* @return void
*/
public function install()
public function install($phpVersion = null)
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
{
if (! $this->brew->hasInstalledPhp()) {
$this->brew->ensureInstalled('php', [], $this->taps);
}

$this->files->ensureDirExists(VALET_HOME_PATH.'/Log', user());

$this->updateConfiguration();
$this->updateConfiguration($phpVersion);

$this->restart();
$this->restart($phpVersion);
}

/**
Expand All @@ -63,13 +64,14 @@ public function uninstall()
/**
* Update the PHP FPM configuration.
*
* @param null $phpVersion
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
* @return void
*/
public function updateConfiguration()
public function updateConfiguration($phpVersion = null)
{
info('Updating PHP configuration...');
info(sprintf('Updating PHP configuration%s...', $phpVersion ? ' for '.$phpVersion : ''));

$fpmConfigFile = $this->fpmConfigPath();
$fpmConfigFile = $this->fpmConfigPath($phpVersion);

$this->files->ensureDirExists(dirname($fpmConfigFile), user());

Expand All @@ -93,6 +95,11 @@ public function updateConfiguration()
$contents = preg_replace('/^;?listen\.group = .+$/m', 'listen.group = staff', $contents);
$contents = preg_replace('/^;?listen\.mode = .+$/m', 'listen.mode = 0777', $contents);
}

if ($phpVersion) {
$contents = str_replace('valet.sock', $this->fpmSockName($phpVersion), $contents);
}

$this->files->put($fpmConfigFile, $contents);
mattstauffer marked this conversation as resolved.
Show resolved Hide resolved

$contents = $this->files->get(__DIR__.'/../stubs/php-memory-limits.ini');
Expand All @@ -118,9 +125,9 @@ public function updateConfiguration()
*
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
* @return void
*/
public function restart()
public function restart($phpVersion = null)
{
$this->brew->restartLinkedPhp();
$this->brew->restartService($phpVersion ?: $this->getPhpVersionsToPerformRestart());
}

/**
Expand All @@ -141,11 +148,13 @@ public function stop()
*
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
* @return string
*/
public function fpmConfigPath()
public function fpmConfigPath($phpVersion = null)
{
$version = $this->brew->linkedPhp();
if (! $phpVersion) {
$phpVersion = $this->brew->linkedPhp();
}

$versionNormalized = $this->normalizePhpVersion($version === 'php' ? Brew::LATEST_PHP_VERSION : $version);
$versionNormalized = $this->normalizePhpVersion($phpVersion === 'php' ? Brew::LATEST_PHP_VERSION : $phpVersion);
$versionNormalized = preg_replace('~[^\d\.]~', '', $versionNormalized);

return $versionNormalized === '5.6'
Expand All @@ -171,10 +180,11 @@ public function stopRunning()
* Use a specific version of php.
*
* @param $version
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
* @param $force
* @param bool $force
* @param null $site
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
* @return string
*/
public function useVersion($version, $force = false)
public function useVersion($version, $force = false, $site = null)
{
$version = $this->validateRequestedVersion($version);

Expand All @@ -192,24 +202,40 @@ public function useVersion($version, $force = false)
$this->brew->ensureInstalled($version, [], $this->taps);
}

// Unlink the current php if there is one
if ($this->brew->hasLinkedPhp()) {
$currentVersion = $this->brew->getLinkedPhpFormula();
info(sprintf('Unlinking current version: %s', $currentVersion));
$this->brew->unlink($currentVersion);
}
// we need to unlink and link only for global php version change
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
if ($site) {
$this->cli->quietly('sudo rm '.VALET_HOME_PATH.'/'.$this->fpmSockName($version));
mattstauffer marked this conversation as resolved.
Show resolved Hide resolved
$this->install($version);
} else {
// Unlink the current php if there is one
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
if ($this->brew->hasLinkedPhp()) {
$linkedPhp = $this->brew->linkedPhp();

// updating old fpm to use a custom sock
// so exising lokced php versioned sites doesn't mess up
$this->updateConfiguration($linkedPhp);
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved

// check all custom nginx config files
// look for the php version and update config files accordingly
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
$this->updateConfigurationForGlobalUpdate($version, $linkedPhp);

info(sprintf('Linking new version: %s', $version));
$this->brew->link($version, true);
$currentVersion = $this->brew->getLinkedPhpFormula();
info(sprintf('Unlinking current version: %s', $currentVersion));
$this->brew->unlink($currentVersion);
}

info(sprintf('Linking new version: %s', $version));
$this->brew->link($version, true);

$this->stopRunning();
$this->stopRunning();

// remove any orphaned valet.sock files that PHP didn't clean up due to version conflicts
$this->files->unlink(VALET_HOME_PATH.'/valet.sock');
$this->cli->quietly('sudo rm '.VALET_HOME_PATH.'/valet.sock');
// remove any orphaned valet.sock files that PHP didn't clean up due to version conflicts
$this->files->unlink(VALET_HOME_PATH.'/valet.sock');
$this->cli->quietly('sudo rm '.VALET_HOME_PATH.'/valet*.sock');

// ensure configuration is correct and start the linked version
$this->install();
// ensure configuration is correct and start the linked version
$this->install();
}

return $version === 'php' ? $this->brew->determineAliasedVersion($version) : $version;
}
Expand Down Expand Up @@ -257,4 +283,81 @@ public function validateRequestedVersion($version)

return $version;
}

/**
* Get fpm sock file name from a given php version.
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
*
* @param string|null $phpVersion
* @return string
*/
public function fpmSockName($phpVersion = null)
{
$versionInteger = preg_replace('~[^\d]~', '', $phpVersion);

return "valet{$versionInteger}.sock";
}

/**
* Preseve exising isolated PHP versioned sites, when running a gloabl php version update. Look for the php version and will adjust that custom nginx config.
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
*
* @param $newPhpVersion
* @param $oldPhpVersion
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
*/
public function updateConfigurationForGlobalUpdate($newPhpVersion, $oldPhpVersion)
{
collect($this->files->scandir(VALET_HOME_PATH.'/Nginx'))
->filter(function ($file) {
return ! starts_with($file, '.');
})
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
->each(function ($file) use ($newPhpVersion, $oldPhpVersion) {
$content = $this->files->get(VALET_HOME_PATH.'/Nginx/'.$file);

if (! starts_with($content, '# Valet isolated PHP version')) {
return;
}

if (strpos($content, $this->fpmSockName($newPhpVersion)) !== false) {
info(sprintf('Updating site %s to keep using version: %s', $file, $newPhpVersion));
$this->files->put(VALET_HOME_PATH.'/Nginx/'.$file, str_replace($this->fpmSockName($newPhpVersion), 'valet.sock', $content));
} elseif (strpos($content, 'valet.sock') !== false) {
info(sprintf('Updating site %s to keep using version: %s', $file, $oldPhpVersion));
$this->files->put(VALET_HOME_PATH.'/Nginx/'.$file, str_replace('valet.sock', $this->fpmSockName($oldPhpVersion), $content));
}
});
}

/**
* Get the PHP versions to perform restart.
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
*
* @return array
*/
public function getPhpVersionsToPerformRestart()
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
{
// scan through custom nginx files
// look for config file, that is using custom .sock files (example: php74.sock)
// restart all those PHP FPM though valet
// to make sure all the custom php versioned sites keep running

NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
$fpmSockFiles = $this->brew->supportedPhpVersions()->map(function ($version) {
return $this->fpmSockName($this->normalizePhpVersion($version));
})->unique();

return collect($this->files->scandir(VALET_HOME_PATH.'/Nginx'))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential future refactor here to:

  • Make it clearer that this map and then the later filter is really rejecting any without a custom valet sock file
  • Stop looking for future instances of any given sock file (e.g. valet74.sock) once you find it in one site config

->filter(function ($file) {
return ! starts_with($file, '.');
})
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
->map(function ($file) use ($fpmSockFiles) {
$content = $this->files->get(VALET_HOME_PATH.'/Nginx/'.$file);

foreach ($fpmSockFiles as $sock) {
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved
if (strpos($content, $sock) !== false) {
// find the PHP version number from .sock path
// valet74.sock will outout php74
$phpVersion = 'php'.str_replace(['valet', '.sock'], '', $sock);
NasirNobin marked this conversation as resolved.
Show resolved Hide resolved

return $this->normalizePhpVersion($phpVersion); // example output php@7.4
}
}
})->merge([$this->brew->getLinkedPhpFormula()])->filter()->unique()->toArray();
}
}
Loading