Yii2 beginner. Display REST exception handling - yii2

In my controller, I extend the Controller class instead of ActiveController
I have a simple actionIndex() method:
public function actionIndex(){
return json_encode(["text" => "some text"]);
}
When I access the certain route in browser, in the debugger, I see that this function is executing (the breakpoint stops inside the function), but I get 500 status code (Internal server error). How can I find the cause of the error? I have implemented the actionError() method, but it is not executing.
public function actionError() {
$exception = Yii::$app->errorHandler->exception;
if ($exception !== null) {
return $this->render('error', ['exception' => $exception]);
}
}
If I put the logic of the actionError() method in the actionIndex(), the $exception variable is null
The only output I get is:
{
name: "PHP Warning",
message: "Expected array for frame 0",
code: 2,
type: "yii\base\ErrorException",
file: "Unknown",
line: 0,
stack-trace: []
}
but it's warning, not error. May this cause the status code 500?
How can I get the error? Thanks!

According to this thread https://github.com/yiisoft/yii2/issues/11120 this is related to Xdebug for Yii 2 < 2.0.9.
When handling an exception, XDebug modifies the exception and Yii2 was not able to handle it correctly.
So several possible solutions here
The best idea is to update Yii2 to a version >= 2.0.9
Also you sould correct the source of the exception (the warning). It is never a good idea to have warnings. It can hide some more issues.
And as a workaround, you can disable XDebug. It is very useful during development but must be disabled in production in all cases.

Don't know about your error, but there is generally no need to return a response as json encoded. Yii checks the Accept-header of the request and adjust output accordingly. In your case I would just use:
public function actionIndex()
{
return ["text" => "some text"];
}
Possibly it might also solve your error...

Related

How to add error code along with validation messages through rules in yii2?

We are creating postman collections for all of our end point built on the top of yii2. In all collections, we are using bdd scenarios where we need to test error messages and of course successes. We can compare with the returned error messages to get assertions passed but messages can be changed which eventually make our test fail.
So instead of comparing with string message, I want to compare it with error codes. but i don't know if it is possible to raise errors like exceptions which can be used to test using codeception or endpoints with the tools like postman or swagger.
I have solved this problem. To achieve my requirement i need to extend Yii2 validators where i overwrite "validateAttribute" method. Here i can raise required exception with the code. And this has also allowed me to pass exception type and code directly from the rules.
namespace common\components\validators;
use yii\base\InvalidArgumentException;
class RequiredValidator extends \yii\validators\RequiredValidator
{
public $code;
public $exception;
public function validateAttribute($model, $attribute)
{
parent::validateAttribute($model, $attribute); // TODO: Change the autogenerated stub
if($this->exception){
throw new $exception($this->formatMessage($this->message, ['attribute' => $attribute]),$this->code);
}
}
}
This has allowed me to define rules like following.
public function rules(){
return [
['param', RequiredValidator::className(), 'code' => 100102, 'exception' => 'UserCustomException']
];
}

Xamarin.Forms Plugin.Geofence giving System.InvalidCastException: Specified cast is not valid

I am using Plugin.Geofence i Implemented its interface,Installed its dependencies i.e. .NETStandard 2.0 and Xamarin.GooglePlayServices.Location.
I am getting this exceptions System.InvalidCastException: Specified cast is not valid. when i run StartMonitoring method.
I am calling this method in the App() Constructor.
public App ()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
CrossGeofence.Current.StartMonitoring(new GeofenceCircularRegion("MyRegion", 31.475085, 74.305833, 200)
{
//To get notified if user stays in region for at least 5 minutes
NotifyOnStay = true,
StayedInThresholdDuration = TimeSpan.FromMinutes(5)
});
}
Tried everything but couldn't resolve this
For anyone looking for a solution to this, ensure that before calling "CrossGeofence.Current" that the correct permissions have been requested and granted. If not, you'll see this error.

ActionResult , exception with new Jodd version

