User tests: Successful: Unsuccessful:
Pull Request for #43471 .
Adding support of Stringable
https://www.php.net/manual/en/class.stringable.php results to Ajax component.
Adding Joomla\CMS\String\StringableInterface
results to Ajax component, this is transitioning interface to Stringable
https://www.php.net/manual/en/class.stringable.php.
This allows to customise the response much better, than it is currently.
Add following code in to plugins/system/skipto/src/Extension/Skipto.php
:
public function onAjaxSkiptoTest1(\Joomla\CMS\Event\Plugin\AjaxEvent $event)
{
$event->updateEventResult(new class ('test 1') extends \Joomla\CMS\Response\JsonResponse implements \Joomla\CMS\String\StringableInterface {});
}
public function onAjaxSkiptoTest2(\Joomla\CMS\Event\Plugin\AjaxEvent $event)
{
$event->addResult(['foo', 'bar']);
}
public function onAjaxSkiptoTestError(\Joomla\CMS\Event\Plugin\AjaxEvent $event)
{
throw new \Exception('test error');
}
Then open following links:
/administrator/index.php?option=com_ajax&plugin=skiptoTest1&group=system&format=raw
/administrator/index.php?option=com_ajax&plugin=skiptoTest1&group=system&format=json
/administrator/index.php?option=com_ajax&plugin=skiptoTest2&group=system&format=json
/administrator/index.php?option=com_ajax&plugin=skiptoTestError&group=system&format=raw
/administrator/index.php?option=com_ajax&plugin=skiptoTestError&group=system&format=json
Check response for each:
{"success":true,"message":null,"messages":null,"data":"test 1"}
{"success":true,"message":null,"messages":null,"data":"test 1"}
{"success":true,"message":null,"messages":null,"data":[["foo","bar"]]}
Exception: test error
{"success":false,"message":"test error","messages":null,"data":null}
Please select:
Status | New | ⇒ | Pending |
Category | ⇒ | Front End com_ajax |
Labels |
Added:
Feature
PR-5.2-dev
|
@korenevskiy Please visit https://issues.joomla.org/tracker/joomla-cms/43530 and push the button, thanks
/administrator/index.php?option=com_ajax&plugin=skiptoTest2&group=system&format=raw
Warning: Array to string conversion in [ROOT]\components\com_ajax\ajax.php on line 238
@carlitorweb that is correct, you cannot run skiptoTest2
with format=raw
, only with format=json
. As in my instruction.
Because format=raw
cannot handle anything else than scalar or stringable objects, and skiptoTest2
produces an array.
I have tested this item ✅ successfully on 66f35b7
I have tested this item ✅ successfully on 66f35b7
Status | Pending | ⇒ | Ready to Commit |
RTC
Labels |
Added:
RTC
|
We discussed this last week and we think this is a b/c break. It would be better to add a new format instead of changing an existing one and do "kind of magic" on this.
we think this is a b/c break
What kind of b.c break? I do not see any
We discussed this last week and we think this is a b/c break. It would be better to add a new format instead of changing an existing one and do "kind of magic" on this.
By creating a new format, we force each developer to refer to the documentation each time in order to understand the subtleties of the differences between each format. But ultimately the formats are the same, it's just a matter of processing JSON.
Status | Ready to Commit | ⇒ | Pending |
p
Labels |
Removed:
RTC
|
Category | Front End com_ajax | ⇒ | Front End com_ajax Libraries Plugins |
I have update PR, to be more backward compatible.
Please test again.
Can you tell me how the Joomla\CMS\String\StringableInterface
interface is used here?
It can be seen that the minimum version of Joomla 5.1 is 8.1, Joomla 5.2 may have the same.
The built-in Stringable
interface in PHP has a minimum version of 8.0.
Using the Joomla\CMS\String\StringableInterface
, you will oblige developers to repeatedly return to the documentation or to the AJAX component code to understand what needs to be inherited from the new interface.
If you inherit from Joomla\CMS\String\StringableInterface
, the extension will still depend on the latest version of Joomla 5.2.
And also the main reason for this whole idea.
The object of the JsonResponse
class will be passed as a result to the parameter of the new object of the JsonResponse
class and will be nested JSON in the main JSON. Thus, it does not give me the opportunity to debug the code by passing additional messages.
Let's replace this
if (!($results instanceof Throwable) && $results instanceof StringableInterface)
to it
if (!($results instanceof Throwable) && $results instanceof \Joomla\CMS\Response\JsonResponse)
Thus, we will allow those who want to additionally serialize a string in JSON to do so. And also independently transmit an object of class \JsonResponse
.
or do a json_validate()
check
It seems that the new interface will cause confusion. After all, its purpose is only for JsonResponse
, Maybe we'll call it \JsonResponseStringableInterface
If you want extend JsonResponse
, you can see it in the test:
new class ('test 1') extends \Joomla\CMS\Response\JsonResponse implements \Joomla\CMS\String\StringableInterface {};
Or make your own:
$response = new class () implements StringableInterface {
public $potatos = [];
public function __toString(): string
{
return json_encode($this->potatos);
}
};
$response->potatos = $arrayOfPotatos;
the extension will still depend on the latest version of Joomla 5.2
yeap, it is new feature
Why don't you allow the \JsonResponse
object to be returned from the user's extension ?
I understood your idea, and I see the potential, but there is no simplicity in this solution. And if you allow the \JsonResponse
object to be returned, the result will be the same.
Maybe?
if (!($results instanceof Throwable)
&& ($results instanceof \Joomla\CMS\Response\JsonResponse || \Joomla\CMS\String\StringableInterface))
Still, the new interface needs to be renamed so that it is clear that this applies to \JsonResponse
, since the wrong interface will be inherited by mistake. And if suddenly the new year is intentionally inherited from the new interface in cases not related to \JsonResponse
, then this will produce meaningless objects, as a result it will need to be inherited from 2 interfaces.
You have created a good flexibility, deeper, but it will cause problems. Either allow \JsonResponse
to be returned, or return an object of a special interface from which \JsonResponse
will be inherited. i.e. inherit \JsonResponse
also from the \Joomla\CMS\String\StringableInterface
interface
but there is no simplicity in this solution
It is transitioning to native PHP Stringable
, which we cannot use now because some backward compatibility problem can happen in some edje cases.
The idea is the response can be anything, as long as it is Stringable, and so can be rendered in browser.
Example for ?format=xml
:
$response = new class () implements StringableInterface {
public $potatoXml;
public function __toString(): string
{
return $this->potatoXml->asXML();
}
};
$response->potatoXml = $potatoXml;
The idea is the response can be anything, as long as it is Stringable, and so can be rendered in browser.
Example for?format=xml
:
Of course, the string can be XML. But we are discussing the return inside the JSON condition. If the condition is JSON, then we need to check for JSON, or for JsonRespone
switch ($format) {
case 'json':
In this case, you need to have an interface for responses only. And the \JsonResponse
and \XMLResponse
classes inherit from the new interface.
StringableResponseInterface
so that it is enough to inherit only a choice either from the interface or only from the class. But at the same time, so that there was such an opportunity to use a choice of either that or that. And you suggest using both at once,
I just realized that there is no point in introducing a new interface. Before, it seemed to me that $var instanceof Stringtable
is equivalent to is_string($var)
. I thought that supposedly both variables to cast a string would give the same value. But it's obvious that the values are the same, but the condition is different.
This means that there is no point in introducing a new interface.
The condition is if the extension code returns an object $obj instanceof Stringable
, which means the object will be cast to a string, but if a string is returned from the extension code, then obviously it needs to be passed as a parameter to the object of the JsonResponse
class.
There is no point for a new interface.
But it's obvious that the values are the same
No. String is a string and Object is object (even when it implement Stringable).
new JsonResponse($string)
will produce: {"data":"the string value"}
new JsonResponse($stringableObject)
will produce : {"data":{ ... the object properties}}
The answer can be anything, including XML. I agree, maybe, but it will be another condition of the format, are we discussing ?format=json
, and json creation takes place inside the switch case 'Json' condition.
Inside the JSON condition, the \JSONResponse
object was created before, which means backward compatibility is quite simple, which is placed in the \JSONResponse
object, the parameter should not be a \JSONResponse
, this will already indicate that backward compatibility has been implemented.
Suppose you want new features to use any object to generate JSON, such an object can inherit the Stringable interface. In any case, there was no such opportunity before. The custom object will be converted to a string instead of a non-\JSONResponse
. But if the user wants to bring his object with the interface to a string and additionally serialize, then let his object itself lead to a string, and already return the string from the extension, this will allow the string to be serialized inside \JSONResponse
.
For the \JSONResponse
class, add the parent \Stringable
But I think that in any case, the interface you offer should be renamed to \StringtableResponseInterface
or don't use the new interface at all..
which means backward compatibility is quite simple
Nope, when existing extension provides result as JsonResponse
, then it will be wrapped in to another JsonResponse
. So in result we will have:
new JsonResponse($anotherJsonResponse)
will produce : {"data":{"data":{ ... the object properties}}}
.
Even if it does not make sense, it still changing the response that com_ajax will send. and that will break that extension.
new JsonResponse($anotherJsonResponse)
will produce :{"data":{"data":{ ... the object properties}}}
.
if (!($results instanceof Throwable) && $results instanceof Stringable)
At the same time, it is necessary to add to the \JSONResponse
e class
libraries/src/Response/JsonResponse.php
class JsonResponse implements Stringable{
}
If you do as you say, parents should do this interface for \JSONResponse
.
I like your idea, but it seems to me that it is possible to achieve an elegant solution without adding a new interface.
OR
if (!($results instanceof Throwable) && $results instanceof StringableResponseInterface)
At the same time, it is necessary to add to the \JSONResponse
e class
libraries/src/Response/JsonResponse.php
class JsonResponse implements StringableResponseInterface{
}
Where StringableResponseInterface
it is joomla new interface
No, JsonResponse
already implements Stringable
implicitly.
Unlike most interfaces, Stringable is implicitly present on any class that has the magic __toString() method defined
And currently we cannot add our StringableInterface
to it, because it will change response format. In reasons I wrote earlier.
Currently developers who wants a custom response should implement StringableInterface
on their own. And in future (~joomla 7), we change com_ajax to use PHP native Stringable
, and all will continue to work without breaks.
You right
Maybe?
if (!($results instanceof Throwable)
&& ($results instanceof \Joomla\CMS\Response\JsonResponse || \StringableResponseInterface))
Where StringableResponseInterface
it is joomla new interface
Is it worth checking the object for JsonSerializable
interface?
https://www.php.net/manual/en/jsonserializable.jsonserialize.php
That a bit diferent approach, but could work in some cases also.
Why is this method bad?
#43530 (comment)
Because as for now it behaviors as "double wrapping", changing it will be b/c break.
#43530 (comment)
if (!($results instanceof Throwable)
&& ($results instanceof \Joomla\CMS\Response\JsonResponse || \StringableResponseInterface)){
echo $results;
}else{
echo new JsonResponse($results, null, false, $input->get('ignoreMessages', true, 'bool'));
}
Where StringableResponseInterface
it is joomla new interface
because if the object is JsonResponse, then there is no "double wrapping"
"will be".
But current behavior (in j3, j4, j5) is diferent
The previous behavior caused a nested object of class \JsonResponse
to be created in a new amendment object of class \JsonResponse
, now this object class \JsonResponse
will be cast to string .
Do you think there are people who returned the \JsonResponse
object from the extension, which was re-serialized in the \JsonResponse
object? If there are any, it will cause an error.
Well, then there is no solution.
Because the right decision will cause an error or misunderstanding.
Then let's assign this amendment to Joomla 6
. Where it will be explicitly stated that nested \JsonResponse
should not be in each other.
That what this PR is doing.
You already can use it with #43530 (comment) (when the PR will be merged). Then your code will continue to work in j7, without changes.
I know for sure that the interface name should indicate that the new interface was created to respond to the request.
There will be confusion in assigning the interface name, people will mistakenly inherit from the wrong interface, or for backward compatibility, you will have to store this file for a long time.
StringableResponseInterface
The full name Joomla\CMS\String\StringableInterface
, so it is fine
What value will this interface have other than the Ajax class of the component?
I have tested this item ✅ successfully on 4ea1349
I have tested this successfully. ( Thanks to @LadySolveig for making sure I didn't kill another test server ;) lol )
This pull request has been automatically rebased to 5.3-dev.
Title |
|
I have tested this item ✅ successfully on 4ea1349
Status | Pending | ⇒ | Ready to Commit |
rtc
Labels |
Added:
RTC
PR-5.3-dev
Removed: PR-5.2-dev |
Status | Ready to Commit | ⇒ | Fixed in Code Base |
Closed_Date | 0000-00-00 00:00:00 | ⇒ | 2024-10-17 07:09:05 |
Closed_By | ⇒ | rdeutz |
Thanks, I tested it. I spent many hours testing on my project.
It can be seen that you spent just as many hours writing, testing, finding bugs and writing documentation for PR.
I tested in JSON format, with error, without error, with data and with a
JsonResponse
object. I also set the parameters$input->set('ignoreMessages',false, 'bool'),
and also tested in RAW mode, also in different configurations.I also made an error throw with a message.
Now Joomla has become better. Now we can send customized messages as system messages in case of an error, and special notifications. Respect.
there were no problems during testing.