diff --git a/.travis.yml b/.travis.yml
index c1a70e8..3a68b3a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -28,6 +28,10 @@ matrix:
env:
- SYMFONY_VERSION="5.0.*"
- CHECK_PHP_SYNTAX="yes"
+ - php: 8.2
+ env:
+ - SYMFONY_VERSION="6.2.*"
+ - CHECK_PHP_SYNTAX="yes"
allow_failures:
- php: nightly
diff --git a/composer.json b/composer.json
index 68c4039..d3ffac6 100644
--- a/composer.json
+++ b/composer.json
@@ -1,5 +1,5 @@
{
- "name": "easycorp/easy-deploy-bundle",
+ "name": "gangsidestep/easy-deploy-bundle",
"type": "symfony-bundle",
"description": "The easiest way to deploy Symfony applications",
"keywords": ["deploy", "deployment", "deployer"],
@@ -12,15 +12,15 @@
}
],
"require": {
- "php": ">=7.2.0",
- "symfony/console": "~2.3|~3.0|~4.0|~5.0",
- "symfony/dependency-injection": "~2.3|~3.0|~4.0|~5.0",
- "symfony/expression-language": "~2.4|~3.0|~4.0|~5.0",
- "symfony/filesystem": "~2.3|~3.0|~4.0|~5.0",
- "symfony/http-foundation": "~2.3|~3.0|~4.0|~5.0",
- "symfony/http-kernel": "~2.3|~3.0|~4.0|~5.0",
+ "php": ">=8.2",
+ "symfony/console": "~2.3|~3.0|~4.0|~5.0|~6.2|~7.2",
+ "symfony/dependency-injection": "~2.3|~3.0|~4.0|~5.0|~6.2|~7.2",
+ "symfony/expression-language": "~2.4|~3.0|~4.0|~5.0|~6.2|~7.2",
+ "symfony/filesystem": "~2.3|~3.0|~4.0|~5.0|~6.2|~7.2",
+ "symfony/http-foundation": "~2.3|~3.0|~4.0|~5.0|~6.2|~7.2",
+ "symfony/http-kernel": "~2.3|~3.0|~4.0|~5.0|~6.2|~7.2",
"symfony/polyfill-mbstring": "^1.3",
- "symfony/process": "~2.3|~3.0|~4.0|~5.0"
+ "symfony/process": "~2.3|~3.0|~4.0|~5.0|~6.2|~7.2"
},
"require-dev": {
"phpunit/phpunit": "^6.1"
diff --git a/src/Command/DeployCommand.php b/src/Command/DeployCommand.php
index a3c6a5c..6c24483 100644
--- a/src/Command/DeployCommand.php
+++ b/src/Command/DeployCommand.php
@@ -24,16 +24,12 @@
class DeployCommand extends Command
{
- private $fileLocator;
private $projectDir;
- private $logDir;
private $configFilePath;
- public function __construct(FileLocator $fileLocator, string $projectDir, string $logDir)
+ public function __construct(private readonly FileLocator $fileLocator, string $projectDir, private readonly string $logDir)
{
- $this->fileLocator = $fileLocator;
$this->projectDir = realpath($projectDir);
- $this->logDir = $logDir;
parent::__construct();
}
@@ -69,7 +65,7 @@ protected function initialize(InputInterface $input, OutputInterface $output)
$this->createDefaultConfigFile($input, $output, $defaultConfigPath, $input->getArgument('stage'));
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$logFilePath = sprintf('%s/deploy_%s.log', $this->logDir, $input->getArgument('stage'));
$context = new Context($input, $output, $this->projectDir, $logFilePath, true === $input->getOption('dry-run'), $output->isVerbose());
diff --git a/src/Command/RollbackCommand.php b/src/Command/RollbackCommand.php
index 817905d..c536cc0 100644
--- a/src/Command/RollbackCommand.php
+++ b/src/Command/RollbackCommand.php
@@ -21,15 +21,10 @@
class RollbackCommand extends Command
{
- private $projectDir;
- private $logDir;
private $configFilePath;
- public function __construct(string $projectDir, string $logDir)
+ public function __construct(private readonly string $projectDir, private readonly string $logDir)
{
- $this->projectDir = $projectDir;
- $this->logDir = $logDir;
-
parent::__construct();
}
@@ -64,7 +59,7 @@ protected function initialize(InputInterface $input, OutputInterface $output)
throw new \RuntimeException(sprintf("The default configuration file does not exist or it's not readable, and no custom configuration file was given either. Create the '%s' configuration file and run this command again.", $defaultConfigPath));
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
$logFilePath = sprintf('%s/deploy_%s.log', $this->logDir, $input->getArgument('stage'));
$context = new Context($input, $output, $this->projectDir, $logFilePath, true === $input->getOption('dry-run'), $output->isVerbose());
diff --git a/src/Configuration/ConfigurationAdapter.php b/src/Configuration/ConfigurationAdapter.php
index c977f80..725ff63 100644
--- a/src/Configuration/ConfigurationAdapter.php
+++ b/src/Configuration/ConfigurationAdapter.php
@@ -19,15 +19,12 @@
* in a consistent manner, even if the configuration of each deployer is
* completely different and defined using incompatible objects.
*/
-final class ConfigurationAdapter
+final class ConfigurationAdapter implements \Stringable
{
- private $config;
- /** @var ParameterBag */
- private $options;
+ private ?ParameterBag $options = null;
- public function __construct(AbstractConfiguration $config)
+ public function __construct(private readonly AbstractConfiguration $config)
{
- $this->config = $config;
}
public function __toString(): string
@@ -60,7 +57,7 @@ private function getOptions(): ParameterBag
try {
$property->setAccessible(true);
$options->set($property->getName(), $property->getValue($this->config));
- } catch (\ReflectionException $e) {
+ } catch (\ReflectionException) {
// ignore this error
}
}
diff --git a/src/Configuration/DefaultConfiguration.php b/src/Configuration/DefaultConfiguration.php
index 1abe5ac..a015f70 100644
--- a/src/Configuration/DefaultConfiguration.php
+++ b/src/Configuration/DefaultConfiguration.php
@@ -24,47 +24,45 @@
final class DefaultConfiguration extends AbstractConfiguration
{
// variables starting with an underscore are for internal use only
- private $_symfonyEnvironmentEnvVarName; // SYMFONY_ENV or APP_ENV
+ private ?string $_symfonyEnvironmentEnvVarName = null; // SYMFONY_ENV or APP_ENV
// properties are defined as private so the developer doesn't see them when using
// their IDE autocompletion. To simplify things, the builder defines setter
// methods named the same as each option.
- private $symfonyEnvironment = 'prod';
- private $keepReleases = 5;
- private $repositoryUrl;
- private $repositoryBranch = 'master';
- private $remotePhpBinaryPath = 'php';
- private $updateRemoteComposerBinary = false;
- private $remoteComposerBinaryPath = '/usr/local/bin/composer';
- private $composerInstallFlags = '--no-dev --prefer-dist --no-interaction --quiet';
- private $composerOptimizeFlags = '--optimize';
- private $installWebAssets = true;
- private $dumpAsseticAssets = false;
- private $warmupCache = true;
- private $consoleBinaryPath;
- private $localProjectDir;
- private $binDir;
- private $configDir;
- private $cacheDir;
- private $deployDir;
- private $logDir;
- private $srcDir;
- private $templatesDir;
- private $webDir;
- private $controllersToRemove = [];
- private $writableDirs = [];
- private $permissionMethod = 'chmod';
- private $permissionMode = '0777';
- private $permissionUser;
- private $permissionGroup;
- private $sharedFiles = [];
- private $sharedDirs = [];
- private $resetOpCacheFor;
-
- public function __construct(string $localProjectDir)
+ private string $symfonyEnvironment = 'prod';
+ private int $keepReleases = 5;
+ private ?string $repositoryUrl = null;
+ private string $repositoryBranch = 'master';
+ private string $remotePhpBinaryPath = 'php';
+ private bool $updateRemoteComposerBinary = false;
+ private string $remoteComposerBinaryPath = '/usr/local/bin/composer';
+ private string $composerInstallFlags = '--no-dev --prefer-dist --no-interaction --quiet';
+ private string $composerOptimizeFlags = '--optimize';
+ private bool $installWebAssets = true;
+ private bool $dumpAsseticAssets = false;
+ private bool $warmupCache = true;
+ private ?string $consoleBinaryPath = null;
+ private ?string $binDir = null;
+ private ?string $configDir = null;
+ private ?string $cacheDir = null;
+ private ?string $deployDir = null;
+ private ?string $logDir = null;
+ private ?string $srcDir = null;
+ private ?string $templatesDir = null;
+ private ?string $webDir = null;
+ private array $controllersToRemove = [];
+ private array $writableDirs = [];
+ private string $permissionMethod = 'chmod';
+ private string $permissionMode = '0777';
+ private ?string $permissionUser = null;
+ private ?string $permissionGroup = null;
+ private array $sharedFiles = [];
+ private array $sharedDirs = [];
+ private ?string $resetOpCacheFor = null;
+
+ public function __construct(private readonly string $localProjectDir)
{
parent::__construct();
- $this->localProjectDir = $localProjectDir;
$this->setDefaultConfiguration(Kernel::MAJOR_VERSION, Kernel::MINOR_VERSION);
}
@@ -264,9 +262,7 @@ public function webDir(string $path): self
// the $paths can be glob() patterns, so this method needs to resolve them
public function controllersToRemove(array $paths): self
{
- $absoluteGlobPaths = array_map(function ($globPath) {
- return $this->localProjectDir.DIRECTORY_SEPARATOR.$globPath;
- }, $paths);
+ $absoluteGlobPaths = array_map(fn($globPath) => $this->localProjectDir.DIRECTORY_SEPARATOR.$globPath, $paths);
$localAbsolutePaths = [];
foreach ($absoluteGlobPaths as $path) {
@@ -337,7 +333,7 @@ public function sharedFilesAndDirs(array $paths = []): self
foreach ($paths as $path) {
$this->validatePathIsRelativeToProject($path, __METHOD__);
if (is_dir($this->localProjectDir.DIRECTORY_SEPARATOR.$path)) {
- $this->sharedDirs[] = rtrim($path, DIRECTORY_SEPARATOR);
+ $this->sharedDirs[] = rtrim((string) $path, DIRECTORY_SEPARATOR);
} else {
$this->sharedFiles[] = $path;
}
diff --git a/src/Configuration/Option.php b/src/Configuration/Option.php
index ef003c8..7997d7c 100644
--- a/src/Configuration/Option.php
+++ b/src/Configuration/Option.php
@@ -21,37 +21,37 @@
*/
final class Option
{
- const binDir = 'binDir';
- const cacheDir = 'cacheDir';
- const composerInstallFlags = 'composerInstallFlags';
- const composerOptimizeFlags = 'composerOptimizeFlags';
- const configDir = 'configDir';
- const consoleBinaryPath = 'consoleBinaryPath';
- const context = 'context';
- const controllersToRemove = 'controllersToRemove';
- const deployDir = 'deployDir';
- const dumpAsseticAssets = 'dumpAsseticAssets';
- const installWebAssets = 'installWebAssets';
- const keepReleases = 'keepReleases';
- const logDir = 'logDir';
- const permissionMethod = 'permissionMethod';
- const permissionMode = 'permissionMode';
- const permissionUser = 'permissionUser';
- const permissionGroup = 'permissionGroup';
- const remotePhpBinaryPath = 'remotePhpBinaryPath';
- const remoteComposerBinaryPath = 'remoteComposerBinaryPath';
- const repositoryBranch = 'repositoryBranch';
- const repositoryUrl = 'repositoryUrl';
- const resetOpCacheFor = 'resetOpCacheFor';
- const servers = 'servers';
- const sharedFiles = 'sharedFiles';
- const sharedDirs = 'sharedDirs';
- const srcDir = 'srcDir';
- const symfonyEnvironment = 'symfonyEnvironment';
- const templatesDir = 'templatesDir';
- const updateRemoteComposerBinary = 'updateRemoteComposerBinary';
- const useSshAgentForwarding = 'useSshAgentForwarding';
- const warmupCache = 'warmupCache';
- const webDir = 'webDir';
- const writableDirs = 'writableDirs';
+ public const binDir = 'binDir';
+ public const cacheDir = 'cacheDir';
+ public const composerInstallFlags = 'composerInstallFlags';
+ public const composerOptimizeFlags = 'composerOptimizeFlags';
+ public const configDir = 'configDir';
+ public const consoleBinaryPath = 'consoleBinaryPath';
+ public const context = 'context';
+ public const controllersToRemove = 'controllersToRemove';
+ public const deployDir = 'deployDir';
+ public const dumpAsseticAssets = 'dumpAsseticAssets';
+ public const installWebAssets = 'installWebAssets';
+ public const keepReleases = 'keepReleases';
+ public const logDir = 'logDir';
+ public const permissionMethod = 'permissionMethod';
+ public const permissionMode = 'permissionMode';
+ public const permissionUser = 'permissionUser';
+ public const permissionGroup = 'permissionGroup';
+ public const remotePhpBinaryPath = 'remotePhpBinaryPath';
+ public const remoteComposerBinaryPath = 'remoteComposerBinaryPath';
+ public const repositoryBranch = 'repositoryBranch';
+ public const repositoryUrl = 'repositoryUrl';
+ public const resetOpCacheFor = 'resetOpCacheFor';
+ public const servers = 'servers';
+ public const sharedFiles = 'sharedFiles';
+ public const sharedDirs = 'sharedDirs';
+ public const srcDir = 'srcDir';
+ public const symfonyEnvironment = 'symfonyEnvironment';
+ public const templatesDir = 'templatesDir';
+ public const updateRemoteComposerBinary = 'updateRemoteComposerBinary';
+ public const useSshAgentForwarding = 'useSshAgentForwarding';
+ public const warmupCache = 'warmupCache';
+ public const webDir = 'webDir';
+ public const writableDirs = 'writableDirs';
}
diff --git a/src/Context.php b/src/Context.php
index 28cd5b4..a5e8b17 100644
--- a/src/Context.php
+++ b/src/Context.php
@@ -20,25 +20,12 @@
* It implements the "Context Object" pattern to encapsulate the global state of
* the deployment in an immutable object.
*/
-class Context
+class Context implements \Stringable
{
- private $localHost;
- private $dryRun;
- private $debug;
- private $input;
- private $output;
- private $projectDir;
- private $logFilePath;
+ private readonly Server $localHost;
- public function __construct(InputInterface $input, OutputInterface $output, string $projectDir, string $logFilePath, bool $isDryRun, bool $isVerbose)
+ public function __construct(private readonly InputInterface $input, private readonly OutputInterface $output, private readonly string $projectDir, private readonly string $logFilePath, private readonly bool $dryRun, private readonly bool $debug)
{
- $this->input = $input;
- $this->output = $output;
- $this->projectDir = $projectDir;
- $this->logFilePath = $logFilePath;
- $this->dryRun = $isDryRun;
- $this->debug = $isVerbose;
-
$this->localHost = $this->createLocalHost();
}
diff --git a/src/Deployer/AbstractDeployer.php b/src/Deployer/AbstractDeployer.php
index fbc7c64..e42e66a 100644
--- a/src/Deployer/AbstractDeployer.php
+++ b/src/Deployer/AbstractDeployer.php
@@ -26,14 +26,10 @@
abstract class AbstractDeployer
{
- /** @var Context */
- private $context;
- /** @var TaskRunner */
- private $taskRunner;
- /** @var Logger */
- private $logger;
- /** @var ConfigurationAdapter */
- private $config;
+ private ?Context $context = null;
+ private ?TaskRunner $taskRunner = null;
+ private ?Logger $logger = null;
+ private ?ConfigurationAdapter $config = null;
abstract public function getRequirements(): array;
diff --git a/src/Deployer/DefaultDeployer.php b/src/Deployer/DefaultDeployer.php
index fa28adc..a5e5954 100644
--- a/src/Deployer/DefaultDeployer.php
+++ b/src/Deployer/DefaultDeployer.php
@@ -22,9 +22,9 @@
abstract class DefaultDeployer extends AbstractDeployer
{
- private $remoteProjectDirHasBeenCreated = false;
- private $remoteSymLinkHasBeenCreated = false;
-
+ private bool $remoteProjectDirHasBeenCreated = false;
+ private bool $remoteSymLinkHasBeenCreated = false;
+
public function getConfigBuilder(): DefaultConfiguration
{
return new DefaultConfiguration($this->getContext()->getLocalProjectRootDir());
@@ -231,8 +231,7 @@ private function createRemoteDirectoryLayout(): void
$this->log('
Creating the remote directory layout>');
$this->runRemote('mkdir -p {{ deploy_dir }} && mkdir -p {{ deploy_dir }}/releases && mkdir -p {{ deploy_dir }}/shared');
- /** @var TaskCompleted[] $results */
- $results = $this->runRemote('export _release_path="{{ deploy_dir }}/releases/$(date +%Y%m%d%H%M%S)" && mkdir -p $_release_path && echo $_release_path');
+ $results = $this->runRemote('export _release_path={{ deploy_dir }}/releases/'.$this->release_folder.' && mkdir -p $_release_path && echo $_release_path');
foreach ($results as $result) {
$remoteProjectDir = $this->getContext()->isDryRun() ? '(the remote project_dir)' : $result->getTrimmedOutput();
$result->getServer()->set(Property::project_dir, $remoteProjectDir);
@@ -290,7 +289,7 @@ private function doCreateSharedFiles(): void
{
$this->log('Creating symlinks for shared files>');
foreach ($this->getConfig(Option::sharedFiles) as $sharedFile) {
- $sharedFileParentDir = dirname($sharedFile);
+ $sharedFileParentDir = dirname((string) $sharedFile);
$this->runRemote(sprintf('mkdir -p {{ deploy_dir }}/shared/%s', $sharedFileParentDir));
$this->runRemote(sprintf('touch {{ deploy_dir }}/shared/%s', $sharedFile));
$this->runRemote(sprintf('ln -nfs {{ deploy_dir }}/shared/%s {{ project_dir }}/%s', $sharedFile, $sharedFile));
@@ -379,9 +378,7 @@ private function doClearControllers(): void
{
$this->log('Clearing controllers>');
foreach ($this->getServers()->findByRoles([Server::ROLE_APP]) as $server) {
- $absolutePaths = array_map(function ($relativePath) use ($server) {
- return $server->resolveProperties(sprintf('{{ project_dir }}/%s', $relativePath));
- }, $this->getConfig(Option::controllersToRemove));
+ $absolutePaths = array_map(fn($relativePath) => $server->resolveProperties(sprintf('{{ project_dir }}/%s', $relativePath)), $this->getConfig(Option::controllersToRemove));
$this->safeDelete($server, $absolutePaths);
}
@@ -427,7 +424,7 @@ private function doKeepReleases(): void
private function deleteOldReleases(Server $server, array $releaseDirs): void
{
foreach ($releaseDirs as $releaseDir) {
- if (!preg_match('/\d{14}/', $releaseDir)) {
+ if (!preg_match('/\d{14}/', (string) $releaseDir)) {
$this->log(sprintf('[%s>] Skipping cleanup of old releases; unexpected "%s" directory found (all directory names should be timestamps)', $server, $releaseDir));
return;
@@ -441,9 +438,7 @@ private function deleteOldReleases(Server $server, array $releaseDirs): void
}
$relativeDirsToRemove = array_slice($releaseDirs, 0, -$this->getConfig(Option::keepReleases));
- $absoluteDirsToRemove = array_map(function ($v) {
- return sprintf('%s/releases/%s', $this->getConfig(Option::deployDir), $v);
- }, $relativeDirsToRemove);
+ $absoluteDirsToRemove = array_map(fn($v) => sprintf('%s/releases/%s', $this->getConfig(Option::deployDir), $v), $relativeDirsToRemove);
// the command must be run only on one server because the timestamps are
// different for all servers, even when they belong to the same deploy and
diff --git a/src/Helper/Str.php b/src/Helper/Str.php
index 5d68cb7..9410e06 100644
--- a/src/Helper/Str.php
+++ b/src/Helper/Str.php
@@ -39,11 +39,9 @@ public static function lineSeparator(string $char = '-'): string
public static function prefix($text, string $prefix): string
{
- $text = is_array($text) ? $text : explode(PHP_EOL, $text);
+ $text = is_array($text) ? $text : explode(PHP_EOL, (string) $text);
- return implode(PHP_EOL, array_map(function ($line) use ($prefix) {
- return $prefix.$line;
- }, $text));
+ return implode(PHP_EOL, array_map(fn($line) => $prefix.$line, $text));
}
public static function stringify($value): string
diff --git a/src/Logger.php b/src/Logger.php
index a9bbd4e..819a249 100644
--- a/src/Logger.php
+++ b/src/Logger.php
@@ -14,13 +14,14 @@
use EasyCorp\Bundle\EasyDeployBundle\Helper\Str;
use Symfony\Component\Console\Formatter\OutputFormatter;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
+use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
final class Logger
{
- private $isDebug;
- private $output;
- private $logFilePath;
+ private readonly bool $isDebug;
+ private readonly OutputInterface $output;
+ private readonly string $logFilePath;
public function __construct(Context $context)
{
diff --git a/src/Requirement/AbstractRequirement.php b/src/Requirement/AbstractRequirement.php
index 7f31ee7..5f8c73d 100644
--- a/src/Requirement/AbstractRequirement.php
+++ b/src/Requirement/AbstractRequirement.php
@@ -16,12 +16,11 @@
abstract class AbstractRequirement
{
- /** @var Server[] */
- private $servers;
-
- public function __construct(array $servers)
+ /**
+ * @param Server[] $servers
+ */
+ public function __construct(private readonly array $servers)
{
- $this->servers = $servers;
}
public function getServers(): array
diff --git a/src/Requirement/AllowsLoginViaSsh.php b/src/Requirement/AllowsLoginViaSsh.php
index 814567c..b641d0b 100644
--- a/src/Requirement/AllowsLoginViaSsh.php
+++ b/src/Requirement/AllowsLoginViaSsh.php
@@ -12,6 +12,7 @@
namespace EasyCorp\Bundle\EasyDeployBundle\Requirement;
use EasyCorp\Bundle\EasyDeployBundle\Task\Task;
+use Exception;
class AllowsLoginViaSsh extends AbstractRequirement
{
@@ -20,9 +21,12 @@ public function getMessage(): string
return '[OK]> The server allows to login via SSH from the local machine';
}
+ /**
+ * @throws Exception
+ */
public function getChecker(): Task
{
- $shellCommand = sprintf('echo %s', mt_rand());
+ $shellCommand = sprintf('echo %s', random_int(0, mt_getrandmax()));
return new Task($this->getServers(), $shellCommand);
}
diff --git a/src/Requirement/CommandExists.php b/src/Requirement/CommandExists.php
index d985eb4..f6b58af 100644
--- a/src/Requirement/CommandExists.php
+++ b/src/Requirement/CommandExists.php
@@ -15,12 +15,9 @@
class CommandExists extends AbstractRequirement
{
- private $commandName;
-
- public function __construct(array $servers, string $commandName)
+ public function __construct(array $servers, private readonly string $commandName)
{
parent::__construct($servers);
- $this->commandName = $commandName;
}
public function getMessage(): string
@@ -30,7 +27,14 @@ public function getMessage(): string
public function getChecker(): Task
{
- $shellCommand = sprintf('%s %s', $this->isWindows() ? 'where' : 'which', $this->commandName);
+ if(str_contains($this->commandName,"/bin/composer"))
+ {
+ $shellCommand = sprintf('%s %s', 'which', $this->commandName);
+ }
+ else
+ {
+ $shellCommand = sprintf('%s %s', $this->isWindows() ? 'where' : 'which', $this->commandName);
+ }
return new Task($this->getServers(), $shellCommand);
}
diff --git a/src/Server/Property.php b/src/Server/Property.php
index 8ac7580..2f46e44 100644
--- a/src/Server/Property.php
+++ b/src/Server/Property.php
@@ -21,15 +21,15 @@
*/
final class Property
{
- const bin_dir = 'bin_dir';
- const config_dir = 'config_dir';
- const console_bin = 'console_bin';
- const cache_dir = 'cache_dir';
- const deploy_dir = 'deploy_dir';
- const log_dir = 'log_dir';
- const project_dir = 'project_dir';
- const src_dir = 'src_dir';
- const templates_dir = 'templates_dir';
- const use_ssh_agent_forwarding = 'use_ssh_agent_forwarding';
- const web_dir = 'web_dir';
+ public const bin_dir = 'bin_dir';
+ public const config_dir = 'config_dir';
+ public const console_bin = 'console_bin';
+ public const cache_dir = 'cache_dir';
+ public const deploy_dir = 'deploy_dir';
+ public const log_dir = 'log_dir';
+ public const project_dir = 'project_dir';
+ public const src_dir = 'src_dir';
+ public const templates_dir = 'templates_dir';
+ public const use_ssh_agent_forwarding = 'use_ssh_agent_forwarding';
+ public const web_dir = 'web_dir';
}
diff --git a/src/Server/Server.php b/src/Server/Server.php
index 676a9a1..6074975 100644
--- a/src/Server/Server.php
+++ b/src/Server/Server.php
@@ -15,19 +15,17 @@
use EasyCorp\Bundle\EasyDeployBundle\Helper\Str;
use Symfony\Component\HttpFoundation\ParameterBag;
-class Server
+class Server implements \Stringable
{
- const ROLE_APP = 'app';
+ final public const ROLE_APP = 'app';
private const LOCALHOST_ADDRESSES = ['localhost', 'local', '127.0.0.1'];
- private $roles;
private $user;
- private $host;
+ private readonly string $host;
private $port;
- private $properties;
+ private readonly ParameterBag $properties;
- public function __construct(string $dsn, array $roles = [self::ROLE_APP], array $properties = [])
+ public function __construct(string $dsn, private readonly array $roles = [self::ROLE_APP], array $properties = [])
{
- $this->roles = $roles;
$this->properties = new ParameterBag($properties);
// add the 'ssh://' scheme so the URL parsing works as expected
diff --git a/src/Server/ServerRepository.php b/src/Server/ServerRepository.php
index d0c9b21..1b7085c 100644
--- a/src/Server/ServerRepository.php
+++ b/src/Server/ServerRepository.php
@@ -15,10 +15,10 @@
* It implements the "Repository" pattern to store the servers involved in the
* deployment and provide some helper methods to find and filter those servers.
*/
-class ServerRepository
+class ServerRepository implements \Stringable
{
/** @var Server[] $servers */
- private $servers = [];
+ private array $servers = [];
public function __toString(): string
{
@@ -40,8 +40,6 @@ public function findAll(): array
*/
public function findByRoles(array $roles): array
{
- return array_filter($this->servers, function (Server $server) use ($roles) {
- return !empty(array_intersect($roles, $server->getRoles()));
- });
+ return array_filter($this->servers, fn(Server $server) => !empty(array_intersect($roles, $server->getRoles())));
}
}
diff --git a/src/Task/Task.php b/src/Task/Task.php
index 007da0a..8b07ab7 100644
--- a/src/Task/Task.php
+++ b/src/Task/Task.php
@@ -16,19 +16,15 @@
class Task
{
/** @var Server[] $servers */
- private $servers;
- private $shellCommand;
- private $envVars;
+ private readonly array $servers;
- public function __construct(array $servers, string $shellCommand, array $envVars = [])
+ public function __construct(array $servers, private readonly string $shellCommand, private readonly array $envVars = [])
{
if (empty($servers)) {
throw new \InvalidArgumentException('The "servers" argument of a Task cannot be an empty array. Add at least one server.');
}
$this->servers = $servers;
- $this->shellCommand = $shellCommand;
- $this->envVars = $envVars;
}
/**
diff --git a/src/Task/TaskCompleted.php b/src/Task/TaskCompleted.php
index 6af3c44..77d521b 100644
--- a/src/Task/TaskCompleted.php
+++ b/src/Task/TaskCompleted.php
@@ -19,15 +19,8 @@
*/
class TaskCompleted
{
- private $server;
- private $output;
- private $exitCode;
-
- public function __construct(Server $server, string $output, int $exitCode)
+ public function __construct(private readonly Server $server, private readonly string $output, private readonly int $exitCode)
{
- $this->server = $server;
- $this->output = $output;
- $this->exitCode = $exitCode;
}
public function isSuccessful(): bool
diff --git a/src/Task/TaskRunner.php b/src/Task/TaskRunner.php
index ce53693..e2c5961 100644
--- a/src/Task/TaskRunner.php
+++ b/src/Task/TaskRunner.php
@@ -19,13 +19,8 @@
class TaskRunner
{
- private $isDryRun;
- private $logger;
-
- public function __construct(bool $isDryRun, Logger $logger)
+ public function __construct(private readonly bool $isDryRun, private readonly Logger $logger)
{
- $this->isDryRun = $isDryRun;
- $this->logger = $logger;
}
/**
@@ -47,9 +42,14 @@ private function createProcess(string $shellCommand): Process
return Process::fromShellCommandline($shellCommand);
}
- return new Process($shellCommand);
+ return new Process((array)$shellCommand);
}
+ private function isWindows(): bool
+ {
+ return DIRECTORY_SEPARATOR === '\\';
+ }
+
private function doRun(Server $server, string $shellCommand, array $envVars): TaskCompleted
{
if ($server->has(Property::project_dir)) {
@@ -62,7 +62,9 @@ private function doRun(Server $server, string $shellCommand, array $envVars): Ta
$envVarsAsString = http_build_query($envVars, '', ' ');
// the ';' after the env vars makes them available to all commands, not only the first one
// parenthesis create a sub-shell so the env vars don't affect to the parent shell
- $shellCommand = sprintf('(export %s; %s)', $envVarsAsString, $shellCommand);
+ $winOrnot= $this->isWindows() ? 'set' : 'export';
+ // 'export' command is valid only for unix shells. In Windows - use 'set' instead
+ $shellCommand = sprintf('(%s %s; %s)',$winOrnot, $envVarsAsString, $shellCommand);
}
$this->logger->log(sprintf('[%s>] Executing command: %s>', $server, $shellCommand));
diff --git a/tests/Configuration/ConfigurationAdapterTest.php b/tests/Configuration/ConfigurationAdapterTest.php
index e6bedbf..c94636c 100644
--- a/tests/Configuration/ConfigurationAdapterTest.php
+++ b/tests/Configuration/ConfigurationAdapterTest.php
@@ -18,10 +18,9 @@
class ConfigurationAdapterTest extends TestCase
{
- /** @var DefaultConfiguration */
- private $config;
+ private \EasyCorp\Bundle\EasyDeployBundle\Configuration\DefaultConfiguration $config;
- protected function setUp()
+ protected function setUp(): void
{
$this->config = (new DefaultConfiguration(__DIR__))
->sharedFilesAndDirs([])
diff --git a/tests/Helper/StrTest.php b/tests/Helper/StrTest.php
index c19c70e..17c43f4 100644
--- a/tests/Helper/StrTest.php
+++ b/tests/Helper/StrTest.php
@@ -58,7 +58,7 @@ public function test_format_as_table()
$this->assertSame($result, Str::formatAsTable($values));
}
- public function startsWithProvider()
+ public function startsWithProvider(): \Generator
{
yield ['', '', false];
yield ['abc', '', false];
@@ -71,7 +71,7 @@ public function startsWithProvider()
yield ['a> bc', 'a', false];
}
- public function endsWithProvider()
+ public function endsWithProvider(): \Generator
{
yield ['', '', true];
yield ['abc', '', false];
@@ -84,7 +84,7 @@ public function endsWithProvider()
yield ['ab c>', 'c', false];
}
- public function containsProvider()
+ public function containsProvider(): \Generator
{
yield ['', '', false];
yield ['abc', '', false];
@@ -102,7 +102,7 @@ public function containsProvider()
yield ['ab c>', 'ab c', false];
}
- public function prefixProvider()
+ public function prefixProvider(): \Generator
{
yield ['', '', ''];
yield ['aaa', 'xxx', 'xxxaaa'];
@@ -110,7 +110,7 @@ public function prefixProvider()
yield [['aaa', 'bbb', 'ccc'], 'xxx', "xxxaaa\nxxxbbb\nxxxccc"];
}
- public function stringifyProvider()
+ public function stringifyProvider(): \Generator
{
yield ['', ''];
yield [fopen('php://memory', 'r+'), 'PHP Resource'];
@@ -121,7 +121,7 @@ public function stringifyProvider()
yield [['a' => 'aaa', 'b' => '3.14', 'c' => ['a', 'b']], '{"a":"aaa","b":"3.14","c":["a","b"]}'];
yield [new class() {
public $a = 'aaa';
- private $b = 'bbb';
+ private string $b = 'bbb';
}, '{"a":"aaa"}'];
yield [new class() {
public function __toString()
diff --git a/tests/Server/ServerRepositoryTest.php b/tests/Server/ServerRepositoryTest.php
index a32d1ed..87ea981 100644
--- a/tests/Server/ServerRepositoryTest.php
+++ b/tests/Server/ServerRepositoryTest.php
@@ -17,10 +17,9 @@
class ServerRepositoryTest extends TestCase
{
- /** @var ServerRepository */
- private $servers;
+ private ServerRepository $servers;
- protected function setUp()
+ protected function setUp(): void
{
$repository = new ServerRepository();
$repository->add(new Server('host0'));
@@ -36,7 +35,7 @@ protected function setUp()
public function test_find_all()
{
$servers = $this->servers->findAll();
- $serverNames = array_values(array_map(function ($v) { return (string) $v; }, $servers));
+ $serverNames = array_values(array_map(fn($v) => (string) $v, $servers));
$this->assertSame(['host0', 'host1', 'host2', 'host3', 'host4', 'host5'], $serverNames);
}
@@ -46,7 +45,7 @@ public function test_find_by_roles(array $roles, array $expectedResult)
{
$servers = $this->servers->findByRoles($roles);
// array_values() is needed to reset the values of the keys
- $serverNames = array_values(array_map(function ($v) { return (string) $v; }, $servers));
+ $serverNames = array_values(array_map(fn($v) => (string) $v, $servers));
$this->assertSame($expectedResult, $serverNames);
}
diff --git a/tests/Server/ServerTest.php b/tests/Server/ServerTest.php
index c3960f9..05f0561 100644
--- a/tests/Server/ServerTest.php
+++ b/tests/Server/ServerTest.php
@@ -11,6 +11,7 @@
namespace EasyCorp\Bundle\EasyDeployBundle\EasyDeployBundle\Tests;
+use EasyCorp\Bundle\EasyDeployBundle\Exception\ServerConfigurationException;
use EasyCorp\Bundle\EasyDeployBundle\Server\Property;
use EasyCorp\Bundle\EasyDeployBundle\Server\Server;
use PHPUnit\Framework\TestCase;
@@ -27,12 +28,10 @@ public function test_dsn_parsing(string $dsn, string $expectedHost, ?string $exp
$this->assertSame($expectedPort, $server->getPort());
}
- /**
- * @expectedException \EasyCorp\Bundle\EasyDeployBundle\Exception\ServerConfigurationException
- * @expectedExceptionMessage The host is missing (define it as an IP address or a host name)
- */
public function test_dsn_parsing_error()
{
+ $this->expectExceptionMessage("The host is missing (define it as an IP address or a host name)");
+ $this->expectException(ServerConfigurationException::class);
new Server('deployer@');
}
@@ -115,16 +114,16 @@ public function test_resolve_properties(array $properties, string $expression, s
/**
* @dataProvider wrongExpressionProvider
- * @expectedException \InvalidArgumentException
- * @expectedExceptionMessageRegExp /The ".*" property in ".*" expression is not a valid server property./
*/
public function test_resolve_unknown_properties(array $properties, string $expression)
{
+ $this->expectExceptionMessageMatches("/The \".*\" property in \".*\" expression is not a valid server property./");
+ $this->expectException(\InvalidArgumentException::class);
$server = new Server('host', [], $properties);
$server->resolveProperties($expression);
}
- public function dsnProvider()
+ public function dsnProvider(): \Generator
{
yield ['123.123.123.123', '123.123.123.123', null, null];
yield ['deployer@123.123.123.123', '123.123.123.123', 'deployer', null];
@@ -143,7 +142,7 @@ public function dsnProvider()
yield ['ssh://deployer@host:22001', 'host', 'deployer', 22001];
}
- public function localDsnProvider()
+ public function localDsnProvider(): \Generator
{
yield ['local'];
yield ['deployer@local'];
@@ -158,7 +157,7 @@ public function localDsnProvider()
yield ['deployer@127.0.0.1:22001'];
}
- public function serverRolesProvider()
+ public function serverRolesProvider(): \Generator
{
yield [[], []];
yield [[Server::ROLE_APP], [Server::ROLE_APP]];
@@ -166,7 +165,7 @@ public function serverRolesProvider()
yield [['custom_role_1', 'custom_role_2'], ['custom_role_1', 'custom_role_2']];
}
- public function sshConnectionStringProvider()
+ public function sshConnectionStringProvider(): \Generator
{
yield ['localhost', ''];
yield ['123.123.123.123', 'ssh 123.123.123.123'];
@@ -174,7 +173,7 @@ public function sshConnectionStringProvider()
yield ['deployer@123.123.123.123:22001', 'ssh deployer@123.123.123.123 -p 22001'];
}
- public function expressionProvider()
+ public function expressionProvider(): \Generator
{
yield [['prop1' => 'aaa'], '{{ prop1 }}', 'aaa'];
yield [['prop.1' => 'aaa'], '{{ prop.1 }}', 'aaa'];
@@ -188,7 +187,7 @@ public function expressionProvider()
yield [['prop1' => 'aaa', 'prop2' => 'bbb'], 'cd {{ prop1 }}{{ prop2 }}', 'cd aaabbb'];
}
- public function wrongExpressionProvider()
+ public function wrongExpressionProvider(): \Generator
{
yield [[], '{{ prop1 }}'];
yield [['prop1' => 'aaa'], '{{ prop 1 }}'];