diff --git a/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php b/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php index 9eda5f84e16b0e45d8621ce26d75c79f139f6192..51345c2b7609cb8e39cf25fb9a4f4f202cba6f65 100644 --- a/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php +++ b/app/code/Magento/Deploy/Console/Command/DeployStaticContentCommand.php @@ -15,12 +15,10 @@ use Symfony\Component\Console\Input\InputArgument; use Magento\Framework\App\ObjectManagerFactory; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Validator\Locale; -use Magento\Framework\Console\Cli; -use Magento\Deploy\Model\ProcessManager; -use Magento\Deploy\Model\Process; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\App\State; use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options; +use Magento\Deploy\Model\DeployManager; /** * Deploy static content command @@ -121,109 +119,116 @@ class DeployStaticContentCommand extends Command Options::FORCE_RUN, '-f', InputOption::VALUE_NONE, - 'If specified, then run files will be deployed in any mode.' + 'Deploy files in any mode.' ), new InputOption( Options::NO_JAVASCRIPT, null, InputOption::VALUE_NONE, - 'If specified, no JavaScript will be deployed.' + 'Do not deploy JavaScript files' ), new InputOption( Options::NO_CSS, null, InputOption::VALUE_NONE, - 'If specified, no CSS will be deployed.' + 'Do not deploy CSS files.' ), new InputOption( Options::NO_LESS, null, InputOption::VALUE_NONE, - 'If specified, no LESS will be deployed.' + 'Do not deploy LESS files.' ), new InputOption( Options::NO_IMAGES, null, InputOption::VALUE_NONE, - 'If specified, no images will be deployed.' + 'Do not deploy images.' ), new InputOption( Options::NO_FONTS, null, InputOption::VALUE_NONE, - 'If specified, no font files will be deployed.' + 'Do not deploy font files.' ), new InputOption( Options::NO_HTML, null, InputOption::VALUE_NONE, - 'If specified, no html files will be deployed.' + 'Do not deploy HTML files.' ), new InputOption( Options::NO_MISC, null, InputOption::VALUE_NONE, - 'If specified, no miscellaneous files will be deployed.' + 'Do not deploy other types of files (.md, .jbf, .csv, etc...).' ), new InputOption( Options::NO_HTML_MINIFY, null, InputOption::VALUE_NONE, - 'If specified, html will not be minified.' + 'Do not minify HTML files.' ), new InputOption( Options::THEME, '-t', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, - 'If specified, just specific theme(s) will be actually deployed.', + 'Generate static view files for only the specified themes.', ['all'] ), new InputOption( Options::EXCLUDE_THEME, null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, - 'If specified, exclude specific theme(s) from deployment.', + 'Do not generate files for the specified themes.', ['none'] ), new InputOption( Options::LANGUAGE, '-l', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, - 'List of languages you want the tool populate files for.', + 'Generate files only for the specified languages.', ['all'] ), new InputOption( Options::EXCLUDE_LANGUAGE, null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, - 'List of langiages you do not want the tool populate files for.', + 'Do not generate files for the specified languages.', ['none'] ), new InputOption( Options::AREA, '-a', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, - 'List of areas you want the tool populate files for.', + 'Generate files only for the specified areas.', ['all'] ), new InputOption( Options::EXCLUDE_AREA, null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, - 'List of areas you do not want the tool populate files for.', + 'Do not generate files for the specified areas.', ['none'] ), new InputOption( Options::JOBS_AMOUNT, '-j', InputOption::VALUE_OPTIONAL, - 'Amount of jobs to which script can be paralleled.', + 'Enable parallel processing using the specified number of jobs.', self::DEFAULT_JOBS_AMOUNT ), + new InputOption( + Options::SYMLINK_LOCALE, + null, + InputOption::VALUE_NONE, + 'Create symlinks for the files of those locales, which are passed for deployment, ' + . 'but have no customizations' + ), new InputArgument( self::LANGUAGES_ARGUMENT, InputArgument::IS_ARRAY, - 'List of languages you want the tool populate files for.' + 'Space-separated list of ISO-636 language codes for which to output static view files.' ), ]); @@ -374,8 +379,11 @@ class DeployStaticContentCommand extends Command if (!$input->getOption(Options::FORCE_RUN) && $this->getAppState()->getMode() !== State::MODE_PRODUCTION) { throw new LocalizedException( __( - "Deploy static content is applicable only for production mode.\n" - . "Please use command 'bin/magento deploy:mode:set production' for set up production mode." + 'NOTE: Manual static content deployment is not required in "default" and "developer" modes.' + . PHP_EOL . 'In "default" and "developer" modes static contents are being deployed ' + . 'automatically on demand.' + . PHP_EOL . 'If you still want to deploy in these modes, use -f option: ' + . "'bin/magento setup:static-content:deploy -f'" ) ); } @@ -390,20 +398,24 @@ class DeployStaticContentCommand extends Command $output->writeln("Requested areas: " . implode(', ', array_keys($deployableAreaThemeMap))); $output->writeln("Requested themes: " . implode(', ', $requestedThemes)); - $deployer = $this->objectManager->create( - \Magento\Deploy\Model\Deployer::class, + /** @var $deployManager DeployManager */ + $deployManager = $this->objectManager->create( + DeployManager::class, [ - 'filesUtil' => $filesUtil, 'output' => $output, 'options' => $this->input->getOptions(), ] ); - if ($this->isCanBeParalleled()) { - return $this->runProcessesInParallel($deployer, $deployableAreaThemeMap, $deployableLanguages); - } else { - return $this->deploy($deployer, $deployableLanguages, $deployableAreaThemeMap); + foreach ($deployableAreaThemeMap as $area => $themes) { + foreach ($deployableLanguages as $locale) { + foreach ($themes as $themePath) { + $deployManager->addPack($area, $themePath, $locale); + } + } } + + return $deployManager->deploy(); } /** @@ -464,89 +476,4 @@ class DeployStaticContentCommand extends Command return [$deployableLanguages, $deployableAreaThemeMap, $requestedThemes]; } - - /** - * @param \Magento\Deploy\Model\Deployer $deployer - * @param array $deployableLanguages - * @param array $deployableAreaThemeMap - * @return int - */ - private function deploy($deployer, $deployableLanguages, $deployableAreaThemeMap) - { - return $deployer->deploy( - $this->objectManagerFactory, - $deployableLanguages, - $deployableAreaThemeMap - ); - } - - /** - * @param \Magento\Deploy\Model\Deployer $deployer - * @param array $deployableAreaThemeMap - * @param array $deployableLanguages - * @return int - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - private function runProcessesInParallel($deployer, $deployableAreaThemeMap, $deployableLanguages) - { - /** @var ProcessManager $processManager */ - $processManager = $this->objectManager->create(ProcessManager::class); - $processNumber = 0; - $processQueue = []; - foreach ($deployableAreaThemeMap as $area => &$themes) { - foreach ($themes as $theme) { - foreach ($deployableLanguages as $lang) { - $deployerFunc = function (Process $process) use ($area, $theme, $lang, $deployer) { - return $this->deploy($deployer, [$lang], [$area => [$theme]]); - }; - if ($processNumber >= $this->getProcessesAmount()) { - $processQueue[] = $deployerFunc; - } else { - $processManager->fork($deployerFunc); - } - $processNumber++; - } - } - } - $returnStatus = null; - while (count($processManager->getProcesses()) > 0) { - foreach ($processManager->getProcesses() as $process) { - if ($process->isCompleted()) { - $processManager->delete($process); - $returnStatus |= $process->getStatus(); - if ($queuedProcess = array_shift($processQueue)) { - $processManager->fork($queuedProcess); - } - if (count($processManager->getProcesses()) >= $this->getProcessesAmount()) { - break 1; - } - } - } - usleep(5000); - } - - return $returnStatus === Cli::RETURN_SUCCESS ?: Cli::RETURN_FAILURE; - } - - /** - * @return bool - */ - private function isCanBeParalleled() - { - return function_exists('pcntl_fork') && $this->getProcessesAmount() > 1; - } - - /** - * @return int - */ - private function getProcessesAmount() - { - $jobs = (int)$this->input->getOption(Options::JOBS_AMOUNT); - if ($jobs < 1) { - throw new \InvalidArgumentException( - Options::JOBS_AMOUNT . ' argument has invalid value. It must be greater than 0' - ); - } - return $jobs; - } } diff --git a/app/code/Magento/Deploy/Console/Command/DeployStaticOptionsInterface.php b/app/code/Magento/Deploy/Console/Command/DeployStaticOptionsInterface.php index 729ccbe8096286fa75adce196a1858371d64c732..25457f5505fb974240e4924c9c7713555ec8e97d 100644 --- a/app/code/Magento/Deploy/Console/Command/DeployStaticOptionsInterface.php +++ b/app/code/Magento/Deploy/Console/Command/DeployStaticOptionsInterface.php @@ -92,4 +92,9 @@ interface DeployStaticOptionsInterface * Force run of static deploy */ const FORCE_RUN = 'force'; + + /** + * Symlink locale if it not customized + */ + const SYMLINK_LOCALE = 'symlink-locale'; } diff --git a/app/code/Magento/Deploy/Model/Deploy/DeployInterface.php b/app/code/Magento/Deploy/Model/Deploy/DeployInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..16168c2b284c5bb7cd34d878d4448dae21d164d6 --- /dev/null +++ b/app/code/Magento/Deploy/Model/Deploy/DeployInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Deploy\Model\Deploy; + +interface DeployInterface +{ + /** + * Base locale option without customizations + */ + const DEPLOY_BASE_LOCALE = 'deploy_base_locale'; + + /** + * @param string $area + * @param string $themePath + * @param string $locale + * @return int + */ + public function deploy($area, $themePath, $locale); +} diff --git a/app/code/Magento/Deploy/Model/Deploy/LocaleDeploy.php b/app/code/Magento/Deploy/Model/Deploy/LocaleDeploy.php new file mode 100644 index 0000000000000000000000000000000000000000..aa112a700133185cf06980fb6b465de509630371 --- /dev/null +++ b/app/code/Magento/Deploy/Model/Deploy/LocaleDeploy.php @@ -0,0 +1,453 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Deploy\Model\Deploy; + +use Magento\Framework\App\Utility\Files; +use Magento\Framework\App\View\Asset\Publisher; +use Magento\Framework\View\Asset\ContentProcessorException; +use Magento\Framework\View\Asset\PreProcessor\AlternativeSourceInterface; +use Magento\Framework\View\Design\Theme\ThemeProviderInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\Config\Theme; +use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options; +use Magento\Framework\Translate\Js\Config as JsTranslationConfig; +use Magento\Framework\View\Asset\Minification; +use Psr\Log\LoggerInterface; +use Magento\Framework\Console\Cli; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) + */ +class LocaleDeploy implements DeployInterface +{ + /** + * @var int + */ + private $count = 0; + + /** + * @var int + */ + private $errorCount = 0; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var \Magento\Framework\View\Asset\Repository + */ + private $assetRepo; + + /** + * @var Publisher + */ + private $assetPublisher; + + /** + * @var \Magento\Framework\View\Asset\Bundle\Manager + */ + private $bundleManager; + + /** + * @var Files + */ + private $filesUtil; + + /** + * @var ThemeProviderInterface + */ + private $themeProvider; + + /** + * @var array + */ + private $options = []; + + /** + * @var JsTranslationConfig + */ + private $jsTranslationConfig; + + /** + * @var Minification + */ + private $minification; + + /** + * @var LoggerInterface + */ + private $logger; + + /** + * @var \Magento\Framework\View\Asset\RepositoryFactory + */ + private $assetRepoFactory; + + /** + * @var \Magento\RequireJs\Model\FileManagerFactory + */ + private $fileManagerFactory; + + /** + * @var \Magento\Framework\RequireJs\ConfigFactory + */ + private $requireJsConfigFactory; + + /** + * @var \Magento\Framework\View\DesignInterfaceFactory + */ + private $designFactory; + + /** + * @var \Magento\Framework\Locale\ResolverInterface + */ + private $localeResolver; + + /** + * @var \Magento\Framework\View\Asset\PreProcessor\AlternativeSourceInterface[] + */ + private $alternativeSources; + + /** + * @var array + */ + private static $fileExtensionOptionMap = [ + 'js' => Options::NO_JAVASCRIPT, + 'map' => Options::NO_JAVASCRIPT, + 'css' => Options::NO_CSS, + 'less' => Options::NO_LESS, + 'html' => Options::NO_HTML, + 'htm' => Options::NO_HTML, + 'jpg' => Options::NO_IMAGES, + 'jpeg' => Options::NO_IMAGES, + 'gif' => Options::NO_IMAGES, + 'png' => Options::NO_IMAGES, + 'ico' => Options::NO_IMAGES, + 'svg' => Options::NO_IMAGES, + 'eot' => Options::NO_FONTS, + 'ttf' => Options::NO_FONTS, + 'woff' => Options::NO_FONTS, + 'woff2' => Options::NO_FONTS, + 'md' => Options::NO_MISC, + 'jbf' => Options::NO_MISC, + 'csv' => Options::NO_MISC, + 'json' => Options::NO_MISC, + 'txt' => Options::NO_MISC, + 'htc' => Options::NO_MISC, + 'swf' => Options::NO_MISC, + 'LICENSE' => Options::NO_MISC, + '' => Options::NO_MISC, + ]; + + /** + * @param OutputInterface $output + * @param JsTranslationConfig $jsTranslationConfig + * @param Minification $minification + * @param \Magento\Framework\View\Asset\Repository $assetRepo + * @param \Magento\Framework\View\Asset\RepositoryFactory $assetRepoFactory + * @param \Magento\RequireJs\Model\FileManagerFactory $fileManagerFactory + * @param \Magento\Framework\RequireJs\ConfigFactory $requireJsConfigFactory + * @param Publisher $assetPublisher + * @param \Magento\Framework\View\Asset\Bundle\Manager $bundleManager + * @param ThemeProviderInterface $themeProvider + * @param LoggerInterface $logger + * @param Files $filesUtil + * @param \Magento\Framework\View\DesignInterfaceFactory $designFactory + * @param \Magento\Framework\Locale\ResolverInterface $localeResolver + * @param array $alternativeSources + * @param array $options + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + OutputInterface $output, + JsTranslationConfig $jsTranslationConfig, + Minification $minification, + \Magento\Framework\View\Asset\Repository $assetRepo, + \Magento\Framework\View\Asset\RepositoryFactory $assetRepoFactory, + \Magento\RequireJs\Model\FileManagerFactory $fileManagerFactory, + \Magento\Framework\RequireJs\ConfigFactory $requireJsConfigFactory, + \Magento\Framework\App\View\Asset\Publisher $assetPublisher, + \Magento\Framework\View\Asset\Bundle\Manager $bundleManager, + \Magento\Framework\View\Design\Theme\ThemeProviderInterface $themeProvider, + LoggerInterface $logger, + Files $filesUtil, + \Magento\Framework\View\DesignInterfaceFactory $designFactory, + \Magento\Framework\Locale\ResolverInterface $localeResolver, + array $alternativeSources, + $options = [] + ) { + $this->output = $output; + $this->assetRepo = $assetRepo; + $this->assetPublisher = $assetPublisher; + $this->bundleManager = $bundleManager; + $this->filesUtil = $filesUtil; + $this->jsTranslationConfig = $jsTranslationConfig; + $this->minification = $minification; + $this->logger = $logger; + $this->assetRepoFactory = $assetRepoFactory; + $this->fileManagerFactory = $fileManagerFactory; + $this->requireJsConfigFactory = $requireJsConfigFactory; + $this->themeProvider = $themeProvider; + $this->alternativeSources = array_map( + function (AlternativeSourceInterface $alternativeSource) { + return $alternativeSource; + }, + $alternativeSources + ); + $this->designFactory = $designFactory; + $this->localeResolver = $localeResolver; + $this->options = $options; + } + + /** + * {@inheritdoc} + */ + public function deploy($area, $themePath, $locale) + { + $this->output->writeln("=== {$area} -> {$themePath} -> {$locale} ==="); + + // emulate application locale needed for correct file path resolving + $this->localeResolver->setLocale($locale); + + $this->deployRequireJsConfig($area, $themePath); + $this->deployAppFiles($area, $themePath, $locale); + $this->deployLibFiles($area, $themePath, $locale); + + if (!$this->getOption(Options::NO_JAVASCRIPT)) { + if ($this->jsTranslationConfig->dictionaryEnabled()) { + $dictionaryFileName = $this->jsTranslationConfig->getDictionaryFileName(); + $this->deployFile($dictionaryFileName, $area, $themePath, $locale, null); + } + } + if (!$this->getOption(Options::NO_JAVASCRIPT)) { + $this->bundleManager->flush(); + } + $this->output->writeln("\nSuccessful: {$this->count} files; errors: {$this->errorCount}\n---\n"); + + return $this->errorCount ? Cli::RETURN_FAILURE : Cli::RETURN_SUCCESS; + } + + /** + * @param string $area + * @param string $themePath + * @return void + */ + private function deployRequireJsConfig($area, $themePath) + { + if (!$this->getOption(Options::DRY_RUN) && !$this->getOption(Options::NO_JAVASCRIPT)) { + $design = $this->designFactory->create()->setDesignTheme($themePath, $area); + $assetRepo = $this->assetRepoFactory->create(['design' => $design]); + /** @var \Magento\RequireJs\Model\FileManager $fileManager */ + $fileManager = $this->fileManagerFactory->create( + [ + 'config' => $this->requireJsConfigFactory->create( + [ + 'assetRepo' => $assetRepo, + 'design' => $design, + ] + ), + 'assetRepo' => $assetRepo, + ] + ); + $fileManager->createRequireJsConfigAsset(); + if ($this->minification->isEnabled('js')) { + $fileManager->createMinResolverAsset(); + } + } + } + + /** + * @param string $area + * @param string $themePath + * @param string $locale + * @return void + */ + private function deployAppFiles($area, $themePath, $locale) + { + foreach ($this->filesUtil->getStaticPreProcessingFiles() as $info) { + list($fileArea, $fileTheme, , $module, $filePath, $fullPath) = $info; + + if ($this->checkSkip($filePath)) { + continue; + } + + if ($this->isCanBeDeployed($fileArea, $fileTheme, $area, $themePath)) { + $compiledFile = $this->deployFile( + $filePath, + $area, + $themePath, + $locale, + $module, + $fullPath + ); + if ($compiledFile !== '' && !$this->checkSkip($compiledFile)) { + $this->deployFile($compiledFile, $area, $themePath, $locale, $module, $fullPath); + } + } + } + } + + /** + * @param string $fileArea + * @param string $fileTheme + * @param string $area + * @param string $themePath + * @return bool + */ + private function isCanBeDeployed($fileArea, $fileTheme, $area, $themePath) + { + return ($fileArea == $area || $fileArea == 'base') + && ($fileTheme == '' || $fileTheme == $themePath + || in_array( + $fileArea . Theme::THEME_PATH_SEPARATOR . $fileTheme, + $this->findAncestors($area . Theme::THEME_PATH_SEPARATOR . $themePath) + ) + ); + } + + /** + * @param string $area + * @param string $themePath + * @param string $locale + * @return void + */ + private function deployLibFiles($area, $themePath, $locale) + { + foreach ($this->filesUtil->getStaticLibraryFiles() as $filePath) { + + if ($this->checkSkip($filePath)) { + continue; + } + + $compiledFile = $this->deployFile($filePath, $area, $themePath, $locale, null); + + if ($compiledFile !== '' && !$this->checkSkip($compiledFile)) { + $this->deployFile($compiledFile, $area, $themePath, $locale, null); + } + } + } + + /** + * Deploy a static view file + * + * @param string $filePath + * @param string $area + * @param string $themePath + * @param string $locale + * @param string $module + * @param string|null $fullPath + * @return string + * + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function deployFile($filePath, $area, $themePath, $locale, $module, $fullPath = null) + { + $compiledFile = ''; + $extension = pathinfo($filePath, PATHINFO_EXTENSION); + + foreach ($this->alternativeSources as $name => $alternative) { + if (in_array($extension, $alternative->getAlternativesExtensionsNames(), true) + && strpos(basename($filePath), '_') !== 0 + ) { + $compiledFile = substr($filePath, 0, strlen($filePath) - strlen($extension) - 1); + $compiledFile = $compiledFile . '.' . $name; + } + } + + if ($this->output->isVeryVerbose()) { + $logMessage = "Processing file '$filePath' for area '$area', theme '$themePath', locale '$locale'"; + if ($module) { + $logMessage .= ", module '$module'"; + } + $this->output->writeln($logMessage); + } + + try { + $asset = $this->assetRepo->createAsset( + $filePath, + ['area' => $area, 'theme' => $themePath, 'locale' => $locale, 'module' => $module] + ); + if ($this->output->isVeryVerbose()) { + $this->output->writeln("\tDeploying the file to '{$asset->getPath()}'"); + } else { + $this->output->write('.'); + } + if ($this->getOption(Options::DRY_RUN)) { + $asset->getContent(); + } else { + $this->assetPublisher->publish($asset); + if (!$this->getOption(Options::NO_JAVASCRIPT)) { + $this->bundleManager->addAsset($asset); + } + } + $this->count++; + } catch (ContentProcessorException $exception) { + $pathInfo = $fullPath ?: $filePath; + $errorMessage = __('Compilation from source: ') . $pathInfo . PHP_EOL . $exception->getMessage(); + $this->errorCount++; + $this->output->write(PHP_EOL . PHP_EOL . $errorMessage . PHP_EOL, true); + + $this->logger->critical($errorMessage); + } catch (\Exception $exception) { + $this->output->write('.'); + if ($this->output->isVerbose()) { + $this->output->writeln($exception->getTraceAsString()); + } + $this->errorCount++; + } + + return $compiledFile; + } + + /** + * @param string $name + * @return mixed|null + */ + private function getOption($name) + { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * Check if skip flag is affecting file by extension + * + * @param string $filePath + * @return boolean + */ + private function checkSkip($filePath) + { + if ($filePath != '.') { + $ext = strtolower(pathinfo($filePath, PATHINFO_EXTENSION)); + $option = isset(self::$fileExtensionOptionMap[$ext]) ? self::$fileExtensionOptionMap[$ext] : null; + + return $option ? $this->getOption($option) : false; + } + + return false; + } + + /** + * Find ancestor themes' full paths + * + * @param string $themeFullPath + * @return string[] + */ + private function findAncestors($themeFullPath) + { + $theme = $this->themeProvider->getThemeByFullPath($themeFullPath); + $ancestors = $theme->getInheritedThemes(); + $ancestorThemeFullPath = []; + foreach ($ancestors as $ancestor) { + $ancestorThemeFullPath[] = $ancestor->getFullPath(); + } + return $ancestorThemeFullPath; + } +} diff --git a/app/code/Magento/Deploy/Model/Deploy/LocaleQuickDeploy.php b/app/code/Magento/Deploy/Model/Deploy/LocaleQuickDeploy.php new file mode 100644 index 0000000000000000000000000000000000000000..8102afb363024b583068543ed319ade7e1e85965 --- /dev/null +++ b/app/code/Magento/Deploy/Model/Deploy/LocaleQuickDeploy.php @@ -0,0 +1,152 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Deploy\Model\Deploy; + +use Magento\Deploy\Model\DeployManager; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\Utility\Files; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\Console\Cli; +use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options; +use \Magento\Framework\RequireJs\Config as RequireJsConfig; + +class LocaleQuickDeploy implements DeployInterface +{ + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var WriteInterface + */ + private $staticDirectory; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var array + */ + private $options = []; + + /** + * @param Filesystem $filesystem + * @param OutputInterface $output + * @param array $options + */ + public function __construct(\Magento\Framework\Filesystem $filesystem, OutputInterface $output, $options = []) + { + $this->filesystem = $filesystem; + $this->output = $output; + $this->options = $options; + } + + /** + * @return WriteInterface + */ + private function getStaticDirectory() + { + if ($this->staticDirectory === null) { + $this->staticDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::STATIC_VIEW); + } + + return $this->staticDirectory; + } + + /** + * {@inheritdoc} + */ + public function deploy($area, $themePath, $locale) + { + if (isset($this->options[Options::DRY_RUN]) && $this->options[Options::DRY_RUN]) { + return Cli::RETURN_SUCCESS; + } + + $this->output->writeln("=== {$area} -> {$themePath} -> {$locale} ==="); + + if (!isset($this->options[self::DEPLOY_BASE_LOCALE])) { + throw new \InvalidArgumentException('Deploy base locale must be set for Quick Deploy'); + } + $processedFiles = 0; + $errorAmount = 0; + + $baseLocale = $this->options[self::DEPLOY_BASE_LOCALE]; + $newLocalePath = $this->getLocalePath($area, $themePath, $locale); + $baseLocalePath = $this->getLocalePath($area, $themePath, $baseLocale); + $baseRequireJsPath = RequireJsConfig::DIR_NAME . DIRECTORY_SEPARATOR . $baseLocalePath; + $newRequireJsPath = RequireJsConfig::DIR_NAME . DIRECTORY_SEPARATOR . $newLocalePath; + + $this->deleteLocaleResource($newLocalePath); + $this->deleteLocaleResource($newRequireJsPath); + + if (isset($this->options[Options::SYMLINK_LOCALE]) && $this->options[Options::SYMLINK_LOCALE]) { + $this->getStaticDirectory()->createSymlink($baseLocalePath, $newLocalePath); + $this->getStaticDirectory()->createSymlink($baseRequireJsPath, $newRequireJsPath); + + $this->output->writeln("\nSuccessful symlinked\n---\n"); + } else { + $localeFiles = array_merge( + $this->getStaticDirectory()->readRecursively($baseLocalePath), + $this->getStaticDirectory()->readRecursively($baseRequireJsPath) + ); + foreach ($localeFiles as $path) { + if ($this->getStaticDirectory()->isFile($path)) { + $destination = $this->replaceLocaleInPath($path, $baseLocale, $locale); + $this->getStaticDirectory()->copyFile($path, $destination); + $processedFiles++; + } + } + + $this->output->writeln("\nSuccessful copied: {$processedFiles} files; errors: {$errorAmount}\n---\n"); + } + + return Cli::RETURN_SUCCESS; + } + + /** + * @param string $path + * @return void + */ + private function deleteLocaleResource($path) + { + if ($this->getStaticDirectory()->isExist($path)) { + $absolutePath = $this->getStaticDirectory()->getAbsolutePath($path); + if (is_link($absolutePath)) { + $this->getStaticDirectory()->getDriver()->deleteFile($absolutePath); + } else { + $this->getStaticDirectory()->getDriver()->deleteDirectory($absolutePath); + } + } + } + + /** + * @param string $path + * @param string $search + * @param string $replace + * @return string + */ + private function replaceLocaleInPath($path, $search, $replace) + { + return preg_replace('~' . $search . '~', $replace, $path, 1); + } + + /** + * @param string $area + * @param string $themePath + * @param string $locale + * @return string + */ + private function getLocalePath($area, $themePath, $locale) + { + return $area . DIRECTORY_SEPARATOR . $themePath . DIRECTORY_SEPARATOR . $locale; + } +} diff --git a/app/code/Magento/Deploy/Model/Deploy/TemplateMinifier.php b/app/code/Magento/Deploy/Model/Deploy/TemplateMinifier.php new file mode 100644 index 0000000000000000000000000000000000000000..7a5d8f92f3c32d3b9f45f5addb5c4f76894cb931 --- /dev/null +++ b/app/code/Magento/Deploy/Model/Deploy/TemplateMinifier.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Deploy\Model\Deploy; + +use Magento\Framework\View\Template\Html\MinifierInterface; +use Magento\Framework\App\Utility\Files; + +class TemplateMinifier +{ + /** + * @var Files + */ + private $filesUtils; + + /** + * @var MinifierInterface + */ + private $htmlMinifier; + + /** + * @param Files $filesUtils + * @param MinifierInterface $htmlMinifier + */ + public function __construct( + Files $filesUtils, + MinifierInterface $htmlMinifier + ) { + $this->filesUtils = $filesUtils; + $this->htmlMinifier = $htmlMinifier; + } + + /** + * Minify template files + * @return int + */ + public function minifyTemplates() + { + $minified = 0; + foreach ($this->filesUtils->getPhtmlFiles(false, false) as $template) { + $this->htmlMinifier->minify($template); + $minified++; + } + return $minified; + } +} diff --git a/app/code/Magento/Deploy/Model/DeployManager.php b/app/code/Magento/Deploy/Model/DeployManager.php new file mode 100644 index 0000000000000000000000000000000000000000..c54aa38f5a33ab21283537c90c717f3844d08a0e --- /dev/null +++ b/app/code/Magento/Deploy/Model/DeployManager.php @@ -0,0 +1,213 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Deploy\Model; + +use Magento\Framework\App\View\Deployment\Version\StorageInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options; +use Magento\Deploy\Model\Deploy\TemplateMinifier; +use Magento\Framework\App\State; + +class DeployManager +{ + /** + * @var array + */ + private $packages = []; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var array + */ + private $options; + + /** + * @var StorageInterface + */ + private $versionStorage; + + /** + * @var DeployStrategyProviderFactory + */ + private $deployStrategyProviderFactory; + + /** + * @var ProcessQueueManagerFactory + */ + private $processQueueManagerFactory; + + /** + * @var TemplateMinifier + */ + private $templateMinifier; + + /** + * @var bool + */ + private $idDryRun; + + /** + * @var State + */ + private $state; + + /** + * @param OutputInterface $output + * @param StorageInterface $versionStorage + * @param DeployStrategyProviderFactory $deployStrategyProviderFactory + * @param ProcessQueueManagerFactory $processQueueManagerFactory + * @param TemplateMinifier $templateMinifier + * @param State $state + * @param array $options + */ + public function __construct( + OutputInterface $output, + StorageInterface $versionStorage, + DeployStrategyProviderFactory $deployStrategyProviderFactory, + ProcessQueueManagerFactory $processQueueManagerFactory, + TemplateMinifier $templateMinifier, + State $state, + array $options + ) { + $this->output = $output; + $this->options = $options; + $this->versionStorage = $versionStorage; + $this->deployStrategyProviderFactory = $deployStrategyProviderFactory; + $this->processQueueManagerFactory = $processQueueManagerFactory; + $this->templateMinifier = $templateMinifier; + $this->state = $state; + $this->idDryRun = !empty($this->options[Options::DRY_RUN]); + } + + /** + * Add package tie to area and theme + * + * @param string $area + * @param string $themePath + * @param string $locale + * @return void + */ + public function addPack($area, $themePath, $locale) + { + $this->packages[$area . '-' . $themePath][$locale] = [$area, $themePath]; + } + + /** + * Deploy local packages with chosen deploy strategy + * @return int + */ + public function deploy() + { + if ($this->idDryRun) { + $this->output->writeln('Dry run. Nothing will be recorded to the target directory.'); + } + + /** @var DeployStrategyProvider $strategyProvider */ + $strategyProvider = $this->deployStrategyProviderFactory->create( + ['output' => $this->output, 'options' => $this->options] + ); + + if ($this->isCanBeParalleled()) { + $result = $this->runInParallel($strategyProvider); + } else { + $result = 0; + foreach ($this->packages as $package) { + $locales = array_keys($package); + list($area, $themePath) = current($package); + foreach ($strategyProvider->getDeployStrategies($area, $themePath, $locales) as $locale => $strategy) { + $result |= $this->state->emulateAreaCode( + $area, + [$strategy, 'deploy'], + [$area, $themePath, $locale] + ); + } + } + } + + $this->minifyTemplates(); + $this->saveDeployedVersion(); + + return $result; + } + + /** + * @return void + */ + private function minifyTemplates() + { + $noHtmlMinify = isset($this->options[Options::NO_HTML_MINIFY]) ? $this->options[Options::NO_HTML_MINIFY] : null; + if (!$noHtmlMinify && !$this->idDryRun) { + $this->output->writeln('=== Minify templates ==='); + $minified = $this->templateMinifier->minifyTemplates(); + $this->output->writeln("\nSuccessful: {$minified} files modified\n---\n"); + } + } + + /** + * @param DeployStrategyProvider $strategyProvider + * @return int + */ + private function runInParallel(DeployStrategyProvider $strategyProvider) + { + $processQueueManager = $this->processQueueManagerFactory->create( + ['maxProcesses' => $this->getProcessesAmount()] + ); + foreach ($this->packages as $package) { + $locales = array_keys($package); + list($area, $themePath) = current($package); + $baseStrategy = null; + $dependentStrategy = []; + foreach ($strategyProvider->getDeployStrategies($area, $themePath, $locales) as $locale => $strategy) { + $deploymentFunc = function () use ($area, $themePath, $locale, $strategy) { + return $this->state->emulateAreaCode($area, [$strategy, 'deploy'], [$area, $themePath, $locale]); + }; + if (null === $baseStrategy) { + $baseStrategy = $deploymentFunc; + } else { + $dependentStrategy[] = $deploymentFunc; + } + + } + $processQueueManager->addTaskToQueue($baseStrategy, $dependentStrategy); + } + + return $processQueueManager->process(); + } + + /** + * @return bool + */ + private function isCanBeParalleled() + { + return function_exists('pcntl_fork') && $this->getProcessesAmount() > 1; + } + + /** + * @return int + */ + private function getProcessesAmount() + { + return isset($this->options[Options::JOBS_AMOUNT]) ? (int)$this->options[Options::JOBS_AMOUNT] : 0; + } + + /** + * Save version of deployed files + * @return void + */ + private function saveDeployedVersion() + { + if (!$this->idDryRun) { + $version = (new \DateTime())->getTimestamp(); + $this->output->writeln("New version of deployed files: {$version}"); + $this->versionStorage->save($version); + } + } +} diff --git a/app/code/Magento/Deploy/Model/DeployStrategyFactory.php b/app/code/Magento/Deploy/Model/DeployStrategyFactory.php new file mode 100644 index 0000000000000000000000000000000000000000..884ff5b6284d4dae3a3983e0a08ccfa8b68cf306 --- /dev/null +++ b/app/code/Magento/Deploy/Model/DeployStrategyFactory.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Deploy\Model; + +use Magento\Deploy\Model\Deploy\DeployInterface; +use Magento\Framework\Exception\InputException; +use Magento\Framework\ObjectManagerInterface; + +class DeployStrategyFactory +{ + /** + * Standard deploy strategy + */ + const DEPLOY_STRATEGY_STANDARD = 'standard'; + + /** + * Quick deploy strategy + */ + const DEPLOY_STRATEGY_QUICK = 'quick'; + + /** + * @param ObjectManagerInterface $objectManager + */ + public function __construct(ObjectManagerInterface $objectManager) + { + $this->objectManager = $objectManager; + } + + /** + * @param string $type + * @param array $arguments + * @return DeployInterface + * @throws InputException + */ + public function create($type, array $arguments = []) + { + $strategyMap = [ + self::DEPLOY_STRATEGY_STANDARD => Deploy\LocaleDeploy::class, + self::DEPLOY_STRATEGY_QUICK => Deploy\LocaleQuickDeploy::class, + ]; + + if (!isset($strategyMap[$type])) { + throw new InputException(__('Wrong deploy strategy type: %1', $type)); + } + + return $this->objectManager->create($strategyMap[$type], $arguments); + } +} diff --git a/app/code/Magento/Deploy/Model/DeployStrategyProvider.php b/app/code/Magento/Deploy/Model/DeployStrategyProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..9893aa1768f55d16f5ca0119788c54287cc6a16f --- /dev/null +++ b/app/code/Magento/Deploy/Model/DeployStrategyProvider.php @@ -0,0 +1,193 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Deploy\Model; + +use Magento\Deploy\Model\Deploy\DeployInterface; +use Magento\Framework\Component\ComponentRegistrar; +use Magento\Framework\Module\Dir; +use Magento\Framework\View\Design\Fallback\Rule\RuleInterface; +use Magento\Framework\View\DesignInterface; +use Magento\Framework\View\Design\Fallback\RulePool; +use Symfony\Component\Console\Output\OutputInterface; + +class DeployStrategyProvider +{ + /** + * @var RulePool + */ + private $rulePool; + + /** + * @var RuleInterface + */ + private $fallBackRule; + + /** + * @var array + */ + private $moduleDirectories; + + /** + * @var DesignInterface + */ + private $design; + + /** + * @var OutputInterface + */ + private $output; + + /** + * @var array + */ + private $options; + + /** + * @var DeployStrategyFactory + */ + private $deployStrategyFactory; + + /** + * @param OutputInterface $output + * @param RulePool $rulePool + * @param DesignInterface $design + * @param DeployStrategyFactory $deployStrategyFactory + * @param array $options + */ + public function __construct( + OutputInterface $output, + RulePool $rulePool, + DesignInterface $design, + DeployStrategyFactory $deployStrategyFactory, + array $options + ) { + $this->rulePool = $rulePool; + $this->design = $design; + $this->output = $output; + $this->options = $options; + $this->deployStrategyFactory = $deployStrategyFactory; + } + + /** + * @param string $area + * @param string $themePath + * @param array $locales + * @return DeployInterface[] + */ + public function getDeployStrategies($area, $themePath, array $locales) + { + if (count($locales) == 1) { + $locale = current($locales); + return [$locale => $this->getDeployStrategy(DeployStrategyFactory::DEPLOY_STRATEGY_STANDARD)]; + } + + $baseLocale = null; + $deployStrategies = []; + + foreach ($locales as $locale) { + $hasCustomization = false; + foreach ($this->getCustomizationDirectories($area, $themePath, $locale) as $directory) { + if (glob($directory . DIRECTORY_SEPARATOR . '*', GLOB_NOSORT)) { + $hasCustomization = true; + break; + } + } + if ($baseLocale === null && !$hasCustomization) { + $baseLocale = $locale; + } else { + $deployStrategies[$locale] = $hasCustomization + ? DeployStrategyFactory::DEPLOY_STRATEGY_STANDARD + : DeployStrategyFactory::DEPLOY_STRATEGY_QUICK; + } + } + $deployStrategies = array_merge( + [$baseLocale => DeployStrategyFactory::DEPLOY_STRATEGY_STANDARD], + $deployStrategies + ); + + return array_map(function ($strategyType) use ($area, $baseLocale) { + return $this->getDeployStrategy($strategyType, $baseLocale); + }, $deployStrategies); + } + + /** + * @param array $params + * @return array + */ + private function getLocaleDirectories($params) + { + $dirs = $this->getFallbackRule()->getPatternDirs($params); + + return array_filter($dirs, function ($dir) { + return strpos($dir, Dir::MODULE_I18N_DIR); + }); + } + + /** + * Get directories which can contains theme customization + * @param string $area + * @param string $themePath + * @param string $locale + * @return array + */ + private function getCustomizationDirectories($area, $themePath, $locale) + { + $customizationDirectories = []; + $this->design->setDesignTheme($themePath, $area); + + $params = ['area' => $area, 'theme' => $this->design->getDesignTheme(), 'locale' => $locale]; + foreach ($this->getLocaleDirectories($params) as $patternDir) { + $customizationDirectories[] = $patternDir; + } + + if ($this->moduleDirectories === null) { + $this->moduleDirectories = []; + $componentRegistrar = new ComponentRegistrar(); + $this->moduleDirectories = array_keys($componentRegistrar->getPaths(ComponentRegistrar::MODULE)); + } + + foreach ($this->moduleDirectories as $moduleDir) { + $params['module_name'] = $moduleDir; + $patternDirs = $this->getLocaleDirectories($params); + foreach ($patternDirs as $patternDir) { + $customizationDirectories[] = $patternDir; + } + } + + return $customizationDirectories; + } + + /** + * @return \Magento\Framework\View\Design\Fallback\Rule\RuleInterface + */ + private function getFallbackRule() + { + if (null === $this->fallBackRule) { + $this->fallBackRule = $this->rulePool->getRule(RulePool::TYPE_STATIC_FILE); + } + + return $this->fallBackRule; + } + + /** + * @param string $type + * @param null|string $baseLocale + * @return DeployInterface + */ + private function getDeployStrategy($type, $baseLocale = null) + { + $options = $this->options; + if ($baseLocale) { + $options[DeployInterface::DEPLOY_BASE_LOCALE] = $baseLocale; + } + + return $this->deployStrategyFactory->create( + $type, + ['output' => $this->output, 'options' => $options] + ); + } +} diff --git a/app/code/Magento/Deploy/Model/Deployer.php b/app/code/Magento/Deploy/Model/Deployer.php index bef65532fd4238edf75c84ff01f6846b878faf04..9c1781dcb08ad14b40753478e3997c68b3a0b206 100644 --- a/app/code/Magento/Deploy/Model/Deployer.php +++ b/app/code/Magento/Deploy/Model/Deployer.php @@ -6,125 +6,41 @@ namespace Magento\Deploy\Model; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\View\Asset\ContentProcessorException; +use Magento\Deploy\Console\Command\DeployStaticOptionsInterface; use Magento\Framework\View\Asset\PreProcessor\AlternativeSourceInterface; use Magento\Framework\App\ObjectManagerFactory; use Magento\Framework\App\View\Deployment\Version; -use Magento\Framework\App\View\Asset\Publisher; use Magento\Framework\App\Utility\Files; -use Magento\Framework\Config\Theme; -use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Translate\Js\Config as JsTranslationConfig; use Symfony\Component\Console\Output\OutputInterface; -use Psr\Log\LoggerInterface; -use Magento\Framework\View\Asset\Minification; use Magento\Framework\App\ObjectManager; -use Magento\Framework\View\Asset\ConfigInterface; -use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options; +use Magento\Deploy\Model\DeployManagerFactory; /** * A service for deploying Magento static view files for production mode * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - * @SuppressWarnings(PHPMD.UnusedLocalVariable) - * @SuppressWarnings(PHPMD.TooManyFields) + * @deprecated + * @see Use DeployManager::deploy instead */ class Deployer { - /** @var Files */ - private $filesUtil; - - /** @var ObjectManagerFactory */ - private $omFactory; - /** @var OutputInterface */ private $output; - /** @var Version\StorageInterface */ - private $versionStorage; - - /** @var \Magento\Framework\View\Asset\Repository */ - private $assetRepo; - - /** @var Publisher */ - private $assetPublisher; - - /** @var \Magento\Framework\View\Asset\Bundle\Manager */ - private $bundleManager; - - /** @var int */ - private $count; - - /** @var int */ - private $errorCount; - - /** @var \Magento\Framework\View\Template\Html\MinifierInterface */ - private $htmlMinifier; - - /** - * @var ObjectManagerInterface - */ - private $objectManager; - /** * @var JsTranslationConfig */ protected $jsTranslationConfig; - /** - * @var AlternativeSourceInterface[] - */ - private $alternativeSources; - /** * @var array */ - private static $fileExtensionOptionMap = [ - 'js' => Options::NO_JAVASCRIPT, - 'map' => Options::NO_JAVASCRIPT, - 'css' => Options::NO_CSS, - 'less' => Options::NO_LESS, - 'html' => Options::NO_HTML, - 'htm' => Options::NO_HTML, - 'jpg' => Options::NO_IMAGES, - 'jpeg' => Options::NO_IMAGES, - 'gif' => Options::NO_IMAGES, - 'png' => Options::NO_IMAGES, - 'ico' => Options::NO_IMAGES, - 'svg' => Options::NO_IMAGES, - 'eot' => Options::NO_FONTS, - 'ttf' => Options::NO_FONTS, - 'woff' => Options::NO_FONTS, - 'woff2' => Options::NO_FONTS, - 'md' => Options::NO_MISC, - 'jbf' => Options::NO_MISC, - 'csv' => Options::NO_MISC, - 'json' => Options::NO_MISC, - 'txt' => Options::NO_MISC, - 'htc' => Options::NO_MISC, - 'swf' => Options::NO_MISC, - 'LICENSE' => Options::NO_MISC, - '' => Options::NO_MISC, - ]; - - /** - * @var Minification - */ - private $minification; - - /** - * @var LoggerInterface - */ - private $logger; - - /** @var ConfigInterface */ - private $assetConfig; + private $options; /** - * @var array + * @var DeployManagerFactory */ - private $options; + private $deployManagerFactory; /** * Constructor @@ -134,7 +50,9 @@ class Deployer * @param Version\StorageInterface $versionStorage * @param JsTranslationConfig $jsTranslationConfig * @param AlternativeSourceInterface[] $alternativeSources + * @param DeployManagerFactory $deployManagerFactory * @param array $options + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( Files $filesUtil, @@ -142,56 +60,29 @@ class Deployer Version\StorageInterface $versionStorage, JsTranslationConfig $jsTranslationConfig, array $alternativeSources, + DeployManagerFactory $deployManagerFactory = null, $options = [] ) { - $this->filesUtil = $filesUtil; $this->output = $output; - $this->versionStorage = $versionStorage; - $this->jsTranslationConfig = $jsTranslationConfig; + $this->deployManagerFactory = $deployManagerFactory; if (is_array($options)) { $this->options = $options; } else { // backward compatibility support - $this->options = [Options::DRY_RUN => (bool)$options]; + $this->options = [DeployStaticOptionsInterface::DRY_RUN => (bool)$options]; } - $this->parentTheme = []; - - array_map( - function (AlternativeSourceInterface $alternative) { - }, - $alternativeSources - ); - $this->alternativeSources = $alternativeSources; - } /** - * @param string $name - * @return mixed|null + * @return \Magento\Deploy\Model\DeployManagerFactory */ - private function getOption($name) + private function getDeployManagerFactory() { - return isset($this->options[$name]) ? $this->options[$name] : null; - } - - /** - * Check if skip flag is affecting file by extension - * - * @param string $filePath - * @return boolean - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - private function checkSkip($filePath) - { - if ($filePath != '.') { - $ext = pathinfo($filePath, PATHINFO_EXTENSION); - $option = isset(self::$fileExtensionOptionMap[$ext]) ? self::$fileExtensionOptionMap[$ext] : null; - - return $option ? $this->getOption($option) : false; + if (null === $this->deployManagerFactory) { + $this->deployManagerFactory = ObjectManager::getInstance()->get(DeployManagerFactory::class); } - return false; + return $this->deployManagerFactory; } /** @@ -201,170 +92,24 @@ class Deployer * @param array $locales * @param array $deployableAreaThemeMap * @return int - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @deprecated */ public function deploy(ObjectManagerFactory $omFactory, array $locales, array $deployableAreaThemeMap = []) { - $this->omFactory = $omFactory; - - if ($this->getOption(Options::DRY_RUN)) { - $this->output->writeln('Dry run. Nothing will be recorded to the target directory.'); - } - $libFiles = $this->filesUtil->getStaticLibraryFiles(); - $appFiles = $this->filesUtil->getStaticPreProcessingFiles(); + /** @var DeployManager $deployerManager */ + $deployerManager = $this->getDeployManagerFactory()->create( + ['options' => $this->options, 'output' => $this->output] + ); foreach ($deployableAreaThemeMap as $area => $themes) { - $this->emulateApplicationArea($area); foreach ($locales as $locale) { - $this->emulateApplicationLocale($locale, $area); foreach ($themes as $themePath) { - - $this->output->writeln("=== {$area} -> {$themePath} -> {$locale} ==="); - $this->count = 0; - $this->errorCount = 0; - - /** @var \Magento\Theme\Model\View\Design $design */ - $design = $this->objectManager->create(\Magento\Theme\Model\View\Design::class); - $design->setDesignTheme($themePath, $area); - - $assetRepo = $this->objectManager->create( - \Magento\Framework\View\Asset\Repository::class, - [ - 'design' => $design, - ] - ); - /** @var \Magento\RequireJs\Model\FileManager $fileManager */ - $fileManager = $this->objectManager->create( - \Magento\RequireJs\Model\FileManager::class, - [ - 'config' => $this->objectManager->create( - \Magento\Framework\RequireJs\Config::class, - [ - 'assetRepo' => $assetRepo, - 'design' => $design, - ] - ), - 'assetRepo' => $assetRepo, - ] - ); - $fileManager->createRequireJsConfigAsset(); - - foreach ($appFiles as $info) { - list($fileArea, $fileTheme, , $module, $filePath, $fullPath) = $info; - - if ($this->checkSkip($filePath)) { - continue; - } - - if (($fileArea == $area || $fileArea == 'base') && - ($fileTheme == '' || $fileTheme == $themePath || - in_array( - $fileArea . Theme::THEME_PATH_SEPARATOR . $fileTheme, - $this->findAncestors($area . Theme::THEME_PATH_SEPARATOR . $themePath) - )) - ) { - $compiledFile = $this->deployFile( - $filePath, - $area, - $themePath, - $locale, - $module, - $fullPath - ); - if ($compiledFile !== '') { - $this->deployFile($compiledFile, $area, $themePath, $locale, $module, $fullPath); - } - } - } - foreach ($libFiles as $filePath) { - - if ($this->checkSkip($filePath)) { - continue; - } - - $compiledFile = $this->deployFile($filePath, $area, $themePath, $locale, null); - - if ($compiledFile !== '') { - $this->deployFile($compiledFile, $area, $themePath, $locale, null); - } - } - if (!$this->getOption(Options::NO_JAVASCRIPT)) { - if ($this->jsTranslationConfig->dictionaryEnabled()) { - $dictionaryFileName = $this->jsTranslationConfig->getDictionaryFileName(); - $this->deployFile($dictionaryFileName, $area, $themePath, $locale, null); - } - if ($this->getMinification()->isEnabled('js')) { - $fileManager->createMinResolverAsset(); - } - } - $this->bundleManager->flush(); - $this->output->writeln("\nSuccessful: {$this->count} files; errors: {$this->errorCount}\n---\n"); + $deployerManager->addPack($area, $themePath, $locale); } } } - if (!($this->getOption(Options::NO_HTML_MINIFY) ?: !$this->getAssetConfig()->isMinifyHtml())) { - $this->output->writeln('=== Minify templates ==='); - $this->count = 0; - foreach ($this->filesUtil->getPhtmlFiles(false, false) as $template) { - $this->htmlMinifier->minify($template); - if ($this->output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { - $this->output->writeln($template . " minified\n"); - } else { - $this->output->write('.'); - } - $this->count++; - } - $this->output->writeln("\nSuccessful: {$this->count} files modified\n---\n"); - } - - $version = (new \DateTime())->getTimestamp(); - $this->output->writeln("New version of deployed files: {$version}"); - if (!$this->getOption(Options::DRY_RUN)) { - $this->versionStorage->save($version); - } - - if ($this->errorCount > 0) { - // we must have an exit code higher than zero to indicate something was wrong - return \Magento\Framework\Console\Cli::RETURN_FAILURE; - } - return \Magento\Framework\Console\Cli::RETURN_SUCCESS; - } - - /** - * Get Minification instance - * - * @deprecated - * @return Minification - */ - private function getMinification() - { - if (null === $this->minification) { - $this->minification = ObjectManager::getInstance()->get(Minification::class); - } - - return $this->minification; - } - - /** - * Emulate application area and various services that are necessary for populating files - * - * @param string $areaCode - * @return void - */ - private function emulateApplicationArea($areaCode) - { - $this->objectManager = $this->omFactory->create( - [\Magento\Framework\App\State::PARAM_MODE => \Magento\Framework\App\State::MODE_PRODUCTION] - ); - /** @var \Magento\Framework\App\State $appState */ - $appState = $this->objectManager->get(\Magento\Framework\App\State::class); - $appState->setAreaCode($areaCode); - $this->assetRepo = $this->objectManager->get(\Magento\Framework\View\Asset\Repository::class); - $this->assetPublisher = $this->objectManager->create(\Magento\Framework\App\View\Asset\Publisher::class); - $this->htmlMinifier = $this->objectManager->get(\Magento\Framework\View\Template\Html\MinifierInterface::class); - $this->bundleManager = $this->objectManager->get(\Magento\Framework\View\Asset\Bundle\Manager::class); + return $deployerManager->deploy(); } /** @@ -373,146 +118,10 @@ class Deployer * @param string $locale * @param string $area * @return void - */ - protected function emulateApplicationLocale($locale, $area) - { - /** @var \Magento\Framework\TranslateInterface $translator */ - $translator = $this->objectManager->get(\Magento\Framework\TranslateInterface::class); - $translator->setLocale($locale); - $translator->loadData($area, true); - /** @var \Magento\Framework\Locale\ResolverInterface $localeResolver */ - $localeResolver = $this->objectManager->get(\Magento\Framework\Locale\ResolverInterface::class); - $localeResolver->setLocale($locale); - } - - /** - * Deploy a static view file - * - * @param string $filePath - * @param string $area - * @param string $themePath - * @param string $locale - * @param string $module - * @param string|null $fullPath - * @return string - * @throws \InvalidArgumentException - * @throws LocalizedException - * - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - private function deployFile($filePath, $area, $themePath, $locale, $module, $fullPath = null) - { - $compiledFile = ''; - $extension = pathinfo($filePath, PATHINFO_EXTENSION); - - foreach ($this->alternativeSources as $name => $alternative) { - if (in_array($extension, $alternative->getAlternativesExtensionsNames(), true) - && strpos(basename($filePath), '_') !== 0 - ) { - $compiledFile = substr($filePath, 0, strlen($filePath) - strlen($extension) - 1); - $compiledFile = $compiledFile . '.' . $name; - } - } - - if ($this->output->isVeryVerbose()) { - $logMessage = "Processing file '$filePath' for area '$area', theme '$themePath', locale '$locale'"; - if ($module) { - $logMessage .= ", module '$module'"; - } - $this->output->writeln($logMessage); - } - - try { - $asset = $this->assetRepo->createAsset( - $filePath, - ['area' => $area, 'theme' => $themePath, 'locale' => $locale, 'module' => $module] - ); - if ($this->output->isVeryVerbose()) { - $this->output->writeln("\tDeploying the file to '{$asset->getPath()}'"); - } else { - $this->output->write('.'); - } - if ($this->getOption(Options::DRY_RUN)) { - $asset->getContent(); - } else { - $this->assetPublisher->publish($asset); - $this->bundleManager->addAsset($asset); - } - $this->count++; - } catch (ContentProcessorException $exception) { - $pathInfo = $fullPath ?: $filePath; - $errorMessage = __('Compilation from source: ') . $pathInfo - . PHP_EOL . $exception->getMessage(); - $this->errorCount++; - $this->output->write(PHP_EOL . PHP_EOL . $errorMessage . PHP_EOL, true); - - $this->getLogger()->critical($errorMessage); - } catch (\Exception $exception) { - $this->output->write('.'); - $this->verboseLog($exception->getTraceAsString()); - $this->errorCount++; - } - - return $compiledFile; - } - - /** - * Find ancestor themes' full paths - * - * @param string $themeFullPath - * @return string[] - */ - private function findAncestors($themeFullPath) - { - /** @var \Magento\Framework\View\Design\Theme\ListInterface $themeCollection */ - $themeCollection = $this->objectManager->get(\Magento\Framework\View\Design\Theme\ListInterface::class); - $theme = $themeCollection->getThemeByFullPath($themeFullPath); - $ancestors = $theme->getInheritedThemes(); - $ancestorThemeFullPath = []; - foreach ($ancestors as $ancestor) { - $ancestorThemeFullPath[] = $ancestor->getFullPath(); - } - return $ancestorThemeFullPath; - } - - /** - * @return \Magento\Framework\View\Asset\ConfigInterface - * @deprecated - */ - private function getAssetConfig() - { - if (null === $this->assetConfig) { - $this->assetConfig = ObjectManager::getInstance()->get(ConfigInterface::class); - } - return $this->assetConfig; - } - - /** - * Verbose log - * - * @param string $message - * @return void - */ - private function verboseLog($message) - { - if ($this->output->isVerbose()) { - $this->output->writeln($message); - } - } - - /** - * Retrieves LoggerInterface instance - * - * @return LoggerInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @deprecated */ - private function getLogger() + protected function emulateApplicationLocale($locale, $area) { - if (!$this->logger) { - $this->logger = $this->objectManager->get(LoggerInterface::class); - } - - return $this->logger; } } diff --git a/app/code/Magento/Deploy/Model/ProcessManager.php b/app/code/Magento/Deploy/Model/ProcessManager.php index cff7e347ade9d9133e6d6cacb837118dd7178c23..9490f6dc67101eb63a14e8b6123e11b11008feeb 100644 --- a/app/code/Magento/Deploy/Model/ProcessManager.php +++ b/app/code/Magento/Deploy/Model/ProcessManager.php @@ -11,6 +11,20 @@ class ProcessManager /** @var Process[] */ private $processes = []; + /** + * @var ProcessFactory + */ + private $processFactory; + + /** + * ProcessManager constructor. + * @param ProcessFactory $processFactory + */ + public function __construct(ProcessFactory $processFactory) + { + $this->processFactory = $processFactory; + } + /** * Forks the currently running process. * @@ -66,7 +80,7 @@ class ProcessManager */ private function createProcess(callable $handler) { - return new Process($handler); + return $this->processFactory->create(['handler' => $handler]); } /** diff --git a/app/code/Magento/Deploy/Model/ProcessQueueManager.php b/app/code/Magento/Deploy/Model/ProcessQueueManager.php new file mode 100644 index 0000000000000000000000000000000000000000..c96a2dbb30bd0c169dee84ee9bd358e6dac49228 --- /dev/null +++ b/app/code/Magento/Deploy/Model/ProcessQueueManager.php @@ -0,0 +1,160 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Deploy\Model; + +use Magento\Framework\App\ResourceConnection; + +class ProcessQueueManager +{ + /** + * Default max amount of processes + */ + const DEFAULT_MAX_PROCESSES_AMOUNT = 4; + + /** + * @var ProcessTask[] + */ + private $tasksQueue = []; + + /** + * @var ProcessTask[] + */ + private $processTaskMap = []; + + /** + * @var int + */ + private $maxProcesses; + + /** + * @var ProcessManager + */ + private $processManager; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var ProcessTaskFactory + */ + private $processTaskFactory; + + /** + * @param ProcessManager $processManager + * @param ResourceConnection $resourceConnection + * @param ProcessTaskFactory $processTaskFactory + * @param int $maxProcesses + */ + public function __construct( + ProcessManager $processManager, + ResourceConnection $resourceConnection, + ProcessTaskFactory $processTaskFactory, + $maxProcesses = self::DEFAULT_MAX_PROCESSES_AMOUNT + ) { + $this->processManager = $processManager; + $this->resourceConnection = $resourceConnection; + $this->processTaskFactory = $processTaskFactory; + $this->maxProcesses = $maxProcesses; + } + + /** + * @param callable $task + * @param callable[] $dependentTasks + * @return void + */ + public function addTaskToQueue(callable $task, $dependentTasks = []) + { + $dependentTasks = array_map(function (callable $task) { + return $this->createTask($task); + }, $dependentTasks); + + $task = $this->createTask($task, $dependentTasks); + $this->tasksQueue[$task->getId()] = $task; + } + + /** + * Process tasks queue + * @return int + */ + public function process() + { + $processQueue = []; + $this->internalQueueProcess($this->tasksQueue, $processQueue); + + $returnStatus = 0; + while (count($this->processManager->getProcesses()) > 0) { + foreach ($this->processManager->getProcesses() as $process) { + if ($process->isCompleted()) { + $dependedTasks = isset($this->processTaskMap[$process->getPid()]) + ? $this->processTaskMap[$process->getPid()] + : []; + + $this->processManager->delete($process); + $returnStatus |= $process->getStatus(); + + $this->internalQueueProcess(array_merge($processQueue, $dependedTasks), $processQueue); + + if (count($this->processManager->getProcesses()) >= $this->maxProcesses) { + break 1; + } + } + } + usleep(5000); + } + $this->resourceConnection->closeConnection(); + + return $returnStatus; + } + + /** + * @param ProcessTask[] $taskQueue + * @param ProcessTask[] $processQueue + * @return void + */ + private function internalQueueProcess($taskQueue, &$processQueue) + { + $processNumber = count($this->processManager->getProcesses()); + foreach ($taskQueue as $task) { + if ($processNumber >= $this->maxProcesses) { + if (!isset($processQueue[$task->getId()])) { + $processQueue[$task->getId()] = $task; + } + } else { + unset($processQueue[$task->getId()]); + $this->fork($task); + $processNumber++; + } + } + } + + /** + * @param callable $handler + * @param array $dependentTasks + * @return ProcessTask + */ + private function createTask($handler, $dependentTasks = []) + { + return $this->processTaskFactory->create(['handler' => $handler, 'dependentTasks' => $dependentTasks]); + } + + /** + * @param ProcessTask $task + * @return void + */ + private function fork(ProcessTask $task) + { + $process = $this->processManager->fork($task->getHandler()); + if ($task->getDependentTasks()) { + $pid = $process->getPid(); + foreach ($task->getDependentTasks() as $dependentTask) { + $this->processTaskMap[$pid][$dependentTask->getId()] = $dependentTask; + } + } + } +} diff --git a/app/code/Magento/Deploy/Model/ProcessTask.php b/app/code/Magento/Deploy/Model/ProcessTask.php new file mode 100644 index 0000000000000000000000000000000000000000..8db7439c3d6f0be14b98ce937f33e8e01a76c85a --- /dev/null +++ b/app/code/Magento/Deploy/Model/ProcessTask.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Deploy\Model; + +class ProcessTask +{ + /** + * @var string + */ + private $taskId; + + /** + * @var callable + */ + private $handler; + + /** + * @var array + */ + private $dependentTasks; + + /** + * @param callable $handler + * @param array $dependentTasks + */ + public function __construct($handler, array $dependentTasks = []) + { + $this->taskId = uniqid('', true); + $this->handler = $handler; + $this->dependentTasks = $dependentTasks; + } + + /** + * @return callable + */ + public function getHandler() + { + return $this->handler; + } + + /** + * @return string + */ + public function getId() + { + return $this->taskId; + } + + /** + * @return ProcessTask[] + */ + public function getDependentTasks() + { + return $this->dependentTasks; + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/DeployStaticContentCommandTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/DeployStaticContentCommandTest.php index 24ccf34bb894ceaf802b0031a461db612920d28c..c8fa2138e7c81fb69d562fb5ec55e049cb5deabc 100644 --- a/app/code/Magento/Deploy/Test/Unit/Console/Command/DeployStaticContentCommandTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/DeployStaticContentCommandTest.php @@ -16,7 +16,7 @@ require 'FunctionExistMock.php'; class DeployStaticContentCommandTest extends \PHPUnit_Framework_TestCase { /** - * @var \Magento\Deploy\Model\Deployer|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Deploy\Model\DeployManager|\PHPUnit_Framework_MockObject_MockObject */ private $deployer; @@ -60,7 +60,7 @@ class DeployStaticContentCommandTest extends \PHPUnit_Framework_TestCase '', false ); - $this->deployer = $this->getMock(\Magento\Deploy\Model\Deployer::class, [], [], '', false); + $this->deployer = $this->getMock(\Magento\Deploy\Model\DeployManager::class, [], [], '', false); $this->filesUtil = $this->getMock(\Magento\Framework\App\Utility\Files::class, [], [], '', false); $this->appState = $this->getMock(\Magento\Framework\App\State::class, [], [], '', false); diff --git a/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleDeployTest.php b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleDeployTest.php new file mode 100644 index 0000000000000000000000000000000000000000..757da133ddbc3df30eac5a590c1c8dbf00fd002a --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleDeployTest.php @@ -0,0 +1,214 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Model\Deploy; + +use Magento\Framework\App\Utility\Files; +use Magento\Framework\App\View\Asset\Publisher; +use Magento\Framework\Translate\Js\Config; +use Magento\Framework\View\Asset\Minification; +use Magento\Framework\View\Asset\Repository; +use Magento\Framework\View\Asset\RepositoryFactory; +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Output\OutputInterface; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class LocaleDeployTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Config + */ + private $jsTranslationMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Minification + */ + private $minificationMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|RepositoryFactory + */ + private $assetRepoFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\RequireJs\Model\FileManagerFactory + */ + private $fileManagerFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\RequireJs\ConfigFactory + */ + private $configFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\View\Asset\Bundle\Manager + */ + private $bundleManagerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Files + */ + private $filesUtilMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\View\DesignInterfaceFactory + */ + private $designFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Locale\ResolverInterface + */ + private $localeResolverMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|OutputInterface + */ + private $outputMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|LoggerInterface + */ + private $loggerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $assetRepoMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $assetPublisherMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $themeProviderMock; + + protected function setUp() + { + $this->outputMock = $this->getMock(OutputInterface::class, [], [], '', false); + $this->loggerMock = $this->getMock(LoggerInterface::class, [], [], '', false); + $this->filesUtilMock = $this->getMock(Files::class, [], [], '', false); + $this->assetRepoMock = $this->getMock(Repository::class, [], [], '', false); + $this->minificationMock = $this->getMock(Minification::class, [], [], '', false); + $this->jsTranslationMock = $this->getMock(Config::class, [], [], '', false); + $this->assetPublisherMock = $this->getMock(Publisher::class, [], [], '', false); + $this->assetRepoFactoryMock = $this->getMock( + RepositoryFactory::class, + ['create'], + [], + '', + false + ); + $this->fileManagerFactoryMock = $this->getMock( + \Magento\RequireJs\Model\FileManagerFactory::class, + ['create'], + [], + '', + false + ); + $this->configFactoryMock = $this->getMock( + \Magento\Framework\RequireJs\ConfigFactory::class, + ['create'], + [], + '', + false + ); + $this->bundleManagerMock = $this->getMock( + \Magento\Framework\View\Asset\Bundle\Manager::class, + [], + [], + '', + false + ); + $this->themeProviderMock = $this->getMock( + \Magento\Framework\View\Design\Theme\ThemeProviderInterface::class, + [], + [], + '', + false + ); + $this->designFactoryMock = $this->getMock( + \Magento\Framework\View\DesignInterfaceFactory::class, + ['create'], + [], + '', + false + ); + $this->localeResolverMock = $this->getMock( + \Magento\Framework\Locale\ResolverInterface::class, + [], + [], + '', + false + ); + } + + public function testDeploy() + { + $area = 'adminhtml'; + $themePath = '/theme/path'; + $locale = 'en_US'; + + $designMock = $this->getMock(\Magento\Framework\View\DesignInterface::class, [], [], '', false); + $assetRepoMock = $this->getMock(Repository::class, [], [], '', false); + $requireJsConfigMock = $this->getMock(\Magento\Framework\RequireJs\Config::class, [], [], '', false); + $fileManagerMock = $this->getMock(\Magento\RequireJs\Model\FileManager::class, [], [], '', false); + + $model = $this->getModel([\Magento\Deploy\Console\Command\DeployStaticOptionsInterface::NO_JAVASCRIPT => 0]); + + $this->localeResolverMock->expects($this->once())->method('setLocale')->with($locale); + $this->designFactoryMock->expects($this->once())->method('create')->willReturn($designMock); + $designMock->expects($this->once())->method('setDesignTheme')->with($themePath, $area)->willReturnSelf(); + $this->assetRepoFactoryMock->expects($this->once())->method('create')->with(['design' => $designMock]) + ->willReturn($assetRepoMock); + $this->configFactoryMock->expects($this->once())->method('create')->willReturn($requireJsConfigMock); + $this->fileManagerFactoryMock->expects($this->once())->method('create')->willReturn($fileManagerMock); + + $fileManagerMock->expects($this->once())->method('createRequireJsConfigAsset')->willReturnSelf(); + $this->filesUtilMock->expects($this->once())->method('getStaticPreProcessingFiles')->willReturn([]); + $this->filesUtilMock->expects($this->once())->method('getStaticLibraryFiles')->willReturn([]); + + $this->jsTranslationMock->expects($this->once())->method('dictionaryEnabled')->willReturn(false); + $this->minificationMock->expects($this->once())->method('isEnabled')->with('js')->willReturn(true); + $fileManagerMock->expects($this->once())->method('createMinResolverAsset')->willReturnSelf(); + + $this->bundleManagerMock->expects($this->once())->method('flush'); + + $this->assertEquals( + \Magento\Framework\Console\Cli::RETURN_SUCCESS, + $model->deploy($area, $themePath, $locale) + ); + } + + /** + * @param array $options + * @return \Magento\Deploy\Model\Deploy\LocaleDeploy + */ + private function getModel($options = []) + { + return new \Magento\Deploy\Model\Deploy\LocaleDeploy( + $this->outputMock, + $this->jsTranslationMock, + $this->minificationMock, + $this->assetRepoMock, + $this->assetRepoFactoryMock, + $this->fileManagerFactoryMock, + $this->configFactoryMock, + $this->assetPublisherMock, + $this->bundleManagerMock, + $this->themeProviderMock, + $this->loggerMock, + $this->filesUtilMock, + $this->designFactoryMock, + $this->localeResolverMock, + [], + $options + ); + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleQuickDeployTest.php b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleQuickDeployTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0c7d459964b7d028e081b85440a58b991acb5661 --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/LocaleQuickDeployTest.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Model\Deploy; + +use Magento\Deploy\Model\Deploy\DeployInterface; +use Magento\Deploy\Model\Deploy\LocaleQuickDeploy; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options; +use \Magento\Framework\RequireJs\Config as RequireJsConfig; + +class LocaleQuickDeployTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var OutputInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $outputMock; + + /** + * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $staticDirectoryMock; + + protected function setUp() + { + $this->outputMock = $this->getMockBuilder(OutputInterface::class) + ->setMethods(['writeln']) + ->getMockForAbstractClass(); + + $this->staticDirectoryMock = $this->getMockBuilder(WriteInterface::class) + ->setMethods(['createSymlink', 'getAbsolutePath', 'getRelativePath', 'copyFile', 'readRecursively']) + ->getMockForAbstractClass(); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Deploy base locale must be set for Quick Deploy + */ + public function testDeployWithoutBaseLocale() + { + $this->getModel()->deploy('adminhtml', 'Magento/backend', 'en_US'); + } + + public function testDeployWithSymlinkStrategy() + { + $area = 'adminhtml'; + $themePath = 'Magento/backend'; + $locale = 'uk_UA'; + $baseLocal = 'en_US'; + + $this->staticDirectoryMock->expects(self::exactly(2)) + ->method('createSymlink') + ->withConsecutive( + ['adminhtml/Magento/backend/en_US', 'adminhtml/Magento/backend/uk_UA'], + ['_requirejs/adminhtml/Magento/backend/en_US', '_requirejs/adminhtml/Magento/backend/uk_UA'] + ); + + $model = $this->getModel([ + DeployInterface::DEPLOY_BASE_LOCALE => $baseLocal, + Options::SYMLINK_LOCALE => 1, + ]); + $model->deploy($area, $themePath, $locale); + } + + public function testDeployWithCopyStrategy() + { + + $area = 'adminhtml'; + $themePath = 'Magento/backend'; + $locale = 'uk_UA'; + $baseLocal = 'en_US'; + + $this->staticDirectoryMock->expects(self::never())->method('createSymlink'); + $this->staticDirectoryMock->expects(self::exactly(2))->method('readRecursively')->willReturnMap([ + ['adminhtml/Magento/backend/en_US', [$baseLocal . 'file1', $baseLocal . 'dir']], + [RequireJsConfig::DIR_NAME . '/adminhtml/Magento/backend/en_US', [$baseLocal . 'file2']] + ]); + $this->staticDirectoryMock->expects(self::exactly(3))->method('isFile')->willReturnMap([ + [$baseLocal . 'file1', true], + [$baseLocal . 'dir', false], + [$baseLocal . 'file2', true], + ]); + $this->staticDirectoryMock->expects(self::exactly(2))->method('copyFile')->withConsecutive( + [$baseLocal . 'file1', $locale . 'file1', null], + [$baseLocal . 'file2', $locale . 'file2', null] + ); + + $model = $this->getModel([ + DeployInterface::DEPLOY_BASE_LOCALE => $baseLocal, + Options::SYMLINK_LOCALE => 0, + ]); + $model->deploy($area, $themePath, $locale); + } + + /** + * @param array $options + * @return LocaleQuickDeploy + */ + private function getModel($options = []) + { + return (new ObjectManager($this))->getObject( + LocaleQuickDeploy::class, + [ + 'output' => $this->outputMock, + 'staticDirectory' => $this->staticDirectoryMock, + 'options' => $options + ] + ); + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Model/Deploy/TemplateMinifierTest.php b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/TemplateMinifierTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e6859faeca88a34692cf8b8c71f2532757207147 --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Model/Deploy/TemplateMinifierTest.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Model\Deploy; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class TemplateMinifierTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Deploy\Model\Deploy\TemplateMinifier + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\Utility\Files + */ + private $filesUtilsMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\View\Template\Html\MinifierInterface + */ + private $minifierMock; + + protected function setUp() + { + $this->minifierMock = $this->getMock( + \Magento\Framework\View\Template\Html\MinifierInterface::class, + [], + [], + '', + false + ); + $this->filesUtilsMock = $this->getMock(\Magento\Framework\App\Utility\Files::class, [], [], '', false); + + $this->model = new \Magento\Deploy\Model\Deploy\TemplateMinifier( + $this->filesUtilsMock, + $this->minifierMock + ); + } + + public function testMinifyTemplates() + { + $templateMock = "template.phtml"; + $templatesMock = [$templateMock]; + + $this->filesUtilsMock->expects($this->once())->method('getPhtmlFiles')->with(false, false) + ->willReturn($templatesMock); + $this->minifierMock->expects($this->once())->method('minify')->with($templateMock); + + self::assertEquals(1, $this->model->minifyTemplates()); + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Model/DeployManagerTest.php b/app/code/Magento/Deploy/Test/Unit/Model/DeployManagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b9a9f6049f0210ca3ad19a3ced6cc042b178e312 --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Model/DeployManagerTest.php @@ -0,0 +1,168 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Model; + +use Magento\Deploy\Console\Command\DeployStaticOptionsInterface as Options; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class DeployManagerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Deploy\Model\DeployStrategyProviderFactory + */ + private $deployStrategyProviderFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Console\Output\OutputInterface + */ + private $outputMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\View\Deployment\Version\StorageInterface + */ + private $versionStorageMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Deploy\Model\Deploy\TemplateMinifier + */ + private $minifierTemplateMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Deploy\Model\ProcessQueueManagerFactory + */ + private $processQueueManagerFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\State + */ + private $stateMock; + + protected function setUp() + { + $this->deployStrategyProviderFactoryMock = $this->getMock( + \Magento\Deploy\Model\DeployStrategyProviderFactory::class, + ['create'], + [], + '', + false + ); + $this->versionStorageMock = $this->getMock( + \Magento\Framework\App\View\Deployment\Version\StorageInterface::class, + [], + [], + '', + false + ); + $this->minifierTemplateMock = $this->getMock( + \Magento\Deploy\Model\Deploy\TemplateMinifier::class, + [], + [], + '', + false + ); + $this->processQueueManagerFactoryMock = $this->getMock( + \Magento\Deploy\Model\ProcessQueueManagerFactory::class, + [], + [], + '', + false + ); + $this->stateMock = $this->getMockBuilder(\Magento\Framework\App\State::class) + ->disableOriginalConstructor() + ->getMock(); + $this->outputMock = $this->getMock(\Symfony\Component\Console\Output\OutputInterface::class, [], [], '', false); + } + + public function testSaveDeployedVersion() + { + $version = (new \DateTime())->getTimestamp(); + $this->outputMock->expects($this->once())->method('writeln')->with("New version of deployed files: {$version}"); + $this->versionStorageMock->expects($this->once())->method('save')->with($version); + + $this->assertEquals( + \Magento\Framework\Console\Cli::RETURN_SUCCESS, + $this->getModel([Options::NO_HTML_MINIFY => true])->deploy() + ); + } + + public function testSaveDeployedVersionDryRun() + { + $options = [Options::DRY_RUN => true, Options::NO_HTML_MINIFY => true]; + + $this->outputMock->expects(self::once())->method('writeln')->with( + 'Dry run. Nothing will be recorded to the target directory.' + ); + $this->versionStorageMock->expects($this->never())->method('save'); + + $this->getModel($options)->deploy(); + } + + public function testMinifyTemplates() + { + $this->minifierTemplateMock->expects($this->once())->method('minifyTemplates')->willReturn(2); + $this->outputMock->expects($this->atLeastOnce())->method('writeln')->withConsecutive( + ["=== Minify templates ==="], + ["\nSuccessful: 2 files modified\n---\n"] + ); + + $this->getModel([Options::NO_HTML_MINIFY => false])->deploy(); + } + + public function testMinifyTemplatesNoHtmlMinify() + { + $version = (new \DateTime())->getTimestamp(); + $this->outputMock->expects($this->once())->method('writeln')->with("New version of deployed files: {$version}"); + $this->versionStorageMock->expects($this->once())->method('save')->with($version); + + $this->getModel([Options::NO_HTML_MINIFY => true])->deploy(); + } + + public function testDeploy() + { + $area = 'frontend'; + $themePath = 'themepath'; + $locale = 'en_US'; + $options = [Options::NO_HTML_MINIFY => true]; + $strategyProviderMock = $this->getMock(\Magento\Deploy\Model\DeployStrategyProvider::class, [], [], '', false); + $deployStrategyMock = $this->getMock(\Magento\Deploy\Model\Deploy\DeployInterface::class, [], [], '', false); + + $model = $this->getModel($options); + $model->addPack($area, $themePath, $locale); + $this->deployStrategyProviderFactoryMock->expects($this->once())->method('create')->with( + ['output' => $this->outputMock, 'options' => $options] + )->willReturn($strategyProviderMock); + $strategyProviderMock->expects($this->once())->method('getDeployStrategies')->with($area, $themePath, [$locale]) + ->willReturn([$locale => $deployStrategyMock]); + $this->stateMock->expects(self::once())->method('emulateAreaCode') + ->with($area, [$deployStrategyMock, 'deploy'], [$area, $themePath, $locale]) + ->willReturn(\Magento\Framework\Console\Cli::RETURN_SUCCESS); + + $version = (new \DateTime())->getTimestamp(); + $this->outputMock->expects(self::once())->method('writeln')->with("New version of deployed files: {$version}"); + $this->versionStorageMock->expects($this->once())->method('save')->with($version); + + $this->assertEquals(\Magento\Framework\Console\Cli::RETURN_SUCCESS, $model->deploy()); + } + + /** + * @param array $options + * @return \Magento\Deploy\Model\DeployManager + */ + private function getModel(array $options) + { + return new \Magento\Deploy\Model\DeployManager( + $this->outputMock, + $this->versionStorageMock, + $this->deployStrategyProviderFactoryMock, + $this->processQueueManagerFactoryMock, + $this->minifierTemplateMock, + $this->stateMock, + $options + ); + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Model/DeployStrategyFactoryTest.php b/app/code/Magento/Deploy/Test/Unit/Model/DeployStrategyFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2af443ef061e887cf6181adb2ff945812660f099 --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Model/DeployStrategyFactoryTest.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Model; + +use Magento\Deploy\Model\Deploy\LocaleDeploy; +use Magento\Deploy\Model\DeployStrategyFactory; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class DeployStrategyFactoryTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $objectManagerMock; + + /** + * @var DeployStrategyFactory + */ + private $unit; + + protected function setUp() + { + $this->objectManagerMock = $this->getMock(ObjectManagerInterface::class); + + $this->unit = (new ObjectManager($this))->getObject( + DeployStrategyFactory::class, + [ + 'objectManager' => $this->objectManagerMock, + ] + ); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + * @expectedExceptionMessage Wrong deploy strategy type: wrong-type + */ + public function testCreateWithWrongStrategyType() + { + $this->unit->create('wrong-type'); + } + + public function testCreate() + { + $this->objectManagerMock->expects(self::once())->method('create') + ->with(LocaleDeploy::class, ['arg1' => 1]); + + $this->unit->create(DeployStrategyFactory::DEPLOY_STRATEGY_STANDARD, ['arg1' => 1]); + } +} diff --git a/app/code/Magento/Deploy/Test/Unit/Model/ProcessQueueManagerTest.php b/app/code/Magento/Deploy/Test/Unit/Model/ProcessQueueManagerTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e77068755036ed4983b920de4f49173a1337b753 --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Model/ProcessQueueManagerTest.php @@ -0,0 +1,85 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Deploy\Test\Unit\Model; + +use Magento\Deploy\Model\ProcessManager; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Deploy\Model\ProcessTaskFactory; +use Magento\Deploy\Model\ProcessTask; + +class ProcessQueueManagerTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Deploy\Model\ProcessQueueManager + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Deploy\Model\ProcessManager + */ + private $processManagerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\ResourceConnection + */ + private $resourceConnectionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|ProcessTaskFactory + */ + private $processTaskFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|ProcessTask + */ + private $processTaskMock; + + protected function setUp() + { + $this->processManagerMock = $this->getMock(ProcessManager::class, [], [], '', false); + $this->resourceConnectionMock = $this->getMock(ResourceConnection::class, [], [], '', false); + $this->processTaskFactoryMock = $this->getMock(ProcessTaskFactory::class, ['create'], [], '', false); + $this->processTaskMock = $this->getMock(ProcessTask::class, [], [], '', false); + $this->processTaskFactoryMock->expects($this->any())->method('create')->willReturn($this->processTaskMock); + $this->model = (new ObjectManager($this))->getObject( + \Magento\Deploy\Model\ProcessQueueManager::class, + [ + 'processManager' => $this->processManagerMock, + 'resourceConnection' => $this->resourceConnectionMock, + 'processTaskFactory' => $this->processTaskFactoryMock + ] + ); + } + + public function testProcess() + { + $callableMock = function () { + return true; + }; + $this->processTaskMock->expects($this->any())->method('getHandler')->willReturn($callableMock); + + $processMock = $this->getMock(\Magento\Deploy\Model\Process::class, [], [], '', false); + + $this->model->addTaskToQueue($callableMock, []); + $this->processManagerMock->expects($this->atLeastOnce())->method('getProcesses')->willReturnOnConsecutiveCalls( + [$processMock], + [$processMock], + [$processMock], + [$processMock], + [$processMock], + [] + ); + $processMock->expects($this->once())->method('isCompleted')->willReturn(true); + $processMock->expects($this->atLeastOnce())->method('getPid')->willReturn(42); + $processMock->expects($this->once())->method('getStatus')->willReturn(0); + $this->processManagerMock->expects($this->once())->method('delete')->with($processMock); + + $this->resourceConnectionMock->expects(self::once())->method('closeConnection'); + + $this->assertEquals(0, $this->model->process()); + } +} diff --git a/app/code/Magento/Deploy/composer.json b/app/code/Magento/Deploy/composer.json index 05db53f039027e2ca8e62f490005a782e0dfc088..856e0d8b3e542a37cdd7fcf7554b9e4e158de245 100644 --- a/app/code/Magento/Deploy/composer.json +++ b/app/code/Magento/Deploy/composer.json @@ -5,7 +5,6 @@ "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*", "magento/module-store": "100.2.*", - "magento/module-theme": "100.2.*", "magento/module-require-js": "100.2.*", "magento/module-user": "100.2.*" }, diff --git a/app/code/Magento/Deploy/etc/di.xml b/app/code/Magento/Deploy/etc/di.xml index e1a0295a0fe32b8a916755f717ad54876c474558..52c880c28d0a71ad7eaeb34b6c2096c6c7b5060a 100644 --- a/app/code/Magento/Deploy/etc/di.xml +++ b/app/code/Magento/Deploy/etc/di.xml @@ -13,6 +13,13 @@ </argument> </arguments> </type> + <type name="Magento\Deploy\Model\Deploy\LocaleDeploy"> + <arguments> + <argument name="alternativeSources" xsi:type="array"> + <item name="css" xsi:type="object">AlternativeSourceProcessors</item> + </argument> + </arguments> + </type> <type name="Magento\Framework\Console\CommandListInterface"> <arguments> <argument name="commands" xsi:type="array"> diff --git a/app/code/Magento/Theme/Model/Theme/ThemeProvider.php b/app/code/Magento/Theme/Model/Theme/ThemeProvider.php index 29da9c3220ee29fa60b24085336eaff919d5b3dd..9fd3ce94dac45888687cbe6bff26f86a2ac9d2e7 100644 --- a/app/code/Magento/Theme/Model/Theme/ThemeProvider.php +++ b/app/code/Magento/Theme/Model/Theme/ThemeProvider.php @@ -22,6 +22,11 @@ class ThemeProvider implements \Magento\Framework\View\Design\Theme\ThemeProvide */ protected $cache; + /** + * @var \Magento\Framework\View\Design\ThemeInterface[] + */ + private $themes; + /** * ThemeProvider constructor. * @@ -44,10 +49,14 @@ class ThemeProvider implements \Magento\Framework\View\Design\Theme\ThemeProvide */ public function getThemeByFullPath($fullPath) { + if (isset($this->themes[$fullPath])) { + return $this->themes[$fullPath]; + } /** @var $themeCollection \Magento\Theme\Model\ResourceModel\Theme\Collection */ $theme = $this->cache->load('theme'. $fullPath); if ($theme) { - return unserialize($theme); + $this->themes[$fullPath] = unserialize($theme); + return $this->themes[$fullPath]; } $themeCollection = $this->collectionFactory->create(); $item = $themeCollection->getThemeByFullPath($fullPath); @@ -55,7 +64,9 @@ class ThemeProvider implements \Magento\Framework\View\Design\Theme\ThemeProvide $themeData = serialize($item); $this->cache->save($themeData, 'theme' . $fullPath); $this->cache->save($themeData, 'theme-by-id-' . $item->getId()); + $this->themes[$fullPath] = $item; } + return $item; } @@ -77,15 +88,20 @@ class ThemeProvider implements \Magento\Framework\View\Design\Theme\ThemeProvide */ public function getThemeById($themeId) { + if (isset($this->themes[$themeId])) { + return $this->themes[$themeId]; + } $theme = $this->cache->load('theme-by-id-' . $themeId); if ($theme) { - return unserialize($theme); + $this->themes[$themeId] = unserialize($theme); + return $this->themes[$themeId]; } /** @var $themeModel \Magento\Framework\View\Design\ThemeInterface */ $themeModel = $this->themeFactory->create(); $themeModel->load($themeId); if ($themeModel->getId()) { $this->cache->save(serialize($themeModel), 'theme-by-id-' . $themeId); + $this->themes[$themeId] = $themeModel; } return $themeModel; } diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/Asset/MinifierTest.php b/dev/tests/integration/testsuite/Magento/Framework/View/Asset/MinifierTest.php index f90fc157e37296e61d1dd15f9254d1803edb8752..c155ef8a5c9c9ee9ba708c3f74e33331ce330aaf 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/View/Asset/MinifierTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/View/Asset/MinifierTest.php @@ -140,43 +140,44 @@ class MinifierTest extends \PHPUnit_Framework_TestCase } /** - * @magentoConfigFixture current_store dev/css/minify_files 1 + * @magentoConfigFixture current_store dev/css/minify_files 0 + * @magentoAppIsolation enabled */ - public function testCssMinification() + public function testCssMinificationOff() { $this->_testCssMinification( - '/frontend/FrameworkViewMinifier/default/en_US/css/styles.min.css', + '/frontend/FrameworkViewMinifier/default/en_US/css/styles.css', function ($path) { - $this->assertEquals( + $content = file_get_contents($path); + $this->assertNotEmpty($content); + $this->assertContains('FrameworkViewMinifier/frontend', $content); + $this->assertNotEquals( file_get_contents( dirname(__DIR__) . '/_files/static/expected/styles.magento.min.css' ), - file_get_contents($path), - 'Minified files are not equal or minification did not work for requested CSS' + $content, + 'CSS is minified when minification turned off' ); } ); } /** - * @magentoConfigFixture current_store dev/css/minify_files 0 + * @magentoConfigFixture current_store dev/css/minify_files 1 */ - public function testCssMinificationOff() + public function testCssMinification() { $this->_testCssMinification( - '/frontend/FrameworkViewMinifier/default/en_US/css/styles.css', + '/frontend/FrameworkViewMinifier/default/en_US/css/styles.min.css', function ($path) { - $content = file_get_contents($path); - $this->assertNotEmpty($content); - $this->assertContains('FrameworkViewMinifier/frontend', $content); - $this->assertNotEquals( + $this->assertEquals( file_get_contents( dirname(__DIR__) . '/_files/static/expected/styles.magento.min.css' ), - $content, - 'CSS is minified when minification turned off' + file_get_contents($path), + 'Minified files are not equal or minification did not work for requested CSS' ); } ); @@ -234,13 +235,13 @@ class MinifierTest extends \PHPUnit_Framework_TestCase ] )); - /** @var \Magento\Deploy\Model\Deployer $deployer */ + /** @var \Magento\Deploy\Model\Deploy\LocaleDeploy $deployer */ $deployer = $this->objectManager->create( - \Magento\Deploy\Model\Deployer::class, + \Magento\Deploy\Model\Deploy\LocaleDeploy::class, ['filesUtil' => $filesUtil, 'output' => $output] ); - $deployer->deploy($omFactory, ['en_US'], ['frontend' => ['FrameworkViewMinifier/default']]); + $deployer->deploy('frontend', 'FrameworkViewMinifier/default', 'en_US', []); $this->assertFileExists($fileToBePublished); $this->assertEquals( diff --git a/lib/internal/Magento/Framework/App/Config/ScopePool.php b/lib/internal/Magento/Framework/App/Config/ScopePool.php index d366349722f0fe8157be6b21ad1a290d17cf67b2..9e6a47d918f7602cd2e2b1d50286eb82f3f5cca6 100644 --- a/lib/internal/Magento/Framework/App/Config/ScopePool.php +++ b/lib/internal/Magento/Framework/App/Config/ScopePool.php @@ -92,16 +92,18 @@ class ScopePool { $scopeCode = $this->_getScopeCode($scopeType, $scopeCode); - // Key by url to support dynamic {{base_url}} and port assignments - $host = $this->getRequest()->getHttpHost(); - $port = $this->getRequest()->getServer('SERVER_PORT'); - $path = $this->getRequest()->getBasePath(); - $urlInfo = $host . $port . trim($path, '/'); - $code = $scopeType . '|' . $scopeCode . '|' . $urlInfo; + $code = $scopeType . '|' . $scopeCode; if (!isset($this->_scopes[$code])) { - $cacheKey = $this->_cacheId . '|' . $code; + // Key by url to support dynamic {{base_url}} and port assignments + $host = $this->getRequest()->getHttpHost(); + $port = $this->getRequest()->getServer('SERVER_PORT'); + $path = $this->getRequest()->getBasePath(); + + $urlInfo = $host . $port . trim($path, '/'); + $cacheKey = $this->_cacheId . '|' . $code . '|' . $urlInfo; $data = $this->_cache->load($cacheKey); + if ($data) { $data = unserialize($data); } else { diff --git a/lib/internal/Magento/Framework/App/ResourceConnection.php b/lib/internal/Magento/Framework/App/ResourceConnection.php index 5a2e4a6710a187d3506dc96020e7f17209513ff6..d41a457d4559cf077259f70210a2318e61f7d93f 100644 --- a/lib/internal/Magento/Framework/App/ResourceConnection.php +++ b/lib/internal/Magento/Framework/App/ResourceConnection.php @@ -92,6 +92,18 @@ class ResourceConnection return $this->getConnectionByName($connectionName); } + /** + * @param string $resourceName + * @return void + */ + public function closeConnection($resourceName = self::DEFAULT_CONNECTION) + { + $processConnectionName = $this->getProcessConnectionName($this->config->getConnectionName($resourceName)); + if (isset($this->connections[$processConnectionName])) { + $this->connections[$processConnectionName] = null; + } + } + /** * Retrieve connection by $connectionName * @@ -101,8 +113,9 @@ class ResourceConnection */ public function getConnectionByName($connectionName) { - if (isset($this->connections[$connectionName])) { - return $this->connections[$connectionName]; + $processConnectionName = $this->getProcessConnectionName($connectionName); + if (isset($this->connections[$processConnectionName])) { + return $this->connections[$processConnectionName]; } $connectionConfig = $this->deploymentConfig->get( @@ -115,10 +128,19 @@ class ResourceConnection throw new \DomainException('Connection "' . $connectionName . '" is not defined'); } - $this->connections[$connectionName] = $connection; + $this->connections[$processConnectionName] = $connection; return $connection; } + /** + * @param string $connectionName + * @return string + */ + private function getProcessConnectionName($connectionName) + { + return $connectionName . '_process_' . getmypid(); + } + /** * Get resource table name, validated by db adapter * diff --git a/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php b/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php index 49d2545ea5bb807bdd7853047e0496966e5ba177..5f2414283ad89a8dd62ebca632e95844ed9a6575 100644 --- a/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php +++ b/lib/internal/Magento/Framework/Css/PreProcessor/Adapter/Less/Processor.php @@ -81,9 +81,11 @@ class Processor implements ContentProcessorInterface } $tmpFilePath = $this->temporaryFile->createFile($path, $content); - $parser->parseFile($tmpFilePath, ''); + gc_disable(); + $parser->parseFile($tmpFilePath, ''); $content = $parser->getCss(); + gc_enable(); if (trim($content) === '') { $errorMessage = PHP_EOL . self::ERROR_MESSAGE_PREFIX . PHP_EOL . $path; diff --git a/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php b/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php index a07c7eea16aeb452aac3b1561b313bf2296a51fb..ab5db005978b1ec4e96ee8295a2a75a8cd5986b5 100644 --- a/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php +++ b/lib/internal/Magento/Framework/Css/PreProcessor/Instruction/MagentoImport.php @@ -5,15 +5,18 @@ */ namespace Magento\Framework\Css\PreProcessor\Instruction; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Css\PreProcessor\ErrorHandlerInterface; use Magento\Framework\View\Asset\File\FallbackContext; use Magento\Framework\View\Asset\LocalInterface; use Magento\Framework\View\Asset\PreProcessorInterface; +use Magento\Framework\View\Design\Theme\ThemeProviderInterface; use Magento\Framework\View\DesignInterface; use Magento\Framework\View\File\CollectorInterface; /** * @magento_import instruction preprocessor + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) Must be deleted after moving themeProvider to construct */ class MagentoImport implements PreProcessorInterface { @@ -45,9 +48,15 @@ class MagentoImport implements PreProcessorInterface /** * @var \Magento\Framework\View\Design\Theme\ListInterface + * @deprecated */ protected $themeList; + /** + * @var ThemeProviderInterface + */ + private $themeProvider; + /** * @param DesignInterface $design * @param CollectorInterface $fileSource @@ -120,8 +129,23 @@ class MagentoImport implements PreProcessorInterface { $context = $asset->getContext(); if ($context instanceof FallbackContext) { - return $this->themeList->getThemeByFullPath($context->getAreaCode() . '/' . $context->getThemePath()); + return $this->getThemeProvider()->getThemeByFullPath( + $context->getAreaCode() . '/' . $context->getThemePath() + ); } return $this->design->getDesignTheme(); } + + /** + * @return ThemeProviderInterface + * @deprecated + */ + private function getThemeProvider() + { + if (null === $this->themeProvider) { + $this->themeProvider = ObjectManager::getInstance()->get(ThemeProviderInterface::class); + } + + return $this->themeProvider; + } } diff --git a/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php b/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php index f5c81d78436aa40e8218d22c6af90a3c388e1891..9bc04c8ff33e9183deebee50faa08765949503d4 100644 --- a/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php +++ b/lib/internal/Magento/Framework/Css/Test/Unit/PreProcessor/Instruction/MagentoImportTest.php @@ -8,6 +8,10 @@ namespace Magento\Framework\Css\Test\Unit\PreProcessor\Instruction; +use Magento\Framework\Css\PreProcessor\Instruction\MagentoImport; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\View\Design\Theme\ThemeProviderInterface; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -39,9 +43,9 @@ class MagentoImportTest extends \PHPUnit_Framework_TestCase private $assetRepo; /** - * @var \Magento\Framework\View\Design\Theme\ListInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ThemeProviderInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $themeList; + private $themeProvider; /** * @var \Magento\Framework\Css\PreProcessor\Instruction\Import @@ -58,14 +62,14 @@ class MagentoImportTest extends \PHPUnit_Framework_TestCase $this->asset = $this->getMock(\Magento\Framework\View\Asset\File::class, [], [], '', false); $this->asset->expects($this->any())->method('getContentType')->will($this->returnValue('css')); $this->assetRepo = $this->getMock(\Magento\Framework\View\Asset\Repository::class, [], [], '', false); - $this->themeList = $this->getMockForAbstractClass(\Magento\Framework\View\Design\Theme\ListInterface::class); - $this->object = new \Magento\Framework\Css\PreProcessor\Instruction\MagentoImport( - $this->design, - $this->fileSource, - $this->errorHandler, - $this->assetRepo, - $this->themeList - ); + $this->themeProvider = $this->getMock(ThemeProviderInterface::class); + $this->object = (new ObjectManager($this))->getObject(MagentoImport::class, [ + 'design' => $this->design, + 'fileSource' => $this->fileSource, + 'errorHandler' => $this->errorHandler, + 'assetRepo' => $this->assetRepo, + 'themeProvider' => $this->themeProvider + ]); } /** @@ -91,7 +95,7 @@ class MagentoImportTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue($relatedAsset)); $relatedAsset->expects($this->once())->method('getContext')->will($this->returnValue($context)); $theme = $this->getMockForAbstractClass(\Magento\Framework\View\Design\ThemeInterface::class); - $this->themeList->expects($this->once())->method('getThemeByFullPath')->will($this->returnValue($theme)); + $this->themeProvider->expects($this->once())->method('getThemeByFullPath')->will($this->returnValue($theme)); $files = []; foreach ($foundFiles as $file) { $fileObject = $this->getMock(\Magento\Framework\View\File::class, [], [], '', false); diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/Write.php b/lib/internal/Magento/Framework/Filesystem/Directory/Write.php index 1f69922b799893068da3d91958ca8d53f79e0424..b7752ddccf886b391e23b121ba29fa324348e68c 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/Write.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/Write.php @@ -144,8 +144,6 @@ class Write extends Read implements WriteInterface */ public function createSymlink($path, $destination, WriteInterface $targetDirectory = null) { - $this->assertIsFile($path); - $targetDirectory = $targetDirectory ?: $this; $parentDirectory = $this->driver->getParentDirectory($destination); if (!$targetDirectory->isExist($parentDirectory)) { diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/WriteInterface.php b/lib/internal/Magento/Framework/Filesystem/Directory/WriteInterface.php index 472c54a08d202d2acfe5d0150949a5e254f04447..3fa0513dd21a0b7a5cd279204547b0aa4069879c 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/WriteInterface.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/WriteInterface.php @@ -48,7 +48,7 @@ interface WriteInterface extends ReadInterface public function copyFile($path, $destination, WriteInterface $targetDirectory = null); /** - * Creates symlink on a file and places it to destination + * Creates symlink on a file or directory and places it to destination * * @param string $path * @param string $destination diff --git a/lib/internal/Magento/Framework/Test/Unit/App/ResourceConnectionTest.php b/lib/internal/Magento/Framework/Test/Unit/App/ResourceConnectionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9cbdea93cf4fcd549b26731e670741ed8391be36 --- /dev/null +++ b/lib/internal/Magento/Framework/Test/Unit/App/ResourceConnectionTest.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Test\Unit\App; + +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Config\ConfigOptionsListConstants; +use Magento\Framework\Model\ResourceModel\Type\Db\ConnectionFactoryInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\App\ResourceConnection\ConfigInterface; + +class ResourceConnectionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ResourceConnection + */ + private $unit; + + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $deploymentConfigMock; + + /** + * @var ConnectionFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $connectionFactoryMock; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + protected function setUp() + { + $this->deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->connectionFactoryMock = $this->getMockBuilder(ConnectionFactoryInterface::class) + ->getMock(); + + $this->configMock = $this->getMockBuilder(ConfigInterface::class)->getMock(); + + $this->objectManager = (new ObjectManager($this)); + $this->unit = $this->objectManager->getObject( + ResourceConnection::class, + [ + 'deploymentConfig' => $this->deploymentConfigMock, + 'connectionFactory' => $this->connectionFactoryMock, + 'config' => $this->configMock, + ] + ); + } + + public function testGetConnectionByName() + { + $this->deploymentConfigMock->expects(self::once())->method('get') + ->with(ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTIONS . '/default') + ->willReturn(['config']); + $this->connectionFactoryMock->expects(self::once())->method('create') + ->with(['config']) + ->willReturn('connection'); + + self::assertEquals('connection', $this->unit->getConnectionByName('default')); + } + + public function testGetExistingConnectionByName() + { + $unit = $this->objectManager->getObject( + ResourceConnection::class, + [ + 'deploymentConfig' => $this->deploymentConfigMock, + 'connections' => ['default_process_' . getmypid() => 'existing_connection'] + ] + ); + $this->deploymentConfigMock->expects(self::never())->method('get'); + + self::assertEquals('existing_connection', $unit->getConnectionByName('default')); + } + + public function testCloseConnection() + { + $this->configMock->expects(self::once())->method('getConnectionName')->with('default'); + + $this->unit->closeConnection('default'); + + } +} diff --git a/lib/internal/Magento/Framework/View/Asset/Bundle/Config.php b/lib/internal/Magento/Framework/View/Asset/Bundle/Config.php index 21e974fca57e01b3154c614a6cb64f5f52850e2e..0fe15fd14af709d151259209872ebfeacb46bff0 100644 --- a/lib/internal/Magento/Framework/View/Asset/Bundle/Config.php +++ b/lib/internal/Magento/Framework/View/Asset/Bundle/Config.php @@ -6,10 +6,12 @@ namespace Magento\Framework\View\Asset\Bundle; +use Magento\Framework\App\ObjectManager; use Magento\Framework\View; use Magento\Framework\View\Asset\Bundle; use Magento\Framework\View\Design\Theme\ListInterface; use Magento\Framework\View\Asset\File\FallbackContext; +use Magento\Framework\View\Design\Theme\ThemeProviderInterface; class Config implements Bundle\ConfigInterface { @@ -30,6 +32,16 @@ class Config implements Bundle\ConfigInterface */ protected $viewConfig; + /** + * @var ThemeProviderInterface + */ + private $themeProvider; + + /** + * @var \Magento\Framework\Config\View[] + */ + private $config = []; + /** * @param View\ConfigInterface $viewConfig * @param ListInterface $themeList @@ -57,12 +69,17 @@ class Config implements Bundle\ConfigInterface */ public function getConfig(FallbackContext $assetContext) { - return $this->viewConfig->getViewConfig([ - 'area' => $assetContext->getAreaCode(), - 'themeModel' => $this->themeList->getThemeByFullPath( - $assetContext->getAreaCode() . '/' . $assetContext->getThemePath() - ) - ]); + $themePath = $assetContext->getAreaCode() . '/' . $assetContext->getThemePath(); + if (!isset($this->config[$themePath])) { + $this->config[$themePath] = $this->viewConfig->getViewConfig([ + 'area' => $assetContext->getAreaCode(), + 'themeModel' => $this->getThemeProvider()->getThemeByFullPath( + $themePath + ) + ]); + } + + return $this->config[$themePath]; } /** @@ -83,7 +100,19 @@ class Config implements Bundle\ConfigInterface case 'MB': return (int)$size * 1024; default: - return (int)$size / 1024; + return (int)($size / 1024); } } + + /** + * @return ThemeProviderInterface + */ + private function getThemeProvider() + { + if (null === $this->themeProvider) { + $this->themeProvider = ObjectManager::getInstance()->get(ThemeProviderInterface::class); + } + + return $this->themeProvider; + } } diff --git a/lib/internal/Magento/Framework/View/Asset/LockerProcess.php b/lib/internal/Magento/Framework/View/Asset/LockerProcess.php index 660cf0418669ead047cb6713883d0a8039ab75bb..e8938211fecbf642ff3994515730e517013f1100 100644 --- a/lib/internal/Magento/Framework/View/Asset/LockerProcess.php +++ b/lib/internal/Magento/Framework/View/Asset/LockerProcess.php @@ -59,7 +59,6 @@ class LockerProcess implements LockerProcessInterface /** * @inheritdoc - * @throws FileSystemException */ public function lockProcess($lockName) { @@ -94,14 +93,18 @@ class LockerProcess implements LockerProcessInterface * Check whether generation process has already locked * * @return bool - * @throws FileSystemException */ private function isProcessLocked() { if ($this->tmpDirectory->isExist($this->lockFilePath)) { - $lockTime = (int) $this->tmpDirectory->readFile($this->lockFilePath); - if ((time() - $lockTime) >= self::MAX_LOCK_TIME) { - $this->tmpDirectory->delete($this->lockFilePath); + try { + $lockTime = (int)$this->tmpDirectory->readFile($this->lockFilePath); + if ((time() - $lockTime) >= self::MAX_LOCK_TIME) { + $this->tmpDirectory->delete($this->lockFilePath); + + return false; + } + } catch (FileSystemException $e) { return false; } diff --git a/lib/internal/Magento/Framework/View/Asset/Minification.php b/lib/internal/Magento/Framework/View/Asset/Minification.php index 255c9690e3fa9f2515fc5ac50d97fb002b87ede8..1e32e32b99676e16e538445ae122221ff5e48ec6 100644 --- a/lib/internal/Magento/Framework/View/Asset/Minification.php +++ b/lib/internal/Magento/Framework/View/Asset/Minification.php @@ -21,18 +21,21 @@ class Minification * @var ScopeConfigInterface */ private $scopeConfig; + /** * @var State */ private $appState; + /** * @var string */ private $scope; + /** * @var array */ - private $excludes = []; + private $configCache = []; /** * @param ScopeConfigInterface $scopeConfig @@ -54,12 +57,16 @@ class Minification */ public function isEnabled($contentType) { - return - $this->appState->getMode() != State::MODE_DEVELOPER && - (bool)$this->scopeConfig->isSetFlag( - sprintf(self::XML_PATH_MINIFICATION_ENABLED, $contentType), - $this->scope - ); + if (!isset($this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$contentType])) { + $this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$contentType] = + $this->appState->getMode() != State::MODE_DEVELOPER && + (bool)$this->scopeConfig->isSetFlag( + sprintf(self::XML_PATH_MINIFICATION_ENABLED, $contentType), + $this->scope + ); + } + + return $this->configCache[self::XML_PATH_MINIFICATION_ENABLED][$contentType]; } /** @@ -131,15 +138,15 @@ class Minification */ public function getExcludes($contentType) { - if (!isset($this->excludes[$contentType])) { - $this->excludes[$contentType] = []; + if (!isset($this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType])) { + $this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType] = []; $key = sprintf(self::XML_PATH_MINIFICATION_EXCLUDES, $contentType); foreach (explode("\n", $this->scopeConfig->getValue($key, $this->scope)) as $exclude) { if (trim($exclude) != '') { - $this->excludes[$contentType][] = trim($exclude); + $this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType][] = trim($exclude); } }; } - return $this->excludes[$contentType]; + return $this->configCache[self::XML_PATH_MINIFICATION_EXCLUDES][$contentType]; } } diff --git a/lib/internal/Magento/Framework/View/Asset/Repository.php b/lib/internal/Magento/Framework/View/Asset/Repository.php index 072b3361cbefea99e2ebb5dd958cc8468bbda088..c898d57c11e7a6a26f205f1288097d4bb5a9a60c 100644 --- a/lib/internal/Magento/Framework/View/Asset/Repository.php +++ b/lib/internal/Magento/Framework/View/Asset/Repository.php @@ -8,7 +8,8 @@ namespace Magento\Framework\View\Asset; use Magento\Framework\UrlInterface; use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\Filesystem; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\View\Design\Theme\ThemeProviderInterface; /** * A repository service for view assets @@ -35,6 +36,7 @@ class Repository /** * @var \Magento\Framework\View\Design\Theme\ListInterface + * @deprecated */ private $themeList; @@ -72,11 +74,17 @@ class Repository * @var File\ContextFactory */ private $contextFactory; + /** * @var RemoteFactory */ private $remoteFactory; + /** + * @var ThemeProviderInterface + */ + private $themeProvider; + /** * @param \Magento\Framework\UrlInterface $baseUrl * @param \Magento\Framework\View\DesignInterface $design @@ -138,7 +146,7 @@ class Repository } if ($theme) { - $params['themeModel'] = $this->themeList->getThemeByFullPath($area . '/' . $theme); + $params['themeModel'] = $this->getThemeProvider()->getThemeByFullPath($area . '/' . $theme); if (!$params['themeModel']) { throw new \UnexpectedValueException("Could not find theme '$theme' for area '$area'"); } @@ -158,6 +166,18 @@ class Repository return $this; } + /** + * @return ThemeProviderInterface + */ + private function getThemeProvider() + { + if (null === $this->themeProvider) { + $this->themeProvider = ObjectManager::getInstance()->get(ThemeProviderInterface::class); + } + + return $this->themeProvider; + } + /** * Get default design parameter * diff --git a/lib/internal/Magento/Framework/View/Asset/Source.php b/lib/internal/Magento/Framework/View/Asset/Source.php index 1bde94402d1a868c80998329f3a6ccf8cc67ea1f..cf80bd0d1b3821cc3050cd94ee691de98e8385e4 100644 --- a/lib/internal/Magento/Framework/View/Asset/Source.php +++ b/lib/internal/Magento/Framework/View/Asset/Source.php @@ -8,8 +8,10 @@ namespace Magento\Framework\View\Asset; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem\Directory\ReadFactory; +use Magento\Framework\App\ObjectManager; use Magento\Framework\View\Asset\PreProcessor\ChainFactoryInterface; use Magento\Framework\View\Design\FileResolution\Fallback\Resolver\Simple; +use Magento\Framework\View\Design\Theme\ThemeProviderInterface; /** * A service for preprocessing content of assets @@ -45,6 +47,7 @@ class Source /** * @var \Magento\Framework\View\Design\Theme\ListInterface + * @deprecated */ private $themeList; @@ -58,6 +61,11 @@ class Source */ private $readFactory; + /** + * @var ThemeProviderInterface + */ + private $themeProvider; + /** * Constructor * @@ -218,7 +226,9 @@ class Source LocalInterface $asset, \Magento\Framework\View\Asset\File\FallbackContext $context ) { - $themeModel = $this->themeList->getThemeByFullPath($context->getAreaCode() . '/' . $context->getThemePath()); + $themeModel = $this->getThemeProvider()->getThemeByFullPath( + $context->getAreaCode() . '/' . $context->getThemePath() + ); $sourceFile = $this->fallback->getFile( $context->getAreaCode(), $themeModel, @@ -229,6 +239,18 @@ class Source return $sourceFile; } + /** + * @return ThemeProviderInterface + */ + private function getThemeProvider() + { + if (null === $this->themeProvider) { + $this->themeProvider = ObjectManager::getInstance()->get(ThemeProviderInterface::class); + } + + return $this->themeProvider; + } + /** * Find asset file by simply appending its path to the directory in context * diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php index e3c50843976588224b20a9b5236858cae1e2d4c1..3b51f7cc3df2a278561f5007f6380e235226a750 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php @@ -6,7 +6,9 @@ namespace Magento\Framework\View\Test\Unit\Asset; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\Asset\Repository; +use Magento\Framework\View\Design\Theme\ThemeProviderInterface; /** * Unit test for Magento\Framework\View\Asset\Repository @@ -31,9 +33,9 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase private $designMock; /** - * @var \Magento\Framework\View\Design\Theme\ListInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ThemeProviderInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $listMock; + private $themeProvider; /** * @var \Magento\Framework\View\Asset\Source|\PHPUnit_Framework_MockObject_MockObject @@ -76,9 +78,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase $this->designMock = $this->getMockBuilder(\Magento\Framework\View\DesignInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->listMock = $this->getMockBuilder(\Magento\Framework\View\Design\Theme\ListInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $this->themeProvider = $this->getMock(ThemeProviderInterface::class); $this->sourceMock = $this->getMockBuilder(\Magento\Framework\View\Asset\Source::class) ->disableOriginalConstructor() ->getMock(); @@ -103,17 +103,17 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); - $this->repository = new Repository( - $this->urlMock, - $this->designMock, - $this->listMock, - $this->sourceMock, - $this->httpMock, - $this->fileFactoryMock, - $this->fallbackFactoryMock, - $this->contextFactoryMock, - $this->remoteFactoryMock - ); + $this->repository = (new ObjectManager($this))->getObject(Repository::class, [ + 'baseUrl' => $this->urlMock, + 'design' => $this->designMock, + 'themeProvider' => $this->themeProvider, + 'assetSource' => $this->sourceMock, + 'request' => $this->httpMock, + 'fileFactory' => $this->fileFactoryMock, + 'fallbackContextFactory' => $this->fallbackFactoryMock, + 'contextFactory' => $this->contextFactoryMock, + 'remoteFactory' => $this->remoteFactoryMock + ]); } /** @@ -124,7 +124,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase public function testUpdateDesignParamsWrongTheme() { $params = ['area' => 'area', 'theme' => 'nonexistent_theme']; - $this->listMock->expects($this->once()) + $this->themeProvider->expects($this->once()) ->method('getThemeByFullPath') ->with('area/nonexistent_theme') ->will($this->returnValue(null)); @@ -139,7 +139,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase */ public function testUpdateDesignParams($params, $result) { - $this->listMock + $this->themeProvider ->expects($this->any()) ->method('getThemeByFullPath') ->willReturn('ThemeID'); @@ -169,7 +169,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase */ public function testCreateAsset() { - $this->listMock + $this->themeProvider ->expects($this->any()) ->method('getThemeByFullPath') ->willReturnArgument(0); @@ -231,7 +231,7 @@ class RepositoryTest extends \PHPUnit_Framework_TestCase 'locale' => 'locale' ] ); - $this->listMock + $this->themeProvider ->expects($this->any()) ->method('getThemeByFullPath') ->willReturnArgument(0); diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php index 66e33e4fe31401c4f874822551acfb85afb36b01..2a704650aa9dd6618f3925d197d4b9e1c732ade6 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/SourceTest.php @@ -10,9 +10,11 @@ namespace Magento\Framework\View\Test\Unit\Asset; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem\DriverPool; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\View\Asset\PreProcessor\ChainFactoryInterface; use Magento\Framework\View\Asset\PreProcessor\Chain; use Magento\Framework\View\Asset\Source; +use Magento\Framework\View\Design\Theme\ThemeProviderInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -76,16 +78,16 @@ class SourceTest extends \PHPUnit_Framework_TestCase protected function setUp() { - $this->preProcessorPool = $this->getMock( + $this->preProcessorPool = $this->getMock( \Magento\Framework\View\Asset\PreProcessor\Pool::class, [], [], '', false ); - $this->viewFileResolution = $this->getMock( + $this->viewFileResolution = $this->getMock( \Magento\Framework\View\Design\FileResolution\Fallback\StaticFile::class, [], [], '', false ); $this->theme = $this->getMockForAbstractClass(\Magento\Framework\View\Design\ThemeInterface::class); /** @var \Magento\Framework\App\Config\ScopeConfigInterface $config */ - $this->chainFactory = $this->getMockBuilder( + $this->chainFactory = $this->getMockBuilder( \Magento\Framework\View\Asset\PreProcessor\ChainFactoryInterface::class) ->getMock(); $this->chain = $this->getMockBuilder(\Magento\Framework\View\Asset\PreProcessor\Chain::class) @@ -96,30 +98,30 @@ class SourceTest extends \PHPUnit_Framework_TestCase ->method('create') ->willReturn($this->chain); - $themeList = $this->getMockForAbstractClass(\Magento\Framework\View\Design\Theme\ListInterface::class); - $themeList->expects($this->any()) + $themeProvider = $this->getMock(ThemeProviderInterface::class); + $themeProvider->expects($this->any()) ->method('getThemeByFullPath') ->with('frontend/magento_theme') ->willReturn($this->theme); - $this->readFactory = $this->getMock( - \Magento\Framework\Filesystem\Directory\ReadFactory::class, - [], - [], - '', - false + $this->readFactory = $this->getMock( + \Magento\Framework\Filesystem\Directory\ReadFactory::class, + [], + [], + '', + false ); $this->initFilesystem(); - $this->object = new Source( - $this->filesystem, - $this->readFactory, - $this->preProcessorPool, - $this->viewFileResolution, - $themeList, - $this->chainFactory - ); + $this->object = (new ObjectManager($this))->getObject(Source::class, [ + 'filesystem' => $this->filesystem, + 'readFactory' => $this->readFactory, + 'preProcessorPool' => $this->preProcessorPool, + 'fallback' => $this->viewFileResolution, + 'themeProvider' => $themeProvider, + 'chainFactory' => $this->chainFactory + ]); } /** @@ -229,11 +231,11 @@ class SourceTest extends \PHPUnit_Framework_TestCase protected function initFilesystem() { $this->filesystem = $this->getMock(\Magento\Framework\Filesystem::class, [], [], '', false); - $this->rootDirRead = $this->getMockForAbstractClass( - \Magento\Framework\Filesystem\Directory\ReadInterface::class + $this->rootDirRead = $this->getMockForAbstractClass( + \Magento\Framework\Filesystem\Directory\ReadInterface::class ); - $this->staticDirRead = $this->getMockForAbstractClass( - \Magento\Framework\Filesystem\Directory\ReadInterface::class + $this->staticDirRead = $this->getMockForAbstractClass( + \Magento\Framework\Filesystem\Directory\ReadInterface::class ); $this->varDir = $this->getMockForAbstractClass(\Magento\Framework\Filesystem\Directory\WriteInterface::class);