Laravel : Dingo/API Pagination custom root key - php

I've developed an API with Laravel 5 and Dingo/API.
Following the documentation, i used pagination and my code look like that
$users = User::paginate(50);
return $this->response->paginator($users, new UserTransformer);
Unfortunately, the response root key is "data"
{
"data": [
{
"id": 1,
"username": "superuser",
......
I'd like to change the "data" key to a custom one, because in my case, emberjs get this response and try to make a link with a "datum" model which doesn't exist, the key need to be set with the same name as the ember model in case of a RESTAdapter.
I already tried some parameters in the response but nothing change
return $this->response->paginator($users, new UserTransformer, ['key' => 'users']);
or
return $this->response->paginator($users, new UserTransformer, ['identifier' => 'users']);
Nothing work, i'm stuck with the "data" key.
Is someone have a solution ?
Thank you in advance.

I managed to fix my problem.
I don't modify the api.php configuration, transformer stay the same
'transformer' => env('API_TRANSFORMER', Dingo\Api\Transformer\Adapter\Fractal::class),
Firstly i create a new serializer
// app/Api/V1/Serializers/CustomJsonSerializer.php
<?php namespace App\Api\V1\Serializers;
use League\Fractal\Pagination\CursorInterface;
use League\Fractal\Pagination\PaginatorInterface;
use League\Fractal\Serializer\SerializerAbstract;
/**
* Create a new Serializer in your project
*/
use League\Fractal\Serializer\ArraySerializer;
class CustomJsonSerializer extends ArraySerializer
{
public function collection($resourceKey, array $data)
{
if ($resourceKey === false) {
return $data;
}
return array($resourceKey ?: 'data' => $data);
}
public function item($resourceKey, array $data)
{
if ($resourceKey === false) {
return $data;
}
return array($resourceKey ?: 'data' => $data);
}
}
And i set my new custom serializer inside the AppServiceProviders
// app\Providers\AppServiceProviders.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Dingo\Api\Transformer\Adapter\Fractal;
use League\Fractal\Manager;
use App\Api\V1\Serializers\CustomJsonSerializer;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->app['Dingo\Api\Transformer\Factory']->setAdapter(function ($app) {
$fractal = new Manager();
$fractal->setSerializer(new CustomJsonSerializer());
return new Fractal($fractal);
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
I hope it'll help ppl :)

or you can use something like this
Answer::where('question_id', '=', $questionId)
->join('articles', 'answers.article_id', '=', 'articles.id')
->orderBy('count_thanks', 'desc')
->limit($perPage)
->offset($offset)
->get();

Related

How to generalize a resource function to be used in all controllers for different models?

In laravel API Resources:
I need a dynamic way to generalize a code for all resources to be used in all controllers instead of using resources in all methods for each controller .. for more clarification, I have a trait that includes generalized functions which return json responses with data and status code, lets take a "sample function" suppose it is showAll(Collection $collection) which is used for returning a collection of data of the specified model for example it is used for returning all users data ..
so I need to build a function that call what ever resource of the specified model, knowing that I have many models...
a) trait that include showAll method:
namespace App\Traits;
use Illuminate\Support\Collection;
trait ApiResponser
{
private function successResponse($data, $code) {
return response()->json($data, $code);
}
protected function showAll(Collection $collection, $code = 200) {
$collection = $this->resourceData($collection);
$collection = $this->filterData($collection);
$collection = $this->sortData($collection);
$collection = $this->paginate($collection);
$collection = $this->cacheResponse($collection);
return $this->successResponse([$collection, 'code' => $code], $code);
}
protected function resourceData(Collection $collection) {
return $collection;
}
}
b) usercontroller as a sample
namespace App\Http\Controllers\User;
use App\User;
use Illuminate\Http\Request;
use App\Http\Controllers\ApiController;
class UserController extends ApiController
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$users = User::all();
// Here the showAll(Collection $collection) is used
return $this->showAll($users);
}
}
c) UserResource:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'identity' => $this->id,
'name' => $this->name,
'email' => $this->email,
'isVerified' => $this->verified,
'isAdmin' => $this->admin,
'createDate' => $this->created_at,
'updateDate' => $this->updated_at,
'deleteDate' => $this->deleted_at,
];
}
}
generalize: means used everywhere without code redundancy
What about providers, you may load data there and make that data reachable at places where user data can be reachable ?
laravel docs
I found a simple solution.. by adding the following method
protected function resourceData($collection) {
$collection = get_class($collection[0]);
$resource = 'App\Http\Resources\\' . str_replace('App\\', '', $collection) .
'Resource';
return $resource;
}
The $collection[0] in the first line of this method will get the
model you are currently using.
get_class will get the model name ex: App\User
'App\Http\Resources\\' . str_replace('App\\', '', $collection):
This will get the path of the resource by adding 'App\Http\Resources\' before the
model
str_replace('App\\', '', $collection): will remove App\ path from the collection
name so App\User should be User
then 'Resource' would be concatenated with the previous results and the whole
string should be like that: App\Http\Resources\UserResource
So at the end you should return the whole string App\Http\Resources\UserResource
,finally you should call the resourceData() in
the showAll() method:
protected function showAll(Collection $collection, $code = 200) {
$collection = $this->resourceData($collection);
$collection = $this->filterData($collection);
$collection = $this->sortData($collection);
$collection = $this->paginate($collection);
//Calling resourceData() method
$resource = $this->resourceData($collection);
$collection = $this->cacheResponse($collection);
return $this->successResponse([$resource::collection($collection), 'code' => $code], $code);
}

