I'm wondering if it is possible to automate the pagination process for data obtained from external API like
$users = App\User::paginate(15);
for models. Maybe do you know any packages? I want to make something like that
$client = new \GuzzleHttp\Client();
$res = $client->request('GET', 'https://xxx');
$data = $res->getBody();
$res = json_decode($data );
///pagination
Do you know any solutions? Is the only one way to create pagination manually?
You could use Laravel resource.
First: create a single resource (I suppose your API is about Post)
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Post extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'name' => $this->resource['name'],
'title' => $this->resource['title']
];
}
}
Second: create a resource collection
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class PostCollection extends ResourceCollection
{
public function toArray($request)
{
return [
'data' => $this->collection
->map
->toArray($request)
->all(),
'links' => [
'self' => 'link-value',
],
];
}
}
after that you could set your api data to collection like this:
$client = new \GuzzleHttp\Client();
$res = $client->request('GET', 'https://xxx');
$data = $res->getBody();
$res = collect(json_decode($data));
return PostCollection::make($res);
and for add pagination to your resource collection you could do this:
$res = collect(json_decode($data));
$page = request()->get('page');
$perPage = 10;
$paginator = new LengthAwarePaginator(
$res->forPage($page, $perPage), $res->count(), $perPage, $page
);
return PostCollection::make($paginator);
for reading more about Laravel collection visit laravel documentation.
for knowing more about Consuming third-party APIs with Laravel Resources visit this great article.
Related
I have an app built on laravel 8 with a Vue Spa front end, using Sanctum.
I have a controller method that requests from another Laravel project (using its Sanctum API) so essentially, the Spa requests from Laravel 1, which requests from Laravel 2 project.
Following the responses from L2 project back, the Controller method on L2 is:
public function popular(Request $request)
{
$limit = 20;
if ($request->has('limit')) {
$limit = $request->limit;
}
$perPage = 20;
if ($request->has('per_page')) {
$limit = $request->per_page;
}
if ($request->has('page')) {
$articles = $request
->user()
->articles()
->activeArticles()
->select('articles.uuid', 'articles.title')
->orderBy('articles.views', 'DESC')
->simplePaginate($perPage);
} else {
$articles = $request
->user()
->articles()
->activeArticles()
->select('articles.uuid', 'articles.title')
->orderBy('articles.views', 'DESC')
->limit($limit)
->get();
}
return $articles;
}
This response is received by L1 Controller method, and sent back to the Spa like this:
public function popular(Request $request)
{
$apiEndPoint = self::$apiBaseUrl . '/article/popular';
$response = self::$httpRequest->get($apiEndPoint, $request->query());
if (!$response->successful()) {
return $this->setMessage(trans('help.api_error'))->send();
}
$body = $response->getBody();
return response(['data' => $body]);
}
With this return:
return response(['data' => $body]);
I get and empty data object:
{
data: {}
}
And with this return:
return response($body);
I get the payload as text / string:
[{"id":15,"uuid":"c6082143-0f34-443b-9447-3fa57ed73f48","name":"dashboard","icon":"database","active":1,"owned_by":2,"product_id":4,"created_at":"2021-12-23T11:46:35.000000Z","updated_at":"2021-12-23T11:46:35.000000Z"},{"id":16,
How do I return the $body as JSON to the Spa?
UPDATE: I tried suggestions below, but the result is still exception.
return response()->json($body);
Returns:
"message": "json_decode(): Argument #1 ($json) must be of type string, GuzzleHttp\\Psr7\\Stream given",
So getting the body in getBody() returns a string I understood.
If I Log the $body I get:
$body = $response->getBody();
Log::info($body);
[2021-12-25 23:15:36] local.INFO: {"current_page":2,"data":[{"uuid":"aa4a47bf-4975-4e78-868a-103398934504","title":"Ritchie-Hoeger"},
Thanks for any help and happy festive season.
API Responser
First create a trait in laravel in 'app\Traits\ApiResponser.php'
<?php
namespace App\Traits;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
trait ApiResponser{
public function set_response($data, $status_code, $status, $details)
{
$resData = response(json_encode(
[
'status' => $status, // true or false
'code' => $status_code,
'data' => $data,
'message' => $details
]
), 200)
->header('Content-Type', 'application/json');
$data = [];
return $resData;
}
}
Second in any controller call this trait's function set_response()
<?php
namespace App\Http\Controllers;
use App\Models\User;
use App\Traits\ApiResponser;
use Illuminate\Http\Request;
class ListController extends Controller
{
use ApiResponser;
public function getAllUserList(Request $request)
{
$data = User::select('id', 'name', 'email')->get();
return $this->set_response(['data' => $data], 200,'success', ['User list']);
}
}
Output will be like this
use the json helper function
return response()->json($body);
or
use Response;
return Response::json($body);
This will create an instance of \Illuminate\Routing\ResponseFactory. See the phpDocs for possible parameters below:
/**
* Return a new JSON response from the application.
*
* #param string|array $data
* #param int $status
* #param array $headers
* #param int $options
* #return \Symfony\Component\HttpFoundation\Response
* #static
*/
public static function json($data = array(), $status = 200, $headers = array(), $options = 0){
return \Illuminate\Routing\ResponseFactory::json($data, $status, $headers, $options);
}
I needed to ->getContents() after the ->getBody()
$body = $response->getBody()->getContents();
All good again...
I am doubting (life of a junior developer) what would be the most practical way to only return the value from a specific column in Laravel of a database record.
Example controller method:
public function show(ProductsCategory $category)
{
return $category;
}
This outputs all the columns, like this
{"id":104,"category_name":"Soft drinks","created_at":"2021-06-09T17:16:54.000000Z","updated_at":"2021-06-09T17:16:54.000000Z"}
However what I am after is just getting the category_name column retuned, like this
{"category_name":"Soft drinks"}
I can accomplish this by doing
public function show($id)
{
$category = ProductsCategory::select('category_name')->findOrFail($id);
echo json_encode($category)
exit;
}
However doubting if this would be the most practical way to go? Is there an more elegant/straight forward way? Or am I grossly overthinking this?
I think its developer choice . One way to select column like you mentioned .Another way is like below
$category = ProductsCategory::findOrFail($id,['category_name']);
By default findOrFail($id, $columns = ['*']) return all columns so they mentioned *
Also instead of json_encode as json,you can directly return $category
Also if you want to pass custom headers or status code then you can return like below
return response()->json($category)
Here is json method params
/**
* Create a new JSON response instance.
*
* #param mixed $data
* #param int $status
* #param array $headers
* #param int $options
* #return \Illuminate\Http\JsonResponse
*/
public function json($data = [], $status = 200, array $headers = [], $options = 0);
If you want to get full control over returned resource you should use API Resources.
To create resource file run:
php artisan make:resource ProductsCategoryResource
In resource file you can define fields to return, i.e. if you need only id and name you do this:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductCategoryResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
];
}
}
Then, set response in controller:
public function show($id)
{
$category = ProductsCategory::select('id', 'name')->findOrFail($id);
return new ProductCategoryResource($category);
}
You can also return resource collection:
public function index()
{
$categories = ProductsCategory::select('id', 'name')->get()l
return ProductCategoryResource::collection($category);
}
I think this is really elegant and organized way.
I have the following code:-
$token = encrypt($guuid);
$tokenDetail = AdminConfig::select('config_value')
->where(array(
'config_key' => 'expiry_duration',
'is_delete' => 0
))->first();
$expiryDuration = $tokenDetail['config_value'];
$expiryTime = date("dmyHis", time() + $expiryDuration);
$created_at = date('Y-m-d H:i:s');
$tokenUpdated = AppToken::updateOrCreate(array(
'user_id' => $user_id,
'token' => $token),
array('expiry'=>$expiryTime,
'created_date'=>$created_at,
'modified_date'=>$created_at)
);
if($tokenUpdated)
{
$return['status'] = 1;
$return['token'] = $token;
}
else
{
$return['status'] = 0;
$return['token'] = $token;
}
return $return;
I am using the updateOrCreate method so that if a record exists, then it will be updated. Else it will be created.
I am getting an exception message,
Add [user_id] to fillable property to allow mass assignment on [App\Http\Model\AppToken].
To be able to use the updateOrCreate method as you did, you have to add user_id to the fillable property within the AppToken class as demonstrated below.
class AppToken extends Model
{
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'user_id', 'token'
];
}
More information on this topic is available here.
You can do it simply by adding this code in your Model file :-
protected $guarded = [];
Like this
class AppToken extends Model
{
protected $guarded = [];
public function fun_name()
{
//function code
}
}
Hope this will help :)
I am using hashid to hash the id parameters in url. I have it set up in my model to automatically hash the id. This is working fine. My problem is decoding the hash in a middleware returns null. I'm not sure if this is a problem with my middleware or because of the hashing.
Model:
public function getIdAttribute($value)
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
return $hashids->encode($value);
}
Middleware:
<?php
namespace App\Http\Middleware;
use Closure;
class HashIdsDecode
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
dd($request->id); //Returns null on show method - example localhost:8000/invoices/afewRfshTl
if($request->has('id'))
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
dd($hashids->decode($request->input('id')));
}
return $next($request);
}
}
Route:
Route::resource('invoices','InvoiceController');
Controller:
public function show($id)
{
$invoice = Invoice::find($id);
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}
NOTE: if I bypass the middleware and do it directly in my controller like this it works but it requires me to repeat myself over and over and probably not the best way to do it.
public function show($id)
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),10);
$invoiceId = $hashids->decode($id)[0];
$invoice = Invoice::find($invoiceId);
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}
Personally, I would be more inclined to write a model trait. You can then use the trait on only the models required, rather than assuming every ID argument in a request is a Hash ID.
E.g.
namespace App\Models\Traits;
use Hashids\Hashids;
use Illuminate\Database\Eloquent\Builder;
trait HashedId
{
public function scopeHashId(Builder $query, $id)
{
$hashIds = new Hashids(env('APP_KEY'), 10);
$id = $hashIds->decode($id)[0];
return $query->where('id', $id);
}
}
Then to use it, you'd use the trait on your Invoice model (edit):
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Invoice extends Model
{
use \App\Models\Traits\HashedId;
// ...
}
And execute the following query in your controller:
public function show($id)
{
$invoice = Invoice::hashId($id)->firstOrFail();
return view('invoices.show', [
'invoice' => $invoice,
'page_title' => ' Invoices',
'page_description' => 'View Invoice',
]);
}
I am trying to integrate Paypal into my Laravel 5 site using this package:
http://packalyst.com/packages/package/netshell/paypal
When I go to: /paypal/checkout though, I get this error:
InvalidArgumentException in UrlGenerator.php line 561: Action
App\Http\Controllers\PayPalController#getDone not defined.
This is my route:
Route::get('/paypal/checkout', [
'as' => 'get-paypal-checkout', 'uses' => 'PayPalController#getCheckout'
]);
And this is the PayPalController:
<?php namespace App\Http\Controllers;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Paypal;
use Redirect;
use Illuminate\Http\Request;
class PayPalController extends Controller {
private $_apiContext;
public function __construct() {
$this->_apiContext = PayPal::ApiContext(
config('services.paypal.client_id'),
config('services.paypal.secret'));
$this->_apiContext->setConfig(array(
'mode' => 'sandbox',
'service.EndPoint' => 'https://api.sandbox.paypal.com',
'http.ConnectionTimeOut' => 30,
'log.LogEnabled' => true,
'log.FileName' => storage_path('logs/paypal.log'),
'log.LogLevel' => 'FINE'
));
}
public function getCheckout() {
$payer = PayPal::Payer();
$payer->setPaymentMethod('paypal');
$amount = PayPal:: Amount();
$amount->setCurrency('EUR');
$amount->setTotal(42); // This is the simple way,
// you can alternatively describe everything in the order separately;
// Reference the PayPal PHP REST SDK for details.
$transaction = PayPal::Transaction();
$transaction->setAmount($amount);
$transaction->setDescription('What are you selling?');
$redirectUrls = PayPal:: RedirectUrls();
$redirectUrls->setReturnUrl(action('PayPalController#getDone'));
$redirectUrls->setCancelUrl(action('PayPalController#getCancel'));
$payment = PayPal::Payment();
$payment->setIntent('sale');
$payment->setPayer($payer);
$payment->setRedirectUrls($redirectUrls);
$payment->setTransactions(array($transaction));
$response = $payment->create($this->_apiContext);
$redirectUrl = $response->links[1]->href;
return Redirect::to( $redirectUrl );
}
public function getDone(Request $request) {
$id = $request->get('paymentId');
$token = $request->get('token');
$payer_id = $request->get('PayerID');
$payment = PayPal::getById($id, $this->_apiContext);
$paymentExecution = PayPal::PaymentExecution();
$paymentExecution->setPayerId($payer_id);
$executePayment = $payment->execute($paymentExecution, $this->_apiContext);
// Clear the shopping cart, write to database, send notifications, etc.
// Thank the user for the purchase
return view('checkout.done');
}
public function getCancel() {
// Curse and humiliate the user for cancelling this most sacred payment (yours)
return view('checkout.cancel');
}
}
Any help would be appreciated.
Thanks
Although laravel allows you to generate url's to actions. I researched what happened in the code.
In the UrlGenerator class:
/**
* Get the URL to a controller action.
*
* #param string $action
* #param mixed $parameters
* #param bool $absolute
* #return string
*
* #throws \InvalidArgumentException
*/
public function action($action, $parameters = [], $absolute = true)
{
if ($this->rootNamespace && !(strpos($action, '\\') === 0)) {
$action = $this->rootNamespace.'\\'.$action;
} else {
$action = trim($action, '\\');
}
if (!is_null($route = $this->routes->getByAction($action))) {
return $this->toRoute($route, $parameters, $absolute);
}
throw new InvalidArgumentException("Action {$action} not defined.");
}
It will search for a defined route with the specified action and in fact returns a link based on that route. So you would have to define the route to PayPayController#getDone for it to work.