├── Console └── Command │ ├── PatchApply.php │ └── PatchDelete.php ├── Model └── PatchManagement.php ├── README.md ├── composer.json ├── etc ├── di.xml └── module.xml └── registration.php /Console/Command/PatchApply.php: -------------------------------------------------------------------------------- 1 | patchManagement = $patchManagement; 40 | $this->moduleList = $moduleList; 41 | } 42 | 43 | protected function configure() 44 | { 45 | $this->setName('j:patch:apply') 46 | ->setDescription(__('Apply patches by module name(s) or class name(s)')) 47 | ->setDefinition([ 48 | new InputOption('module', 'M', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, __('The affected modules')), 49 | new InputOption('className', 'C', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, __('The patch class names')) 50 | ]); 51 | } 52 | 53 | /** 54 | * @param InputInterface $input 55 | * @param OutputInterface $output 56 | * @return int 57 | * @throws NotFoundException 58 | * @throws Exception 59 | */ 60 | protected function execute(InputInterface $input, OutputInterface $output) 61 | { 62 | $classNames = $input->getOption('className'); 63 | $modules = $input->getOption('module'); 64 | if (count($classNames)) { 65 | foreach ($classNames as $className) { 66 | $output->writeln(__('Processing patch class %1', $className)); 67 | $this->patchManagement->applyByClassName($className); 68 | } 69 | } else { 70 | if (!count($modules)) { 71 | $modules = array_column($this->moduleList->getAll(), 'name'); 72 | } 73 | 74 | foreach ($modules as $module) { 75 | $output->writeln(__('Processing module %1', $module)); 76 | $this->patchManagement->applyByModule($module); 77 | } 78 | } 79 | $output->writeln('Patches applied'); 80 | return \Magento\Framework\Console\Cli::RETURN_SUCCESS; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Console/Command/PatchDelete.php: -------------------------------------------------------------------------------- 1 | patchManagement = $patchManagement; 29 | } 30 | 31 | protected function configure() 32 | { 33 | $this->setName('j:patch:delete') 34 | ->setDescription(__('Delete patches by its class name(s)')) 35 | ->setDefinition([ 36 | new InputOption('className', 'C', InputOption::VALUE_REQUIRED|InputOption::VALUE_IS_ARRAY, __('The patch class names')) 37 | ]); 38 | } 39 | 40 | protected function execute(InputInterface $input, OutputInterface $output) 41 | { 42 | $classNames = $input->getOption('className'); 43 | foreach ($classNames as $className) { 44 | $output->writeln(__('Deleting patch %1', $className)); 45 | $this->patchManagement->deletePatchByClassName($className); 46 | } 47 | $output->writeln(__('Patches deleted successfully')); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Model/PatchManagement.php: -------------------------------------------------------------------------------- 1 | moduleList = $moduleList; 56 | $this->patchApplier = $patchApplier; 57 | $this->moduleDataSetup = $moduleDataSetup; 58 | $this->objectManager = $objectManager; 59 | $this->patchHistory = $patchHistory; 60 | } 61 | 62 | /** 63 | * @param string $module 64 | * @return void 65 | * @throws NotFoundException 66 | * @throws Exception 67 | */ 68 | public function applyByModule($module) 69 | { 70 | if (!$this->isModuleExists($module)) { 71 | throw new NotFoundException(__('Module with name %1 does not exist', $module)); 72 | } 73 | $this->patchApplier->applyDataPatch($module); 74 | } 75 | 76 | public function applyByClassName($className) 77 | { 78 | $dataPatch = $this->objectManager->create( 79 | '\\' . $className, 80 | ['moduleDataSetup' => $this->moduleDataSetup] 81 | ); 82 | if (!$dataPatch instanceof DataPatchInterface) { 83 | throw new SetupException( 84 | __("Patch %1 should implement DataPatchInterface", [get_class($dataPatch)]) 85 | ); 86 | } 87 | if ($dataPatch instanceof NonTransactionableInterface) { 88 | $dataPatch->apply(); 89 | $this->patchHistory->fixPatch(get_class($dataPatch)); 90 | } else { 91 | try { 92 | $this->moduleDataSetup->getConnection()->beginTransaction(); 93 | $dataPatch->apply(); 94 | $this->patchHistory->fixPatch(get_class($dataPatch)); 95 | foreach ($dataPatch->getAliases() as $patchAlias) { 96 | $this->patchHistory->fixPatch($patchAlias); 97 | } 98 | $this->moduleDataSetup->getConnection()->commit(); 99 | } catch (\Exception $e) { 100 | $this->moduleDataSetup->getConnection()->rollBack(); 101 | throw new SetupException( 102 | new Phrase( 103 | 'Unable to apply data patch %1. Original exception message: %2', 104 | [ 105 | get_class($dataPatch), 106 | $e->getMessage() 107 | ] 108 | ), 109 | $e 110 | ); 111 | } 112 | } 113 | } 114 | 115 | public function deletePatchByClassName($className) 116 | { 117 | $this->patchHistory->revertPatchFromHistory($className); 118 | } 119 | 120 | 121 | /** 122 | * @param $moduleName 123 | * @return bool 124 | */ 125 | private function isModuleExists($moduleName) 126 | { 127 | return $this->moduleList->getOne($moduleName) !== null; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Patch Manager 2 | 3 | ## Installation 4 | `composer require jayanka/patch-manager` 5 | 6 | ## This magento extension provides a set of `bin/magento` CLI commands to maintain data patches 7 | 8 | ### Commands 9 | 1. `bin/magento j:patch:apply` - Apply patches by module name(s) or class name(s) 10 | Examples 11 | - `bin/magento j:patch:apply --module Magento_Catalog --module Magento_Sales` 12 | - `bin/magento j:patch:apply --className Magento\\Catalog\\Setup\\Patch\\Data\\InstallDefaultCategories` 13 | - `bin/magento j:patch:apply # Apply patches for all modules` 14 | 15 | 2. `bin/magento j:patch:delete` - Delete patches by its class name(s) 16 | Examples 17 | - `bin/magento j:patch:delete --className Magento\\Catalog\\Setup\\Patch\\Data\\InstallDefaultCategories` 18 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jayanka/patch-manager", 3 | "description": "A magento extension to maintain data patches", 4 | "type": "magento2-module", 5 | "license": "proprietary", 6 | "version": "1.0.0", 7 | "require": {}, 8 | "authors": [ 9 | { 10 | "email": "jayankaghosh@gmail.com", 11 | "name": "Jayanka Ghosh" 12 | } 13 | ], 14 | "minimum-stability": "dev", 15 | "autoload": { 16 | "psr-4": { 17 | "Jayanka\\PatchManager\\": "" 18 | }, 19 | "files": [ 20 | "registration.php" 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /etc/di.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Jayanka\PatchManager\Console\Command\PatchApply 7 | Jayanka\PatchManager\Console\Command\PatchDelete 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /etc/module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /registration.php: -------------------------------------------------------------------------------- 1 |