I have two laravel projects, one have an API(laravel_1) and the other consumes it(laravel_2).
I can call the api on Postman without problem or consume api on laravel_1 using guzzle call, but when I do the same on laravel_2 it throws error of databases if I try to call some eloquent method like
Articles::all(); for example, so I did some tests and seems like laravel_2 try to uses his .env when calling laravel_1.
If I call the laravel_1 api from postman it returns his app name correct "Laravel_1" but if I call it from laravel_2 it's says "Laravel_2" it takes his own app.env name instead of laravel_1 how is that? Also it's the same when I try to call a database
Here is laravel_1 api code:
web.php:
Route::post('/housingAPI/getEventOffers', 'HousingApiController#getEventOffers')->name("housingApi.getEventOffers");
HousingApiController:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class HousingApiController extends Controller
{
public function getEventOffers(Request $request)
{
//todo code here with requests
return config('app.name');
}
}
and here is laravel_2 config:
web.php
Route::get('/test', 'HousingInfoController#index')
HousingInfoController.php
<?php
namespace App\Http\Controllers\frontend;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class HousingInfoController extends Controller
{
public function test()
{
//Ofertas de eventos
$eventOffers = $this->getAllEventOffersApiCall("H", 1, 416, "esp", '');
}
private function getAllEventOffersApiCall($type, $zoneId, $hotelId, $lang, $moda, $ajax = false)
{
$form_params = [
'type' => $type,
'zoneId' => $zoneId,
'hotelId' => $hotelId,
'lang' => $lang,
'moda' => $moda,
'ajax' => $ajax
];
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', 'http://laravel-api.test/housingAPI/getEventOffers', ['form_params' => $form_params]);
$body = $response->getBody();
dd($body->getContents());
return json_decode($body->getContents(), true);
}
}
Related
In a Laravel project (Laravel 8 on PHP 8.0) I have a feature test in which I test an internal endpoint. The endpoint has a Controller calls a method on a Service. The Service then tries to call a third-party endpoint. It is this third-party endpoint that I would like to mock. The situation currently looks like this:
Internal Endpoint Feature Test
public function testStoreInternalEndpointSuccessful(): void
{
// arrange, params & headers are not important in this problem
$params = [];
$headers = [];
// act
$response = $this->json('POST', '/v1/internal-endpoint', $params, $headers);
// assert
$response->assertResponseStatus(Response::HTTP_OK);
}
Internal Endpoint Controller
class InternalEndpointController extends Controller
{
public function __construct(protected InternalService $internalService)
{
}
public function store(Request $request): InternalResource
{
$data = $this.internalService->fetchExternalData();
return new InternalResource($data); // etc.
}
}
Internal Service
use GuzzleHttp\ClientInterface;
class InternalService
{
public function __construct(protected ClientInterface $client)
{
}
public function fetchExternalData()
{
$response = $this->httpClient->request('GET', 'v1/external-data');
$body = json_decode($response->getBody()->getContents(), false, 512, JSON_THROW_ON_ERROR);
return $body;
}
}
I have looked at Guzzle's documentation, but it seems like the MockHandler strategy requires you to execute the http request inside of the test, which is not wat I want in my test. I want Guzzle's http client to be mocked and to return a custom http response that I can specify in my test. I have tried to mock Guzzle's http client like this:
public function testStoreInternalEndpointSuccessful(): void
{
// arrange, params & headers are not important in this problem
$params = [];
$headers = [];
$mock = new MockHandler([
new GuzzleResponse(200, [], $contactResponse),
]);
$handlerStack = HandlerStack::create($mock);
$client = new Client(['handler' => $handlerStack]);
$mock = Mockery::mock(Client::class);
$mock
->shouldReceive('create')
->andReturn($client);
// act
$response = $this->json('POST', '/v1/internal-endpoint', $params, $headers);
// assert
$response->assertResponseStatus(Response::HTTP_OK);
}
But the InternalService does not seem to hit this mock in the test.
I have also considered and tried to use Http Fake, but it didn't work and I assume Guzzle's http client does not extend Laravel's http client.
What would be the best way to approach this problem and mock the third-party endpoint?
Edit
Inspired by this StackOverflow question, I have managed to solve this problem by injecting a Guzzle client with mocked responses into my service. The difference to the aforementioned StackOverflow question is that I had to use $this->app->singleton instead of $this->app->bind because my DI was configured differently:
AppServiceProvider.php
namespace App\Providers;
use App\Service\InternalService;
use GuzzleHttp\Client;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
// my app uses ->singleton instead of ->bind
$this->app->singleton(InternalService::class, function () {
return new InternalService(new Client([
'base_uri' => config('app.internal.base_url'),
]));
});
}
}
Depending on your depending injection, you want to bind or singleton-ify your InternalService with a custom Guzzle http client that returns mocked responses, e.g. like this:
public function testStoreInternalEndpointSuccessful(): void
{
// depending on your DI configuration,
// this could be ->bind or ->singleton
$this->app->singleton(InternalService::class, function($app) {
$mockResponse = json_encode([
'data' => [
'id' => 0,
'name' => 'Jane Doe',
'type' => 'External',
'description' => 'Etc. you know the drill',
]
]);
$mock = new GuzzleHttp\Handler\MockHandler([
new GuzzleHttp\Psr7\Response(200, [], $mockResponse),
]);
$handlerStack = GuzzleHttp\HandlerStack::create($mock);
$client = new GuzzleHttp\Client(['handler' => $handlerStack]);
return new InternalService($client);
});
// arrange, params & headers are not important in this problem
$params = [];
$headers = [];
// act
$response = $this->json('POST', '/v1/internal-endpoint', $params, $headers);
// assert
$response->assertResponseStatus(Response::HTTP_OK);
}
See also: Unit Testing Guzzle inside of Laravel Controller with PHPUnit
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);
I am working on Laravel Project It require to add Google sheets & after add this it must be shareable at my site
this my Code.
When I open the link
http://localhost:8000/viewsheet/18ioWbPXjd1RM2wqJuJ0eFzkYAfC03OT9_X5tWJEV3uU/sheet/Phrasebook
from another browser, it gives me an error because I am not logged in
how I can make it shareable for all users without others login?
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Sheets;
use Google;
class sheetview extends Controller
{
//
public function __invoke(Request $request, $spreadsheet_id, $sheet_id)
{
//// print_r($request);
// $rows = $request->user()
// ->sheets()
// ->spreadsheet($spreadsheet_id)
// ->sheet($sheet_id)
// ->get();
$user = $request->user();
$token = [
'access_token' => $user->access_token,
'refresh_token' => $user->refresh_token,
'expires_in' => $user->expires_in,
'created' => $user->updated_at->getTimestamp(),
];
// all() returns array
$values = Sheets::setAccessToken($token)- >spreadsheet($spreadsheet_id)->sheet($sheet_id)->all();
print_r($values);
///$headers = $rows->pull(0);
// return view('sheets.viewsheet')->with(compact('headers', 'rows'));
}
}
I have a Command that is listening via Redis Pub/Sub. When a Publish is received, I want to call a controller method so that I can update the database.
However, I have not been able to find any solution on how to call a controller method with parameters from inside of the project but outside of the routes. The closest thing I have seen is something like:
return redirect()->action(
'TransactionController#assignUser', [
'transId' => $trans_id,
'userId' => $user_id
]);
My complete command that I've tried looks like this:
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Redis;
class RedisSubscribe extends Command
{
protected $signature = 'redis:subscribe';
protected $description = 'Subscribe to a Redis channel';
public function handle()
{
Redis::subscribe('accepted-requests', function ($request) {
$trans_array = json_decode($request);
$trans_id = $trans_array->trans_id;
$user_id = $trans_array->user_id;
$this->assignUser($trans_id, $user_id);
});
}
public function assignUser($trans_id, $user_id)
{
return redirect()->action(
'TransactionController#assignUser', [
'transId' => $trans_id,
'userId' => $user_id
]);
}
}
However, this does not seem to work. When I run this Command, I get an error that assignUser() cannot be found (even though it exists and is expecting two paramters). I am also not sure a "redirect" is really what I am after here.
Is there some other way to call a controller function in a Command, or some other way that would make this possible to do?
If your controller does not have any required parameters, you can just create the controller as a new object, and call the function.
$controller = new TransactionController();
$controller->assignUser([
'transId' => $trans_id,
'userId' => $user_id
]);
I am getting 404 while am trying to access my api url here is my route :
MY Api route list:
<?php
use Illuminate\Http\Request;
Route::middleware('auth:api')->get('/', function (Request $request) {
return $request->user();
});
Route::get('testapi','ApiDoctorController#testapi');
and the controller function that is provide data response is :
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ApiDoctorController extends Controller
{
public function testapi(){
$data = [
'name' => 'HEllo',
'age' => '24',
'height' => '5.4'
]
return response()->json($data);
}
}
When i try to access it in post-man at http://mydomain/project/api/testapi
its showing 404 error i am new to laravel help me out please
[![POSTMAN RESPONSE][1]][1][1]: https://i.stack.imgur.com/1uV6Z.png
First of all your missing the semicolon on the end of your data array.
Is Route::get('testapi','ApiDoctorController#testapi'); in your routes/api.php?
If not you'll need to define it so.
In postman you're doing domain/project/api/testapi when it should be domain/api/testapi as that is what you have specified in your routes file unless your entire laravel install is in domain/project.
I have added the semi-colon for you and formatted the code. If you're testing via postman please ensure that CSRF is disabled in your App/Http/Kernel.php (Just comment it out for your testing) then place it back in when you've setup authentication.
Let me know if this helps!
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ApiDoctorController extends Controller
{
public function testapi()
{
$data = [
'name' => 'Hello',
'age' => '24',
'height' => '5.4'
];
return response()->json($data);
}
}
Thanks everyone i got it i just add public after my project directory and it work thanks to all of you for your valuable time