Sub request in Laravel 5 - php

I'm looking to do sub requests in my API, to other parts of my API. I have done this before in Symfony - but I'm not sure how to achieve this in Laravel.
$url = route('some.route', ['param' => $val]);
$request = Request::create($url, 'get', []);
Route::dispatch($request);
Always seems to fail giving something along the lines of
Class api does not exist
So I've tried
app()->handle($request);
This works, but processes a request, but I cant handle any exceptions thrown (e.g. validation as the app layer handles it and throws a html response)
Handle has a signature of the HttpKernelInterface, so can take a property of sub requests and catch exceptions - but these are not used....
...->handle($request, HttpKernelInterface::SUB_REQUEST, false);
Is it possible to do this in Laravel without having to send an actual http request?
Thanks

So after digging deep into the framework, it seems that this way is not possible as it stores some values as statics and they're not updated via the dipatch() method.
So ive created this package to handle setting and reapplying the original values.
https://github.com/myerscode/laravel-sub-request

Related

Laravel remembers original response during http tests

Given the following pest test:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->get('/courses')->assertDontSee('WebTechnologies');
$this->followingRedirects()->post('/courses', [
'course-name' => 'WebTechnologies',
])->assertStatus(200)->assertSee('WebTechnologies');
});
The above should fully work; however, the second request post('/courses')...
fails saying that:
Failed asserting that <...> contains "WebTechnologies".
If I remove the first request:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->followingRedirects()->post('/courses', [
'course-name' => 'WebTechnologies',
])->assertStatus(200)->assertSee('WebTechnologies');
});
The test passes.
If I remove the second request instead:
it('allows admins to create courses', function () {
$admin = User::factory()->admin()->create();
actingAs($admin);
$this->get('/courses')->assertDontSee('WebTechnologies');
});
It also passes.
So why should the combination of the two cause them to fail? I feel Laravel is caching the original response, but I can't find anything within the documentation supporting this claim.
I have created an issue about this on Laravel/Sanctum as my problem was about authentication an stuff...
https://github.com/laravel/sanctum/issues/377
One of the maintainers of Laravel Said:
You can't perform two HTTP requests in the same test method. That's not supported.
I would have wanted a much clearer explanation on why it's not supported.
but I guess, we would never know. (Unless we dive deep into the Laravel framework and trace the request)
UPDATE:
My guess is that, knowing how Laravel works, for each REAL request Laravel initializes a new instance of the APP...
but when it comes to Test, Laravel Initializes the APP for each Test case NOT for each request, There for making the second request not valid.
here is the file that creates the request when doing a test...
vendor/laravel/framework/src/Illuminate/Foundation/Testing/Concerns/MakesHttpRequests.php
it's on the call method line: 526 (Laravel v9.26.1)
as you can see...
Laravel only uses 1 app instance... not rebuilding the app...
Line 528: $kernel = $this->app->make(HttpKernel::class);
https://laravel.com/docs/9.x/container#the-make-method
the $kernel Variable is an instance of vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php
My guess here is that the HttpKernel::class is a singleton.
P.S. I can do a little more deep dive, but I've procrastinated too much already by answering this question, it was fun thou.
TL;DR.
You can't perform two HTTP requests in the same test method. That's not supported.
UPDATE:
I was not able to stop myself...
I found Laravel initializing Kernel as a singleton
/{probject_dir}/bootstrap/app.php:29-32
Please make sure to not use any classic singleton pattern which isn't invoked with singleton binding or facades.
https://laravel.com/docs/9.x/container#binding-a-singleton
$this->app->singleton(Transistor::class, function ($app) {
return new Transistor($app->make(PodcastParser::class));
});
The Laravel app won't be completely restarted during tests unlike different incoming HTTP requests - even if you call different API endpoints in your tests

laravel reactjs put all api request in one get request

