How to use custom keys in laravel routes without scoping? - laravel

When using custom keys Laravel forces us with scoping, for example, I have a route to getting a country and a post
api/countries/{country:slug}/posts/{post:slug}
but I can't get that using slug key because it doesn't have a relation with country, and in this case, I want to handle scope myself and I don't need implicitly scope binding, but I get an error (Call to undefined method App\Country::posts() ).
so because of that I cant using this Laravel feature. is there a way to turn the implicitly scope binding off?

If the posts are not related to the countries, it may not make sense to nest them in the URI?
But, nonetheless, to answer your question, you need to do one of two things:
Instead of setting {country:slug}, just use {country} and then override getKeyRouteName() function on your Country and Post models.
Alternatively, especially if you want to use the ID elsewhere, use explicit model binding.
To use a slug without custom keys in the routes file
class Post
{
[...]
public function getRouteKeyName()
{
return 'slug';
}
}
To use explicit route model binding
Add the following to the boot() method of your RouteServiceProvider:
public function boot()
{
parent::boot();
Route::bind('post', function ($value) {
return App\Post::where('slug', $value)->firstOrFail();
});
}

Related

How can I encrypt all ids in URL (laravel 9) using repository pattern

I need to do all url ids encrypted like :
user/edit/1
items/edit/35
posts/details/52
to below url path
user/edit/gd43dfrg
items/edit/sdfg4343
posts/details/fasdf23423
there is lots of areas in repository pattern like UserRepository , UserController blade files and in controllers that id used url ('items/edit/2')
however also in controller some function are passed by objects like
public function itemedit(Items $items)
I tried
$encrypt_val = Crypt::encrypt($value) and $decrypt_val = Crypt::decrypt($encrypt_val );
but I need to do it all over app. There is any short way or Middleware function to do it using repository pattern ?
The proper way to do this would be like so:
Use any boot function of a service provider (e.g. RouteServiceProvider) to define how a route parameter should be invoked:
public function boot()
{
// Bind any `{order}` route parameter such that it decodes the value before retrieving the order.
Route::bind('order', function ($value) {
return User::query()->where('id', $this->yourDecodeFunction($value))->firstOrFail();
});
}
Now when you create urls like route('orders.show', yourEncodeFunction($order->id)) from a route
Route::get('/orders/{order}', /*...*/);
your controller method will receive the expected order in its signature i.e.
public function show(Request $request, Order $order) {
//
}
To improve this code, you can define the getRouteKey() function (part of the UrlRoutable trait) on your Eloquent model to simplify the creation of your routes so that you can call route('orders.show', $order):
// App/Models/Order.php
public function getRouteKey()
{
return yourEncodeFunction($this->getKey());
}
This will make sure that the route parameter is automatically identified when you pass the complete object to the route call (if you don't override this function yourself it uses the model's primary key).
https://packagist.org/packages/hashids/hashids might be a good package for you if you just want to obfuscate some of the url (note that this package is an 'encoder' and not an 'encrypter').

Custom Routes not working - 404 not found

I'm trying to create a custom route. The must be in this format: http://localhost:8000/home-back-to-school but instead I get a 404 not found error. http://localhost:8000/posts/home-back-to-school works, but that's not what I'm trying to get working.
My routes on web.php are defined as: Route::resource('posts',PostsController::class);
I modified the Route Service Provider by adding the code below:
parent::boot();
Route::bind('post',function($slug){
return Post::published()->where('slug',$slug)->first();
});
The published scope is defined in the Post Model file(Post.php) as:
public function scopePublished()
{
return $this->where('published_at','<=',today())->orderBy('published_at', 'desc');
}
I've done previously with laravel 5.x, now struggling with laravel 8.x
Link to the Documentation: Laravel 8 Documentation
You should define a custom route since you don't want to use the resourceful route for this method.
In your web.php
// Keep all your resource routes except the 'show' route since you want to customize it
Route::resource('posts', PostsController::class)->except(['show']);
// Define a custom route for the show controller method
Route::get('{slug}', PostsController::class)->name('posts.show');
In your PostController:
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
In your Post model:
// Tell Laravel your model key isn't the default ID anymore since you want to use the slug
public function getRouteKeyName()
{
return 'slug';
}
You may have to fix your other Post routes to make them work with this change since you are now using $post->slug instead of $post->id as the model key.
Read more about customizing the model key:
https://laravel.com/docs/8.x/routing#customizing-the-default-key-name
You should also remove the code you have in the boot method and use the controller instead.
Finally, make sure your post slug is always unique for obvious reason.
Note:
You may run into problems if your other routes are not related to the Post model.
Imagine if you have a route called example.com/contact-us. Laravel has no way to "guess" if this route should be sent to the PostController or the ContactController. contact-us could be a Post slug or it could be a static route to your contact page. That's why it's generally a good idea to start your urls with the model name. In your case, it would be a good idea for your Post route to start with "/posts/" like this: http://example.com/posts/your-post-slug. Otherwise you may run into all sorts unexpected routing issues.
Don't fight the framework: Always follow best practices and naming conventions when you can.

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.

