-
-
Notifications
You must be signed in to change notification settings - Fork 825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Exceptions - What could possibly go wrong? #23471
Conversation
(Standard links)
|
Apparently this is what could wrong.... Test Result (4 failures / +4)Civi\API\KernelTest.testResolveExceptionCRM_Core_BAO_MappingTest.testGetCreateMappingValuesapi_v3_APITest.testV3WrapperExceptionapi_v3_ActivityTest.testActivityCreateWithInvalidPriorityTest Result (4 failures / +4) |
… before PEAR_Exception API_Exception is narrower, and we may want API_Exception to become a subclass of PEAR_Exception
It looks like these 4 failures all stemmed from the same (2M) multi-catch statement within |
@totten @colemanw @seamuslee001 @demeritcowboy @mattwire - assuming we merge this then the protocol going forwards would be to always throw Aside from the issue where this came up I would say there is a good handful of bugs I've seen over time when the exeption thrown changed between these 3 - by-passing a Catch so I think this will prevent regressions in future |
I have conflicting intuitions about this. From a general OOP POV, it seems intuitively bad (BC-breaky). But from a Civi-specific POV (a "feel" for the classes), it feels intuitively good. Here's the best rationalization (in support) that I can come up with:
In this view, things are already unstable. Someone who needs broad compatibility (eg say try {
...
}
catch (CRM_Core_Exception|API_Exception|CiviCRM_API3_Exception $e) {
$errorData = is_callable([$e, 'getErrorData']) ? $e->getErrorData() : $e->getExtraParams();
if ($e->getErrorCode()... or... $e->getMessage()...) {
...
}
} The patch here improves upon the status quo because:
But does that outweigh the other intuition (from a general OOP POV) that messing with exception-types is always a breaky signature change? |
Hmm - so it's not breaky in the sense of the functions stopping working (getErrorData, getErrorCode) just in that the type of exception in a given scenario will change - but that happens all the time anyway. I've made a note to put in dev-digest |
I like the concept. Instead of class alias though, couldn't we |
I interpret that to mean: class API_Exception extends CRM_Core_Exception { ... } The effect would be similar-but-different. Aliasing means that
I struggle to find a basis for saying that either formulation is functionally better or administratively easier for downstream (eg if you have 100 existing use-cases with try/catches, maybe one approach works well for 91% and the other works well for 87%; but I can't give reasoned numbers for either approach). For me, the appeal of |
I think at the moment we throw all 3 variants a lot - so it will take a long time to consolidate - but we could at least encourage catching only |
The main risk I see is from multi-catch statements. So I grepped There was one true case of multi-catch expression ( try {
// This function will do redirect if the payment failed
$response = validateEwayContribution($paymentProcessor, $contributionInvoiceID);
} catch (CRM_Core_Exception $e) {
} catch (CiviCRM_API3_Exception $e) {
} There was one (obvious) case of nested try-catch ( try {
$a = civicrm_api3();
try {
$b = civicrm_api3();
}
catch (CiviCRM_API3_Exception $e2) { log($e2); }
}
catch (CiviCRM_API3_Exception $e1) { log($e1); } Most files had independent try/catch blocks, where each involved a different API call (eg in try { ... civicrm_api3() ... } catch (CiviCRM_API3_Exception $e) { ... }
try { ... civicrm_api4() ... } catch (API_Exception $e) {... } I didn't imagine any of these behaving differently with the patch -- and, if anything, there might be some opportunities to simplify if each API call uses the same kind of exception. |
yep - that's consistent with my experience |
I'll add a +1. I'd need to spend more time with it though to see if it makes it easier to get the actual location/error of api exceptions (but out of scope for this). |
That makes good sense to me and thank you for explaining it. Since people throw whatever and catch whatever the class alias approach seems safer. |
In light of the affirmative comments, I raised the question of timing with Eileen, Seamus, and Coleman. We agreed it's best to merge this one next week (after freezing 5.51, when we start the 5.52 cycle). |
we have frozen |
Overview
An observation from @eileenmcnaughton:
I agree these classes basically all implement the same pattern (ie general-purpose exception class, using
$code
to represent the actual error condition).This PR is a trial-balloon to see what would happen if simply combined the three into the same class.
Before
CRM_Core_Exception
,API_Exception
,CiviCRM_API3_Exception
After
CRM_Core_Exception
API_Exception
,CiviCRM_API3_Exception
Technical Details
Is this safe? Who knows? I can think of a couple general questions:
For
#1
, I think we can analyze the classes directly. (Dunno if current draft is perfect - but I think we can be optimistic about this one.) Observations about similarities/differences:CRM_Core_Exception
is more generic (ie it would fit in more places thanAPI_Exception
orCiviCRM_API3_Exception
)$message, $error_code = 0, $errorData = [], $previous = NULL
)$errorData
vs$extraParams
). In each case, the field isprivate
, so callers would only care about getter/setter methods.getErrorData()
andgetExtraParams()
can simply do the same thing.$message
is slightly different.API_Exception
andCiviCRM_API3_Exception
have the formulationparent::__construct(ts($message)...
, which is generally suspect on l10n grounds.API_Exception
has more developed$errorCode
-- it has a list of known-codes, and it explicitly bridges theint|string
discrepancy. (IIRC, top-levelException
always suggestedint
but didn't enforce it, and so it was common to find Civi callers that gavestring
s? But as type-checking gets stronger onException::$code
, that gets ill-defined.API_Exception
explicitly allows either.) IMHO, it's sensible for all of them to bridgeint|string
situation this way.I think
#2
is the harder question. Brainstorming a bit... here are some foreseeable effects ofclass_alias()
approach:(2T) All three symbols can still be thrown the same way.
(2E) All three symbols can still be extended the same way.
(2C) All three symbols can still be caught the same way
(2I) There would be functional-changes in code which checked one type but ignored the other types.
(2M) There would be functional-changes in code which specifically discriminates between these types (ie you expected both
CRM_Core_Exception
andAPI_Exception
, but for different reasons, and wanted to do something different with each)(2S) There would be functional-changes in string-literal reflection on the exception-type.