I'm working on a laravel/reactjs project and I use Axios to get data from the database with the Laravel API.
I use the Laravel api.php and web.php for the API calls.
I have a few pages that do multiple requests at the render of the reactjs component but sometimes Laravel throws a random 500 or 403 error when the request are done (1 in 25 or something). Also, calls that go through the web.php have a bit of extra loading time.
So I was wondering if it is a smart idea to make a Laravel function that returns all the data that one page needs at the render. So in react only one get request has to be made at the render.
Is this the best practice or are there any better solutions?
Thanks for helping, If you don't understand the question please tell me.
edit:
the error returned by Laravel :
{
"message": "Server Error"
}{
"message": "Server Error"
}
I would suggest increasing the API throttle in app/Http/Kernal.php, you can also use Route::dispatch to call routes internally and then group them in a single response:
// GET Request
$request = Request::create('/some/url/1', 'GET');
$response = Route::dispatch($request);
// POST Request
$request = Request::create('/some/url/1', 'POST', Request::all());
$response = Route::dispatch($request);

How do I integrate a Symfony controller action in a legacy PHP page?

I do have a legacy PHP application and a Symfony 4.1 application. I need to integrate both by rendering parts of a plain PHP page as the result of a predetermined controller action. That is, no resolving based on request is necessary or wanted and the response body should be inserted in the legacy page.
Put differently: I want Symfony to act on the current request, but in advance tell it to use ExampleController::exampleAction() and get the response (body). What is the cleanest way to achieve that?
Well, this is weird :)
I would try something like this:
1, Get an instance of your kernel. Check public/index.php about how to do that.
$kernel = new Kernel($env, $debug);
2, Create a request manually
$request = new Request([], [], ['_controller' => MyController::class . '::myAction']);
3, Handle the request
$kernel->handle($request);
4, Send the response, and terminate the kernel (like in index.php)
Tested it with a custom front controller, see https://gist.github.com/Padam87/27a7d0825816fa358678bce7a640dd47
If you only need the response body, then use $response->getContent().

How Follow the Don't Repeat Yourself Principle When Consuming My Own Laravel API?

