Phalcon ActionFilters - php

I'm learning Phalcon (trying REST API in multi-module application template), and I did simple checking for each request, "does this request contain specific header" for example x-api-key (something like ActionFilters in ASP.NET MVC).
I tried doing it with annotations, plugins, beforeExecuteRoute, and beforeException. But when I write in one of them throw new \Exception("Some exception", 500); then Phalcon returns a blank page without an exception message and code. AFAIK this is a known bug.
I tried to do it with the dispatcher in beforeException:
public function beforeException($event, $dispatcher, $exception)
{
if ($exception instanceof \Phalcon\Http\Request\Exception)
{
$dispatcher->forward(
array(
'controller' => 'error',
'action' => 'showInternalServerError'
)
);
return false;
}
//...
}
and it seems that's working, but this is not an elegant solution and I'm too lazy for this :)
QUESTION: Do you have any better ideas how to do ActionFilters in PhalconPHP?

Take a look at the solution on cmoore4/phalcon-rest/HTTPException
When the application throws an HTTPError this one modifies the response object to reflect the error details and headers and send it to the output.
I like the cmoore4 way of doing many things on the REST implementation.

You can use the Match Callbacks in order to check for your api key:
Assume you have the following route:
$router->add('/api/v1', array(
'module' => 'api',
'controller' => 'index'
))
You can prepend a check to it like this:
$router->add('/api/v1', array(
'module' => 'api',
'controller' => 'index'
))
->beforeMatch(array(new AuthenticationFilter(), 'check'));
And in your custom created AuthenticationFilter, you are able to check for a valid api key:
<?php
class AuthenticationFilter
{
public function check($uri, $route)
{
$response = new \Phalcon\Http\Response();
if ($response->getHeaders()->get('X-Api-Key') != 'XYZ')
{
throw new CustomAuthenticationErrorExteption('Api Key Invalid');
// you can also just return false here and redirect to a default non-authenticated 404 response
}
else return true;
}
}
Reference
https://docs.phalconphp.com/en/latest/reference/routing.html#match-callbacks

Related

The try catch catches the exception but the code after runs anyway cakephp

I'm doing policy authorization in CakePHP. For all CRUD methods, I have to test if the user has the right to execute them. So I create that method to use in all the methods:
Code in SchoolsController
private function authorize(School $s){
try{
$this->Authorization->authorize($s);
} catch(ForbiddenException $e){
$this->Flash->error("You don't have permission.");
return $this->redirect(['controller' => 'Schools', 'action' => 'index']);
}
}
I'm testing the code for a user who wouldn't have permission. This should work but the code after calling this method is still called.
public function delete($id = null) {
$school = $this->Schools->get($id);
$this->authorize($school);
$this->request->allowMethod(['post', 'delete']);
if ($this->Schools->delete($school)) {
$this->Flash->success(__("School has been successfully removed."));
} else {
$this->Flash->error(__("The school could not be deleted. Please try again."));
}
return $this->redirect(['action' => 'index']);
}
I'm redirected and I get the two messages:
"You don't have permission."
"School has been successfully removed."
Here my SchoolPolicy
public function canDelete(IdentityInterface $user, School $school)
{
return $this->isAuthor($user,$school);
}
protected function isAuthor(IdentityInterface $user, School $school)
{
return $school->userId === $user->id;
}
If you catch an exception, then it will of course not halt execution, that's the whole point of catching it. And if you then return a value from your method (Controller::redirect() will return the response object with the Location header configured accordingly), you'll need to do something with that value, otherwise it will just vanish into the void, so for example:
$response = $this->authorize($school);
if ($response) {
return $response;
}
It's a bit hidden in the docs, but the easier approach would be to throw a redirect exception from your authorize() method. Also, if you do not actually make any use of the forbidden exception and the information that it holds, then you could simply use the can() method, which returns a boolean, eg:
if (!$this->Authorization->can($s)) {
$this->Flash->error("You don't have permission.");
throw new \Cake\Http\Exception\RedirectException(
\Cake\Routing\Router::url([
'controller' => 'Schools',
'action' => 'index',
])
);
}
You may also want to consider using a custom unauthorized handler instead.

