how to use apiReources method with `only`? - laravel

I'm creating an api with Laravel and I am looking for an easy lazy way to to register Api resources.
I'm currently defining my routes like this:
Route::apiResource('categories', 'CategoryController')->only(['index', 'show']);
I checked Laravel's controller documentation and I saw apiResources method which I can create multiple api resources at once.
the goal:
is to be able to use apiResources with only method like this
Route::apiResources(['categories' => 'CategoryController', 'products' => 'ProductController'])->only(['index', 'show']);
current result:
Call to a member function only() on null

long story short (if you don't want to read the whole story) you can just do it like this:
Route::apiResources(['brands' => 'BrandController', 'categories' => 'CategoryController'], ['only' => ['index', 'show']]);
When I was writing the question it passed to my mind to check the apiResources declaration and I found this:
/**
* Register an array of API resource controllers.
*
* #param array $resources
* #param array $options
* #return void
*/
public function apiResources(array $resources, array $options = [])
{
foreach ($resources as $name => $controller) {
$this->apiResource($name, $controller, $options);
}
}
and since it is using apiResource under the hood and it is passing options parameter I can check what are these options
/**
* Route an API resource to a controller.
*
* #param string $name
* #param string $controller
* #param array $options
* #return \Illuminate\Routing\PendingResourceRegistration
*/
public function apiResource($name, $controller, array $options = [])
{
$only = ['index', 'show', 'store', 'update', 'destroy'];
if (isset($options['except'])) {
$only = array_diff($only, (array) $options['except']);
}
return $this->resource($name, $controller, array_merge([
'only' => $only,
], $options));
}

Related

How can i solve this error "Too few arguments to function App\Http\Controllers\CtnController::show(), 0 passed and exactly 1 expected"

I have reviewed similar questions but none of the solutions worked for me. I have show view that fetches data from the db which I want to display. I believe I have the right code for my show function on my CtnController but I keep getting this frustrating error. Ctn in this case is a type of form I'm trying to create.
This is my controller.
<?php
namespace App\Http\Controllers;
use App\Ctn;
use Illuminate\Http\Request;
class CtnController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
$ctns = Ctn::orderBy('created_at', 'desc')->paginate(5);
return view('/ctn.index')->with('ctns', $ctns);
}
public function create(){
return view('/ctn.create');
}
public function store(Request $request){
$validatedData = $request -> validate([
'bol' => 'required',
'carrier' => 'required',
'address' => 'required',
'etd' => 'required',
'eta' => 'required',
'portload' => 'required',
'portdischarge' => 'required',
]);
$ctn = new Ctn;
$ctn->bill_landing = request('bol');
$ctn->carrier = request('carrier');
$ctn->address = request('address');
$ctn->eta = request('eta');
$ctn->etd = request('etd');
$ctn->incoterm = request('incoterm');
$ctn->forwarder = request('forwarder');
$ctn->ctnref = request('ctnref');
$ctn->portloading = request('portload');
$ctn->portdischarge = request('portdischarge');
$ctn->quantity = request('quantity');
$ctn->origin_goods = request('origin');
$ctn->cost_goods = request('cost');
$ctn->currency = request('currency');
$ctn->package_type = request('package');
$ctn->save();
return redirect('/ctn')->with('success', 'CTN created');
}
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
$ctn = Ctn::find($id);
return view('/ctn.show', compact('ctn'));
}
}
Below is my show route on web.php file
Route::get('/ctn/show', 'CtnController#show')->name('show');
The show form is just a HTML form.
Your show() method excepts an $id, however, you've not specified the value in your route. Change your route definition so that is can accept the id:
Route::get('/ctn/show/{id}', 'CtnController#show')->name('show');
This will assume that you're using a url like:
http://example.com/ctn/show/1
For more information you can view the Route Parameters documentation
The $id argument of your show method expects an implicit binding from the route parameters, but your routes does not know any id parameter, therefore it can't be bound to your method.

How to Paginate loaded relation with Laravel API resources