I'm developing a Laravel 4 app that will make the same CRUD operations on my data set available through a JSON REST API and a Web UI. It seems that to prevent breaking the DRY principle that my UI should consume my own API by routing all requests from the UI back to the API. I'm unsure though about the best approach to making this work. Presumably I would have separate UI and API controllers and somehow route the requests through. Or should I be looking at a different approach altogether?
I'm actually tinkering with the same idea and it's pretty neat. With Laravel you do have the ability to make internal requests (some might refer to this as HMVC, but I won't). Here's the basics of an internal request.
$request = Request::create('/api/users/1', 'GET');
$response = Route::dispatch($request);
$response will now contain the returned response of the API. Typically this will be returned a JSON encoded string which is great for clients, but not that great for an internal API request. You'll have to extend a few things here but basically the idea is to return the actual object back through for the internal call, and for external requests return the formatted JSON response. You can make use of things like $response->getOriginalContent() here for this kind of thing.
What you should look at doing is constructing some sort of internal Dispatcher that allows you to dispatch API requests and return the original object. The dispatcher should also handle malformed requests or bad responses and throw exceptions to match.
The idea itself is solid. But planning an API is hard work. I'd recommend you write up a good list of all your expected endpoints and draft a couple of API versions then select the best one.
NOTE: As vcardillo pointed out below, route filters are not called with these methods.
I am currently doing the same thing, and Jason's answer got me going in a great direction. Looking at the Symfony\Component\HttpFoundation\Request documentation, I figured out how to POST, as well as everything else I'd need to do. Assuming you're using a form, here is some code that could help you:
GET:
$request = Request::create('/api/users/1', 'GET');
$response = Route::dispatch($request);
POST:
$request = Request::create('/api/users/1', 'POST', Input::get());
$response = Route::dispatch($request);
POST w/ cookies
$request = Request::create('/api/users/1', 'POST', Input::get(), Cookie::get('name'));
$response = Route::dispatch($request);
POST w/ files
$request = Request::create('/api/users/1', 'POST', Input::get(), null, Input::file('file'));
$response = Route::dispatch($request);
I hope this helps someone else. If you aren't using a form, or you are but not using Laravel's Input / Cookie facade, replace the Input / Cookie facades with your own content.
Taylor Otwell suggested using app()->handle() rather than Route::dispatch() to achieve a clean request.
For Route::dispatch($request) I noticed if the endpoint of your non-GET request (parameters on the HTTP request body) uses a dependency injected \Illuminate\Http\Request or \Illuminate\Foundation\Http\FormRequest extending instance, state of the parameters, cookies, files, etc. are from the original HTTP request. i.e., for your application's controller action method.
If parameter names and post method type for your app controller and API controller are the same, you won't notice the difference since the original parameter values are passed on. But when you're manually assembling the 3rd parameter of Request::create(), Route::dispatch() will result in it being ignored.
app()->handle() fixes that context problem in the Laravel request lifecycle.
Caveat: app()->handle() affects Illuminate\Support\Facades\Request, refreshing it with this new request instance. As a knock-on effect, calls like Request::isXmlHttpRequest() or redirect()->back() invoked after app()->handle() will cause unpredictable behaviour. I'd suggest tracking the context of your original request and instead use redirect()->to(route('...')) so you strictly control flow and state of your app.
Given all these corner cases, it may be best to just do a manual curl using a Guzzle HTTP client.
If you are looking for using passport login api internally, then you need to add the parameters to original request:
protected function manualLogin(Request $request)
{
$email = $request->input('email');
$password = $request->input('password');
$request->request->add([
'username' => $email,
'password' => $password,
'grant_type' => 'password',
'client_id' => $clientID,
'client_secret' => $clientSecret,
'scope' => '*']);
$newRequest = Request::create('/oauth/token', 'post');
return Route::dispatch($newRequest)->getContent();
}
If you're consuming your own API, use app()->handle() instead of Route::dispatch() as Derek MacDonald has suggested.
app()->handle() creates a fresh request, while Route::dispatch() runs the route within the stack, effectively ignoring parameters that are part of the request that you're sending.
Edit: Just a heads-up. Taylor Otwell advises against using sub-requests to make internal API calls, as they mess the current route. You can an HTTP API client like Guzzle instead to make the API calls.
You can use Optimus API consumer, the API is clean and simple, example making an internal request:
$response = app()->make('apiconsumer')->post('/oauth/token', $data);
In it's core, it uses Illuminate\Routing\Router and Illuminate\Http\Request to make the call
// create the request
$this->request->create($uri, $method, $data, [], [], $server, $content);
// get the response
$response = $this->router->prepareResponse($request, $this->app->handle($request));

AWS S3 SDK for PHP 2 - Get HTTP Request/Response Strings

I have a generic HTTP file access API which I use for the system I'm working on. To make it as flexible as possible, it returns request and response data in the form of HTTP strings.
I'm currently implementing a version which interacts with the S3, via the AWS SDK for PHP 2.
Is there an easy way to quickly get the Request and Response HTTP requests which the S3Client makes when performing operations? If not, is there a more piecemeal way which I can use to not have to fake it?
Basically, I'd like the full-text of both the Request and Response on demand, or at least access to relevant data (headers, response codes, URLs, etc) so I can properly populate the return data for my framework.
Thanks.
You can get either the request or response object from a command object. Assuming $s3 holds an instance of Aws\S3\S3Client, you could do something like this:
$command = $s3->getCommand('ListObjects', array('Bucket' => '<bucket-name>'));
$request = $command->getRequest();
$response = $command->getResponse();
Those objects have methods for viewing the body, headers, status codes, etc. and you can cast them to string to see the string form.
If you want to quickly see the request and response as you are executing commands, you can attach the wire logger, and see what comes out on STDOUT (or STDERR)
$s3->addSubscriber(\Guzzle\Plugin\Log\LogPlugin::getDebugPlugin());
$s3->listObjects(array('Bucket' => '<bucket-name>'));
You will need to look into the Guzzle\Http\Client class, which is an ancestor class to S3Client, to have a look at the methods that it makes available. You can always override some of these methods in your own child of S3Client to make accessing this information easier for you.
Ultimately the data you are looking for resides in an object of class Guzzle\Http\Message\Response, which I believe is returned from Guzzle\Http\Client::send().
So perhaps in your own implementation of S3Client you can override the send() method to send the HTTP requests, then process the response data as needed.

Categories