Laravel method that can be used by multiple controllers and commands. Where should it be?

I'd like to define a "global" method that can be used by multiple controllers and commands. Where should it be placed in Laravel 5.4?
Let's say I have the following controller. How would I call the "global" method instead, and where would that "global" method be located exactly?
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Flight;
class FlightsController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Index
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$flights = Flight::where('active', 1)
->orderBy('name', 'desc')
->take(10)
->get();
foreach ($flights as $flight) {
if ( $flight->price == 0 )
{
$output = "some value";
}
else
{
$output = "some other value";
}
}
return view('flights.index')
->with(['output' => $output])
;
}
}
When you want a method that fetches many models, and you want to use it in many places, put it in a Repository:
class FlightRepository
{
public function getLastTenFlights()
{
return Flight::where('active', 1)
->orderBy('name', 'desc')
->take(10)
->get();
}
}
For example from your controller:
public function index( FlightRepository $repo )
{
$flights = $repo->getLastTenFlights();
//if you want you can put this additional login in the method too...
foreach ($flights as $flight) {
if ( $flight->price == 0 )
{
$output = "some value";
}
else
{
$output = "some other value";
}
}
return view('flights.index')
->with(['output' => $output])
;
}
You can create a Object and call the object when you want.
See example:
FlighRepository = new FlighRepository;
FlighRepository->index();
I personally prefer query scopes to repositories, so I would do something like this:
class Flight extends Model
{
// model setup
/**
* Scope query to get last 10 flights.
*
* #param \Illuminate\Database\Eloquent\Builder $query
* #return \Illuminate\Database\Eloquent\Builder
*/
public function scopeLastTen($query)
{
return $query->where('active', 1)->orderBy('name', 'desc')->take(10);
}
// rest of model
}
And you can use it similarly to how you're currently using it, only it's more readable:
$flights = Flight::lastTen()->get();
This also has the advantage of being able to chain other queries off of it. Say, for example, you wanted the last ten American Airlines flights, you could do:
$flights = Flight::lastTen()->where('airline', 'American')->get();
// equivalent to
// $flights = Flight::where('airline', 'American')->lastTen()->get();
I think that service is the best option to store the functionality which is shared between controllers and commands. You can access them using Service Container (https://laravel.com/docs/5.5/container).

Using parseincludes in Laravel5 Fractal

Struggling using parseIncludes in https://github.com/thephpleague/fractal.
I have two tables, Property and Weeks. Each property has many weeks. Using Fractal I can return my property item with a collection of weeks. What I want to do is use parseIncludes, so that the return of weeks is optional.
PropertyTransformer.php
<?php
namespace App\Transformer;
use App\Models\Property;
use League\Fractal\TransformerAbstract;
class PropertyTransformer extends TransformerAbstract
{
protected $availableIncludes = [
'week'
];
public function transform(Property $property)
{
return [
'id' => (int) $property['PropertyID'],
'PropertyName' => $property['PropertyName'],
'ExactBeds' => (int) $property['ExactBeds'],
'weeks' => $property->week
];
}
/**
* Include Week
*
* #return League\Fractal\ItemResource
*/
public function includeWeek( Property $property )
{
$week = $property->week;
return $this->item($week, new WeekTransformer);
}
}
WeekTransformer.php
<?php
namespace App\Transformer;
use App\Models\Week;
use League\Fractal;
class WeekTransformer extends Fractal\TransformerAbstract
{
public function transform(Week $week)
{
return [
'Week' => $week['week'],
'Available' => $week['available'],
'Price' => (int) $week['price'],
];
}
}
My PropertyController.php
<?php namespace App\Http\Controllers\Api\v1;
use App\Http\Requests;
use App\Models\Week;
use Illuminate\Support\Facades\Response;
use App\Models\Property;
use League\Fractal;
use League\Fractal\Manager;
use League\Fractal\Resource\Collection as Collection;
use League\Fractal\Resource\Item as Item;
use App\Transformer\PropertyTransformer;
class PropertyController extends \App\Http\Controllers\Controller {
public function show($id)
{
$property = Property::with('bedroom')->with('week')->find($id);
$fractal = new Fractal\Manager();
if (isset($_GET['include'])) {
$fractal->parseIncludes($_GET['include']);
}
$resource = new Fractal\Resource\Item($property, new PropertyTransformer);
//$resource = new Fractal\Resource\Collection($properies, new PropertyTransformer);
return $fractal->createData( $resource )->parseIncludes('weeks')->toJson();
}
I get the following error on the parseIncludes:-
Method 'parseIncludes' not found in class \League\Fractal\Scope
I'm following the guide here on transformers - http://fractal.thephpleague.com/transformers/
I think I am going wrong somewhere here where it says:-
These includes will be available but can never be requested unless the Manager::parseIncludes() method is called:
<?php
use League\Fractal;
$fractal = new Fractal\Manager();
if (isset($_GET['include'])) {
$fractal->parseIncludes($_GET['include']);
}
If I remove the parseIncludes, I don't get an error, I also get my property data with my collection of weeks, but ?include=week doesn't work to optionally get it.
Your problem is in this line:
return $fractal->createData( $resource )->parseIncludes('weeks')->toJson();
createData() returns \League\Fractal\Scope and it has no parseInlcudes method.
You've already called parseIncludes here:
if (isset($_GET['include'])) {
$fractal->parseIncludes($_GET['include']);
}
So just remove the second call to it in the return statement:
return $fractal->createData($resource)->toJson();

Laravel 5 return JSON or View depends if ajax or not

I would like to know if there is a magic method to use this scenario :
If I call a page via an AJAX request the controller returns a JSON object, otherwise it returns a view, i'm trying to do this on all my controllers without changin each method.
for example i know that i can do this :
if (Request::ajax()) return compact($object1, $object2);
else return view('template', compact($object, $object2));
but I have a lot of controllers/methods, and I prefer to change the basic behavior instead of spending my time to change all of them. any Idea ?
The easiest way would be to make a method that is shared between all of your controllers.
Example:
This is your controller class that all other controllers extend:
<?php namespace App\Http\Controllers;
use Illuminate\Routing\Controller as BaseController;
abstract class Controller extends BaseController
{
protected function makeResponse($template, $objects = [])
{
if (\Request::ajax()) {
return json_encode($objects);
}
return view($template, $objects);
}
}
And this is one of the controllers extending it:
<?php namespace App\Http\Controllers;
class MyController extends Controller
{
public function index()
{
$object = new Object1;
$object2 = new Object2;
return $this->makeResponse($template, compact($object, $object2));
}
}
Update for Laravel 5+
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
protected function makeResponse($request, $template, $data = [])
{
if ($request->ajax()) {
return response()->json($data);
}
return view($template, $data);
}
}
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class MyController extends Controller
{
public function index(Request $request)
{
$object = new Object1;
$object2 = new Object2;
return $this->makeResponse($request, $template, compact($object, $object2));
}
}
There is no magic but you can easily override ViewService in 3 steps:
1.create your view factory (your_project_path/app/MyViewFactory.php)
<?php
/**
* Created by PhpStorm.
* User: panos
* Date: 5/2/15
* Time: 1:35 AM
*/
namespace App;
use Illuminate\View\Factory;
class MyViewFactory extends Factory {
public function make($view, $data = array(), $mergeData = array())
{
if (\Request::ajax()) {
return $data;
}
return parent::make($view, $data, $mergeData);
}
}
2.create your view service provider (your_project_path/app/providers/MyViewProvider.php)
<?php namespace App\Providers;
use App\MyViewFactory;
use Illuminate\View\ViewServiceProvider;
class MyViewProvider extends ViewServiceProvider {
/**
* Register the application services.
*
* #return void
*/
public function register()
{
parent::register();
}
/**
* Overwrite original so we can register MyViewFactory
*
* #return void
*/
public function registerFactory()
{
$this->app->singleton('view', function($app)
{
// Next we need to grab the engine resolver instance that will be used by the
// environment. The resolver will be used by an environment to get each of
// the various engine implementations such as plain PHP or Blade engine.
$resolver = $app['view.engine.resolver'];
$finder = $app['view.finder'];
// IMPORTANT in next line you should use your ViewFactory
$env = new MyViewFactory($resolver, $finder, $app['events']);
// We will also set the container instance on this view environment since the
// view composers may be classes registered in the container, which allows
// for great testable, flexible composers for the application developer.
$env->setContainer($app);
$env->share('app', $app);
return $env;
});
}
}
3.in your_project_path/config/app.php:
change 'Illuminate\View\ViewServiceProvider',
to 'App\Providers\MyViewProvider',
What this do:
it tells your application to use another view provider which will register your view factory
$env = new MyViewFactory($resolver, $finder, $app['events']);
in line 33 of MyViewProvider.php which will check if request is AJAX and return if true or continue with original behavior
return parent::make($view, $data, $mergeData);
in MyViewFactory.php line 19
Hope this help you,
In laravel 5.1, this is the best way:
if (\Illuminate\Support\Facades\Request::ajax())
return response()->json(compact($object1, $object2));
else
return view('template', compact($object, $object2));
The solution suggested by #ryanwinchester is really good. I, however, wanted to use it for the responses from update() and delete(), and there naturally return view() at the end doesn't make a lot of sense as you mostly want to use return redirect()->route('whatever.your.route.is'). I thus came up with that idea:
// App\Controller.php
/**
* Checks whether request is ajax or not and returns accordingly
*
* #param array $data
* #return mixed
*/
protected function forAjax($data = [])
{
if (request()->ajax()) {
return response()->json($data);
}
return false;
}
// any other controller, e.g. PostController.php
public function destroy(Post $post)
{
// all stuff that you need until delete, e.g. permission check
$comment->delete();
$r = ['success' => 'Wohoo! You deleted that post!']; // if necessary
// checks whether AJAX response is required and if not returns a redirect
return $this->forAjax($r) ?: redirect()->route('...')->with($r);
}

Custom validator in Laravel 5

I am upgrading my Laravel application from 4 to 5. However, I have a custom validator that I cannot get to work.
In L4, I made a validators.php file and included it in global.php using require app_path().'/validators.php';.
I tried doing somewhat the same in L5. I dropped a validator in app/Validators/Validators.php, and updated my composer.json.
"files": [
"app/Validators/Validators.php"
]
However, now nothing renders on any page. What've I done wrong?
Try the following:
Make a bind class where you can implement each rule you want extending Validator class.
Make a service provider that extends ServiceProvider.
Add your custom validator provider at config/app.php file.
You can create the bind at Services folder like this:
namespace MyApp\Services;
class Validator extends \Illuminate\Validation\Validator{
public function validateFoo($attribute, $value, $parameters){
return $value == "foo"
}
}
Then, use a service provider to extends the core:
namespace MyApp\Providers;
use MyApp\Services\Validator;
use Illuminate\Support\ServiceProvider;
class ValidatorServiceProvider extends ServiceProvider{
public function boot()
{
\Validator::resolver(function($translator, $data, $rules, $messages)
{
return new Validator($translator, $data, $rules, $messages);
});
}
public function register()
{
}
}
Finally, import your service provider at config/app.php like so:
'providers' => [
...
...
'MyApp\Providers\ValidatorServiceProvider';
]
so here's what I did on adding a custom validation. this is for laravel 5.1
run PHP Artisan make:request MyFormValidationRequest file is created under app\Requests\MyFormValidationRequest.php
Here's the initial code:
<?php
namespace App\Http\Requests;
use App\Http\Requests\Request;
class MyFormValidationRequest extends Request
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
//
];
}
}
IMPORTANT: Change the return value of authorize() method to true, if you're not doing any authentication. it's initial value is false. else you get a white page with a "Forbidden" error message.
I added a rule under the function rules(), here's what it looks like
public function rules() {
return [
'activeuntil' => 'today_onwards'
];
}
today_onwards is my new validation.
I created a folder named 'Services' under App folder
I created a file named 'ValidatorExtended.php' under App\Services folder , here's the code below:
<?php
namespace App\Services;
use Illuminate\Validation\Validator;
use Carbon\Carbon;
class ValidatorExtended extends Validator {
private $_custom_messages = array(
"today_onwards" => "The :attribute must be today onwards",
);
public function __construct( $translator, $data, $rules, $messages = array(), $customAttributes = array() ) {
parent::__construct( $translator, $data, $rules, $messages, $customAttributes );
$this->_set_custom_stuff();
}
protected function _set_custom_stuff() {
//setup our custom error messages
$this->setCustomMessages( $this->_custom_messages );
}
protected function validateTodayOnwards( $attribute, $value ) {
$now = strtotime('-1 day');
$valueDateFormat = strtotime($value);
if($valueDateFormat > $now){
return true;
}
else {
return false;
}
}
}
Note: the validateTodayOnwards method is where you put your logic.
the name of the method should always start in "validate" then the name of your new validation key which should be in title case,
Another note your validation key should be separated by underscore and all small letters, in this case, "today_onwards". the underscore should be put before all first capital letters in the method name. I hope I explained it good.
TodayOnwards method is equivalent to validation name of "today_onwards",
another example, if I created validateOldPassword, your validation key should be "old_password".
I added below code in app\Providers\AppServiceProvider.php inside boot() method.
Validator::resolver(function($translator, $data, $rules, $messages = array(), $customAttributes = array())
{
return new ValidatorExtended($translator, $data, $rules, $messages, $customAttributes);
});
Don't forget to add below library, one is the Validator class and the other is your own class which is the "ValidatorExtended".
use App\Services\ValidatorExtended;
use Illuminate\Support\Facades\Validator;
Here's what the whole file looks like, [app\Providers\AppServiceProvider.php]
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\ValidatorExtended;
use Illuminate\Support\Facades\Validator;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
Validator::resolver(function($translator, $data, $rules, $messages = array(), $customAttributes = array())
{
return new ValidatorExtended($translator, $data, $rules, $messages, $customAttributes);
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
That's it. done. you created your own custom validation.
Additionally, if you want to use it in your controller, below is the code:
class testController extends Controller
{
public function updatePass(MiscValidation $request){
//code here
}
}
Instead of using Request Class you use your own class which is an extension of the Request class.

Categories