Laravel 5 Route binding and Hashid - php

I am using Hashid to hide the id of a resource in Laravel 5.
Here is the route bind in the routes file:
Route::bind('schedule', function($value, $route)
{
$hashids = new Hashids\Hashids(env('APP_KEY'),8);
if( isset($hashids->decode($value)[0]) )
{
$id = $hashids->decode($value)[0];
return App\Schedule::findOrFail($id);
}
App::abort(404);
});
And in the model:
public function getRouteKey()
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),8);
return $hashids->encode($this->getKey());
}
Now this works fine the resource displays perfectly and the ID is hashed.
BUT when I go to my create route, it 404's - if I remove App::abort(404) the create route goes to the resource 'show' view without any data...
Here is the Create route:
Route::get('schedules/create', [
'uses' => 'SchedulesController#create',
'as' => 'schedules.create'
]);
The Show route:
Route::get('schedules/{schedule}', [
'uses' => 'Schedules Controller#show',
'as' => 'schedules.show'
]);
I am also binding the model to the route:
Route::model('schedule', 'App\Schedule');
Any ideas why my create view is not showing correctly? The index view displays fine.

Turns out to solve this, I had to rearrange my crud routes.
Create needed to come before the Show route...

There's a package that does exactly what you want to do: https://github.com/balping/laravel-hashslug
Also note, that it's not a good idea to use APP_KEY as salt because it can be exposed.
Using the above package all you need to do is add a trait and typehint in controller:
class Post extends Model {
use HasHashSlug;
}
// routes/web.php
Route::resource('/posts', 'PostController');
// app/Http/Controllers/PostController.php
public function show(Post $post){
return view('post.show', compact('post'));
}

Related

How to show id in Resource Routes url?

Update:
This line of code in the frontend was the culprit:
<inertia-link v-if="options.edit" :href="'/admin/gallery/edit/1'">
I had to change it to:
<inertia-link v-if="options.edit" :href="'/admin/gallery/1/edit'">
to make it comply with the laravel resource format for edit, provided by #Babak.
Original Post:
How would I transform this route in web.php:
Route::get('/admin/gallery/edit/{id}', function ($id) {
$data = Gallery::find($id);
return inertia('backend/cms-gallery-edit', ['data' => $data]);
});
to a resource route with its resource controller function:
Route::resource('/admin/gallery', GalleryController::class);
GalleryController.php:
public function edit($id)
{
$data = Gallery::find($id);
// assign id to end of route
return inertia('backend/cms-gallery-edit', ['data' => $data]);
}
Edit:
I've tried both approaches of #Babak's answer, which work for index and create routes but the edit route still throws a 404. It is the only route encompassing an id.
web.php:
Route::resource('/admin/gallery', GalleryController::class)->only('index', 'create', 'edit');
GalleryController.php:
public function edit($gallery)
{
$data = Gallery::find($gallery);
return inertia('backend/cms-gallery-edit', ['data' => $data]);
}
Inertia passes the id from the frontend via href:
<inertia-link v-if="options.edit" :href="'/admin/gallery/edit/1'">
Browser shows:
GET http://127.0.0.1:8000/admin/gallery/edit/1 404 (Not Found)
There is a fixed structure for laravel resource route method, you can see full list here. For edit page, it will generate something like '/admin/gallery/{gallery}/edit'
You can write it like below:
In your web.php file:
Route::resource('/admin/gallery', GalleryController::class)->only('edit');
And in your controller, name of the resource must be the same as your function's parameter.
public function edit($gallery)
{
$data = Gallery::find($gallery);
// assign id to end of route
return inertia('backend/cms-gallery-edit', ['data' => $data]);
}
Or, you can customize it using parameter method. Refer to here
Route::resource('/admin/gallery', GalleryController::class)->only('edit')->parameters([
'gallery' => 'id'
]);
And your controller
public function edit($id)
{
$data = Gallery::find($id);
// assign id to end of route
return inertia('backend/cms-gallery-edit', ['data' => $data]);
}

Laravel 7 Binding Routes

I have this route declared on laravel:
Route::get('pages/{page}/{slug}', 'Common\Pages\CustomPageController#show')
->middleware(['web', 'prerenderIfCrawler']);
This route works fine and works if you make requests to:
https://example.com/pages/1/test-page
https://example.com/pages/2/other-page
https://example.com/pages/3/url-test
The problem is that I need a more friendly url as well as.
https://example.com/test-page
https://example.com/other-page
https://example.com/url-test
I want remove the suffix called pages, The numbers for the pages will never change and will be static for each one.
I've tried to make static routes for each one but can't get it to work.
Route::get('other-page', array('as' => 'other-page', function() {
return App::make('Common\Pages\CustomPageController')->show(2);
}))->middleware(['web', 'prerenderIfCrawler']);
I would appreciate a little help.
You could always get the URL segment in the Controller and use that to know what page you are on. If you don't want to do that you could pass extra information in the 'action' to specify the page:
Route::middleware(['web', 'prerenderIfCrawler'])->group(function () {
Route::get('test-page', [
'uses' => 'Common\Pages\CustomPageController#show',
'page' => 'test-page',
]);
...
});
Then you can get this extra information in the Controller:
public function show(Request $request)
{
$page = $request->route()->getAction('page');
...
}
If you knew all the pages you can use a route parameter with a regex constraint to restrict it to only those page names:
Route::get('{page:slug}', ...)->where('page', 'test-page|other-page|...');
public function show(Page $page)
{
...
}
You could just make use of a wildcard to catch your routes like this:
Route::get('/{slug}', 'Common\Pages\CustomPageController#show')
->middleware(['web', 'prerenderIfCrawler']);
Then in your controller:
public function show($slug)
{
$page = Page::where('slug', $slug)->first();
// ...
}
Just be careful with where you place the route. It should be at the end of your routes otherwise it will catch all the request of your app.
// ...
// my other routes
// ...
Route::get('/{slug}', ...);
By the way, if you want to bind your page models using the slug attribute do this:
Route::get('/{page:slug}', 'Common\Pages\CustomPageController#show')->//...
^^^^^^^^^
Then in your controller:
public function show(Page $page)
{ ^^^^^^^^^^
// ...
}
Check this section of the docs.

