Okay so I have a following situation:
The system I am building is retrieving data from a REST api and saving that data into a database. What I am wondering is how could this be implemented and where would behaviour like this go in sense of Laravels structure (controller, model etc.)? Does Laravel have a built in mechanism to retrieve data from external sources?
Edit:
Buzz hasn't been updated for over a year, it's recomended to now use Guzzle, see Mohammed Safeer's answer.
I have used Buzz package in order to make API requests.
You can add this package by adding it to the require section in your composer.json file.
{
require: {
"kriswallsmith/buzz": "dev-master"
}
}
Then run composer update to get it installed.
Then in Laravel you can wrap it in a class (perhaps a repository-like class) that handles making API request and returning data for your app to use.
<?php namespace My\App\Service;
class SomeApi {
public function __construct($buzz)
{
$this->client = $buzz;
}
public function getAllWidgets()
{
$data = $this->client->get('http://api.example.com/all.json');
// Do things with data, etc etc
}
}
Note: This is pseudocode. You'll need to create a class that works for your needs, and do any fancy dependency injection or code architecture that you want/need.
As #Netbulae pointed out, a Repository might help you. The article he linked is a great place to start. The only difference between the article and what your code will do is that instead of using an Eloquent model to get your data from your database, you're making an API request and transforming the result into a set of arrays/objects that your application can use (Essentially, just the data storage is different, which is one of the benefits of bothering with a repository class in the first place).
We can use package Guzzle in Laravel, it is a PHP HTTP client to send HTTP requests.
You can install Guzzle through composer
composer require guzzlehttp/guzzle:~6.0
Or you can specify Guzzle as a dependency in your project's existing composer.json
{
"require": {
"guzzlehttp/guzzle": "~6.0"
}
}
Example code in laravel 5 using Guzzle as shown below,
use GuzzleHttp\Client;
class yourController extends Controller {
public function saveApiData()
{
$client = new Client();
$res = $client->request('POST', 'https://url_to_the_api', [
'form_params' => [
'client_id' => 'test_id',
'secret' => 'test_secret',
]
]);
$result= $res->getBody();
dd($result);
}
First you have to make routes in your app/routes.php
/*
API Routes
*/
Route::group(array('prefix' => 'api/v1', 'before' => 'auth.basic'), function()
{
Route::resource('pages', 'PagesController', array('only' => array('index', 'store', 'show', 'update', 'destroy')));
Route::resource('users', 'UsersController');
});
Note: If you are not required authentication for API call, you can remove 'before' => 'auth.basic'
Here you can access index, store, show, update and destroy methods from your PagesController.
And the request urls will be,
GET http://localhost/project/api/v1/pages // this will call index function
POST http://localhost/project/api/v1/pages // this will call store function
GET http://localhost/project/api/v1/pages/1 // this will call show method with 1 as arg
PUT http://localhost/project/api/v1/pages/1 // this will call update with 1 as arg
DELETE http://localhost/project/api/v1/pages/1 // this will call destroy with 1 as arg
The command line CURL request will be like this (here the username and password are admin) and assumes that you have .htaccess file to remove index.php from url,
curl --user admin:admin localhost/project/api/v1/pages
curl --user admin:admin -d 'title=sample&slug=abc' localhost/project/api/v1/pages
curl --user admin:admin localhost/project/api/v1/pages/2
curl -i -X PUT --user admin:admin -d 'title=Updated Title' localhost/project/api/v1/pages/2
curl -i -X DELETE --user admin:admin localhost/project/api/v1/pages/1
Next, you have two controllers named PagesController.php and UsersController.php in your app/controllers folder.
The PagesController.php,
<?php
class PagesController extends BaseController {
/**
* Display a listing of the resource.
*
* #return Response
* curl --user admin:admin localhost/project/api/v1/pages
*/
public function index() {
$pages = Page::all();;
return Response::json(array(
'status' => 'success',
'pages' => $pages->toArray()),
200
);
}
/**
* Store a newly created resource in storage.
*
* #return Response
* curl --user admin:admin -d 'title=sample&slug=abc' localhost/project/api/v1/pages
*/
public function store() {
// add some validation also
$input = Input::all();
$page = new Page;
if ( $input['title'] ) {
$page->title =$input['title'];
}
if ( $input['slug'] ) {
$page->slug =$input['slug'];
}
$page->save();
return Response::json(array(
'error' => false,
'pages' => $page->toArray()),
200
);
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
* curl --user admin:admin localhost/project/api/v1/pages/2
*/
public function show($id) {
$page = Page::where('id', $id)
->take(1)
->get();
return Response::json(array(
'status' => 'success',
'pages' => $page->toArray()),
200
);
}
/**
* Update the specified resource in storage.
*
* #param int $id
* #return Response
* curl -i -X PUT --user admin:admin -d 'title=Updated Title' localhost/project/api/v1/pages/2
*/
public function update($id) {
$input = Input::all();
$page = Page::find($id);
if ( $input['title'] ) {
$page->title =$input['title'];
}
if ( $input['slug'] ) {
$page->slug =$input['slug'];
}
$page->save();
return Response::json(array(
'error' => false,
'message' => 'Page Updated'),
200
);
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return Response
* curl -i -X DELETE --user admin:admin localhost/project/api/v1/pages/1
*/
public function destroy($id) {
$page = Page::find($id);
$page->delete();
return Response::json(array(
'error' => false,
'message' => 'Page Deleted'),
200
);
}
}
Then you have model named Page which will use table named pages.
<?php
class Page extends Eloquent {
}
You can use Laravel4 Generators to create these resources using php artisan generator command. Read here.
So using this route grouping you can use the same application to make API request and as a front-end.
You can choose what to use:
Guzzle
CURL
file_get_contents :
$json = json_decode(file_get_contents('http://host.com/api/v1/users/1'), true);
Referrer
Try looking into the external API's manuals. There you will find info on how to retrieve information.
Then the best plan is to build an Interface.
Check this out:
http://culttt.com/2013/07/08/creating-flexible-controllers-in-laravel-4-using-repositories/
It's up to you how you use php to solve this.
Related
I want to test the next method of my controller
function index(){
if(Auth::User()->can('view_roles'))
{
$roles = Role::all();
return response()->json(['data' => $roles], 200);
}
return response()->json(['Not_authorized'], 401);
}
it is already configured for authentication (tymondesigns / jwt-auth) and the management of roles (spatie / laravel-permission), testing with postman works, I just want to do it in an automated way.
This is the test code, if I remove the conditional function of the controller the TEST passes, but I would like to do a test using a user but I have no idea how to do it.
public function testIndexRole()
{
$this->json('GET', '/role')->seeJson([
'name' => 'admin',
'name' => 'secratary'
]);
}
Depends on what kind of app are you building.
A - Using Laravel for the entire app
If your using Laravel for frontend/backend, well to simulate a logged-in user you could use the awesome Laravel Dusk package, made by the Laravel team. You can check the documentation here.
This package has some helpful methods to mock login sessions amongs a lot more of other things, you can use:
$this->browse(function ($first, $second) {
$first->loginAs(User::find(1))
->visit('/home');
});
That way you hit an endpoint with a logged-in user of id=1. And a lot more of stuff.
B - Using Laravel as a backend
Now, this is mainly how I use Laravel.
To identify a user that hits an endpoint, the request must send an access_token. This token helps your app to identify the user. So, you will need to make and API call to that endpoint attaching the token.
I made a couple of helper functions to simply reuse this in every Test class. I wrote a Utils trait that is being used in the TestCase.php and given this class is extended by the rest of the Test classes it will be available everywhere.
1. Create the helper methods.
path/to/your/project/ tests/Utils.php
Trait Utils {
/**
* Make an API call as a User
*
* #param $user
* #param $method
* #param $uri
* #param array $data
* #param array $headers
* #return TestResponse
*/
protected function apiAs($user, $method, $uri, array $data = [], array $headers = []): TestResponse
{
$headers = array_merge([
'Authorization' => 'Bearer ' . \JWTAuth::fromUser($user),
'Accept' => 'application/json'
], $headers);
return $this->api($method, $uri, $data, $headers);
}
protected function api($method, $uri, array $data = [], array $headers = [])
{
return $this->json($method, $uri, $data, $headers);
}
}
2. Make them available.
Then in your TestCase.php use the trait:
path/to/your/project/tests/TestCase.php
abstract class TestCase extends BaseTestCase
{
use CreatesApplication, Utils; // <-- note `Utils`
// the rest of the code
3. Use them.
So now you can do API calls from your test methods:
/**
* #test
* Test for: Role index
*/
public function a_test_for_role_index()
{
/** Given a registered user */
$user = factory(User::class)->create(['name' => 'John Doe']);
/** When the user makes the request */
$response = $this->apiAs($user,'GET', '/role');
/** Then he should see the data */
$response
->assertStatus(200)
->assertJsonFragment(['name' => 'admin'])
->assertJsonFragment(['name' => 'secretary']);
}
Side note
check that on top of the test methods there is a #test annotation, this indicates Laravel that the method is a test. You can do this or prefix your tests names with test_
I have a route that calls a third-party API and returns its response. This route can take an ID as a parameter like so: /my/route/{id}.
When I request /my/route/1 I get a 200 success, but when I do /my/route/2, /my/route/3, /my/route/4, etc I get 500 error.
The funny thing is that I always get the correct response body. So both the 200 and 500 responses are returning the data that I need.
My issue is when getSponsor(..) is triggered here:
<?php
namespace App\Http\Controllers\Matrix;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class SponsorReadingController extends Controller
{
/**
* SponsorReadingController constructor.
*/
public function __construct()
{
$this->cookieJar = app('MatrixCookieJar');
$this->client = app('GuzzleClientForMatrix');
}
public function getSponsor($sponsorId, Request $request)
{
// TODO: Refactor this monstrous function
if (!AuthController::isAlreadyLoggedIn($request)) {
$loginRes = AuthController::loginToMatrixApi($this->cookieJar);
if ($loginRes->getStatusCode() === 200) {
$sessionId = AuthController::getSessionIdFromResponse($loginRes);
} else {
return $loginRes;
}
} else {
$sessionId = null;
AuthController::setAuthCookie(
$this->cookieJar, $request->cookie('matrix_api_session')
);
}
$respData = [
'error' => null,
'message' => null,
'data' => json_decode(
$this->client->get(
'/admin/sponsor/detail',
[
'query' => ['sponsorId' => $sponsorId],
'cookies' => $this->cookieJar,
'allow_redirects' => false
]
)->getBody())->response
];
return $this->handleResponse($respData, $sessionId);
}
/**
* Handle the response with the provided data and
* cookie value.
*
* #param array $respData
* #param string $cookieVal
* #return Response
*/
public function handleResponse($respData, $cookieVal)
{
if (!empty($cookieVal)) {
return response()->json($respData)->withCookie(
cookie('matrix_api_session', $cookieVal, 29, '/matrix/api')
);
}
return response()->json($respData);
}
EDIT: If I do dd($res) instead of return $res inside handleResponse(...) I get a 200 status code, weird.
For reference, this answer helped me out: 500 Internal Server Error for php file not for html
So basically, I added ini_set('display_errors', 1) so the response would include any errors on the back-end that I wasn't seeing (turned out apache's error log had it), and sure enough there was an error that was not directly affecting the response so I would still get the correct response data.
The file were the cookies were being stored couldn't be seen from Guzzle's point of view, but the Laravel app itself could see it. For obvious reasons you need to specify the full path to a folder/file on the server. I ended up using Laravel's own storage folder to store them, so I had to change my service provider from this (where cookies/jar.json used to be in Laravel's public folder:
$this->app->bind('MatrixCookieJar', function ($app) {
return new FileCookieJar(
'cookies/jar.json', true
);
});
To this:
$this->app->singleton('MatrixCookieJar', function ($app) {
return new FileCookieJar(
storage_path('cookies').'/'.'jar.json', true
);
});
Trying to get the header authorization key in controller for making an API. Request is making from fiddler.
$headers = apache_request_headers();
And the $header contains an array.
Array
(
[User-Agent] => Fiddler
[Host] => localhost:8000
[Content-Length] => 102
[Authorization] => TestKey
)
If am trying like this to fetch the Authorization , its throwing error.
$header['Authorization]
Error :
Undefined index: Authorization
Tried many ways to get the authorization, but not working anything. Is there any way to fetch this?
To get headers from the request you should use the Request class
public function yourControllerFunction(\Illuminate\Http\Request $request)
{
$header = $request->header('Authorization');
// do some stuff
}
See https://laravel.com/api/5.5/Illuminate/Http/Request.html#method_header
Though it's an old topic, it might be useful for somebody...
In new Laravel versions, it's possible to get bearer Authorization token directly by calling Illuminate\Http\Request's bearerToken() method:
Auth::viaRequest('costom-token', function (Request $request) {
$token = $request->bearerToken();
// ...
});
Or directly from a controller:
public function index(Request $request) {
Log::info($request->bearerToken());
// ...
}
If you use a specific package like "JWT" or "sanctum" you can use their own middleware to retrieve user information.
Also, Laravel provides many ways to get the authorization key like :
$request->bearerToken(); to get only token without 'Bearer' word the result will be like 44|9rJp2TWvTTpWy535S1Rq2DF0AEmYbEotwydkYCZ3.
$request->header('Authorization'); to get the full key like Bearer 44|9rJp2TWvTTpWy535S1Rq2DF0AEmYbEotwydkYCZ3
P.S. you can use request() shortcut instead of using $request variable
You can try install the jwt(JSON Web Token Authentication for Laravel & Lumen) http://jwt-auth.com via composer.
And create a middleware that verify if exists yours token key in the header request.
After to install jwt, Your middleware it could be as follows
<?php
namespace App\Http\Middleware;
use Closure;
use JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
class VerifyJWTToken
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
try {
$user = JWTAuth::toUser($request->header('token'));
} catch (JWTException $e) {
if ($e instanceof TokenExpiredException) {
return response()->json([
'error' => 'token_expired',
'code' => $e->getStatusCode()
], $e->getStatusCode());
}
else if($e instanceof TokenInvalidException){
return response()->json([
'error' => "token_invalid",
'code' => $e->getStatusCode()
], $e->getStatusCode());
}
else {
return response()->json([
'error' => 'Token is required',
'code' => $e->getStatusCode(),
], $e->getStatusCode());
}
}
return $next($request);
}
}
I used token for example for a header key, but you can name it, as you like.
Then you could use this on any controller
There is a simple way to get headers in any file or controller with $_SERVER.
print_r($_SERVER); // check your header here
then, you can simply get your header:
$AuthToken = $_SERVER['HTTP_AUTHORIZATION'];
For my rest api I use yii\rest\Controller
class TweetController extends Controller
{
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBasicAuth::className(),
'auth' => [$this, 'auth']
];
$behaviors['contentNegotiator'] = [
'class' => ContentNegotiator::className(),
'formats' => [
'application\json' => Response::FORMAT_JSON,
]
];
return $behaviors;
}
// not solved yet
public function auth($pass)
{}
/**
* #param int $count
*
* #return array
* #throws \yii\base\InvalidConfigException
*/
public function actionLastTweets($count = 10)
{
/**
* #var TweetLastfinder $tweetLastFinder
*/
$tweetLastFinder = Yii::$app->get('tweetlastfinder');
return $tweetLastFinder->findLastTweets($count);
}
also i use prettyUrls 'GET tweet/last-tweets/<count>' => 'tweet/last-tweets'
actionLastTweets return array which one convert into json.
Idea is made simply authentication. In docs is example how to implements IdentityInterface in model. But i don`t work with AR directly. As i understand. I need to properly write auth() method.
I dont get it, whith value should be returned from auth() when authentification is passed? And how it will be send by request? (i mean http://localhost/index.php/tweet/last-tweets/50 without authentication, how it change?)
For simplicity there is a string value $password = 'qwerty' and i want to check if parameter $pass equal $password - authentication passed
some kinda:
public function auth($pass)
{
$password = 'qwerty';
if ($pass == $password) {
authentication passed
} else {
authentication failed
}
}
The subject of the authentication is your Identity (aka user) that has to implements the funtion findIdentityByAccessToken
see
\yii\web\IdentityInterface
to try your API on command line you can use cUrl as follows ( the token: XXXXXXXX_accessTokenString_XXXXXX )
note, the ":" divides user from password in the basic autentication, the token is used as user
POST a JSON to MessageController (the pluralization is not an error)
curl --user "XXXXXXXX_accessTokenString_XXXXXX:" -H "Accept:application/json" -H "Content-Type:application/json" -XPOST "http://rest.my-domain.com/messages" -d '{"email": "me#example.com"}'
POST a JSON to UserController enabling xdebug
curl --user "XXXXXXXX_accessTokenString_XXXXXX:" --cookie 'XDEBUG_SESSION=1221221' -H "Accept:application/json" -H "Content-Type:application/json" -XPOST "http://rest.my-domain.com/messages" -d '{"email": "me#example.com"}'
GET a JSON to UserController enabling xdebug
curl --user "XXXXXXXX_accessTokenString_XXXXXX:" -H "Accept:application/json" -H "Content-Type:application/json" -XGET "http://rest.my-domain.com/messages/123"
see also the Yii2 guide to REST services
http://www.yiiframework.com/doc-2.0/guide-rest-quick-start.html
Using Laravel 4 to create a "Read-it-Later" application just for testing purposes.
I'm able to successfully store a URL and Description into my application using the following curl command:
curl -d 'url=http://testsite.com&description=For Testing' readitlater.local/api/v1/url
I'm interested in using GET to accomplish the same thing but by passing my variables in a URL (e.g. readitlater.local/api/v1/url?url=testsite.com?description=For%20Testing)
Here is my UrlController segment:
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
$url = new Url;
$url->url = Request::get('url');
$url->description = Request::get('description');
$url->save();
return Response::json(array(
'error' => false,
'urls' => $urls->toArray()),
200
);
}
Here is my Url model:
<?php
class Url extends Eloquent {
protected $table = 'urls';
}
I read through the Laravel docs on input types but I'm not certain how to apply that to my current controller: http://laravel.com/docs/requests#basic-input
Any tips?
You didn't apply what you correctly linked to...Use Input::get() to fetch anything from GET or POST, and the Request class to get info on the current request. Are you looking for something like this?
public function store()
{
$url = new Url; // I guess this is your Model
$url->url = Request::url();
$url->description = Input::get('description');
$url->save();
return Response::json(array(
'error' => false,
'urls' => Url::find($url->id)->toArray(),
/* Not sure about this. You want info for the current url?
(you already have them...no need to query the DB) or you want ALL the urls?
In this case, use Url::all()->toArray()
*/
200
);
}