Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define a custom ExceptionStrategy in a ZF2 module

Hi all,

I've been struggling with this issue for more than a week and finally decided to ask for help hoping that someone knows the answer.

I am developing an application, which is using Google's Protocol Buffers as the data exchange format. I am using DrSlump's PHP implementation, which let's you populate class instances with data and then serialize them into a binary string (or decode binary strings into PHP objects).

I have managed to implement my custom ProtobufStrategy whose selectRenderer(ViewEvent $e) returns an instance of ProtobufRenderer in case the event contains an instance of ProtobufModel. The renderer then extracts my custom parameters from the model by calling $model->getOptions() to determine which message needs to be sent back to the client, serializes the data and outputs the binary string to php://output.

For it to make more sense, let's look at the following sample message:

message SearchRequest {
    required string query = 1;
    optional int32 page_number = 2;
    optional int32 result_per_page = 3;
}

If I wanted to respond to the client with this message, I would return something like this from my action:

public function getSearchRequestAction()
{
    [..]
    $data = array(
        'query'           => 'my query',
        'page_number'     => 3,
        'result_per_page' => 20,
    );
    return new ProtobufModel($data, array(
        'message' => 'MyNamespace\Protobuf\SearchRequest',
    ));
}

As you can see I am utilizing ViewModel's second parameter, $options, to tell which message needs to be serialized. That can then, as mentioned earlier, be extracted inside the renderer by calling $model->getOptions().

So far, so good. My controller actions output binary data as expected.

However, I am having issues with handling exceptions. My plan was to catch all exceptions and respond to the client with an instance of my Exception message, which looks like this:

message Exception {
    optional string message = 1;
    optional int32 code = 2;
    optional string file = 3;
    optional uint32 line = 4;
    optional string trace = 5;
    optional Exception previous = 6;
}

In theory it should work out of the box, but it does not. The issue is that Zend\Mvc\View\Http\ExceptionStrategy::prepareExceptionViewModel(MvcEvent $e) returns an instance of ViewModel, which obviously does not contain the additional $options information I need.

Also it returns ViewModel and not ProtobufModel, which means that the Zend invokes the default ViewPhpRenderer and outputs the exception as an HTML page.

What I want to do is replace the default ExceptionStrategy (and eventually also the RouteNotFoundStrategy) with my own classes, which would be returning something like this:

$data = array(
    'message'  => $e->getMessage(),
    'code'     => $e->getCode(),
    'file'     => $e->getFile(),
    'line'     => $e->getLine(),
    'trace'    => $e->getTraceAsString(),
    'previous' => $e->getPrevious(),
);
return new ProtobufModel($data, array(
    'message' => 'MyNamespace\Protobuf\Exception',
));

...and I can't find the way to do it...

I tried creating my own ExceptionStrategy class and alias it to the existing ExceptionStrategy service but Zend complained that a service with such name already exists.

I have a suspicion that I am on the right path with the custom strategy extension I can't find a way to override the default one.

I noticed that the default ExceptionStrategy and the console one get registered in Zend/Mvc/View/Http/ViewManager. I hope I won't have to add custom view managers to achieve such a simple thing but please, correct me if I'm wrong.

Any help will be appreciated!

like image 507
Andris Avatar asked Oct 02 '12 15:10

Andris


1 Answers

The easiest way is to do a little fudging.

First, register your listener to run at a higher priority than the ExceptionStrategy; since it registers at default priority, this means any priority higher than 1.

Then, in your listener, before you return, make sure you set the "error" in the the MvcEvent to a falsy value:

$e->setError(false);

Once you've done that, the default ExceptionStrategy will say, "nothing to do here, move along" and return early, before doing anything with the ViewModel.

While you're at it, you should also make sure you change the result instance in the event:

$e->setResult($yourProtobufModel)

as this will ensure that this is what is inspected by other listeners.

like image 95
weierophinney Avatar answered Oct 02 '22 19:10

weierophinney