Laravel HTTP tests and request attributes - laravel

On my app I add attributes to the HTTP request so I can use it later. My app is a multi domain app (.co.uk, .dk, .de). I findout the domain in the RouteServiceProvider and add the detected language to the HTTP request so I can load the data according to the language and some other things.
I findout and add the website directly in the RouteServiceProvider:
$website = Website::where('domain', '=', request()->getHttpHost())->first();
request()->attributes->add(['website' => $website]);
Then in my controller or anywere else I just have to query the request
if (!$request->attributes->has('website')) {
\Log::error('Abort HTTP request: invalid website: ' . request()->getHttpHost());
abort('500');
}
$language = $request->attributes->get('website')->language();
When testing my app the code execute normally (website is found in the RouteServiceProvider) but then it break in the controller:
testing.ERROR: Abort HTTP request: invalid website
When looking at the attribute, the data are empty in controller but not in the RouteServiceProvider:
dump($request->attributes); // in RouteServiceProvider.php
Symfony\Component\HttpFoundation\ParameterBag {
#parameters: array:1 [
"website" => ...
dump($request->attributes); // in controller
Symfony\Component\HttpFoundation\ParameterBag {
#parameters: []
}
It looks like the request object in the controller is no longer the same. When dumping :
dump(['RouteServiceProvider' => request()]);
I get:
"RouteServiceProvider" => Illuminate\Http\Request {#385
And in controller:
dump(['Controller' => request()]);
"Controller" => Illuminate\Http\Request {#9379
How can I fix this?

First of all, I agree that this code should be in a middleware instead of the RouteServiceProvider.
If the language is you only concern here, I would suggest to just use app()->setLocale() instead of saving the website in your request. If you need other informations contained in the website object, I would suggest to store it in the session instead of the request because I think that this kind of information is more under the responsability of the session than the request, which is more designed to handle inputs, http verbs, headers, ...
This could solve your problem, if it is not the case, let us see more of your code and of the new dd() results

Related

Test api returns 201 instead 200

I do not understand what happens on an API test (laravel 8).
This call (a very simple put) returns a response 200 , using postman.
The same test using phpunit, returns 201 :
public function testPutOrganizationOk()
{
$organization = Organization::factory()->create();
$superAdmin = User::factory()->create([
'organization_id' => $organization->id,
'role_id' => 'SUPERADMIN'
]);
Sanctum::actingAs($superAdmin);
$organizationToModify = [
'name' => 'mon organization moif',
'contact' => 'contact name modif',
'comment' => 'comment comment comment modif',
'ads_max' => 12345,
'state_id' => 'VALIDATED'
];
$response = $this->putJson($this->getUrl() . '/organizations/' . $organization->id, $organizationToModify);
$response->assertStatus(200);
}
The error is :
Tests\Feature\OrganizationTest::testPutOrganizationOk Expected status code 200 but received 201. Failed asserting that 200 is
identical to 201.
I tried a lot of things , without success. I really do not understand what happens. Any suggestions will be appreciated. Thanks.
EDIT :my controller
public function update(StoreOrganizationRequest $request, Organization $organization)
{
$this->authorize('update', Organization::class);
$organizationUpdated = $this->organizationRepository->updateOrganization($organization, $request->only(['name', 'contact', 'comment', 'ads_max', 'state_id']));
return new OrganizationResource($organizationUpdated);
}
EDIT 7 hours later ;-)
When I replace , in the controller, the return of the resource by a return of a simple json, then I have the same behaviour between postman and phpunit . The api call receives a 200 for the update.
Strange, it means that the problem is around the resource ?
Why a different behavior between postman and phpunit ? Who is right : postman or phpunit ?
The http code 201, it mean created success.
see here developer.mozilla.org
and you able to customize the header code by:
return Response::json(new OrganizationResource($organizationUpdated), 200);
201 Status Code says that you just create an Instance, and
200 Status Code says that already existing Instance has been update
The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being defined as a new resource by the requesting user agent, the origin server can create the resource with that URI."
I might be wrong but seems like you have created the instance first and trying to modifying it then
Finally, I give up!
I will write the response with a status code like that:
return (new OrganizationResource($organization))->response()->setStatusCode(200);
instead of:
return new OrganizationResource($organization);
it's longer to write, but at least my tests are OK.

How to call resource route Codeigniter 4

Please I need help to enable me know how to map the resource route to http request.
for example am trying to call this url via axios
http://localhost/nmc/public/api/unregisteredpatients?userid=hen11#gmail.com
and i have created the following resource routes to call my api
//$routes->resource('api/unregisteredpatients/(:any)', 'UnregisteredPatients::show/$1', ['namespace' => 'Api']);
//$routes->get('api/unregisteredpatients/(:any)', 'UnregisteredPatients::show/$1', ['namespace' => 'Api']);
$routes->resource('api/unregisteredpatients/(:any)', ['controller' => 'Api/UnregisteredPatients']);
None of these was able to call my api method instead they all called the index() method, whereas I wanted it to call the show($id) method to enable me utilise the $id to fetch data.
public function show($id = null) {
.....
}
Please I need help
This solves it for me, though I couldn't have a way to capture query parameters but passing the parameter as a segment gives me an AFAIK solution.
$routes->match(['get'], 'api/unregisteredpatients/(:any)', 'Api\UnregisteredPatients::show/$1');

How to find referer of redirect to oauth client's callback

I am having trouble setting up a generic oauth client (and can't find good material on google).
I have this as my route to receive the callback from the oauth process:
Route::get('/oauth/callback', function (Request $request) {
$http = new GuzzleHttp\Client;
$response = $http->post('https://www.wunderlist.com/oauth/access_token', [
'client_id' => 'xxxxxxxxxxxxxxx',
'client_secret' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'code' => $request->code
]);
});
but in order to make it generic, I must be able to identify where the redirect came from.
something in the lines of
$service = App\Service::where(<field>, $request-><information about the referer>);
does Request contain any kind of information that can help me identify the source of the redirect? I looked at the object with dd() and couldn't find anything
You should use request()->headers->get('referer') to check the referrer url.
I managed to work around the problem by defining the callback url so that it contains a query parameter identifying the service.
This means that I tell the service to callback to /oauth/callback?service=XXX
and I find it in my services table like this:
$service = Service::where('slug', Input::get('service'))->firstOrFail();

Laravel 5.5 Request is empty in Restful controller

I have such a route in my routes/web.php
Route::resource('/api/surveys', 'SurveyController');
As documentation says, it creates all needed routes for API. This is a function, that gets executed when I go for /api/surveys route:
public function index()
{
$request = request();
if(!$request->hasHeader('token')) {
return "No auth token found.";
}
$tokenCheck = $this->userService->isTokenValid($request->header('token'));
if($tokenCheck !== true) {
return $tokenCheck;
}
return $this->surveyService->all();
}
What it does, it checks if token header parameter is set, if not, it returns an error, if yes, it checks if token is valid and etc. if everything is OK, it should return surveys from database.
public function surveys() {
$request = \Request::create('/api/surveys', 'GET');
$request->headers->set('Accept', 'application/json');
$request->headers->set('token', \Cookie::get('token'));
$response = \Route::dispatch($request);
print_r('<pre>');
print_r($response);
print_r('</pre>');
}
I have a website, that should use that API I just created to get all survey records. I create a new request object, set header "token" with token I get from a cookie and then try to dispatch and get a response. But the problem is that everytime I get "No auth token found." error. That means $request->hasHeader('token') returns false, even tough I set it here in my request. If I print_r $request->all() in Restful controller, I get an empty array.
I tried Postman to access this API with token parameter, and it works fine in postman, but here, it seems that Request disappears while it travels to API controller.
What I did wrong here?
When you manually create a request and dispatch it, that works to get the routing to call the correct controller, however that does not affect the request that is bound in the container.
When your "fake" request is handled by the api controller, the request that it pulls out of the container is the original "real" request that was made by the user.
Instead of dispatching the route with your new request, you will need to app()->handle($request) the new request. This, however, will completely replace the original "real" request with your new "fake" request, so everything from the original request will be lost.
Having said all that, this method of consuming your own api is discouraged, even by Taylor. You can read his comment on this Github issue. So, consuming your own api like this may work, but you may also run into some other unforeseen issues.
The more appropriate solution would be to extract out the logic called by the api routes to another class, and then call that extracted logic from both your api routes and your web routes.

laravel api with vue 2 js not returning data - could 'localhost:8000' (or '127.0.0.1:8000') be the issue?

I am using the repo https://github.com/mschwarzmueller/laravel-ng2-vue/tree/03-vue-frontend so I have 100% confidence in the reliability of the code. I can post through the laravel api endpoint through the very simple Vue client, and also through Postman. Through Postman I can retrieve the table data array, but not so in the client app. In POSTMAN:
localhost:8000/api/quotes
works just fine.
IN THE vue 2 js CLIENT APP:
methods: {
onGetQuotes() {
axios.get('http://localhost:8000/api/quotes')
.then(
response => {
this.quotes = (response.data.quotes);
}
)
.catch(
error => console.log(error)
);
}
returns nothing. returning the response to Console.log returns nothing. The Network/XHR tab shows the table data rows, but I am not sure what that means.
I know for sure that this code works for others with their unique api endpoints, which I assume may not use localhost or '127:0.0.1:1080.
Edit: in response to request for more info
public function getQuotes()
{
$quotes = Quote::all();
$response = [$quotes];
return response()->json($response, 200);
}
and the relevant route:
Route::get('/quotes', [
'uses' => 'QuoteController#getQuotes'
]);
Just to confirm: I am using verified github repo code in which the ONLY change is my api endpoint addressas mentioned in the first line of the body of this question. . Note that the Laravel back end is also derived from a related repo in Max's fine tutorial. The running code can be seen at
So I really don't think this is a coding error- but is it a configuration error due to me using local host??
EDIT: It WAS a coding error in the laravel controller as shown below
The reason your code isn't working if because you haven't provided a key for your $quotes in your controller but you're looking for it in your vue file (response.data.quotes).
[$quotes] is essentially [0 => $quotes] so when the json response comes through it be 0: [...] not quotes: [...].
To get this to work you just need to change:
$response = [$quotes];
to:
$response = ['quotes' => $quotes];
Furthermore, just an FYI, you don't need to provide the 200 in response->json() as it's the default and you can just return an array and Laravel automatically return the correct json response e.g.:
public function getQuotes()
{
$quotes = \App\Models\Artist::all();
return compact('quotes'); //<-- This is just another way of writting ['quotes' => $quotes]
}
Obviously, you don't have to if you don't want to.
Hope this helps!

Resources