How to create an optional global first URL segment in Laravel 8 - laravel

We have a website that has events each year, this year's events would be shown here:
example.com/events
Route::get('/events', [EventController::class, 'index']);
But we want to create an archive of last year's events which would be shown on the following URLs:
example.com/2021/events
example.com/2020/events
We are able to create the following route to the same method:
Route::get('{year}/events', [EventController::class, 'index']);
Or we can create a separate method for each archive, but it's showing the same page, different content.
Route::get('{year}/events', [EventController::class, 'yearIndex']);
It would be much cleaner if there was a global way to detect if '2021' or '2020' was present in segment 1 globally then tell Laravel to ignore segment 1 and use segment 2 as the new base for all routing. This would be much cleaner. How to do this and how to pass the global $year variable to the methods to filter the content correctly.
We want to use the same method for both the archive (2020, 2021) and present events.

Opinion based questions are usually closed. Before this happenes, I like to give you my opinion.
If the endpoint is different, not only the method, but the controller should be different. I would have created PastEventController and use index method with year parameter there. Of course, both index methods will probably be quite similar. So, extract the index to an action like IndexEvents, and than use it in both controller by passing the year. If the year null make current year in the action like.
class IndexEvents
{
public function __invoke($year = null)
{
return Event::where('year', $year ?? date("Y"))->get();
}
}
EventController:
public function index()
{
return (new IndexEvents)();
}
PastEventController:
public function index($year)
{
return (new IndexEvents)($year);
}

Pretty sure you can do this
routes
Route::get('/events', [EventController::class, 'index']);
Route::get('{year}/events', [EventController::class, 'index']);
then index method
public function index( Request $request, $year = false ) {
$selectedYear = $year ? $year : now()->format('Y');
return Event::whereYear('created_at', $selectedYear)->paginate(20);
}

Related

How do I pass a value in my Route to the Controller to be used in the View in Laravel?

I have 2 entities called Match and Roster.
My Match routes are like this
http://localhost:8888/app/public/matches (index)
http://localhost:8888/app/public/matches/14 (show)
In order to view/create the teams for each specific match I added the routes for the match roster like this:
Route::get('/matches/'.'{id}'.'/roster/', [App\Http\Controllers\RosterController::class, 'index']);
Now I need that {id} i have in my URL to pass it to the Controller here:
public function index()
{
return view('roster.index');
}
I need that for a couple of things. First I need to do a search on the Roster table filtering by a column with that value, so I can display only the players that belong to that match.
Second, I need to pass it on to the view so I can use it on my store and update forms. I want to add or remove players from the roster from that same index view.
How can I do that?
#1 You can get the route parameter defined on ur routes via request()->route('parameter_name').
public function index()
{
// get {id} from the route (/matches/{id}/roster)
$id = request()->route('id');
}
#2 You can pass the data object via using return view(file_name, object)
public function index()
{
// get {id} from the route (/matches/{id}/roster)
$id = request()->route('id');
// query what u want to show
// dunno ur models specific things, so just simple example.
$rosters = Roster::where('match_id', '=', $id);
// return view & data
return view('roster.index', $rosters);
}
#3 It can be done not only index but also others (create, store, edit, update)
In addition, STRONGLY RECOMMEND learn Official Tutorial with simple example first.
Like a Blog, Board, etc..
You need to know essentials to build Laravel App.
Most of the time, I prefer named routes.
Route::get('{bundle}/edit', [BundleController::class, 'edit'])->name('bundle.edit');
In controller
public function edit(Bundle $bundle): Response
{
// do your magic here
}
You can call the route by,
route('bundle.edit', $bundle);

Laravel route difference between {id} vs {tag}

I am new in Laravel pardon me if question is silly. I have seen a doc where they used
For get request
Route::get("tags/{id}","TagsController#show");
For put request
Route::put("tags/{tag}","TagsController#update");
What is the difference and benefit between this ? I understood 1st one, confusion on put route.
There’s no real difference as it’s just a parameter name, but you’d need some way to differential parameters if you had more than one in a route, i.e. a nested resource controller:
Route::get('articles/{article}/comments/{comment}', 'ArticleCommentController#show');
Obviously you couldn’t use just {id} for both the article and comment parameters. For this reason, it’s best to use the “slug” version of a model for a parameter name, even if there’s just one in your route:
Route::get('articles/{article}', 'ArticleController#show');
You can also use route model binding. If you add a type-hint to your controller action for the parameter name, Laravel will attempt to look up an instance of the given class with the primary key in the URL.
Given the route in the second code example, if you had a controller that looked like this…
class ArticleController extends Controller
{
public function show(Article $article)
{
//
}
}
…and you requested /articles/123, then Laravel would attempt to look for an Article instance with the primary key of 123.
Route model binding is great as it removes a lot of find / findOrFail method calls in your controller. In most instances, you can reduce your controller actions to be one-liners:
class ArticleController extends Controller
{
public function show(Article $article)
{
return view('article.show', compact('article'));
}
}
Generally there's no practical difference unless you define a custom binding for a route parameter. Typically these bindings are defined in RouteServiceProvider as shown in the example in the docs
public function boot()
{
parent::boot();
Route::model('tag', App\Tag::class);
}
When you bind tag this way then your controller action can use the variable via model resultion:
public function update(Tag $tag) {
// $tag is resolved based on the identifier passed in the url
}
Usually models are automatically bound so doing it manually doesn't really need to be done however you can customise resolution logic if you do it manually
Normal way
Route::get("tags/{id}","TagsController#show");
function($id)
{
$tag = Tag::find($id);
dd($tag); // tag
}
With route model bindings
Route::put("tags/{tag}","TagsController#update");
function(Tag $tag) // Tag model binding
{
dd($tag); // tags
}
ref link https://laravel.com/docs/5.8/routing#implicit-binding
It's just a convention. You can call it all you want. Usually, and {id} refers to the id in your table. A tag, or similarly, a slug, is a string value. A tag could be 'entertainment' for video categories, while 'my-trip-to-spain' is a slug for the description of a video.
You have to chose the words what you are comfortable with. The value will be used to find in your database what record is needed to show the correct request in the view. Likewise you can use video/view/{id}/{slug} or any combination thereof.
Just make sure your URLs don't get too long. Because search engines won't show your website nicely in search results if you do. Find the balance between the unambiguous (for your database) and logic (for your visitors).
Check this out: Route model bindings
Use id, Laravel will get the id from route, and it will be the tag's id, it is integer.
function show($id) {
$tag = Tag::find($id);
}
Use tag, Laravel automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name.
In URL, your tag parameter is integer, however in your controller action $tag will be a model object:
function action(Tag $tag) {
$tag->name;
}
So you don't need to get the $tag by eloquent in your controller action. You just need to specify it is From model Tag $tag
It will do it automatically.

