Foreach do action static function by "name" in laravel :x - php

Someone please help me enlighten in LARAVEL !!!
in LARAVEL controller i difine static function like this :
namespace App\Http\Controllers\MyAPI;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class MyAPIController extends Controller {
const acceptMethod = ['GET','POST','PUT','DELETE']
public function handler(Request $request) {
$acceptMethod = self::acceptMethod;
$ctrl = new PromotionController;
$method = $request->method()
// This is my question :((
if ($method == 'GET')
$ctrl::read($request);
if ($method == 'GET')
$ctrl::post($request);
$ctrl::put($request);
...
//I want to be like this :
foreach($acceptMethod as $method) {
// Not work
$ctrl::($method)($request);
}
}
public static function read(Request $request) {
return something;
}
public static function post(Request $request) {
return ...;
}
public static function put(Request $request) {
return ...;
}
public static function delete(Request $request) {
return ...;
}
}
And then i must use controll like :
if ($method == 'get')
$ctrl::read($request);
if ($method == 'post')
$ctrl::post($request);
$ctrl::put($request);
But i have a array :
and i want to be like this :
$acceptMethod = ['GET','POST','PUT','DELETE'];
foreach($acceptMethod as $functionName) {
// Not work
$ctrl::$functionName($request);
}
Is there any way to make this possible ??

Use {};
Please, try this inside the loop:
$fn = strtolower($functionName)
$ctrl::{$fn}($request);
You can call a attribute too..
$instance->{'attribute_name'};

Routes
The proper way to do it would be to define a RESTful resource for your object so you get all the routes RESTfully. In your routes/api.php
Route::resource('thing','MyAPIController');
That will magically route:
GET api/thing to index()
GET api/thing/create to create()
POST api/thing to store()
GET api/thing/{id} to show($id)
GET api/thing/{id}/edit to edit()
PATCH api/thing/{id} to update()
DELETE api/thing/{id} to destroy()
If you have multiple objects to REST, you'd just add a controller for each.
Whats wrong $ctrl::{$fn}($request)
Injection is always on the OWASP top 10 each year, and this opens up potential function injection. You can mitigate that risk by making sure you white list the method. But, I'd rather just use Laravel the way it was intended.

Related

Laravel create a route method GET with param in url

I created a route & contronller:
Route::group(['prefix' => 'test'], function () {
Route::get('product/{id}', ['uses' => 'ProductController#getProduct']);
});
ProductController:
class ProductController extends MyController {
public $_params = null;
public function __construct(Request $request) {
$this->request = $request;
$this->_params = $request->all();
$options = array();
parent::__construct($options);
}
public function getProduct() {
dd($this->_params);
}
}
I requested: http://localhost/test/product/123
But the id = 123 not exist in $this->_params
Request params are input data like POST data. To access route params you will need to make another class property like "$routeParams"
class ProductController extends MyController {
public $_params = null;
public $routeParams = null;
public function __construct(Request $request) {
$this->request = $request;
$this->_params = $request->all();
$this->routeParams = $request->route()->parameters();
$options = array();
parent::__construct($options);
}
public function getProduct() {
dd($this->routeParams);
}
}
I understand need to implement your logic on top of the Laravel, but I would suggest that you do that in some Services, Actions, Domain.... Maybe this can help: https://laravel-news.com/controller-refactor
You can try do it like basic controller from documentation
and make some custom service for complex stuff.
class ProductController extends Controller
{
public function getProduct($id)
{
$productService = new ProductService($id);
//....
}
}
If you want to get an array of the route parameters, you need to use $request->route()->parameters(), not $request->all()
$request->all() returns the query parameters for GET requests
$request->all() only get parameter from header, body and URL but can't get parameter from route product/{id}
You should replace func getProduct to param id
public function getProduct($id) { dd($id); }
İf you want all params in request use $request->all() method but you want only id in url $request->id

Laravel: set dependecy injection for Service like Controller