PHPUnit how to write a Laravel Nova Observer test

I would like to write a test for my CommentObserver. This observer is only registered in the NovaServiceProvider but not the AppServiceProvider. This means I cannot test my observer by using my own Controllers.
In my eyes I have 3 ways to test my observer:
Either performing a feature test by sending a post request to the Nova API
Mocking the observer by calling the function in the observer to check if the function perfoms as desired
Trying to register my observer on the fly in the AppServiceProvider, performing a request and deregistering the observer in the AppServiceProvider again.
I tried to find a solution for any of these 3 ways to test my observer but unfortunately I faild with any of them.
Problems:
For way 1 I always get a validation error and Nova tells me that my input is invalid.
For way 2 I fail at mocking the observer function
For way 3 I didn't find any solution on how to register and deregister the oberserver on the fly at the AppServiceProvider
Do you guys have idea and solition on how I can test my CommentObserver (which is as written above only registered in my NovaServiceProvider).
Update:
So, here is the code of my observer. I need to have an valid request to test my observer in order to have the ability to access the $request->input('images') variable. I do know I can also use $comment->content instead of request()->input('content') because $comment->content already contains the new content which is not saved it this point.
The reason why I need a valid request is that the variable images is not part of the Comment model. So I cannot use $comment->images because it simply doesn't exist. That's why I need to access the request input. What my observer is basically doing is to extract the base64 images from the content, saves them to the server and replaces them by an image link.
class CommentObserver
{
public function updating(Comment $comment)
{
if (!request()->input('content')) {
return;
}
if (request()->input('content') == $comment->getRawOriginal('content')) {
return;
}
$images = request()->input('images');
if(!is_array($images)) {
$images = json_decode(request()->input('images'));
}
checkExistingImagesAndDeleteWhenNotFound($comment, request()->input('content'), 'comments', 'medium');
$comment->content = addBase64ImagesToModelFromContent($comment, request()->input('content'), $images, 'comments', 'medium');
}
}
This is my test so far. I choose way 1 but as described already this always leads to an validation error by the nova controller and I cannot figure out what is the error/what is missing or wrong.
class CommentObserverTest extends TestCase
{
/** #test */
public function it_test()
{
$user = User::factory()->create([
'role_id' => Role::getIdByName('admin')
]);
$product = Product::factory()->create();
$comment = Comment::factory()->create(['user_id' => $user->id, 'content' => '<p>Das ist wirklich ein super Preis!</p>', 'commentable_type' => 'App\Models\Product', 'commentable_id' => $product->id]);
$data = [
'content' => '<p>Das ist wirklich ein HAMMER Preis!</p>',
'contentDraftId' => '278350e2-1b6b-4009-b4a5-05b92aedaae6',
'pageStatus' => PageStatus::getIdByStatus('publish'),
'pageStatus_trashed' => false,
'commentable' => $product->id,
'commentable_type' => 'App\Models\Product',
'commentable_trashed' => false,
'user' => $user->id,
'user_trashed' => false,
'_method' => 'PUT',
'_retrieved_at' => now()
];
$this->actingAs($user);
$response = $this->put('http://nova.mywebsiteproject.test/nova-api/comments/' . $comment->id, $data);
dd($response->decodeResponseJson());
$das = new CommentObserver();
}
}
Kind regards and thank you
Why depend on the boot method in your NovaServiceProvider? It is possible to call the observe() method on the fly in your test:
class ExampleTest extends TestCase
{
/** #test */
public function observe_test()
{
Model::observe(ModelObserver::class);
// If you need the request helper, you can add input like so:
request()->merge([
'content' => 'test'
]);
// Fire model event by updating model
$model->update([
'someField' => 'someValue',
]);
// Updating should be triggered in ModelObserver
}
}
It should be now be possible in your observer class:
public function updating(Model $model)
{
dd(request()->input('content')); // returns 'test'
}

