Laravel throwing "Function () does not exist" with method ->controller() on routes - php

I have this piece of routing here:
<?php
use App\Http\Controllers\SurveyController;
Route::middleware(['auth:api'])->controller(SurveyController::class)->group(function ($route) {
$route->prefix('survey')->group(function ($route) {
$route->post('show', ['show'])->name('survey_show');
$route->post('answer', ['answer'])->name('survey_answer');
});
$route->prefix('member/survey')->group(function ($route) {
$route->post('list', ['list'])->name('member_survey_list');
});
});
The problem is that the route is unable to "find" the controller, I've tried many different approaches to this issue, I found a lot of info about this issue, but none of them could help me solve the problem, and either I get a "Function () does not exist" or a "Target class [App\\Http\\Controllers\\App\\Http\\Controllers\\SurveyController] does not exist.". I didn't want to declare the controller on each route as $route->request('path', [Controller::class, 'function']) since it will have an impact in future maintenance as the routes number grows bigger.
Am I using the method Route::controller()? Should I just write the controller on each route?
I'm using Laravel 8.83.5 and php 8.1.13.
UPDATED
my SurveyController
<?php
namespace App\Http\Controllers;
use App\Http\Resources\SurveyResource;
use App\Services\SurveyService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Exception;
class SurveyController extends Controller
{
private SurveyService $service;
public function __construct(SurveyService $surveyService)
{
$this->service = $surveyService;
}
/**
* php doc here
*/
public function list(Request $request): array
{
$data = $request->only(['keys']);
$validator = Validator::make(
$data,
[
'keys' => 'string'
],
$this->customMessages
);
if ($validator->fails()) {
throw new Exception($validator->errors(), 1);
}
return SurveyResource::method(
$this->service->method(
$data['keys']
)
);
}
/**
* php doc here
*/
public function show(Request $request): array
{
$data = $request->only(['keys']);
$validator = Validator::make(
$data,
[
'keys' => 'string'
],
$this->customMessages
);
if ($validator->fails()) {
throw new Exception($validator->errors(), 2);
}
return SurveyResource::show(
$this->service->show(
$data['keys']
)
);
}
/**
* php doc here
*/
public function answer(Request $request): array
{
$data = $request->only(['keys']);
$validator = Validator::make(
$data,
[
'keys' => 'string'
],
$this->customMessages
);
if ($validator->fails()) {
throw new Exception($validator->errors(), 3);
}
return SurveyResource::answer(
$this->service->answer(
$data['keys']
)
);
}
}
on routes, I've tried calling the route with the method controller() as the first, this way I get a "Function () does not exist" error, the same error pops up when I use it just before the group() method
Route::controller(SurveyController::class)->middleware(['auth:api'])->group(function ($route) {
$route->prefix('member/survey')->group(function ($route) {
$route->post('list', ['list'])->name('member_survey_list');
});
});
also tried calling the route with SurveyController's method without an array, this way I get an "Target class [App\\Http\\Controllers\\App\\Http\\Controllers\\SurveyController] does not exist." error
Route::controller(SurveyController::class)->middleware(['auth:api'])->group(function ($route) {
$route->prefix('member/survey')->group(function ($route) {
$route->post('list', 'list')->name('member_survey_list');
});
});
Also saw some old threads saying that using an array inside the group() method with a namespace key and the namespace value could help, so I tried, but got a "Array to string conversion" error
Route::middleware(['auth:api'])->group(['namespace' => 'App\Http\Controllers\SurveyController'],function ($route) {
$route->prefix('member/survey')->group(function ($route) {
$route->post('list', 'list')->name('member_survey_list');
});
});
SOLUTION
Following the solution given by #matiaslauriti, first you need to remove the declaration of the namespace in the route file (you can keep the declaration if you remove it from the RouteServiceProvider.php), then you can either call it as Route::controller(ControllerName::class) or literally as Route::controller('ControllerName'). Here's my working code:
<?php
Route::controller(SurveyController::class)->middleware(['auth:api'])->group(function ($route) {
$route->prefix('survey')->group(function ($route) {
$route->post('show', 'show')->name('survey_show');
$route->post('answer', 'answer')->name('survey_answer');
});
$route->prefix('member/survey')->group(function ($route) {
$route->post('list', 'list')->name('member_survey_list');
});
});