I have this code in my CompanyController
use App\Services\CompanyService;
public function store(CompanyService $companyService) {
$result = $companyService->store();
return response()->json($result);
}
And this code in my CompanyService
use stdClass;
use App\Company;
use Illuminate\Http\Request;
public function store(Request $request, Company $company) {
// this also not work
dd($request->all());
$data = new stdClass;
$data->status = 1;
$data->message = 'success';
return $data;
}
When i run this code, Laravel show error
Too few arguments to function App\Services\CompanyService::store() 0
passed but exactly 1 expected
I know that type hint dependecy injection issue, because it work in Controllers but not work in my CompanyService when i call store() without params
How can i fix this and make it work in my CompanyService ?
Edit:
You can use call() method of service container
In your case:
public function store()
{
$companyService = app(CompanyService::class);
$result = $companyService->call('store');
return response()->json($result);
}
Unfortunately it's not possible to method inject dependencies but you can inject your dependencies in your construct method, so in your case it would be like this:
use stdClass;
use App\Company;
use Illuminate\Http\Request;
class Foo
{
protected $request;
protected $company;
public function __construct(Request $request, Company $company)
{
$this->request = $request;
$this->company = $company;
}
public function store()
{
// this works
dd($this->request->all());
// Also injected
dd($this->company);
$data = new stdClass;
$data->status = 1;
$data->message = 'success';
return $data;
}
}
PS:
I believe laravel handles method injection in one of it's middlewares so as far as I know there is not possible way to perform method injection you can read about Service Container for more info

Laravel constructor and method injection

