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

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

Related

PHP Guzzle - do I need to set a handler if I'm just using it for middleware?

I've been researching a bit about Guzzle middleware and am a bit confused about some things.
My main goal: set up custom defined middleware to log requests and responses.
I'm planning on using composer require rtheunissen/guzzle-log-middleware because it seems to make creating exactly the kind of middleware I'm interested in much easier -- all the other middleware seems cumbersome or doesn't do what I want, too much magic, to little control.
So anyway, what I'm confused about is this whole 'handler' business as it relates to middleware. All the code examples on the Guzzle website create a curl handler, like so:
$stack = new HandlerStack();
$stack->setHandler(new CurlHandler());
$stack->push($middleware);
$client = new Client(['handler' => $stack]);
Do I need to call $stack->setHandler()? If I don't, will it just use the default handler? Is the default handler CurlHandler anyway? I just want guzzle to do what it normally does, and use that middleware package to just log the requests and responses, I don't want to tell Guzzle to use curl or anything else.
Handler is just a way of firing and handling responses of the requests.
Looking at the source code, it does not seem you can pass HandlerStack with just the middlewares only, you need a handler too. However you can create a default HandlerStack with it's create static method and then push your own middleware at the end. Like so:
$stack = HandlerStack::create();
$stack->push($middleware);
$client = new Client(['handler' => $stack]);
Here is the source code of how default HandlerStack gets created. Link
/**
* Creates a default handler stack that can be used by clients.
*
* The returned handler will wrap the provided handler or use the most
* appropriate default handler for your system. The returned HandlerStack has
* support for cookies, redirects, HTTP error exceptions, and preparing a body
* before sending.
*
* The returned handler stack can be passed to a client in the "handler"
* option.
*
* #param callable $handler HTTP handler function to use with the stack. If no
* handler is provided, the best handler for your
* system will be utilized.
*
* #return HandlerStack
*/
public static function create(callable $handler = null)
{
$stack = new self($handler ?: choose_handler());
$stack->push(Middleware::httpErrors(), 'http_errors');
$stack->push(Middleware::redirect(), 'allow_redirects');
$stack->push(Middleware::cookies(), 'cookies');
$stack->push(Middleware::prepareBody(), 'prepare_body');
return $stack;
}
$stack = new self($handler ?: choose_handler());
And here is choose_handler() function that determines which handler is best. Link
/**
* Chooses and creates a default handler to use based on the environment.
*
* The returned handler is not wrapped by any default middlewares.
*
* #throws \RuntimeException if no viable Handler is available.
* #return callable Returns the best handler for the given system.
*/
function choose_handler()
{
$handler = null;
if (function_exists('curl_multi_exec') && function_exists('curl_exec')) {
$handler = Proxy::wrapSync(new CurlMultiHandler(), new CurlHandler());
} elseif (function_exists('curl_exec')) {
$handler = new CurlHandler();
} elseif (function_exists('curl_multi_exec')) {
$handler = new CurlMultiHandler();
}
if (ini_get('allow_url_fopen')) {
$handler = $handler
? Proxy::wrapStreaming($handler, new StreamHandler())
: new StreamHandler();
} elseif (!$handler) {
throw new \RuntimeException('GuzzleHttp requires cURL, the '
. 'allow_url_fopen ini setting, or a custom HTTP handler.');
}
return $handler;
}
Guzzle documentation can be a bit annoying when it comes to figuring out niche things like this, don't be afraid to look at their source code to understand things better in the future.

How to roll back changes and display error when an event throws an exception in Laravel