If you are getting App\\Http\\Controllers\\App\\Http\\Controllers\\SurveyController error, that means you only have to pass SurveyController literally:
Route::controller('SurveyController')->middleware(['auth:api'])->group(function ($route) {
$route->prefix('member/survey')->group(function ($route) {
$route->post('list', ['list'])->name('member_survey_list');
});
});
I would recommend to switch to the new routing system, delete $namespace like this file and from everywhere on that file.
Then, you can do controller(SurveyController::class)

Related

How can you create wildcard routes on Lumen?

Let's say I have a controller called TeamsController. Controller has following method, that returns all teams user has access to.
public function findAll(Request $request): JsonResponse
{
//...
}
Then I have bunch of other controllers with the same method. I would like to create a single route, that would work for all controllers, so I would not need to add a line for each controller every time I create a new controller.
I am unable to catch the controller name from URI. This is what I have tried.
$router->group(['middleware' => 'jwt.auth'], function () use ($router) {
// This works
//$router->get('teams', 'TeamsController#findAll');
// This just returns TeamsController#findAll string as a response
$router->get('{resource}', function ($resource) {
return ucfirst($resource) . 'Controller#findAll';
});
});
You return a string instead of calling a controller action:
I believe Laravel loads the controllers this way (not tested)
$router->group(['middleware' => 'jwt.auth'], function () use ($router) {
$router->get('{resource}', function ($resource) {
$app = app();
$controller = $app->make('\App\Http\Controllers\'. ucfirst($resource) . 'Controller');
return $controller->callAction('findAll', $parameters = array());
});
});
But again, I don't really think it's a good idea.

Customizing The "Not Found" Behavior of Laravel's Routing "Explicit Binding"

Here's the documentation: https://laravel.com/docs/5.2/routing#route-model-binding
The routes:
Route::group(['prefix' => 'u'], function () {
Route::post('create', ['as' => 'createUser', 'uses' => 'UserController#create']);
Route::get('{uuid}', ['as' => 'userDashboard', 'uses' => 'UserController#dashboard']);
});
The UserController.php:
public function dashboard(User $uuid)
{
return View::make('user.dashboard');
}
Whenever the User isn't found in the database it throws these two exceptions:
2/2
NotFoundHttpException in Handler.php line 103:
No query results for model [App\User].
1/2
ModelNotFoundException in Builder.php line 303:
No query results for model [App\User].
How do I customize the error? I want to redirect to the createUser route. The documentation instructs to pass a Closure as a third argument but I don't know how to do that with my current code.
EDIT 1
This is what I've tried so far without success:
Route::model('{uuid}', ['as' => 'userDashboard', 'uses' => 'UserController#dashboard'], function () {
App::abort(403, 'Test.');
});
Route::get('{uuid}', ['as' => 'userDashboard', 'uses' => 'UserController#dashboard'], function () {
App::abort(403, 'Test.');
});
This is actually very simple. As none of the answers really give a definite answer I am answering it myself.
In the file RouteServiceController.php's boot function add the following:
$router->model('advertiser', 'App\Advertiser', function () {
throw new AdvertiserNotFoundException;
});
Then create a new empty class in App\Exceptions called (in this case) AdvertiserNotFoundException.php:
<?php
namespace App\Exceptions;
use Exception;
class AdvertiserNotFoundException extends Exception
{
}
The last thing to do is to catch the exception in the Handler.php's render function (App\Exception) like so:
public function render($request, Exception $e)
{
switch ($e)
{
case ($e instanceof AdvertiserNotFoundException):
//Implement your behavior here (redirect, etc...)
}
return parent::render($request, $e);
}
That's it! :)
for a similar case i did,
I took the parent Illuminate\Foundation\Exceptions\Handler isHttpException function and copied it to app/Exceptions/Handler.php and changed it's name to my isUserNotFoundException.
protected function isUserNotFoundException(Exception $e)
{
return $e instanceof UserNotFoundException;
}
and than in the render function add
if ($this->isUserNotFoundException($e))
return redirect('path')->with('error',"Your error message goes here");
Following code must be placed in your RouteServiceProvider::boot method
$router->model('uuid', 'App\User', function () {
throw new UserNotFoundException;
});
and make sure to include this in your view file
and this forum post might help you
https://scotch.io/tutorials/creating-a-laravel-404-page-using-custom-exception-handlers
To do so you need to check if the id exist in the model like so:
public function dashboard(User $uuid)
{
if(User::find($uuid))
{
return View::make('user.dashboard');
} else {
redirect('xyz');
}
}
I think this tutorial will be helpful for you Laravel Model Binding
You could add the exception and treat in in app/Exceptions/Handler.php
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
if (!env('APP_DEBUG')) {
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
//treat error
return response()->view('errors.404');
}
return parent::render($request, $e);
}
Edit 1:
This piece of code is from a working project so if this doesn't work it must have an error somewhere else:
if ($e instanceof \Illuminate\Database\Eloquent\ModelNotFoundException) {
$data= \App\Data::orderBy('order', 'asc')->get();
return response()->view('errors.404', [
'data' => $data
], 404);
}
Edit 2:
You can use the above code and this tutorial in order to create a new Exception type in order to get your desired behavior. To my understanding at least. :)