I need to load model relations in it's resource and paginate them.
In my case i have Category and Path models, plus CategoryResource and PathResource
The toArray method of CategoryResource is like below:
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'order' => $this->order,
'paths' => PathResource::collection($this->whenLoaded('paths'))
];
}
and toArray method of PathResource is like below:
public function toArray($request)
{
return parent::toArray($request);
}
Question is how can i load and paginate related Path's in my CategoryResource?
I had same problem and solved it this way:
Prerequisites
You must have/create a resource for Path model i.e. PathResource
to create one use this command:
php artisan make:resource PathResource
Solution
The solution is to use laravel paginate on relation and use transform method on the paginated collection to convert it's items to your resource.
First Step
Create a base class for paginating any resource in your app, using this command:
php artisan make:resource PaginatedCollection -c
Edit the PaginatedCollection and add following codes:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class PaginatedCollection extends ResourceCollection
{
/**
* An array to store pagination data that comes from paginate() method.
* #var array
*/
protected $pagination;
/**
* PaginatedCollection constructor.
*
* #param mixed $resource paginated resource using paginate method on models or relations.
*/
public function __construct($resource)
{
$this->pagination = [
'total' => $resource->total(), // all models count
'count' => $resource->count(), // paginated result count
'per_page' => $resource->perPage(),
'current_page' => $resource->currentPage(),
'total_pages' => $resource->lastPage()
];
$resource = $resource->getCollection();
parent::__construct($resource);
}
/**
* Transform the resource collection into an array.
* now we have data and pagination info.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
// our resources
'data' => $this->collection,
// pagination data
'pagination' => $this->pagination
];
}
}
Second Step
make a collection resource for your model and extend PaginatedCollection
instead of default ResourceCollection.
Run this command to do so:
php artisan make:resource PathCollection -c
Now edit your new collection class PathCollection and override toArray method:
/**
* Transform the resource collection into an array.
*
* In this method use your already created resources
* to avoid code duplications
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
// Here we transform any item in paginated items to a resource
'data' => $this->collection->transform(function ($path) {
return new PathResource($path);
}),
'pagination' => $this->pagination,
];
}
Final Step
In your CategoryResource use PathCollection like this:
return [
'id' => $this->id,
'name' => $this->name,
'slug' => $this->slug,
'order' => $this->order,
'paths' => new PathCollection(
new LengthAwarePaginator(
$this->whenLoaded('paths'),
$this->paths_count,
10
)
),
];
and make sure you import LengthAwarePaginator class:
use Illuminate\Pagination\LengthAwarePaginator;
Usage
$category = Category::with('paths')->withCount('paths')->find(1);
return new CategoryResource($category);
You should probably checkout the documentation on Resources and ResourceCollections. ResourceCollections will allow you to easily paginate your resources. Api Resource Collection Pagination Documentation

Specifying Middleware Within Controller's Constructor

Here's a code snippet from laravel in-built RegisterController.
public function __construct(){
$this->middleware('guest'); //What does it actually do?
}
I know that it's a good practice to define middleware in the
controller constructor but I need to know what
$this->middleware('guest') actually does and what parameter (where
?) it sets.
Second question: within the same controller (RegisterController), we use RegisterUser (defined in namespace Illuminate\Foundation\Auth) but it seems we never use it throughout the controller (overriding methods or attributes). I'm a little bit confused. Thnaks in advance!
class RegisterController extends Controller
{
use RegistersUsers; //?????
/**
* Where to redirect users after registration.
*
* #var string
*/
protected $redirectTo = '/home';
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get a validator for an incoming registration request.
*
* #param array $data
* #return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
}
/**
* Create a new user instance after a valid registration.
*
* #param array $data
* #return \App\User
*/
protected function create(array $data)
{
return User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
}
}
1) $this->middleware(...) called in a constructor of a controller is only adding what ever value is in ... to an array named middleware on the controller. That is it at that point. Nothing is "ran" at that point. You can see this functionality in the Controller your Controllers extend from, Illuminate\Routing\Controller.
Later when the Router needs to dispatch the Request it will build a middleware stack to pass the request through by gathering the middleware which includes asking the instance of the controller for its defined middleware via getMiddleware.
public function getMiddleware()
{
return $this->middleware;
}
2) The trait is including functionality and variables into your controller. There are routes pointing to some of these methods on the controller.
You could take all that code and paste it into your controller and it would be the same thing basically.
If you need to change any functionality of that controller or customize it you will end up redefining some of those methods or adjusting the variables.

Laravel : Overwrite Socialite Provider to add new fields