TL;DR
I would like to know how I should handle an exception that occurs in an event and subsequently rollback changes made to the database prior to the exception, provide feedback to the user and write a test for all of this too.
Full Details
In my Laravel web application I have four models: Applicant, Application, ApplicationAction and Document. An Applicant can have one Application which can have many ApplicationActions. Also, an Application can have many Documents.
Conceptually, within the application when an ApplicationAction is created it is applied to an Application which in turn has its status attribute updated. For example, if an ApplicationAction is of type reject the Application's status then becomes rejected.
Getting into the code where this all happens, I have an ActionController with a store() method that creates a new ApplicationAction:
ActionController.php
public function store(StoreActionFormRequest $request)
{
$actionDetails = (new ApplicationAction)->createFromRequest($request);
}
ApplicationAction.php
public function createFromRequest(Request $request)
{
return $this->create([
'action' => $request->action,
'action_meta' => $this->actionMeta($request),
'details' => $request->details,
'details_external' => $request->details_external,
'action_by' => $request->user()->id,
'application_id' => $request->application->id,
]);
}
On the creation of a new ApplicationAction the ApplicationActionCreated event is fired with a listener that modifies the Application's status attribute based on ApplicationAction. I am not including the code in as it's not relevant.
When the Application is updated in this process (i.e. its status is changed) an event is triggered: ApplicationUpdated. A listener for this event is the NotifyApplicationWasUpdated listener which sends an email to the Applicant:
NotifyApplicationWasUpdated.php
/**
* Handle the event.
*
* #param ApplicationUpdated $event
* #return void
*/
public function handle(ApplicationUpdated $event)
{
if ($mailable = MailableType::resolve($event->application)) {
$to = $event->application->applicant->user->email;
$from = $event->application->applicant->dispatchEmail('verification');
Mail::to($to)
->bcc($from)
->send(new $mailable($event->application));
}
}
The mailable is determined dynamically (code not relevant) and within the mailable an attachment is added to the email:
A dynamically determined MailableClass
/**
* Build the message.
*
* #return $this
*/
public function build()
{
//... some code
$documents = $this->application->documents->whereIn('type', ['some-document', 'some-other-document']);
$documents->each(function ($document, $key) {
// TODO: check the file exists first
$this->attach(storage_path($document->file_path));
});
return $this->from($from)->view($view);
}
I would like to handle the FileNotFound exception if the file to be used as an attachment cannot be found. If this exception occurs then the email would not be sent, so I thought it would be a good idea to rollback the changes made to the database prior to the exception which would include deleting the newly made ApplicationAction and returning the Application's status to its previous value.
I would also like to provide the user appropriate feedback such as the file intended to be used as an attachment couldn't be found and also that the ApplicationAction was not created and the Application status was not modified.
The approaches I have considered to handling the FileNotFound exception in the event that the Document cannot be found when executing this line of code $this->attach(storage_path($document->file_path)); is to check if the file exists first and if it doesn't then to rollback changes and respond to user with an error:
if(!file_exists(storage_path($document->file_path))) {
// roll back DB changes and show user error page
$this->application->rollback(); // some method that sets the status to its previous value, not implemented it yet
$this->application->lastAction()->delete(); // delete the last action
// then throw an exception to be handled by Laravel exception handler
// which in turn responds to user with an appropriate message?
}
$this->attach(storage_path($document->file_path));
...but I'm a bit unsure on the details of how to rollback the DB changes and show the error to the user. I could either use the above approach, having a Application rollback() method etc. or instead wrapping the controller store() action in a try-block and DB::transaction()/commit(), which would look something like this:
ActionController.php
public function store(StoreActionFormRequest $request)
{
DB::beginTransaction();
try {
$actionDetails = (new ApplicationAction)->createFromRequest($request);
} catch(FileNotFoundException $e) {
DB::rollback();
throw $e;
}
DB::commit();
}
Then, as mentioned before, use the Laravel exception handler which in turn responds to user with an appropriate error message:
Handler.php
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
if ($exception instanceof FileNotFoundException) {
return response()->view('errors.file_not_found', [], 500);
}
return parent::render($request, $exception);
}
If I did use this approach would it be possible to pass some specific text explaining to the user the email was not sent and why? Would I be better off creating my own EmailAttachmentFileNotFoundException and have it hardcoded in the view and using this in Handlers render() function?
Finally, I would most importantly like to ask how may I write a test for all the above? As most of the logic I'm trying to implement is around an event, which is currently occuring synchronously, how would I write a test for this specific event/listener because I already have something testing the controller action in the form of an integration test but it all it tests for is if the event is fired. How would I then write an isolated test of a test that goes smoothly, i.e. the email is sent successfully and secondly how would I write a test that goes wrong and check it's handled correctly, i.e. email attachment is not found and FileNotFound exception is triggered then check the DB stuff is rolled back and the user gets the correct error page?
Any help would be appreciated and if this question can be made any shorter please feel free to edit away!

