From 7a4ac33a7b08f2f4472445c04fdda5384d1f7865 Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Fri, 2 May 2025 18:13:57 +0200 Subject: [PATCH 1/3] Document new error handling in Joomla 6.0 onwards --- migrations/54-60/compat-plugin.md | 2 +- migrations/54-60/errorhandling.md | 117 ++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 migrations/54-60/errorhandling.md diff --git a/migrations/54-60/compat-plugin.md b/migrations/54-60/compat-plugin.md index 8a6c2405..3140360f 100644 --- a/migrations/54-60/compat-plugin.md +++ b/migrations/54-60/compat-plugin.md @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 5 --- # Compatibility Plugin diff --git a/migrations/54-60/errorhandling.md b/migrations/54-60/errorhandling.md new file mode 100644 index 00000000..a842e847 --- /dev/null +++ b/migrations/54-60/errorhandling.md @@ -0,0 +1,117 @@ +--- +sidebar_position: 4 +--- + +# Error handling in Joomla 6.0 and onwards + +Joomla 1.0 originally supported PHP4, which didn't have exceptions yet. That is why Joomla introduced its own error +handling with methods like `$this->setError()` and `$this->getError()`. Unfortunately it has been very hard to break +this old habbit and so Joomla 5 still has this bad behavior, but at least all those `getError()/setError()` have been + combined into the `LegacyErrorHandlingTrait`. + +For a transition away from this custom error handling to a normal exceptions-based solution, the `LegacyErrorHandlingTrait` +was extended in **Joomla 5.4** with the `shouldUseExceptions()` flag. This allows to check if exceptions should be thrown or +the legacy error handling system should be used. Joomla will convert all calls to `LegacyErrorHandlingTrait::setError()` +to check this flag and if the flag is set, will throw an exception. If it is not set, it will still call the legacy +`setError()`. This will allow to gradually convert all code from the old system to the standard exception handling. + +You can set the flag by calling `setUseExceptions(true)` on the object which should use the new system. To query the flag +call `shouldUseExceptions()`. The legacy code when encountering an error looked like this: +``` +if ($errorConditionEvaluatingToFalse) { + $this->setError($table->getError()); + + return false; +} +``` +The new code in the core would look similar to this: +``` +if ($errorConditionEvaluatingToFalse) { + if ($this->shouldUseExceptions()) { + throw new \Exception($table->getError()); + } + + $this->setError($table->getError()); + + return false; +} +``` + +**To ease the transition to exceptions, `setError()` will also throw the error as exception when the flag is set.** +Ideally, you should use error-specific exceptions, which is why the core will use this more complex solution. `setError()` +just throws a generic `Exception` exception. + +If your object instantiates other objects which also use the `LegacyErrorHandlingTrait`, you should pass the current +status of the `shouldUseExceptions()` flag to the new object as well. + +## How will the core drop the `LegacyErrorHandlingTrait`? +The plan is to implement proper exceptions everywhere in the core code during the time of Joomla 6.x in parallel with the +legacy system. In Joomla 7.0, the core will remove calls to `setError()` and the checks for `shouldUseExceptions()`. +At that point in time, the Joomla core would be entirely transitioned to exceptions. It is still up for discussion if +the calls to `shouldUseException()` should then also default to `true` instead of `false` like right now. The trait itself +is planned to be dropped in Joomla 8.0. + +## What should third party developers do? +The above examples from the core look quite complex and cumbersome. Fortunately third party developers can go a much +simpler route by simply switching to exceptions right away. Remember that the core needs to support both code which does +and doesn't supports exceptions at the same time. As a third party developer, you can simply switch all of your code to +exceptions at once and only have to make sure that the core classes, which you are extending from, also throw exceptions. +For this, you can simply call `$this->setUseExceptions(true);` in the constructor of your object to directly force +exceptions. + +This will allow you to do one refactoring of your code to use exceptions anywhere in the release cycle of Joomla 6 and + you only have to touch this somewhere before Joomla 8 to remove the `$this->setUseExceptions(true);` again. + +## Example of new error handling in views +This is the current error handling in views on the example of the articles view in the backend of com_content: +```php + $model = $this->getModel(); + $this->items = $model->getItems(); + $this->pagination = $model->getPagination(); + $this->state = $model->getState(); + $this->filterForm = $model->getFilterForm(); + $this->activeFilters = $model->getActiveFilters(); + $this->vote = PluginHelper::isEnabled('content', 'vote'); + $this->hits = ComponentHelper::getParams('com_content')->get('record_hits', 1) == 1; + + if (!\count($this->items) && $this->isEmptyState = $model->getIsEmptyState()) { + $this->setLayout('emptystate'); + } + + if (ComponentHelper::getParams('com_content')->get('workflow_enabled')) { + PluginHelper::importPlugin('workflow'); + + $this->transitions = $model->getTransitions(); + } + + // Check for errors. + if (\count($errors = $model->getErrors()) || $this->transitions === false) { + throw new GenericDataException(implode("\n", $errors), 500); + } +``` +And this is the new solution based on exceptions in Joomla 6.0 and onwards: +```php + $model = $this->getModel(); + $model->setUseExceptions(true); + + try { + $this->items = $model->getItems(); + $this->pagination = $model->getPagination(); + $this->state = $model->getState(); + $this->filterForm = $model->getFilterForm(); + $this->activeFilters = $model->getActiveFilters(); + $this->vote = PluginHelper::isEnabled('content', 'vote'); + $this->hits = ComponentHelper::getParams('com_content')->get('record_hits', 1) == 1; + + if (!\count($this->items) && $this->isEmptyState = $model->getIsEmptyState()) { + $this->setLayout('emptystate'); + } + + if (ComponentHelper::getParams('com_content')->get('workflow_enabled')) { + PluginHelper::importPlugin('workflow'); + $this->transitions = $model->getTransitions(); + } + } catch (\Exception $e) { + throw new GenericDataException($e->getMessage(), 500, $e); + } +``` From 4b61b193fb7b528da25f8c70af2d859f8586794a Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Fri, 2 May 2025 18:17:23 +0200 Subject: [PATCH 2/3] Improve code highlighting --- migrations/54-60/errorhandling.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/54-60/errorhandling.md b/migrations/54-60/errorhandling.md index a842e847..476a0cc1 100644 --- a/migrations/54-60/errorhandling.md +++ b/migrations/54-60/errorhandling.md @@ -17,7 +17,7 @@ to check this flag and if the flag is set, will throw an exception. If it is not You can set the flag by calling `setUseExceptions(true)` on the object which should use the new system. To query the flag call `shouldUseExceptions()`. The legacy code when encountering an error looked like this: -``` +```php if ($errorConditionEvaluatingToFalse) { $this->setError($table->getError()); @@ -25,7 +25,7 @@ if ($errorConditionEvaluatingToFalse) { } ``` The new code in the core would look similar to this: -``` +```php if ($errorConditionEvaluatingToFalse) { if ($this->shouldUseExceptions()) { throw new \Exception($table->getError()); From 4fc81344537072a9c69e3958391a4df59a88a54d Mon Sep 17 00:00:00 2001 From: Hannes Papenberg Date: Fri, 2 May 2025 18:19:16 +0200 Subject: [PATCH 3/3] Update migrations/54-60/errorhandling.md Co-authored-by: qodo-merge-pro[bot] <151058649+qodo-merge-pro[bot]@users.noreply.github.com> --- migrations/54-60/errorhandling.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/54-60/errorhandling.md b/migrations/54-60/errorhandling.md index 476a0cc1..0730d5c0 100644 --- a/migrations/54-60/errorhandling.md +++ b/migrations/54-60/errorhandling.md @@ -48,7 +48,7 @@ status of the `shouldUseExceptions()` flag to the new object as well. The plan is to implement proper exceptions everywhere in the core code during the time of Joomla 6.x in parallel with the legacy system. In Joomla 7.0, the core will remove calls to `setError()` and the checks for `shouldUseExceptions()`. At that point in time, the Joomla core would be entirely transitioned to exceptions. It is still up for discussion if -the calls to `shouldUseException()` should then also default to `true` instead of `false` like right now. The trait itself +the calls to `shouldUseExceptions()` should then also default to `true` instead of `false` like right now. The trait itself is planned to be dropped in Joomla 8.0. ## What should third party developers do?