I am having an issue setting up an injection on both the constructor and the method in a controller.
What I need to achieve is to be able to set up a global controller variable without injecting the same on the controller method.
From below route;
Route::group(['prefix' => 'test/{five}'], function(){
Route::get('/index/{admin}', 'TestController#index');
});
I want the five to be received by the constructor while the admin to be available to the method.
Below is my controller;
class TestController extends Controller
{
private $five;
public function __construct(PrimaryFive $five, Request $request)
{
$this->five = $five;
}
public function index(Admin $admin, Request $request)
{
dd($request->segments(), $admin);
return 'We are here: ';
}
...
When I run the above, which I'm looking into using, I get an error on the index method:
Symfony\Component\Debug\Exception\FatalThrowableError thrown with message "Argument 1 passed to App\Http\Controllers\TestController::index() must be an instance of App\Models\Admin, string given"
Below works, but I don't need the PrimaryFive injection at the method.
class TestController extends Controller
{
private $five;
public function __construct(PrimaryFive $five, Request $request)
{
$this->five = $five;
}
public function index(PrimaryFive $five, Admin $admin, Request $request)
{
dd($request->segments(), $five, $admin);
return 'We are here: ';
}
...
Is there a way I can set the constructor injection with a model (which works) and set the method injection as well without having to inject the model set in the constructor?
One way you could do this is to use controller middleware:
public function __construct()
{
$this->middleware(function (Request $request, $next) {
$this->five = PrimaryFive::findOrFail($request->route('five'));
$request->route()->forgetParameter('five');
return $next($request);
});
}
The above is assuming that PrimaryFive is an Eloquent model.
This will mean that $this->five is set for the controller, however, since we're using forgetParameter() it will no longer be passed to your controller methods.
If you've specific used Route::model() or Route::bind() to resolve your five segment then you can retrieve the instance straight from $request->route('five') i.e.:
$this->five = $request->route('five');
The error is because of you cannot pass a model through the route. it should be somethiing like /index/abc or /index/123.
you can use your index function as below
public function index($admin,Request $request){}
This will surely help you.
Route::group(['prefix' => 'test/{five}'], function () {
Route::get('/index/{admin}', function ($five, $admin) {
$app = app();
$ctr = $app->make('\App\Http\Controllers\TestController');
return $ctr->callAction("index", [$admin]);
});
});
Another way to call controller from the route. You can control what do you want to pass from route to controller

is it better to create separate request class for each new method in controller, or edit existing request class in laravel or any better idea?

Is it better to create separate request class for each new method in controller or edit existing request class in laravel or any better idea ?
example
class fooBarController {
public function a(fooBarARequest $r) {
}
public function b(fooBarBrequest $r) {
}
public function c(fooBarCDRequest $r) {
}
public function d(fooBarCDRequest $r) {
}
}
Using extra request classes allows you to define validation rules which your request is checked against before it reaches your controller. You can also handle authorization in the request class. An example would be:
class UpdateAccountEmail extends FormRequest
{
public function authorize()
{
return true; // authorization is handled on route/middleware level
}
public function rules()
{
return [
'new_email' => 'required|email|confirmed',
'new_email_confirmation' => 'required',
];
}
}
So, to sum it up: it does not make sense to use a custom request class for requests which do not have payload that needs to be validated. This means, for a normal GET request we most likely (of course there are exceptions) want to use the normal Request class provided by laravel. A controller like this would be quite normal:
class AccountController
{
public function show(Request $request)
{
return view('account.show', ['user' => $request->user()]);
}
public function edit()
{
return view('account.edit', ['user' => \Auth::user()]);
}
public function updateEmail(UpdateAccountEmail $request)
{
$user = $request->user();
$user->email = $request->input('new_email');
$user->save();
return redirect()->route('account.index');
}
public function logins(Request $request)
{
$logins = $request->user()->logins()
->when($request->get('filter_from'), function ($query, $from) {
$query->where('created_at', '>=', $from);
})
->when($request->get('filter_until'), function ($query, $until) {
$query->where('created_at', '<=', $until);
})
->get();
return view('account.logins', ['logins' => $logins]);
}
}
As you can see, for the GET request that is handled by logins(Request $request), we do not use a custom request class because we don't need to validate anything (well, we could validate the filter parameters, but for simplicity we don't).
The example above also shows different methods of retrieving the current user. Even for that you don't need a request at all.
This is no actual production code, just something off the top of my head...

Laravel 5 requests: Authorizing and then parsing object to controller

I am not sure if I am using this correctly, but I am utilising the requests in Laravel 5, to check if the user is logged in and if he is the owner of an object. To do this I need to get the actual object in the request class, but then I need to get the same object in the controller?
So instead of fetching it twice, I thought, why not just set the object as a variable on the request class, making it accessible to the controller?
It works, but I feel dirty? Is there a more appropriate way to handle this?
Ex.
Request Class
class DeleteCommentRequest extends Request {
var $comment = null;
public function authorize() {
$this->comment = comment::find(Input::get('comment_id'));
$user = Auth::user();
if($this->comment->user == $user)
return true;
return false;
}
public function rules() {
return [
'comment_id' => 'required|exists:recipes_comments,id'
];
}
}
Ex. Controller:
public function postDeleteComment(DeleteCommentRequest $request) {
$comment = $request->comment;
$comment->delete();
return $comment;
}
So what is my question? How do I best handle having to use the object twice when using the new Laravel 5 requests? Am I possibly overextending the functionality of the application? Is it ok to store the object in the application class so I can reach it later in my controller?
I would require ownership on the query itself and then check if the collection is empty.
class DeleteCommentRequest extends Request {
var $comment = null;
public function authorize() {
$this->comment = comment::where('id',Input::get('comment_id'))->where('user_id',Auth::id())->first();
if($this->comment->is_empty())
return false;
return true;
}
public function rules() {
return [
'comment_id' => 'required|exists:recipes_comments,id'
];
}
}
Since you're wanting to use the Model in two different places, but only query it once I would recommenced you use route-model binding.
In your RouteServiceProvider class (or any relevant provider) you'll want to bind the comment query from inside the boot method. The first parameter of bind() will be value that matches the wildcard in your route.
public function boot()
{
app()->router->bind( 'comment_id', function ($comment_id) {
return comment::where('id',$comment_id)->where('user_id',Auth::id())->first();
} );
}
Once that's set up you can access the Model from your DeleteCommentRequest like so
$this->comment_id
Note: The variable is Comment_id because that's what matches your route, but it will contain the actual model.
From your controller you just inject it like so
public function postDeleteComment(Comment $comment, DeleteCommentRequest $request) {
$comment->delete();
return $comment;
}

Categories