Laravel Routing - Best way to differentiate routes with same format

Say I have a database of items, each belonging to a country, county and a city. I want to have routes to list all of the items within a singular country, county or a city. Each country/county/city has a slug, for example france for France that is to be used in URL.
I want all the routes to have the same format:
/items-in-{slug}, so for example /items-in-france or /items-in-paris.
However, the slug can be a slug of one of multiple Models. What is the best set up for this sort of situation? I can think of 3 main options:
A single route that will catch all matching URLs, which will run a specialised RoutingController or similar, which will then in turn check which Model slug represents and propogate to the correct controller method (for example, viewInCountry($slug) or viewInCity($slug))
One route for each type of Model, and putting restrictions on each route that would only accept one of the existing slugs (i.e. fetch all of the slugs and generate a regex that will only accept one of the existing slugs)
Fetch all Models (countries/cities/counties) and generate a Route for each one
All options seem a little hacky and I am wondering if there is a more elegant solution to this.
I would go for the second option, except don't use a regex to accept existing slugs. Instead you could write your own model route binding resolution logic as mentioned here under 'Customizing The Resolution Logic'. Something like this could probably do the trick:
// Put this in your RouteServiceProvider.php
public function boot()
{
parent::boot();
Route::bind('slug', function ($value) {
$country = App\Country::where('slug', $value)->first();
if ($country !== null) {
return $country;
}
$city = App\City::where('slug', $value)->first();
if ($city !== null) {
return $city;
}
// Repeat for each model.
// 404 in case no model has been matched.
abort(404).
});
}
Alternatively if you are willing to adjust the url a bit, then you could create a route and a controller per model. That would require you to have urls like /items/france or items/paris etc.

Implicit route, with firstOrCreate instead of findOrFail

Is it possible to create an implicit route where if it is not found the thing is created? I am using Laravel 5.5.13.
For instance this is my implicit route:
Route::post('thumbs/{player}', 'ThumbController#store');
And in my controller it is this:
public function store(Request $request, Player $player)
{
$thumb = new Thumb($request->all());
$player->thumbs()->save($thumb);
return response()->json($thumb, 201);
}
So now if I go to the endpoint of ..../api/thumb/1 it will create a thumb related with Player of id 1. However instead of a id number I wanted to provide it a string like this:
..../api/thumb/PLAYER_NAME
So example of ..../api/thumb/Blagoh, then my endpoint should first find if a player exists by name "Blagoh", and if it doesn't then it should create it. I couldn't figure this one out.
What you should do is adding into boot method of RouteServiceProvider something like this:
Route::bind('player', function ($value) {
if ($player = \App\Player::find($value)) {
return $player;
}
return Player::create(['name' => $value]);
});
It's called explicit binding and you can update logic as showed above. Reference - Route model binding

Laravel Backpack - getting current record from crud controller

In my crud controller I am trying to get the name of the person who is currently being edited.
so
http://192.168.10.10/admin/people/93/edit
In the people crud controller
public function setup() {
dd(\App\Models\People::get()->first()->name)
}
This returns the first person not the person currently being edited.
How do I return the current person (with an id of 93 in this example)
Ok, So since you use backpack look into CrudController to see how the method looks:
public function edit($id)
{
$this->crud->hasAccessOrFail('update');
$this->data['entry'] = $this->crud->getEntry($id);
$this->data['crud'] = $this->crud;
$this->data['fields'] = $this->crud->getUpdateFields($id);
$this->data['id'] = $id;
return view('crud::edit', $this->data);
}
So now you can overwrite the edit function and change whatever you want. You can even create a custom edit page if you so wish.
Setup on the other hand is usually used to add things like
$this->crud->addClause(...);
Or you can even get the entire constructor and put it in the setup method because setup call looks like this:
public function __construct()
{
// call the setup function inside this closure to also have the request there
// this way, developers can use things stored in session (auth variables, etc)
$this->middleware(function ($request, $next) {
$this->setup();
return $next($request);
});
}
So you could do something like \Auth::user()->id;
Also it's normal to work like this. If you only use pure laravel you will only have access to the current id in the routes that you set accordingly.
Rahman said about find($id) method. If you want to abort 404 exception just use method findOrFail($id). In my opinion it's better way, because find($id)->name can throw
"Trying to get property of non-object error ..."
findOrFail($id) first fetch user with specified ID. If doesn't exists just throw 404, not 500.
The best answer is:
public function edit($id)
{
return \App\Models\People::findOrFail($id);
}
Good luck.
you need person against id, try below
public function setup($id) {
dd(\App\Models\People::find($id)->name);
}

Resources