Catch not found error by certain route prefix [Laravel]

I'm building web api along side with website in laravel 5.2, when user visit unavailable url on my web like http://stage.dev/unavailable/link then it will throw on 404 page on errors view resource, then i want to catch with different way if user try to access my API with unavailable url like http//stage.dev/api/v1/unavailable/link, then i want to return json/xml response
{
'status' : 'not found',
'code' : 404,
'data' : []
}
not the view, is there a way to detect it by url prefix 'api/*' or how.., maybe another approach with similar result, so the device/client which access it could proceed by standard format (i have simple format in all json response)
{
'status' : 'success|failed|restrict|...',
'api_id' : '...',
'token' : '...',
'data' : [
{'...' : '...'},
{'...' : '...'},
...
]
}
SOLVED
I figure out something after read answer from Chris and Alexey this approach works for me i add couples of lines in handler.php at render() method,
if($e instanceof ModelNotFoundException || $this->isHttpException($e)) {
if($request->segment(1) == 'api' || $request->ajax()){
$e = new NotFoundHttpException($e->getMessage(), $e);
$result = collect([
'request_id' => uniqid(),
'status' => $e->getStatusCode(),
'timestamp' => Carbon::now(),
]);
return response($result, $e->getStatusCode());
}
}
my header request respond 404 error code and return json data like i want..
Maybe there's a better way to do that, but you could create custom error 404 handler. Follow this tutorial, but change case 404 part to something like this:
if(str_contains(Request::url(), 'api/v1/')){
return response()->json(your_json_data_here);
}else{
return \Response::view('custom.404',array(),404);
}
Inside App\Exception\Handler.php you have a render method which can be handy for general purpose error catching and handling.
In this case you can also use the request()->ajax() method to determine if it's ajax. It does this by checking that certain headers are present, in particular:
'XMLHttpRequest' == $this->headers->get('X-Requested-With')
Anyway, back to the render method in Handler.php.
You can do something like:
public function render($request, Exception $e)
{
if($e instanceof HttpException && $e->getStatusCode() == 404) {
if (request()->ajax()) {
return response()->json(['bla' => 'foo']);
} else {
return response()->view('le-404');
}
}
return parent::render($request, $e);
}
I figure out something after read answer from Chris and Alexey this approach works for me i add couples of lines in handler.php at render() method,,
if($e instanceof ModelNotFoundException || $this->isHttpException($e)) {
if($request->segment(1) == 'api' || $request->ajax()){
$e = new NotFoundHttpException($e->getMessage(), $e);
$result = collect([
'request_id' => uniqid(),
'status' => $e->getStatusCode(),
'timestamp' => Carbon::now(),
]);
return response($result, $e->getStatusCode());
}
}

Phalcon php multilingual routing

