Calling a controller method from laravel middleware - php

I have a method in my base controller.php that formats all my responses to how I like it like so;
public function sendError($error, $errorMessages = [], $code = 404)
{
$response = [
'success' => false,
'message' => $error,
];
if (!empty($errorMessages)) {
$response['data'] = $errorMessages;
}
return response()->json($response, $code);
}
If I am calling it from another controller, i simply just call
return $this->sendError('Validation Error', $validator->errors(), 400);
But i am also using middleware for my JWT-Auth. Instead of re-writing the method, is there any way to call this controller method from inside middleware?

try this one in middleware by create of your controller
return (new yourChildController)->sendError('xyz errro',[],400)

First get the existing instance:
use Illuminate\Support\Facades\Route;
// ...
$myController = Route::getCurrentRoute()->getController();
Then call as you would normally, in OP's case:
return $myController->sendError('My error message.', [], 400);
Note that above is tested with Laravel 6.x release.

Related

Call to a member function validated() on null (Unit testing Laravel 9)

I am writing test for some controller method that will validate request data and create document by 3rd party API. Then it should return response with 201 status code. I am using mocking to mock my service class that is creating document. Here is my controller:
public function margin(MarginRequest $request){
$data = $request->validated();
$fileId = $this->documentService->createDocument(auth()->user()->id, SignableDocumentAbstract::MARGIN_CERTIFICATE, new MarginDocument(), $data);
return response()->json([
'code' => 201,
'message' => 'Margin agreement generated successfully',
'data' => [
'uuid' => $fileId
]
], 201);
}
And my test:
public function test_public_margin()
{
$marginData = (new MarginFaker())->fake();
Auth::shouldReceive('user')->once()->andReturn((object)['id' => 999999]);
$this->mock(DocumentService::class, function (MockInterface $mock) {
$mock->shouldReceive('createDocument')
->once()
->andReturn(Str::random());
});
$request = MarginRequest::create('/api/public/documents/margin', 'POST', $marginData);
$response = app(PublicController::class)->margin($request);
$this->assertEquals(201, $response->getStatusCode());
}
Everything look OK but when I run my test it throws error that
Call to a member function validated() on null
It is given in $data = $request->validated(); line of controller. But I can't understand why my $request is recognized as null. Even if I dump request object by dump($request) I can see that it is object and holds all required fields inside.
Then what can be the reason, why I can't call validated() method while testing?
You do not test like that when you want to test a URL. You NEVER mock a controller or do new Controller and call a method inside it.
You have to read the HTTP Test section of the documentation.
So, your test should look like this:
public function test_public_margin()
{
$this->actingAs(User::factory()->create());
$this->mock(DocumentService::class, function (MockInterface $mock) {
$mock->shouldReceive('createDocument')
->once()
->andReturn(Str::uuid());
});
$response = $this->post(
'/api/public/documents/margin',
['pass the needed data as an array, so the validation passes']
);
$response->assertStatus(201);
}

How to add a custom validation method in Laravel

As we know that in Laravel, we can validate a request by $request->validate() method that automatically redirect the user if validation failed.
I am trying to develop a function with a similar feature only difference is that, instead of redirecting the user, this function will send a JSON response on validation failure.
Here is my code
<?php
namespace App\Libs;
use Illuminate\Http\Request as RequestCore;
use Illuminate\Support\Facades\Validator;
class Request extends RequestCore {
public function validateAjax($data){
$input = $this->all();
$validator = Validator::make($input,$data);
if ($validator->fails()) {
return response()->json([
"errors" => $validator->errors(),
"message" => "Invalid Input"
], 401);
}
return true;
}
}
Problem is, when i run the code, It doesn't stop on error. What am i missing?
You should make new Request class (php artisan make:request ExampleRequest). And after add this function in that class for JSON response
protected function failedValidation(Validator $validator)
{
$errors = (new ValidationException($validator))->errors();
throw new HttpResponseException(
response()->json(['status' => 'error', 'errors' => $errors], JsonResponse::HTTP_UNPROCESSABLE_ENTITY)
);
}

Laravel 7 - cannot fake 3rd party Http call using a Middleware

