diff --git a/bin/magento b/bin/magento index 1cb700def7fa7f3faf073dbd7bb0300892b06332..3fa9221763cf5fca0a09eb4bc6f43d3a5cb22a42 100755 --- a/bin/magento +++ b/bin/magento @@ -28,5 +28,5 @@ try { echo "\n\n"; $e = $e->getPrevious(); } - exit(Cli::RETURN_FAILURE); + exit(Magento\Framework\Console\Cli::RETURN_FAILURE); } diff --git a/lib/internal/Magento/Framework/Console/Cli.php b/lib/internal/Magento/Framework/Console/Cli.php index 5a89d0f6b60571680068463df5b78f85951c809c..30242b488b14c3ce838d957a56c431828388bb2a 100644 --- a/lib/internal/Magento/Framework/Console/Cli.php +++ b/lib/internal/Magento/Framework/Console/Cli.php @@ -5,102 +5,105 @@ */ namespace Magento\Framework\Console; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\State; +use Magento\Framework\Code\Generator\Io; use Magento\Framework\Composer\ComposerJsonFinder; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Output\ConsoleOutput; -use Symfony\Component\Console\Input\ArgvInput; -use Symfony\Component\Console\Application as SymfonyApplication; +use Magento\Framework\Exception\FileSystemException; +use Magento\Setup\Model\ObjectManagerProvider; +use Symfony\Component\Console; use Magento\Framework\App\Bootstrap; use Magento\Framework\Filesystem\Driver\File; use Magento\Framework\Shell\ComplexParameter; use Magento\Setup\Console\CompilerPreparation; -use \Magento\Framework\App\ProductMetadata; +use Magento\Framework\App\ProductMetadata; +use Magento\Framework\ObjectManagerInterface; +use Zend\ServiceManager\ServiceManager; /** - * Magento 2 CLI Application. This is the hood for all command line tools supported by Magento + * Magento 2 CLI Application. + * This is the hood for all command line tools supported by Magento. * * {@inheritdoc} * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Cli extends SymfonyApplication +class Cli extends Console\Application { /** - * Name of input option + * Name of input option. */ const INPUT_KEY_BOOTSTRAP = 'bootstrap'; - /** - * Cli exit codes + /**#@+ + * Cli exit codes. */ const RETURN_SUCCESS = 0; const RETURN_FAILURE = 1; + /**#@-*/ - /** @var \Zend\ServiceManager\ServiceManager */ + /** + * Service Manager. + * + * @var ServiceManager + */ private $serviceManager; /** - * Initialization exception + * Initialization exception. * * @var \Exception */ private $initException; /** - * @param string $name application name - * @param string $version application version - * @SuppressWarnings(PHPMD.ExitExpression) + * Object Manager. + * + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @param string $name the application name + * @param string $version the application version */ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') { $this->serviceManager = \Zend\Mvc\Application::init(require BP . '/setup/config/application.config.php') ->getServiceManager(); - $generationDirectoryAccess = new GenerationDirectoryAccess($this->serviceManager); - if (!$generationDirectoryAccess->check()) { - $output = new ConsoleOutput(); - $output->writeln( - '<error>Command line user does not have read and write permissions on var/generation directory. Please' - . ' address this issue before using Magento command line.</error>' - ); - exit(0); - } - /** - * Temporary workaround until the compiler is able to clear the generation directory - * @todo remove after MAGETWO-44493 resolved - */ - if (class_exists(CompilerPreparation::class)) { - $compilerPreparation = new CompilerPreparation($this->serviceManager, new ArgvInput(), new File()); - $compilerPreparation->handleCompilerEnvironment(); - } + + $this->assertCompilerPreparation(); + $this->initObjectManager(); + $this->assertGenerationPermissions(); if ($version == 'UNKNOWN') { - $directoryList = new DirectoryList(BP); + $directoryList = new DirectoryList(BP); $composerJsonFinder = new ComposerJsonFinder($directoryList); - $productMetadata = new ProductMetadata($composerJsonFinder); + $productMetadata = new ProductMetadata($composerJsonFinder); $version = $productMetadata->getVersion(); } + parent::__construct($name, $version); } /** - * Process an error happened during initialization of commands, if any + * {@inheritdoc} * - * @param InputInterface $input - * @param OutputInterface $output - * @return int - * @throws \Exception + * @throws \Exception the exception in case of unexpected error */ - public function doRun(InputInterface $input, OutputInterface $output) + public function doRun(Console\Input\InputInterface $input, Console\Output\OutputInterface $output) { $exitCode = parent::doRun($input, $output); + if ($this->initException) { $output->writeln( "<error>We're sorry, an error occurred. Try clearing the cache and code generation directories. " . "By default, they are: var/cache, var/di, var/generation, and var/page_cache.</error>" ); + throw $this->initException; } + return $exitCode; } @@ -113,46 +116,141 @@ class Cli extends SymfonyApplication } /** - * Gets application commands + * Gets application commands. * - * @return array + * @return array a list of available application commands */ protected function getApplicationCommands() { $commands = []; try { - $bootstrapParam = new ComplexParameter(self::INPUT_KEY_BOOTSTRAP); - $params = $bootstrapParam->mergeFromArgv($_SERVER, $_SERVER); - $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null; - $bootstrap = Bootstrap::create(BP, $params); - $objectManager = $bootstrap->getObjectManager(); - /** @var \Magento\Setup\Model\ObjectManagerProvider $omProvider */ - $omProvider = $this->serviceManager->get(\Magento\Setup\Model\ObjectManagerProvider::class); - $omProvider->setObjectManager($objectManager); - if (class_exists(\Magento\Setup\Console\CommandList::class)) { $setupCommandList = new \Magento\Setup\Console\CommandList($this->serviceManager); $commands = array_merge($commands, $setupCommandList->getCommands()); } - if ($objectManager->get(\Magento\Framework\App\DeploymentConfig::class)->isAvailable()) { - /** @var \Magento\Framework\Console\CommandListInterface */ - $commandList = $objectManager->create(\Magento\Framework\Console\CommandListInterface::class); + if ($this->objectManager->get(DeploymentConfig::class)->isAvailable()) { + /** @var CommandListInterface */ + $commandList = $this->objectManager->create(CommandListInterface::class); $commands = array_merge($commands, $commandList->getCommands()); } - $commands = array_merge($commands, $this->getVendorCommands($objectManager)); + $commands = array_merge( + $commands, + $this->getVendorCommands($this->objectManager) + ); } catch (\Exception $e) { $this->initException = $e; } + return $commands; } /** - * Gets vendor commands + * Object Manager initialization. + * + * @return void + * @SuppressWarnings(PHPMD.ExitExpression) + */ + private function initObjectManager() + { + try { + $params = (new ComplexParameter(self::INPUT_KEY_BOOTSTRAP))->mergeFromArgv($_SERVER, $_SERVER); + $params[Bootstrap::PARAM_REQUIRE_MAINTENANCE] = null; + + $this->objectManager = Bootstrap::create(BP, $params)->getObjectManager(); + + /** @var ObjectManagerProvider $omProvider */ + $omProvider = $this->serviceManager->get(ObjectManagerProvider::class); + $omProvider->setObjectManager($this->objectManager); + } catch (FileSystemException $exception) { + $this->writeGenerationDirectoryReadError(); + + exit(static::RETURN_FAILURE); + } + } + + /** + * Checks whether generation directory is read-only. + * Depends on the current mode: + * production - application will proceed + * default - application will be terminated + * developer - application will be terminated + * + * @return void + * @SuppressWarnings(PHPMD.ExitExpression) + */ + private function assertGenerationPermissions() + { + /** @var GenerationDirectoryAccess $generationDirectoryAccess */ + $generationDirectoryAccess = $this->objectManager->create( + GenerationDirectoryAccess::class, + ['serviceManager' => $this->serviceManager] + ); + /** @var State $state */ + $state = $this->objectManager->get(State::class); + + if ( + $state->getMode() !== State::MODE_PRODUCTION + && !$generationDirectoryAccess->check() + ) { + $this->writeGenerationDirectoryReadError(); + + exit(static::RETURN_FAILURE); + } + } + + /** + * Checks whether compiler is being prepared. + * + * @return void + * @SuppressWarnings(PHPMD.ExitExpression) + */ + private function assertCompilerPreparation() + { + /** + * Temporary workaround until the compiler is able to clear the generation directory + * @todo remove after MAGETWO-44493 resolved + */ + if (class_exists(CompilerPreparation::class)) { + $compilerPreparation = new CompilerPreparation( + $this->serviceManager, + new Console\Input\ArgvInput(), + new File() + ); + + try { + $compilerPreparation->handleCompilerEnvironment(); + } catch (FileSystemException $e) { + $this->writeGenerationDirectoryReadError(); + + exit(static::RETURN_FAILURE); + } + } + } + + /** + * Writes read error to console. * - * @param \Magento\Framework\ObjectManagerInterface $objectManager - * @return array + * @return void + */ + private function writeGenerationDirectoryReadError() + { + $output = new Console\Output\ConsoleOutput(); + $output->writeln( + '<error>' + . 'Command line user does not have read and write permissions on ' . Io::DEFAULT_DIRECTORY . ' directory. ' + . 'Please address this issue before using Magento command line.' + . '</error>' + ); + } + + /** + * Retrieves vendor commands. + * + * @param ObjectManagerInterface $objectManager the object manager + * + * @return array an array with external commands */ protected function getVendorCommands($objectManager) { @@ -165,6 +263,7 @@ class Cli extends SymfonyApplication ); } } + return $commands; } } diff --git a/setup/src/Magento/Setup/Console/CompilerPreparation.php b/setup/src/Magento/Setup/Console/CompilerPreparation.php index df32f9b018a0f2bbb8103ae9556c8863d17ff286..e986749ee6efb04a26aa73ddbd59fb933cc003a2 100644 --- a/setup/src/Magento/Setup/Console/CompilerPreparation.php +++ b/setup/src/Magento/Setup/Console/CompilerPreparation.php @@ -1,49 +1,62 @@ <?php -/*** +/** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Setup\Console; - use Magento\Framework\App\Bootstrap; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Console\GenerationDirectoryAccess; +use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Filesystem\Driver\File; +use Magento\Framework\Phrase; use Magento\Setup\Console\Command\DiCompileCommand; use Magento\Setup\Mvc\Bootstrap\InitParamListener; use Symfony\Component\Console\Input\ArgvInput; +use Zend\ServiceManager\ServiceManager; class CompilerPreparation { - /** @var \Zend\ServiceManager\ServiceManager */ + /** + * @var ServiceManager + */ private $serviceManager; - /** @var ArgvInput */ + /** + * @var ArgvInput + */ private $input; - /** @var File */ + /** + * @var File + */ private $filesystemDriver; /** - * @param \Zend\ServiceManager\ServiceManager $serviceManager + * @var GenerationDirectoryAccess + */ + private $generationDirectoryAccess; + + /** + * @param ServiceManager $serviceManager * @param ArgvInput $input * @param File $filesystemDriver */ public function __construct( - \Zend\ServiceManager\ServiceManager $serviceManager, - \Symfony\Component\Console\Input\ArgvInput $input, - \Magento\Framework\Filesystem\Driver\File $filesystemDriver + ServiceManager $serviceManager, + ArgvInput $input, + File $filesystemDriver ) { - $this->serviceManager = $serviceManager; - $this->input = $input; + $this->serviceManager = $serviceManager; + $this->input = $input; $this->filesystemDriver = $filesystemDriver; } /** - * Determine whether a CLI command is for compilation, and if so, clear the directory + * Determine whether a CLI command is for compilation, and if so, clear the directory. * - * @throws \Magento\Framework\Exception\FileSystemException + * @throws FileSystemException if generation directory is read-only * @return void */ public function handleCompilerEnvironment() @@ -63,10 +76,30 @@ class CompilerPreparation $compileDirList[] = $directoryList->getPath(DirectoryList::GENERATION); $compileDirList[] = $directoryList->getPath(DirectoryList::DI); + if (!$this->getGenerationDirectoryAccess()->check()) { + throw new FileSystemException( + new Phrase('Generation directory can not be written.') + ); + } + foreach ($compileDirList as $compileDir) { if ($this->filesystemDriver->isExists($compileDir)) { $this->filesystemDriver->deleteDirectory($compileDir); } } } + + /** + * Retrieves generation directory access checker. + * + * @return GenerationDirectoryAccess the generation directory access checker + */ + private function getGenerationDirectoryAccess() + { + if (null === $this->generationDirectoryAccess) { + $this->generationDirectoryAccess = new GenerationDirectoryAccess($this->serviceManager); + } + + return $this->generationDirectoryAccess; + } } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php b/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php index 813cb5dccc98f7ac7d34b0fdc28ff7769070bbaf..93118211062eddac065cdfe4b75bcb8f310421f3 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/CompilerPreparationTest.php @@ -1,48 +1,69 @@ <?php -/*** +/** * Copyright © 2013-2017 Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Setup\Test\Unit\Console; - +use Magento\Framework\Console\GenerationDirectoryAccess; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Setup\Console\Command\DiCompileCommand; use Magento\Setup\Mvc\Bootstrap\InitParamListener; +use Magento\Framework\Filesystem\Driver\File; use Symfony\Component\Console\Input\ArgvInput; +use Zend\ServiceManager\ServiceManager; +use Magento\Setup\Console\CompilerPreparation; +use PHPUnit_Framework_MockObject_MockObject as Mock; class CompilerPreparationTest extends \PHPUnit_Framework_TestCase { - /** @var \Magento\Setup\Console\CompilerPreparation */ + /** + * @var CompilerPreparation|Mock + */ private $model; - /** @var \Zend\ServiceManager\ServiceManager | \PHPUnit_Framework_MockObject_MockObject */ + /** + * @var ServiceManager|Mock + */ private $serviceManagerMock; - /** @var \Symfony\Component\Console\Input\ArgvInput | \PHPUnit_Framework_MockObject_MockObject */ + /** + * @var ArgvInput|Mock + */ private $inputMock; - /** @var \Magento\Framework\Filesystem\Driver\File | \PHPUnit_Framework_MockObject_MockObject */ + /** + * @var File|Mock + */ private $filesystemDriverMock; + /** + * @var GenerationDirectoryAccess|Mock + */ + private $generationDirectoryAccessMock; + public function setUp() { - $this->serviceManagerMock = $this->getMockBuilder(\Zend\ServiceManager\ServiceManager::class) + $this->serviceManagerMock = $this->getMockBuilder(ServiceManager::class) + ->disableOriginalConstructor() + ->getMock(); + $this->inputMock = $this->getMockBuilder(ArgvInput::class) ->disableOriginalConstructor() ->getMock(); - $this->inputMock = $this->getMockBuilder(\Symfony\Component\Console\Input\ArgvInput::class) + $this->filesystemDriverMock = $this->getMockBuilder(File::class) ->disableOriginalConstructor() ->getMock(); - $this->filesystemDriverMock = $this->getMockBuilder(\Magento\Framework\Filesystem\Driver\File::class) + $this->generationDirectoryAccessMock = $this->getMockBuilder(GenerationDirectoryAccess::class) ->disableOriginalConstructor() ->getMock(); + $this->model = (new ObjectManager($this))->getObject( - \Magento\Setup\Console\CompilerPreparation::class, + CompilerPreparation::class, [ 'serviceManager' => $this->serviceManagerMock, 'input' => $this->inputMock, - 'filesystemDriver' => $this->filesystemDriverMock + 'filesystemDriver' => $this->filesystemDriverMock, + 'generationDirectoryAccess' => $this->generationDirectoryAccessMock, ] ); } @@ -56,21 +77,31 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase */ public function testClearGenerationDirWhenNeeded($commandName, $isCompileCommand, $isHelpOption, $dirExists = false) { - $this->inputMock->expects($this->once())->method('getFirstArgument')->willReturn($commandName); + $this->inputMock->expects($this->once()) + ->method('getFirstArgument') + ->willReturn($commandName); $this->inputMock->expects($this->atLeastOnce()) ->method('hasParameterOption') - ->with( - $this->logicalOr('--help', '-h') - )->willReturn($isHelpOption); + ->with($this->logicalOr('--help', '-h')) + ->willReturn($isHelpOption); + if ($isCompileCommand && !$isHelpOption) { $this->filesystemDriverMock->expects($this->exactly(2)) ->method('isExists') ->willReturn($dirExists); - $this->filesystemDriverMock->expects($this->exactly(((int)$dirExists) * 2))->method('deleteDirectory'); + $this->filesystemDriverMock->expects($this->exactly(((int)$dirExists) * 2)) + ->method('deleteDirectory'); } else { - $this->filesystemDriverMock->expects($this->never())->method('isExists'); - $this->filesystemDriverMock->expects($this->never())->method('deleteDirectory'); + $this->filesystemDriverMock->expects($this->never()) + ->method('isExists'); + $this->filesystemDriverMock->expects($this->never()) + ->method('deleteDirectory'); } + + $this->generationDirectoryAccessMock->expects($this->any()) + ->method('check') + ->willReturn(true); + $this->model->handleCompilerEnvironment(); } @@ -108,10 +139,6 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase $customGenerationDirectory = '/custom/generated/code/directory'; $defaultDiDirectory = '/custom/di/directory'; $mageInitParams = ['MAGE_DIRS' => ['generation' => ['path' => $customGenerationDirectory]]]; - - $this->inputMock->expects($this->once()) - ->method('getFirstArgument') - ->willReturn(DiCompileCommand::NAME); $dirValueMap = [ [ $customGenerationDirectory, @@ -122,16 +149,24 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase true ] ]; - // Filesystem mock - $this->filesystemDriverMock->expects($this->exactly(2))->method('isExists')->willReturn(true); + + $this->inputMock->expects($this->once()) + ->method('getFirstArgument') + ->willReturn(DiCompileCommand::NAME); + $this->filesystemDriverMock->expects($this->exactly(2)) + ->method('isExists') + ->willReturn(true); $this->filesystemDriverMock->expects($this->exactly(2)) ->method('deleteDirectory') - ->will($this->returnValueMap($dirValueMap)); - + ->willReturnMap($dirValueMap); $this->serviceManagerMock->expects($this->once()) ->method('get') ->with(InitParamListener::BOOTSTRAP_PARAM) ->willReturn($mageInitParams); + $this->generationDirectoryAccessMock->expects($this->once()) + ->method('check') + ->willReturn(true); + $this->model->handleCompilerEnvironment(); } @@ -139,10 +174,6 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase { $customGenerationDirectory = '/custom/generated/code/directory'; $customDiDirectory = '/custom/di/directory'; - - $this->inputMock->expects($this->once()) - ->method('getFirstArgument') - ->willReturn(DiCompileCommand::NAME); $dirResultMap = [ [ $this->logicalNot($this->equalTo($customGenerationDirectory)), @@ -154,10 +185,18 @@ class CompilerPreparationTest extends \PHPUnit_Framework_TestCase ] ]; - $this->filesystemDriverMock->expects($this->exactly(2))->method('isExists')->willReturn(true); + $this->inputMock->expects($this->once()) + ->method('getFirstArgument') + ->willReturn(DiCompileCommand::NAME); + $this->filesystemDriverMock->expects($this->exactly(2)) + ->method('isExists') + ->willReturn(true); $this->filesystemDriverMock->expects($this->exactly(2)) ->method('deleteDirectory') - ->will($this->returnValueMap($dirResultMap)); + ->willReturnMap($dirResultMap); + $this->generationDirectoryAccessMock->expects($this->once()) + ->method('check') + ->willReturn(true); $this->model->handleCompilerEnvironment(); }