I am hoping that someone can help me out with dynamic routing for urls that can have multiple segments. I've been doing some searching all over the web, but nothing I find helps me out with my specific situation.
A little background ... several years ago, I developed a CMS package for custom client websites that was built on CodeIgniter. This CMS package has several modules (Pages, Blog, Calendar, Inquiries, etc). For the Pages module, I was caching the routes to a "custom routes" config file that associated the full route for the page (including parent, grandparent, etc) with the ID of the page. I did this so that I didn't have to do a database lookup to find the page to display.
I am currently working on rebuilding this CMS package using Laravel (5.1) [while I'm learning Laravel]. I need to figure out the routing situation before I can move on with my Pages module in the new version of the package.
I know that I can do something like ...
// routes.php
Route::get('{slug}', ['uses' => 'PageController#view']);
// PageController.php
class PageController extends Controller
{
public function view($slug)
{
// do a query to get the page by the slug
// display the view
}
}
And this would work if I didn't allow nested pages, but I do. And I only enforce uniqueness of the slug based on the parent. So there could be more than one page with a slug of fargo ...
locations/fargo
staff/fargo
As with the package that I built using CodeIgniter, I would like to be able to avoid extra database lookups to find the correct page to display.
My initial thought was to create a config file that would have the dynamic routes like I did with the old version of the system. The routes will only change at specific times (when page is created, when slug is modified, when parent is changed), so "caching" them would work great. But I'm still new to Laravel, so I'm not sure what the best way to go about this would be.
I did manage to figure out that the following routes work. But is this the best way to set this up?
Route::get('about/foobar', function(){
return App::make('\App\Http\Controllers\PageController')->callAction('view', [123]);
});
Route::get('foobar', function(){
return App::make('\App\Http\Controllers\PageController')->callAction('view', [345]);
});
Basically, I would like to bind a specific route to a specific page ID when the page is created (or when the slug or parent are changed).
Am I just overcomplicating things?
Any help or direction regarding this would be greatly appreciated.
Thanks!
The way I handle this is to use two routes, one for the home page (which generally contains more complex logic like news, pick up articles, banners, etc), and a catch all for any other page.
Routes
// Home page
Route::get('/', [
'as' => 'home',
'uses' => 'PageController#index'
]);
// Catch all page controller (place at the very bottom)
Route::get('{slug}', [
'uses' => 'PageController#getPage'
])->where('slug', '([A-Za-z0-9\-\/]+)');
The important part to note in the above is the ->where() method chained on the end of the route. This allows you to declare regex pattern matching for the route parameters. In this case I am allowing alphanumeric characters, hyphens and forward slashes for the {slug} parameter.
This will match slugs like
test-page
test-page/sub-page
another-page/sub-page
PageController Methods
public function index()
{
$page = Page::where('route', '/')->where('active', 1)->first();
return view($page->template)
->with('page', $page);
}
public function getPage($slug = null)
{
$page = Page::where('route', $slug)->where('active', 1);
$page = $page->firstOrFail();
return view($page->template)->with('page', $page);
}
I keep the template file information in the database, as I allow users to create templates in the content management system.
The response from the query on the database is then passed to the view where it can be output to the metadata, page, breadcrumbs, etc.
I was also looking for the same answer that is about creating a dynamic routing in laravel i come up with this:
In routes.php
<?php
/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/
$str=Request::url();
$del="/public/";
$pos=strpos($str, $del);
$important1=substr($str, $pos+strlen($del), strlen($str)-1);
$important=ucfirst($important1);
$asif=explode("/", $important);
$asif1=explode("/", $important1);
//echo $important;
$post=$asif1[0];
$post1=$asif1[1];
if(isset($asif1[2]))
{
$post2=$asif1[2];
}
if(!(isset($post2)))
{
Route::match(array('GET','POST'),$important1, $asif[0].'Controller#'.$asif[1]);
}
if(isset($post2))
{ Route::match(array('GET','POST'),$post.'/'.$post1.'/{id}',$asif[0].'Controller#'.$asif[1]);
}
Route::get('/', function () {
return view('welcome');
});
Ex
if you have PostController with method hello in laravel. You can use this url http://localhost/shortproject/public/post/hello. Where shortproject is your project folder name.
Related
im building a website using symfony 4. The website's pages are dynamically created in an admin section.
How can i create an exception or requirements that the rout rendering the custom pages should only be used for custom page and will not affect routs for login and register? here is my rout
/**
* #Route("/{page}", name="subpages", requirements={"page"="\d+"})
*/
public function subpages(Request $request): Response
{
$page = $request->get('page');
$content = $this->getDoctrine()->getRepository(Pages::class)->find($page);
return $this->render('public_pages/subpage.html.twig', [
'controller_name' => 'home',
'content' => $content
]);
}
this case, i want to only use that rout if the {page} is not /login or /register
Thank you in advance
The order of your controller functions matter, you should put /login and /register before your subpage function.
However, sometimes it might not be possible due to other functions being in different controllers files with different names etc making ordering difficult..
You can use Regex in the requirements. So in your case you could do this:
#Route("/{page}", name="subpages", requirements={"page"="^(?!\blogin\b|\bregister\b).+"})
This will match any route except login or register. You can add more into the regex with a word boundary, eg \bcontact\b.
This might not be the best approach if you have lots of routes however as it can be hard to keep track. Instead you could also consider having the route like "/pages/{page}"
Symfony 5.1 supports priority which makes this situation easier to deal with using the priority parameter in the annotation.
I would like to create an administrator interface for my Laravel project, which is completely separated from the user side.
For example, in Yii framework I can make a module and this will ensure full separation from the user side. Inside a module I can use separate folder structure etc.
This is really a broad question and one answer can't cover everything about best practice for admin controllers or back end management but there are some basic concepts for building an Admin Panel:
// Keep all of your admin routes inside something like this
Route::group(array('prefix'=> 'admin', 'before' => 'auth.admin'), function() {
// Show Dashboard (url: http://yoursite.com/admin)
Route::get('/', array('uses' => 'Admin\\DashBoardController#index', 'as' => 'admin.home'));
// Resource Controller for user management, nested so it needs to be relative
Route::resource('users', 'Admin\\UserController');
});
// Other routes (Non-Admin)
Route::get('login', array('uses' => 'AuthController#showLogin' 'as' => 'login'));
By using a prefix you may separate all admin routes whose url will be prefixed with admin so, if you have a users controller for user management in back end then it's url will be prefixed with admin, i.e. site.com/admin/users. Also using a before filter you may add an authentication for all admin controllers in one place, that means, to access all of your admin controllers user must be logged in and the filter could be something like this:
Route::filter('auth.admin', function($route, $request, $args){
// Check if the user is logged in, if not redirect to login url
if (Auth::guest()) return Redirect::guest('login');
// Check user type admin/general etc
if (Auth::user()->type != 'admin') return Redirect::to('/'); // home
});
For, CRUD (Create, Read, Update, Delete) use a resourceful controller, for example, the UserController in an example of resourceful route declaration.
Use repository classes (Repository Pattern) for decoupling of dependencies, read this article.
Always use a named route, i.e. array('as' => 'routename', 'uses' => 'SomeController#method'), this is an example of naming a route. Named routes are easy to refer, i.e. return Redirect::route('admin.home') will redirect to site.com/admin because we have used admin.home in as to assign the name for that route.
Keep admin controllers in a separate folder and use a namespace for example, Admin\\DashBoardController#index controller should be in app/controllers/admin and your DashBoardController controller should look like this:
<?php namespace Admin;
class DashBoardController extends \BaseController {
public function index()
{
//...
}
}
There are more but it's enough to start with, read articles online and must read the documentation.
If you are familiar with composer you can import in packages (aka modules)
There is a widely available module with multi level interface already called Sentry 2.0:
https://github.com/cartalyst/sentry
You could also make your own if needed if the one I propose is too complex.
There is even a "laravel-ready" version of sentry.
I use the same directory structure that you would like to use on most (if not all) my Laravel projects. Basically, I keep admin views and admin controllers separate from the front-end ones.
Examples:
Controllers:
app/controllers/admin/Admin*Name*Controller.php
app/controllers/site/*Name*Controller.php
Views:
app/views/admin/some_folder/index.blade.php
app/views/site/some_folder/index.blade.php
I would also suggest that you install this laravel project https://github.com/andrewelkins/Laravel-4-Bootstrap-Starter-Site which will give a very good starting on how to organise things in your laravel project. It also has the same folder structure you would like to use.
Good luck.
Laravel routing functionality allows you to name a resource and name a controller to go with it. I am new to Laravel and would like to know if anyone knows how to extend the resources method in the route class provided.
Basically say I have: (which works fine)
/invoices
But say I want:
/invoices/status/unpaid
How is this achievable?
To see the basics of what I am doing check:
http://laravel.com/docs/controllers#resource-controllers
Resource controllers tie you into a specific URLs, such as:
GET|POST /invoices
GET|PUT /invoices/{$id}
GET /invoices/create
and so on as documented.
Since, by convention, GET /invoices is used to list all invoices, you may want to add some filtering on that:
/invoices?status=unpaid - which you can then use in code
<?php
class InvoiceController extends BaseController {
public function index()
{
$status = Input::get('status');
// Continue with logic, pagination, etc
}
}
If you don't want to use filtering via a query string, in your case, you may be able to do something like:
// routes.php
Route::group(array('prefix' => 'invoice'), function()
{
Route::get('status/unpaid', 'InvoiceController#filter');
});
Route::resource('invoice', 'InvoiceController');
That might work as the order routes are created matter. The first route that matches will be the one used to fulfill the request.
I am new to Laravel 4 and am having a hard time grasping routes. I have a frontend to my site and a backend. All the stuff that happens on the backend I want to have displayed under example.com/dashboard/.... I also want to use resourceful controllers. What do I need to setup in routes.php to have it so I can always refer to my users controller but have it all happen under dashboard in the URL?
Example:
I link to users/edit/1 but in the URL looks like example.com/dashboard/users/edit/1. Dashboard should have an index page (so example.com/dashboard actually shows a page) but all other URLs are appended to that.
I think this is covered pretty well in the Larvel 4 Documentation.
Unless I'm misunderstanding, this should get you the desired results for your example case:
Route::get('dashboard', 'DashboardController#index');
Route::get('dashboard/users/edit/{id}', 'UsersController#edit');
etc.
// edit
Alternatively, using a Closure callback, you could do something like this:
Route::get('dashboard/users/{var1}/{var2?}', function($var1, $var2 = null)
{
$controller = new UsersController;
return $controller->{$var1}($var2);
});
Which wouldn't require you to specify each and every route. Or, as I mentioned below in comments, you could use a Resource Controller if it suits your needs.
There will be several high profile links for customers to focus on, for example:
Contact Us # domain.com/home/contact
About the Service # domain.com/home/service
Pricing # domain.com/home/pricing
How It Works # domain.com/home/how_it_works
Stuff like that. I would like to hide the home controller from the URL so the customer only sees /contact/, not /home/contact/. Same with /pricing/ not /home/pricing/
I know I can setup a controller or a route for each special page, but they will look the same except for content I want to pull from the database, and I would rather keep my code DRY.
I setup the following routes:
Route::get('/about_us', 'home#about_us');
Route::get('/featured_locations', 'home#featured_locations');
Which work well, but I am afraid of SEO trouble if I have duplicate content on the link with the controller in the URL. ( I don't plan on using both, but I have been known to do dumber things.)
So then made routes like these:
Route::get('/about_us', 'home#about_us');
Route::get('/home/about_us', function()
{
return Redirect::to('/about_us', 301);
});
Route::get('/featured_locations', 'home#featured_locations');
Route::get('/home/featured_locations', function()
{
return Redirect::to('/featured_locations', 301);
});
And now I have a redirect. It feels dumb, but it appears to be working the way I want. If I load the page at my shorter URL, it loads my content. If I try to visit the longer URL I get redirected.
It is only for about 8 or 9 special links, so I can easily manage the routes, but I feel there must be a smart way to do it.
Is this even an PHP problem, or is this an .htaccess / web.config problem?
What hell have I created with this redirection scheme. How do smart people do it? I have been searching for two hours but I cannot find a term to describe what I am doing.
Is there something built into laravel 4 that handles this?
UPDATE:
Here is my attempt to implement one of the answers. This is NOT working and I don't know what I am doing wrong.
application/routes.php
Route::controller('home');
Route::controller('Home_Controller', '/');
(you can see the edit history if you really want to look at some broken code)
And now domain.com/AboutYou and domain.com/aboutUs are returning 404. But the domain.com/home/AboutYou and domain.com/home/aboutUs are still returning as they should.
FINAL EDIT
I copied an idea from the PongoCMS routes.php (which is based on Laravel 3) and I see they used filters to get any URI segment and try to create a CMS page.
See my answer below using route filters. This new way doesn't require that I register every special route (good) but does give up redirects to the canonical (bad)
Put this in routes.php:
Route::controller('HomeController', '/');
This is telling you HomeController to route to the root of the website. Then, from your HomeController you can access any of the functions from there. Just make sure you prefix it with the correct verb. And keep in mind that laravel follows PSR-0 and PSR-1 standards, so methods are camelCased. So you'll have something like:
domain.com/aboutUs
In the HomeController:
<?php
class HomeController extends BaseController
{
public function getAboutUs()
{
return View::make('home.aboutus');
}
}
I used routes.php and filters to do it. I copied the idea from the nice looking PongoCMS
https://github.com/redbaron76/PongoCMS-Laravel-cms-bundle/blob/master/routes.php
application/routes.php
// automatically route all the items in the home controller
Route::controller('home');
// this is my last route, so it is a catch all. filter it
Route::get('(.*)', array('as' => 'layouts.locations', 'before' => 'checkWithHome', function() {}));
Route::filter('checkWithHome', function()
{
// if the view isn't a route already, then see if it is a view on the
// home controller. If not, then 404
$response = Controller::call('home#' . URI::segment(1));
if ( ! $response )
{
//didn't find it
return Response::error('404');
}
else
{
return $response;
}
});
They main problem I see is that the filter basically loads all the successful pages twice. I didn't see a method in the documentation that would detect if a page exists. I could probably write a library to do it.
Of course, with this final version, if I did find something I can just dump it on the page and stop processing the route. This way I only load all the resources once.
applicaiton/controllers/home.php
public function get_aboutUs()
{
$this->view_data['page_title'] = 'About Us';
$this->view_data['page_content'] = 'About Us';
$this->layout->nest('content', 'home.simplepage', $this->view_data);
}
public function get_featured_locations()
{
$this->view_data['page_title'] = 'Featured Locations';
$this->view_data['page_content'] = 'Featured properties shown here in a pretty row';
$this->layout->nest('content', 'home.simplepage', $this->view_data);
}
public function get_AboutYou()
{
//works when I return a view as use a layout
return View::make('home.index');
}