Pass id parameter to resource in Laravel - php

I have the following method in my Laravel controller:
public function specialOffers($id) {
return \App\Http\Resources\SpecialOfferResource::collection(Offers::all());
}
I need some special manipulations, so I've created this SpecialOfferResource resource. The resource code is:
class SpecialOfferResource extends Resource {
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request) {
//here I need the $id passed to the controller's method,
//but I only have $request
return [
//my request fields, everything ok
];
}
}
How can I pass $id from the controller's method to this resource? I know I can pass through the request as a field, but is it possible this other way?

The resource collection is just a wrapper that formats, or maps, the collection you pass to it.
The collection you are passing is Offers::all(), which would include all Offers models.
You'd likely want to use the query builder to narrow down the collection you are passing:
public function specialOffers($id) {
$results = Offers::where('column', $id)->get();
return \App\Http\Resources\SpecialOfferResource::collection($results);
}

I'm not sure whether this is acceptable or not, but in some case i do need some parameter passed from controller to use inside toArray resource method and this is what i did.
Create resource class that extend Illuminate\Http\Resources\Json\ResourceCollection.
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\ResourceCollection;
class TestResource extends ResourceCollection
{
private $id;
public function __construct($id, $collection)
{
parent::__construct($collection);
$this->id = $id;
}
public function toArray($request)
{
return [
'data' => $this->collection,
'id' => $this->id
];
}
}
And from controller you can call like this:
<?php
namespace App\Http\Controllers;
use App\Http\Resources\TestResource;
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\ResourceCollection;
class TestController extends Controller
{
public function index()
{
$id = 30;
$collection = collect([['name' => 'Norli'], ['name' => 'Hazmey']]);
return new TestResource($id, $collection);
}
}

Related

Laravel Trait with constructor works in controller but not in model

I have a StripeClient service provider which needs a key to instantiate:-
namespace App\Providers;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
use Stripe\StripeClient;
class StripeServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->singleton(StripeClient::class, function ($app) {
return new StripeClient(config('services.stripe.secret'));
});
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides()
{
return [StripeClient::class];
}
Then a trait with a bunch of api call functions like this:-
trait StripeClientTrait
{
protected $stripe;
function __construct(StripeClient $stripeClient)
{
$this->stripe = $stripeClient;
}
/**
* #param User $user
*
* #return \Stripe\Customer
* #throws \Stripe\Exception\ApiErrorException
*/
function createCustomer(User $user)
{
return $this->stripe->customers->create([ 'name' => $user->fullname,
'email' => $user->email
]);
}
...
The trait works in a controller perfectly as expected:-
class SubscriptionContoller extends Controller
{
use StripeClientTrait;
public function checkout()
{
try {
$customer = $this->createCustomer(Auth::user());
if($checkoutSession = $this->createCheckoutSession($customer)) {
return redirect($checkoutSession->url);
}
} catch (ApiErrorException $ex){
Log::error($ex->getMessage());
return back()->with(['error'=>$ex->getMessage()]);
}
return back();
}
...
But I now need to use the trait in a model to provide access to some api functions.
class Company extends Tenant
{
use HasFactory, StripeClientTrait;
but adding the trait causes:-
Too few arguments to function App\Models\Company::__construct(), 0 passed in /home/vagrant/code/profiler/vendor/spatie/laravel-multitenancy/src/Models/Concerns/UsesTenantModel.php on line 13 and exactly 1 expected
Can anyone tell me how to implement the trait without using the constructor? I just need some static function helpers to lookup stuff on the API.
Thanks for any guidance :-)
having persevered I've found this way to use the service container in a model:-
public function getPrices()
{
$stripe = app(StripeClient::class);
return $stripe->prices->all(['active'=>true]);
}
But would still like to understand how to use the trait in the model, if anyone could explain I'd be grateful

Laravel Eloquent Model Dependency Injection with preformated Id to find()

Normally we can simplify finding User by id logic in controller by injecting the User Class in parameter. Like this:
class UserController extends Controller
{
public function show(User $id)
{
return $user;
}
}
But now I must treat the Id to find like this:
<?php
namespace App\Http\Controllers;
class UserController extends Controller
{
public function show(User $id)
{
$preformattedId = '98'.$id;
$user = User::find($preformattedId );
return $user;
}
}
My basic question is: how I can achieved that same trick to my preformatted id in below code like the above code?
Note: I have to use the Id this way because i work with legacy database that actually adding that '98' prefix in every Id, despite that we only use characters after that prefix.
You can use Inversion of Control by using explicit binding on your router.
In your RouteServiceProvider
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
parent::boot();
Route::bind('user', function ($value) {
return User::find('98'.$value);
});
}
Or in your User model
/**
* Retrieve the model for a bound value.
*
* #param mixed $value
* #param string|null $field
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value, $field = null)
{
return $this->find('98'.$value);
}
https://laravel.com/docs/7.x/routing#explicit-binding
You can share your route file ?
But if your file is
Route::get('user/{id}', 'UserController#show');
When you use
class UserController extends Controller
{
public function show(User $id)
{
// you don't need use find function, it is make automatic by laravel
$user = $id;
return $user;
}
}
if you want to get id, just remove User type inside show parameter
class UserController extends Controller
{
public function show($id)
{
$preformattedId = '98'.$id;
$user = User::find($preformattedId );
return $user;
}
}

Is there any helper function that assign form data into model in laravel?

I know that there is a resource functionality in laravel and as far as I know resource is something like json to model and its reverse..
So when I process form data, currently, use following custom helper method..
public function assignFormdata(Request $request, $model, $map = [])
{
foreach($map as $input=>$field) {
// $field is model's param. $input is form data key.
$model->$field = $request->input($input) ?? $field;
}
return $model;
}
.. Is this method already exist in laravel? or something similar..?
There is no "standard" way in Laravel, that I am aware of, that will accomplish what you have above, where you assign a default value to an input if it is missing, and control what attributes are being set using the map.
The closest thing to what you are looking for is Mass Assignment, I believe.
There are many different methods and patterns to handle these types of requests, and your approach seems fine to me. I personally use Form Requests + DTO because the code documents itself quite well. As an example:
Controller:
class UsersController extends Controller
{
...
public function store(CreateUserRequest $request)
{
$user = User::create($request->toCommand());
// Return response however you like
}
...
}
FormRequest
class CreateUserRequest extends FormRequest
{
...
public function rules()
{
// Validate all the data here
}
...
public function toCommand() : CreateUserCommand
{
return new CreateUserCommand([
'name' => $this->input('name'),
'birthdate' => Carbon::parse($this->input('birthdate')),
'role' => $this->input('role'),
'gender' => $this->input('gender'),
...
]);
}
}
Command DTO
class CreateUserCommand extends DataTransferObject
{
/** #var string */
public $name;
/** #var \Carbon\Carbon */
public $birthdate;
/** #var string */
public $role = 'employee'; // Sets default to employee
/** #var null|string */
public $gender; // Not required
}
class User extends Model
{
...
protected $fillable = [
'name',
'birthdate',
'role',
'gender',
];
...
public static function create(CreateUserCommand $command)
{
// Whatever logic you need to create a user
return parent::create($command->toArray());
}
}
That is a fairly "Laravel way" of doing things, and the code itself conveys a lot of information to anyone else (and you later :D) who needs to use it.

