25

im writing a fairly simple website for a school ... this website has news , articles , video clips ... etc

the way it works is in the home page we present visitor with some lessons like

>math 
>geography 
>chemistry 

user selects 1 on these and website contents changes based on the user selection

for example if user selects math he will see news , article , videos about math and so on ... right now this is what im doing (pleas ignore syntax errors)

Route::group(['prefix'=>'math'], function () {
    Route::get('/news', 'NewsController@index')->name('news_index');
    Route::get('/article', 'ArticleController@index')->name('article_index');
});

Route::group(['prefix'=>'geography'], function () {
    Route::get('/news', 'NewsController@index')->name('news_index');
    Route::get('/article', 'ArticleController@index')->name('article_index');
});

Route::group(['prefix'=>'chemistry'], function () {
    Route::get('/news', 'NewsController@index')->name('news_index');
    Route::get('/article', 'ArticleController@index')->name('article_index');
});

basically repeating all links for each prefix .... but as the links grow it will become more and more unmanageable ... is there any better way to do this ? something like

Route::group(['prefix'=>['chemistry','math' , 'geography' ], function () {
    Route::get('/news', 'NewsController@index')->name('news_index');
    Route::get('/article', 'ArticleController@index')->name('article_index');
});

------------------------- update -------------

i've tried this

$myroutes =  function () {
    Route::get('/news', 'NewsController@index')->name('news_index');
    Route::get('/article', 'ArticleController@index')->name('article_index');
};

Route::group(['prefix' => 'chemistry'], $myroutes);
Route::group(['prefix' => 'math'], $myroutes);
Route::group(['prefix' => 'geography'], $myroutes);

and it works fine , the problem is the last prefix gets attached to all the internal links

for example if i click on math

my links will be

site.com/math/news

but all the links on the loaded page like

<a href="{{route('article_index')"> link to article </a>

look like

site.com/geography/article

basically link get the last mentioned prefix regardless of currently selected one

Peon
  • 7,902
  • 7
  • 59
  • 100
hretic
  • 999
  • 9
  • 36
  • 78

8 Answers8

23

Why not do it this way:

$subjects = [
    'chemistry', 'geography', 'math'
];

foreach ($subjects as $subject) {
    Route::prefix($subject)->group(function () {
        Route::get('news', 'NewsController@index')->name('news_index');
        Route::get('article', 'ArticleController@index')->name('article_index');
    });
}

I know this is an elementary way do to it. Yet you can easily add subjects, it is clear and effortless to understand.

Update

As pointed in the comments it could be convenient to name the route as per subject, here is how to do this:

$subjects = [
    'chemistry', 'geography', 'math'
];

foreach ($subjects as $subject) {
    Route::prefix($subject)->group(function () use ($subject) {
        Route::get('news', 'NewsController@index')->name("{$subject}_news_index");
        Route::get('article', 'ArticleController@index')->name("{$subject}_article_index");
    });
}
louisfischer
  • 1,968
  • 2
  • 20
  • 38
  • 1
    This is a great answer, but the name of the route is the same for every subject, you won't be able to call the correct route when rendering/calling the route with `{{ route( 'news_index`) }} (you want news for which subject?) in your template (or anywhere else within your code) . Maybe add a discriminator in front of the route name like so `->name( $subject . '.news_index' );` This will allow calling `{{ route( 'math.news_index' ) }}` – bynicolas Oct 14 '18 at 20:05
  • 1
    You are completely right @bynicolas. I did not take care of this in my answer because it was not a requirement of the question. It is good to point this out though. As you said it is very convenient to generate URLs by their name, whether it is from a Blade template or in an API resource class. – louisfischer Nov 03 '18 at 17:51
  • @bynicolas By the way, it is not possible to append the the $subject to the route parameter as the variable is not available once in the group expression. Try it. – Biniyam Asnake Feb 09 '20 at 03:33
  • Unfortunately, this is the only way. Otherwise it is not a good way – Hossein Piri Nov 24 '20 at 12:27
  • @HosseinPiri I don't understand what you mean and why the downvote. Can you elaborate please? – louisfischer Nov 30 '20 at 10:18
12

I think it's better to do:

Route::get('/news/{group}', 'NewsController@index')->name('news_index')->where('group', 'math|geography|chemistry');

And then just put condition on the controller function whether it is geography/math/chemistry/etc.

Don't you think?

Tiago Gouvêa
  • 15,036
  • 4
  • 75
  • 81
Wreigh
  • 3,215
  • 16
  • 37
  • 5
    In this case, it could be good to add some constraints like this: `Route::get('/news/{group}', 'NewsController@index')->name('news_index')->where('group', 'math|geography|chemistry');` – louisfischer Aug 30 '17 at 11:36
10

You can try following:

$myroutes =  function () {
    Route::get('/news', 'NewsController@index')->name('news_index');
    Route::get('/article', 'ArticleController@index')->name('article_index');
};

Route::group(['prefix' => 'chemistry'], $myroutes);
Route::group(['prefix' => 'math'], $myroutes);
Route::group(['prefix' => 'geography'], $myroutes);

Use as following:

 {!!URL::to('chemistry/news')!!}
 {!!URL::to('geography/news')!!}
 {!!URL::to('math/news')!!}
Chin Leung
  • 14,621
  • 3
  • 34
  • 58
Govind Samrow
  • 9,981
  • 13
  • 53
  • 90
  • 1
    it's very good but all my links change to last prefix (geography in this example ) ... pleas see the update on my question – hretic Aug 22 '17 at 23:01
  • Your doing it wrong way , its all ways return last because you are calling the via name because the name is same for all that why it return last one. I've updated my answer try this – Govind Samrow Aug 23 '17 at 05:12
5

You could try to use the as option within your groups to tell the Router to prepend a string to every route name within that group. To do so try the following:

Route::group(['prefix' => 'chemistry', 'as' => 'chemistry.'], $myroutes);
Route::group(['prefix' => 'math', 'as' => 'math.'], $myroutes);
Route::group(['prefix' => 'geography', 'as' => 'geography.'], $myroutes);

So what you will be able to do should be:

<a href="{{route('chemistry.article_index')}}"> link to article </a>
<a href="{{route('math.article_index')}}"> link to article </a>
<a href="{{route('geography.article_index'}})"> link to article </a>

Hope it helps.

Desh901
  • 2,633
  • 17
  • 24
  • BTW, i agree with @Wreigh about the structure. But you should still be able to do that as i described above. – Desh901 Aug 24 '17 at 13:58
4

There are several good answers here already, it is probably just a matter of personal preference or deeper project specifics which one suits. Here's another option for the pile.

I am not sure why @Shams answer was downvoted, it seems like the cleanest approach to me - but only if the prefixes are constrained so that only valid subjects are accepted. Something like:

// Only 1 place to update if you add subjects
$subjectRegex = 'math|geography|chemistry';

// Only 1 route per 'group'
Route::get('{subject}/news', 'NewsController@index')->name('news_index')->where('subject', $subjectRegex);
Route::get('{subject}/article', 'ArticleController@index')->name('article_index')->where('subject', $subjectRegex);

As a bonus you have $subject available in your Controller methods, which seems like it might be useful, for example you can use it to generate routes within the current subject:

route('article_index', ['subject' => $subject])
Don't Panic
  • 13,965
  • 5
  • 32
  • 51
4

You can wildcard the route group and specify the preferred prefixes in your RouteServiceProvider

routes.php

Route::group(['prefix'=>'{slug}'],function (){
 Route::get('/news', 'NewsController@index')->name('news_index');
 Route::get('/article', 'ArticleController@index')->name('article_index');
});

RouteServiceProvider boot method

Route::bind('slug',function ($name){
   $prefix = ["math","chemistry","geography"];
    if(!in_array($name,$prefix))
     {
        //handle wrong prefixes
        throw new \Exception("Something went wrong");
     }
 });

use named route

{{route('news_index',['slug'=>'math'])}}

Norris Oduro
  • 1,019
  • 8
  • 17
2

Just for Curiosity sake I attempted optional parameter on prefix route grouping in laravel and it worked. Check it out:

Route::group(['prefix' => '{subject?}', 'as'=> 'subject.', where' => ['subject' => 'math|english|geo']],function (){
    Route::get('news', function (){
       return 'This is the news';
    })->name('news');
});

Pretty sure this is the solution you dreamt of.

Well before this would be the correct answer, there might be a little issue. Calling route('subject.news') will give http://example.com/news. To make it happy, you have to pass the optional parameter to route() function i.e. route('subject.news','math'); for example; then you'll have http://example.com/math/news.

PS: This was done on Laravel 5.4.30 PHP 7.1

  • thanx , i'll try this but i have to say since all the routes are using same controllers/views the argument in `route('subject.news','math')` should be dynamically set to the current one ... i guess i can use a middleware to read the current prefix from route and make it available in the view – hretic Aug 25 '17 at 17:05
  • Just give it a try... the only time you will have to set that route is if you want to generate a url for any of them...so you can specify which one you want to generate... – Oluwatobi Samuel Omisakin Aug 25 '17 at 20:14
0

Instead of grouping you can use route parameters

Route::get('/{prefix}/news', 'NewsController@index')->name('news_index');
Route::get('/{prefix}/article', 'ArticleController@index')->name('article_index');
Shams Reza
  • 1,087
  • 8
  • 10