Need Laravel Route::controller and Route::controllers

As we know in Laravel 5.2 Route::controller() and Route::controllers() method was deprecated but it was very handy for reducing the number of routes. I was able to write simple route like this Route::controller('admin/invoice','InvoiceController'). With this simple one route, I can manage all things related to making invoice related work by a controller.
class InvoiceController extends Controller{
public function getInvoices(){ }
public function getInvoiceDetails(){ }
public function postStoreInvoice(){ }
public function postUpdateInvoice(){ }
public function postStoreInvoiceDetails(){ }
public function postupdateInvoiceDetails(){ }
public function postDeleteInvoice(){ }
public function postDeleteInvoiceDetails(){ }
....
}
but unfortunately this Route::controller() and Route::controllers() no longer available laravel version > 5.1. An option available Route::resource() but it has a limited number of the route. The laravel route is Macroable, there is an option to extend the route features like
Illuminate\Routing\Router::macro('controller', function ($routes) {
// implementation
});
Is there anyone who implements Route::controller() and Route::controllers() method for Laravel 5.8, 6 ? or suggest any way.
You can use Route::resource() or Route::resources().
Example:
Route::resource('books', 'BookController');
this will assumes you have
class BookController extends Controller {
// to list resources.
public function index();
// to show create form.
public function create();
// to store resource in database.
public function store();
// to show single resource.
public function show();
// to show edit form.
public function edit();
// to edit and then store the modified resource in database.
public function update();
// to delete a resource from database.
public function destroy();
}
You should read https://laravel.com/docs/master/controllers#resource-controllers for more information.
Edit
Implicit controllers was removed in version 5.2 for some reason.
If you come from the CodeIgniter world, then you may have warm and fuzzy
feelings for implicit routing. You know, where the URI matches up to
the controller method that will be called. You might even want this
for your Laravel development (which Laravel can do).
Though it might seem useful at first to simply call
Route::controller('admin', 'AdminController') and then declare all of
your desired routes from the controller, there are a number of
setbacks to this. Think about how you would, when using implicit routing,
leverage named routes, or create nested resources, or even do
something as simple as rename your controller class without affecting
your URI design.
No, when it comes to implicit routing, just say no.
source: https://laracasts.com/lessons/say-no-to-implicit-routing
However if you want this functionality you can use this package:
Laravel Routes Publisher or Laravel Advanced Route

Different state for Eloquent model fields depending on current user in laravel

I have the model:
class Task extends Model {
}
with some fields
protected $fillable = ['message', 'due_time', 'status', 'etc...'];
I've added custom function:
public function getEditableStateFor{AttributeName}
In my helper function I check that if
method_exists($class, 'getEditableStateForField1')
than I allow to edit this field depending on boolean value returned from this function.
Example:
if( ! $class->getEditableStateForField1() ) {
return "You can not edit field field1";
}
Here is how looks like some functions in Task:
private function isCreator() {
$user = Auth::user();
if($user) {
return $user->id === $this->creator_id;
}
return false;
}
public function getEditableStateForMessage() {
return $this->isCreator();
}
public function getEditableStateForDueTime() {
return $this->isCreator();
}
Is this a good way to do it or it is very bad design because of hidden dependency on Auth::user()?
What is a better way?
I do not want to put this logic inside controllers because this logic propagates to another models and is universal across application.
I'm like you and like to have Models that contain as much of the business logic as possible while remaining totally free of depencies on the "web" part of the application, which I believe should stay in Controllers, Request objects, etc. Ideally, Models should be easily usable from command line interfaces to the application, from within the Tinker REPL, and elsewhere while still guaranteeing data integrity and that business rules are observed.
That said, it seems the Laravel creators had slightly different ideas, hence the Auth facade being easily available in the model.
What I would likely do is add a parameter of type User to the getEditableStateFor series functions, and then in turn pass that parameter to isCreator ($user) and elsewhere. That also frees you up to be able to allow associated users to edit each other's Tasks if that ever became a desired feature in the future.
Edit: another, perhaps better or perhaps worse, is to have an instance method like setCurrentUser ($user) then use setFieldNameAttribute methods so that the controller doesn't have to check the editability of fields, keeping that the model's responsibility. Then you could call the getEditableStateFor methods, which now check for the current user set by the above method (maybe falling back to Auth::user() or throwing a helpful error), inside the setter.

Resources