Hi I'm new to the php world.
I'm wondering What is the best way to handle multilingual routing ?
I'm starting to create a website with Phalcon php.
I have the following routing structure.
$router->add('/{language:[a-z]{2}}/:controller/:action/:params', array(
'controller' => 2,
'action' => 3,
'params' => 4,
));
$router->add('/{language:[a-z]{2}}/:controller/:action', array(
'controller' => 2,
'action' => 3,
));
$router->add('/{language:[a-z]{2}}/:controller', array(
'controller' => 2,
'action' => 'index',
));
$router->add('/{language:[a-z]{2}}', array(
'controller' => 'index',
'action' => 'index',
));
My problem is for instance when I go on mywebsite.com/ I want to change my url in the dispatcher like mywebsite.com/en/ or other language. Is it a good practise to handle it in the beforeDispatchLoop ? I seek the best solutions.
/**Triggered before entering in the dispatch loop.
* At this point the dispatcher don't know if the controller or the actions to be executed exist.
* The Dispatcher only knows the information passed by the Router.
* #param Event $event
* #param Dispatcher $dispatcher
*/
public function beforeDispatchLoop(Event $event, Dispatcher $dispatcher)
{
//$params = $dispatcher->getParams();
$params = array('language' => 'en');
$dispatcher->setParams($params);
return $dispatcher;
}
This code doesn't work at all, my url is not change. The url stay mywebsite.com/ and not mywebsite.com/en/
Thanks in advance.
I try one solution above.
The redirect doesn't seems to work. I even try to hard-coded it for test.
use Phalcon\Http\Response;
//Obtain the standard eventsManager from the DI
$eventsManager = $di->getShared('eventsManager');
$dispatcher = new Phalcon\Mvc\Dispatcher();
$eventsManager->attach("dispatch:beforeDispatchLoop",function($event, $dispatcher)
{
$dispatcher->getDI->get('response')->redirect('/name/en/index/index/');
}
I think you are confusing something. If I understand correctly: you want to redirect users to a valid url if they open a page without specifying a language.
If that's the case you should verify in your event handler whether the language parameter is specified and redirect user to the same url + the default language if its missing. I am also assuming that your beforeDispatchLoop is located in your controller, which is also part of the problem, because your route without a language never matches and you never get into that controller. Instead you need to use it as an event handler with the event manager as per the documentation. Here is how you do the whole thing.
$di->get('eventsManager')->attach("dispatch:beforeDispatchLoop", function(Event $event, Dispatcher $dispatcher)
{
$params = $dispatcher->getParams();
// Be careful here, if it matches a valid route without a language it may go into a massive
// recursion. Really, you probably want to use `beforeExecuteRoute` event or explicitly
// check if `$_SERVER['REQUEST_URI']` contains the language part before redirecting here.
if (!isset($params['language']) {
$dispatcher->getDI->get('response')->redirect('/en' . $_SERVER['REQUEST_URI']);
return false;
}
return $dispatcher;
});

How to get response from action dispatched in phpunit

I am dispatching some POST data to an action of a controller. That action echoes some json-encoded string. I want to verify that the json-encoded string of that action is as I want it. I want to know how I can get that string?
My test looks like this:
$this->request->setMethod('POST')
->setPost(['test' => 'databaseschema_Database']);
$params = ['action' => 'analysis', 'controller' => 'Index', 'module' => 'default'];
$urlParams = $this->urlizeOptions($params);
$url = $this->url($urlParams);
$result = $this->dispatch($url);
$this->assertJsonStringEqualsJsonString(
$result, json_encode(["status" => "Success"])
);
My test is failing and I am getting following message:
1) IndexControllerTest::testAnalysisAction
Expected value JSON decode error - Unknown error
stdClass Object (...) does not match expected type "NULL".
Can any one guide me on how to do this?
If you want to do unit testing, what you really want to do is extract the json encoding into it's own class (or a method inside a utils class or something) and then test those method instead of your whole controller.
The problem with your approach is that when running phpunit, there is not $_POST array. The code above does not show what is happening, but I guess there is different behaviour when run through apache and cli which causes your test to fail.
I would create a TransformerClass and test this in isolation:
class JsonTransformer
{
public function transformPostData(array $postArray)
{
// transformation happening
}
}
class JsonTransformerTest extends \PHPUnit_Framework_TestCase
{
public function testTransformPostData()
{
$transformer = new JsonTransformer();
$data = array('action' => 'analysis', 'controller' => 'Index', 'module' => 'default');
$result = $transformer->transformPostData(data);
$this->assertJsonStringEqualsJsonString($result, json_encode(array("status" => "Success")));
}
}
If you need to test your whole request/response, you would use some kind of HTTPClient, request the url, send the post data and see if the response is what you'd expect.
Everything in between (like faking the post data) leaves you with more problems and more code to maintain than it does you good.

Categories