From e9f7d0a70dacf5599e34ad8f6bc6b6043227d504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 20 Feb 2017 10:03:12 +0100 Subject: [PATCH 1/2] Adding the notion of type --- composer.json | 4 +- ...PatchInstaller.php => PatchInstaller2.php} | 16 +++- src/Mouf/Utils/Patcher/PatchInterface.php | 34 ++++---- src/Mouf/Utils/Patcher/PatchService.php | 82 +++++++++++++------ src/Mouf/Utils/Patcher/PatchType.php | 52 ++++++++++++ 5 files changed, 143 insertions(+), 45 deletions(-) rename src/Mouf/Utils/Patcher/{PatchInstaller.php => PatchInstaller2.php} (67%) create mode 100644 src/Mouf/Utils/Patcher/PatchType.php diff --git a/composer.json b/composer.json index a45f699..dc35bf5 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ } ], "require": { - "php": ">=5.5.0", + "php": ">=7.0", "mouf/mouf-validators-interface": "~2.0", "mouf/utils.console": "~1.0" }, @@ -26,7 +26,7 @@ "mouf": { "install" : [{ "type" : "class", - "class" : "Mouf\\Utils\\Patcher\\PatchInstaller", + "class" : "Mouf\\Utils\\Patcher\\PatchInstaller2", "description": "Create the patchService instance." } ], diff --git a/src/Mouf/Utils/Patcher/PatchInstaller.php b/src/Mouf/Utils/Patcher/PatchInstaller2.php similarity index 67% rename from src/Mouf/Utils/Patcher/PatchInstaller.php rename to src/Mouf/Utils/Patcher/PatchInstaller2.php index 2f81396..f383f5f 100644 --- a/src/Mouf/Utils/Patcher/PatchInstaller.php +++ b/src/Mouf/Utils/Patcher/PatchInstaller2.php @@ -16,7 +16,7 @@ use Mouf\Utils\Patcher\Commands\RevertPatchCommand; use Mouf\Utils\Patcher\Commands\SkipPatchCommand; -class PatchInstaller implements PackageInstallerInterface +class PatchInstaller2 implements PackageInstallerInterface { /** * (non-PHPdoc) @@ -27,7 +27,19 @@ class PatchInstaller implements PackageInstallerInterface public static function install(MoufManager $moufManager) { // Let's create the instance. - $patchService = InstallUtils::getOrCreateInstance('patchService', 'Mouf\\Utils\\Patcher\\PatchService', $moufManager); + $patchDefaultType = InstallUtils::getOrCreateInstance('patch.default_type', PatchType::class, $moufManager); + $patchDefaultType->getConstructorArgumentProperty('name')->setValue(''); + $patchDefaultType->getConstructorArgumentProperty('description')->setValue('Patches that should be always applied should have this type. Typically, use this type for DDL changes or reference data insertion.'); + + $patchTestDataType = InstallUtils::getOrCreateInstance('patch.testdata_type', PatchType::class, $moufManager); + $patchTestDataType->getConstructorArgumentProperty('name')->setValue('test_data'); + $patchTestDataType->getConstructorArgumentProperty('description')->setValue('Use this type to mark patches that contain test data that should only be used in staging environment.'); + + $patchService = InstallUtils::getOrCreateInstance('patchService', PatchService::class, $moufManager); + + if (empty($patchService->getConstructorArgumentProperty('types')->getValue())) { + $patchService->getConstructorArgumentProperty('types')->setValue([ $patchDefaultType, $patchTestDataType ]); + } $consoleUtils = new ConsoleUtils($moufManager); diff --git a/src/Mouf/Utils/Patcher/PatchInterface.php b/src/Mouf/Utils/Patcher/PatchInterface.php index e9cce49..7ba7226 100644 --- a/src/Mouf/Utils/Patcher/PatchInterface.php +++ b/src/Mouf/Utils/Patcher/PatchInterface.php @@ -26,33 +26,33 @@ */ interface PatchInterface { - const STATUS_AWAITING = "awaiting"; - const STATUS_APPLIED = "applied"; - const STATUS_SKIPPED = "skipped"; - const STATUS_ERROR = "error"; + const STATUS_AWAITING = 'awaiting'; + const STATUS_APPLIED = 'applied'; + const STATUS_SKIPPED = 'skipped'; + const STATUS_ERROR = 'error'; /** * Applies the patch. */ - function apply(); + public function apply(); /** * Skips the patch (sets its status to "skipped"). */ - function skip(); + public function skip(); /** * Reverts (cancels) the patch. * Note: patchs do not have to provide a "revert" feature (see canRevert method). */ - function revert(); + public function revert(); /** * Returns true if this patch can be canceled, false otherwise. * * @return boolean */ - function canRevert(); + public function canRevert(); /** * Returns the status of this patch. @@ -65,28 +65,28 @@ function canRevert(); * * @return string */ - function getStatus(); + public function getStatus(); /** * Returns a unique name for this patch. * * @return string */ - function getUniqueName(); + public function getUniqueName(); /** * Returns a short description of the patch. * * @return string */ - function getDescription(); + public function getDescription(); /** * Returns the error message of the last action performed, or null if last action was successful. * * @return string */ - function getLastErrorMessage(); + public function getLastErrorMessage(); /** * Returns the URL that can be used to edit this patch. @@ -94,6 +94,12 @@ function getLastErrorMessage(); * * @return string */ - function getEditUrl(); -} + public function getEditUrl(); + /** + * Returns the type of the patch. + * + * @return PatchType + */ + public function getPatchType() : PatchType; +} diff --git a/src/Mouf/Utils/Patcher/PatchService.php b/src/Mouf/Utils/Patcher/PatchService.php index dc638a0..3057554 100644 --- a/src/Mouf/Utils/Patcher/PatchService.php +++ b/src/Mouf/Utils/Patcher/PatchService.php @@ -1,6 +1,6 @@ types = $types; + } + + /** * The list of patches declared for this application. * @param PatchInterface[] $patchs * @return PatchService @@ -47,10 +65,17 @@ public function setPatchs(array $patchs) { $this->patchs = $patchs; return $this; } - - const IFEXISTS_EXCEPTION = "exception"; - const IFEXISTS_IGNORE = "ignore"; - + + /** + * The list of exiting patch types for this application. + * + * @return PatchType[] + */ + public function getTypes(): array + { + return $this->types; + } + /** * Adds this patch to the list of existing patches. * If the patch already exists, an exception is thrown. @@ -64,7 +89,7 @@ public function setPatchs(array $patchs) { */ public function register(PatchInterface $patch, $ifExists = self::IFEXISTS_IGNORE) { if ($this->has($patch->getUniqueName())) { - if ($ifExists == self::IFEXISTS_IGNORE) { + if ($ifExists === self::IFEXISTS_IGNORE) { return $this; } else { throw new PatchException("The patch '".$patch->getUniqueName()."' is already registered."); @@ -81,7 +106,7 @@ public function register(PatchInterface $patch, $ifExists = self::IFEXISTS_IGNOR */ public function has($uniqueName) { foreach ($this->patchs as $patch) { - if ($patch->getUniqueName() == $uniqueName) { + if ($patch->getUniqueName() === $uniqueName) { return true; } } @@ -96,7 +121,7 @@ public function has($uniqueName) { */ public function get($uniqueName) { foreach ($this->patchs as $patch) { - if ($patch->getUniqueName() == $uniqueName) { + if ($patch->getUniqueName() === $uniqueName) { return $patch; } } @@ -108,10 +133,10 @@ public function get($uniqueName) { * * @return int */ - public function getNbAwaitingPatchs() { + public function getNbAwaitingPatchs(): int { $cnt = 0; foreach ($this->patchs as $patch) { - if ($patch->getStatus() == PatchInterface::STATUS_AWAITING) { + if ($patch->getStatus() === PatchInterface::STATUS_AWAITING) { $cnt++; } } @@ -123,10 +148,10 @@ public function getNbAwaitingPatchs() { * * @return int */ - public function getNbPatchsInError() { + public function getNbPatchsInError(): int { $cnt = 0; foreach ($this->patchs as $patch) { - if ($patch->getStatus() == PatchInterface::STATUS_ERROR) { + if ($patch->getStatus() === PatchInterface::STATUS_ERROR) { $cnt++; } } @@ -141,11 +166,11 @@ public function validateInstance() { $nbPatchs = count($this->patchs); $nbAwaitingPatchs = $this->getNbAwaitingPatchs(); - $nbPatchsInError = $this->getNbPatchsInError(); + $nbPatchesInError = $this->getNbPatchsInError(); $instanceName = MoufManager::getMoufManager()->findInstanceName($this); - if ($nbAwaitingPatchs == 0 && $nbPatchsInError == 0) { - if ($nbPatchs == 0) { + if ($nbAwaitingPatchs === 0 && $nbPatchesInError === 0) { + if ($nbPatchs === 0) { return new MoufValidatorResult(MoufValidatorResult::SUCCESS, "Patcher: No patches declared"); } elseif ($nbPatchs == 0) { return new MoufValidatorResult(MoufValidatorResult::SUCCESS, "Patcher: The patch has been successfully applied"); @@ -153,7 +178,7 @@ public function validateInstance() { return new MoufValidatorResult(MoufValidatorResult::SUCCESS, "Patcher: All $nbPatchs patches have been successfully applied"); } } else { - if ($nbPatchsInError == 0) { + if ($nbPatchesInError == 0) { $status = MoufValidatorResult::WARN; } else { $status = MoufValidatorResult::ERROR; @@ -162,12 +187,12 @@ public function validateInstance() { $html = 'Patcher: Apply '; if ($nbAwaitingPatchs != 0) { $html .= $nbAwaitingPatchs." awaiting patch".(($nbAwaitingPatchs != 1)?"es":""); - if ($nbPatchsInError != 0) { + if ($nbPatchesInError != 0) { $html .=" and"; } } - if ($nbPatchsInError != 0) { - $html .=$nbPatchsInError." patch".(($nbPatchsInError != 1)?"es":"")." in error"; + if ($nbPatchesInError != 0) { + $html .=$nbPatchesInError." patch".(($nbPatchesInError != 1)?"es":"")." in error"; } $html .=''; @@ -179,7 +204,7 @@ public function validateInstance() { /** * Returns a PHP array representing the patchs. */ - public function getView() { + public function getView(): array { $view = array(); foreach ($this->patchs as $patch) { $uniqueName = null; @@ -188,6 +213,7 @@ public function getView() { $description = null; $error_message = null; $editUrl = null; + $patchType = null; try { $uniqueName = $patch->getUniqueName(); @@ -196,6 +222,7 @@ public function getView() { $editUrl = $patch->getEditUrl()."&name=".MoufManager::getMoufManager()->findInstanceName($this); $status = $patch->getStatus(); $error_message = $patch->getLastErrorMessage(); + $patchType = $patch->getPatchType()->getName(); } catch (\Exception $e) { $status = PatchInterface::STATUS_ERROR; @@ -208,7 +235,8 @@ public function getView() { "canRevert"=>$canRevert, "description"=>$description, "error_message"=>$error_message, - "edit_url"=>$editUrl + "edit_url"=>$editUrl, + "patch_type"=>$patchType ); $view[] = $patchView; } @@ -219,7 +247,7 @@ public function getView() { * Applies the patch whose unique name is passed in parameter. * @param string $uniqueName */ - public function apply($uniqueName) { + public function apply($uniqueName): void { $patch = $this->get($uniqueName); $patch->apply(); } @@ -228,7 +256,7 @@ public function apply($uniqueName) { * Skips the patch whose unique name is passed in parameter. * @param string $uniqueName */ - public function skip($uniqueName) { + public function skip($uniqueName): void { $patch = $this->get($uniqueName); $patch->skip(); } @@ -238,7 +266,7 @@ public function skip($uniqueName) { * Reverts the patch whose unique name is passed in parameter. * @param string $uniqueName */ - public function revert($uniqueName) { + public function revert($uniqueName): void { $patch = $this->get($uniqueName); $patch->revert(); } diff --git a/src/Mouf/Utils/Patcher/PatchType.php b/src/Mouf/Utils/Patcher/PatchType.php new file mode 100644 index 0000000..6fbb5ac --- /dev/null +++ b/src/Mouf/Utils/Patcher/PatchType.php @@ -0,0 +1,52 @@ +name = $name; + $this->description = $description; + } + + /** + * The name of the patch type. Should not contain special characters or spaces. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * The description of the patch type. + * + * @return string + */ + public function getDescription(): string + { + return $this->description; + } +} From 8b9674a1d854c118b8aa6e8ac2d890a45f830c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Sat, 25 Feb 2017 11:51:26 +0100 Subject: [PATCH 2/2] Adding the notion of patch types to apply patches conditionnally --- README.md | 38 ++++- .../Commands/ApplyAllPatchesCommand.php | 12 +- .../Patcher/Commands/ListPatchesCommand.php | 4 +- .../Patcher/Controllers/PatchController.php | 133 +++++++++++++++--- src/Mouf/Utils/Patcher/PatchInstaller2.php | 1 + src/Mouf/Utils/Patcher/PatchInterface.php | 18 +-- src/Mouf/Utils/Patcher/PatchService.php | 13 ++ src/Mouf/Utils/Patcher/PatchType.php | 25 +++- src/views/applyPatches.php | 15 ++ src/views/patchesList.php | 4 +- 10 files changed, 225 insertions(+), 38 deletions(-) create mode 100644 src/views/applyPatches.php diff --git a/README.md b/README.md index b6e9c60..3ff9c2c 100644 --- a/README.md +++ b/README.md @@ -34,8 +34,8 @@ As you can see, we are installing two packages here. - **mouf/utils.patcher** contains the patch service. The patch service can be used to install any kinds of patches, but does not contain any patches implementation. This is why we need the second packages. - **mouf/database.patcher** adds an easy way to create database patches (the most common use of the patch system). -Using the patch service ------------------------ +Using the patch service (graphical user interface) +-------------------------------------------------- Once the patch service is installed, you will notice there is a new menu in Mouf UI. @@ -55,6 +55,28 @@ If you need a more *fine-tuned* approach, you can **apply** each patch one by on **skip** the patch if you prefer to run it yourself or if you know it has already been applied. Finally, you will notice that some patches can be **reverted**. +Using the patch service (command line interface) +------------------------------------------------ + +You can also apply patches using the Mouf console. This is especially useful for deployments in continuous integration environments or on a production server. + +```sh +$ # Apply all default patches +$ vendor/bin/mouf_console patches:apply-all +$ +$ # Apply all patches from type default AND test_data +$ vendor/bin/mouf_console patches:apply-all --test-data +$ +$ # View a list of all patches +$ vendor/bin/mouf_console patches:list +$ +$ # Apply one specific patch by name +$ vendor/bin/mouf_console patches:apply [patch_name] +$ +$ # Revert one specific patch by name +$ vendor/bin/mouf_console patches:revert [patch_name] +``` + Creating/Editing a database patch --------------------------------- @@ -74,13 +96,23 @@ In this case, you should **skip** the patch (there is no point in applying this - If you haven't applied the patch yet, you can choose to save and **apply** the patch. - Finally, you can also choose to save, but **do not apply** the patch yet. In this case, the patch will be in **Awating** state. -###Advanced options +### Advanced options There are a number of advanced options. These will allow you to: - Choose the file saving the patch (the SQL of the patch is stored in its own file, usually in the **database/up** directory. - Set up a *reverse patch* that can be used to cancel/revert your patch. +### Patch type + +When creating a database patch, you can select a patch "type". + +By default, the package comes with 2 bundled types: + +- *default*: for patches that should always be applied (like patches modifying the database model) +- *test_data*: for patches that should be applied conditionnally based on the environment (you might want test data in your development environment but not in production) + +You can also edit thos patch types or add your own patch types by editing the `patchService` instance in Mouf. [You are a package developer? You want your own package to create/modify tables? See how you can use the patch system for that.](doc/for_packages_developer.md) [Want to learn more about the patch system? Want to learn how to create you own non db-related patches? Have a look at the advanced documentation.](doc/advanced.md) diff --git a/src/Mouf/Utils/Patcher/Commands/ApplyAllPatchesCommand.php b/src/Mouf/Utils/Patcher/Commands/ApplyAllPatchesCommand.php index 4350496..f0c2f5a 100644 --- a/src/Mouf/Utils/Patcher/Commands/ApplyAllPatchesCommand.php +++ b/src/Mouf/Utils/Patcher/Commands/ApplyAllPatchesCommand.php @@ -22,8 +22,8 @@ class ApplyAllPatchesCommand extends Command public function __construct(PatchService $patchService) { - parent::__construct(); $this->patchService = $patchService; + parent::__construct(); } @@ -34,16 +34,22 @@ protected function configure() { $this ->setName('patches:apply-all') - ->setDescription('Apply all pending patches.') + ->setDescription('Apply pending patches.') ->setDefinition(array( )) ->setHelp(<<patchService->getTypes() as $type) { + if ($type->getName() !== '') { + $this->addOption($type->getName(), null, InputOption::VALUE_NONE, 'Applies patches of type "'.$type->getName().'". '.$type->getDescription()); + } + } } /** diff --git a/src/Mouf/Utils/Patcher/Commands/ListPatchesCommand.php b/src/Mouf/Utils/Patcher/Commands/ListPatchesCommand.php index 65e4732..804e38f 100644 --- a/src/Mouf/Utils/Patcher/Commands/ListPatchesCommand.php +++ b/src/Mouf/Utils/Patcher/Commands/ListPatchesCommand.php @@ -54,12 +54,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $patches = $this->patchService->getView(); $rows = array_map(function($row) { - return [ $row['uniqueName'], $this->renderStatus($row['status']) ]; + return [ $row['uniqueName'], $this->renderStatus($row['status']), $row['patch_type'] ?: '(default)' ]; }, $patches); $table = new Table($output); $table - ->setHeaders(array('Patch', 'Status')) + ->setHeaders(array('Patch', 'Status', 'Type')) ->setRows($rows) ; $table->render(); diff --git a/src/Mouf/Utils/Patcher/Controllers/PatchController.php b/src/Mouf/Utils/Patcher/Controllers/PatchController.php index 430daf2..574dafb 100644 --- a/src/Mouf/Utils/Patcher/Controllers/PatchController.php +++ b/src/Mouf/Utils/Patcher/Controllers/PatchController.php @@ -5,6 +5,7 @@ use Mouf\Database\TDBM\Utils\TDBMDaoGenerator; +use Mouf\Html\Widgets\MessageService\Service\UserMessageInterface; use Mouf\MoufManager; use Mouf\Mvc\Splash\Controllers\Controller; @@ -36,6 +37,8 @@ class PatchController extends AbstractMoufInstanceController { protected $nbAwaiting = 0; protected $nbError = 0; + + protected $nbPatchesByType = []; /** * Page listing the patches to be applied. @@ -57,7 +60,7 @@ public function defaultAction($name, $selfedit="false") { } } - $this->content->addFile(dirname(__FILE__)."/../../../../views/patchesList.php", $this); + $this->content->addFile(__DIR__."/../../../../views/patchesList.php", $this); $this->template->toHtml(); } @@ -93,30 +96,126 @@ public function runPatch($name, $uniqueName, $action, $selfedit) { header('Location: .?name='.urlencode($name)); } - - /** - * Runs all patches in a row. - * - * @Action - * @Logged - * @param string $name - * @param string $selfedit - */ - public function runAllPatches($name, $selfedit) { + + /** + * Displays the page to select the patch types to be applied. + * + * @Action + * @Logged + * @param string $name + * @param string $selfedit + */ + public function runAllPatches($name, $selfedit) { + $this->initController($name, $selfedit); + + $patchService = new InstanceProxy($name, $selfedit == "true"); + $this->patchesArray = $patchService->getView(); + + $types = $patchService->_getSerializedTypes(); + + foreach ($types as $type) { + $this->nbPatchesByType[$type['name']] = 0; + } + + $nbNoneDefaultPatches = 0; + + foreach ($this->patchesArray as $patch) { + if ($patch['status'] == PatchInterface::STATUS_AWAITING || $patch['status'] == PatchInterface::STATUS_ERROR) { + $type = $patch['patch_type']; + if ($type !== '') { + $nbNoneDefaultPatches++; + } + $this->nbPatchesByType[$type]++; + } + } + + // If all patches to be applied are default patches, let's do this right now. + if ($nbNoneDefaultPatches === 0) { + $this->applyAllPatches($name, [''], $selfedit); + return; + } + + ksort($this->nbPatchesByType); + + // Otherwise, let's display a screen to select the patch types to be applied. + $this->content->addFile(__DIR__."/../../../../views/applyPatches.php", $this); + $this->template->toHtml(); + } + + + /** + * Runs all patches in a row. + * + * @Action + * @Logged + * @param string $name + * @param array $types + * @param string $selfedit + */ + public function applyAllPatches($name, array $types, $selfedit) { $patchService = new InstanceProxy($name, $selfedit == "true"); $this->patchesArray = $patchService->getView(); - + + // Array of cound of applied and skip patched. Key is the patch type. + $appliedPatchArray = []; + $skippedPatchArray = []; + try { foreach ($this->patchesArray as $patch) { - if ($patch['status'] == PatchInterface::STATUS_AWAITING || $patch['status'] == PatchInterface::STATUS_ERROR) { - $patchService->apply($patch['uniqueName']); - } + if ($patch['status'] == PatchInterface::STATUS_AWAITING || $patch['status'] == PatchInterface::STATUS_ERROR) { + $type = $patch['patch_type']; + if (in_array($type, $types) || $type === '') { + $patchService->apply($patch['uniqueName']); + if (!isset($appliedPatchArray[$type])) { + $appliedPatchArray[$type] = 0; + } + $appliedPatchArray[$type]++; + } else { + $patchService->skip($patch['uniqueName']); + if (!isset($skippedPatchArray[$type])) { + $skippedPatchArray[$type] = 0; + } + $skippedPatchArray[$type]++; + } + } } + } catch (\Exception $e) { $htmlMessage = "An error occured while applying the patch: ".$e->getMessage(); set_user_message($htmlMessage); } - - header('Location: .?name='.urlencode($name)); + + $this->displayNotificationMessage($appliedPatchArray, $skippedPatchArray); + + header('Location: .?name='.urlencode($name)); } + + private function displayNotificationMessage(array $appliedPatchArray, array $skippedPatchArray) + { + $nbPatchesApplied = array_sum($appliedPatchArray); + $nbPatchesSkipped = array_sum($skippedPatchArray); + $msg = ''; + if ($nbPatchesApplied !== 0) { + $patchArr = []; + foreach ($appliedPatchArray as $name => $number) { + $name = $name ?: 'default'; + $patchArr[] = plainstring_to_htmlprotected($name).': '.$number; + } + + $msg .= sprintf('%d patch(es) applied (%s)', $nbPatchesApplied, implode(', ', $patchArr)); + } + if ($nbPatchesSkipped !== 0) { + $patchArr = []; + foreach ($skippedPatchArray as $name => $number) { + $name = $name ?: 'default'; + $patchArr[] = plainstring_to_htmlprotected($name).': '.$number; + } + + $msg .= sprintf('%d patch(es) skipped (%s)', $nbPatchesSkipped, implode(', ', $patchArr)); + } + + if ($msg !== '') { + set_user_message($msg, UserMessageInterface::SUCCESS); + } + } } \ No newline at end of file diff --git a/src/Mouf/Utils/Patcher/PatchInstaller2.php b/src/Mouf/Utils/Patcher/PatchInstaller2.php index f383f5f..e439f66 100644 --- a/src/Mouf/Utils/Patcher/PatchInstaller2.php +++ b/src/Mouf/Utils/Patcher/PatchInstaller2.php @@ -8,6 +8,7 @@ use Mouf\Actions\InstallUtils; use Mouf\Console\ConsoleUtils; +use Mouf\Database\Patcher\PatchConnection; use Mouf\Installer\PackageInstallerInterface; use Mouf\MoufManager; use Mouf\Utils\Patcher\Commands\ApplyAllPatchesCommand; diff --git a/src/Mouf/Utils/Patcher/PatchInterface.php b/src/Mouf/Utils/Patcher/PatchInterface.php index 7ba7226..8e99029 100644 --- a/src/Mouf/Utils/Patcher/PatchInterface.php +++ b/src/Mouf/Utils/Patcher/PatchInterface.php @@ -34,25 +34,25 @@ interface PatchInterface { /** * Applies the patch. */ - public function apply(); + public function apply(): void; /** * Skips the patch (sets its status to "skipped"). */ - public function skip(); + public function skip(): void; /** * Reverts (cancels) the patch. * Note: patchs do not have to provide a "revert" feature (see canRevert method). */ - public function revert(); + public function revert(): void; /** * Returns true if this patch can be canceled, false otherwise. * * @return boolean */ - public function canRevert(); + public function canRevert(): bool; /** * Returns the status of this patch. @@ -65,28 +65,28 @@ public function canRevert(); * * @return string */ - public function getStatus(); + public function getStatus(): string; /** * Returns a unique name for this patch. * * @return string */ - public function getUniqueName(); + public function getUniqueName(): string; /** * Returns a short description of the patch. * * @return string */ - public function getDescription(); + public function getDescription(): string; /** * Returns the error message of the last action performed, or null if last action was successful. * * @return string */ - public function getLastErrorMessage(); + public function getLastErrorMessage(): ?string; /** * Returns the URL that can be used to edit this patch. @@ -94,7 +94,7 @@ public function getLastErrorMessage(); * * @return string */ - public function getEditUrl(); + public function getEditUrl(): string; /** * Returns the type of the patch. diff --git a/src/Mouf/Utils/Patcher/PatchService.php b/src/Mouf/Utils/Patcher/PatchService.php index 3057554..a5094f2 100644 --- a/src/Mouf/Utils/Patcher/PatchService.php +++ b/src/Mouf/Utils/Patcher/PatchService.php @@ -76,6 +76,17 @@ public function getTypes(): array return $this->types; } + /** + * @internal Returns a serialized list of types for the patch UI. + * @return array + */ + public function _getSerializedTypes(): array + { + return array_map(function(PatchType $type) { + return $type->jsonSerialize(); + }, $this->types); + } + /** * Adds this patch to the list of existing patches. * If the patch already exists, an exception is thrown. @@ -203,6 +214,8 @@ public function validateInstance() { /** * Returns a PHP array representing the patchs. + * + * @internal */ public function getView(): array { $view = array(); diff --git a/src/Mouf/Utils/Patcher/PatchType.php b/src/Mouf/Utils/Patcher/PatchType.php index 6fbb5ac..247701b 100644 --- a/src/Mouf/Utils/Patcher/PatchType.php +++ b/src/Mouf/Utils/Patcher/PatchType.php @@ -4,7 +4,9 @@ namespace Mouf\Utils\Patcher; -class PatchType +use Mouf\MoufManager; + +class PatchType implements \JsonSerializable { /** * @var string @@ -16,14 +18,15 @@ class PatchType private $description; /** + * @Important * @param string $name The name of the patch type. Should not contain special characters or spaces. Note: the default type is an empty string. * @param string $description The description of the patch type * @throws PatchException */ public function __construct(string $name, string $description) { - if (!preg_match('/[^a-z_\-0-9]/i', $name)) { - throw new PatchException('A patch name can only contain alphanumeric characters and underscore.'); + if (!preg_match('/^[a-z_\-0-9]*$/i', $name)) { + throw new PatchException('A patch name can only contain alphanumeric characters and underscore. Name passed: "'.$name.'"'); } $this->name = $name; @@ -49,4 +52,20 @@ public function getDescription(): string { return $this->description; } + + /** + * Specify data which should be serialized to JSON + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @return mixed data which can be serialized by json_encode, + * which is a value of any type other than a resource. + * @since 5.4.0 + */ + public function jsonSerialize() + { + return [ + 'name' => $this->name, + 'description' => $this->description, + 'instanceName' => MoufManager::getMoufManager()->findInstanceName($this) + ]; + } } diff --git a/src/views/applyPatches.php b/src/views/applyPatches.php new file mode 100644 index 0000000..2908d1d --- /dev/null +++ b/src/views/applyPatches.php @@ -0,0 +1,15 @@ + +

Apply patches

+ +Please select the patch types you want to apply: + +
+ + +nbPatchesByType as $name => $number): ?> + + + + +
\ No newline at end of file diff --git a/src/views/patchesList.php b/src/views/patchesList.php index 5b065f4..dcfd1bc 100644 --- a/src/views/patchesList.php +++ b/src/views/patchesList.php @@ -37,7 +37,8 @@ Status Name - Description + Description + Type Actions + (default)' ?>