Dynamic routes in laravel instead of manual routes

I want to prevent write all route in Laravel route.php,actually i follow MVC routing like this www.example.com/controller/action/p1/p2/p3
if you have any good idea give it to me,
i wrote this
$controller = ucfirst(Request::segment(1));
$controller = $controller . 'Controller';
$result=App::make('indexController')->ChechIfExistController($controller);
if($result){
if(Request::segment(2))
$action=Request::segment(2);
else
$action='index';
if(Request::segment(5))
Route::any('/{controller?}/{action?}/{p1?}/{p2?}/{p3?}',array('uses'=>$controller.'#'.$action));
else if(Request::segment(4))
Route::any('/{controller?}/{action?}/{p1?}/{p2?}',array('uses'=>$controller.'#'.$action));
else if(Request::segment(3))
Route::any('/{controller?}/{action?}/{p1?}',array('uses'=>$controller.'#'.$action));
else
Route::any('/{controller?}/{action?}',array('uses'=>$controller.'#'.$action));
} else{
echo '404';
EXIT;
}
but i don't know how to control and check controller and action in laravel to understand if it exist or not.
i need your help.
thanks a lot.
ifound it,this code fix the problem and check if action exist or not,but i would like to do that with laravel but it seems laravel does not have any thing for checking controller and actions
$controller=='Controller'?$controller='IndexController':$controller;
$controllers=new $controller ();
if(method_exists($controllers,$action)){...}
and in composer define my route,
that's all
routes.php
Route::controllers([
'auth' => 'Auth\AuthController',
]);
in AuthController you can do that:
// will be available as METHODNAME /auth/url/{one?}/{two?}/{three?}/{four?}/{five?}
public [methodName]Url($one, $two, $three, $four, $five)
{
//...
}
// for example POST /auth/register
public function postRegister(Request $request)
{
// ...
}
// GET /auth/login
public function getLogin()
{
//...
}
it's not documented, but you can see that in sources:
https://github.com/laravel/framework/blob/5.0/src%2FIlluminate%2FRouting%2FControllerInspector.php
https://github.com/laravel/framework/blob/5.0/src%2FIlluminate%2FRouting%2FRouter.php#L238
That can be done such way:
First we have to write static routes and after that, dynamic route which uses database.
routes.php
Route::get('/', function () {
return 'welcome';
});
Route::get('/faq', function () {
return 'faq';
});
Route::get('/about', function () {
return 'about';
});
Route::get('/{slug}', function ($slug) {
return Article::where('slug', $slug)->first();
});

How add Custom Validation Rules when using Form Request Validation in Laravel 5