I have some code which works Ok with jodd 3.9.1 and I want to upgrade it to jodd version 4.1.4. but I have some errors:
Example:
#POST #Action
public String save(){
... some code for validation
if (!valid){
return VTOR_JSON; //Error
}
service.store(Object);
return NONE; //Error
}
Exception:
jodd.madvoc.MadvocException: Invalid result name:vtor_json (or Invalid result name:none)
at jodd.madvoc.component.ResultsManager.lookup(ResultsManager.java:181)
at jodd.madvoc.component.MadvocController.render(MadvocController.java:183)
at jodd.madvoc.ActionRequest.lambda$createExecutionArray$0(ActionRequest.java:208)
at jodd.madvoc.ActionRequest.invoke(ActionRequest.java:237)
at jodd.madvoc.component.MadvocController.invoke(MadvocController.java:154)
at jodd.madvoc.MadvocServletFilter.doFilter(MadvocServletFilter.java:108)
What is the problem?
How to solve this?
igr, Thanks for your answer.
I try your sugestion and that's works.
But, I have some additional code:
Example:
public Object save(){
if (!authorized()){
return Redirect.to(AuthAction.ALIAS_ACCESS_DENIED); //don't work
}
if (!valid){
return JsonResult.of(vtor.getViolations()); //work
}
....
return null; //work
}
Here is debug output
38675 [DEBUG] j.m.r.AbstractTemplateViewActionResult.render:79 - new target: /accessDenied:
38676 [DEBUG] j.m.r.ServletDispatcherActionResult.targetExists:99 - target check: /accessDenied.jspf
38676 [DEBUG] j.m.r.ServletDispatcherActionResult.targetExists:99 - target check: /accessDenied.jsp
38676 [DEBUG] j.m.r.AbstractTemplateViewActionResult.render:90 - target found: /accessDenied.jsp
But , never redirect to accessDenied.html.
I try with Object but it doesn't work.
Any suggest?
I found the solution:
if (!valid){
return JsonResult.of(VtorUtil.createViolationsJsonString(request, vtor.getViolations())).value();
}
It's very easy like you said, igr ;-)
Let me explain:)
With Jodd v4 we don't have anymore the VTOR_JSON type of results - those that return strings. (The string constant is still there, but it is not used). We had to remove this way of returning results as it is not scalable - simply can not add easily different result types.
How to fix it?
We are back to basic :) VTOR_JSON was returning the JSON with the error. So now you have few options (as you can see here):
return an object annotated with #RenderWith annotation;
put a #RenderWith annotation on the action;
return a PathResult helper object;
specify the result in the action configuration.
In a short, you should do this by yourself - but don't worry its super easy. For example, you can return:
if (!valid) {
return JsonResult.of(violations());
}
return null;
If null is not working, try returning new NoneActionResult() (sorry, we will add more convenient way).
Checkout the results package: results.
Sorry for this transition, it is really for good purpose. We will add more helpers like JsonResult. And of course, if you need any support, let us know.

Send a specific response, or at least a specific HTTP status code, using an exception

