Problem/Motivation
The current availability manager is currently very limited as it doesn't allow the availability checkers to return a custom error message and does not provide access to the order item we're checking the availability for.
You only have access to the purchasable entity, which means no access to the order as well (if referenced by the order item).
We've discussed several approaches to fix the current availability manager, but IMO it's best to introduce a new API for checking the availability of order items rather than checking the existing one by introducing "hacks" or "workarounds".
Because of its current limitations, the current API is not even used by the Stock module for example, and I personally tried to use this API on a custom project, and failed for the reasons outlined.
Proposed resolution
Introduce a new Availability manager that lives under the Drupal\commerce_order namespace that has a similar check() method.
Deprecate Drupal\commerce\AvailabilityManager and Drupal\commerce\AvailabilityCheckerInterface via @trigger_error(), introduce new availability checkers interfaces that allow returning value objects instead of a boolean.
Then our code should migrate to using the new availability manager.
Because we have to worry about backwards compatibility, we keep invoking the "legacy" availability checkers from the new availability manager, and return an AvailabilityResult value object on their behalf.
API changes
The new availability manager people now lives under Drupal\commerce_order.
// Before.
$availability_manager = \Drupal::service('commerce.availability_manager');
$availability_manager->check($purchased_entity, $quantity, $context);
// After...
$availability_manager = \Drupal::service('commerce_order.availability_manager');
$availability_manager->check($order_item, $context);
The availability checkers should now implement Drupal\commerce_order\AvailabiltyCheckerInterface instead of Drupal\commerce\AvailabiltyCheckerInterface
The new availability checkers should now return an AvailabilityResult value object (rather than a boolean).
Patch to follow.
| Comment | File | Size | Author |
|---|---|---|---|
| #11 | interdiff_10-11.txt | 2.33 KB | jsacksick |
| #11 | 3138952-11.patch | 32.42 KB | jsacksick |
Comments
Comment #2
jsacksick commentedLet's hope for the tests to pass :).
Comment #4
jsacksick commented95% of the test failures are coming from the deprecation notices (caused by
@trigger_errror()). Unfortunately, I don't think we can keep those since we still to test that the legacy API still works...Comment #5
mglamanGoing to review
We should be able to utilize
@trigger_errorand tell our tests to expect that deprecation.Should move this to #2937041: AvailabilityManager needs to be aware of the order, if there is one instead of a new issue. Or I guess that is not really needed.
We shouldn't title this "Deprecate the AvailabilityManager", it's more "Make the AvailabilityManager order aware", which causes moving it to the order module and deprecating the old service. Which is why, with that name, I thought we could just use the existing issue which has 14 followers.
We can hack on it here and then decide what to do with the related issues.
We should probably close #3107547: Availability checks should use a value object instead of boolean return values for this. There's no reason to land the availability result object without a new method to utilize it, especially if we plan on rewriting the entire tidbit.
Comment #6
mglamanInitial review, (I just realized this was on the interdiff) will follow up with more.
I think it's fine to abandon this interface and keep the @trigger_error, since the interface is technically dead code for us, internally.
If this is causing grief, because of the trigger error at the top of the screen, what if we called the trigger error during
check?That's what I did in #3107547-21: Availability checks should use a value object instead of boolean return values when we receive a truthy value.
That's what core did for EntityManager: https://git.drupalcode.org/project/drupal/-/blob/8.8.x/core/lib/Drupal/C.... Granted it was deprecated over time, but we can leverage this.
If someone is calling the old service
::checkthey'll get the deprecation.This needs to have the messaging "Depredated in X will be removed in X use X"
Assert isn't needed, the code would crash since we have a typed parameter.
Do we need to allow getting them? it should just be about check
Should we allow an empty result? Should you be forced to return available? And if anything, I'd prefer changing not empty to a direct instance check
This is where we can throw trigger errors for deprecated implementations, like in #3107547-21: Availability checks should use a value object instead of boolean return values.
Before we even check if it applies, trigger a deprecation that we're checking a legacy checker.
👍
It should default to FALSE, shouldn't it? Why should we allow NULL when it's either TRUE or FALSE.
This should return TRUE. The access system supports neutral in the fact if nothing says allowed, access is denied. Neutral means pass. Our system is "if nothing said unavailable, it is available"
It also shows in the fact we don't have a matching "available" which sets TRUE, which is perfect. Let's just have this explicitly set TRUE and make $result not be nillable
Should we keep this to prove we support legacy checkers? Our tests can silence deprecations, core did this.
tests/src/Unit/AvailabilityManagerTest.php, per the last item, is this our test that the legacy checkers still work?Comment #7
jsacksick commented@mglaman: Thanks for this very efficient code review :).
5. The getters were used in the unit test, but I just changed that, I guess we can live without em.
Yes!
I moved the @trigger_error to AvailabilityManager::test, so it's triggered only when it's actually used.
Comment #8
jsacksick commentedComment #10
jsacksick commentedOk, I now trigger the deprecation notice from the
check()method, in the new availability manager, if "legacy" checkers are detected.Comment #11
jsacksick commentedCoding standard fixes only.
Comment #12
mglamanThis patch looks great!
@agoradesign had a good item for feedback that would introduce the explicit
::availablemethod.So you could set
XyAvailabilityCheckerat the highest priority to explicitly allow specific SKUs.The alternative is you craft proper logic in the
appliesmethod. My example is if a site supports dropshipping. Your normal availability checker for inventory should see iffield_is_dropshippedis true, and not apply to the order item.The code is pretty simple right now. Adding
::availableadds more logic and possibly accidents. I wanted to at least capture this here as notes. I think we can commit this and still add an::availablemethod without worrying about backward compatibility for explicit "always allow"Comment #13
olafkarsten commentedFINALLY ... after years ...
;) Guys, this is cool stuff. Unfortunately I can't provide the time for a line by line review this week, but the concept is clean and simple and the patch looks well coded. I'm really happy to see this happen. It will give us the API we need to remove a bunch of custom form alter hooks and stuff in commerce_stock. Allows for much cleaner code and testing.
#12 We have a "AlwaysAvailable" stock service in commerce stock from the beginning, exactly for such use cases. We will see how it goes in practice, as soon as we update our code to the new AvailablityManager API. I think these use cases are the more advanced ones. In such projects/scenarios there should be a developer nearby, that can code a proper applies logic to your custom AvailabilityManager. And as you said, if we miss something, we can add it later.
Thanks for the work!
Comment #14
mglamanWorking on the change record
Comment #15
mglamanWrote the change record. Getting peer review of the text, and then committing.
Comment #17
mglaman🥳 Committed! Thanks @jsacksick, and @olafkarsten for the input.