I am using form request validation method for validating request in laravel 5.I would like to add my own validation rule with form request validation method.My request class is given below.I want to add custom validation numeric_array with field items.
protected $rules = [
'shipping_country' => ['max:60'],
'items' => ['array|numericarray']
];
My cusotom function is given below
Validator::extend('numericarray', function($attribute, $value, $parameters) {
foreach ($value as $v) {
if (!is_int($v)) {
return false;
}
}
return true;
});
How can use this validation method with about form request validation in laravel5?
While the above answer is correct, in a lot of cases you might want to create a custom validation only for a certain form request. You can leverage laravel FormRequest and use dependency injection to extend the validation factory. I think this solution is much simpler than creating a service provider.
Here is how it can be done.
use Illuminate\Validation\Factory as ValidationFactory;
class UpdateMyUserRequest extends FormRequest {
public function __construct(ValidationFactory $validationFactory)
{
$validationFactory->extend(
'foo',
function ($attribute, $value, $parameters) {
return 'foo' === $value;
},
'Sorry, it failed foo validation!'
);
}
public function rules()
{
return [
'username' => 'foo',
];
}
}
Using Validator::extend() like you do is actually perfectly fine you just need to put that in a Service Provider like this:
<?php namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ValidatorServiceProvider extends ServiceProvider {
public function boot()
{
$this->app['validator']->extend('numericarray', function ($attribute, $value, $parameters)
{
foreach ($value as $v) {
if (!is_int($v)) {
return false;
}
}
return true;
});
}
public function register()
{
//
}
}
Then register the provider by adding it to the list in config/app.php:
'providers' => [
// Other Service Providers
'App\Providers\ValidatorServiceProvider',
],
You now can use the numericarray validation rule everywhere you want
The accepted answer works for global validation rules, but many times you will be validating certain conditions that are very specific to a form. Here's what I recommend in those circumstances (that seems to be somewhat intended from Laravel source code at line 75 of FormRequest.php):
Add a validator method to the parent Request your requests will extend:
<?php namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Validator;
abstract class Request extends FormRequest {
public function validator(){
$v = Validator::make($this->input(), $this->rules(), $this->messages(), $this->attributes());
if(method_exists($this, 'moreValidation')){
$this->moreValidation($v);
}
return $v;
}
}
Now all your specific requests will look like this:
<?php namespace App\Http\Requests;
use App\Http\Requests\Request;
class ShipRequest extends Request {
public function rules()
{
return [
'shipping_country' => 'max:60',
'items' => 'array'
];
}
// Here we can do more with the validation instance...
public function moreValidation($validator){
// Use an "after validation hook" (see laravel docs)
$validator->after(function($validator)
{
// Check to see if valid numeric array
foreach ($this->input('items') as $item) {
if (!is_int($item)) {
$validator->errors()->add('items', 'Items should all be numeric');
break;
}
}
});
}
// Bonus: I also like to take care of any custom messages here
public function messages(){
return [
'shipping_country.max' => 'Whoa! Easy there on shipping char. count!'
];
}
}
Custom Rule Object
One way to do it is by using Custom Rule Object, this way you can define as many rule as you want without need to make changes in Providers and in controller/service to set new rules.
php artisan make:rule NumericArray
In NumericArray.php
namespace App\Rules;
class NumericArray implements Rule
{
public function passes($attribute, $value)
{
foreach ($value as $v) {
if (!is_int($v)) {
return false;
}
}
return true;
}
public function message()
{
return 'error message...';
}
}
Then in Form request have
use App\Rules\NumericArray;
.
.
protected $rules = [
'shipping_country' => ['max:60'],
'items' => ['array', new NumericArray]
];
Alternatively to Adrian Gunawan's solution this now also can be approached like:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreBlogPost extends FormRequest
{
public function rules()
{
return [
'title' => ['required', 'not_lorem_ipsum'],
];
}
public function withValidator($validator)
{
$validator->addExtension('not_lorem_ipsum', function ($attribute, $value, $parameters, $validator) {
return $value != 'lorem ipsum';
});
$validator->addReplacer('not_lorem_ipsum', function ($message, $attribute, $rule, $parameters, $validator) {
return __("The :attribute can't be lorem ipsum.", compact('attribute'));
});
}
}
You need to override getValidatorInstance method in your Request class, for example this way:
protected function getValidatorInstance()
{
$validator = parent::getValidatorInstance();
$validator->addImplicitExtension('numericarray', function($attribute, $value, $parameters) {
foreach ($value as $v) {
if (!is_int($v)) {
return false;
}
}
return true;
});
return $validator;
}
You don't need to extend the validator to validate array items, you can validate each item of a array with "*" as you can see in
Array Validation
protected $rules = [
'shipping_country' => ['max:60'],
'items' => ['array'],
'items.*' => 'integer'
];
All answers on this page will solve you the problem, but... But the only right way by the Laravel conventions is solution from Ganesh Karki
One example:
Let’s take an example of a form to fill in Summer Olympic Games events – so year and city. First create one form.
<form action="/olimpicyear" method="post">
Year:<br>
<input type="text" name="year" value=""><br>
City:<br>
<input type="text" name="city" value=""><br><br>
<input type="submit" value="Submit">
</form>
Now, let’s create a validation rule that you can enter only the year of Olympic Games. These are the conditions
Games started in 1896
Year can’t be bigger than current year
Number should be divided by 4
Let’s run a command:
php artisan make:rule OlympicYear
Laravel generates a file app/Rules/OlympicYear.php. Change that file to look like this:
namespace App\Rules;
use Illuminate\Contracts\Validation\Rule;
class OlympicYear implements Rule
{
/**
* Determine if the validation rule passes.
*
* #param string $attribute
* #param mixed $value
* #return bool
*/
public function passes($attribute, $value)
{
// Set condition that should be filled
return $value >= 1896 && $value <= date('Y') && $value % 4 == 0;
}
/**
* Get the validation error message.
*
* #return string
*/
public function message()
{
// Set custom error message.
return ':attribute should be a year of Olympic Games';
}
}
Finally, how we use this class? In controller's store() method we have this code:
public function store(Request $request)
{
$this->validate($request, ['year' => new OlympicYear]);
}
If you want to create validation by Laravel conventions follow tutorial in link below. It is easy and very well explained. It helped me a lot. Link for original tutorial is here Tutorial link.
For me works the solution that give us lukasgeiter, but with a difference that we create a class with our custom validations ,like this, for laravel 5.2.* The next example is for add a validation to a range of date in where the second date has to be equals or more big that the first one
In app/Providers create ValidatorExtended.php
<?php
namespace App\Providers;
use Illuminate\Validation\Validator as IlluminateValidator;
class ValidatorExtended extends IlluminateValidator {
private $_custom_messages = array(
"after_or_equal" => ":attribute debe ser una fecha posterior o igual a
:date.",
);
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 );
}
/**
* La fecha final debe ser mayor o igual a la fecha inicial
*
* after_or_equal
*/
protected function validateAfterOrEqual( $attribute, $value, $parameters,
$validator) {
return strtotime($validator->getData()[$parameters[0]]) <=
strtotime($value);
}
} //end of class
Ok. now lets create the Service Provider. Create ValidationExtensionServiceProvider.php inside app/Providers, and we code
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Validator;
class ValidationExtensionServiceProvider extends ServiceProvider {
public function register() {}
public function boot() {
$this->app->validator->resolver( function( $translator, $data, $rules,
$messages = array(), $customAttributes = array() ) {
return new ValidatorExtended( $translator, $data, $rules, $messages,
$customAttributes );
} );
}
} //end of class
Now we to tell Laravel to load this Service Provider, add to providers array at the end in config/app.php and
//Servicio para extender validaciones
App\Providers\ValidationExtensionServiceProvider::class,
now we can use this validation in our request in function rules
public function rules()
{
return [
'fDesde' => 'date',
'fHasta' => 'date|after_or_equal:fDesde'
];
}
or in Validator:make
$validator = Validator::make($request->all(), [
'fDesde' => 'date',
'fHasta' => 'date|after_or_equal:fDesde'
], $messages);
you have to notice that the name of the method that makes the validation has the prefix validate and is in camel case style validateAfterOrEqual but when you use the rule of validation every capital letter is replaced with underscore and the letter in lowercase letter.
All this I take it from https://www.sitepoint.com/data-validation-laravel-right-way-custom-validators// here explain in details. thanks to them.

