How can I add function name as route in Laravel 5.7? - php

I have a controller which returns enums for respective fields. e.g.
// Expected route - /api/getFamilyTypes - only GET method is allowed
public function getFamilyTypes()
{
return [
'Nuclear Family',
'Joint Family'
];
}
I've around 20 functions like this. How can I add this without manually adding an entry per function in routes file?
Thanks in advance.

In your routes file, add something like this,
Route::get('/something/{func}', 'SomeController#functionRoute');
Where something is whatever path you're wanting to use and SomeController is the controller with the 20 functions you're using and functionRoute is the action that we're about to make.
Then in your controller, make a function like this,
public function functionRoute($func)
{
return $this->$func();
}
This will make it so that whenever someone browses to /something/* on your website, it'll execute the function name at the end. So if you navigate to /something/getFamilyTypes it'll run your getFamilyTypes function.
This isn't particularly secure. If you do this, the user will be able to run any of the controller's methods. You could set up a blacklist like this.
public function functionRoute($func)
{
$blacklist = [
'secret',
'stuff',
];
return in_array($func, $blacklist) ? redirect('/') : $this->$func();
}
Or you could set up a whitelist like this,
public function functionRoute($func)
{
$whitelist = [
'getFamilyTypes',
'otherUserFriendlyStuff',
];
return in_array($func, $whitelist) ? $this->$func() : redirect('/');
}

If the responses are always from hard-coded arrays (as opposed to being from a database) then one way might be to have a variable in your route:
Route::get('/api/enum/{field}', 'EnumController#getField');
And then in your controller method, use the variable to get the correct data from a keyed array:
public function getField($field)
{
$fields = [
'family' => [
'Nuclear Family',
'Joint Family'
],
// ...
];
return $fields[$field];
}
If you want to continue using different methods for every field then Michael's answer is the easiest option, with one caveat. Allowing users to call any method by name on your controller is a security risk. To protect yourself, you should validate the method name against a whitelist.

Related

Laravel 9 pass variable from route to controller

My problem:
I am currently trying to refactor some of my controllers. Doing so I found these two routes:
Route::get('/events', [EventsController::class, 'eventsList'])->name('event-list');
Route::get('/courses', [EventsController::class, 'allCoursesList'])->name('all-events');
they show different filter options in the frontend.
What I want to do:
Example Code:
Route::get('/courses', [
'all' => 1,
EventsController::class, 'courseList'
])->name('all-events');
so having the ability to pass a variable, in this case all to my controller. EventsController So I can check with if in my controller and handle these routes differently with only one function instead of two.
With the current solutions on StackOverflow, users are using:
'uses'=>'myController#index'
now if I try it like this:
Route::get('/courses', [
'all' => 1,
'uses' => 'EventsController#CourseList'
])->name('all-events');
I get the following error:
Target class [EventsController] does not exist.
Question:
What is the current, correct way, to pass a variable to a controller, from a route. In Laravel 9 and 10.
You can pass arbitrary data to the route as a parameter using the defaults method of Route:
Route::get('courses', [EventsController::class, 'list'])
->name('all-events')
->defaults('all', 1);
Route::get('events', [EventsController::class, 'list'])
->name('event-list');
public function list(Request $request, $all = 0)
{
...
}
There are also other ways of using the Route to pass data.
Laravel versions 8 and above do not automatically apply namespace prefixes. This means that when passing the class name as a string, you need to use the fully qualified class name (FQCN).
For example:
Route::get('/courses', [
'all' => 1,
'uses' => '\App\Http\Controllers\EventsController#CourseList'
])->name('all-events');
If it makes sense for your use case, you could also use URL parameters. For example, if your Course models belong to a Category model, you might do something like this:
Route::get('/courses/{category}', [EventsController::class, 'allCourseList');
Then in your countroller, you define the allCoursesList function like so:
public function allCoursesList(Category $category)
{
// ... do something with $category which is an instance of the Category model.
}
You can use Route Parameters to pass variable from route to controller.
// web.php
Route::get('/events/{all}', [EventsController::class, 'eventsList'])->name('event-list');
then in your controller you can access the variables
public function eventsList(Request $request,$all){
if($all==1){
//your logic for handling the condition when $all =1
}
// the remaining condition
}
if you have multiple parameters you can pass them like so, you can use ? for optional parameter.
// web.php
Route::get('/courses/{param_one}/{param_two?}', [EventsController::class, 'allCoursesList'])->name('all-events');
then in your controller you can access the variables
public function allCoursesList(Request $request,$paramOne,$paramTwo){
return $paramOne.' '.$paramTwo;
}
to access the query parameter
// web.php
Route::get('/evets', [EventsController::class, 'allCoursesList'])->name('all-events');
if the route were /events?timeframe=0&category=1 you can access the query parameter like so
public function allCoursesList(Request $request,$paramOne,$paramTwo){
$timeframe= $request->query('timeframe');
// do this for all the query parameters
}

Laravel route redirecting with data

I have a basic route that looks like this:
Route::prefix('/group')->group(function () {
// Some routes here
Route::prefix('/{uuid}')->group(function () {
// Some routes here
Route::get('/user/{id}', 'Controller#preview')->name('view-user')->where('id', '[0-9]+');
}
}
The logic is that I want the id to be only numerical value. What I want to do now is, to declare a redirection to this, if the value is non-numerical. Let's say the input of id is fs. In that case I would want it to redirect to id with value 1.
I tried using Route:redirect, but could not make it work. It looks something like this:
Route::redirect('/group/{uuid}/user/{id}', '/group/{uuid}/user/1')->where('id', '[^0-9]+');
I would prefer to put the redirect inside the groups, but it can be outside if this is the only way. Any help will be greatly appreciated.
What happens is, that I get a 404 error if I have the route redirect declared.
EDIT: I want to do it in the routes/web.php file. I know how to do it in the controller, but it is not what I need in the current case.
Closures are not an option either, because that would prevent routes caching.
Following up on the comment
You can create a Route in routes/web.php file that catches non-digit ids and redirect this to 'view-user' with id=1
It would look something like this
Route::get('/group/{uuid}/user/{id}', function ($uuid, $id) {
return redirect('view-user', ['uuid' => $uuid, 'id' => 1]);
})->where('id', '[^0-9]+');
// and then below have your normal route
Route::get('/group/{uuid}/user/{id}', 'Controller#preview')->name('view-user')->where('id', '[0-9]+');
Update
Following you comment that you do not want to use closures.
Change the "bad input route" to
Route::get('/group/{uuid}/user/{id}', 'Controller#redirectBadInput')->where('id', '[^0-9]+');
and then add the method in class Controller:
public function redirectBadInput ($uuid, $id) {
return redirect('view-user', ['uuid' => $uuid, 'id' => 1]);
}
You can see more in this SO thread about redirects and caching.
You declared it inverted.
In Laravel you can redirect passing parameters in this way:
You can pass the name instead of the url and simply pass variables.
Redirect::route('view-user', [$uuid, $id])
I think that you can do it inside of the controller of the router, with a logic like this:
class Controller {
// previous code ..
public function preview($uuid, $id) {
if(! is_numeric($id))
return redirect("/my-url/1");
// run the code below if $id is a numeric value..
// if not, return to some url with the id = 1
}
}
I think that there is no way to override the 'where' function of laravel, but I guess that have something like that in Routing Bindings:
Alternatively, you may override the resolveRouteBinding method on your Eloquent model. This method will receive the value of the URI segment and should return the instance of the class that should be injected into the route:
/**
* Retrieve the model for a bound value.
*
* #param mixed $value
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value)
{
return $this->where('name', $value)->first() ?? abort(404);
}
But it's require that you manage consise model's values instead of ids of whatever you want.
assign route name in route as like.
return Redirect::route('view-user', ['uuid'=>$uuid,'id'=>$id]);
As you want in web.php file then.
Route::get('/group/{uuid}/user/{id}', function($uuid, $id){
echo $uuid;
echo $id;
})->name('view-user')->where('id', '[0-9]+');

Laravel validate data coming from my own application and database

In a function in my controller I call this:
$item = Item::where('i_id', $Id)->where('type', 1)->first();
$firebaseData = app('firebase')->getDatabase()->getReference('items/'.$Id)->getSnapshot()->getValue();
Then I do a lot of "validation" between the data from the two sources above like:
if ($item->time_expires < strtotime(Carbon::now()) && $firebaseData['active'] == 1) {
return response()->json(['errors' => [trans('api.pleaserenew')]], 422);
}
And since this is not data coming from a user/request I cant use Laravels validate method
I dont want to keep this kind of logic inside my controller but where should I put it? Since part of my data is coming from Firebase I cant setup a Eloquent model to handle it either.
I recommend to receive the firebase data via a method within the model:
public function getFirebaseData()
{
app('firebase')->getDatabase()->getReference('items'/ . $this->i_id)->getSnapshot()->getValue();
}
That way you have the logic to receive the data decoupled from controller logic and moved it to where it makes more sense. Adding a validation method could work similarily within the model then:
public function validateData()
{
$combined = array_merge($this->toArray(), $this->getFirebaseData());
Validator::make($combined, [
'active' => 'in:1',
'time_expires' => 'before:' . Carbon::now(),
]);
}
The caveat with this is that the validation error will be thrown within the model instead of the controller, but that shouldn't really be an issue I don't think.
For any data you have in your application you can use Laravel validation.
You can merge your data and process it using Validator facade like this:
$combinedData = array_merge($item->toArray(), $firebaseData);
Validator::make($combinedData, [
'active' => 'required|in:1',
'time_expires' => 'required|before:' . Carbon::now()->toDateTimeString()
], $customMessageArray);
I think the best place for this code is some kind of service class you will inject to controller or other service class using Laravel dependency injection.

laravel controller function parameters

I'm trying to call a function inside one of my controller from the action() helper function. I need to pass a paramter to my function.
Here is the funciton I'm trying to call :
public function my_function($clef = 'title')
{
$songs = Song::orderBy($clef)->get();
return View::make('my_view', compact('songs'));
}
Here is the way I call it :
Author
The function is always running with the default value, even if I put anything else in my call. From what I can see in my address bar, the paramter seems to be sent along with the call :
http://www.example.com/my_view?clef=author
From the little I know, it seems correct to me, but since it doesn't work, I must come to the evidence that it isn't. What would be the cleanest way to call my function with the right parameter?
The reason why it's not working is because query strings aren't passed as arguments to your controller method. Instead, you need to grab them from the request like this:
public function my_function(Request $request)
{
$songs = Song::orderBy($request->query('clef'))->get();
return View::make('my_view', compact('songs'));
}
Extra tidbit: Because Laravel uses magic methods, you can actually grab the query parameter by just doing $request->clef.
Laravel URL Parameters
I think assigning parameters need not be in key value pair. I got it to work without names.
If your route is like /post/{param} you can pass parameter as shown below. Your URL will replace as /post/100
URL::action('PostsController#show', ['100'])
For multiple parameters say /post/{param1}/attachment/{param2} parameter can be passed as shown below. Similarly your url will be replaced as /post/100/attachment/10
URL::action('PostsController#show', ['100', '10'])
Here show is a method in PostsController
In PostsController
public function show($param1 = false, $param2 = false)
{
$returnData = Post::where(['column1' => $param1, 'column2' => $param2 ])->get();
return View::make('posts.show', compact('returnData'));
}
In View
Read More
In Routes
Route::get('/post/{param1}/attachment/{param2}', [ 'as' => 'show', 'uses' => 'PostsController#show' ] );
URL Should be: http://www.example.com/post/100/attachment/10
Hope this is helpful.

laravel route with parameter not coming from url

I have multiple routes that look like this:
Route::get('pending-submit', 'CasesController#cases');
Route::get('submited', 'CasesController#cases');
Route::get('closed', 'CasesController#cases');
I have been looking around even in the router API documentation and I can't find a solution for my requirement other than creating multiple methods within the controller. The method does the exact same query except for adding a where clause to identify the different status between each case, what I was trying to do is have a method like this
public function cases($whereStatus = 0){
return Cases::where('status', $whereStatus)->get();
}
Instead of doing this:
public function pendingCases(){
return Cases::where('status', 0)->get();
}
public function submitedCases(){
return Cases::where('status', 1)->get();
}
public function closedCases(){
return Cases::where('status', 2)->get();
}
But I can figure a way to pass that parameter to the method from the route so I now have to create a method for each route which does not seem necessary to me. I understand I could just generate urls with the get parameter in it but I wanted to make that cleaner, is there a way for me to add that parameter without having it in the url?
By the way, I also tried something like this which did not wok:
Route::get(
'pending-submit',
array(
'uses' => 'CasesController#cases',
'params' => array(
'filter' => 0
)
)
);
EDIT:
I understand I can make URLs like https://someurl.com/cases?status=0 and can also have URLs like https://someurl.com/cases which require a different method per route however what I want is have URLs like https://someurl.com/cases and have a single method where the parameter is passed by the router instead of me getting it from the request so I can do it like this:
public function myMethod($param){
/*
* Here I access the $param without calling Request::input('someparam');
* or without Request::path() where then I have to check what path is it
*/
echo $param; /* this should already have the param from the route */
}
EDIT:
#AndyNoelker what I have is 3 different values either 0, 1 or 2
I want to have something like this
Route::get(
'cases',
array(
'uses' => 'CasesController#cases',
'status' => 0 /* this is what I need */
)
);
If not possible from the routes.php it is fine, I just want to know, all other methods you are giving me is not what I want or asking for since I already know how to do those.
You are going to have to pass the desired status in through the URL - otherwise the route will have no way of knowing which status you desire. You can either do it through URL query parameters or as a fully-fledged route parameter. I would personally suggest using a query parameter in this case, but I'll show you both.
Using Query parameters
URL
example.com/cases?status=1
Routes
Route::get('cases', CasesController#cases);
CasesController
public method cases(Request $request)
{
$input = $request->all();
$status = $input['status'];
return Cases::where('status',$status)->get();
}
Using Route parameters
URL
example.com/cases/1
Routes
Route::get('cases/{id}', CasesController#cases);
CasesController
public method cases($id)
{
return Cases::where('status',$id)->get();
}
Of course if you'd prefer that they use a slug or something other than a unique id in the route, then you'd have to adjust for that in your query, but this should give you the right idea.
I think you're looking for route parameters:
Route::get("/cases/{case}", "CasesController#cases");
This will match any of the following:
some_url/cases/pending-submit
some_url/cases/submited
some_url/cases/closed
...
Then, your function public function cases in CasesController would look like this:
public function cases($case){
if($case == "pending-submit"){
// Do Something
} else if($case == "submited") {
// Do Something Else
}
// OR
return Cases::where('status', $case)->get();
}
Etc etc. Look more into url parameters here: Documentation
Route::get('pending-submit', array('status' => 0, 'uses' => function(){
$CasesController = $app->make('CasesController');
return $CasesController->callAction('cases', $parameters = array());
}));
If I am understanding your question correctly, this should work.
Web Route
Route::get('pending-submit', [
'as' => 'pending',
'uses' => 'CasesController#cases',
'status-id' => '0'
]);
And, you can access the parameter passed with the route like below,
In Controller
$request->route()->getActions('status-id')
In View
{{ request()->route()->getAction('status-id') }}
Tested and perfectly works in L5.6

Categories