Define a custom ExceptionStrategy in a ZF2 module - php

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!

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.

Related

Dead lettering with php-amqplib and RabbitMQ?

I'm just starting out in using php-amqplib and RabbitMQ and want a way to handle messages that, for whatever reason, can't be processed and are nack'd. I thought that one way people handle this is with a dead letter queue. I'm trying to set this up but have not had any luck so far and hope someone could offer some suggestions.
My initiation of the queues looks a little something like:
class BaseAbstract
{
/** #var AMQPStreamConnection */
protected $connection;
/** #var AMQPChannel */
protected $channel;
/** #var array */
protected $deadLetter = [
'exchange' => 'dead_letter',
'type' => 'direct',
'queue' => 'delay_queue',
'ttl' => 10000 // in milliseconds
];
protected function initConnection(array $config)
{
try {
$this->connection = AMQPStreamConnection::create_connection($config);
$this->channel = $this->connection->channel();
// Setup dead letter exchange and queue
$this->channel->exchange_declare($this->deadLetter['exchange'], $this->deadLetter['type'], false, true, false);
$this->channel->queue_declare($this->deadLetter['queue'], false, true, false, false, false, new AMQPTable([
'x-dead-letter-exchange' => $this->deadLetter['exchange'],
'x-dead-letter-routing-key' => $this->deadLetter['queue'],
'x-message-ttl' => $this->deadLetter['ttl']
]));
$this->channel->queue_bind($this->deadLetter['queue'], $this->deadLetter['exchange']);
// Set up regular exchange and queue
$this->channel->exchange_declare($this->getExchangeName(), $this->getExchangeType(), true, true, false);
$this->channel->queue_declare($this->getQueueName(), true, true, false, false, new AMQPTable([
'x-dead-letter-exchange' => $this->deadLetter['exchange'],
'x-dead-letter-routing-key' => $this->deadLetter['queue']
]));
if (method_exists($this, 'getRouteKey')) {
$this->channel->queue_bind($this->getQueueName(), $this->getExchangeName(), $this->getRouteKey());
} else {
$this->channel->queue_bind($this->getQueueName(), $this->getExchangeName());
}
} catch (\Exception $e) {
throw new \RuntimeException('Cannot connect to the RabbitMQ service: ' . $e->getMessage());
}
return $this;
}
// ...
}
which I thought should set up my dead letter exchange and queue, and then also set up my regular exchange and queue (with the getRouteKey, getQueueName, and getExchangeName/Type methods provided by extending classes)
When I try to handle a message like:
public function process(AMQPMessage $message)
{
$msg = json_decode($message->body);
if (empty($msg->payload) || empty($msg->payload->run)) {
$message->delivery_info['channel']->basic_nack($message->delivery_info['delivery_tag'], false, true);
return;
}
// removed for post brevity, but compose $cmd variable
exec($cmd, $output, $returned);
if ($returned !== 0) {
$message->delivery_info['channel']->basic_ack($message->delivery_info['delivery_tag']);
} else {
$message->delivery_info['channel']->basic_nack($message->delivery_info['delivery_tag']);
}
}
But I get back the error Something went wrong: Cannot connect to the RabbitMQ service: PRECONDITION_FAILED - inequivalent arg 'x-dead-letter-exchange' for queue 'delay_queue' in vhost '/': received 'dead_letter' but current is ''
Is this the way I should be setting up dead lettering? Different examples I've seen around all seem to show a bit of a different way of handling it, none of which seem to work for me. So I've obviously misunderstood something here and am appreciative of any advice. :)
Setting up (permanent) queues and exchanges is something you want to do once, when deploying code, not every time you want to use them. Think of them like your database schema - although the protocol provides "declare" rather than "create", you should generally be writing code that assumes things are configured a particular way. You could build the first part of your code into a setup script, or use the web- and CLI-based management plugin to manage these using a simple JSON format.
The error you are seeing is probably a result of trying to declare the same queue at different times with different parameters - the "declare" won't replace or reconfigure an existing queue, it will treat the arguments as "pre-conditions" to be checked. You'll need to drop and recreate the queue, or manage it via the management UI, to change its existing parameters.
Where run-time declares become more useful is when you want to dynamically create items in your broker. You can either give them names you know will be unique to that purpose, or pass null as the name to receive a randomly-generated name back (people sometimes refer to creating an "anonymous queue", but every queue in RabbitMQ has a name, even if you didn't choose it).
If I'm reading it correctly, your "schema" looks likes this:
# Dead Letter eXchange and Queue
Exchange: DLX
Queue: DLQ; dead letter exchange: DLX, with key "DLQ"; automatic expiry
Binding: copy messages arriving in DLX to DLQ
# Regular eXchange and Queue
Exchange: RX
Queue: RQ; dead letter exchange: DLX, with key "DLQ"
Binding: copy messages from RX to RQ, optionally filtered by routing key
When a message is "nacked" in RQ, it will be passed to DLX, with its routing key overwritten to be "DLQ". It will then be copied to DLQ. If it is nacked from DLQ, or waits in that queue too long, it will be routed round to itself.
I would simplify in two ways:
Remove the dead letter exchange and TTL from the "dead letter queue" (which I've labelled DLQ); that loop's likely to be more confusing than useful.
Remove the x-dead-letter-routing-key option from the regular queue (which I've labelled RQ). The configuration of the regular queue shouldn't need to know whether the Dead Letter Exchange has zero, one, or several queues attached to it, so shouldn't know the name of that other queue. If you want nacked messages to go straight to one queue, just make it a "fanout exchange" (which ignores routing keys) or a "topic exchange" with the binding key set to # (which is a wildcard matching all routing keys).
An alternative might be to set x-dead-letter-routing-key to the name of the regular queue, i.e. to label which queue it came from. But until you have a use case for that, I'd keep it simple and leave the message with its original routing key.

Is there a way of using CakeResponse Object in unconstructed Class?

I'm currently doing a custom ErrorHandler for an App in CakePHP.
The reason? Well, bots are always trying to find stuff in your servers and sometimes they provoke exceptions and or errors.
The idea with this ErrorHandler is to filter requests and respond with the appropriate headers and prevent further request damage by handling this type of requests and make it transparent for the user client (because it might affect JavaScript for instance).
And what better way than to use the Framework's functionality, right?
The thing is that since this ErrorHandler is being used statically,
well, there is no constructor so nothing inherits anything, it doesn't
matter if you instantiate any other CakePHP Object.
What would be the appropriate way to use CakeResponse Object?
CakePHP's configuration:
app/Config/bootstrap.php:
App::uses('CustomErrorHandler', 'Lib');
app/Config/core.php:
// Error and exception handlers.
Configure::write('Error', array(
'handler' => 'CustomErrorHandler::handleError',
'level' => E_ALL & ~E_DEPRECATED,
'trace' => true
));
Configure::write('Exception', array(
'handler' => 'CustomErrorHandler::handleException',
'renderer' => 'ExceptionRenderer',
'log' => true
));
app/Lib/CustomErrorHandler.php:
... rest of class code ...
/**
* Named after convention: This method receives all CakePHP's
* errors and exceptions…
*
* #param array $e The exception object.
* #return mixed Returns the error handling or header redirection.
*/
public static function handleException($e)
{
$message = (string) $e->getMessage();
$code = (int) $e->getCode();
$file = (string) $e->getFile();
$line = (string) $e->getLine();
// If it's a Blacklist resource exception it will log it and redirect to home.
if (self::__isResourceException($message))
{
return self::__dismissError();
}
return parent::handleException($e);
}
/**
* This method redirects to home address using CakeResponse Object.
*
* #return mixed
*/
private static function __dismissError()
{
return (new CakeResponse)->header(array(
'Location' => self::$redirectUrl
));
}
}
UPDATE 2:
Will try a small layer over the ExceptionRenderer.
There's not really a point in using a CakeResponse object there... it would work if you'd call send() on it, however with only that one header, there's no advantage over using header() directly.
That being said, you are however in any case ditching the Controller.shutdown and Dispatcher.afterDispatch events. They are being dispatched in ExceptionRenderer::_shutdown(), and are often used to set response headers (CORS related headers are a good example), so you should figure whether its OK to drop them, or maybe even required.
If you need to keep the shutdown and afterDispatch events, then you should either fire them on your own, or maybe even use an extended ExceptionRenderer that handles that specific type of exception and sends an empty reponse with your location header added.
See also
Cookbook > Development > Exceptions > Using a custom renderer with Exception.renderer to handle application exceptions

Infusionsoft PHP CC info with Novak Solutions SDK

I am having issues getting a users credit card info using Novak Solutions Infusionsoft SDK. Both systems say that I can use a "Find by Field" query but I seem to get an error with the CC object.
So it would look something like this:
Infusionsoft_DataService::findByField(new Infusionsoft_CreditCard(), 'ContactId', 9 (the id), null, 0, false, null);
Response:
Fatal error: Uncaught [NoFieldAccess]Access denied to field CreditCard.CardNumber Attempted: 3 time(s).
The DataService seems to work with all object but the Infusionsoft_CreditCard() one.
As you can see in Infusionsoft API documentation, CreditCard.CardNumber field is not readable, Add access is only allowed.
To avoid the error throwing, the simplest fix is to pass the 6th parameter: array of $returnFields. For example:
Infusionsoft_DataService::findByField(new Infusionsoft_CreditCard(), 'ContactId', 9, null, 0, ['Id', 'Last4', 'ContactId'], null);
For another solution take a look at Infusionsoft_DataService.load() method implementation, specifically lines 101 to 105.
if(!$returnFields){
$object->removeRestrictedFields();
$returnFields = $object->getFields();
$object->addRestrictedFields();
}
These lines either need to replace lines 40 to 42 in the same file (changing findByField() method implementation, and I'm just mentioning here it would be better to further refactor the code after that, abstracting these lines for multiple usages).
Or use them in your code (with needed updates) to get $requiredFields without read-restricted fields for further passing them to Infusionsoft_DataService.findByField() method as shown above.
One more useful reference is Infusionsoft_CreditCard.removeRestrictedFields() method implementation
Try creating the Infusionsoft_CreditCard object first then removing the restricted fields before passing it on to the query!
$CC = new Infusionsoft_CreditCard();
$CC->removeRestrictedFields();
// Now continue with your former query and use the above object.
Infusionsoft_DataService::findByField( $CC, 'ContactId', 9, null, 0, false, null);
// Or use the query method.
Infusionsoft_DataService::query( $CC, array( 'ContactId' => 9 ) );
This should return all the CC fields except the restricted ones.

Silex FormServiceProvider and form.secret parameter

I am using the FormServiceProvider from Silex and reading through the documentation it explains how it has one parameter called form.secret which I assumed meant constructing the provider with this:
$app->register(new Silex\Provider\FormServiceProvider(), [
'form.secret' => 'SECRET HERE'
]);
The problem is however when I look through the source code for this file I cannot see a constructor where this parameter would get used. Only seeing it set internally within the container to md5(__DIR__).
https://github.com/silexphp/Silex/blob/master/src/Silex/Provider/FormServiceProvider.php#L48
Or would it be simply a case of not providing form.secret when constructing and simply setting $app['form.secret'] = 'SECRET HERE' after the provider has been registered?
Am I right in this assumption or I am I missing something?
You can see it being used in line 100, when $app["form.csrf_provider"] is first accessed:
$app['form.csrf_provider'] = function ($app) {
if (isset($app['session'])) {
return new SessionCsrfProvider($app['session'], $app['form.secret']);
}
return new DefaultCsrfProvider($app['form.secret']);
};
Since whatever you pass is ignored and overwritten with the md5 call you mention, correct usage would be:
$app->register(new FormServiceProvider());
$app["form.secret"] = "foo";

How declare WSDL for PHP function that returns multiple TYPES?

I'm writing a PHP web service and one function. I want to set up a web service in PHP. I need to generate the WSDL description for this web service so it's accessible from I.e. visual studio. It takes documents / search strings as inputs and recommends similar documents as output. I return an array with a first element resultCode (int) which shows if the operation was a success (1) or a failure (0). The second element, however, could either be an error message (string) which tells the user what went wrong, or a complex return type like an array with subelements for the different matching articles, i.e.
array( array("heading"=>"article heading", "articleId"=>12345, "text"=>"body text of article"), array( ... ), ... ). I need to know how to generate/write the WSDL for that return type or how to do that in NuSOAP. How would you do that?
This is some of the code that I'm currently using to set up the service.
$server->wsdl->addComplexType(
'returnStructBase',
'complexType',
'struct',
'all',
'',
array('resultCode' => array('name'=>'resultCode', 'type'=>'xsd:int'),
'resultData' => array('name'=>'resultData', 'type'=>'xsd:anyType')
)
);
$server->wsdl->addComplexType(
'returnStructArray',
'complexType',
'array',
'',
'SOAP-ENC:Array',
array(),
array(
array('ref' => 'SOAP-ENC:arrayType',
'wsdl:arrayType' => 'tns:returnStructBase[]'
)
),
'tns:returnStructArray'
);
$server->register("GetRecommendations", array('username'=>'xsd:string', 'password'=>'xsd:string','articleId'=>'xsd:string',
'text'=>'xsd:string', 'returnText'=>'xsd:boolean'), array('return'=>'tns:returnStructArray'), $namespace, $namespace . '#getRecommendations', 'rpc', 'encoded', ' ... ');
Maybe PHP's loose typing made me use a bad design for a return type and I need to use something else?
Any recommendations welcome.
You could use xsd:anytype. In fact I'd not recomend it, since type based environments like .NET and Java will not be able to handle your wsdl.
To get a clean solution I'd rethink that design php seduced you to... ;)
You can return
First element: Error code, 0 = bad, 1 = good
Second element: Error message, empty if we are good
Third element: your complex type, empty one if we are bad.
You should always return the same structure. In the case of a failure you should use exceptions just like in normal usage by using SOAP faults:
http://www.ibm.com/developerworks/webservices/library/ws-tip-jaxrpc.html

Categories