Laravel 4 passing data from filter to route

How can I pass a variable that is processed in the filter back to route for later usage?
Example:
Filter:
Route::filter('myfilter', function()
{
return "some data";
});
Route:
Route::get('/mypage', array('before'=>'myfilter', function($filter) {
if($filter!= 'admin') {
return Redirect::to('home');
}
}));
The above example doesn't work. How can I make it works?
Thank you.
WARNING: THIS DOESN'T SEEM TO WORK!
Store it in the IoC container: http://laravel-recipes.com/recipes/3
Filter:
Route::filter('myfilter', function()
{
App::instance('app.name', 'John Doe'); // store var
});
Route:
Route::get('/mypage', array('before'=>'myfilter', function() {
$name = app('app.name'); // read var
return $name;
}));
You cannot. If you return anything from filters Laravel will return that filter to your browser and end your request.
Whay you can do is to redirect to a route and pass values to that route:
Route::filter('myfilter', function()
{
return Redirect::to('mynewpage')->with('var', 'some data');
});
This data will come back in the Session:
$data = Session::get('var');
You may pass data by any of below methods:
Session::put() and Session::get() ( slow because of disk writes )
Config::set() and Config::set() Defining PHP constant by
define() ( recommended for your purpose ) Setting and getting a
global variable by $_GLOBALS[] array ( not recommended )
Recently working in an old Laravel 4.2 project & approached this with a controller method as Controller Filter:
namespace App\Controllers;
Class MyController extends BaseController
{
protected $filterValue;
public function __construct()
{
$this->beforeFilter('#myFilter', array(
'only' => 'myPage'
));
}
public function myFilter($route, $request)
{
$this->filterValue = 'some data';
}
public function myPage()
{
if ($this->filterValue != 'admin') {
return Redirect::to('home');
}
}
}

Categories