├── 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 |