Global route in Laravel 5 - laravel

Some context: I've setup my Laravel 5 app to be split into modules. The boot() function in my AppServiceProvider looks like this:
public function boot()
{
// We want to register the modules in the Modules folder
$modulesPath = app_path() . '/Modules';
$handle = opendir($modulesPath);
// Loop through the module directory and register each module
while (($module = readdir($handle)) !== false) {
if ($module != "." && $module != ".." && is_dir("{$modulesPath}/{$module}")) {
// Check if there are routes for that module, if so include
if (file_exists($modulesPath . '/' . $module . '/routes.php')) {
include $modulesPath . '/' . $module . '/routes.php';
}
// Check if there are views for that module, if so set a namespace for those views
if (is_dir($modulesPath . '/' . $module . '/Views')) {
$this->loadViewsFrom($modulesPath . '/' . $module . '/Views', strtolower($module));
}
}
}
}
The idea is to be able to keep things separated in modules, but also have global routes and a global controller. Therefore, each module has its own routes.php file that looks something like this:
<?php
Route::group(array('module'=>'MyModule','namespace' => 'NexusHub\Modules\MyModule\Controllers'), function() {
Route::resource('mymodule', 'MyModuleController');
});
I then have a global routes.php file that looks like this:
<?php
Route::any('{catchall}', 'GlobalController#myAction')->where('catchall', '(.*)');
Route::group(array('module'=>'Global'), function() {
Route::resource('', 'GlobalController');
});
The problem I'm running into is that it seems my catchall route isn't picking up for the modules. The modules run their own routes but the catchall route is ignored.
As far as why I'm trying to accomplish this, for now the purpose is that all modules use the same layout, and that layout requires some data to be retrieved always, so the global controller would grab what's needed and make it available to the layout. But I suppose there may be some other things in the future where having a global route file that can catch multiple different routes based on arbitrary rules and run additional code would come in handy.
UPDATE: Removed the line that included the global routes since I realized they already got included by default anyway.

Your global routes file is being loaded first.
Try moving your service provider that loads all your module routes before the "App\Providers\RouteServiceProvider" in the config/app.php file.
...see if that helps.