I want to extend/overwrite my LinkedInProvider.php (in vendor\laravel\socialite\src\Two) to add new fields in the Linkedin Request.
I've create a new LinkedInProvider.php (in app\Providers) with the following code :
namespace App\Providers;
use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use Laravel\Socialite\Two\AbstractProvider;
use Laravel\Socialite\Two\ProviderInterface;
use Laravel\Socialite\Two\User;
class LinkedInProvider extends AbstractProvider implements ProviderInterface
{
/**
* The scopes being requested.
*
* #var array
*/
protected $scopes = ['r_basicprofile', 'r_emailaddress'];
/**
* The separating character for the requested scopes.
*
* #var string
*/
protected $scopeSeparator = ' ';
/**
* The fields that are included in the profile.
*
* #var array
*/
protected $fields = [
'id', 'first-name', 'last-name', 'formatted-name',
'email-address', 'headline', 'location', 'industry', 'positions',
'public-profile-url', 'picture-url', 'picture-urls::(original)',
];
/**
* {#inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state);
}
/**
* {#inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://www.linkedin.com/oauth/v2/accessToken';
}
/**
* Get the POST fields for the token request.
*
* #param string $code
* #return array
*/
protected function getTokenFields($code)
{
return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
}
/**
* {#inheritdoc}
*/
protected function getUserByToken($token)
{
$fields = implode(',', $this->fields);
$url = 'https://api.linkedin.com/v1/people/~:('.$fields.')';
$response = $this->getHttpClient()->get($url, [
'headers' => [
'x-li-format' => 'json',
'Authorization' => 'Bearer '.$token,
],
]);
return json_decode($response->getBody(), true);
}
/**
* {#inheritdoc}
*/
protected function mapUserToObject(array $user)
{
return (new User)->setRaw($user)->map([
'id' => $user['id'], 'nickname' => null, 'name' => Arr::get($user, 'formattedName'),
'email' => Arr::get($user, 'emailAddress'), 'avatar' => Arr::get($user, 'pictureUrl'),
'avatar_original' => Arr::get($user, 'pictureUrls.values.0'),
]);
}
/**
* Set the user fields to request from LinkedIn.
*
* #param array $fields
* #return $this
*/
public function fields(array $fields)
{
$this->fields = $fields;
return $this;
}
}
But now, I've got this error :
Type error: Argument 1 passed to Laravel\Socialite\Two\AbstractProvider::__construct() must be an instance of Illuminate\Http\Request, instance of Illuminate\Foundation\Application given, called in G:\laragon\www\localhost\vendor\laravel\framework\src\Illuminate\Foundation\ProviderRepository.php on line 201
I know I can install Socialite Manager, but I just want to overwrite the fields list to add new field (like position and industry)
You shouldn't have to overwrite/extend the whole class. In the Laravel\Socialite\Two\User object that is being created, there is a $user property, which contains the raw information the provider sent back.
When making the request, you can set the fields you want LinkedIn to return in your controller method:
public function redirectToProvider()
{
$fields = [
'id', 'first-name', 'last-name', 'formatted-name',
'email-address', 'headline', 'location', 'industry',
'public-profile-url', 'picture-url', 'picture-urls:(original)',
'positions', 'summary' // <-- additional fields here
];
return Socialite::driver('linkedin')->fields($fields)->redirect();
}
You can see two additional fields being requested, positions and summary, which aren't included by default.
Happy hacking!

Display list and details using separate controllers in Laravel

I'm using the following two routes in my app, but they are failing. (only one works when I switch the positions). Notice that I'm using two separate controllers. What's the correct way of specifying routes for a scenario like this?
Route::controller('schedulers', 'SchedulersController', [
'getIndex' => 'schedulers.index',
'getUpdate' => 'schedulers.edit'
]);
Route::controller('schedulers/{schedulerId}', 'ReportsController', [
'getIndex' => 'schedulers.reports',
]);
You can add route prefix
Example :
Route::group(['prefix' => 'schedulers'], function() {
Route::controller('/{schedulerId}', 'ReportersController', [
'getIndex' => 'schedulers.reports',
]);
Route::controller('/', 'SchedulersController', [
'getIndex' => 'schedulers.index',
'getUpdate' => 'schedulers.edit'
]);
});
Try to user resource (routes.php):
Route::resource('schedulers', SchedulersController::class);
After this create SchedulersController with two methods: show, index:
<?php
namespace App\Http\Controllers;
class SchedulersController extends Controller
{
/**
* Display a listing of the resource.
*
* #return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
}
schedulers => SchedulersController::index
schedulers/1 => SchedulersController::show

Resources