How to pass custom request to Api Resource

I have custom Request class:
class ApiRequest extends FormRequest
{
...
public function locale()
{
$lang = $this->header('Accept-Language');
return $lang ? $lang : 'uz';
}
}
and API recourse:
class SomeResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
...
];
}
}
but I can't use ApiRequest's method locale() in SomeResource. Because toArray() accepts only \Illuminate\Http\Request.
Is there any idea that I pass my class into this? Or a better solution from you.
The request instance bound in the container is injected into the JSON resource class when the response is created.
One method to achieve what you want would be to create your custom request that extends the standard request class and then bind that in the container as the request instance to be used for the remainder of the request. This could be done in a middleware assigned specifically to the API group or just in specific controllers.
This will then be injected into the toArray() method in your JSON resource class when the response is built and your custom methods will be available to use.
Custom Request Class
class ApiRequest extends Request
{
...
public function locale()
{
$lang = $this->header('Accept-Language');
return $lang ? $lang : 'uz';
}
}
API Middleware
class ApiMiddleware
{
...
public function handle($request, $next)
{
$request = app()->instance('request', ApiRequest::createFrom($request));
return $next($request);
}
}
JSON Resource
class SomeResource extends JsonResource
{
public function toArray($request)
{
// $request is now instanceof ApiRequest
return [
'id' => $this->id,
'locale' => $request->locale()
];
}
}
API Resources should receive model instances not Requests itself. This is an example:
ApiRequest.php
ApiRequest extends Request {
public function authorize() { /** your logic */ }
public function rules() { /** your logic */ }
}
MyCoolController.php
MyCoolController extends Controller {
public function myFunction(ApiRequest $request)
{
$lang = $request->header('Accept-Language') ?? 'uz';
\App::setLocale($lang); // <--
$model = MyModel::find($request->get('id'));
return new SomeResource($model);
}
}
I created a base Resource class, that all my other resource classes extend it with my custom method:
class BaseResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
protected function locale($request)
{
$lang = $request->header('Accept-Language');
return $lang ? $lang : 'uz';
}
}
and I use it in child class:
class SomeResource extends BaseResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
$lang = $this->locale($request);
return [
'id' => $this->id,
'hash' => $this->hash,
'name' => $this->translation('name', $this->locale($request)),
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
}

Laravel: Get class name in policy

I am writing a general policy which which will apply on multiple Models. How can I retrieve the class name of the class which needs to be authorized?
Policies:
protected $policies = [
'App\User' => 'App\Policies\ModelPolicy',
'App\Customer' => 'App\Policies\ModelPolicy',
];
The ModelPolicy:
class ModelPolicy
{
use HandlesAuthorization;
/**
* Create a new policy instance.
*
* #return void
*/
public function __construct()
{
}
public function index(User $user){
// how can I retrieve the class name, like User or Customer?
return true;
}
}
This is for example my customer controller. So in the policy I want to retrieve something like: App\Customer.
class CustomerController extends Controller
{
public function index(){
$this->authorize('index', Customer::class);
echo "test";
}
}
You'll need custom gates.
In your controller:
$this->authorize('model-index', Appointment::first());
In AuthServiceProvider:
Gate::define('model-index', function ($user, $model) {
var_dump(get_class($model));
die();
});
This way you can take the parameters you need for your authorization methods.
Please see
https://laravel.com/docs/5.5/authorization#gates
Laravel Policies - How to Pass Multiple Arguments to function

Categories