Call a method from another class in another application with FOSTRestBundle in Symfony

I have the class file ex: Stats.php when i want to give an array with information from another method in another application and class file ex: Information.php.
File Stats.php
public function getStats()
{
$myInformations = // here i want to get information from Information.php
.
.
.
return $myInformations;
}
In different application.
File Informations.php
/**
* Get all locales
* #FOS\View()
* #FOS\Get("/locales")
* #param ParamFetcherInterface $paramFetcher
* #return mixed
*/
public function getLocales(ParamFetcherInterface $paramFetcher)
{
.
.
.
return $locales;
}
How I call function getLocales from url: http://myhost.com/api/locales in function getStatus()?
When you are using the GuzzleBundle as mentioned in your comment. You can just inject a client into the class containing getStats() or by making it ContainerAware and retrieving the guzzle-client by its service id from the container. If you don't have to set default options for your client, i.e. you just want to access a url you assume is always available for all environments from all places you could just create a new client with default values:
$guzzleClient = new GuzzleHttp\Client();
Making a request with the client is described in the guzzle docs in the Quickstart in section Sending Requests:
$response = $guzzleClient->get('http://myhost.com/api/locales')
When the request was successful you can retrieve the locales by calling:
$content = $response->getBody()->getContent();
Either casting getBody() to string or using getContent() is important here. Again refer to Guzzle's documentation particularly the section on using responses.
For example if you send a json-encoded string you can do something like:
$encodedLocales = $response->getBody()->getContent();
$decodedLocales = json_decode($encodedLocales, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception(json_last_error_msg());
}
There are many things that should be said about this approach such as it's fragile because it relies on a working network connection possibly to a different server, it's not easily testable, transfer exceptions are not handled gracefully, etc. pp. but for a simple proof of concept or asa starting point this should suffice.

Luracast Restler Multiple Authentication Classes Not Allowing Access

I have two authentication classes defined.
API Keys (APIKeyAuth)
OAUTH2 (OAUTH2Server)
In my index.php I have the following defined
$r = new Restler();
$r->addAuthenticationClass('APIKeyAuth');
$r->addAuthenticationClass('OAUTH2Server');
I then protect one of the rest methods for APIKeyAuth
/**
* #access protected
* #class APIKeyAuth{#requires apikey}
*/
public function .......etc
If I debug it , it goes through the first step and $authObj (see code below from restler.php) will
be APIKeyAuth. It checks __isAllowed and returns true ... which is good.
It then however goes through OAUTH2Server (which in my opinion it shouldn't as the rest method has
been decorated to use APIKeyAuth.
So it goes through and __isAllowed in OAUTH2Server is false so then the user will get a Unauthorzied response.
foreach ($this->authClasses as $authClass) {
$authObj = Scope::get($authClass);
if (!method_exists($authObj,
Defaults::$authenticationMethod)
) {
throw new RestException (
500, 'Authentication Class ' .
'should implement iAuthenticate');
} elseif (
!$authObj->{Defaults::$authenticationMethod}()
) {
throw new RestException(401);
}
}
Do I need to alter the OAUTH2 Server to check if its using an API Key and add logic ? (seems wrong approach).
Restler upto RC5 handles authentication classes serially, meaning that all the authentication classes must return true to go through the protected api call
Since RC6 this has changed to parallel, meaning that any one of the authentication class can allow access to the protected api

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!
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.

Categories