Laravel – Calling API connections - php

Repeated Calls – Lets say you need your Application to talk to an API and you're using guzzle or a wrapper or whatever. I find myself having to call the connection in every controller function e.g:
class ExampleController extends Controller
{
public function one()
{
$client = new Client();
$response = $client->get('http://',
[ 'query' => [ 'secret' => env('SECRET')]]);
$json = json_decode($response->getBody());
$data = $json->object;
// do stuff
}
public function two()
{
$client = new Client();
$response = $client->get('http://',
[ 'query' => [ 'secret' => env('SECRET')]]);
$json = json_decode($response->getBody());
$data = $json->object;
// do stuff
}
}
How do I better handle this? Do I use a service Provider? if so, how would I best implement these calls? Should I create another controller and call all my API connections in each function and then include that controller and call upon each function as required? Should I place it in a __construct?

lets try the Dependency inversion principle
Ok this might sound a bit hard at first and my code might have some typos or minor mistakes but try this
You need to create the interface
namespace app\puttherightnamespace; // this deppends on you
interface ExempleRepositoryInterface
{
public function getquery(); // if you passinga variable -> public function getquery('variable1');
}
Now you have to create the repo
class ExempleRepository implements ExempleRepositoryInterface {
public function getquery() {
$client = new Client();
$response = $client->get('http://',
[ 'query' => [ 'secret' => env('SECRET')]]);
$json = json_decode($response->getBody());
return $json->object;
}
Now last step is to bind the interface to the repo in a service provider register method
public function register()
{
$this->app->bind('namespacehere\ExempleRepositoryInterface', 'namespacehere\ExempleRepository');
}
Now everytime you need the result in a controller all you have to do is to ineject
class ExempleController extends Controller {
private $exemple;
public function __construct(ExempleRepositoryInterface $home) {
$this->exemple = $exemple;
}
public function test() {
$data = $this->exemple->getquery(); / you can pass a variable here if you want like this $this->exemple->getquery('variable');
// do stuff
}
this is not the simplest way but this is the best way i guess

Related

Testing API call in laravel controller/view

I'm testing an API call that I have working in Postman and other interfaces built over the API, but I'm trying to actually get my return values within my laravel site.
Should be simple enough, I made a very bare service file that uses guzzle to get to the API url, and I have a function in there for logging in using a test email, password and client ID (all of which work in the api interface such as Postman)
I'm calling the class and function inside my authController and then returning the function call in the view, then dumping that inside the view.
However, currently I'm getting null on my page dump.
Is there something I'm missing here, possibly in my service file. I don't think there should be anything passed here, but maybe I'm overlooking something more obvious.
Authorize.php
<?php
namespace App\Services\restAPI;
use Illuminate\Support\Facades\Auth;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\ServerException;
use Session;
use Exception;
class AuthService
{
protected $baseurl;
protected $guzzleClient;
public function __construct() {
$this->baseurl = config('http://ip.address');
$this->baseurl = rtrim($this->baseurl, '/') . '/'; // ensure trailing slash
$this->guzzleClient = new \GuzzleHttp\Client(
['verify' => config('app.ca_pem_file'),
'headers' => [
'Event-ID' => ACCESS_EVENT_ID
],
'base_uri' => $this->baseurl
]
);
}
public function loginGetToken(){
$password = "password";
$email = "test#thisAPI.com";
$client_id = "0";
$payload = (object)[
'password'=>(string)$password,
'email'=>(string)$email
];
$retval = $this->post('login/?client_id='.$client_id,$payload);
return $retval;
}
private function post($endpoint,$payload){
$result = $this->guzzleClient->request('POST', $endpoint, ['json'=>$payload]);
$body = $result->getBody();
return json_decode($body);
}
}
AuthController.php
use Redirect;
use Illuminate\Http\Request;
use App\Services\restAPI\AuthService;
class AuthController extends Controller
{
public function login(Request $request)
{
$authService = new AuthService();
$login = $authService->loginGetToken();
return redirect(route('auth.login'))
->with('login', $login)
}
login.blade.php
<?php
dd($login);
?>
Since you are redirecting, the data is not directly available in the view as a variable. It should be in the session though, so you can try this in your view:
dd(session('login'));
https://laravel.com/docs/5.7/redirects#redirecting-with-flashed-session-data

Mockery, App Instance not working on some laravel tests

Basically, my question is; Why does the $this->app->instance() Call work on one instance of the mocked object, but the other doesn't...
In the example below, the getGroupSingleSignOnLink function actually gets called, the other is mocked and the test passes...
TEST
namespace Tests\Feature;
use App\Models\Group;
use App\Models\User;
use Tests\TestCase;
use App\Clients\SingleSignOnApi;
use Mockery;
class SingleSignOnTest extends TestCase
{
private $validUrl = 'http://www.google.com';
public function setUp()
{
parent::setUp();
$single_sign_on = Mockery::mock(SingleSignOnApi::class);
$single_sign_on->shouldReceive('getGroupSingleSignOnLink')->andReturn($this->validUrl);
$single_sign_on->shouldReceive('getSingleSignOnLink')->andReturn($this->validUrl);
$this->app->instance(SingleSignOnApi::class, $single_sign_on);
}
//THIS TEST FAILS, SingleSignOnApi Class Not Mocked
public function testGroupAuthConnection()
{
$group = Group::whereNotNull('external_platform_key')->first();
$user = $group->users()->first();
$this->be($user);
$group_sso = $group->groupAuthConnections()->first();
$response = $this->get(route('sso.group.connect', ['id' => $group_sso->id]));
$response->assertRedirect($this->validUrl);
$response->assertSessionMissing('__danger');
}
//THIS TEST PASSES, The SingleSignOnApi Class is Mocked
public function testAuthConnectionConnect()
{
$user = User::first();
$this->be($user);
$sso = $user->authConnections()->firstOrFail();
$response = $this->get(route('sso.connect', ['id' => $sso->id]));
$response->assertRedirect($this->validUrl);
$response->assertSessionMissing('__danger');
}
}
CONTROLLER FUNC - TEST MOCK WORKING
public function connect($id)
{
$auth_connection = $this->findAuthConnection($id, Auth::user());
$sso_client = App::make(SingleSignOnApi::class);
$url = $sso_client->getSingleSignOnLink($auth_connection);
return redirect($url);
}
CONTROLLER FUNC - TEST MOCK NOT WORKING
public function connect($id)
{
$group_ids = Auth::user()->groups()->pluck('groups.id')->toArray();
$group_auth_connection = $this->findGroupAuthConnection($id, Auth::user());
//This is the Mocked Object in my Test: SingleSignOnApi
$sso_client = App::make(SingleSignOnApi::class, [$group_auth_connection->group->external_platform_key]);
$url = $sso_client->getGroupSingleSignOnLink($group_auth_connection, Auth::user());
return redirect($url);
}
I'll use Quickbooks as an example to illustrate how I got this to work consistently for me.
Here's my AppServiceProvider, defining a custom QuickbooksSDK Class I created:
...
public function boot()
{
$this->app->bind(QuickbooksSDK::class, function($app) {
return new QuickbooksSDK(
new GuzzleHttp\Client([
'base_uri' => config('invoicing.baseUri'),
'timeout' => 3,
'headers' => [
'Authorization' => 'Bearer '.config('invoicing.apiKey'),
'Accept' => 'application/json'
],
'http_errors' => false
]),
config('invoicing.apiKey'),
env('QUICKBOOKS_REFRESH_TOKEN'),
env('QUICKBOOKS_CLIENT_ID'),
env('QUICKBOOKS_CLIENT_SECRET')
);
});
}
Then I created a second custom class, called the QuickbooksInvoicingDriver, that takes the instantiated SDK Class from the Service container:
public function __construct()
{
$this->api = app(QuickbooksSDK::class);
}
Finally, in my test class, I can mock the QuickbooksSDK, with my own custom responses, to make testing easier:
$vendorResponse = '{"Vendor": {"Balance": 0,"Vendor1099": false,"CurrencyRef": {"value": "GYD","name": "Guyana Dollar"},"domain": "QBO","sparse": false,"Id": "25","SyncToken": "0","MetaData": {"CreateTime": "2018-04-04T12:36:47-07:00","LastUpdatedTime": "2018-04-04T12:36:47-07:00"},"DisplayName": "daraul","PrintOnCheckName": "daraul","Active": true},"time": "2018-04-04T12:36:47.576-07:00"}';
$mock = new MockHandler([
new Response(200, [], $vendorResponse),
]);
$handler = HandlerStack::create($mock);
$client = new Client(['handler' => $handler]);
$api = new QuickbooksSDK(
$client,
'test',
'test',
'test',
'test'
);
$this->app->instance(QuickbooksSDK::class, $api);
Now I can run my tests normally, without worrying about third parties. These links were really helpful for me:
http://docs.guzzlephp.org/en/stable/testing.html
https://laravel.com/docs/5.5/container
Let me know if this was helpful.

Slim Framework 3 - using router in a global template

I'm relatively new to Slim Framework 3. One thing I'm trying to understand is how to use the router, $this->router, in a "global" template.
What I mean by this is a template such as a navigation menu - something that appears on every page.
For templates I'm using the "php-view" library as per the example tutorial which I installed with:
composer require slim/php-view
In my templates directory I have a file called nav.php where I want to output my links.
I understand how to call the router like so
Sign Up
But... the example tutorial only shows how you would pass that link from 1 individual place, e.g. $app->get('/sign-up' ... })->setName("sign-up");
How can you use the router globally in any template, without passing it into every individual URL route as a parameter?
I'm more familiar with frameworks like CakePHP where there is an "AppController" which allows you to set things globally, i.e. available in every request. I don't know if this is how it's done in Slim but this is the effect I'm after.
Well, you can pass it as template variable.
When you instantiate or register PhpRenderer in a container, you have multiple options to define a "global" variable, i.e. a variable that is accessible in all of your templates:
// via the constructor
$templateVariables = [
"router" => "Title"
];
$phpView = new PhpRenderer("./path/to/templates", $templateVariables);
// or setter
$phpView->setAttributes($templateVariables);
// or individually
$phpView->addAttribute($key, $value);
Assuming you're registering PhpRenderer via Pimple:
<?php
// Create application instance
$app = new \Slim\App();
// Get container
$container = $app->getContainer();
// Register PhpRenderer in the container
$container['view'] = function ($container) {
// Declaring "global" variables
$templateVariables = [
'router' => $container->get('router')
];
// And passing the array as second argument to the contructor
return new \Slim\Views\PhpRenderer('path/to/templates/with/trailing/slash/', $templateVariables);
};
<?php namespace App\Helpers;
/********************/
//LinksHelper.php
/********************/
use Interop\Container\ContainerInterface;
class LinksHelper
{
protected $ci;
public function __construct(ContainerInterface $container){
$this->ci = $container;
}
public function __get($property){
if ($this->ci->has($property)) {
return $this->ci->get($property);
}
}
public function pathFor($name, $data = [], $queryParams = [], $appName = 'default')
{
return $this->router->pathFor($name, $data, $queryParams);
}
public function baseUrl()
{
if (is_string($this->uri)) {
return $this->uri;
}
if (method_exists($this->uri, 'getBaseUrl')) {
return $this->uri->getBaseUrl();
}
}
public function isCurrentPath($name, $data = [])
{
return $this->router->pathFor($name, $data) === $this->uri->getPath();
}
public function setBaseUrl($baseUrl)
{
$this->uri = $baseUrl;
}
}
?>
<?php
/********************/
//dependencies.php
/********************/
$container['link'] = function ($c) {
return new \App\Helpers\LinksHelper($c);
};
// view renderer
$container['view'] = function ($c) {
$settings = $c->get('settings');
$view = new App\Views\MyPhpRenderer($settings['renderer']['template_path']);
$view->setLayout('default.php');
//$view->addAttribute('title_for_layout', $settings['title_app'] .' :: ');
$view->setAttributes([
'title_for_layout'=>$settings['title_app'] .' :: ',
'link' => $c->get('link')
]);
return $view;
};
?>
<?php
/********************/
//routes.php
/********************/
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
$app->get('/', function (Request $request, Response $response, array $args) {
return $this->view->render($response, 'your_view.php');
})->setName('home');
?>
<?php
/********************/
//your_view.php
/********************/
?>
Home
You should create a new class, e.g. MainMenu, and there you should create an array with all paths for menu. Object of MainMenu should return an array with labels and paths and then you can pass that array to your view:
$menu = (new MainMenu())->buildMenu();
$response = $this->view->render($response, "index.phtml", [
'menu' => $menu
]);
Then in your *.phtml file you have access to the $menu variable. But what if you do not want repeat that code in each route?
Use middlewares. You can pass a variable from middleware using
$request = $request->withAttribute('foo', 'bar');
and retrieve
$foo = $request->getAttribute('foo');

Symfony2 UrlGenerator in tests

I'm trying to achieve generated routes in my functional tests. I want them to be generated dynamically,preferably by name, but I cannot find a proper way how to do it. Point me please.
A simple test:
public function testIndex()
{
// I have service container
$container = $this->client->getContainer();
$crawler = $client->request('GET', helper_function("route_name", $params));
$heading = $crawler->filter('h1')->eq(0)->text();
$this->assertEquals('Application list', $heading);
}
What that helper_function() should be?
Since you have access to service container you can get router (which by default will return Symfony\Component\Routing) service and call generate method on it.
$route = $container->get('router')->generate($routeName, $params);
In the setup of the WebTestCase class you can take an instance of the router component then use it as usually.
As Example:
class AcmeDemoTestCase extends WebTestCase
protected $router;
protected function setUp()
{
........
$this->client = static::createClient();
$this->router = $this->client->getContainer()->get('router');
.....
}
public function testIndex()
{
$crawler = $this->client->request('GET', $this->router->generate($routeName, $params););
}
Hope this help

How do I access the Router through an artisan command?

I need access to the RouteCollection that Laravel possesses when it gets ran normally and all ServiceProviders are booted. I need the RouteCollection because a legacy app needs to know if Laravel has the particular route, so if it doesn't, legacy code can take care of the route.
I figure if I can somehow get a hold of Illuminate\Routing\Router in an artisan command, I could simply call the getRoutes() function and output a JSON file containing an array of all the routes. Then when the legacy code needs to determine if Laravel supports the Route, it could read that file.
In order to do that though, I need access to the Router class. Not sure how to accomplish that... I looked at Illuminate\Foundation\Console\RoutesCommand source code and I can't figure out how it works. What's really odd is it looks like the Router class is being injected, but when I do Artisan::resolve('MyCommand'), I get an empty RouteCollection.
EDIT
I never did figure out how to accomplish this question, but for those in a similar situation, I found this works for the most part, although I'm not sure how bad the overhead is starting Laravel each request just to check the routes. Right now it doesn't seem like that much.
// Start laravel, so we can see if it should handle the request
require_once(__DIR__.'/laravel/bootstrap/autoload.php');
$app = require_once(__DIR__.'/laravel/bootstrap/start.php');
$app->boot();
// Create the fake request for laravel router
$request = Request::createFromGlobals();
$request->server->set('PHP_SELF', '/laravel/public/index.php/'.$_REQUEST['modname']);
$request->server->set('SCRIPT_NAME', '/laravel/public/index.php');
$request->server->set('SCRIPT_FILENAME', __DIR__.'/laravel/public/index.php');
$request->server->set('REQUEST_URI', '/laravel/public/'.$_REQUEST['modname']);
$routes = Route::getRoutes();
foreach($routes as $route) {
if ($route->matches($request)) {
$app->run($request);
exit;
}
}
Here is a simplified implementation
JsonRoutes.php
<?php
use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
use Illuminate\Routing\Route;
use Illuminate\Routing\Router;
class JsonRoutes extends Command {
protected $name = 'routes:json';
protected $description = 'Spits route information in JSON format.';
protected $router;
protected $routes;
public function __construct(Router $router) {
parent::__construct();
$this->router = $router;
$this->routes = $router->getRoutes();
}
public function fire() {
$result = [];
foreach ($this->routes as $route) {
$result[] = [
'methods' => implode(',', $route->methods()),
'host' => $route->domain(),
'uri' => $route->uri(),
'name' => $route->getName(),
'action' => $route->getActionName(),
];
}
$file = $this->option('file');
if ($file) {
File::put($file, json_encode($result));
return;
}
$this->info(json_encode($result));
}
protected function getArguments() { return []; }
protected function getOptions() {
return [
['file', 'f', InputOption::VALUE_OPTIONAL, 'File name.'],
];
}
}
You register it in artisan.php:
Artisan::resolve('JsonRoutes');
Sample usage:
Spit it out to stdout
$ artisan routes:json
[{"methods":"GET,HEAD","host":null,"uri":"\/","name":null,"action":"Closure"}]
Write it to a file
$ artisan routes:json -f /tmp/routes.json

Categories