diff --git a/src/Symfony/Installer/DemoCommand.php b/src/Symfony/Installer/DemoCommand.php
index 8944c31..ed8fe87 100644
--- a/src/Symfony/Installer/DemoCommand.php
+++ b/src/Symfony/Installer/DemoCommand.php
@@ -15,6 +15,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Installer\Exception\AbortException;
+use Symfony\Installer\Manager\ComposerManager;
/**
* This command creates a full-featured Symfony demo application.
@@ -60,6 +61,8 @@ protected function initialize(InputInterface $input, OutputInterface $output)
$this->projectDir = $this->fs->isAbsolutePath($directory) ? $directory : getcwd().DIRECTORY_SEPARATOR.$directory;
$this->projectName = basename($directory);
}
+
+ $this->composerManager = new ComposerManager($this->projectDir);
}
/**
@@ -75,7 +78,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
->download()
->extract()
->cleanUp()
- ->updateComposerJson()
+ ->updateComposerConfig()
->createGitIgnore()
->checkSymfonyRequirements()
->displayInstallationResult()
diff --git a/src/Symfony/Installer/DownloadCommand.php b/src/Symfony/Installer/DownloadCommand.php
index 2ac118d..a88b07b 100644
--- a/src/Symfony/Installer/DownloadCommand.php
+++ b/src/Symfony/Installer/DownloadCommand.php
@@ -28,6 +28,7 @@
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Intl\Exception\MethodArgumentValueNotImplementedException;
use Symfony\Installer\Exception\AbortException;
+use Symfony\Installer\Manager\ComposerManager;
/**
* Abstract command used by commands which download and extract compressed Symfony files.
@@ -82,6 +83,9 @@ abstract class DownloadCommand extends Command
*/
protected $requirementsErrors = array();
+ /** @var ComposerManager */
+ protected $composerManager;
+
/**
* Returns the type of the downloaded application in a human readable format.
* It's mainly used to display readable error messages.
@@ -363,23 +367,9 @@ private function getSymfonyRequirementsFilePath()
*
* @return $this
*/
- protected function updateComposerJson()
+ protected function updateComposerConfig()
{
- $composerConfig = $this->getProjectComposerConfig();
-
- if (isset($composerConfig['config']['platform']['php'])) {
- unset($composerConfig['config']['platform']['php']);
-
- if (empty($composerConfig['config']['platform'])) {
- unset($composerConfig['config']['platform']);
- }
-
- if (empty($composerConfig['config'])) {
- unset($composerConfig['config']);
- }
- }
-
- $this->saveProjectComposerConfig($composerConfig);
+ $this->composerManager->initializeProjectConfig();
return $this;
}
@@ -418,17 +408,13 @@ protected function createGitIgnore()
*/
protected function getInstalledSymfonyVersion()
{
- $composer = json_decode(file_get_contents($this->projectDir.'/composer.lock'), true);
+ $symfonyVersion = $this->composerManager->getPackageVersion('symfony/symfony');
- foreach ($composer['packages'] as $package) {
- if ('symfony/symfony' === $package['name']) {
- if ('v' === substr($package['version'], 0, 1)) {
- return substr($package['version'], 1);
- };
+ if (!empty($symfonyVersion) && 'v' === substr($symfonyVersion, 0, 1)) {
+ return substr($symfonyVersion, 1);
+ };
- return $package['version'];
- }
- }
+ return $symfonyVersion;
}
/**
@@ -600,107 +586,6 @@ protected function getUrlContents($url)
return $client->get($url)->getBody()->getContents();
}
- /**
- * It returns the project's Composer config as a PHP array.
- *
- * @return $this|array
- */
- protected function getProjectComposerConfig()
- {
- $composerJsonFilepath = $this->projectDir.'/composer.json';
-
- if (!is_writable($composerJsonFilepath)) {
- if ($this->output->isVerbose()) {
- $this->output->writeln(sprintf(
- " [WARNING] Project's Composer config cannot be updated because\n".
- " the %s file is not writable.\n",
- $composerJsonFilepath
- ));
- }
-
- return $this;
- }
-
- return json_decode(file_get_contents($composerJsonFilepath), true);
- }
-
- /**
- * It saves the given PHP array as the project's Composer config. In addition
- * to JSON-serializing the contents, it synchronizes the composer.lock file to
- * avoid out-of-sync Composer errors.
- *
- * @param array $config
- */
- protected function saveProjectComposerConfig(array $config)
- {
- $composerJsonFilepath = $this->projectDir.'/composer.json';
- $this->fs->dumpFile($composerJsonFilepath, json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
-
- $this->syncComposerLockFile();
- }
-
- /**
- * Updates the hash values stored in composer.lock to avoid out-of-sync
- * problems when the composer.json file contents are changed.
- */
- private function syncComposerLockFile()
- {
- $composerJsonFileContents = file_get_contents($this->projectDir.'/composer.json');
- $composerLockFileContents = json_decode(file_get_contents($this->projectDir.'/composer.lock'), true);
-
- if (array_key_exists('hash', $composerLockFileContents)) {
- $composerLockFileContents['hash'] = md5($composerJsonFileContents);
- }
-
- if (array_key_exists('content-hash', $composerLockFileContents)) {
- $composerLockFileContents['content-hash'] = $this->getComposerContentHash($composerJsonFileContents);
- }
-
- $this->fs->dumpFile($this->projectDir.'/composer.lock', json_encode($composerLockFileContents, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
- }
-
- /**
- * Returns the md5 hash of the sorted content of the composer file.
- *
- * @see https://github.com/composer/composer/blob/master/src/Composer/Package/Locker.php (getContentHash() method)
- *
- * @param string $composerJsonFileContents The contents of the composer.json file.
- *
- * @return string The hash of the composer file content.
- */
- private function getComposerContentHash($composerJsonFileContents)
- {
- $composerConfig = json_decode($composerJsonFileContents, true);
-
- $relevantKeys = array(
- 'name',
- 'version',
- 'require',
- 'require-dev',
- 'conflict',
- 'replace',
- 'provide',
- 'minimum-stability',
- 'prefer-stable',
- 'repositories',
- 'extra',
- );
-
- $relevantComposerConfig = array();
-
- foreach (array_intersect($relevantKeys, array_keys($composerConfig)) as $key) {
- $relevantComposerConfig[$key] = $composerConfig[$key];
- }
-
- if (isset($composerConfig['config']['platform'])) {
- $relevantComposerConfig['config']['platform'] = $composerConfig['config']['platform'];
- }
-
- ksort($relevantComposerConfig);
-
- return md5(json_encode($relevantComposerConfig));
- }
-
/**
* Enables the signal handler.
*
diff --git a/src/Symfony/Installer/Manager/ComposerManager.php b/src/Symfony/Installer/Manager/ComposerManager.php
new file mode 100644
index 0000000..e7c9266
--- /dev/null
+++ b/src/Symfony/Installer/Manager/ComposerManager.php
@@ -0,0 +1,193 @@
+projectDir = $projectDir;
+ $this->fs = new Filesystem();
+ }
+
+ public function initializeProjectConfig()
+ {
+ $composerConfig = $this->getProjectConfig();
+
+ if (isset($composerConfig['config']['platform']['php'])) {
+ unset($composerConfig['config']['platform']['php']);
+
+ if (empty($composerConfig['config']['platform'])) {
+ unset($composerConfig['config']['platform']);
+ }
+
+ if (empty($composerConfig['config'])) {
+ unset($composerConfig['config']);
+ }
+ }
+
+ $this->saveProjectConfig($composerConfig);
+ }
+
+ public function updateProjectConfig(array $newConfig)
+ {
+ $oldConfig = $this->getProjectConfig();
+ $projectConfig = array_replace_recursive($oldConfig, $newConfig);
+
+ // remove null values from project's config
+ $projectConfig = array_filter($projectConfig, function($value) { return !is_null($value); });
+
+ $this->saveProjectConfig($projectConfig);
+ }
+
+ public function getPackageVersion($packageName)
+ {
+ $composerLockFileContents = json_decode(file_get_contents($this->projectDir.'/composer.lock'), true);
+
+ foreach ($composerLockFileContents['packages'] as $packageConfig) {
+ if ($packageName === $packageConfig['name']) {
+ return $packageConfig['version'];
+ }
+ }
+ }
+
+ /**
+ * Generates a good Composer project name based on the application name
+ * and on the user name.
+ *
+ * @param $projectName
+ *
+ * @return string The generated Composer package name
+ */
+ public function createPackageName($projectName)
+ {
+ if (!empty($_SERVER['USERNAME'])) {
+ $packageName = $_SERVER['USERNAME'].'/'.$projectName;
+ } elseif (true === extension_loaded('posix') && $user = posix_getpwuid(posix_getuid())) {
+ $packageName = $user['name'].'/'.$projectName;
+ } elseif (get_current_user()) {
+ $packageName = get_current_user().'/'.$projectName;
+ } else {
+ // package names must be in the format foo/bar
+ $packageName = $projectName.'/'.$projectName;
+ }
+
+ return $this->fixPackageName($packageName);
+ }
+
+ /**
+ * It returns the project's Composer config as a PHP array.
+ *
+ * @return array
+ */
+ private function getProjectConfig()
+ {
+ $composerJsonPath = $this->projectDir.'/composer.json';
+ if (!is_writable($composerJsonPath)) {
+ return [];
+ }
+
+ return json_decode(file_get_contents($composerJsonPath), true);
+ }
+
+ /**
+ * It saves the given PHP array as the project's Composer config. In addition
+ * to JSON-serializing the contents, it synchronizes the composer.lock file to
+ * avoid out-of-sync Composer errors.
+ *
+ * @param array $config
+ */
+ private function saveProjectConfig(array $config)
+ {
+ $composerJsonPath = $this->projectDir.'/composer.json';
+ $this->fs->dumpFile($composerJsonPath, json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
+
+ $this->syncComposerLockFile();
+ }
+
+ /**
+ * Updates the hash values stored in composer.lock to avoid out-of-sync
+ * problems when the composer.json file contents are changed.
+ */
+ private function syncComposerLockFile()
+ {
+ $composerJsonFileContents = file_get_contents($this->projectDir.'/composer.json');
+ $composerLockFileContents = json_decode(file_get_contents($this->projectDir.'/composer.lock'), true);
+
+ if (array_key_exists('hash', $composerLockFileContents)) {
+ $composerLockFileContents['hash'] = md5($composerJsonFileContents);
+ }
+
+ if (array_key_exists('content-hash', $composerLockFileContents)) {
+ $composerLockFileContents['content-hash'] = $this->getComposerContentHash($composerJsonFileContents);
+ }
+
+ $this->fs->dumpFile($this->projectDir.'/composer.lock', json_encode($composerLockFileContents, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n");
+ }
+
+ /**
+ * Returns the md5 hash of the sorted content of the composer file.
+ *
+ * @see https://github.com/composer/composer/blob/master/src/Composer/Package/Locker.php (getContentHash() method)
+ *
+ * @param string $composerJsonFileContents The contents of the composer.json file.
+ *
+ * @return string The hash of the composer file content.
+ */
+ private function getComposerContentHash($composerJsonFileContents)
+ {
+ $composerConfig = json_decode($composerJsonFileContents, true);
+
+ $relevantKeys = array(
+ 'name',
+ 'version',
+ 'require',
+ 'require-dev',
+ 'conflict',
+ 'replace',
+ 'provide',
+ 'minimum-stability',
+ 'prefer-stable',
+ 'repositories',
+ 'extra',
+ );
+
+ $relevantComposerConfig = array();
+
+ foreach (array_intersect($relevantKeys, array_keys($composerConfig)) as $key) {
+ $relevantComposerConfig[$key] = $composerConfig[$key];
+ }
+
+ if (isset($composerConfig['config']['platform'])) {
+ $relevantComposerConfig['config']['platform'] = $composerConfig['config']['platform'];
+ }
+
+ ksort($relevantComposerConfig);
+
+ return md5(json_encode($relevantComposerConfig));
+ }
+
+ /**
+ * Transforms a project name into a valid Composer package name.
+ *
+ * @param string $name The project name to transform
+ *
+ * @return string The valid Composer package name
+ */
+ private function fixPackageName($name)
+ {
+ $name = str_replace(
+ ['à', 'á', 'â', 'ä', 'æ', 'ã', 'å', 'ā', 'é', 'è', 'ê', 'ë', 'ę', 'ė', 'ē', 'ī', 'į', 'í', 'ì', 'ï', 'î', 'ō', 'ø', 'œ', 'õ', 'ó', 'ò', 'ö', 'ô', 'ū', 'ú', 'ù', 'ü', 'û', 'ç', 'ć', 'č', 'ł', 'ñ', 'ń', 'ß', 'ś', 'š', 'ŵ', 'ŷ', 'ÿ', 'ź', 'ž', 'ż'],
+ ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'e', 'e', 'e', 'e', 'e', 'e', 'e', 'i', 'i', 'i', 'i', 'i', 'i', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'o', 'u', 'u', 'u', 'u', 'u', 'c', 'c', 'c', 'l', 'n', 'n', 's', 's', 's', 'w', 'y', 'y', 'z', 'z', 'z'],
+ $name
+ );
+ $name = preg_replace('#[^A-Za-z0-9_./-]+#', '', $name);
+
+ return strtolower($name);
+ }
+}
diff --git a/src/Symfony/Installer/NewCommand.php b/src/Symfony/Installer/NewCommand.php
index f6df447..23e00cc 100644
--- a/src/Symfony/Installer/NewCommand.php
+++ b/src/Symfony/Installer/NewCommand.php
@@ -15,6 +15,7 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Installer\Exception\AbortException;
+use Symfony\Installer\Manager\ComposerManager;
/**
* This command creates new Symfony projects for the given Symfony version.
@@ -48,6 +49,8 @@ protected function initialize(InputInterface $input, OutputInterface $output)
$this->version = trim($input->getArgument('version'));
$this->projectDir = $this->fs->isAbsolutePath($directory) ? $directory : getcwd().DIRECTORY_SEPARATOR.$directory;
$this->projectName = basename($directory);
+
+ $this->composerManager = new ComposerManager($this->projectDir);
}
/**
@@ -66,7 +69,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
->cleanUp()
->dumpReadmeFile()
->updateParameters()
- ->updateComposerJson()
+ ->updateComposerConfig()
->createGitIgnore()
->checkSymfonyRequirements()
->displayInstallationResult()
@@ -322,71 +325,19 @@ protected function updateParameters()
*
* @return $this
*/
- protected function updateComposerJson()
+ protected function updateComposerConfig()
{
- parent::updateComposerJson();
-
- $composerConfig = $this->getProjectComposerConfig();
-
- $composerConfig['name'] = $this->generateComposerProjectName();
- $composerConfig['license'] = 'proprietary';
-
- if (isset($composerConfig['description'])) {
- unset($composerConfig['description']);
- }
-
- if (isset($composerConfig['extra']['branch-alias'])) {
- unset($composerConfig['extra']['branch-alias']);
- }
-
- $this->saveProjectComposerConfig($composerConfig);
+ parent::updateComposerConfig();
+ $this->composerManager->updateProjectConfig([
+ 'name' => $this->composerManager->createPackageName($this->projectName),
+ 'license' => 'proprietary',
+ 'description' => null,
+ 'extra' => ['branch-alias' => null],
+ ]);
return $this;
}
- /**
- * Generates a good Composer project name based on the application name
- * and on the user name.
- *
- * @return string The generated Composer project name
- */
- protected function generateComposerProjectName()
- {
- $name = $this->projectName;
-
- if (!empty($_SERVER['USERNAME'])) {
- $name = $_SERVER['USERNAME'].'/'.$name;
- } elseif (true === extension_loaded('posix') && $user = posix_getpwuid(posix_getuid())) {
- $name = $user['name'].'/'.$name;
- } elseif (get_current_user()) {
- $name = get_current_user().'/'.$name;
- } else {
- // package names must be in the format foo/bar
- $name = $name.'/'.$name;
- }
-
- return $this->fixComposerPackageName($name);
- }
-
- /**
- * Transforms uppercase strings into dash-separated strings
- * (e.g. FooBar -> foo-bar) to comply with Composer rules for package names.
- *
- * @param string $name The project name to transform
- *
- * @return string The fixed Composer project name
- */
- private function fixComposerPackageName($name)
- {
- return strtolower(
- preg_replace(
- array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'),
- array('\\1-\\2', '\\1-\\2'),
- strtr($name, '-', '.')
- )
- );
- }
-
/**
* {@inheritdoc}
*/
diff --git a/tests/Symfony/Installer/Tests/Manager/ComposerManagerTest.php b/tests/Symfony/Installer/Tests/Manager/ComposerManagerTest.php
new file mode 100644
index 0000000..153abba
--- /dev/null
+++ b/tests/Symfony/Installer/Tests/Manager/ComposerManagerTest.php
@@ -0,0 +1,32 @@
+setAccessible(true);
+
+ $fixedName = $method->invoke($composerManager, $originalName);
+ $this->assertSame($expectedName, $fixedName);
+ }
+
+ public function getProjectNames()
+ {
+ return [
+ ['foo/bar', 'foo/bar'],
+ ['áèî/øū', 'aei/ou'],
+ ['çñß/łŵž', 'cns/lwz'],
+ ['foo#bar\foo?bar=foo!bar{foo]bar', 'foobarfoobarfoobarfoobar'],
+ ['FOO/bar', 'foo/bar'],
+ ];
+ }
+}