diff --git a/.travis.yml b/.travis.yml
index ed70733..11424c2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,9 +12,7 @@ matrix:
- php: 5.5
- php: 5.6
- php: 7.0
- - php: hhvm
- allow_failures:
- - php: hhvm
+ - php: 7.1
before_install:
- composer self-update
@@ -24,7 +22,7 @@ before_install:
install:
- composer install
- - php box build
+ - ~/.phpenv/versions/5.6/bin/php box build
script:
- - phpunit
+ - ./vendor/bin/simple-phpunit
diff --git a/README.md b/README.md
index 82d2257..aa9f633 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,21 @@
Symfony Installer
=================
-**This is the official installer to start new projects based on the Symfony
-full-stack framework.**
+This is the official installer to start new projects based on the Symfony
+full-stack framework. The installer is only compatible with Symfony 2 and 3.
+
+Creating Symfony 4 projects
+---------------------------
+
+**This installer is not compatible with Symfony 4** and newer versions. Instead,
+use [Composer](https://getcomposer.org/) and create your Symfony 4 project as follows:
+
+```bash
+$ composer create-project symfony/skeleton my_project_name
+```
+
+See the [Symfony Installation article](https://symfony.com/doc/current/setup.html)
+on the official Symfony Documentation for more details.
Installing the installer
------------------------
diff --git a/composer.json b/composer.json
index f2d3582..7f924b6 100644
--- a/composer.json
+++ b/composer.json
@@ -6,7 +6,7 @@
"authors": [
{
"name": "Symfony Community",
- "homepage": "http://symfony.com/contributors"
+ "homepage": "https://symfony.com/contributors"
}
],
"autoload": {
@@ -20,7 +20,8 @@
"raulfraile/distill": "~0.9,!=0.9.3,!=0.9.4"
},
"require-dev": {
- "symfony/process": "~2.5"
+ "symfony/process": "~2.5",
+ "symfony/phpunit-bridge": "^4.0"
},
"extra": {
"branch-alias": {
diff --git a/composer.lock b/composer.lock
index 32c1a8a..b82669d 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,8 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "7587a63f96b596f379d4d830bee56119",
- "content-hash": "3c97ce42899ed9440a9ef2cc5d78847f",
+ "content-hash": "8f72036c453520314de3967a312a6733",
"packages": [
{
"name": "guzzlehttp/guzzle",
@@ -57,7 +56,7 @@
"rest",
"web service"
],
- "time": "2016-07-15 19:28:39"
+ "time": "2016-07-15T19:28:39+00:00"
},
{
"name": "guzzlehttp/ringphp",
@@ -108,7 +107,7 @@
}
],
"description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.",
- "time": "2015-05-20 03:37:09"
+ "time": "2015-05-20T03:37:09+00:00"
},
{
"name": "guzzlehttp/streams",
@@ -158,7 +157,7 @@
"Guzzle",
"stream"
],
- "time": "2014-10-12 19:18:40"
+ "time": "2014-10-12T19:18:40+00:00"
},
{
"name": "pimple/pimple",
@@ -204,7 +203,7 @@
"container",
"dependency injection"
],
- "time": "2015-09-11 15:10:35"
+ "time": "2015-09-11T15:10:35+00:00"
},
{
"name": "raulfraile/distill",
@@ -273,7 +272,7 @@
"xz",
"zip"
],
- "time": "2015-10-17 06:41:00"
+ "time": "2015-10-17T06:41:00+00:00"
},
{
"name": "react/promise",
@@ -317,7 +316,7 @@
}
],
"description": "A lightweight implementation of CommonJS Promises/A for PHP",
- "time": "2015-07-03 13:48:55"
+ "time": "2015-07-03T13:48:55+00:00"
},
{
"name": "symfony/console",
@@ -374,7 +373,7 @@
],
"description": "Symfony Console Component",
"homepage": "https://symfony.com",
- "time": "2015-09-25 08:32:23"
+ "time": "2015-09-25T08:32:23+00:00"
},
{
"name": "symfony/filesystem",
@@ -423,7 +422,7 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
- "time": "2015-09-09 17:42:36"
+ "time": "2015-09-09T17:42:36+00:00"
},
{
"name": "symfony/process",
@@ -472,10 +471,73 @@
],
"description": "Symfony Process Component",
"homepage": "https://symfony.com",
- "time": "2015-09-19 19:59:23"
+ "time": "2015-09-19T19:59:23+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "symfony/phpunit-bridge",
+ "version": "v4.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/phpunit-bridge.git",
+ "reference": "61c84ebdce0d4c289413a222ee545f0114e60120"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/61c84ebdce0d4c289413a222ee545f0114e60120",
+ "reference": "61c84ebdce0d4c289413a222ee545f0114e60120",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
+ },
+ "suggest": {
+ "ext-zip": "Zip support is required when using bin/simple-phpunit",
+ "symfony/debug": "For tracking deprecated interfaces usages at runtime with DebugClassLoader"
+ },
+ "bin": [
+ "bin/simple-phpunit"
+ ],
+ "type": "symfony-bridge",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Bridge\\PhpUnit\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony PHPUnit Bridge",
+ "homepage": "https://symfony.com",
+ "time": "2017-12-14T19:48:22+00:00"
}
],
- "packages-dev": [],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": [],
diff --git a/src/Symfony/Installer/DemoCommand.php b/src/Symfony/Installer/DemoCommand.php
index 8944c31..c941a78 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()
@@ -145,13 +148,22 @@ private function displayInstallationResult()
));
}
+ $serverRunCommand = extension_loaded('pcntl') ? 'server:start' : 'server:run';
+
$this->output->writeln(sprintf(
" 1. Change your current directory to %s\n\n".
- " 2. Execute the php bin/console server:run command to run the demo application.\n\n".
+ " 2. Execute the php bin/console %s command to run the demo application.\n\n".
" 3. Browse to the http://localhost:8000 URL to see the demo application in action.\n\n",
- $this->projectDir
+ $this->projectDir, $serverRunCommand
));
+ $this->output->writeln(
+ " WARNING >\n\n".
+ " This installer downloads the old Symfony Demo version based on Symfony 3.\n".
+ " If you prefer to install the new version based on Symfony 4 and Symfony Flex,\n".
+ " execute the following command:\n\n".
+ " composer create-project symfony/symfony-demo\n");
+
return $this;
}
@@ -168,6 +180,6 @@ protected function getDownloadedApplicationType()
*/
protected function getRemoteFileUrl()
{
- return 'http://symfony.com/download?v=Symfony_Demo';
+ return 'https://symfony.com/download?v=Symfony_Demo';
}
}
diff --git a/src/Symfony/Installer/DownloadCommand.php b/src/Symfony/Installer/DownloadCommand.php
index 2ac118d..b4634aa 100644
--- a/src/Symfony/Installer/DownloadCommand.php
+++ b/src/Symfony/Installer/DownloadCommand.php
@@ -21,6 +21,7 @@
use GuzzleHttp\Event\ProgressEvent;
use GuzzleHttp\Utils;
use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -28,6 +29,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 +84,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.
@@ -129,7 +134,7 @@ protected function download()
$symfonyArchiveFile = $distill
->getChooser()
->setStrategy(new MinimumSize())
- ->addFilesWithDifferentExtensions($this->getRemoteFileUrl(), ['tgz', 'zip'])
+ ->addFilesWithDifferentExtensions($this->getRemoteFileUrl(), array('tgz', 'zip'))
->getPreferredFile()
;
@@ -146,10 +151,10 @@ protected function download()
if (null === $progressBar) {
ProgressBar::setPlaceholderFormatterDefinition('max', function (ProgressBar $bar) {
- return $this->formatSize($bar->getMaxSteps());
+ return Helper::formatMemory($bar->getMaxSteps());
});
ProgressBar::setPlaceholderFormatterDefinition('current', function (ProgressBar $bar) {
- return str_pad($this->formatSize($bar->getProgress()), 11, ' ', STR_PAD_LEFT);
+ return str_pad(Helper::formatMemory($bar->getProgress()), 11, ' ', STR_PAD_LEFT);
});
$progressBar = new ProgressBar($this->output, $downloadSize);
@@ -179,7 +184,7 @@ protected function download()
$request->getEmitter()->on('progress', $downloadCallback);
$response = $client->send($request);
} catch (ClientException $e) {
- if ('new' === $this->getName() && ($e->getCode() === 403 || $e->getCode() === 404)) {
+ if ('new' === $this->getName() && (403 === $e->getCode() || 404 === $e->getCode())) {
throw new \RuntimeException(sprintf(
"The selected version (%s) cannot be installed because it does not exist.\n".
"Execute the following command to install the latest stable Symfony release:\n".
@@ -224,6 +229,10 @@ protected function checkProjectName()
));
}
+ if ('demo' === $this->projectName && 'new' === $this->getName()) {
+ $this->output->writeln("\n TIP > If you want to download the Symfony Demo app, execute 'symfony demo' instead of 'symfony new demo'");
+ }
+
return $this;
}
@@ -363,23 +372,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 +413,13 @@ protected function createGitIgnore()
*/
protected function getInstalledSymfonyVersion()
{
- $composer = json_decode(file_get_contents($this->projectDir.'/composer.lock'), true);
-
- foreach ($composer['packages'] as $package) {
- if ('symfony/symfony' === $package['name']) {
- if ('v' === substr($package['version'], 0, 1)) {
- return substr($package['version'], 1);
- };
+ $symfonyVersion = $this->composerManager->getPackageVersion('symfony/symfony');
- return $package['version'];
- }
+ if (!empty($symfonyVersion) && 'v' === substr($symfonyVersion, 0, 1)) {
+ return substr($symfonyVersion, 1);
}
+
+ return $symfonyVersion;
}
/**
@@ -449,26 +440,6 @@ protected function checkPermissions()
return $this;
}
- /**
- * Utility method to show the number of bytes in a readable format.
- *
- * @param int $bytes The number of bytes to format
- *
- * @return string The human readable string of bytes (e.g. 4.32MB)
- */
- protected function formatSize($bytes)
- {
- $units = array('B', 'KB', 'MB', 'GB', 'TB');
-
- $bytes = max($bytes, 0);
- $pow = $bytes ? floor(log($bytes, 1024)) : 0;
- $pow = min($pow, count($units) - 1);
-
- $bytes /= pow(1024, $pow);
-
- return number_format($bytes, 2).' '.$units[$pow];
- }
-
/**
* Formats the error message contained in the given Requirement item
* using the optional line length provided.
@@ -600,111 +571,10 @@ 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.
*
- * @throws AbortException If the execution has been aborted with SIGINT signal.
+ * @throws AbortException if the execution has been aborted with SIGINT signal
*/
private function enableSignalHandler()
{
@@ -712,7 +582,7 @@ private function enableSignalHandler()
return;
}
- declare(ticks = 1);
+ declare(ticks=1);
pcntl_signal(SIGINT, function () {
error_reporting(0);
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..b3bf403 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()
@@ -94,7 +97,7 @@ protected function execute(InputInterface $input, OutputInterface $output)
/**
* Checks whether the given Symfony version is installable by the installer.
* Due to the changes introduced in the Icu/Intl components
- * (see http://symfony.com/blog/new-in-symfony-2-6-farewell-to-icu-component)
+ * (see https://symfony.com/blog/new-in-symfony-2-6-farewell-to-icu-component)
* not all the previous Symfony versions are installable by the installer.
*
* The rules to decide if the version is installable are as follows:
@@ -121,12 +124,12 @@ protected function checkSymfonyVersionIsInstallable()
// Get the full list of Symfony versions to check if it's installable
$client = $this->getGuzzleClient();
- $symfonyVersions = $client->get('http://symfony.com/versions.json')->json();
+ $symfonyVersions = $client->get('https://symfony.com/versions.json')->json();
if (empty($symfonyVersions)) {
throw new \RuntimeException(
"There was a problem while downloading the list of Symfony versions from\n".
"symfony.com. Check that you are online and the following URL is accessible:\n\n".
- 'http://symfony.com/versions.json'
+ 'https://symfony.com/versions.json'
);
}
@@ -136,7 +139,7 @@ protected function checkSymfonyVersionIsInstallable()
throw new \RuntimeException(sprintf(
"The selected branch (%s) does not exist, or is not maintained.\n".
"To solve this issue, install Symfony with the latest stable release:\n\n".
- '%s %s %s', $this->version, $_SERVER['PHP_SELF'], $this->getName(), $this->projectDir
+ '%s %s %s', $this->version, $_SERVER['PHP_SELF'], $this->getName(), $this->projectDir
));
}
@@ -170,11 +173,21 @@ protected function checkSymfonyVersionIsInstallable()
}
// check that the system has the PHP version required by the Symfony version to be installed
- if (version_compare($this->version, '3.0.0', '>=') && version_compare(phpversion(), '5.5.9', '<')) {
+ if (version_compare($this->version, '3.0.0', '>=') && version_compare(PHP_VERSION, '5.5.9', '<')) {
throw new \RuntimeException(sprintf(
"The selected version (%s) cannot be installed because it requires\n".
"PHP 5.5.9 or higher and your system has PHP %s installed.\n",
- $this->version, phpversion()
+ $this->version, PHP_VERSION
+ ));
+ }
+
+ // check that the Symfony version to be installed is not 4.x, which is incompatible with this installer
+ if (version_compare($this->version, '4.0.0', '>=')) {
+ throw new \RuntimeException(sprintf(
+ "The Symfony Installer is not compatible with Symfony 4.x or newer versions.\n".
+ "Run this other command to install Symfony using Composer instead:\n\n".
+ 'composer create-project symfony/skeleton %s',
+ $this->projectName
));
}
@@ -256,14 +269,15 @@ protected function displayInstallationResult()
}
$consoleDir = ($this->isSymfony3() ? 'bin' : 'app');
+ $serverRunCommand = version_compare($this->version, '2.6.0', '>=') && extension_loaded('pcntl') ? 'server:start' : 'server:run';
$this->output->writeln(sprintf(
" * Configure your application in app/config/parameters.yml file.\n\n".
" * Run your application:\n".
- " 1. Execute the php %s/console server:run command.\n".
+ " 1. Execute the php %s/console %s command.\n".
" 2. Browse to the http://localhost:8000 URL.\n\n".
- " * Read the documentation at http://symfony.com/doc\n",
- $consoleDir
+ " * Read the documentation at https://symfony.com/doc\n",
+ $consoleDir, $serverRunCommand
));
return $this;
@@ -322,71 +336,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(array(
+ 'name' => $this->composerManager->createPackageName($this->projectName),
+ 'license' => 'proprietary',
+ 'description' => null,
+ 'extra' => array('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}
*/
@@ -400,6 +362,6 @@ protected function getDownloadedApplicationType()
*/
protected function getRemoteFileUrl()
{
- return 'http://symfony.com/download?v=Symfony_Standard_Vendors_'.$this->version;
+ return 'https://symfony.com/download?v=Symfony_Standard_Vendors_'.$this->version;
}
}
diff --git a/src/Symfony/Installer/SelfUpdateCommand.php b/src/Symfony/Installer/SelfUpdateCommand.php
index 0b1810e..2a93865 100644
--- a/src/Symfony/Installer/SelfUpdateCommand.php
+++ b/src/Symfony/Installer/SelfUpdateCommand.php
@@ -89,7 +89,7 @@ public function isEnabled()
protected function initialize(InputInterface $input, OutputInterface $output)
{
parent::initialize($input, $output);
- $this->remoteInstallerFile = 'http://symfony.com/installer';
+ $this->remoteInstallerFile = 'https://symfony.com/installer';
$this->currentInstallerFile = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0];
$this->tempDir = sys_get_temp_dir();
$this->currentInstallerBackupFile = basename($this->currentInstallerFile, '.phar').'-backup.phar';
@@ -136,6 +136,8 @@ protected function execute(InputInterface $input, OutputInterface $output)
if ($this->output->isVeryVerbose()) {
$this->output->writeln($e->getMessage());
}
+
+ return 1;
}
}
@@ -148,11 +150,11 @@ private function downloadNewVersion()
{
// check for permissions in local filesystem before start downloading files
if (!is_writable($this->currentInstallerFile)) {
- throw new \RuntimeException('Symfony Installer update failed: the "'.$this->currentInstallerFile.'" file could not be written');
+ throw new IOException('Symfony Installer update failed: the "'.$this->currentInstallerFile.'" file could not be written');
}
if (!is_writable($this->tempDir)) {
- throw new \RuntimeException('Symfony Installer update failed: the "'.$this->tempDir.'" directory used to download files temporarily could not be written');
+ throw new IOException('Symfony Installer update failed: the "'.$this->tempDir.'" directory used to download files temporarily could not be written');
}
if (false === $newInstaller = $this->getUrlContents($this->remoteInstallerFile)) {
@@ -225,7 +227,7 @@ private function rollback()
{
$this->output->writeln(array(
'',
- 'There was an error while updating the installer.',
+ 'There was an error while updating the installer.',
'The previous Symfony Installer version has been restored.',
'',
));
@@ -250,6 +252,6 @@ protected function getDownloadedApplicationType()
*/
protected function getRemoteFileUrl()
{
- return 'http://symfony.com/installer';
+ return 'https://symfony.com/installer';
}
}
diff --git a/symfony b/symfony
index 14fd272..b9f2d8d 100755
--- a/symfony
+++ b/symfony
@@ -14,11 +14,22 @@ if (PHP_VERSION_ID < 50400) {
exit(1);
}
+if (extension_loaded('suhosin')) {
+ file_put_contents('php://stderr',
+ "Symfony Installer is not compatible with the 'suhosin' PHP extension.\n".
+ "Disable that extension before running the installer.\n\n".
+ "Alternatively, install Symfony manually executing the following command:\n\n".
+ "composer create-project symfony/framework-standard-edition \n\n"
+ );
+
+ exit(1);
+}
+
require file_exists(__DIR__.'/vendor/autoload.php')
? __DIR__.'/vendor/autoload.php'
: __DIR__.'/../../autoload.php';
-$appVersion = '1.5.7-DEV';
+$appVersion = '1.5.12-DEV';
// Windows uses Path instead of PATH
if (!isset($_SERVER['PATH']) && isset($_SERVER['Path'])) {
diff --git a/tests/Symfony/Installer/Tests/IntegrationTest.php b/tests/Symfony/Installer/Tests/IntegrationTest.php
index 90a8d5a..7ade375 100644
--- a/tests/Symfony/Installer/Tests/IntegrationTest.php
+++ b/tests/Symfony/Installer/Tests/IntegrationTest.php
@@ -52,11 +52,7 @@ public function testDemoApplicationInstallation()
$this->assertContains('Downloading the Symfony Demo Application', $output);
$this->assertContains('Symfony Demo Application was successfully installed.', $output);
- $output = $this->runCommand('php bin/console --version', $projectDir);
- $this->assertRegExp('/Symfony version 3\.\d+\.\d+(-DEV)? - app\/dev\/debug/', $output);
-
$composerConfig = json_decode(file_get_contents($projectDir.'/composer.json'), true);
-
$this->assertArrayNotHasKey('platform', $composerConfig['config'], 'The composer.json file does not define any platform configuration.');
}
@@ -65,7 +61,7 @@ public function testDemoApplicationInstallation()
*/
public function testSymfonyInstallation($versionToInstall, $messageRegexp, $versionRegexp, $requiredPhpVersion)
{
- if (version_compare(phpversion(), $requiredPhpVersion, '<')) {
+ if (version_compare(PHP_VERSION, $requiredPhpVersion, '<')) {
$this->markTestSkipped(sprintf('This test requires PHP %s or higher.', $requiredPhpVersion));
}
@@ -76,14 +72,10 @@ public function testSymfonyInstallation($versionToInstall, $messageRegexp, $vers
$this->assertContains('Downloading Symfony...', $output);
$this->assertRegExp($messageRegexp, $output);
- if ('3' === substr($versionToInstall, 0, 1) || '' === $versionToInstall) {
- if (PHP_VERSION_ID < 50500) {
- $this->markTestSkipped('Symfony 3 requires PHP 5.5.9 or higher.');
- }
-
- $output = $this->runCommand('php bin/console --version', $projectDir);
- } else {
+ if (file_exists($projectDir.'/app/console')) {
$output = $this->runCommand('php app/console --version', $projectDir);
+ } else {
+ $output = $this->runCommand('php bin/console --version', $projectDir);
}
$this->assertRegExp($versionRegexp, $output);
@@ -109,6 +101,19 @@ public function testSymfonyRequiresNewerPhpVersion()
$this->runCommand(sprintf('php %s/symfony.phar new my_test_project 3.0.0', $this->rootDir));
}
+ /**
+ * @expectedException \RuntimeException
+ * @expectedExceptionMessageRegExp /.+The Symfony Installer is not compatible with Symfony 4\.x or newer versions.*Run this other command to install Symfony using Composer instead:.*composer create-project symfony\/skeleton .+/s
+ */
+ public function testUseComposerToInstallSymfony4()
+ {
+ if (PHP_VERSION_ID < 50500) {
+ $this->markTestSkipped('This test requires PHP 5.5 or newer.');
+ }
+
+ $this->runCommand(sprintf('php %s/symfony.phar new my_test_project', $this->rootDir));
+ }
+
public function testSymfonyInstallationInCurrentDirectory()
{
$projectDir = sprintf('%s/my_test_project', sys_get_temp_dir());
@@ -122,6 +127,17 @@ public function testSymfonyInstallationInCurrentDirectory()
$this->assertContains('Symfony version 2.7.5 - app/dev/debug', $output);
}
+ public function testSymfonyDemoInstallationWithNewCommand()
+ {
+ if (PHP_VERSION_ID < 50500) {
+ $this->markTestSkipped('This test requires PHP 5.5 or higher.');
+ }
+
+ $output = $this->runCommand(sprintf('php %s/symfony.phar new demo 3.4', $this->rootDir));
+ $this->assertContains("If you want to download the Symfony Demo app, execute 'symfony demo' instead of 'symfony new demo'", $output);
+ $this->fs->remove('demo');
+ }
+
/**
* Runs the given string as a command and returns the resulting output.
* The CWD is set to the root project directory to simplify command paths.
@@ -150,13 +166,6 @@ private function runCommand($command, $workingDirectory = null)
public function provideSymfonyInstallationData()
{
return array(
- array(
- '',
- '/.*Symfony 3\.1\.\d+ was successfully installed.*/',
- '/Symfony version 3\.1\.\d+(-DEV)? - app\/dev\/debug/',
- '5.5.9',
- ),
-
array(
'3.0',
'/.*Symfony 3\.0\.\d+ was successfully installed.*/',
@@ -166,9 +175,9 @@ public function provideSymfonyInstallationData()
array(
'lts',
- '/.*Symfony 2\.8\.\d+ was successfully installed.*/',
- '/Symfony version 2\.8\.\d+(-DEV)? - app\/dev\/debug/',
- '5.3.9',
+ '/.*Symfony 3\.4\.\d+ was successfully installed.*/',
+ '/Symfony 3\.4\.\d+ \(kernel: app, env: dev, debug: true\)/',
+ '5.5.9',
),
array(
diff --git a/tests/Symfony/Installer/Tests/Manager/ComposerManagerTest.php b/tests/Symfony/Installer/Tests/Manager/ComposerManagerTest.php
new file mode 100644
index 0000000..6b8a14e
--- /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'],
+ ];
+ }
+}