Calling Apex method with multiple signatures from LWC - salesforce

I've noticed some interesting behavior in an LWC that I am building and haven't been able to find much info on the cause. Basically I have an Apex method declared with multiple signatures:
// myMethod with 2 param signature
#AuraEnabled
public static void myMethod(String param1, String param2) {
// I expect this method to be invoked when called from the LWC code shown below
....
}
// myMethod with 3 param signature
#AuraEnabled
public static void myMethod(String param1, String param2, Boolean param3) {
// However this method gets invoked instead
....
}
When I attempt to call myMethod from an LWC, and only pass two parameters into the method, I would expect that the 2 param signature method would be invoked, however what happens is that the 3 param signature method is invoked, and the value of null is passed as the third params value.
runJob({param1, param2}).then(value => ... )
Is this expected behavior? When I call the methods through apex, the correct method is invoked by its signature, however this doesn't seem to be the case when calling the method from an LWC.
Is there a way for me to invoke the myMethod Apex method of the correct signature from an LWC?

Edit:
it will be banned at compile time in Summer'22 release. https://help.salesforce.com/s/articleView?id=release-notes.rn_apex_ValidationForAuraEnabledAnnotation.htm&type=5&release=238
Original:
It's worse than you think. runJob({param2, param1}) will work correctly too. "Correctly" meaning their names matter, not the position!
Your stuff is always passed as an object, not a list of parameters. You could have a helper Apex wrapper class and (assuming all fields are #AuraEnabled) it'll map them correctly. If you have list of parameters - well, a kind of unboxing and type matching happens even before your code is called. If you passed "abc" to a Date variable - a JSON deserialization error is thrown even before your code runs, you have no means to catch it.
https://developer.salesforce.com/docs/component-library/documentation/en/lwc/lwc.apex_wire_method
If the Apex method is overloaded, the choice of what method to call is
non-deterministic (effectively random), and the parameters passed may
cause errors now or in the future. Don't overload #AuraEnabled Apex
methods.
So... pick different names? Or funnel them so the 3-param version looks at the last param and calls 2-param one if needed and the business logic allows it. Generally - keep it simple, leave some comments and good unit tests or 2 months from now poor maintenance guy will struggle.
And (if you use the Apex PMD plugin and/or sfdx scanner) functions with long parameter lists are frowned upon anyway: https://pmd.github.io/latest/pmd_rules_apex_design.html#excessiveparameterlist
See also
https://github.com/trailheadapps/lwc-recipes/issues/196
https://salesforce.stackexchange.com/questions/359728/apex-method-overloading-is-not-working-when-called-from-lwc-javascript-controlle

Related

CakePHP4: Convert or cast Cake\ORM\Entity to actual entity for type hinting

Let's say I have a method that is expecting an Invoice entity as a parameter. So I add the type hint Invoice like so:
public function doThatThing (Invoice $invoiceEntity)
{
// ... operate on $invoiceEntity
}
Unfortunately, when I pass the results of InvoiceTable->get(123), I get the error "TypeError: Argument 1 passed to doThatThing must be an instance of App\Model\Entity\Invoice, instance of Cake\ORM\Entity given..." in my unit tests.
Is there a good way to cast or convert the generic ORM results of ->get() to the specific Entity type that I know it must be?
#greg-schmidt got me looking in the right direction. I'm afraid my question only mentioned the key clue in passing when I said "in my unit tests."
As it turns out, my unit test has a fake invoice table that extends InvoiceTable. My fake does call setTable and setAlias so that it looks like the real table.
But when I call get() on the fake, the call chain bypasses InvoiceTable, lands in ORM/Table, and eventually calls into ORM/Query. When ORM/Table calls the constructor on ORM/Query, it passes $this, which is an instance of my fake, and not of the real, underlying InvoiceTable (setTable and setAlias have no effect at this point). And as #greg-schmidt guessed,
InvoiceTable is almost certainly not your invoice table implementation
My solution will be to have my fake invoice implement its own get() method which will return the response from a real InvoiceTable.

I don't understand this sequenced calling of static methods in Snackbar class. Can someone explain it? Thanks

Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
how does the above code works?
The Snackbar class has a static method make that takes 3 parameters (view, "Replace with your own action", Snackbar.LENGTH_LONG). I don't know what view is but the 2nd is a string and the third is a final variable defined in Snackbar (most probably a long value).
The method make returns a object which has a method called setAction which has two parameters ("Action", null) The first parameter is a String and the 2nd could be any type of object.
The method setAction returns a new object which has a method show() which may return a new value but can also be void.

Camel bean binding: set parameter from variable

Given a bean method that takes a String parameter:
public void emptyDirectory(String directory) {
// code to empty give directory if it exists
}
how do i pass this parameter? The method is called here:
String to = configuration.getTo();
from(configuration.getFrom())
.to("bean:splitFileByProductType?method=emptyDirectory(to)")
....
This doesn't work as 'to' evaluates to "to", and not the value of configuration.getTo().
The documentation does not mention a case like this, so i don't know if what i'm trying to do is even possible, for example in the Simple language.
I know the value becomes accessible if i add it to the exchange header or if i hardcode it.
You can pass a value as method argument with ${body}, ${body.NAME}, ${property.NAME} and ${header.NAME}.
Examples http://camel.apache.org/bean-binding.html
So first of all you have to put your variable in Camel exchange.
To pass information from the Camel Exchange to a bean method you must not add or change anything in the route.
If you have this route (from your question). Notice that if the bean has only one method you can omit the method name.
from(whatever)
.to("bean:splitFileByProductType?methodName=emptyDirectory")
Or alternative
from(whatever)
.bean(splitFileByProductType, "emptyDirectory")
You can annotate your bean method to get the needed Exchange information automagically:
public void emptyDirectory(
#Header("directory") String directory,
#Body String body
... [other stuff to be injected] ) {
// your method impl
}
See the Camel docs for more information about this feature.
You could assign the parameter to an exchange header or property and then use simple language to pass it the bean method
String to = configuration.getTo();
from(configuration.getFrom())
.setHeader("foo", constant(to))
.to("bean:splitFileByProductType?method=emptyDirectory(${header.foo})")
...
From documentation:
The only things that can be passed to a bean are Simple tokens, a String value, a numeric value, a boolean or null.
The variable from the example cannot be expressed using Simple. What can be expressed are various properties of the exchange, most notably the exchange headers, and also some random things like literally random numbers, the current date and so on. Check the documentation for more info.
I decided to treat the whole thing as a code smell, and moved the method to a utility class which i call prior to initializing the route.

What does this error actually mean?

So I have seen this error show up, just sometimes, but it is not helpful in describing the actual error which has occured. Nor does it give any clues as to what might cause it to display.
Cannot use modParams with indexes that do not exist.
Can anyone explain more verbosly what this error means, what it relates to (such as a behaviour, component, controller, etc), the most common causes and how to fix it?
To kickstart the investigation, you can find the error here.
https://github.com/cakephp/cakephp/blob/master/lib/Cake/Utility/ObjectCollection.php#L128
Layman's Terms
CakePHP is being told to apply an array of parameters to a collection of objects, such that each particular object can modify the parameters sent on to the next object. There is an error in how CakePHP is being told to do this.
In Depth
Generically, this rises from the CakePHP event publication mechanism. Somewhere in your code is an instance of ObjectCollection, which is being triggered with certain parameters. That is, a method is being called on every object in that collection.
Each callback method is given parameters. Originally the parameters are passed into trigger(). In normal cases (where modParams is false), every callback gets the same parameters. But when modParams is not strictly false, the result of each callback overwrites the parameter indicated by modParams.
So if there are two objects in the collection, modParams is 1, and the params[1] is 'a' initially, then the callback is given the first object with params[1] == a. That callback returns 'b', so when the next callback is called, the second object gets params[1] == b.
The exception raises when the modParams value given does not exist in the originally given params. Eg, if modParams is 2 and params is array (0 => 'a', 1 => 'b'), you'll get this exception.
In your case
Specifically, debugging this has to be done at a low-level because it's a method on a generic class. The backtrace from the Exception should get you bubbled up to a trigger() call on a particular concrete class. That call is being given non-false modParams and a params that doesn't have the given modParams. It could be a code bug in a concrete class extending ObjectCollection, or it could simply be a generic message arising from a method not being given expected arguments.
Have you tried reading the documentation?
/*
* - `modParams` Allows each object the callback gets called on to modify the parameters to the next object.
* Setting modParams to an integer value will allow you to modify the parameter with that index.
* Any non-null value will modify the parameter index indicated.
* Defaults to false.
*/
You did not paste any code, so I guess your 3rd arg of the method contains something wrong.

CakePHP: How one Behavior use the Behavior function of a Plugin?

I have two scenarios.
Scenario 1:
A ProcessableBehavior written in my APP/Model/Behavior needs to use a function inside another Behavior called Queryable in a Plugin.
How do I call Queryable.Queryable function doSomething from inside ProcessableBehavior?
Scenario 2:
If I write a Plugin Processable that now contains ProcessableBehavior and so this Behavior depends on Queryable.Queryable function doSomething, how do I do the calling?
Inside behaviors you can always invoke model methods. Since behaviors attached behave like those, you should be able to call them as they were part of the model.
// behavior 1
public function myFirstBehaviorMethod(Model $Model) {
// do sth
}
And
// behavior 2
public function mySecondBehaviorMethod(Model $Model) {
$Model->myFirstBehaviorMethod($foo, $bar);
}
The basic idea is that they don't necessarily have to know about it other. You just assume that they are part of the model (as behaviors enrich the functionality of models).
Note that you don't have to pass the $Model object, as it will internally use $Model.
Make sure you attach/load them in the right order.
If one depends on the other you could check on it in setup() etc:
// throw exception if not available
if (!$Model->Behaviors->attached('Queryable') {}

Resources