├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── box.json ├── composer.json ├── composer.lock ├── phpunit.xml.dist ├── src └── Symfony │ └── Installer │ ├── AboutCommand.php │ ├── Application.php │ ├── DemoCommand.php │ ├── DownloadCommand.php │ ├── Exception │ └── AbortException.php │ ├── Manager │ └── ComposerManager.php │ ├── NewCommand.php │ └── SelfUpdateCommand.php ├── symfony └── tests └── Symfony └── Installer └── Tests ├── IntegrationTest.php └── Manager └── ComposerManagerTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | sudo: false 3 | 4 | cache: 5 | directories: 6 | - $HOME/.composer/cache/files 7 | 8 | matrix: 9 | fast_finish: true 10 | include: 11 | - php: 5.4 12 | - php: 5.5 13 | - php: 5.6 14 | - php: 7.0 15 | - php: 7.1 16 | 17 | before_install: 18 | - composer self-update 19 | - curl -LSs https://box-project.github.io/box2/installer.php | php 20 | - mv box.phar box 21 | - chmod 755 box 22 | 23 | install: 24 | - composer install 25 | - ~/.phpenv/versions/5.6/bin/php box build 26 | 27 | script: 28 | - ./vendor/bin/simple-phpunit 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2016 Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Symfony Installer 2 | ================= 3 | 4 | This is the official installer to start new projects based on the Symfony 5 | full-stack framework. The installer is only compatible with Symfony 2 and 3. 6 | 7 | Creating Symfony 4 projects 8 | --------------------------- 9 | 10 | **This installer is not compatible with Symfony 4** and newer versions. Instead, 11 | use [Composer](https://getcomposer.org/) and create your Symfony 4 project as follows: 12 | 13 | ```bash 14 | $ composer create-project symfony/skeleton my_project_name 15 | ``` 16 | 17 | See the [Symfony Installation article](https://symfony.com/doc/current/setup.html) 18 | on the official Symfony Documentation for more details. 19 | 20 | Installing the installer 21 | ------------------------ 22 | 23 | This step is only needed the first time you use the installer: 24 | 25 | ### Linux and Mac OS X 26 | 27 | ```bash 28 | $ sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony 29 | $ sudo chmod a+x /usr/local/bin/symfony 30 | ``` 31 | 32 | ### Windows 33 | 34 | ```bash 35 | c:\> php -r "file_put_contents('symfony', file_get_contents('https://symfony.com/installer'));" 36 | ``` 37 | 38 | Move the downloaded `symfony` file to your projects directory and execute 39 | it as follows: 40 | 41 | ```bash 42 | c:\> php symfony 43 | ``` 44 | 45 | If you prefer to create a global `symfony` command, execute the following: 46 | 47 | ```bash 48 | c:\> (echo @ECHO OFF & echo php "%~dp0symfony" %*) > symfony.bat 49 | ``` 50 | 51 | Then, move both files (`symfony` and `symfony.bat`) to any location included 52 | in your execution path. Now you can run the `symfony` command anywhere on your 53 | system. 54 | 55 | Using the installer 56 | ------------------- 57 | 58 | **1. Start a new project with the latest stable Symfony version** 59 | 60 | Execute the `new` command and provide the name of your project as the only 61 | argument: 62 | 63 | ```bash 64 | # Linux, Mac OS X 65 | $ symfony new my_project 66 | 67 | # Windows 68 | c:\> php symfony new my_project 69 | ``` 70 | 71 | **2. Start a new project with the latest Symfony LTS (Long Term Support) version** 72 | 73 | Execute the `new` command and provide the name of your project as the first 74 | argument and `lts` as the second argument. The installer will automatically 75 | select the most recent LTS (*Long Term Support*) version available: 76 | 77 | ```bash 78 | # Linux, Mac OS X 79 | $ symfony new my_project lts 80 | 81 | # Windows 82 | c:\> php symfony new my_project lts 83 | ``` 84 | 85 | **3. Start a new project based on a specific Symfony branch** 86 | 87 | Execute the `new` command and provide the name of your project as the first 88 | argument and the branch number as the second argument. The installer will 89 | automatically select the most recent version available for the given branch: 90 | 91 | ```bash 92 | # Linux, Mac OS X 93 | $ symfony new my_project 2.8 94 | 95 | # Windows 96 | c:\> php symfony new my_project 2.8 97 | ``` 98 | 99 | **4. Start a new project based on a specific Symfony version** 100 | 101 | Execute the `new` command and provide the name of your project as the first 102 | argument and the exact Symfony version as the second argument: 103 | 104 | ```bash 105 | # Linux, Mac OS X 106 | $ symfony new my_project 2.8.1 107 | 108 | # Windows 109 | c:\> php symfony new my_project 2.8.1 110 | ``` 111 | 112 | **5. Install the Symfony demo application** 113 | 114 | The Symfony Demo is a reference application developed using the official Symfony 115 | Best Practices: 116 | 117 | ```bash 118 | # Linux, Mac OS X 119 | $ symfony demo 120 | 121 | # Windows 122 | c:\> php symfony demo 123 | ``` 124 | 125 | Updating the installer 126 | ---------------------- 127 | 128 | New versions of the Symfony Installer are released regularly. To update your 129 | installer version, execute the following command: 130 | 131 | ```bash 132 | # Linux, Mac OS X 133 | $ symfony self-update 134 | 135 | # Windows 136 | c:\> php symfony self-update 137 | ``` 138 | 139 | > **NOTE** 140 | > 141 | > If your system requires the use of a proxy server to download contents, the 142 | > installer tries to guess the best proxy settings from the `HTTP_PROXY` and 143 | > `http_proxy` environment variables. Make sure any of them is set before 144 | > executing the Symfony Installer. 145 | 146 | Troubleshooting 147 | --------------- 148 | 149 | ### SSL and certificates issues on Windows systems 150 | 151 | If you experience any error related with SSL or security certificates when using 152 | the Symfony Installer on Windows systems: 153 | 154 | 1) Check that the OpenSSL extension is enabled in your `php.ini` configuration: 155 | 156 | ```ini 157 | ; make sure that the following line is uncommented 158 | extension=php_openssl.dll 159 | ``` 160 | 161 | 2) Check that the path to the file that contains the security certificates 162 | exists and is defined in `php.ini`: 163 | 164 | ```ini 165 | openssl.cafile=C:/path/to/cacert.pem 166 | ``` 167 | 168 | If you can't locate the `cacert.pem` file anywhere on your system, you can 169 | safely download it from the official website of the cURL project: 170 | http://curl.haxx.se/ca/cacert.pem 171 | -------------------------------------------------------------------------------- /box.json: -------------------------------------------------------------------------------- 1 | { 2 | "directories": ["src/"], 3 | "finder": [ 4 | { 5 | "name": "*.php", 6 | "exclude": [ 7 | ".gitignore", 8 | ".md", 9 | "phpunit", 10 | "Tester", 11 | "Tests", 12 | "tests", 13 | "yaml" 14 | ], 15 | "in": "vendor" 16 | } 17 | ], 18 | "compactors": [ 19 | "Herrera\\Box\\Compactor\\Json", 20 | "Herrera\\Box\\Compactor\\Php" 21 | ], 22 | "compression": "GZ", 23 | "git-version": "package_version", 24 | "main": "symfony", 25 | "output": "symfony.phar", 26 | "stub": true, 27 | "chmod": "0755" 28 | } 29 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/symfony-installer", 3 | "description": "The official installer to create projects based on the Symfony full-stack framework.", 4 | "keywords": ["symfony", "framework", "installer", "php"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Symfony Community", 9 | "homepage": "https://symfony.com/contributors" 10 | } 11 | ], 12 | "autoload": { 13 | "psr-4": { "Symfony\\": "src/Symfony/" } 14 | }, 15 | "require": { 16 | "php": ">=5.4.0", 17 | "guzzlehttp/guzzle": "^5.3.1", 18 | "symfony/console": "~2.6", 19 | "symfony/filesystem": "~2.5", 20 | "raulfraile/distill": "~0.9,!=0.9.3,!=0.9.4" 21 | }, 22 | "require-dev": { 23 | "symfony/process": "~2.5", 24 | "symfony/phpunit-bridge": "^4.0" 25 | }, 26 | "extra": { 27 | "branch-alias": { 28 | "dev-master": "1.0-dev" 29 | } 30 | }, 31 | "bin": [ 32 | "symfony" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "8f72036c453520314de3967a312a6733", 8 | "packages": [ 9 | { 10 | "name": "guzzlehttp/guzzle", 11 | "version": "5.3.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/guzzle/guzzle.git", 15 | "reference": "70f1fa53b71c4647bf2762c09068a95f77e12fb8" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/70f1fa53b71c4647bf2762c09068a95f77e12fb8", 20 | "reference": "70f1fa53b71c4647bf2762c09068a95f77e12fb8", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "guzzlehttp/ringphp": "^1.1", 25 | "php": ">=5.4.0" 26 | }, 27 | "require-dev": { 28 | "ext-curl": "*", 29 | "phpunit/phpunit": "^4.0" 30 | }, 31 | "type": "library", 32 | "autoload": { 33 | "psr-4": { 34 | "GuzzleHttp\\": "src/" 35 | } 36 | }, 37 | "notification-url": "https://packagist.org/downloads/", 38 | "license": [ 39 | "MIT" 40 | ], 41 | "authors": [ 42 | { 43 | "name": "Michael Dowling", 44 | "email": "mtdowling@gmail.com", 45 | "homepage": "https://github.com/mtdowling" 46 | } 47 | ], 48 | "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", 49 | "homepage": "http://guzzlephp.org/", 50 | "keywords": [ 51 | "client", 52 | "curl", 53 | "framework", 54 | "http", 55 | "http client", 56 | "rest", 57 | "web service" 58 | ], 59 | "time": "2016-07-15T19:28:39+00:00" 60 | }, 61 | { 62 | "name": "guzzlehttp/ringphp", 63 | "version": "1.1.0", 64 | "source": { 65 | "type": "git", 66 | "url": "https://github.com/guzzle/RingPHP.git", 67 | "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b" 68 | }, 69 | "dist": { 70 | "type": "zip", 71 | "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/dbbb91d7f6c191e5e405e900e3102ac7f261bc0b", 72 | "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b", 73 | "shasum": "" 74 | }, 75 | "require": { 76 | "guzzlehttp/streams": "~3.0", 77 | "php": ">=5.4.0", 78 | "react/promise": "~2.0" 79 | }, 80 | "require-dev": { 81 | "ext-curl": "*", 82 | "phpunit/phpunit": "~4.0" 83 | }, 84 | "suggest": { 85 | "ext-curl": "Guzzle will use specific adapters if cURL is present" 86 | }, 87 | "type": "library", 88 | "extra": { 89 | "branch-alias": { 90 | "dev-master": "1.1-dev" 91 | } 92 | }, 93 | "autoload": { 94 | "psr-4": { 95 | "GuzzleHttp\\Ring\\": "src/" 96 | } 97 | }, 98 | "notification-url": "https://packagist.org/downloads/", 99 | "license": [ 100 | "MIT" 101 | ], 102 | "authors": [ 103 | { 104 | "name": "Michael Dowling", 105 | "email": "mtdowling@gmail.com", 106 | "homepage": "https://github.com/mtdowling" 107 | } 108 | ], 109 | "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", 110 | "time": "2015-05-20T03:37:09+00:00" 111 | }, 112 | { 113 | "name": "guzzlehttp/streams", 114 | "version": "3.0.0", 115 | "source": { 116 | "type": "git", 117 | "url": "https://github.com/guzzle/streams.git", 118 | "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5" 119 | }, 120 | "dist": { 121 | "type": "zip", 122 | "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", 123 | "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", 124 | "shasum": "" 125 | }, 126 | "require": { 127 | "php": ">=5.4.0" 128 | }, 129 | "require-dev": { 130 | "phpunit/phpunit": "~4.0" 131 | }, 132 | "type": "library", 133 | "extra": { 134 | "branch-alias": { 135 | "dev-master": "3.0-dev" 136 | } 137 | }, 138 | "autoload": { 139 | "psr-4": { 140 | "GuzzleHttp\\Stream\\": "src/" 141 | } 142 | }, 143 | "notification-url": "https://packagist.org/downloads/", 144 | "license": [ 145 | "MIT" 146 | ], 147 | "authors": [ 148 | { 149 | "name": "Michael Dowling", 150 | "email": "mtdowling@gmail.com", 151 | "homepage": "https://github.com/mtdowling" 152 | } 153 | ], 154 | "description": "Provides a simple abstraction over streams of data", 155 | "homepage": "http://guzzlephp.org/", 156 | "keywords": [ 157 | "Guzzle", 158 | "stream" 159 | ], 160 | "time": "2014-10-12T19:18:40+00:00" 161 | }, 162 | { 163 | "name": "pimple/pimple", 164 | "version": "v3.0.2", 165 | "source": { 166 | "type": "git", 167 | "url": "https://github.com/silexphp/Pimple.git", 168 | "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a" 169 | }, 170 | "dist": { 171 | "type": "zip", 172 | "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a30f7d6e57565a2e1a316e1baf2a483f788b258a", 173 | "reference": "a30f7d6e57565a2e1a316e1baf2a483f788b258a", 174 | "shasum": "" 175 | }, 176 | "require": { 177 | "php": ">=5.3.0" 178 | }, 179 | "type": "library", 180 | "extra": { 181 | "branch-alias": { 182 | "dev-master": "3.0.x-dev" 183 | } 184 | }, 185 | "autoload": { 186 | "psr-0": { 187 | "Pimple": "src/" 188 | } 189 | }, 190 | "notification-url": "https://packagist.org/downloads/", 191 | "license": [ 192 | "MIT" 193 | ], 194 | "authors": [ 195 | { 196 | "name": "Fabien Potencier", 197 | "email": "fabien@symfony.com" 198 | } 199 | ], 200 | "description": "Pimple, a simple Dependency Injection Container", 201 | "homepage": "http://pimple.sensiolabs.org", 202 | "keywords": [ 203 | "container", 204 | "dependency injection" 205 | ], 206 | "time": "2015-09-11T15:10:35+00:00" 207 | }, 208 | { 209 | "name": "raulfraile/distill", 210 | "version": "v0.9.9", 211 | "source": { 212 | "type": "git", 213 | "url": "https://github.com/raulfraile/distill.git", 214 | "reference": "8bc2fb68b570c07e9fa35e4b5d631bca68fcb193" 215 | }, 216 | "dist": { 217 | "type": "zip", 218 | "url": "https://api.github.com/repos/raulfraile/distill/zipball/8bc2fb68b570c07e9fa35e4b5d631bca68fcb193", 219 | "reference": "8bc2fb68b570c07e9fa35e4b5d631bca68fcb193", 220 | "shasum": "" 221 | }, 222 | "require": { 223 | "php": ">=5.4.0", 224 | "pimple/pimple": "~3.0", 225 | "symfony/filesystem": "~2.4", 226 | "symfony/process": "~2.4" 227 | }, 228 | "require-dev": { 229 | "mockery/mockery": "0.9.1", 230 | "raulfraile/ladybug": "~1.0", 231 | "symfony/finder": "~2.4" 232 | }, 233 | "suggest": { 234 | "ext-rar": "Allows to uncompress rar files using RarArchive", 235 | "ext-zip": "Allows to uncompress zip files using ZipArchive" 236 | }, 237 | "type": "library", 238 | "autoload": { 239 | "psr-4": { 240 | "Distill\\": "src/" 241 | } 242 | }, 243 | "notification-url": "https://packagist.org/downloads/", 244 | "license": [ 245 | "MIT" 246 | ], 247 | "authors": [ 248 | { 249 | "name": "Raul Fraile", 250 | "email": "raulfraile@gmail.com" 251 | } 252 | ], 253 | "description": "Smart compressed files extractor", 254 | "keywords": [ 255 | "7zip", 256 | "archive", 257 | "bz2", 258 | "bzip", 259 | "bzip2", 260 | "cab", 261 | "compression", 262 | "epub", 263 | "extractor", 264 | "gzip", 265 | "phar", 266 | "rar", 267 | "strategy", 268 | "tar.gz", 269 | "tar.xz", 270 | "tgz", 271 | "unzip", 272 | "xz", 273 | "zip" 274 | ], 275 | "time": "2015-10-17T06:41:00+00:00" 276 | }, 277 | { 278 | "name": "react/promise", 279 | "version": "v2.2.1", 280 | "source": { 281 | "type": "git", 282 | "url": "https://github.com/reactphp/promise.git", 283 | "reference": "3b6fca09c7d56321057fa8867c8dbe1abf648627" 284 | }, 285 | "dist": { 286 | "type": "zip", 287 | "url": "https://api.github.com/repos/reactphp/promise/zipball/3b6fca09c7d56321057fa8867c8dbe1abf648627", 288 | "reference": "3b6fca09c7d56321057fa8867c8dbe1abf648627", 289 | "shasum": "" 290 | }, 291 | "require": { 292 | "php": ">=5.4.0" 293 | }, 294 | "type": "library", 295 | "extra": { 296 | "branch-alias": { 297 | "dev-master": "2.0-dev" 298 | } 299 | }, 300 | "autoload": { 301 | "psr-4": { 302 | "React\\Promise\\": "src/" 303 | }, 304 | "files": [ 305 | "src/functions_include.php" 306 | ] 307 | }, 308 | "notification-url": "https://packagist.org/downloads/", 309 | "license": [ 310 | "MIT" 311 | ], 312 | "authors": [ 313 | { 314 | "name": "Jan Sorgalla", 315 | "email": "jsorgalla@gmail.com" 316 | } 317 | ], 318 | "description": "A lightweight implementation of CommonJS Promises/A for PHP", 319 | "time": "2015-07-03T13:48:55+00:00" 320 | }, 321 | { 322 | "name": "symfony/console", 323 | "version": "v2.7.5", 324 | "source": { 325 | "type": "git", 326 | "url": "https://github.com/symfony/console.git", 327 | "reference": "06cb17c013a82f94a3d840682b49425cd00a2161" 328 | }, 329 | "dist": { 330 | "type": "zip", 331 | "url": "https://api.github.com/repos/symfony/console/zipball/06cb17c013a82f94a3d840682b49425cd00a2161", 332 | "reference": "06cb17c013a82f94a3d840682b49425cd00a2161", 333 | "shasum": "" 334 | }, 335 | "require": { 336 | "php": ">=5.3.9" 337 | }, 338 | "require-dev": { 339 | "psr/log": "~1.0", 340 | "symfony/event-dispatcher": "~2.1", 341 | "symfony/phpunit-bridge": "~2.7", 342 | "symfony/process": "~2.1" 343 | }, 344 | "suggest": { 345 | "psr/log": "For using the console logger", 346 | "symfony/event-dispatcher": "", 347 | "symfony/process": "" 348 | }, 349 | "type": "library", 350 | "extra": { 351 | "branch-alias": { 352 | "dev-master": "2.7-dev" 353 | } 354 | }, 355 | "autoload": { 356 | "psr-4": { 357 | "Symfony\\Component\\Console\\": "" 358 | } 359 | }, 360 | "notification-url": "https://packagist.org/downloads/", 361 | "license": [ 362 | "MIT" 363 | ], 364 | "authors": [ 365 | { 366 | "name": "Fabien Potencier", 367 | "email": "fabien@symfony.com" 368 | }, 369 | { 370 | "name": "Symfony Community", 371 | "homepage": "https://symfony.com/contributors" 372 | } 373 | ], 374 | "description": "Symfony Console Component", 375 | "homepage": "https://symfony.com", 376 | "time": "2015-09-25T08:32:23+00:00" 377 | }, 378 | { 379 | "name": "symfony/filesystem", 380 | "version": "v2.7.5", 381 | "source": { 382 | "type": "git", 383 | "url": "https://github.com/symfony/filesystem.git", 384 | "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab" 385 | }, 386 | "dist": { 387 | "type": "zip", 388 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/a17f8a17c20e8614c15b8e116e2f4bcde102cfab", 389 | "reference": "a17f8a17c20e8614c15b8e116e2f4bcde102cfab", 390 | "shasum": "" 391 | }, 392 | "require": { 393 | "php": ">=5.3.9" 394 | }, 395 | "require-dev": { 396 | "symfony/phpunit-bridge": "~2.7" 397 | }, 398 | "type": "library", 399 | "extra": { 400 | "branch-alias": { 401 | "dev-master": "2.7-dev" 402 | } 403 | }, 404 | "autoload": { 405 | "psr-4": { 406 | "Symfony\\Component\\Filesystem\\": "" 407 | } 408 | }, 409 | "notification-url": "https://packagist.org/downloads/", 410 | "license": [ 411 | "MIT" 412 | ], 413 | "authors": [ 414 | { 415 | "name": "Fabien Potencier", 416 | "email": "fabien@symfony.com" 417 | }, 418 | { 419 | "name": "Symfony Community", 420 | "homepage": "https://symfony.com/contributors" 421 | } 422 | ], 423 | "description": "Symfony Filesystem Component", 424 | "homepage": "https://symfony.com", 425 | "time": "2015-09-09T17:42:36+00:00" 426 | }, 427 | { 428 | "name": "symfony/process", 429 | "version": "v2.7.5", 430 | "source": { 431 | "type": "git", 432 | "url": "https://github.com/symfony/process.git", 433 | "reference": "b27c8e317922cd3cdd3600850273cf6b82b2e8e9" 434 | }, 435 | "dist": { 436 | "type": "zip", 437 | "url": "https://api.github.com/repos/symfony/process/zipball/b27c8e317922cd3cdd3600850273cf6b82b2e8e9", 438 | "reference": "b27c8e317922cd3cdd3600850273cf6b82b2e8e9", 439 | "shasum": "" 440 | }, 441 | "require": { 442 | "php": ">=5.3.9" 443 | }, 444 | "require-dev": { 445 | "symfony/phpunit-bridge": "~2.7" 446 | }, 447 | "type": "library", 448 | "extra": { 449 | "branch-alias": { 450 | "dev-master": "2.7-dev" 451 | } 452 | }, 453 | "autoload": { 454 | "psr-4": { 455 | "Symfony\\Component\\Process\\": "" 456 | } 457 | }, 458 | "notification-url": "https://packagist.org/downloads/", 459 | "license": [ 460 | "MIT" 461 | ], 462 | "authors": [ 463 | { 464 | "name": "Fabien Potencier", 465 | "email": "fabien@symfony.com" 466 | }, 467 | { 468 | "name": "Symfony Community", 469 | "homepage": "https://symfony.com/contributors" 470 | } 471 | ], 472 | "description": "Symfony Process Component", 473 | "homepage": "https://symfony.com", 474 | "time": "2015-09-19T19:59:23+00:00" 475 | } 476 | ], 477 | "packages-dev": [ 478 | { 479 | "name": "symfony/phpunit-bridge", 480 | "version": "v4.0.2", 481 | "source": { 482 | "type": "git", 483 | "url": "https://github.com/symfony/phpunit-bridge.git", 484 | "reference": "61c84ebdce0d4c289413a222ee545f0114e60120" 485 | }, 486 | "dist": { 487 | "type": "zip", 488 | "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/61c84ebdce0d4c289413a222ee545f0114e60120", 489 | "reference": "61c84ebdce0d4c289413a222ee545f0114e60120", 490 | "shasum": "" 491 | }, 492 | "require": { 493 | "php": ">=5.3.3" 494 | }, 495 | "conflict": { 496 | "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" 497 | }, 498 | "suggest": { 499 | "ext-zip": "Zip support is required when using bin/simple-phpunit", 500 | "symfony/debug": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" 501 | }, 502 | "bin": [ 503 | "bin/simple-phpunit" 504 | ], 505 | "type": "symfony-bridge", 506 | "extra": { 507 | "branch-alias": { 508 | "dev-master": "4.0-dev" 509 | } 510 | }, 511 | "autoload": { 512 | "files": [ 513 | "bootstrap.php" 514 | ], 515 | "psr-4": { 516 | "Symfony\\Bridge\\PhpUnit\\": "" 517 | }, 518 | "exclude-from-classmap": [ 519 | "/Tests/" 520 | ] 521 | }, 522 | "notification-url": "https://packagist.org/downloads/", 523 | "license": [ 524 | "MIT" 525 | ], 526 | "authors": [ 527 | { 528 | "name": "Nicolas Grekas", 529 | "email": "p@tchwork.com" 530 | }, 531 | { 532 | "name": "Symfony Community", 533 | "homepage": "https://symfony.com/contributors" 534 | } 535 | ], 536 | "description": "Symfony PHPUnit Bridge", 537 | "homepage": "https://symfony.com", 538 | "time": "2017-12-14T19:48:22+00:00" 539 | } 540 | ], 541 | "aliases": [], 542 | "minimum-stability": "stable", 543 | "stability-flags": [], 544 | "prefer-stable": false, 545 | "prefer-lowest": false, 546 | "platform": { 547 | "php": ">=5.4.0" 548 | }, 549 | "platform-dev": [] 550 | } 551 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ./tests 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Symfony/Installer/AboutCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Installer; 13 | 14 | use Symfony\Component\Console\Command\Command; 15 | use Symfony\Component\Console\Input\InputInterface; 16 | use Symfony\Component\Console\Output\OutputInterface; 17 | 18 | /** 19 | * This command provides information about the Symfony installer. 20 | * 21 | * @author Javier Eguiluz 22 | */ 23 | class AboutCommand extends Command 24 | { 25 | /** 26 | * @var string The current version of the Symfony installer 27 | */ 28 | private $appVersion; 29 | 30 | /** 31 | * Constructor. 32 | * 33 | * @param string $appVersion The current version of the Symfony installer 34 | */ 35 | public function __construct($appVersion) 36 | { 37 | parent::__construct(); 38 | 39 | $this->appVersion = $appVersion; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | protected function configure() 46 | { 47 | $this 48 | ->setName('about') 49 | ->setDescription('Symfony Installer Help.') 50 | ; 51 | } 52 | 53 | /** 54 | * {@inheritdoc} 55 | */ 56 | protected function execute(InputInterface $input, OutputInterface $output) 57 | { 58 | $commandHelp = <<blog in the current directory using 67 | the latest stable version of Symfony, execute the following command: 68 | 69 | %s new blog 70 | 71 | Create a project based on the Symfony Long Term Support version (LTS): 72 | 73 | %3\$s new blog lts 74 | 75 | Create a project based on a specific Symfony branch: 76 | 77 | %3\$s new blog 2.8 or %3\$s new blog 3.0 78 | 79 | Create a project based on a specific Symfony version: 80 | 81 | %3\$s new blog 2.8.1 or %3\$s new blog 3.0.1 82 | 83 | Create a demo application to learn how a Symfony application works: 84 | 85 | %3\$s demo 86 | 87 | COMMAND_HELP; 88 | 89 | // show the self-update information only when using the PHAR file 90 | if ('phar://' === substr(__DIR__, 0, 7)) { 91 | $commandUpdateHelp = <<update your 97 | installer version, execute the following command: 98 | 99 | %3\$s self-update 100 | 101 | COMMAND_UPDATE_HELP; 102 | 103 | $commandHelp .= $commandUpdateHelp; 104 | } 105 | 106 | $output->writeln(sprintf($commandHelp, 107 | $this->appVersion, 108 | str_repeat('=', 20 + strlen($this->appVersion)), 109 | $this->getExecutedCommand() 110 | )); 111 | } 112 | 113 | /** 114 | * Returns the executed command. 115 | * 116 | * @return string The executed command 117 | */ 118 | private function getExecutedCommand() 119 | { 120 | $pathDirs = explode(PATH_SEPARATOR, $_SERVER['PATH']); 121 | $executedCommand = $_SERVER['PHP_SELF']; 122 | $executedCommandDir = dirname($executedCommand); 123 | 124 | if (in_array($executedCommandDir, $pathDirs)) { 125 | $executedCommand = basename($executedCommand); 126 | } 127 | 128 | return $executedCommand; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Symfony/Installer/Application.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Installer; 13 | 14 | use Symfony\Component\Console\Input\InputInterface; 15 | use Symfony\Component\Console\Output\OutputInterface; 16 | use Symfony\Component\Console\Application as ConsoleApplication; 17 | 18 | /** 19 | * This is main Symfony Installer console application class. 20 | * 21 | * @author Jerzy Zawadzki 22 | */ 23 | class Application extends ConsoleApplication 24 | { 25 | const VERSIONS_URL = 'https://get.symfony.com/symfony.version'; 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function doRun(InputInterface $input, OutputInterface $output) 31 | { 32 | return parent::doRun($input, $output); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Symfony/Installer/DemoCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Installer; 13 | 14 | use Symfony\Component\Console\Input\InputArgument; 15 | use Symfony\Component\Console\Input\InputInterface; 16 | use Symfony\Component\Console\Output\OutputInterface; 17 | use Symfony\Installer\Exception\AbortException; 18 | use Symfony\Installer\Manager\ComposerManager; 19 | 20 | /** 21 | * This command creates a full-featured Symfony demo application. 22 | * 23 | * @author Javier Eguiluz 24 | */ 25 | class DemoCommand extends DownloadCommand 26 | { 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | protected function configure() 31 | { 32 | $this 33 | ->setName('demo') 34 | ->addArgument('directory', InputArgument::OPTIONAL, 'Directory where the new project will be created.') 35 | ->setDescription('Creates a demo Symfony project.') 36 | ; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | protected function initialize(InputInterface $input, OutputInterface $output) 43 | { 44 | parent::initialize($input, $output); 45 | 46 | $this->version = 'lts'; 47 | 48 | if (!$input->getArgument('directory')) { 49 | $this->projectDir = getcwd(); 50 | 51 | $i = 1; 52 | $projectName = 'symfony_demo'; 53 | while (file_exists($this->projectDir.DIRECTORY_SEPARATOR.$projectName)) { 54 | $projectName = 'symfony_demo_'.(++$i); 55 | } 56 | 57 | $this->projectName = $projectName; 58 | $this->projectDir = $this->projectDir.DIRECTORY_SEPARATOR.$projectName; 59 | } else { 60 | $directory = rtrim(trim($input->getArgument('directory')), DIRECTORY_SEPARATOR); 61 | $this->projectDir = $this->fs->isAbsolutePath($directory) ? $directory : getcwd().DIRECTORY_SEPARATOR.$directory; 62 | $this->projectName = basename($directory); 63 | } 64 | 65 | $this->composerManager = new ComposerManager($this->projectDir); 66 | } 67 | 68 | /** 69 | * {@inheritdoc} 70 | */ 71 | protected function execute(InputInterface $input, OutputInterface $output) 72 | { 73 | try { 74 | $this 75 | ->checkInstallerVersion() 76 | ->checkProjectName() 77 | ->checkPermissions() 78 | ->download() 79 | ->extract() 80 | ->cleanUp() 81 | ->updateComposerConfig() 82 | ->createGitIgnore() 83 | ->checkSymfonyRequirements() 84 | ->displayInstallationResult() 85 | ; 86 | } catch (AbortException $e) { 87 | aborted: 88 | 89 | $output->writeln(''); 90 | $output->writeln('Aborting download and cleaning up temporary directories.'); 91 | 92 | $this->cleanUp(); 93 | 94 | return 1; 95 | } catch (\Exception $e) { 96 | // Guzzle can wrap the AbortException in a GuzzleException 97 | if ($e->getPrevious() instanceof AbortException) { 98 | goto aborted; 99 | } 100 | 101 | $this->cleanUp(); 102 | throw $e; 103 | } 104 | } 105 | 106 | /** 107 | * Removes all the temporary files and directories created to 108 | * download the demo application. 109 | * 110 | * @return $this 111 | */ 112 | private function cleanUp() 113 | { 114 | $this->fs->remove(dirname($this->downloadedFilePath)); 115 | 116 | return $this; 117 | } 118 | 119 | /** 120 | * It displays the message with the result of installing the Symfony Demo 121 | * application and provides some pointers to the user. 122 | * 123 | * @return $this 124 | */ 125 | private function displayInstallationResult() 126 | { 127 | if (empty($this->requirementsErrors)) { 128 | $this->output->writeln(sprintf( 129 | " %s Symfony Demo Application was successfully installed. Now you can:\n", 130 | defined('PHP_WINDOWS_VERSION_BUILD') ? 'OK' : '✔' 131 | )); 132 | } else { 133 | $this->output->writeln(sprintf( 134 | " %s Symfony Demo Application was successfully installed but your system doesn't meet the\n". 135 | " technical requirements to run Symfony applications! Fix the following issues before executing it:\n", 136 | defined('PHP_WINDOWS_VERSION_BUILD') ? 'FAILED' : '✕' 137 | )); 138 | 139 | foreach ($this->requirementsErrors as $helpText) { 140 | $this->output->writeln(' * '.$helpText); 141 | } 142 | 143 | $this->output->writeln(sprintf( 144 | " After fixing these issues, re-check Symfony requirements executing this command:\n\n". 145 | " php %s/bin/symfony_requirements\n\n". 146 | " Then, you can:\n", 147 | $this->projectName 148 | )); 149 | } 150 | 151 | $serverRunCommand = extension_loaded('pcntl') ? 'server:start' : 'server:run'; 152 | 153 | $this->output->writeln(sprintf( 154 | " 1. Change your current directory to %s\n\n". 155 | " 2. Execute the php bin/console %s command to run the demo application.\n\n". 156 | " 3. Browse to the http://localhost:8000 URL to see the demo application in action.\n\n", 157 | $this->projectDir, $serverRunCommand 158 | )); 159 | 160 | $this->output->writeln( 161 | " WARNING \n\n". 162 | " This installer downloads the old Symfony Demo version based on Symfony 3.\n". 163 | " If you prefer to install the new version based on Symfony 4 and Symfony Flex,\n". 164 | " execute the following command:\n\n". 165 | " composer create-project symfony/symfony-demo\n"); 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * {@inheritdoc} 172 | */ 173 | protected function getDownloadedApplicationType() 174 | { 175 | return 'the Symfony Demo Application'; 176 | } 177 | 178 | /** 179 | * {@inheritdoc} 180 | */ 181 | protected function getRemoteFileUrl() 182 | { 183 | return 'https://symfony.com/download?v=Symfony_Demo'; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/Symfony/Installer/DownloadCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Installer; 13 | 14 | use Distill\Distill; 15 | use Distill\Exception\IO\Input\FileCorruptedException; 16 | use Distill\Exception\IO\Input\FileEmptyException; 17 | use Distill\Exception\IO\Output\TargetDirectoryNotWritableException; 18 | use Distill\Strategy\MinimumSize; 19 | use GuzzleHttp\Client; 20 | use GuzzleHttp\Exception\ClientException; 21 | use GuzzleHttp\Event\ProgressEvent; 22 | use GuzzleHttp\Utils; 23 | use Symfony\Component\Console\Command\Command; 24 | use Symfony\Component\Console\Helper\Helper; 25 | use Symfony\Component\Console\Helper\ProgressBar; 26 | use Symfony\Component\Console\Input\InputInterface; 27 | use Symfony\Component\Console\Output\OutputInterface; 28 | use Symfony\Component\Filesystem\Exception\IOException; 29 | use Symfony\Component\Filesystem\Filesystem; 30 | use Symfony\Component\Intl\Exception\MethodArgumentValueNotImplementedException; 31 | use Symfony\Installer\Exception\AbortException; 32 | use Symfony\Installer\Manager\ComposerManager; 33 | 34 | /** 35 | * Abstract command used by commands which download and extract compressed Symfony files. 36 | * 37 | * @author Christophe Coevoet 38 | * @author Javier Eguiluz 39 | */ 40 | abstract class DownloadCommand extends Command 41 | { 42 | /** 43 | * @var Filesystem To dump content to a file 44 | */ 45 | protected $fs; 46 | 47 | /** 48 | * @var OutputInterface To output content 49 | */ 50 | protected $output; 51 | 52 | /** 53 | * @var string The project name 54 | */ 55 | protected $projectName; 56 | 57 | /** 58 | * @var string The project dir 59 | */ 60 | protected $projectDir; 61 | 62 | /** 63 | * @var string The version to install 64 | */ 65 | protected $version = 'latest'; 66 | 67 | /** 68 | * @var string The latest installer version 69 | */ 70 | protected $latestInstallerVersion; 71 | 72 | /** 73 | * @var string The version of the local installer being executed 74 | */ 75 | protected $localInstallerVersion; 76 | 77 | /** 78 | * @var string The path to the downloaded file 79 | */ 80 | protected $downloadedFilePath; 81 | 82 | /** 83 | * @var array The requirement errors 84 | */ 85 | protected $requirementsErrors = array(); 86 | 87 | /** @var ComposerManager */ 88 | protected $composerManager; 89 | 90 | /** 91 | * Returns the type of the downloaded application in a human readable format. 92 | * It's mainly used to display readable error messages. 93 | * 94 | * @return string The type of the downloaded application in a human readable format 95 | */ 96 | abstract protected function getDownloadedApplicationType(); 97 | 98 | /** 99 | * Returns the absolute URL of the remote file downloaded by the command. 100 | * 101 | * @return string The absolute URL of the remote file downloaded by the command 102 | */ 103 | abstract protected function getRemoteFileUrl(); 104 | 105 | /** 106 | * {@inheritdoc} 107 | */ 108 | protected function initialize(InputInterface $input, OutputInterface $output) 109 | { 110 | $this->output = $output; 111 | $this->fs = new Filesystem(); 112 | 113 | $this->latestInstallerVersion = $this->getUrlContents(Application::VERSIONS_URL); 114 | $this->localInstallerVersion = $this->getApplication()->getVersion(); 115 | 116 | $this->enableSignalHandler(); 117 | } 118 | 119 | /** 120 | * Chooses the best compressed file format to download (ZIP or TGZ) depending upon the 121 | * available operating system uncompressing commands and the enabled PHP extensions 122 | * and it downloads the file. 123 | * 124 | * @return $this 125 | * 126 | * @throws \RuntimeException If the Symfony archive could not be downloaded 127 | */ 128 | protected function download() 129 | { 130 | $this->output->writeln(sprintf("\n Downloading %s...\n", $this->getDownloadedApplicationType())); 131 | 132 | // decide which is the best compressed version to download 133 | $distill = new Distill(); 134 | $symfonyArchiveFile = $distill 135 | ->getChooser() 136 | ->setStrategy(new MinimumSize()) 137 | ->addFilesWithDifferentExtensions($this->getRemoteFileUrl(), array('tgz', 'zip')) 138 | ->getPreferredFile() 139 | ; 140 | 141 | /** @var ProgressBar|null $progressBar */ 142 | $progressBar = null; 143 | $downloadCallback = function (ProgressEvent $event) use (&$progressBar) { 144 | $downloadSize = $event->downloadSize; 145 | $downloaded = $event->downloaded; 146 | 147 | // progress bar is only displayed for files larger than 1MB 148 | if ($downloadSize < 1 * 1024 * 1024) { 149 | return; 150 | } 151 | 152 | if (null === $progressBar) { 153 | ProgressBar::setPlaceholderFormatterDefinition('max', function (ProgressBar $bar) { 154 | return Helper::formatMemory($bar->getMaxSteps()); 155 | }); 156 | ProgressBar::setPlaceholderFormatterDefinition('current', function (ProgressBar $bar) { 157 | return str_pad(Helper::formatMemory($bar->getProgress()), 11, ' ', STR_PAD_LEFT); 158 | }); 159 | 160 | $progressBar = new ProgressBar($this->output, $downloadSize); 161 | $progressBar->setFormat('%current%/%max% %bar% %percent:3s%%'); 162 | $progressBar->setRedrawFrequency(max(1, floor($downloadSize / 1000))); 163 | $progressBar->setBarWidth(60); 164 | 165 | if (!defined('PHP_WINDOWS_VERSION_BUILD')) { 166 | $progressBar->setEmptyBarCharacter('░'); // light shade character \u2591 167 | $progressBar->setProgressCharacter(''); 168 | $progressBar->setBarCharacter('▓'); // dark shade character \u2593 169 | } 170 | 171 | $progressBar->start(); 172 | } 173 | 174 | $progressBar->setProgress($downloaded); 175 | }; 176 | 177 | $client = $this->getGuzzleClient(); 178 | 179 | // store the file in a temporary hidden directory with a random name 180 | $this->downloadedFilePath = rtrim(getcwd(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'.'.uniqid(time()).DIRECTORY_SEPARATOR.'symfony.'.pathinfo($symfonyArchiveFile, PATHINFO_EXTENSION); 181 | 182 | try { 183 | $request = $client->createRequest('GET', $symfonyArchiveFile); 184 | $request->getEmitter()->on('progress', $downloadCallback); 185 | $response = $client->send($request); 186 | } catch (ClientException $e) { 187 | if ('new' === $this->getName() && (403 === $e->getCode() || 404 === $e->getCode())) { 188 | throw new \RuntimeException(sprintf( 189 | "The selected version (%s) cannot be installed because it does not exist.\n". 190 | "Execute the following command to install the latest stable Symfony release:\n". 191 | '%s new %s', 192 | $this->version, 193 | $_SERVER['PHP_SELF'], 194 | str_replace(getcwd().DIRECTORY_SEPARATOR, '', $this->projectDir) 195 | )); 196 | } else { 197 | throw new \RuntimeException(sprintf( 198 | "There was an error downloading %s from symfony.com server:\n%s", 199 | $this->getDownloadedApplicationType(), 200 | $e->getMessage() 201 | ), null, $e); 202 | } 203 | } 204 | 205 | $this->fs->dumpFile($this->downloadedFilePath, $response->getBody()); 206 | 207 | if (null !== $progressBar) { 208 | $progressBar->finish(); 209 | $this->output->writeln("\n"); 210 | } 211 | 212 | return $this; 213 | } 214 | 215 | /** 216 | * Checks the project name. 217 | * 218 | * @return $this 219 | * 220 | * @throws \RuntimeException If there is already a projet in the specified directory 221 | */ 222 | protected function checkProjectName() 223 | { 224 | if (is_dir($this->projectDir) && !$this->isEmptyDirectory($this->projectDir)) { 225 | throw new \RuntimeException(sprintf( 226 | "There is already a '%s' project in this directory (%s).\n". 227 | 'Change your project name or create it in another directory.', 228 | $this->projectName, $this->projectDir 229 | )); 230 | } 231 | 232 | if ('demo' === $this->projectName && 'new' === $this->getName()) { 233 | $this->output->writeln("\n TIP If you want to download the Symfony Demo app, execute 'symfony demo' instead of 'symfony new demo'"); 234 | } 235 | 236 | return $this; 237 | } 238 | 239 | /** 240 | * Returns the Guzzle client configured according to the system environment 241 | * (e.g. it takes into account whether it should use a proxy server or not). 242 | * 243 | * @return Client The configured Guzzle client 244 | * 245 | * @throws \RuntimeException If the php-curl is not installed or the allow_url_fopen ini setting is not set 246 | */ 247 | protected function getGuzzleClient() 248 | { 249 | $defaults = array(); 250 | 251 | // check if the client must use a proxy server 252 | if (!empty($_SERVER['HTTP_PROXY']) || !empty($_SERVER['http_proxy'])) { 253 | $defaults['proxy'] = !empty($_SERVER['http_proxy']) ? $_SERVER['http_proxy'] : $_SERVER['HTTP_PROXY']; 254 | } 255 | 256 | if ($this->output->getVerbosity() >= OutputInterface::VERBOSITY_DEBUG) { 257 | $defaults['debug'] = true; 258 | } 259 | 260 | try { 261 | $handler = Utils::getDefaultHandler(); 262 | } catch (\RuntimeException $e) { 263 | throw new \RuntimeException('The Symfony installer requires the php-curl extension or the allow_url_fopen ini setting.'); 264 | } 265 | 266 | return new Client(array('defaults' => $defaults, 'handler' => $handler)); 267 | } 268 | 269 | /** 270 | * Extracts the compressed Symfony file (ZIP or TGZ) using the 271 | * native operating system commands if available or PHP code otherwise. 272 | * 273 | * @return $this 274 | * 275 | * @throws \RuntimeException If the downloaded archive could not be extracted 276 | */ 277 | protected function extract() 278 | { 279 | $this->output->writeln(" Preparing project...\n"); 280 | 281 | try { 282 | $distill = new Distill(); 283 | $extractionSucceeded = $distill->extractWithoutRootDirectory($this->downloadedFilePath, $this->projectDir); 284 | } catch (FileCorruptedException $e) { 285 | throw new \RuntimeException(sprintf( 286 | "%s can't be installed because the downloaded package is corrupted.\n". 287 | "To solve this issue, try executing this command again:\n%s", 288 | ucfirst($this->getDownloadedApplicationType()), $this->getExecutedCommand() 289 | )); 290 | } catch (FileEmptyException $e) { 291 | throw new \RuntimeException(sprintf( 292 | "%s can't be installed because the downloaded package is empty.\n". 293 | "To solve this issue, try executing this command again:\n%s", 294 | ucfirst($this->getDownloadedApplicationType()), $this->getExecutedCommand() 295 | )); 296 | } catch (TargetDirectoryNotWritableException $e) { 297 | throw new \RuntimeException(sprintf( 298 | "%s can't be installed because the installer doesn't have enough\n". 299 | "permissions to uncompress and rename the package contents.\n". 300 | "To solve this issue, check the permissions of the %s directory and\n". 301 | "try executing this command again:\n%s", 302 | ucfirst($this->getDownloadedApplicationType()), getcwd(), $this->getExecutedCommand() 303 | )); 304 | } catch (\Exception $e) { 305 | throw new \RuntimeException(sprintf( 306 | "%s can't be installed because the downloaded package is corrupted\n". 307 | "or because the installer doesn't have enough permissions to uncompress and\n". 308 | "rename the package contents.\n". 309 | "To solve this issue, check the permissions of the %s directory and\n". 310 | "try executing this command again:\n%s", 311 | ucfirst($this->getDownloadedApplicationType()), getcwd(), $this->getExecutedCommand() 312 | ), null, $e); 313 | } 314 | 315 | if (!$extractionSucceeded) { 316 | throw new \RuntimeException(sprintf( 317 | "%s can't be installed because the downloaded package is corrupted\n". 318 | "or because the uncompress commands of your operating system didn't work.", 319 | ucfirst($this->getDownloadedApplicationType()) 320 | )); 321 | } 322 | 323 | return $this; 324 | } 325 | 326 | /** 327 | * Checks if environment meets symfony requirements. 328 | * 329 | * @return $this 330 | */ 331 | protected function checkSymfonyRequirements() 332 | { 333 | if (null === $requirementsFile = $this->getSymfonyRequirementsFilePath()) { 334 | return $this; 335 | } 336 | 337 | try { 338 | require $requirementsFile; 339 | $symfonyRequirements = new \SymfonyRequirements(); 340 | $this->requirementsErrors = array(); 341 | foreach ($symfonyRequirements->getRequirements() as $req) { 342 | if ($helpText = $this->getErrorMessage($req)) { 343 | $this->requirementsErrors[] = $helpText; 344 | } 345 | } 346 | } catch (MethodArgumentValueNotImplementedException $e) { 347 | // workaround https://github.com/symfony/symfony-installer/issues/163 348 | } 349 | 350 | return $this; 351 | } 352 | 353 | private function getSymfonyRequirementsFilePath() 354 | { 355 | $paths = array( 356 | $this->projectDir.'/app/SymfonyRequirements.php', 357 | $this->projectDir.'/var/SymfonyRequirements.php', 358 | ); 359 | 360 | foreach ($paths as $path) { 361 | if (file_exists($path)) { 362 | return $path; 363 | } 364 | } 365 | 366 | return null; 367 | } 368 | 369 | /** 370 | * Updates the composer.json file to provide better values for some of the 371 | * default configuration values. 372 | * 373 | * @return $this 374 | */ 375 | protected function updateComposerConfig() 376 | { 377 | $this->composerManager->initializeProjectConfig(); 378 | 379 | return $this; 380 | } 381 | 382 | /** 383 | * Creates the appropriate .gitignore file for a Symfony project if it doesn't exist. 384 | * 385 | * @return $this 386 | */ 387 | protected function createGitIgnore() 388 | { 389 | if (!is_file($path = $this->projectDir.'/.gitignore')) { 390 | try { 391 | $client = $this->getGuzzleClient(); 392 | 393 | $response = $client->get(sprintf( 394 | 'https://raw.githubusercontent.com/symfony/symfony-standard/v%s/.gitignore', 395 | $this->getInstalledSymfonyVersion() 396 | )); 397 | 398 | $this->fs->dumpFile($path, $response->getBody()->getContents()); 399 | } catch (\Exception $e) { 400 | // don't throw an exception in case the .gitignore file cannot be created, 401 | // because this is just an enhancement, not something mandatory for the project 402 | } 403 | } 404 | 405 | return $this; 406 | } 407 | 408 | /** 409 | * Returns the full Symfony version number of the project by getting 410 | * it from the composer.lock file. 411 | * 412 | * @return string The installed Symfony version 413 | */ 414 | protected function getInstalledSymfonyVersion() 415 | { 416 | $symfonyVersion = $this->composerManager->getPackageVersion('symfony/symfony'); 417 | 418 | if (!empty($symfonyVersion) && 'v' === substr($symfonyVersion, 0, 1)) { 419 | return substr($symfonyVersion, 1); 420 | } 421 | 422 | return $symfonyVersion; 423 | } 424 | 425 | /** 426 | * Checks if the installer has enough permissions to create the project. 427 | * 428 | * @return $this 429 | * 430 | * @throws IOException If the installer does not have enough permissions to write to the project parent directory 431 | */ 432 | protected function checkPermissions() 433 | { 434 | $projectParentDirectory = dirname($this->projectDir); 435 | 436 | if (!is_writable($projectParentDirectory)) { 437 | throw new IOException(sprintf('Installer does not have enough permissions to write to the "%s" directory.', $projectParentDirectory)); 438 | } 439 | 440 | return $this; 441 | } 442 | 443 | /** 444 | * Formats the error message contained in the given Requirement item 445 | * using the optional line length provided. 446 | * 447 | * @param \Requirement $requirement The Symfony requirements 448 | * @param int $lineSize The maximum line length 449 | * 450 | * @return string The formatted error message 451 | */ 452 | protected function getErrorMessage(\Requirement $requirement, $lineSize = 70) 453 | { 454 | if ($requirement->isFulfilled()) { 455 | return; 456 | } 457 | 458 | $errorMessage = wordwrap($requirement->getTestMessage(), $lineSize - 3, PHP_EOL.' ').PHP_EOL; 459 | $errorMessage .= ' > '.wordwrap($requirement->getHelpText(), $lineSize - 5, PHP_EOL.' > ').PHP_EOL; 460 | 461 | return $errorMessage; 462 | } 463 | 464 | /** 465 | * Generates a good random value for Symfony's 'secret' option. 466 | * 467 | * @return string The randomly generated secret 468 | */ 469 | protected function generateRandomSecret() 470 | { 471 | if (function_exists('openssl_random_pseudo_bytes')) { 472 | return hash('sha1', openssl_random_pseudo_bytes(23)); 473 | } 474 | 475 | return hash('sha1', uniqid(mt_rand(), true)); 476 | } 477 | 478 | /** 479 | * Returns the executed command with all its arguments 480 | * (e.g. "symfony new blog 2.8.1"). 481 | * 482 | * @return string The executed command with all its arguments 483 | */ 484 | protected function getExecutedCommand() 485 | { 486 | $commandBinary = $_SERVER['PHP_SELF']; 487 | $commandBinaryDir = dirname($commandBinary); 488 | $pathDirs = explode(PATH_SEPARATOR, $_SERVER['PATH']); 489 | if (in_array($commandBinaryDir, $pathDirs)) { 490 | $commandBinary = basename($commandBinary); 491 | } 492 | 493 | $commandName = $this->getName(); 494 | 495 | if ('new' === $commandName) { 496 | $commandArguments = sprintf('%s %s', $this->projectName, ('latest' !== $this->version) ? $this->version : ''); 497 | } elseif ('demo' === $commandName) { 498 | $commandArguments = ''; 499 | } 500 | 501 | return sprintf('%s %s %s', $commandBinary, $commandName, $commandArguments); 502 | } 503 | 504 | /** 505 | * Checks whether the given directory is empty or not. 506 | * 507 | * @param string $dir the path of the directory to check 508 | * 509 | * @return bool Whether the given directory is empty 510 | */ 511 | protected function isEmptyDirectory($dir) 512 | { 513 | // glob() cannot be used because it doesn't take into account hidden files 514 | // scandir() returns '.' and '..' for an empty dir 515 | return 2 === count(scandir($dir.'/')); 516 | } 517 | 518 | /** 519 | * Checks that the asked version is in the 3.x branch. 520 | * 521 | * @return bool Whether is Symfony3 522 | */ 523 | protected function isSymfony3() 524 | { 525 | return '3' === $this->version[0] || 'latest' === $this->version; 526 | } 527 | 528 | /** 529 | * Checks if the installed version is the latest one and displays some 530 | * warning messages if not. 531 | * 532 | * @return $this 533 | */ 534 | protected function checkInstallerVersion() 535 | { 536 | // check update only if installer is running via a PHAR file 537 | if ('phar://' !== substr(__DIR__, 0, 7)) { 538 | return $this; 539 | } 540 | 541 | if (!$this->isInstallerUpdated()) { 542 | $this->output->writeln(sprintf( 543 | "\n WARNING Your Symfony Installer version (%s) is outdated.\n". 544 | ' Execute the command "%s selfupdate" to get the latest version (%s).', 545 | $this->localInstallerVersion, $_SERVER['PHP_SELF'], $this->latestInstallerVersion 546 | )); 547 | } 548 | 549 | return $this; 550 | } 551 | 552 | /** 553 | * @return bool Whether the installed version is the latest one 554 | */ 555 | protected function isInstallerUpdated() 556 | { 557 | return version_compare($this->localInstallerVersion, $this->latestInstallerVersion, '>='); 558 | } 559 | 560 | /** 561 | * Returns the contents obtained by making a GET request to the given URL. 562 | * 563 | * @param string $url The URL to get the contents from 564 | * 565 | * @return string The obtained contents of $url 566 | */ 567 | protected function getUrlContents($url) 568 | { 569 | $client = $this->getGuzzleClient(); 570 | 571 | return $client->get($url)->getBody()->getContents(); 572 | } 573 | 574 | /** 575 | * Enables the signal handler. 576 | * 577 | * @throws AbortException if the execution has been aborted with SIGINT signal 578 | */ 579 | private function enableSignalHandler() 580 | { 581 | if (!function_exists('pcntl_signal')) { 582 | return; 583 | } 584 | 585 | declare(ticks=1); 586 | 587 | pcntl_signal(SIGINT, function () { 588 | error_reporting(0); 589 | 590 | throw new AbortException(); 591 | }); 592 | } 593 | } 594 | -------------------------------------------------------------------------------- /src/Symfony/Installer/Exception/AbortException.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class AbortException extends \RuntimeException 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/Symfony/Installer/Manager/ComposerManager.php: -------------------------------------------------------------------------------- 1 | projectDir = $projectDir; 15 | $this->fs = new Filesystem(); 16 | } 17 | 18 | public function initializeProjectConfig() 19 | { 20 | $composerConfig = $this->getProjectConfig(); 21 | 22 | if (isset($composerConfig['config']['platform']['php'])) { 23 | unset($composerConfig['config']['platform']['php']); 24 | 25 | if (empty($composerConfig['config']['platform'])) { 26 | unset($composerConfig['config']['platform']); 27 | } 28 | 29 | if (empty($composerConfig['config'])) { 30 | unset($composerConfig['config']); 31 | } 32 | } 33 | 34 | $this->saveProjectConfig($composerConfig); 35 | } 36 | 37 | public function updateProjectConfig(array $newConfig) 38 | { 39 | $oldConfig = $this->getProjectConfig(); 40 | $projectConfig = array_replace_recursive($oldConfig, $newConfig); 41 | 42 | // remove null values from project's config 43 | $projectConfig = array_filter($projectConfig, function($value) { return !is_null($value); }); 44 | 45 | $this->saveProjectConfig($projectConfig); 46 | } 47 | 48 | public function getPackageVersion($packageName) 49 | { 50 | $composerLockFileContents = json_decode(file_get_contents($this->projectDir.'/composer.lock'), true); 51 | 52 | foreach ($composerLockFileContents['packages'] as $packageConfig) { 53 | if ($packageName === $packageConfig['name']) { 54 | return $packageConfig['version']; 55 | } 56 | } 57 | } 58 | 59 | /** 60 | * Generates a good Composer project name based on the application name 61 | * and on the user name. 62 | * 63 | * @param $projectName 64 | * 65 | * @return string The generated Composer package name 66 | */ 67 | public function createPackageName($projectName) 68 | { 69 | if (!empty($_SERVER['USERNAME'])) { 70 | $packageName = $_SERVER['USERNAME'].'/'.$projectName; 71 | } elseif (true === extension_loaded('posix') && $user = posix_getpwuid(posix_getuid())) { 72 | $packageName = $user['name'].'/'.$projectName; 73 | } elseif (get_current_user()) { 74 | $packageName = get_current_user().'/'.$projectName; 75 | } else { 76 | // package names must be in the format foo/bar 77 | $packageName = $projectName.'/'.$projectName; 78 | } 79 | 80 | return $this->fixPackageName($packageName); 81 | } 82 | 83 | /** 84 | * It returns the project's Composer config as a PHP array. 85 | * 86 | * @return array 87 | */ 88 | private function getProjectConfig() 89 | { 90 | $composerJsonPath = $this->projectDir.'/composer.json'; 91 | if (!is_writable($composerJsonPath)) { 92 | return []; 93 | } 94 | 95 | return json_decode(file_get_contents($composerJsonPath), true); 96 | } 97 | 98 | /** 99 | * It saves the given PHP array as the project's Composer config. In addition 100 | * to JSON-serializing the contents, it synchronizes the composer.lock file to 101 | * avoid out-of-sync Composer errors. 102 | * 103 | * @param array $config 104 | */ 105 | private function saveProjectConfig(array $config) 106 | { 107 | $composerJsonPath = $this->projectDir.'/composer.json'; 108 | $this->fs->dumpFile($composerJsonPath, json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n"); 109 | 110 | $this->syncComposerLockFile(); 111 | } 112 | 113 | /** 114 | * Updates the hash values stored in composer.lock to avoid out-of-sync 115 | * problems when the composer.json file contents are changed. 116 | */ 117 | private function syncComposerLockFile() 118 | { 119 | $composerJsonFileContents = file_get_contents($this->projectDir.'/composer.json'); 120 | $composerLockFileContents = json_decode(file_get_contents($this->projectDir.'/composer.lock'), true); 121 | 122 | if (array_key_exists('hash', $composerLockFileContents)) { 123 | $composerLockFileContents['hash'] = md5($composerJsonFileContents); 124 | } 125 | 126 | if (array_key_exists('content-hash', $composerLockFileContents)) { 127 | $composerLockFileContents['content-hash'] = $this->getComposerContentHash($composerJsonFileContents); 128 | } 129 | 130 | $this->fs->dumpFile($this->projectDir.'/composer.lock', json_encode($composerLockFileContents, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)."\n"); 131 | } 132 | 133 | /** 134 | * Returns the md5 hash of the sorted content of the composer file. 135 | * 136 | * @see https://github.com/composer/composer/blob/master/src/Composer/Package/Locker.php (getContentHash() method) 137 | * 138 | * @param string $composerJsonFileContents The contents of the composer.json file. 139 | * 140 | * @return string The hash of the composer file content. 141 | */ 142 | private function getComposerContentHash($composerJsonFileContents) 143 | { 144 | $composerConfig = json_decode($composerJsonFileContents, true); 145 | 146 | $relevantKeys = array( 147 | 'name', 148 | 'version', 149 | 'require', 150 | 'require-dev', 151 | 'conflict', 152 | 'replace', 153 | 'provide', 154 | 'minimum-stability', 155 | 'prefer-stable', 156 | 'repositories', 157 | 'extra', 158 | ); 159 | 160 | $relevantComposerConfig = array(); 161 | 162 | foreach (array_intersect($relevantKeys, array_keys($composerConfig)) as $key) { 163 | $relevantComposerConfig[$key] = $composerConfig[$key]; 164 | } 165 | 166 | if (isset($composerConfig['config']['platform'])) { 167 | $relevantComposerConfig['config']['platform'] = $composerConfig['config']['platform']; 168 | } 169 | 170 | ksort($relevantComposerConfig); 171 | 172 | return md5(json_encode($relevantComposerConfig)); 173 | } 174 | 175 | /** 176 | * Transforms a project name into a valid Composer package name. 177 | * 178 | * @param string $name The project name to transform 179 | * 180 | * @return string The valid Composer package name 181 | */ 182 | private function fixPackageName($name) 183 | { 184 | $name = str_replace( 185 | ['à', 'á', 'â', 'ä', 'æ', 'ã', 'å', 'ā', 'é', 'è', 'ê', 'ë', 'ę', 'ė', 'ē', 'ī', 'į', 'í', 'ì', 'ï', 'î', 'ō', 'ø', 'œ', 'õ', 'ó', 'ò', 'ö', 'ô', 'ū', 'ú', 'ù', 'ü', 'û', 'ç', 'ć', 'č', 'ł', 'ñ', 'ń', 'ß', 'ś', 'š', 'ŵ', 'ŷ', 'ÿ', 'ź', 'ž', 'ż'], 186 | ['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'], 187 | $name 188 | ); 189 | $name = preg_replace('#[^A-Za-z0-9_./-]+#', '', $name); 190 | 191 | return strtolower($name); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/Symfony/Installer/NewCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Installer; 13 | 14 | use Symfony\Component\Console\Input\InputArgument; 15 | use Symfony\Component\Console\Input\InputInterface; 16 | use Symfony\Component\Console\Output\OutputInterface; 17 | use Symfony\Installer\Exception\AbortException; 18 | use Symfony\Installer\Manager\ComposerManager; 19 | 20 | /** 21 | * This command creates new Symfony projects for the given Symfony version. 22 | * 23 | * @author Christophe Coevoet 24 | * @author Javier Eguiluz 25 | */ 26 | class NewCommand extends DownloadCommand 27 | { 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | protected function configure() 32 | { 33 | $this 34 | ->setName('new') 35 | ->setDescription('Creates a new Symfony project.') 36 | ->addArgument('directory', InputArgument::REQUIRED, 'Directory where the new project will be created.') 37 | ->addArgument('version', InputArgument::OPTIONAL, 'The Symfony version to be installed (defaults to the latest stable version).', 'latest') 38 | ; 39 | } 40 | 41 | /** 42 | * {@inheritdoc} 43 | */ 44 | protected function initialize(InputInterface $input, OutputInterface $output) 45 | { 46 | parent::initialize($input, $output); 47 | 48 | $directory = rtrim(trim($input->getArgument('directory')), DIRECTORY_SEPARATOR); 49 | $this->version = trim($input->getArgument('version')); 50 | $this->projectDir = $this->fs->isAbsolutePath($directory) ? $directory : getcwd().DIRECTORY_SEPARATOR.$directory; 51 | $this->projectName = basename($directory); 52 | 53 | $this->composerManager = new ComposerManager($this->projectDir); 54 | } 55 | 56 | /** 57 | * {@inheritdoc} 58 | */ 59 | protected function execute(InputInterface $input, OutputInterface $output) 60 | { 61 | try { 62 | $this 63 | ->checkInstallerVersion() 64 | ->checkProjectName() 65 | ->checkSymfonyVersionIsInstallable() 66 | ->checkPermissions() 67 | ->download() 68 | ->extract() 69 | ->cleanUp() 70 | ->dumpReadmeFile() 71 | ->updateParameters() 72 | ->updateComposerConfig() 73 | ->createGitIgnore() 74 | ->checkSymfonyRequirements() 75 | ->displayInstallationResult() 76 | ; 77 | } catch (AbortException $e) { 78 | aborted: 79 | 80 | $output->writeln(''); 81 | $output->writeln('Aborting download and cleaning up temporary directories.'); 82 | 83 | $this->cleanUp(); 84 | 85 | return 1; 86 | } catch (\Exception $e) { 87 | // Guzzle can wrap the AbortException in a GuzzleException 88 | if ($e->getPrevious() instanceof AbortException) { 89 | goto aborted; 90 | } 91 | 92 | $this->cleanUp(); 93 | throw $e; 94 | } 95 | } 96 | 97 | /** 98 | * Checks whether the given Symfony version is installable by the installer. 99 | * Due to the changes introduced in the Icu/Intl components 100 | * (see https://symfony.com/blog/new-in-symfony-2-6-farewell-to-icu-component) 101 | * not all the previous Symfony versions are installable by the installer. 102 | * 103 | * The rules to decide if the version is installable are as follows: 104 | * 105 | * - 2.0, 2.1, 2.2 and 2.4 cannot be installed because they are unmaintained. 106 | * - 2.3 can be installed starting from version 2.3.21 (inclusive) 107 | * - 2.5 can be installed starting from version 2.5.6 (inclusive) 108 | * - 2.6, 2.7, 2.8 and 2.9 can be installed regardless the version. 109 | * 110 | * @return $this 111 | * 112 | * @throws \RuntimeException If the given Symfony version is not compatible with this installer 113 | */ 114 | protected function checkSymfonyVersionIsInstallable() 115 | { 116 | // validate the given version syntax 117 | if (!preg_match('/^latest|lts|[2-9]\.\d(?:\.\d{1,2})?(?:-(?:dev|BETA\d*|RC\d*))?$/i', $this->version)) { 118 | throw new \RuntimeException(sprintf( 119 | "The Symfony version can be a branch number (e.g. 2.8), a full version\n". 120 | "number (e.g. 3.1.4), a special word ('latest' or 'lts') and a unstable\n". 121 | "version number (e.g. 3.2.0-rc1) but '%s' was given.", $this->version 122 | )); 123 | } 124 | 125 | // Get the full list of Symfony versions to check if it's installable 126 | $client = $this->getGuzzleClient(); 127 | $symfonyVersions = $client->get('https://symfony.com/versions.json')->json(); 128 | if (empty($symfonyVersions)) { 129 | throw new \RuntimeException( 130 | "There was a problem while downloading the list of Symfony versions from\n". 131 | "symfony.com. Check that you are online and the following URL is accessible:\n\n". 132 | 'https://symfony.com/versions.json' 133 | ); 134 | } 135 | 136 | // if a branch number is used, transform it into a real version number 137 | if (preg_match('/^[2-9]\.\d$/', $this->version)) { 138 | if (!isset($symfonyVersions[$this->version])) { 139 | throw new \RuntimeException(sprintf( 140 | "The selected branch (%s) does not exist, or is not maintained.\n". 141 | "To solve this issue, install Symfony with the latest stable release:\n\n". 142 | '%s %s %s', $this->version, $_SERVER['PHP_SELF'], $this->getName(), $this->projectDir 143 | )); 144 | } 145 | 146 | $this->version = $symfonyVersions[$this->version]; 147 | } 148 | 149 | // if a special version name is used, transform it into a real version number 150 | if (in_array($this->version, array('latest', 'lts'))) { 151 | $this->version = $symfonyVersions[$this->version]; 152 | } 153 | 154 | // versions are case-sensitive in the download server (3.1.0-rc1 must be 3.1.0-RC1) 155 | if ($isUnstableVersion = preg_match('/^.*\-(BETA|RC)\d*$/i', $this->version)) { 156 | $this->version = strtoupper($this->version); 157 | } 158 | 159 | $isNonInstallable = in_array($this->version, $symfonyVersions['non_installable']); 160 | $isInstallable = in_array($this->version, $symfonyVersions['installable']); 161 | 162 | // installable and non-installable versions are explicitly declared in the 163 | // list of versions; there is an edge-case: unstable versions are not listed 164 | // and they are generally installable (e.g. 3.1.0-RC1) 165 | if ($isNonInstallable || (!$isInstallable && !$isUnstableVersion)) { 166 | throw new \RuntimeException(sprintf( 167 | "The selected version (%s) cannot be installed because it is not compatible\n". 168 | "with this installer or because it hasn't been published as a package yet.\n". 169 | "To solve this issue install Symfony manually executing the following command:\n\n". 170 | 'composer create-project symfony/framework-standard-edition %s %s', 171 | $this->version, $this->projectDir, $this->version 172 | )); 173 | } 174 | 175 | // check that the system has the PHP version required by the Symfony version to be installed 176 | if (version_compare($this->version, '3.0.0', '>=') && version_compare(PHP_VERSION, '5.5.9', '<')) { 177 | throw new \RuntimeException(sprintf( 178 | "The selected version (%s) cannot be installed because it requires\n". 179 | "PHP 5.5.9 or higher and your system has PHP %s installed.\n", 180 | $this->version, PHP_VERSION 181 | )); 182 | } 183 | 184 | // check that the Symfony version to be installed is not 4.x, which is incompatible with this installer 185 | if (version_compare($this->version, '4.0.0', '>=')) { 186 | throw new \RuntimeException(sprintf( 187 | "The Symfony Installer is not compatible with Symfony 4.x or newer versions.\n". 188 | "Run this other command to install Symfony using Composer instead:\n\n". 189 | 'composer create-project symfony/skeleton %s', 190 | $this->projectName 191 | )); 192 | } 193 | 194 | if ($isUnstableVersion) { 195 | $this->output->writeln("\n WARNING You are downloading an unstable Symfony version."); 196 | } 197 | 198 | return $this; 199 | } 200 | 201 | /** 202 | * Removes all the temporary files and directories created to 203 | * download the project and removes Symfony-related files that don't make 204 | * sense in a proprietary project. 205 | * 206 | * @return $this 207 | */ 208 | protected function cleanUp() 209 | { 210 | $this->fs->remove(dirname($this->downloadedFilePath)); 211 | 212 | try { 213 | $licenseFile = array($this->projectDir.'/LICENSE'); 214 | $upgradeFiles = glob($this->projectDir.'/UPGRADE*.md'); 215 | $changelogFiles = glob($this->projectDir.'/CHANGELOG*.md'); 216 | 217 | $filesToRemove = array_merge($licenseFile, $upgradeFiles, $changelogFiles); 218 | $this->fs->remove($filesToRemove); 219 | } catch (\Exception $e) { 220 | // don't throw an exception in case any of the Symfony-related files cannot 221 | // be removed, because this is just an enhancement, not something mandatory 222 | // for the project 223 | } 224 | 225 | return $this; 226 | } 227 | 228 | /** 229 | * It displays the message with the result of installing Symfony 230 | * and provides some pointers to the user. 231 | * 232 | * @return $this 233 | */ 234 | protected function displayInstallationResult() 235 | { 236 | if (empty($this->requirementsErrors)) { 237 | $this->output->writeln(sprintf( 238 | " %s Symfony %s was successfully installed. Now you can:\n", 239 | defined('PHP_WINDOWS_VERSION_BUILD') ? 'OK' : '✔', 240 | $this->getInstalledSymfonyVersion() 241 | )); 242 | } else { 243 | $this->output->writeln(sprintf( 244 | " %s Symfony %s was successfully installed but your system doesn't meet its\n". 245 | " technical requirements! Fix the following issues before executing\n". 246 | " your Symfony application:\n", 247 | defined('PHP_WINDOWS_VERSION_BUILD') ? 'FAILED' : '✕', 248 | $this->getInstalledSymfonyVersion() 249 | )); 250 | 251 | foreach ($this->requirementsErrors as $helpText) { 252 | $this->output->writeln(' * '.$helpText); 253 | } 254 | 255 | $checkFile = $this->isSymfony3() ? 'bin/symfony_requirements' : 'app/check.php'; 256 | 257 | $this->output->writeln(sprintf( 258 | " After fixing these issues, re-check Symfony requirements executing this command:\n\n". 259 | " php %s/%s\n\n". 260 | " Then, you can:\n", 261 | $this->projectName, $checkFile 262 | )); 263 | } 264 | 265 | if ('.' !== $this->projectDir) { 266 | $this->output->writeln(sprintf( 267 | " * Change your current directory to %s\n", $this->projectDir 268 | )); 269 | } 270 | 271 | $consoleDir = ($this->isSymfony3() ? 'bin' : 'app'); 272 | $serverRunCommand = version_compare($this->version, '2.6.0', '>=') && extension_loaded('pcntl') ? 'server:start' : 'server:run'; 273 | 274 | $this->output->writeln(sprintf( 275 | " * Configure your application in app/config/parameters.yml file.\n\n". 276 | " * Run your application:\n". 277 | " 1. Execute the php %s/console %s command.\n". 278 | " 2. Browse to the http://localhost:8000 URL.\n\n". 279 | " * Read the documentation at https://symfony.com/doc\n", 280 | $consoleDir, $serverRunCommand 281 | )); 282 | 283 | return $this; 284 | } 285 | 286 | /** 287 | * Dump a basic README.md file. 288 | * 289 | * @return $this 290 | */ 291 | protected function dumpReadmeFile() 292 | { 293 | $readmeContents = sprintf("%s\n%s\n\nA Symfony project created on %s.\n", $this->projectName, str_repeat('=', strlen($this->projectName)), date('F j, Y, g:i a')); 294 | try { 295 | $this->fs->dumpFile($this->projectDir.'/README.md', $readmeContents); 296 | } catch (\Exception $e) { 297 | // don't throw an exception in case the file could not be created, 298 | // because this is just an enhancement, not something mandatory 299 | // for the project 300 | } 301 | 302 | return $this; 303 | } 304 | 305 | /** 306 | * Updates the Symfony parameters.yml file to replace default configuration 307 | * values with better generated values. 308 | * 309 | * @return $this 310 | */ 311 | protected function updateParameters() 312 | { 313 | $filename = $this->projectDir.'/app/config/parameters.yml'; 314 | 315 | if (!is_writable($filename)) { 316 | if ($this->output->isVerbose()) { 317 | $this->output->writeln(sprintf( 318 | " [WARNING] The value of the secret configuration option cannot be updated because\n". 319 | " the %s file is not writable.\n", 320 | $filename 321 | )); 322 | } 323 | 324 | return $this; 325 | } 326 | 327 | $ret = str_replace('ThisTokenIsNotSoSecretChangeIt', $this->generateRandomSecret(), file_get_contents($filename)); 328 | file_put_contents($filename, $ret); 329 | 330 | return $this; 331 | } 332 | 333 | /** 334 | * Updates the composer.json file to provide better values for some of the 335 | * default configuration values. 336 | * 337 | * @return $this 338 | */ 339 | protected function updateComposerConfig() 340 | { 341 | parent::updateComposerConfig(); 342 | $this->composerManager->updateProjectConfig(array( 343 | 'name' => $this->composerManager->createPackageName($this->projectName), 344 | 'license' => 'proprietary', 345 | 'description' => null, 346 | 'extra' => array('branch-alias' => null), 347 | )); 348 | 349 | return $this; 350 | } 351 | 352 | /** 353 | * {@inheritdoc} 354 | */ 355 | protected function getDownloadedApplicationType() 356 | { 357 | return 'Symfony'; 358 | } 359 | 360 | /** 361 | * {@inheritdoc} 362 | */ 363 | protected function getRemoteFileUrl() 364 | { 365 | return 'https://symfony.com/download?v=Symfony_Standard_Vendors_'.$this->version; 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/Symfony/Installer/SelfUpdateCommand.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Installer; 13 | 14 | use Symfony\Component\Console\Input\InputInterface; 15 | use Symfony\Component\Console\Input\InputOption; 16 | use Symfony\Component\Console\Output\OutputInterface; 17 | use Symfony\Component\Filesystem\Exception\IOException; 18 | 19 | /** 20 | * This command is inspired by the self-update command included 21 | * in the PHP-CS-Fixer library. 22 | * 23 | * @link https://github.com/fabpot/PHP-CS-Fixer/blob/master/Symfony/CS/Console/Command/SelfUpdateCommand.php. 24 | * 25 | * @author Igor Wiedler 26 | * @author Stephane PY 27 | * @author Grégoire Pineau 28 | */ 29 | class SelfUpdateCommand extends DownloadCommand 30 | { 31 | /** 32 | * @var string The temp dir 33 | */ 34 | private $tempDir; 35 | 36 | /** 37 | * @var string The URL where the latest installer version can be downloaded 38 | */ 39 | private $remoteInstallerFile; 40 | 41 | /** 42 | * @var string The filepath of the installer currently installed in the local machine 43 | */ 44 | private $currentInstallerFile; 45 | 46 | /** 47 | * @var string The filepath of the new installer downloaded to replace the current installer 48 | */ 49 | private $newInstallerFile; 50 | 51 | /** 52 | * @var string The filepath of the backup of the current installer in case a rollback is performed 53 | */ 54 | private $currentInstallerBackupFile; 55 | 56 | /** 57 | * @var bool Flag which indicates that, in case of a rollback, it's safe to restore the installer backup because 58 | * it corresponds to the most recent version 59 | */ 60 | private $restorePreviousInstaller; 61 | 62 | /** 63 | * {@inheritdoc} 64 | */ 65 | protected function configure() 66 | { 67 | $this 68 | ->setName('self-update') 69 | ->setAliases(array('selfupdate')) 70 | ->addOption('force-update', 'f', InputOption::VALUE_NONE, 'It updates the installer to the latest available version without checking if it\'s older or newer than the locally installed version.') 71 | ->setDescription('Update the Symfony Installer to the latest version.') 72 | ->setHelp('The %command.name% command updates the installer to the latest available version.') 73 | ; 74 | } 75 | 76 | /** 77 | * The self-update command is only available when using the installer via the PHAR file. 78 | * 79 | * @return bool Whether the command is enabled 80 | */ 81 | public function isEnabled() 82 | { 83 | return 'phar://' === substr(__DIR__, 0, 7); 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | protected function initialize(InputInterface $input, OutputInterface $output) 90 | { 91 | parent::initialize($input, $output); 92 | $this->remoteInstallerFile = 'https://symfony.com/installer'; 93 | $this->currentInstallerFile = realpath($_SERVER['argv'][0]) ?: $_SERVER['argv'][0]; 94 | $this->tempDir = sys_get_temp_dir(); 95 | $this->currentInstallerBackupFile = basename($this->currentInstallerFile, '.phar').'-backup.phar'; 96 | $this->newInstallerFile = $this->tempDir.'/'.basename($this->currentInstallerFile, '.phar').'-temp.phar'; 97 | $this->restorePreviousInstaller = false; 98 | } 99 | 100 | /** 101 | * {@inheritdoc} 102 | */ 103 | protected function execute(InputInterface $input, OutputInterface $output) 104 | { 105 | $forceUpdate = true === $input->getOption('force-update'); 106 | if (!$forceUpdate && $this->isInstallerUpdated()) { 107 | $this->output->writeln(sprintf('// Symfony Installer is already updated to the latest version (%s).', $this->latestInstallerVersion)); 108 | 109 | return; 110 | } 111 | 112 | $this->output->writeln(sprintf('// updating Symfony Installer to %s version', $this->latestInstallerVersion)); 113 | 114 | try { 115 | $this 116 | ->downloadNewVersion() 117 | ->checkNewVersionIsValid() 118 | ->backupCurrentVersion() 119 | ->replaceCurrentVersionbyNewVersion() 120 | ->cleanUp() 121 | ; 122 | } catch (IOException $e) { 123 | if ($this->output->isVeryVerbose()) { 124 | $this->output->writeln($e->getMessage()); 125 | } 126 | 127 | throw new \RuntimeException(sprintf( 128 | "The installer couldn't be updated, probably because of a permissions issue.\n". 129 | "Try to execute the command again with super user privileges:\n". 130 | " sudo %s\n", 131 | $this->getExecutedCommand() 132 | )); 133 | } catch (\Exception $e) { 134 | $this->rollback(); 135 | 136 | if ($this->output->isVeryVerbose()) { 137 | $this->output->writeln($e->getMessage()); 138 | } 139 | 140 | return 1; 141 | } 142 | } 143 | 144 | /** 145 | * Downloads the new version of the Symfony installer. 146 | * 147 | * @return $this 148 | */ 149 | private function downloadNewVersion() 150 | { 151 | // check for permissions in local filesystem before start downloading files 152 | if (!is_writable($this->currentInstallerFile)) { 153 | throw new IOException('Symfony Installer update failed: the "'.$this->currentInstallerFile.'" file could not be written'); 154 | } 155 | 156 | if (!is_writable($this->tempDir)) { 157 | throw new IOException('Symfony Installer update failed: the "'.$this->tempDir.'" directory used to download files temporarily could not be written'); 158 | } 159 | 160 | if (false === $newInstaller = $this->getUrlContents($this->remoteInstallerFile)) { 161 | throw new \RuntimeException('The new version of the Symfony Installer couldn\'t be downloaded from the server.'); 162 | } 163 | 164 | $newInstallerPermissions = $this->currentInstallerFile ? fileperms($this->currentInstallerFile) : 0777 & ~umask(); 165 | $this->fs->dumpFile($this->newInstallerFile, $newInstaller, $newInstallerPermissions); 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Checks if the new version is valid. 172 | * 173 | * @return $this 174 | */ 175 | private function checkNewVersionIsValid() 176 | { 177 | // creating a Phar instance for an existing file is not allowed 178 | // when the Phar extension is in readonly mode 179 | if (!ini_get('phar.readonly')) { 180 | // test the phar validity 181 | $phar = new \Phar($this->newInstallerFile); 182 | 183 | // free the variable to unlock the file 184 | unset($phar); 185 | } 186 | 187 | return $this; 188 | } 189 | 190 | /** 191 | * Does a backup of the current version of the Symfony installer. 192 | * 193 | * @return $this 194 | */ 195 | private function backupCurrentVersion() 196 | { 197 | $this->fs->copy($this->currentInstallerFile, $this->currentInstallerBackupFile, true); 198 | $this->restorePreviousInstaller = true; 199 | 200 | return $this; 201 | } 202 | 203 | /** 204 | * Replaces the currenct version of the Symfony installer with the new one. 205 | * 206 | * @return $this 207 | */ 208 | private function replaceCurrentVersionbyNewVersion() 209 | { 210 | $this->fs->copy($this->newInstallerFile, $this->currentInstallerFile, true); 211 | 212 | return $this; 213 | } 214 | 215 | /** 216 | * Removes the temporary used files. 217 | */ 218 | private function cleanUp() 219 | { 220 | $this->fs->remove(array($this->currentInstallerBackupFile, $this->newInstallerFile)); 221 | } 222 | 223 | /** 224 | * Restores the previously installed version of the Symfony installer. 225 | */ 226 | private function rollback() 227 | { 228 | $this->output->writeln(array( 229 | '', 230 | 'There was an error while updating the installer.', 231 | 'The previous Symfony Installer version has been restored.', 232 | '', 233 | )); 234 | 235 | $this->fs->remove($this->newInstallerFile); 236 | 237 | if ($this->restorePreviousInstaller) { 238 | $this->fs->copy($this->currentInstallerBackupFile, $this->currentInstallerFile, true); 239 | } 240 | } 241 | 242 | /** 243 | * {@inheritdoc} 244 | */ 245 | protected function getDownloadedApplicationType() 246 | { 247 | return 'Symfony Installer'; 248 | } 249 | 250 | /** 251 | * {@inheritdoc} 252 | */ 253 | protected function getRemoteFileUrl() 254 | { 255 | return 'https://symfony.com/installer'; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /symfony: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | \n\n", 11 | PHP_VERSION 12 | )); 13 | 14 | exit(1); 15 | } 16 | 17 | if (extension_loaded('suhosin')) { 18 | file_put_contents('php://stderr', 19 | "Symfony Installer is not compatible with the 'suhosin' PHP extension.\n". 20 | "Disable that extension before running the installer.\n\n". 21 | "Alternatively, install Symfony manually executing the following command:\n\n". 22 | "composer create-project symfony/framework-standard-edition \n\n" 23 | ); 24 | 25 | exit(1); 26 | } 27 | 28 | require file_exists(__DIR__.'/vendor/autoload.php') 29 | ? __DIR__.'/vendor/autoload.php' 30 | : __DIR__.'/../../autoload.php'; 31 | 32 | $appVersion = '1.5.12-DEV'; 33 | 34 | // Windows uses Path instead of PATH 35 | if (!isset($_SERVER['PATH']) && isset($_SERVER['Path'])) { 36 | $_SERVER['PATH'] = $_SERVER['Path']; 37 | } 38 | 39 | $app = new Symfony\Installer\Application('Symfony Installer', $appVersion); 40 | $app->add(new Symfony\Installer\AboutCommand($appVersion)); 41 | $app->add(new Symfony\Installer\NewCommand()); 42 | $app->add(new Symfony\Installer\DemoCommand()); 43 | $app->add(new Symfony\Installer\SelfUpdateCommand()); 44 | 45 | $app->setDefaultCommand('about'); 46 | 47 | $app->run(); 48 | -------------------------------------------------------------------------------- /tests/Symfony/Installer/Tests/IntegrationTest.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Installer\Tests; 13 | 14 | use Symfony\Component\Console\Command\Command; 15 | use Symfony\Component\Process\Process; 16 | use Symfony\Component\Filesystem\Filesystem; 17 | use Symfony\Component\Process\Exception\ProcessFailedException; 18 | use Symfony\Component\Process\ProcessUtils; 19 | 20 | class IntegrationTest extends \PHPUnit_Framework_TestCase 21 | { 22 | /** 23 | * @var string The root directory 24 | */ 25 | private $rootDir; 26 | 27 | /** 28 | * @var Filesystem The Filesystem component 29 | */ 30 | private $fs; 31 | 32 | public function setUp() 33 | { 34 | $this->rootDir = realpath(__DIR__.'/../../../../'); 35 | $this->fs = new Filesystem(); 36 | 37 | if (!$this->fs->exists($this->rootDir.'/symfony.phar')) { 38 | throw new \RuntimeException(sprintf("Before running the tests, make sure that the Symfony Installer is available as a 'symfony.phar' file in the '%s' directory.", $this->rootDir)); 39 | } 40 | } 41 | 42 | public function testDemoApplicationInstallation() 43 | { 44 | if (PHP_VERSION_ID < 50500) { 45 | $this->markTestSkipped('Symfony 3 requires PHP 5.5.9 or higher.'); 46 | } 47 | 48 | $projectDir = sprintf('%s/my_test_project', sys_get_temp_dir()); 49 | $this->fs->remove($projectDir); 50 | 51 | $output = $this->runCommand(sprintf('php symfony.phar demo %s', ProcessUtils::escapeArgument($projectDir))); 52 | $this->assertContains('Downloading the Symfony Demo Application', $output); 53 | $this->assertContains('Symfony Demo Application was successfully installed.', $output); 54 | 55 | $composerConfig = json_decode(file_get_contents($projectDir.'/composer.json'), true); 56 | $this->assertArrayNotHasKey('platform', $composerConfig['config'], 'The composer.json file does not define any platform configuration.'); 57 | } 58 | 59 | /** 60 | * @dataProvider provideSymfonyInstallationData 61 | */ 62 | public function testSymfonyInstallation($versionToInstall, $messageRegexp, $versionRegexp, $requiredPhpVersion) 63 | { 64 | if (version_compare(PHP_VERSION, $requiredPhpVersion, '<')) { 65 | $this->markTestSkipped(sprintf('This test requires PHP %s or higher.', $requiredPhpVersion)); 66 | } 67 | 68 | $projectDir = sprintf('%s/my_test_project', sys_get_temp_dir()); 69 | $this->fs->remove($projectDir); 70 | 71 | $output = $this->runCommand(sprintf('php symfony.phar new %s %s', ProcessUtils::escapeArgument($projectDir), $versionToInstall)); 72 | $this->assertContains('Downloading Symfony...', $output); 73 | $this->assertRegExp($messageRegexp, $output); 74 | 75 | if (file_exists($projectDir.'/app/console')) { 76 | $output = $this->runCommand('php app/console --version', $projectDir); 77 | } else { 78 | $output = $this->runCommand('php bin/console --version', $projectDir); 79 | } 80 | 81 | $this->assertRegExp($versionRegexp, $output); 82 | 83 | $composerConfig = json_decode(file_get_contents($projectDir.'/composer.json'), true); 84 | $this->assertArrayNotHasKey( 85 | isset($composerConfig['config']) ? 'platform' : 'config', 86 | isset($composerConfig['config']) ? $composerConfig['config'] : $composerConfig, 87 | 'The composer.json file does not define any platform configuration.' 88 | ); 89 | } 90 | 91 | /** 92 | * @expectedException \RuntimeException 93 | * @expectedExceptionMessageRegExp /.+The selected version \(3.0.0\) cannot be installed because it requires.+PHP 5.5.9 or higher and your system has PHP 5.4.* installed.+/s 94 | */ 95 | public function testSymfonyRequiresNewerPhpVersion() 96 | { 97 | if (PHP_VERSION_ID >= 50500) { 98 | $this->markTestSkipped('This test requires PHP 5.4 or lower.'); 99 | } 100 | 101 | $this->runCommand(sprintf('php %s/symfony.phar new my_test_project 3.0.0', $this->rootDir)); 102 | } 103 | 104 | /** 105 | * @expectedException \RuntimeException 106 | * @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 107 | */ 108 | public function testUseComposerToInstallSymfony4() 109 | { 110 | if (PHP_VERSION_ID < 50500) { 111 | $this->markTestSkipped('This test requires PHP 5.5 or newer.'); 112 | } 113 | 114 | $this->runCommand(sprintf('php %s/symfony.phar new my_test_project', $this->rootDir)); 115 | } 116 | 117 | public function testSymfonyInstallationInCurrentDirectory() 118 | { 119 | $projectDir = sprintf('%s/my_test_project', sys_get_temp_dir()); 120 | $this->fs->remove($projectDir); 121 | $this->fs->mkdir($projectDir); 122 | 123 | $output = $this->runCommand(sprintf('php %s/symfony.phar new . 2.7.5', $this->rootDir), $projectDir); 124 | $this->assertContains('Downloading Symfony...', $output); 125 | 126 | $output = $this->runCommand('php app/console --version', $projectDir); 127 | $this->assertContains('Symfony version 2.7.5 - app/dev/debug', $output); 128 | } 129 | 130 | public function testSymfonyDemoInstallationWithNewCommand() 131 | { 132 | if (PHP_VERSION_ID < 50500) { 133 | $this->markTestSkipped('This test requires PHP 5.5 or higher.'); 134 | } 135 | 136 | $output = $this->runCommand(sprintf('php %s/symfony.phar new demo 3.4', $this->rootDir)); 137 | $this->assertContains("If you want to download the Symfony Demo app, execute 'symfony demo' instead of 'symfony new demo'", $output); 138 | $this->fs->remove('demo'); 139 | } 140 | 141 | /** 142 | * Runs the given string as a command and returns the resulting output. 143 | * The CWD is set to the root project directory to simplify command paths. 144 | * 145 | * @param string $command The name of the command to execute 146 | * @param null|string $workingDirectory The working directory 147 | * 148 | * @return string The output of the command 149 | * 150 | * @throws ProcessFailedException If the command execution is not successful 151 | */ 152 | private function runCommand($command, $workingDirectory = null) 153 | { 154 | $process = new Process($command); 155 | $process->setWorkingDirectory($workingDirectory ?: $this->rootDir); 156 | $process->mustRun(); 157 | 158 | return $process->getOutput(); 159 | } 160 | 161 | /** 162 | * Provides Symfony installation data. 163 | * 164 | * @return array 165 | */ 166 | public function provideSymfonyInstallationData() 167 | { 168 | return array( 169 | array( 170 | '3.0', 171 | '/.*Symfony 3\.0\.\d+ was successfully installed.*/', 172 | '/Symfony version 3\.0\.\d+(-DEV)? - app\/dev\/debug/', 173 | '5.5.9', 174 | ), 175 | 176 | array( 177 | 'lts', 178 | '/.*Symfony 3\.4\.\d+ was successfully installed.*/', 179 | '/Symfony 3\.4\.\d+ \(kernel: app, env: dev, debug: true\)/', 180 | '5.5.9', 181 | ), 182 | 183 | array( 184 | '2.3', 185 | '/.*Symfony 2\.3\.\d+ was successfully installed.*/', 186 | '/Symfony version 2\.3\.\d+ - app\/dev\/debug/', 187 | '5.3.9', 188 | ), 189 | 190 | array( 191 | '2.5.6', 192 | '/.*Symfony 2\.5\.6 was successfully installed.*/', 193 | '/Symfony version 2\.5\.6 - app\/dev\/debug/', 194 | '5.3.9', 195 | ), 196 | 197 | array( 198 | '2.7.0-BETA1', 199 | '/.*Symfony 2\.7\.0\-BETA1 was successfully installed.*/', 200 | '/Symfony version 2\.7\.0\-BETA1 - app\/dev\/debug/', 201 | '5.3.9', 202 | ), 203 | 204 | array( 205 | '3.0.0-BETA1', 206 | '/.*Symfony dev\-master was successfully installed.*/', 207 | '/Symfony version 3\.0\.0\-BETA1 - app\/dev\/debug/', 208 | '5.5.9', 209 | ), 210 | ); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /tests/Symfony/Installer/Tests/Manager/ComposerManagerTest.php: -------------------------------------------------------------------------------- 1 | setAccessible(true); 17 | 18 | $fixedName = $method->invoke($composerManager, $originalName); 19 | $this->assertSame($expectedName, $fixedName); 20 | } 21 | 22 | public function getProjectNames() 23 | { 24 | return [ 25 | ['foo/bar', 'foo/bar'], 26 | ['áèî/øū', 'aei/ou'], 27 | ['çñß/łŵž', 'cns/lwz'], 28 | ['foo#bar\foo?bar=foo!bar{foo]bar', 'foobarfoobarfoobarfoobar'], 29 | ['FOO/bar', 'foo/bar'], 30 | ]; 31 | } 32 | } 33 | --------------------------------------------------------------------------------