Laravel Undefined variable: intent Facade\Ignition\Exceptions\ViewException

I tried looking for all the possible solutions none of it worked and this is very basic trying to send data from a controller to view in Laravel.
Paymentcontroller
public function payment() {
$plans =[
'Basic' => "Monthly"
];
$intent = $user->createSetupIntent();
return view('pages.subscription', compact('intent', 'plans'));
}
PageController
public function index(string $page)
{
if (view()->exists("pages.{$page}")) {
return view("pages.{$page}");
}
return abort(404);
}
View pages.subscription
<div>
{{ $intent }}
</div>
route
Route::get('{page}', ['as' => 'page.index', 'uses' => 'PageController#index']);
Route::get('/subscription', 'PaymentController#payment');
This makes the page work but doesn't display the data
Move Route::get('/subscription', 'PaymentController#payment'); before Route::get('{page}',.... (it should be your last route in the list).
Currently when you call /subscription endpoint you are calling PageController#index, but it doesn't contain logic of your PaymentController#payment and doesn't pass any data to view.

Laravel Displaying Specific Project Detail of User

I have a many-to-many pivot table (project_user) and am successful in getting all the projects of the authenticated user.
WriterController.php
public function writerProjects()
{
$projects = auth()->user()->projects;
dd($projects);
return view('writers.projects', compact('projects'));
}
web.php
Route::get('users/{user}/projects', ['as' => 'showProjects',
'uses' => 'WriterController#writerProjects']);
My question is how can I get the specific project's details? Here's my approach so far (it doesn't work though).
public function showWriterProjects($id)
{
$projects = auth()->user()->projects;
foreach($projects as $p)
{
dd($p->name);
}
return view('writers.projects.show', compact('projects'));
}
web.php for that
Route::get('users/{user}/projects/{project}', ['as' => 'showSingleProject',
'uses' => 'WriterController#showWriterProjects']);
What am I doing wrong?
Many-to-Many relations have been defined in User.php and Project.php, they seemed pretty obvious to post.
Since I'm not clear what is meant by "project's details", I'll go with project name.
Just imagine you only need 'name' attribute of 'projects'
Controller
public function showWriterProjects($id)
{
$project_names = auth()->user()->projects->map->name;
return view('writers.projects.show', compact('project_names'));
}

Laravel 5 Controller using variable arguments

I have a controller in Laravel 5.
I would like to write a controller function that accepts variable arguments.
For example,
public function show(Request $request, ...$id)
{
// handle multiple $id values here
}
The reason is that I have a url structure that has 'nested' models.
For instance:
\item\{$id}
\parent\{$parent_id}\item\{$id}
\grandparent\{$grandparent_id}\parent\{$parent_id}\item\{$id}
The routes are defined as:
Route::resource('item', 'ItemController');
Route::resource('parent.item', 'ParentController');
Route::resource('grandparent.parent.item', 'GrandparentController');
My desire is to write a single show() method as a trait that each controller can use.
Because of the structure of my database, it is possible.
But the UrlGenerator keeps throwing a UrlGenerationException when I try to use variable arguments. It seems like it doesn't understand this construct?
Ok, here's an idea for you that should get you on the right path:
For the various resource routes you defined, re-declare them to exclude the 'show' action, and define a separate GET route to map the routes you are trying to centralise.
app/Http/routes.php:
Route::resource('item', 'ItemController', ['except' => ['show']]);
Route::get('item/{item}', ['uses' => 'AggregateController#handleShow', 'as' => 'item.show']);
Route::resource('parent.item', 'ParentController', ['except' => ['show']]);
Route::get('parent/{parent}/item/{item}', ['uses' => 'AggregateController#handleShow', 'as' => 'parent.item.show']);
Route::resource('grandparent.parent.item', 'GrandParentController', ['except' => ['show']]);
Route::get('grandparent/{grandparent}/parent/{parent}/item/{item}', ['uses' => 'AggregateController#handleShow', 'as' => 'grandparent.parent.item.show']);
app/Http/Controllers/AggregateController.php:
class AggregateController extends Controller
{
public function handleShow(Request $request, ...$items)
{
dd($request->path(), $items);
}
}
http://stackoverflow42005960.dev/grandparent/1/parent/2/item/3:
"grandparent/1/parent/2/item/3"
array:3 [▼
0 => "1"
1 => "2"
2 => "3"
]
If you still have issues with getting the variable arguments, then check your PHP version and if < 5.6 you'll have to use func_get_args()
There're many ways to go about this. For example, you can use a comma separated list in routes and simply explode in controller.
The way you have it currently, you will have to use a fixed number of optional parameters, e.g.
public function show(Request $request, $id1, $id2 = false, $id3 = false)
{
//if parent item exists
if($id2)
{
//if grandparent item resource
if($id3)
{
}
}
else
{
//just item
}
}

Categories