Is there a way, in Laravel 5, to call routes internally/programmatically from within the application? I've found a lot of tutorials for Laravel 4, but I cannot find the information for version 5.
Using laravel 5.5, this method worked for me:
$req = Request::create('/my/url', 'POST', $params);
$res = app()->handle($req);
$responseBody = $res->getContent();
// or if you want the response to be json format
// $responseBody = json_decode($res->getContent(), true);
Source:
https://laracasts.com/discuss/channels/laravel/route-dispatch
*note: maybe you will have issue if the route you're trying to access
has authentication middleware and you're not providing the right credentials.
to avoid this, be sure to set the correct headers required so that the request is processed normally (eg Authorisation bearer ...).
UPDATE: i've tried this method with laravel 8 and it works but if you're using PHP version 8.0 you might need to call opcache_reset(); before this line $req = Request::create('/my/url', 'POST', $params); to avoid an error.
see guzzlehttp/guzzle dosn't work after update php to php 8 for more info
You may try something like this:
// 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);
You can actually call the controller that associates to that route instead of 'calling' the route internally.
For example:
Routes.php
Route::get('/getUser', 'UserController#getUser');
UserController.php
class UserController extends Controller {
public function getUser($id){
return \App\User::find($id);
};
}
Instead of calling /getUser route, you can actually call UserController#getUser instead.
$ctrl = new \App\Http\Controllers\UserController();
$ctrl->getUser(1);
This is the same as calling the route internally if that what you mean. Hope that helps
// this code based on laravel 5.8
// I tried to solve this using guzzle first . but i found guzzle cant help me while I
//am using same port. so below is the answer
// you may pass your params and other authentication related data while calling the
//end point
public function profile(){
// '/api/user/1' is my api end please put your one
//
$req = Request::create('/api/user/1', 'GET',[ // you may pass this without this array
'HTTP_Accept' => 'application/json',
'Content-type' => 'application/json'
]);
$res = app()->handle($req);
$responseBody = json_decode($res->getContent()); // convert to json object using
json_decode and used getcontent() for getting content from response
return response()->json(['msg' =>$responseBody ], 200); // return json data with
//status code 200
}
None of these answers worked for me: they would either not accept query parameters, or could not use the existing app() instance (needed for config & .env vars).
I want to call routes internally because I'm writing console commands to interface with my app's API.
Here's what I did that works well for me:
<?php // We're using Laravel 5.3 here.
namespace App\Console;
use App\MyModel;
use App\MyOtherModel;
use App\Http\Controllers\MyController;
use Illuminate\Console\Command;
class MyCommand extends Command
{
protected $signature = 'mycommand
{variable1} : First variable
{variable2} : Another variable';
public function handle()
{
// Set any required headers. I'm spoofing an AJAX request:
request()->headers->set('X-Requested-With', 'XMLHttpRequest');
// Set your query data for the route:
request()->merge([
'variable1' => $this->argument('variable1'),
'variable2' => $this->argument('variable2'),
]);
// Instantiate your controller and its dependencies:
$response = (new MyController)->put(new MyModel, new MyOtherModel);
// Do whatever you want with the response:
var_dump($response->getStatusCode()); // 200, 404, etc.
var_dump($response->getContent()); // Entire response body
// See what other fun stuff you can do!:
var_dump(get_class_methods($response));
}
}
Your Controller/Route will work exactly as if you had called it using curl. Have fun!
Related
I have to test the routes of a controller which uses Guzzle Wrapper as a client to get data from an API. The app uses Laravel as a framework.
This is the part of the code that gives me trouble in the controller's function I am currently testing:
public function addContacts(Request $request)
{
...
$client = new GuzzleWrapper();
$response = $client->post($uri, $data);
if($response->getStatusCode() != 200) {
return response()->json("Problem getting data", 500);
}
...
}
Now, what I have tried are
getMockBuilder:
$mock = getMockBuilder(GuzzleWrapper::class)->onlyMethods(array('post'))->getMock();
$mock->expects($this->once())->method('post')->willReturn(response()->json([$responseData], 200));
Http::fake :
Http::fake(
[$uri => Http::response([$responseData], 200)
);
Mockery :
$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class);
$mockGuzzleClient->shouldReceive('post')
->andReturn(response()->json([$responseData], 200));
I also tried Mockery like this:
$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock){
$mock->shouldReceive('post')
->andReturn(response()->json([$responseData], 200));
});
And like this:
$this->app->instance(
GuzzleWrapper::class,
Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock){
$mock->shouldReceive('post')
->andReturn(response()->json([$responseData], 200));
})
);
and following everything I tried is the call to test my controller's function:
//Successfully add contacts to list
$this->json('POST', $addContactUri, $input_data, $token);
$this->seeStatusCode(201);
Now! Whatever I tried, it's as if the GuzzleWrapper is never mocked, it still does the post and doesn't return status code 200. No matter what I find on google, it never fits with this scenario... Can anyone help me?
Mocking is based on the container, for Laravel to pick up your mocked classes, you should never use the new keyword. Instead use the container by using resolve().
$client = resolve(GuzzleWrapper::class);
This should work with one of the following mock approach where you use Mockery::mock(). But the way you are mocking the response, without seing the GuzzleWrapper, i would not expect it to return a Laravel response or else it is custom code.
$mockGuzzleClient = Mockery::mock(GuzzleWrapper::class, function (MockInterface $mock){
$mock->shouldReceive('post')
->andReturn(response()->json([$responseData], 200));
});
The most correct way would to use the Http facade. Your call in the controller should look like this.
Http::post($uri, $data);
The mocking should look like this, in general it seems like you are combining guzzle and Http interchangeable in your mocking attempts and that wont fly. If you mock with Http use the Http facade.
Http::fake([
$uri => Http::response(['your data' => 'response'], 200, []),
]);
What if you convert the GuzzleWrapper to a dependency? What I mean is to declare the GuzzleWrapper as the private property of the Controller, this way, it would be easier to test the controller: you need to pass the proper GuzzleWrapper to the constructor.
class ContactController {
private GuzzleWrapper $client;
public function __construct(GuzzleWrapper $client)
{
$this->client = $client;
}
public function addContacts(Request $request)
{
...
$response = $this->client->post($uri, $data);
if($response->getStatusCode() != 200) {
return response()->json("Problem getting data", 500);
}
...
}
}
Once you have done this, it should be easier to test. Then, if you need to test the client, I would recommend either the PHPUnit Mocks or Mockery.
If you can avoid testing the client (maybe you already tested it properly in other places), then I would recommend looking at my composer package https://packagist.org/packages/doppiogancio/mocked-client.
I have two projects in laravel, one only with my vision (AppView) and another with the web service (AppWs), in my web service I have the following route
Route project AppWs
Route::group(['prefix' => 'api'],function(){
Route::group(['prefix' => 'user'], function(){
Route::group(['prefix' => 'tipoprojeto'], function(){
Route::get('','Painel\TipoProjetoController#All');
});
});
});
when I access http://localhost/WebServiceApp/public/api/user/tipoprojeto/
it returns me an array with all the data, until then all right.
in my other project, I have a TypeProjectController controller and I have my index () method (AppView), so how can I retrieve the webservice data to load here?
EDIT
AppWs responsible for manipulating the data
public function All(){
return $this->ModelTipoProjeto->paginate(5);
}
AppView responsible for displaying data
Route::resource('/Painel/TipoProjeto', 'Painel\TipoProjetoController');
public function index()
{
$getData = `http://localhost/WebServiceApp/public/api/user/tipoprojeto/` // <~~
return view('Painel.TipoProjeto.index');
}
Retrieve the data that the AppWebservice link returns
First of all in order to consume an external service you have to perfrom a http request towards the endpoint where you intend to get the data form.
Your endpoing: http://localhost/WebServiceApp/public/api/user/tipoprojeto/
Install guzzle which is a php curl wrapper to perform http calls.
In your root dir open command line and inject guzzle in your project by firing :
composer require guzzlehttp/guzzle
Make sure you import guzzle at the top of the controller by adding
use GuzzleHttp\Client;
Then go to your index method and do the following:
public function index(){
// Create a client with a base URI
$client = new GuzzleHttp\Client(['base_uri' => 'http://localhost/WebServiceApp/public/api/user/tipoprojeto/']);
// Send a request to http://localhost/WebServiceApp/public/api/user/tipoprojeto/
$response = $client->request('GET', 'test');
// $response contains the data you are trying to get, you can do whatever u want with that data now. However to get the content add the line
$contents = $response->getBody()->getContents();
dd($contents);
}
$contents contains the data now you can do whatever you want.
I am learning Laravel 5.4, Still new to Laravel and PHPUnit. Everything is working great after following online basic tutorial.
This test function is working correctly when run phpunit
public function testBasicExample()
{
$response = $this->call( 'GET' , '/welcome');
$this->assertTrue(strpos($response->getContent(), 'Laravel') !== false);
}
Problem comes when I try to test Api
Steps I took
Create Api route for books
Return all users from users talbe as json from localhost/api/books/
public function index()
{
$users = DB::table('users')->get()->toJson();
echo $users;
}
I open the link in browser and json is returned correctly
copy and pasted json into online json validator jsonlint and it is valid.
Create a new test function
public function test_index_method_returns_all_books()
{
$response = $this->call( 'GET' , '/api/books/');
$this->assertEquals(200, $response->getStatusCode());
$data = json_decode($response->getContent(),true);
$this->assertJson($data);
}
run phpunit
200 status test passed but assertJson did not pass.
I tried to do var_dump for $response->getContent() and found out it return empty.
now I am not able to get getContent() for api/book/. Does anyone know if there is a solution for this?
Thanks.
Here is a screenshot
Try create some data with a factory before you call the api:
e.g.:
factory(\App\Books::class, 20)->create();
then
$response = $this->call( 'GET' , '/api/books/');
If you had set ":memory:" as your database on phpunit.xml, you will no longer see any data from your local database, that's why you should use factory instead.
I trying to pass some json to a controller in cakePHP 2.5 and returning it again just to make sure it is all going through fine.
However I getting no response content back. Just a 200 success. From reading the docs I am under the impression that if I pass some json then the responseHandler will the return json as the response.
Not sure what I am missing.
Data being passed
var neworderSer = $(this).sortable("serialize");
which gives
item[]=4&item[]=3&item[]=6&item[]=5&item[]=7
appController.php
public $components = array(
'DebugKit.Toolbar',
'Search.Prg',
'Session',
'Auth',
'Session',
'RequestHandler'
);
index.ctp
$.ajax({
url: "/btstadmin/pages/reorder",
type: "post",
dataType:"json",
data: neworderSer,
success: function(feedback) {
notify('Reordered pages');
},
error: function(e) {
notify('Reordered pages failed', {
status: 'error'
});
}
});
PagesController.php
public function reorder() {
$this->request->onlyAllow('ajax');
$data = $this->request->data;
$this->autoRender = false;
$this->set('_serialize', 'data');
}
UPDATE:
I have now added the following to the routes.php
Router::parseExtensions('json', 'xml');
and I have updated my controller to
$data = $this->request->data;
$this->set("status", "OK");
$this->set("message", "You are good");
$this->set("content", $data);
$this->set("_serialize", array("status", "message", "content"));
All now works perfectly.
A proper Accept header or an extension should to be supplied
In order for the request handler to be able to pick the correct view, you need to either send the appropriate Accept header (application/json), or supply an extension, in your case .json. And in order for extensions to be recognized at all, extension parsing needs to be enabled.
See http://book.cakephp.org/...views.html#enabling-data-views-in-your-application
The view only serializes view vars
The JSON view only auto-serializes view variables, and from the code you are showing it doesn't look like you'd ever set a view variable named data.
See http://book.cakephp.org/...views.html#using-data-views-with-the-serialize-key
The view needs to be rendered
You shouldn't disable auto rendering unless you have a good reason, and in your case also finally invoke Controller:render() manually. Currently your action will not even try to render anything at all.
CakeRequest::onlyAllow() is for HTTP methods
CakeRequest::onlyAllow() (which btw is deprecated as of CakePHP 2.5) is for specifying the allowed HTTP methods, ie GET, POST, PUT, etc. While using any of the available detectors like for example ajax will work, you probably shouldn't rely on it.
Long story short
Your reorder() method should look more like this:
public function reorder() {
if(!$this->request->is('ajax')) {
throw new BadRequestException();
}
$this->set('data', $this->request->data);
$this->set('_serialize', 'data');
}
And finally, in case you don't want/can't use the Accept header, you need to append the .json extension to the URL of the AJAX request:
url: "/btstadmin/pages/reorder.json"
and consequently enable extension parsing in your routes.php like:
Router::parseExtensions('json');
ps
See Cakephp REST API remove the necessity of .format for ways to use the JSON view without using extensions.
Output your json data
public function reorder() {
$this->request->onlyAllow('ajax');
$data = $this->request->data;
$this->autoRender = false;
$this->set('_serialize', 'data');
echo json_encode($data);
}
In Laravel 3, you could call a controller using the Controller::call method, like so:
Controller::call('api.items#index', $params);
I looked through the Controller class in L4 and found this method which seems to replace the older method: callAction(). Though it isn't a static method and I couldn't get it to work. Probably not the right way to do it?
How can I do this in Laravel 4?
You may use IoC.
Try this:
App::make($controller)->{$action}();
Eg:
App::make('HomeController')->getIndex();
and you may also give params:
App::make('HomeController')->getIndex($params);
If I understand right, you are trying to build an API-centric application and want to access the API internally in your web application to avoid making an additional HTTP request (e.g. with cURL). Is that correct?
You could do the following:
$request = Request::create('api/items', 'GET', $params);
return Route::dispatch($request)->getContent();
Notice that, instead of specifying the controller#method destination, you'll need to use the uri route that you'd normally use to access the API externally.
Even better, you can now specify the HTTP verb the request should respond to.
Like Neto said you can user:
App::make('HomeController')->getIndex($params);
But to send for instance a POST with extra data you could use "merge" method before:
$input = array('extra_field1' => 'value1', 'extra_field2' => 'value2');
Input::merge($input);
return App:make('HomeController')->someMethodInController();
It works for me!
bye
This is not the best way, but you can create a function to do that:
function call($controller, $action, $parameters = array())
{
$app = app();
$controller = $app->make($controller);
return $controller->callAction($app, $app['router'], $action, $parameters);
}
Route::get('/test', function($var = null) use ($params)
{
return call('TestController', 'index', array($params));
});
Laurent's solution works (though you need a leading / and the $params you pass to Request::create are GET params, and not those handled by Laravel (gotta put them after api/items/ in the example).
I can't believe there isn't an easier way to do this though (not that it's hard, but it looks kinda hackish to me). Basically, Laravel 4 doesn't provide an easy way to map a route to a controller using a callback function? Seriously? This is the most common thing in the world...
I had to do this on one of my projects:
Route::controller('players', 'PlayerController');
Route::get('player/{id}{rest?}', function($id)
{
$request = Request::create('/players/view/' . $id, 'GET');
return Route::dispatch($request)->getContent();
})
->where('id', '\d+');
Hope I'm missing something obvious.
$request = Request::create('common_slider', 'GET', $parameters);
return Controller::getRouter()->dispatch($request)->getContent();
For laravel 5.1
It's an Old question. But maybe is usefull. Is there another way.
In your controller: You can declare the function as public static
public static function functioNAME(params)
{
....
}
And then in the Routes file or in the View:
ControllerClassName::functionNAME(params);