I am trying to use a middleware in Laravel 7 to fake Http calls to a 3rd party API. So I can assign that middleware to any route which will make calls to that 3rd party API. So whenever that route is called, it will call to the faked API.
Purpose of this is, when I want to fake the API, I just only have to assign the middleware to the route. When I don't want to fake the API, I will just remove the middleware from the route.
The middleware looks like below.
namespace App\Http\Middleware;
class MockApiEndpoints
{
public function handle($request, Closure $next)
{
// mock api/endpoint
$vacancies = $this->vacancyRepository->all();
$url = 'api/endpoint';
$sessionId = Str::uuid()->toString();
$response = [
'result' => 'OK',
'content' => [
'suggestedVacancies' => array_map(function ($uid) {
return [
'id' => $uid,
'relevance' => bcdiv(rand(9, 99), 100, 2)
];
}, $vacancies->pluck('uid')->all()),
'sessionId' => $sessionId
]
];
$this->mockHttpRequest($url, $response);
return $next($request);
}
protected function mockHttpRequest(string $uri, $response, int $status = 200, array $headers = [])
{
$url = config('api.base_url') . '/' . $uri;
Http::fake([
$url => Http::response($response, $status, $headers)
]);
}
}
Even though I attach this this middleware to the route, route still makes calls to the original API. So the Htpp::fake doesn't work in the middleware it seems. Htpp::fake does work if I use it inside the controller.
Middleware is attached to the route as below. (Middleware is properly registered in the $routeMiddleware array in app/Http/Kernal.php)
namespace App\Providers;
class RouteServiceProvider extends ServiceProvider
{
protected function mapApiRoutes()
{
Route::prefix('api')
->middleware(['MockApiEndpoints'])
->namespace($this->namespace . '\Api')
->group(base_path('routes/api.php'));
}
}
I got my work done by using a workaround. But I want to know why does Http::fake doesn't work in middleware. Thank you for your answers in advance.
Instead of returning $next($request) you should return a response(...) in your middleware.
Perhaps just forward the response from the fake call.
return response($responseFrom3rdPartyApiCall, 200);

Laravel custom validation and ajax

The validation on my Laravel 4.2 project was done with Ardent package. After going to Laravel 5.5 I have eliminated Ardent and wanted to do Laravel's native validation with form requests.
The problem I have is that the Ajax call was validated before like this:
public function postRegisterAjax(A)
{
try {
...
} catch (ExceptionBag $e) {
$msg = $e->getMessageBag()->all(':message');
$status = Status::ERROR;
}
return $this->responseJson($status, $msg);
}
Now I introduced UserValidationRequest class and I would like Ajax call to throw me an error message without the need to reload the page. In order to do that, I need to forward status and message as Json response.
I somehow tried to do that with after validation hooks, but it doesn't work:
protected function getValidatorInstance()
{
$validator = parent::getValidatorInstance();
if ($validator->fails()) {
\Log::info($validator->errors());
$msg = $validator->errors();
$status = Status::ERROR;
return response()->json(['response' => [
'status' => $status,
'msg' => $msg,
]]);
}
return $validator;
}
The code fails on return response() saying that Method passes does not exist (Illuminate/Support/Traits/Macroable.php:96).
Does anyone know what seems to be the issue?
From Laravel 5.x version (not sure), failedValidation() method was introduced in form requests instead of what Laravel 4.x had as response().
I resolved my issue by tailoring the response to my needs by overriding that method in my form request:
public function failedValidation(Validator $validator)
{
if ($validator->fails()) {
$status = Status::ERROR;
throw new HttpResponseException(response()->json(["response" => [
'msg' => $validator->errors()->all(':message'),
'status' => $status
]]));
}
return response()->json(["response" => [
'msg' => 'User successfully registered',
'status' => Status::SUCCESS
]]);
}

Lumen - remove duplicate method

In Lumen I have duplicated method 'respondError()' in the base Controller.php class and Handler.php in the Exception folder.
To avoid duplication, where do you suggest to move this method to so it will be easily accessible to any classes?
public function respondError($errorType = '', $message = null, $statusCode = 500)
{
return response([
'success' => false,
'error_type' => $errorType,
'errors' => [],
'message' => $message,
], $statusCode);
}
You should move the code to its own class:
class ErrorResponse {
protected $errorType = null;
protected $message = null;
protected $statusCode = null;
public __construct($errorType = '', $message = null, $statusCode = 500) {
$this->errorType = $errorType;
$this->message = $message;
$this->statusCode = statusCode ;
}
public getResponse() {
return response([
'success' => false,
'error_type' => $this->errorType,
'errors' => [],
'message' => $this->message,
], $this->statusCode);
}
}
Why should you add the overhead you will ask? You may find in the future, that you want to add details to the Response, e.g. a debugging traceback of an exception, etc. or you might want to return the response from a function: The typical situation: Your controller calls an API function, the API function gives some low level error, but the controller needs to add details to it. Using the framework error class for this, will make your whole API dependent on the framework, which you should avoid as best as you can.
You can use the code now from everywhere:
$error = new ErrorResponse('bad error', 'something went wrong!');
return $error->getResponse();
it depends on what pattern you are writing, if you use base mvc, it is better to create a controller that inherits from the base controller and the rest from him and writing in it your code. If you use the repository and service it is better to write there in the basic service.

Categories