I need some clarification with dynamic controller method
i upgrading the laravel 5.1 to 8.*, all is done, but only one bug,
my url is admin/admin-profile in 5.1 is working fine, but laravel 8 is not working 404 page error is showing.
this url will call method getAdminProfile(){ } but is not calling.
if this functionality is not available in laravel 8, then how can i manage this, if single url i will create route, but my application have more than 100 url like this, so please help me to solve this...
i was check this issues by compare all file both laravel 5.1 and laravel 8
missing one file to capture the like this problem from ControllerInspector from routing folder.
so please help to solve this..
i can't write each method in web.php
Route::controller('admin', 'AdminController');
class AdminController extends BaseController {
public function getIndex()
{
//
}
public function getAdminProfile()
{
//
}
public function anyLogin()
{
//
}
}
Resource Controller
If Resource Controller can handle your need so use that:
Documentation: https://laravel.com/docs/8.x/controllers#resource-controllers
and it's like below:
Routing:
use App\Http\Controllers\PhotoController;
Route::resource('photos', PhotoController::class);
----------------
If Resource Controller is not what you want:
you can get url and transfer url to controller function, for example:
router file getting any url after admin\
Route::prefix('admin')->group(function () {
Route::get('/{my_url?}', [AdminControlle:class, 'handle'])->where('my_url', '(.*)');
});
AdminController
public function handle(){
// get url segments after admin/ - if url is like your_domain.com/admin/...
$segments = array_slice(request()->segments(), 1);
// Translate array of segments to function name - implement by you
$functionName='';
// calling function with knowing its name
$functionName() // or can use call_user_func($functionName);
}
*Note: be aware that this dynamic route handling doesn't provide a solution for dynamic handling of http methods (post, get, patch, ...).
in this example i used GET method.
I just add below code in router.php
/**
* Register an array of controllers with wildcard routing.
*
* #param array $controllers
* #return void
*
* #deprecated since version 5.1.
/
public function controllers(array $controllers)
{
foreach ($controllers as $uri => $controller) {
$this->controller($uri, $controller);
}
}
/*
* Prepend the last group uses onto the use clause.
*
* #param string $uses
* #return string
*/
protected function prependGroupUses($uses)
{
$group = end($this->groupStack);
return isset($group['namespace']) && strpos($uses, '\\') !== 0 ? $group['namespace'].'\\'.$uses : $uses;
}
/**
* Route a controller to a URI with wildcard routing.
*
* #param string $uri
* #param string $controller
* #param array $names
* #return void
*
* #deprecated since version 5.1.
*/
public function controller($uri, $controller, $names = [])
{
$prepended = $controller;
// First, we will check to see if a controller prefix has been registered in
// the route group. If it has, we will need to prefix it before trying to
// reflect into the class instance and pull out the method for routing.
if (! empty($this->groupStack)) {
$prepended = $this->prependGroupUses($controller);
}
$routable = (new ControllerInspector)
->getRoutable($prepended, $uri);
// When a controller is routed using this method, we use Reflection to parse
// out all of the routable methods for the controller, then register each
// route explicitly for the developers, so reverse routing is possible.
// print_r($routable);
foreach ($routable as $method => $routes) {
foreach ($routes as $route) {
$this->registerInspected($route, $controller, $method, $names);
}
}
$this->addFallthroughRoute($controller, $uri);
}
/**
* Register an inspected controller route.
*
* #param array $route
* #param string $controller
* #param string $method
* #param array $names
* #return void
*
* #deprecated since version 5.1.
*/
protected function registerInspected($route, $controller, $method, &$names)
{
$action = ['uses' => $controller.'#'.$method];
// If a given controller method has been named, we will assign the name to the
// controller action array, which provides for a short-cut to method naming
// so you don't have to define an individual route for these controllers.
$action['as'] = Arr::get($names, $method);
$this->{$route['verb']}($route['uri'], $action);
}
/**
* Add a fallthrough route for a controller.
*
* #param string $controller
* #param string $uri
* #return void
*
* #deprecated since version 5.1.
*/
protected function addFallthroughRoute($controller, $uri)
{
$missing = $this->any($uri.'/{_missing}', $controller.'#missingMethod');
$missing->where('_missing', '(.*)');
}
Now is working normally
Related
I have a routes group in Laravel that gets a parameter like below:
Route::group(['prefix' => '{ProjectCode}'], function () {
Route::get('/categories', 'CategoriesController#Categories');
Route::get('/add-category', 'CategoriesController#AddCategory');
});
The ProjectCode is an id, that is used to get some data from the database
I want to pass the retrieved data to controllers that they are in sub of the route group and avoid getting data in every function in controller
You may use "Implicit/Explicit Route Model Binding", assuming you have Project model, you can have this controller method (use camelCase for parameters and controllers' methods name not PascalCase):
Route::group(['prefix' => '{projectCode}'], function () {
Route::get('/categories', 'CategoriesController#pategories');
Route::get('/add-category', 'CategoriesController#addCategory');
});
class CategoriesController extends Controller
{
/**
* Display a listing of the resource.
*
* #param Request $request
* #return \Illuminate\Http\Response
*/
public function categories(\App\Project $projectCode)
{
//
}
}
You may wish to use your own resolution binding, override the resolveRouteBinding on your 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->where($field ?? $this->getRouteKeyName(), $value)->first();
}
See Laravel docs for more info.
Currently I am working on a project where we are trying to create a RESTful API. This API uses some default classes, for example the ResourceController, for basic behaviour that can be overwritten when needed.
Lets say we have an API resource route:
Route::apiResource('posts', 'ResourceController');
This route will make use of the ResourceController:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Repositories\ResourceRepository;
class ResourceController extends Controller
{
/**
* The resource class.
*
* #var string
*/
private $resourceClass = '\\App\\Http\\Resources\\ResourceResource';
/**
* The resource model class.
*
* #var string
*/
private $resourceModelClass;
/**
* The repository.
*
* #var \App\Repositories\ResourceRepository
*/
private $repository;
/**
* ResourceController constructor.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
public function __construct(Request $request)
{
$this->resourceModelClass = $this->getResourceModelClass($request);
$this->repository = new ResourceRepository($this->resourceModelClass);
$exploded = explode('\\', $this->resourceModelClass);
$resourceModelClassName = array_last($exploded);
if (!empty($resourceModelClassName)) {
$resourceClass = '\\App\\Http\\Resources\\' . $resourceModelClassName . 'Resource';
if (class_exists($resourceClass)) {
$this->resourceClass = $resourceClass;
}
}
}
...
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$this->validate($request, $this->getResourceModelRules());
$resource = $this->repository->create($request->all());
$resource = new $this->resourceClass($resource);
return response()->json($resource);
}
/**
* Display the specified resource.
*
* #param int $id
* #return \Illuminate\Http\Response
*/
public function show($id)
{
$resource = $this->repository->show($id);
$resource = new $this->resourceClass($resource);
return response()->json($resource);
}
...
/**
* Get the model class of the specified resource.
*
* #param \Illuminate\Http\Request $request
* #return string
*/
private function getResourceModelClass(Request $request)
{
if (is_null($request->route())) return '';
$uri = $request->route()->uri;
$exploded = explode('/', $uri);
$class = str_singular($exploded[1]);
return '\\App\\Models\\' . ucfirst($class);
}
/**
* Get the model rules of the specified resource.
*
* #param \Illuminate\Http\Request $request
* #return string
*/
private function getResourceModelRules()
{
$rules = [];
if (method_exists($this->resourceModelClass, 'rules')) {
$rules = $this->resourceModelClass::rules();
}
return $rules;
}
}
As you can maybe tell we are not making use of model route binding and we make use of a repository to do our logic.
As you can also see we make use of some dirty logic, getResourceModelClass(), to determine the model class needed to perform logic on/with. This method is not really flexible and puts limits on the directory structure of the application (very nasty).
A solution could be adding some information about the model class when registrating the route. This could look like:
Route::apiResource('posts', 'ResourceController', [
'modelClass' => Post::class
]);
However it looks like this is not possible.
Does anybody have any suggestions on how to make this work or how to make our logic more clean and flexible. Flexibility and easy of use are important factors.
The nicest way would be to refactor the ResourceController into an abstract class and have a separate controller that extends it - for each resource.
I'm pretty sure that there is no way of passing some context information in routes file.
But you could bind different instances of repositories to your controller. This is generally a good practice, but relying on URL to resolve it is very hacky.
You'd have to put all the dependencies in the constructor:
public function __construct(string $modelPath, ResourceRepository $repo // ...)
{
$this->resourceModelClass = $this->modelPath;
$this->repository = $repo;
// ...
}
And do this in a service provider:
use App\Repositories\ResourceRepository;
use App\Http\Controllers\ResourceController;
// ... model imports
// ...
public function boot()
{
if (request()->path() === 'posts') {
$this->app->bind(ResourceRepository::class, function ($app) {
return new ResourceRepository(new Post);
});
$this->app->when(ResourceController::class)
->needs('$modelPath')
->give(Post::class);
} else if (request()->path() === 'somethingelse') {
// ...
}
}
This will give you more flexibility, but again, relying on pure URL paths is hacky.
I just showed an example for binding the model path and binding a Repo instance, but if you go down this road, you'll want to move all the instantiating out of the Controller constructor.
After a lot of searching and diving in the source code of Laravel I found out the getResourceAction method in the ResourceRegistrar handles the option passed to the route.
Further searching led me to this post where someone else already managed to extend this registrar en add some custom functionality.
My custom registrar looks like:
<?php
namespace App\Http\Routing;
use Illuminate\Routing\ResourceRegistrar as IlluResourceRegistrar;
class ResourceRegistrar extends IlluResourceRegistrar
{
/**
* Get the action array for a resource route.
*
* #param string $resource
* #param string $controller
* #param string $method
* #param array $options
* #return array
*/
protected function getResourceAction($resource, $controller, $method, $options)
{
$action = parent::getResourceAction($resource, $controller, $method, $options);
if (isset($options['model'])) {
$action['model'] = $options['model'];
}
return $action;
}
}
Do not forget to bind in the AppServiceProvider:
$registrar = new ResourceRegistrar($this->app['router']);
$this->app->bind('Illuminate\Routing\ResourceRegistrar', function () use ($registrar) {
return $registrar;
});
This custom registrar allows the following:
Route::apiResource('posts', 'ResourceController', [
'model' => Post::class
]);
And finally we are able to get our model class:
$resourceModelClass = $request->route()->getAction('model');
No hacky url parse logic anymore!
In Drupal there is a simple url rewrite system that stores path aliases and the real route in the database.
For example:
/category/hello => node/5
I would like to imitate this system in Laravel.
I know how to create the database structure. What I would like suggestions for is actually overriding and remapping the incoming request.
I've taken a glance at the router. No events are really sticking out. What I would like to avoid is adding every permutation as a static route. I would like to for this to be completely dynamic.
I was reading middleware with a redirect would work but don't know if that is the best route to go. Keep in mind that the aliases could be anything. There isn't any set pattern.
The actual business case for this is the application has a hierarchy of categories like for a catalog on an ecommerce site. For every path a dynamic page will need to exist and possibly also allow pass-thrus to other pages.
Ex.
/sports/football/nfl => \App\Http\Controllers\Category::lp(2)
Even something like:
/sports/football/nfl/:game/lines => \App\Http\Controllers\Lines::lp(:game)
However, I don't want to have every permutation in the db. Just the base one and allow everything after /sports/football/nfl/* pass thru to a completely different location.
If I do recall in Symfony this could be done with a custom route matcher. However, I don't see anything like that in Laravel. Unless I'm just missing something. It looks like you either add a static route or nothing all but I haven't taken the deep dive into that code yet so could be wrong.
I was able to implement a dynamic routing system by creating my own custom route and adding to the route collection manually.
Custom Route
use Illuminate\Routing\Route as BaseRoute;
use Modules\Catalog\Routing\Matching\CategoryValidator;
use Illuminate\Routing\Matching\MethodValidator;
use Illuminate\Routing\Matching\SchemeValidator;
use Illuminate\Routing\Matching\HostValidator;
use Illuminate\Http\Request;
use Modules\Event\Repositories\CategoryRepository;
use Illuminate\Routing\ControllerDispatcher;
/**
* Special dynamic touting for catalog categories.
*/
class CategoryRoute extends BaseRoute {
protected $validatorOverrides;
/**
* #param CategoryRepository
*/
protected $categoryRepository;
/**
* Create a new Route instance.
*
* #param CategoryRepository $categoryRepository
* The category repository.
*/
public function __construct(CategoryRepository $categoryRepository)
{
$this->categoryRepository = $categoryRepository;
$action = [
'uses'=> function() use ($categoryRepository) {
$path = app('request')->path();
$category = $categoryRepository->findOneByHierarchicalPath($path);
$controller = app()->make('Modules\Catalog\Http\Controllers\Frontend\CategoryController');
return $controller->callAction('getIndex', ['categoryId'=>$category->getId()]);
}
];
$action['uses']->bindTo($this);
parent::__construct(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'],'_catalog_category',$action);
}
/**
* Determine if the route matches given request.
*
* #param \Illuminate\Http\Request $request
* #param bool $includingMethod
* #return bool
*/
public function matches(Request $request, $includingMethod = true)
{
$this->compileRoute();
$validators = $this->getValidatorOverrides();
foreach ($validators as $validator) {
/*if (! $includingMethod && $validator instanceof MethodValidator) {
continue;
}*/
if (! $validator->matches($this, $request)) {
return false;
}
}
return true;
}
/**
* Get the route validators for the instance.
*
* #return array
*/
public function getValidatorOverrides()
{
if (isset($this->validatorOverrides)) {
return $this->validatorOverrides;
}
$this->validatorOverrides = [
new MethodValidator, new SchemeValidator,
new HostValidator, /*new UriValidator,*/
new CategoryValidator($this->categoryRepository)
];
return $this->validatorOverrides;
}
}
Custom Route Validator
<?php
namespace Modules\Catalog\Routing\Matching;
use Illuminate\Routing\Matching\ValidatorInterface;
use Illuminate\Routing\Route;
use Illuminate\Http\Request;
use Modules\Event\Repositories\CategoryRepository;
class CategoryValidator implements ValidatorInterface
{
protected $categoryRepository;
public function __construct(CategoryRepository $categoryRepository) {
$this->categoryRepository = $categoryRepository;
}
/**
* Validate a given rule against a route and request.
*
* #param \Illuminate\Routing\Route $route
* #param \Illuminate\Http\Request $request
* #return bool
*/
public function matches(Route $route, Request $request)
{
$path = $request->path() == '/' ? '/' : '/'.$request->path();
$category = $this->categoryRepository->findOneByHierarchicalPath($path);
return $category?true:false;
}
}
To satisfy the requirements of the category repository dependency I had to also create a subscriber that adds the route after all the providers had been booted. Simply placing it in the routes.php file would not work because there was no guarantee that all the dependencies for IoC would be configured when that file gets loaded.
Bootstrap Subscriber
use Modules\Catalog\Routing\CategoryRoute;
use Modules\Event\Repositories\CategoryRepository;
use Illuminate\Support\Facades\Route as RouteFacade;
class BootstrapSubscriber {
public function subscribe($events) {
$events->listen(
'bootstrapped: Illuminate\Foundation\Bootstrap\BootProviders',
'Modules\Catalog\Subscribers\BootstrapSubscriber#onBootstrappedBootProviders'
);
}
public function onBootstrappedBootProviders($event) {
$categoryRepository = app(CategoryRepository::class);
RouteFacade::getRoutes()->add(new CategoryRoute($categoryRepository));
}
}
I will probably expand on this but that is the basic way to do it.
I get the error when trying to make a post call to /api/subject/search
I assume it's a simple syntax error I'm missing
I have my api routes defined below
Route::group(array('prefix' => 'api'), function()
{
Route::post('resource/search', 'ResourceController');
Route::resource('resource', 'ResourceController');
Route::post('subject/search', 'SubjectController');
Route::resource('subject', 'SubjectController');
Route::resource('user', 'UserController');
Route::controller('/session', 'SessionController');
Route::post('/login', array('as' => 'session', 'uses' => 'SessionController#Store'));
});
And my controller is mostly empty
class SubjectController extends \BaseController
{
public function search()
{
$subjects = [];
if((int)Input::get('grade_id') < 13 && (int)Input::get('grade_id') > 8)
$subjects = Subject::where('name', 'like', '%HS%')->get();
else
$subjects = Subject::where('name', 'not like', '%HS%')->get();
return Response::json([
'success' => true,
'subjects' => $subjects->toArray()
]);
}
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* #return Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
//
}
/**
* Display the specified resource.
*
* #param int $id
* #return Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* #param int $id
* #return Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* #param int $id
* #return Response
*/
public function update($id)
{
//
}
/**
* Remove the specified resource from storage.
*
* #param int $id
* #return Response
*/
public function destroy($id)
{
//
}
}
You need to specify the method.
try
Route::post('subject/search', 'SubjectController#search');
See the named route example:
Laravel Docs
In your case I think search is not resolved by the controller to load the search() method. You are also sending a POST for search functionality and I guess it's better to do a GET request since POST and PUT are for storing data.
Conventions
When creating API's it's a good thing to stick to naming conventions and patterns.
http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
Solution
Your route could be simpler like this: api.yourdomain.com/api/subject?search=term1,term2. Doing this with a GET query makes it going to the index() method. There you can check the GET params and do your search stuff and return.
Check this for the cleanest and truely RESTful way to make an API in Laravel:
How do I create a RESTful API in Laravel to use in my BackboneJS app
I got same error when accessing object at index of an empty array in view blade php file.
I have a Laravel 3 application that has several REST-ful controllers.
The controllers that take no parameters (e.g. a controller that handles the URL /api/books) works fine, but when I try and access the URL of a controller that takes parameters (e.g. /api/book/1), it doesn't work. However, if I append the method name to the URL (e.g. /api/book/index/1), it does work properly.
Is there a way to not be required to use the keyword "index" on a controller?
An example of one of the non-functioning controllers--
<?php
class API_Book_Controller extends Base_Controller {
/**
* Indicates the controller is RESTful
* #var boolean
*/
public $restful = true;
/**
* Fetch a book by ID
* #param integer $id ID number of the book
* #return Response HTTP response
*/
public function get_index($id = null){
$book = Book::find($id);
if(is_null($book)){
return Response::error('404');
}
return Response::eloquent($book);
}
Route::get('api/book/(:num?)', 'API_Book_Controller#get_index');