├── .gitignore ├── CHANGELOG.md ├── .travis.yml ├── tests ├── bootstrap.php ├── phpunit.xml └── src │ └── AssetsInstallerTest.php ├── composer.json ├── LICENSE ├── phpunit.xml.dist ├── src ├── AssetsInstallerPlugin.php ├── DirectoryHandler.php └── AssetsInstaller.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor/ 3 | composer.lock -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v2.0.0 4 | 5 | - Handle [Symfony 3](http://symfony.com) -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.6 6 | - 7.0 7 | - 7.1 8 | 9 | before_script: composer install 10 | -------------------------------------------------------------------------------- /tests/bootstrap.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 | $loader = require __DIR__.'/../vendor/autoload.php'; -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "reputation-vip/composer-assets-installer", 3 | "license": "MIT", 4 | "type": "composer-plugin", 5 | "authors": [ 6 | { 7 | "name": "Alban Pommeret", 8 | "email": "ap@reputationvip.com" 9 | } 10 | ], 11 | "require": { 12 | "composer-plugin-api": "~1.0", 13 | "symfony/filesystem": "^3.0" 14 | }, 15 | "require-dev": { 16 | "composer/composer": "^1.4", 17 | "phpunit/phpunit": "~4.4" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "ReputationVIP\\Composer\\": "src" 22 | } 23 | }, 24 | "extra": { 25 | "class": "ReputationVIP\\Composer\\AssetsInstallerPlugin" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | . 17 | 18 | 19 | 20 | 21 | 22 | ../src/AssetsInstaller 23 | 24 | vendor 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Reputation VIP 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | 16 | ./tests 17 | 18 | 19 | 20 | 21 | 22 | ../src/AssetsInstaller 23 | 24 | vendor 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/AssetsInstallerPlugin.php: -------------------------------------------------------------------------------- 1 | assetsInstaller = new AssetsInstaller($composer, $io); 32 | } 33 | 34 | public static function getSubscribedEvents() 35 | { 36 | return array( 37 | ScriptEvents::POST_UPDATE_CMD => array( 38 | array('onPostInstall', 0) 39 | ), 40 | ScriptEvents::POST_INSTALL_CMD => array( 41 | array('onPostInstall', 0) 42 | ) 43 | ); 44 | } 45 | 46 | public function onPostInstall(Event $event) 47 | { 48 | $this->assetsInstaller->install(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/DirectoryHandler.php: -------------------------------------------------------------------------------- 1 | copyDirectory($srcFile, $dstFile); 29 | } else { 30 | copy($srcFile, $dstFile); 31 | } 32 | } 33 | } 34 | closedir($dir); 35 | 36 | return $this; 37 | } 38 | 39 | /** 40 | * @param string $dirPath 41 | * @return $this 42 | */ 43 | public function deleteDirectory($dirPath) 44 | { 45 | if (!is_dir($dirPath)) { 46 | return false; 47 | } 48 | if (substr($dirPath, strlen($dirPath) - 1, 1) != DIRECTORY_SEPARATOR) { 49 | $dirPath .= DIRECTORY_SEPARATOR; 50 | } 51 | $files = glob($dirPath . '*', GLOB_MARK); 52 | foreach ($files as $file) { 53 | if (is_dir($file)) { 54 | $this->deleteDirectory($file); 55 | } else { 56 | unlink($file); 57 | } 58 | } 59 | rmdir($dirPath); 60 | return $this; 61 | } 62 | 63 | /** 64 | * @param string $path 65 | * @return bool 66 | */ 67 | public function isDirectory($path) 68 | { 69 | return is_dir($path); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Composer Assets Installer [![Build Status](https://travis-ci.org/ReputationVIP/composer-assets-installer.svg?branch=master)](https://travis-ci.org/ReputationVIP/composer-assets-installer) 2 | 3 | Composer Assets Installer provides a **fast and easy** way to **copy the assets of your Composer packages into your public folder**. You only have to chose one or multiple asset directories of the target in your "composer.json" file. 4 | 5 | ## Code Example 6 | 7 | Here is the composer.json file of a distant Composer package we want to use in our project : 8 | 9 | { 10 | "require": { 11 | "reputation-vip/composer-assets-installer": "~1.0" 12 | }, 13 | "name": "reputation-vip/required-distant-package", 14 | "extra": { 15 | "assets-dir" : "public" 16 | } 17 | } 18 | 19 | 20 | Here is the composer.json file of our project : 21 | 22 | { 23 | "require": { 24 | "reputation-vip/composer-assets-installer": "~1.0", 25 | "my/required-distant-package": "~1.0" 26 | }, 27 | "extra": { 28 | "assets-dir" : "web" 29 | } 30 | } 31 | 32 | Then, the assets will be accessible through the following path: **web/my/required-distant-package**. 33 | 34 | ## Motivation 35 | 36 | As members of the **Reputation VIP**'s development team, we are used to creating full Composer packages, embedding a javascript logic and a css layer. 37 | 38 | When we first started using Composer, we were somehow frustrated by it's lack of asset handling. Indeed, Composer forced us to manually copy the assets into our public directory. Furthermore, every time we updated the package, we had to repeat this task. 39 | 40 | That's why we needed a **tested, documented and easily configurable** Composer plugin which allowed us to **keep control on the asset directories**. 41 | 42 | ## Installation 43 | 44 | You simply have to add the following line to the requirements of your composer.json file: 45 | 46 | "require": { 47 | "reputation-vip/composer-assets-installer": "~1.0" 48 | } 49 | 50 | Then, you can specify the target for your asset directory (web for example): 51 | 52 | "extra": { 53 | "assets-dir": "web" 54 | } 55 | 56 | 57 | ## API Reference 58 | 59 | With this solution, you can specify as many targets as you want: 60 | 61 | "extra": { 62 | "assets-dir": { 63 | "js": "web/js", 64 | "css": "web/css" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/AssetsInstaller.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace ReputationVIP\Composer; 14 | 15 | use Composer\Composer; 16 | use Composer\Json\JsonFile; 17 | use Composer\IO\IOInterface; 18 | use Composer\Package; 19 | use Symfony\Component\Filesystem\Filesystem; 20 | 21 | class AssetsInstaller 22 | { 23 | const LABEL_ASSETS_DIR = "assets-dir"; 24 | const LABEL_PUBLIC_DIR = "public"; 25 | 26 | const LOG_WARNING = 1; 27 | const LOG_INFO = 2; 28 | const LOG_ERROR = 3; 29 | 30 | const CODE_INSTALLING_DIR = 1; 31 | const CODE_NO_MATCHING_DIR = 2; 32 | const CODE_MATCHING_DIR = 3; 33 | const CODE_NOT_DIRECTORY = 4; 34 | const CODE_DIRECTORY = 5; 35 | const CODE_DIR_INSTALLED = 6; 36 | const CODE_INSTALLING_PACKAGE = 7; 37 | const CODE_NO_INSTALL = 8; 38 | 39 | /** 40 | * @var Composer 41 | */ 42 | public $composer; 43 | /** 44 | * @var IOInterface 45 | */ 46 | private $io; 47 | /** 48 | * @var Package\RootPackageInterface 49 | */ 50 | private $package; 51 | /** 52 | * @var string|null 53 | */ 54 | private $vendorPath = null; 55 | /** 56 | * @var string 57 | */ 58 | public $assetsDirectories = array(self::LABEL_PUBLIC_DIR => "public/assets/"); 59 | /** 60 | * @var DirectoryHandler|null 61 | */ 62 | private $directoryHandler; 63 | 64 | public $packagesStatuses = array(); 65 | 66 | 67 | /** 68 | * Initializes the plugin 69 | * Reads the composer.json file and 70 | * retrieves the assets-dir set if any. 71 | * This assets-dir is the path where 72 | * the other packages assets will be installed 73 | * 74 | * @param Filesystem $fs 75 | * @param Composer $composer 76 | * @param IOInterface $io 77 | * @param null $directoryHandler 78 | */ 79 | public function __construct($composer, $io, $directoryHandler = null, Filesystem $fs = null) 80 | { 81 | $this->composer = $composer; 82 | $this->io = $io; 83 | $this->directoryHandler = (!is_null($directoryHandler)) ? $directoryHandler : new DirectoryHandler(); 84 | $this->fs = $fs ?: new Filesystem(); 85 | 86 | // We get the current package instance 87 | $this->package = $composer->getPackage(); 88 | // We read its extra section of composer.json 89 | $extra = $this->package->getExtra(); 90 | if (isset($extra[self::LABEL_ASSETS_DIR])) { 91 | $assetsDirs = $extra[self::LABEL_ASSETS_DIR]; 92 | if (is_string($assetsDirs)) { 93 | // If the assets dir is a string, it targets the whole public directory, we uniform it 94 | $assetsDirs = array(self::LABEL_PUBLIC_DIR => $assetsDirs); 95 | } 96 | // If an assets directory is specified, 97 | // This directory will be used to put 98 | // The other packages assets into 99 | $this->assetsDirectories = $assetsDirs; 100 | } 101 | } 102 | 103 | /** 104 | * Returns the vendor path of the current package 105 | * @return string 106 | */ 107 | public function getVendorPath() 108 | { 109 | if (is_null($this->vendorPath)) { 110 | // We get the current package install path 111 | $installPath = $this->composer->getInstallationManager()->getInstallPath($this->package); 112 | // We get the name of the package in order to get its target path 113 | $targetDir = $this->package->getName(); 114 | // We remove this target path to isolate the vendor path only 115 | $this->vendorPath = explode($targetDir, $installPath)[0]; 116 | } 117 | return $this->vendorPath; 118 | } 119 | 120 | /** 121 | * Installs all the assets directories of the dependency packages 122 | * @return $this 123 | */ 124 | public function install() 125 | { 126 | // We get all the packages required 127 | $packages = $this->composer->getPackage()->getRequires(); 128 | /** @var Package\Link $package */ 129 | foreach ($packages as $package) { 130 | $this->packagesStatuses[$package->getTarget()] = array('extra' => null, 'dirs' => null); 131 | $this->installPackage($package); 132 | } 133 | return $this; 134 | } 135 | 136 | /** 137 | * Installs all the assets directories of the given package 138 | * @param Package\Link $package 139 | */ 140 | public function installPackage($package) 141 | { 142 | // We read its composer file 143 | $jsonData = $this->getPackageJsonFile($package); 144 | // We get the package assets dirs 145 | $packagesAssetsDir = $this->getPackageAssetsDirs($jsonData); 146 | if (!is_null($packagesAssetsDir)) { 147 | $this->log($package->getTarget(), self::LOG_INFO, self::CODE_INSTALLING_PACKAGE); 148 | $this->packagesStatuses[$package->getTarget()]['extra'] = 1; 149 | foreach ($packagesAssetsDir as $namespace => $packageAssetsDir) { 150 | if (!isset($this->packagesStatuses[$package->getTarget()]['dirs'])) { 151 | $this->packagesStatuses[$package->getTarget()]['dirs'] = array(); 152 | } 153 | $this->packagesStatuses[$package->getTarget()]['dirs'][$namespace] = null; 154 | // We install each package directory 155 | $this->installPackageDir($package, $namespace, $packageAssetsDir); 156 | } 157 | } else { 158 | $this->log($package->getTarget(), self::LOG_INFO, self::CODE_NO_INSTALL); 159 | $this->packagesStatuses[$package->getTarget()]['extra'] = 0; 160 | } 161 | } 162 | 163 | /** 164 | * Returns the Json file of the given Package 165 | * @param Package\Link $package 166 | * @return mixed 167 | */ 168 | public function getPackageJsonFile($package) 169 | { 170 | // We get the project vendor path 171 | $vendorPath = $this->getVendorPath(); 172 | // We get the target dir of the iterated package 173 | $targetDir = $package->getTarget(); 174 | $jsonPath = $vendorPath . $targetDir . "/composer.json"; 175 | if (method_exists($package, 'getJsonFile')) { 176 | $jsonFile = $package->getJsonFile($jsonPath); 177 | } else { 178 | // @codeCoverageIgnoreStart 179 | $jsonFile = new JsonFile($jsonPath); 180 | // @codeCoverageIgnoreEnd 181 | } 182 | 183 | return $this->fs->exists($jsonPath) ? $jsonFile->read() : null; 184 | } 185 | 186 | /** 187 | * Returns all the assets directories contained in the given jsonData 188 | * @param array $jsonData 189 | * @return array|null 190 | */ 191 | public function getPackageAssetsDirs($jsonData) 192 | { 193 | $packagesAssetsDir = null; 194 | // If some assets were set on the package 195 | if (isset($jsonData["extra"][self::LABEL_ASSETS_DIR])) { 196 | // We get the assets-dir of this package 197 | $packagesAssetsDir = $jsonData["extra"][self::LABEL_ASSETS_DIR]; 198 | if (is_string($packagesAssetsDir)) { 199 | // If we get a string, it's the public shortcut so we uniform the return 200 | $packagesAssetsDir = array(self::LABEL_PUBLIC_DIR => $packagesAssetsDir); 201 | } 202 | } 203 | return $packagesAssetsDir; 204 | } 205 | 206 | /** 207 | * Installs a specific directory from the given package 208 | * @param Package\Link $package 209 | * @param string $namespace 210 | * @param string $packageAssetsDir 211 | */ 212 | public function installPackageDir($package, $namespace, $packageAssetsDir) 213 | { 214 | $this->log($package->getTarget(), self::LOG_INFO, self::CODE_INSTALLING_DIR, array('namespace' => $namespace)); 215 | if (!isset($this->assetsDirectories[$namespace])) { 216 | $this->log($package->getTarget(), self::LOG_WARNING, self::CODE_NO_MATCHING_DIR, array('namespace' => $namespace)); 217 | $this->packagesStatuses[$package->getTarget()]['dirs'][$namespace]['status'] = 0; 218 | return; 219 | } 220 | $this->log($package->getTarget(), self::LOG_INFO, self::CODE_MATCHING_DIR, array('namespace' => $namespace)); 221 | // We get the project vendor path 222 | $vendorPath = $this->getVendorPath(); 223 | // We get the target dir of the iterated package 224 | $targetDir = $package->getTarget(); 225 | // We get the full path of the current assets directory 226 | $packagePath = $vendorPath 227 | . $targetDir . "/" . $packageAssetsDir; 228 | // We check if this path is a directory 229 | if ($this->directoryHandler->isDirectory($packagePath)) { 230 | $this->log($package->getTarget(), self::LOG_INFO, self::CODE_DIRECTORY, array('directory' => $this->assetsDirectories[$namespace])); 231 | $src = $packagePath; 232 | $dst = $vendorPath . "../" . $this->assetsDirectories[$namespace] . $targetDir; 233 | // We delete the remaining directory if any 234 | // In order to update it 235 | $this->directoryHandler->deleteDirectory($dst); 236 | // We finally copy the package assets-dir 237 | // Into the project assets-dir 238 | $this->directoryHandler->copyDirectory($src, $dst); 239 | $this->log($package->getTarget(), self::LOG_INFO, self::CODE_DIR_INSTALLED, array('destination' => $dst)); 240 | $this->packagesStatuses[$package->getTarget()]['dirs'][$namespace]['status'] = 1; 241 | } else { 242 | $this->log($package->getTarget(), self::LOG_ERROR, self::CODE_NOT_DIRECTORY, array('directory' => $this->assetsDirectories[$namespace])); 243 | $this->packagesStatuses[$package->getTarget()]['dirs'][$namespace]['status'] = 0; 244 | } 245 | } 246 | 247 | private function log($packageName, $type, $code, $extras = array()) 248 | { 249 | $message = ''; 250 | switch ($code) { 251 | case self::CODE_INSTALLING_DIR: 252 | $message = $packageName . ' : Installing assets : "' . $extras['namespace'] . '"...'; 253 | break; 254 | case self::CODE_NO_MATCHING_DIR: 255 | $message = $packageName . ' : Assets directory not set in composer.json : "' . $extras['namespace'] . '"'; 256 | break; 257 | case self::CODE_MATCHING_DIR: 258 | $message = $packageName . ' : Assets directory matches : "' . $extras['namespace'] . '"'; 259 | break; 260 | case self::CODE_DIRECTORY: 261 | $message = $packageName . ' : Directory found : "' . $extras['directory'] . '"'; 262 | break; 263 | case self::CODE_NOT_DIRECTORY: 264 | $message = $packageName . ' : Directory not found : "' . $extras['directory'] . '"'; 265 | break; 266 | case self::CODE_DIR_INSTALLED: 267 | $message = $packageName . ' : Directory installed : "' . $extras['destination'] . '"'; 268 | break; 269 | case self::CODE_INSTALLING_PACKAGE: 270 | $message = $packageName . ' : Installation in progress...'; 271 | break; 272 | case self::CODE_NO_INSTALL: 273 | $message = $packageName . ' : No assets to install'; 274 | break; 275 | } 276 | switch ($type) { 277 | case self::LOG_INFO: 278 | $this->io->write(array(' ' . $message . '')); 279 | break; 280 | case self::LOG_WARNING: 281 | $this->io->write(array(' ' . $message . '')); 282 | break; 283 | case self::LOG_ERROR: 284 | $this->io->write(array(' ' . $message . '')); 285 | break; 286 | } 287 | } 288 | 289 | } 290 | -------------------------------------------------------------------------------- /tests/src/AssetsInstallerTest.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 | use ReputationVIP\Composer\AssetsInstaller; 13 | use Composer\Config; 14 | use Composer\IO\NullIO; 15 | use Composer\Package; 16 | 17 | class AssetsInstallerTest extends \PHPUnit_Framework_TestCase 18 | { 19 | const NS_DEFAULT = 'default'; 20 | 21 | private $mockLinks = array( 22 | self::NS_DEFAULT => array( 23 | array( 24 | 'target' => 'default/package', 25 | 'jsonFile' => array( 26 | 'extra' => array( 27 | 'assets-dir' => 'web' 28 | ) 29 | ) 30 | ) 31 | ), 32 | 'noAssetsDir' => array( 33 | array( 34 | 'target' => 'default/package', 35 | 'jsonFile' => array() 36 | ) 37 | ) 38 | ); 39 | 40 | private $mockDirectoryHandler = array( 41 | self::NS_DEFAULT => array( 42 | 'isDirectory' => true 43 | ), 44 | 'notDirectory' => array( 45 | 'isDirectory' => false 46 | ) 47 | ); 48 | 49 | private $mockPackage = array( 50 | self::NS_DEFAULT => array( 51 | 'extra' => array( 52 | 'assets-dir' => 'customdir' 53 | ), 54 | 'name' => 'default/package', 55 | 'installPath' => '/tmp/default/package', 56 | 'target' => 'default/package', 57 | 'jsonFile' => array( 58 | 'extra' => array( 59 | 'assets-dir' => 'customdir' 60 | ) 61 | ) 62 | ), 63 | 'noExtra' => array( 64 | 'extra' => null, 65 | 'name' => 'default/package', 66 | 'installPath' => '/tmp/default/package', 67 | 'target' => 'default/package', 68 | 'jsonFile' => array( 69 | 'extra' => null 70 | ) 71 | ), 72 | 'multipleDirs' => array( 73 | 'extra' => array( 74 | 'assets-dir' => array( 75 | 'js' => 'public/js', 76 | 'css' => 'css' 77 | ) 78 | ), 79 | 'name' => 'default/package', 80 | 'installPath' => '/tmp/default/package', 81 | 'target' => 'default/package', 82 | 'jsonFile' => array( 83 | 'extra' => array( 84 | 'assets-dir' => array( 85 | 'js' => 'public/js', 86 | 'css' => 'css' 87 | ) 88 | ) 89 | ) 90 | ) 91 | ); 92 | 93 | public function testShouldFormatAssetsDirToArrayWhenStringIsConfigured() 94 | { 95 | $assetsInstaller = $this->getAssetsInstaller(); 96 | $this->assertEquals(array('public' => 'customdir'), $assetsInstaller->assetsDirectories); 97 | } 98 | 99 | public function testShouldSetDefaultAssetsDirWhenNoneIsConfigured() 100 | { 101 | $assetsInstaller = $this->getAssetsInstaller('noExtra'); 102 | $this->assertEquals(array('public' => 'public/assets/'), $assetsInstaller->assetsDirectories); 103 | } 104 | 105 | public function testShouldSetMultipleAssetsDirWhenMultipleAreConfigured() 106 | { 107 | $assetsInstaller = $this->getAssetsInstaller('multipleDirs'); 108 | $this->assertEquals(array('js' => 'public/js', 'css' => 'css'), $assetsInstaller->assetsDirectories); 109 | } 110 | 111 | public function testShouldCorrectlyGetVendorPath() 112 | { 113 | $assetsInstaller = $this->getAssetsInstaller(); 114 | $this->assertEquals('/tmp/', $assetsInstaller->getVendorPath()); 115 | } 116 | 117 | public function testShouldReturnThePackageJsonFile() 118 | { 119 | $assetsInstaller = $this->getAssetsInstaller(); 120 | $packages = $this->getMockPackagesLinks(self::NS_DEFAULT); 121 | $package = $packages[0]; 122 | $expectedJsonFile = array( 123 | 'extra' => array( 124 | 'assets-dir' => 'web' 125 | ) 126 | ); 127 | $this->assertEquals($expectedJsonFile, $assetsInstaller->getPackageJsonFile($package)); 128 | } 129 | 130 | public function testShouldReturnThePublicPackageAssetsDirWhenDirIsString() 131 | { 132 | $assetsInstaller = $this->getAssetsInstaller(); 133 | $jsonData = array( 134 | 'extra' => array( 135 | 'assets-dir' => 'web' 136 | ) 137 | ); 138 | $expectedPackagesAssetsDir = array('public' => 'web'); 139 | $this->assertEquals($expectedPackagesAssetsDir, $assetsInstaller->getPackageAssetsDirs($jsonData)); 140 | } 141 | 142 | public function testShouldReturnSuccessStatusWhenPackageDirIsInstalled() 143 | { 144 | $assetsInstaller = $this->getAssetsInstaller(); 145 | $packages = $this->getMockPackagesLinks(self::NS_DEFAULT); 146 | $package = $packages[0]; 147 | $assetsInstaller->installPackageDir($package, 'public', 'web'); 148 | $this->assertEquals(1, $assetsInstaller->packagesStatuses['default/package']['dirs']['public']['status']); 149 | } 150 | 151 | public function testShouldReturnErrorStatusWhenAssetsDirIsUndefined() 152 | { 153 | $assetsInstaller = $this->getAssetsInstaller(); 154 | $packages = $this->getMockPackagesLinks(self::NS_DEFAULT); 155 | $package = $packages[0]; 156 | $assetsInstaller->installPackageDir($package, 'undefined', 'web'); 157 | $this->assertEquals(0, $assetsInstaller->packagesStatuses['default/package']['dirs']['undefined']['status']); 158 | } 159 | 160 | public function testShouldReturnErrorStatusWhenPackagePathIsNotADirectory() 161 | { 162 | $assetsInstaller = $this->getAssetsInstaller(self::NS_DEFAULT, self::NS_DEFAULT, 'notDirectory'); 163 | $packages = $this->getMockPackagesLinks(self::NS_DEFAULT); 164 | $package = $packages[0]; 165 | $assetsInstaller->installPackageDir($package, 'public', 'notADirectory'); 166 | $this->assertEquals(0, $assetsInstaller->packagesStatuses['default/package']['dirs']['public']['status']); 167 | } 168 | 169 | public function testShouldInitializeStatusesArrayWhenPackageIsInstalled() 170 | { 171 | $assetsInstaller = $this->getAssetsInstaller(); 172 | $packages = $this->getMockPackagesLinks(self::NS_DEFAULT); 173 | $package = $packages[0]; 174 | $assetsInstaller->installPackage($package); 175 | $this->assertArrayHasKey('public', $assetsInstaller->packagesStatuses['default/package']['dirs']); 176 | } 177 | 178 | public function testShouldReturnErrorStatusWhenPackageAssetsDirIsNotDefined() 179 | { 180 | $assetsInstaller = $this->getAssetsInstaller(); 181 | $packages = $this->getMockPackagesLinks('noAssetsDir'); 182 | $package = $packages[0]; 183 | $assetsInstaller->installPackage($package); 184 | $this->assertEquals(0, $assetsInstaller->packagesStatuses['default/package']['extra']); 185 | } 186 | 187 | public function testShouldReturnThePackageAssetsDirWhenDirIsArray() 188 | { 189 | $assetsInstaller = $this->getAssetsInstaller(); 190 | $jsonData = array( 191 | 'extra' => array( 192 | 'assets-dir' => array( 193 | 'js' => 'web/js', 194 | 'css' => 'web/css' 195 | ) 196 | ) 197 | ); 198 | $expectedPackagesAssetsDir = array( 199 | 'js' => 'web/js', 200 | 'css' => 'web/css' 201 | ); 202 | $this->assertEquals($expectedPackagesAssetsDir, $assetsInstaller->getPackageAssetsDirs($jsonData)); 203 | } 204 | 205 | public function testShouldReturnSuccessStatusWhenAllPackagesAreInstalled() 206 | { 207 | $assetsInstaller = $this->getAssetsInstaller(); 208 | $assetsInstaller->install(); 209 | $this->assertEquals(1, $assetsInstaller->packagesStatuses['default/package']['dirs']['public']['status']); 210 | } 211 | 212 | 213 | private function getAssetsInstaller($packageNs = self::NS_DEFAULT, $linksNs = self::NS_DEFAULT, $directoryHandlerNs = self::NS_DEFAULT) 214 | { 215 | $package = $this->getMockPackage($packageNs, $linksNs); 216 | $composer = $this->getComposer($package, $packageNs); 217 | $directoryHandler = $this->getDirectoryHandler($directoryHandlerNs); 218 | $io = $this->getIO(); 219 | 220 | $fsStub = $this 221 | ->getMockBuilder('Symfony\Component\Filesystem\Filesystem') 222 | ->getMock(); 223 | $fsStub 224 | ->method('exists') 225 | ->willReturn(true); 226 | 227 | return new AssetsInstaller($composer, $io, $directoryHandler, $fsStub); 228 | } 229 | 230 | private function getMockPackage($packageNs = self::NS_DEFAULT, $linksNs = self::NS_DEFAULT) 231 | { 232 | $mockPackageData = $this->mockPackage[$packageNs]; 233 | 234 | $jsonFileReader = $this->getMock('JsonFile', array('read')); 235 | $jsonFileReader->expects($this->any()) 236 | ->method('read') 237 | ->will($this->returnValue($mockPackageData['jsonFile'])); 238 | 239 | $package = $this->getMock('Package', array('getExtra', 'getName', 'getRequires', 'getTarget')); 240 | $package->expects($this->any()) 241 | ->method('getExtra') 242 | ->will($this->returnValue($mockPackageData['extra'])); 243 | $package->expects($this->any()) 244 | ->method('getName') 245 | ->will($this->returnValue($mockPackageData['name'])); 246 | $package->expects($this->any()) 247 | ->method('getTarget') 248 | ->will($this->returnValue($mockPackageData['target'])); 249 | $package->expects($this->any()) 250 | ->method('getRequires') 251 | ->will($this->returnValue($this->getMockPackagesLinks($linksNs))); 252 | $package->expects($this->any()) 253 | ->method('getJsonFile') 254 | ->will($this->returnValue($jsonFileReader)); 255 | return $package; 256 | } 257 | 258 | private function getComposer($package, $packageNs = self::NS_DEFAULT) 259 | { 260 | $mockPackageData = $this->mockPackage[$packageNs]; 261 | $installationManager = $this->getMock('InstallationManager', array('getInstallPath')); 262 | $installationManager->expects($this->any()) 263 | ->method('getInstallPath') 264 | //->with($package) 265 | ->will($this->returnValue($mockPackageData['installPath'])); 266 | 267 | $composer = $this->getMock('Composer', array('getPackage', 'getInstallationManager')); 268 | $composer->expects($this->any()) 269 | ->method('getPackage') 270 | ->will($this->returnValue($package)); 271 | $composer->expects($this->any()) 272 | ->method('getInstallationManager') 273 | ->will($this->returnValue($installationManager)); 274 | return $composer; 275 | } 276 | 277 | private function getDirectoryHandler($directoryHandlerNs = self::NS_DEFAULT) 278 | { 279 | $directoryHandlerData = $this->mockDirectoryHandler[$directoryHandlerNs]; 280 | $directoryHandler = $this->getMock('DirectoryHandler', array('isDirectory', 'copyDirectory', 'deleteDirectory')); 281 | $directoryHandler->expects($this->any()) 282 | ->method('isDirectory') 283 | ->will($this->returnValue($directoryHandlerData['isDirectory'])); 284 | return $directoryHandler; 285 | } 286 | 287 | private function getIO() 288 | { 289 | return new NullIO(); 290 | } 291 | 292 | private function getMockPackagesLinks($mockLinksNs) 293 | { 294 | $mockLinksData = $this->mockLinks[$mockLinksNs]; 295 | $mockLinks = array(); 296 | foreach ($mockLinksData as $mockLinkData) { 297 | $mockLinks[] = $this->getMockPackageLink($mockLinkData); 298 | } 299 | return $mockLinks; 300 | } 301 | 302 | private function getMockPackageLink($mockLinkData) 303 | { 304 | $jsonFileReader = $this->getMock('JsonFile', array('read')); 305 | $jsonFileReader->expects($this->any()) 306 | ->method('read') 307 | ->will($this->returnValue($mockLinkData['jsonFile'])); 308 | 309 | $link = $this->getMock('Link', array('getTarget', 'getJsonFile')); 310 | $link->expects($this->any()) 311 | ->method('getTarget') 312 | ->will($this->returnValue($mockLinkData['target'])); 313 | $link->expects($this->any()) 314 | ->method('getJsonFile') 315 | ->will($this->returnValue($jsonFileReader)); 316 | 317 | return $link; 318 | } 319 | } 320 | --------------------------------------------------------------------------------