In Django, there are a couple of exceptions that are designed to be intercepted by the framework and turned into specific HTTP response codes, such as 404 Not Found and 403 Forbidden.
This is especially useful for request validation, because it allows you to factor out common validation logic into utility functions and cleanup your controller actions.
Whenever the utility functions decide that the current request must be aborted with a specific HTTP error code, they can do so by throwing the relevant exception, without any support code in the controller action, in the form of a return statement or a try/catch.
For example, given a tree of nested REST resources:
static mappings = {
"/authors" (resources: "author") {
"/sagas" (resources: "saga") {
"/books" (resources: "book") {
}
}
}
Then the URL pattern for the Book resource is /authors/$authorId/sagas/$sagaId/books/$id, which means that any of the show(), delete(), or update() actions in BookController have this signature and must include some boilerplate validation logic:
def actionName(int authorId, int sagaId, Book book) {
// -- common validation logic ----------
// fetch parent objects
def author = Author.get(authorId)
def saga = Saga.get(sagaId)
// check that they exists
if (author == null || saga == null || book == null) {
return render(status: NOT_FOUND)
}
// check consistency
if (book.author != author || book.saga != saga || saga.author != author) {
return render(status: BAD_REQUEST)
}
// -- end of commond code --------------
...
}
What is the Grails way of factoring this out into a common method, while still allowing it to terminate request processing whenever an exceptional condition occurs?
I would think the best way is a NotFoundException, ForbiddenException, BadRequestException, and so on, or maybe a generic exception that accepts a HTTP status code. Is there anything like it in Grails? If not, where is the best place to add it? A filter?
Edit: I see now that the standard method is to add an error controller with a matching URL pattern, such as:
"500" (controller: "error")
The problem with this is that Grails will still log full stacktraces for all exceptions, including those that are not programming errors. This spams log files with all sorts of useless tracebacks.
Is there a solution?
You catch the exception in the beforeInterceptor closure of your controller. I resolved this same problem by examining the exception thrown and then acting accordingly. For example:
class BaseController {
/**
* Define DRA exception handlers. This prevents the default Grails
* behavior of returning an HTTP 500 error for every exception.
*
* Instead the exceptions are intercepted and modified according to
* the exception that was thrown. These exceptions are not logged
* whereas application exceptions are.
*/
def beforeInterceptor = {
request.exceptionHandler = { exception ->
def cause = exception.cause
def exceptionBody = [:]
if(cause.class == BadRequestException) {
response.setStatus(HttpStatus.BAD_REQUEST.value()) // HTTP 400 BAD REQUEST
exceptionBody.httpStatus = HttpStatus.BAD_REQUEST.value()
exceptionBody.error = cause.message
}
// render the exception body, the status code is set above.
render exceptionBody as JSON
return true
}
}
}
In order to get this to work you will have to create an ErrorController or something where all server errors are processed and rendered. For example:
class ErrorController {
def serverError() {
def handler = request.exceptionHandler
if(handler) {
request.exceptionHandler = null
if(handler.call(request.exception)) {
return
}
}
}
I have tested this an it does work. I copied the code from a running project that I have been working on. You can build out the if statement in the beforeInterceptor to catch any type of Exception you wish.

How to know exception occurred within grails transaction?

I have a service method which does some operation inside a transaction.
public User method1() {
// some code...
Vehicle.withTransaction { status ->
// some collection loop
// some other delete
vehicle.delete(failOnError:true)
}
if (checkSomething outside transaction) {
return throw some user defined exception
}
return user
}
If there is a runtime exception we dont have to catch that exception and the transaction will be rolled back automatically. But how to determine that transaction rolled back due to some exception and I want to throw some user friendly error message. delete() call also wont return anything.
If I add try/catch block inside the transaction by catching the Exception (super class) it is not getting into that exception block. But i was expecting it to go into that block and throw user friendly exception.
EDIT 1: Is it a good idea to add try/catch arround withTransaction
Any idea how to solver this?? Thanks in advance.
If I understand you question correctly, you want to know how to catch an exception, determine what the exception is, and return a message to the user. There are a few ways to do this. I will show you how I do it.
Before I get to the code there are a few things I might suggest. First, you don't need to explicitly declare the transaction in a service (I'm using v2.2.5). Services are transactional by default (not a big deal).
Second, the transaction will automatically roll back if any exception occurs while executing the service method.
Third, I would recommend removing failOnError:true from save() (I don't think it works on delete()... I may be wrong?). I find it is easier to run validate() or save() in the service then return the model instance to the controller where the objects errors can be used in a flash message.
The following is a sample of how I like to handle exceptions and saves using a service method and try/catch in the controller:
class FooService {
def saveFoo(Foo fooInstance) {
return fooInstance.save()
}
def anotherSaveFoo(Foo fooInstance) {
if(fooInstance.validate()){
fooInstance.save()
}else{
do something else or
throw new CustomException()
}
return fooInstance
}
}
class FooController {
def save = {
def newFoo = new Foo(params)
try{
returnedFoo = fooService.saveFoo(newFoo)
}catch(CustomException | Exception e){
flash.warning = [message(code: 'foo.validation.error.message',
args: [org.apache.commons.lang.exception.ExceptionUtils.getRootCauseMessage(e)],
default: "The foo changes did not pass validation.<br/>{0}")]
redirect('to where ever you need to go')
return
}
if(returnedFoo.hasErrors()){
def fooErrors = returnedFoo.errors.getAllErrors()
flash.warning = [message(code: 'foo.validation.error.message',
args: [fooErrors],
default: "The foo changes did not pass validation.<br/>${fooErrors}")]
redirect('to where ever you need to go')
return
}else {
flash.success = [message(code: 'foo.saved.successfully.message',
default: "The foo was saved successfully")]
redirect('to where ever you need to go')
}
}
}
Hope this helps, or gets some other input from more experienced Grails developers.
Here are a few other ways I've found to get exception info to pass along to your user:
request.exception.cause
request.exception.cause.message
response.status
A few links to other relevant questions that may help:
Exception handling in Grails controllers
Exception handling in Grails controllers with ExceptionMapper in Grails 2.2.4 best practice
https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/exception/ExceptionUtils.html