I ended up doing what I wanted using middleware instead (see https://laravel.com/docs/master/middleware#global-middleware).
In my case I used something similar to the BeforeMiddleware class example and registered it in the $middleware property of my app/Http/Kernel.php class since it's global and not route dependant.

Related

Laravel installation in sub-folder and horizon not working

I have installed the Laravel in sub-folder and is trying to install the horizon. After routing to "test.com/sub-folder/horizon", all the design in broken and also the internal links are pointing to main domain instead of main-domain-without-subfolder.
After the search, it seems to be the known issue which is already reported in github issue
Has there is any work around to make horizon work when Laravel is installed in sub-folder?
I have a solution that only involves PHP.
The issue, as pointed out by #Isaiahiroko, is the basePath defined for Horizon's interface. That code is in Laravel\Horizon\Http\Controllers\HomeController::index(). The idea is this: we are going to pass to Laravel's service container our own implementation of that controller that will override the basePath definition passed to Horizon's interface.
Create a new controller with code like this:
<?php
namespace App\Http\Controllers;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\View\Factory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Laravel\Horizon\Horizon;
use Laravel\Horizon\Http\Controllers\HomeController;
class HorizonHomeController extends HomeController
{
/**
* Overrides default horizon route to support subdirectory hosting.
*/
public function index ()
{
// We use a plain request to check for the base url.
$request = request();
// Set up our base path.
$base_path = Str::substr($request->getBasePath(), 1);
if (!empty($base_path)) {
$base_path .= '/';
}
// Patch default horizon variables with our own base path.
$variables = Horizon::scriptVariables();
$variables['path'] = $base_path . config('horizon.path');
// Render horizon's home view.
return view('horizon::layout', [
'assetsAreCurrent' => Horizon::assetsAreCurrent(),
'horizonScriptVariables' => $variables,
'cssFile' => Horizon::$useDarkTheme ? 'app-dark.css' : 'app.css',
'isDownForMaintenance' => App::isDownForMaintenance(),
]);
}
}
What's left is telling Laravel's service container that when Horizon's HomeController is requested, it should provide our HorizonHomeController class. In your AppServiceProvider, at the end of the register() method, set this up:
// [...]
class AppServiceProvider extends ServiceProvider
{
// [...]
/**
* Register any application services.
*
* #return void
* #throws InvalidConfiguration
*/
public function register()
{
// [...]
// Horizon's subdirectory hack
$this->app->bind(
Laravel\Horizon\Http\Controllers\HomeController::class,
App\Http\Controllers\HorizonHomeController::class
);
}
// [...]
}
After that, you should be able to browse to http(s)://<your-host>/<your-sub-dir>/horizon normally.
Considerations:
To me this feels cleaner that patching a compiled js, which also has the downside that needs to be re-applied every time Horizon is updated (this can be mitigated with a post-update script in composer, tho). Also, for additional points, this solution is only overriding the method that renders the view, but not the route, which means all of Horizon's authentication mechanisms (middlewares and gates) are working exactly as described in the documentation.
If you desperately need to do this, here is a hack:
In public\vendor\horizon\app.js, search for window.Horizon.basePath
replace window.Horizon.basePath="/"+window.Horizon.path; with window.Horizon.basePath="/[you sub-directoy]/"+window.Horizon.path;
It should work...until you run update one day and it mysteriously stop working.

Laravel: Best way to implement dynamic routing in routes.php based on environment variable?

My aim is to roll out a big re-theming / re-skinning (including new URL routing) for a Laravel v5 project without touching the existing business logic (as much as possible that is).
This is my current approach:
I placed a APP_SKIN=v2 entry in my .env file
My app\Http\routes.php file has been changed as follows:
if (env('APP_SKIN') === "v2") {
# Point to the v2 controllers
Route::get('/', 'v2\GeneralController#home' );
... all other v2 controllers here ...
} else {
# Point to the original controllers
Route::get('/', 'GeneralController#home' );
... all other controllers
}
All v2 controllers have been placed in app/Http/Controllers/v2 and namespaced accordingly
All v2 blade templates have been placed in resources/views/v2
the rest of the business logic remains exactly the same and shared between the "skins".
My question: Is there a "better" way to achieve the above?. Please note that the idea here is to affect as few files as possible when doing the migration, as well as ensure that the admin can simply change an environment variable and "roll back" to the previous skin if there are problems.
Within app/Providers/RouteServiceProvider.php you can define your routes and namespaces, etc. This is where I would put the logic you talked about (rather than in the routes file):
protected function mapWebRoutes()
{
if (App::env('APP_SKIN') === 'v2') {
Route::group([
'middleware' => 'web',
'namespace' => $this->namespace,
], function ($router) {
require base_path('routes/web_v2.php');
});
} else {
// ...
}
}
This way, you can create separate route files to make it a bit cleaner.
Aside from that, I personally can't see a better solution for your situation than what you described as I'm guessing your templates want to vary in the data that they provide, which if that is the case then you will need new controllers - otherwise you could set a variable in a middleware which is then retrieved by your current controllers which could then determine which views, css and js are included. This would mean you would only need to update your existing controllers, but depending upon your current code - this could mean doing just as much work as your current solution.
Routes pass through Middleware. Thus you can achieve this by BeforeMiddleware as follows
public function handle($request, Closure $next)
{
// Get path and append v2 if env is v2
$path = $request->path();
$page = $str = str_replace('', '', $path); // You can replace if neccesary
// Before middleware
if (env('APP_SKIN') === "v2")
{
return $next($request);
}
else
{
}
}

Calling controllers dynamically

I'm attempting to create dynamic routing in Laravel for my controllers - I know this can be done in Kohana, but I've been unsuccessful trying to get it working with Laravel.
This is what I have right now:
Route::get('/{controller}/{action?}/{id?}'...
So I would like to call controller/method($id) with that.
Ideally this is what I would like to do:
Route::get('/{controller}/{action?}/{id?}', $controller . '#' . $action);
And have it dynamically call $controller::$action.
I've tried doing this:
Route::get('/{controller}/{action?}/{id?}', function($controller, $action = null, $id = null)
{
$controller = new $controller();
$controller->$action();
});
But I get an error message: Class Controller does not exist.
So it appears that Laravel is not including all the necessary files when the controller extends the BaseController.
If I use $controller::$action() it tells me I can't call a non-static function statically.
Any ideas for how to make this work?
You can auto register all controllers in one fell swoop:
Route::controller( Controller::detect() );
If you're using Laravel 4 (as your tag implies), you can't use Controller::detect() anymore. You'll have to manually register all the controllers you want to use.
After reading that Laravel doesn’t support this anymore, I came up with this solution:
$uri = $_SERVER['REQUEST_URI'];
$results = array();
preg_match('#^\/(\w+)?\/?(\w+)?\/?(\w+)?\/?#', $_SERVER['REQUEST_URI'], $results);
// set the default controller to landing
$controller = (empty($results[1])) ? 'landing' : $results[1];
// set the default method to index
$method = (empty($results[2])) ? 'index' : $results[2];
Route::get('{controller?}/{action?}/{id?}', $controller . '#' . $method);
// now we just need to catch and process the error if no controller#method exists.

Two or more loaded controllers with the same name "admin" are not working in HMVC in CodeIgniter

The structure looks like this:
modules
admin
controllers/
admin.php
models/
admin_model.php
views/
admin/
index.php
categories/
controllers/
admin.php
categories.php
models/
categories_model.php
views/
admin/
index.php
menu.php
frontpage.php
posts/
controllers/
admin.php
posts.php
models/
posts_model.php
views/
admin/
index.php
menu.php
frontpage.php
The admin controller looks like:
class Admin extends Backend_Controller {
function __construct()
{
parent::__construct();
$this->load->model('categories_model');
}
public function index()
{
// index stuff
}
public function _menu()
{
$this->load->view('categories/admin/menu');
}
}
And when I am calling it from another module view like this:
<?php echo Modules::run('categories/admin/_menu'); ?>
it doesn't work ;(
However this works:
<?php echo Modules::run('categories/categories'); ?>
So my problem is how to load the controller with a name admin and not the name as the module's name and the method "menu"
Any idea how could I make it work in CodeIgniter?
EDIT:
I have found out that if I change my controller name from "admin" to something else e.g. "blablacontroller" it magically starts working.
I have already another module called "admin" so could this be a problem?
If I'm right, you are using Modular Extensions - HMVC. So I've based my answer on the following script : https://bitbucket.org/wiredesignz/codeigniter-modular-extensions-hmvc/src/868e97533562e910d8263af22750985d57004baa/third_party/MX/Modules.php?at=default.
This will only work if you are using PHP 5.3 or above.
Declare every Admin classes inside a namepsace (admin, categories, post) by adding namespace admin; before the class keywork.
Create a child class of *third_party/MX/Modules.php* and extend the run and load methods. If extending is not possible, you'll have to replace the methods :/
This is not the whole thing, but those simples string operations should be in the load method before the existing logic.
A module should be run with the following syntax : *Modules::run('categories\admin/_menu');*
$module = 'categories\admin';
if (strpos($module, '\\') !== false)
{
// Extract namespace
$ns = explode('\\', $module);
// Get the top level namespace to locate the controller
$top_level_ns = reset($ns); //
// Remove namespace from module
$module = array_pop($ns);
// Class location
$path = APPPATH . $top_level_ns . '/controllers/' . $module;
// It's better to extend the load_file method
include_once $path . EXT;
// Full class name with namespace, we use explode on $module in case of sub folders
$controller = '\\' . implode('\\', $ns) . '\\' . ucfirst(end(explode('/', $module))) . CI::$APP->config->item('controller_suffix');
// create and register the new controller
$alias = strtolower($controller);
self::$registry[$alias] = new $controller($params);
var_dump('Full class name: ' . $controller, 'Class path: ' . $path);
}
In PHP you can only have a single class declared with a name. Eg. you can only have a single Admin class unless the classes are in different namespaces. What might be happening is:
admin/controllers/admin.php is declared then later in your scripts execution categories/controllers/admin.php is attempted to be declared and throws an error as admin/controllers/admin.php already exists and which Admin class should it use if some code somewhere says new Admin().
If both classes are needed then one will need to renamed, or the code to be restructured so that only one of them is declared in a single execution cycle. I am not too sure if the HMVC stuff your using will allow namespaces, but you can look at extending it that way. If not maybe rename both admin classes to something a bit more specific.

codeigniter hmvc routes not working properly

I installed HMVC by wiredesignz but the routes from application/modules/xxx/config/routes.php didn't get recognized at all.
Here is an example:
In application/modules/pages/config/routes.php I have:
$route['pages/admin/(:any)'] = 'admin/$1';
$route['pages/admin'] = 'admin';
If I type the URL, domain.com/admin/pages/create it is not working, the CI 404 Page not found appears.
If I move the routes to application/config/routes.php it works just fine.
How do I make it work without putting all the admin routes in main routes.php?
I searched the web for over 4 hours but found no answers regarding this problem. I already checked if routes.php from modules is loading and is working just fine, but any routes I put inside won't work.
I found a way of making the routes from modules working just fine, I don't know if is the ideal solution but works fine so far:
open your application/config/routes.php and underneath $route['404_override'] = ''; add the following code:
$modules_path = APPPATH.'modules/';
$modules = scandir($modules_path);
foreach($modules as $module)
{
if($module === '.' || $module === '..') continue;
if(is_dir($modules_path) . '/' . $module)
{
$routes_path = $modules_path . $module . '/config/routes.php';
if(file_exists($routes_path))
{
require($routes_path);
}
else
{
continue;
}
}
}
the following solution works fine even if config folder or routes.php is missing from your module folder
Here's the thing: the module's routes.php only gets loaded when that module is "invoked", otherwise CI would have to load all route configurations from all modules in order to process each request (which does not happen).
You'll have to use your main application's routes.php to get this to work. You aren't using the pages segment in your URL, therefore the routing for that module never gets loaded.
I know that's what you wanted to avoid, but unfortunately it's not possible unless you want to get "hacky".
Here's the routing I use to map requests for admin/module to module/admin, maybe you can use it:
// application/config/routes.php
$route['admin'] = "dashboard/admin"; // dashboard is a module
$route['admin/([a-zA-Z_-]+)/(:any)'] = "$1/admin/$2";
$route['admin/([a-zA-Z_-]+)'] = "$1/admin/index";
$route['(:any)/admin'] = "admin/$1";
You just need this https://gist.github.com/Kristories/5227732.
Copy MY_Router.php into application/core/

Resources