change naming convention of the url - php

I want to change the naming convention of the following url :
http://example.org/designs/CV%20Designs
To be the following:
http://example.org/designs/cv-designs
Here is my web.php route file:
Route::get('designs/{design}', 'DesignsController#show')
->name('designs.show');
And here is my Design Model:
public function getRouteKeyName()
{
$slug = Str::slug('Laravel 5 Framework', '-');
dd($slug);
return 'designName';
}
When I dd(slug); the output is 'Laravel 5 Framework' but I want it to be designName

Ok, so I am going to make some assumptions here, but lets say you have this function on your Design model:
Design.php
class Design extends Model
{
...
/**
* Assuming you dont have a slug column on your designs table
*
* Also assuming the slug is built from a column called 'name' on
* your designs table
*/
public function getSlugAttribute()
{
return \Illuminate\Support\Str::slug($this->name);
}
// This assumes there is a column on posts table of 'design_id'
public function posts()
return $this->hasMany(Post::class);
}
...
}
Now let's make an example of how you can build the desired route.
EDIT
Through further discussions with the asker, they wan't to show all Posts related to the Design they are showing (See model above). The setup in this answer is suitable for that, and you can refer to the show method defined below.
Assume we have DesignsController.php:
class DesignsController extends Controller
{
...
public function index()
{
return view('designs.index', [
'designs' => Design::all(),
]);
}
public function show(Request $request, string $design)
{
// It is worth noting here if name has a double space you wont
// be able to build backwards to it for a query
// ie) Design\s\sOne !== $lower(Design\sOne)\
$spaced = str_replace('-', ' ', $design);
$lower = strtolower($spaced);
$design = Design::with('posts')->whereRaw("LOWER(name) = '$lower'")->first();
return view('designs.show', [
'design' => $design,
]);
}
...
}
Now in the 'designs/index.blade.php' file you could do something like:
#foreach($designs as $design)
{{ $design->name }}
#endforeach
That would list all of your designs by name, linked to the designs.show route by their slug.
If you would always like the slug value to be loaded when serializing to an array or json, you can add it to the protected $appends array on the model.
If you don't ALWAYS want it appended, you need to append it at run time using for example $design->append('slug').
Or if you have a collection of Designs you can do $designs->each->append('slug').
Now in your designs.show blade file you can, access the Design's posts using the relation we loaded using Design::with('posts'), by doing the following:
#foreach ($design->posts as $post)
<img src="{{ asset('storage/'.$post->postImage) }}">
#endforeach

Related

Laravel apiResource to fetch record by field other than id

I am using Laravel to fetch records from the database for which I have created an apiResource controller. I have setup the following code inside routes.
Route::apiResource('/MyController',MyController::class)->middleware('auth:api');
In MyController.php my code to display a specific data is:
/**
* Display the specified resource.
*
* #param \App\Models\ContentType $contentType
* #return \Illuminate\Http\Response
*/
public function show(MyModel $Model)
{
// show content type info
return response([
'data' => new MyControllerResource($Model)
],200);
}
I get the data when I place an api call like this:
http://localhost:8000/api/MyController/1
What I want is a record getting fetched by passing other field value instead of id in the route. For example.
http://localhost:8000/api/MyController/mypost
Any idea how can I achieve this?
The route key name defaults to id for all models. You will want to update this to name or whatever field "mypost" is by adding a getRouteKeyName() method.
<?php
namespace App;
...
class Post extends Model
{
public function getRouteKeyName()
{
return 'name';
}
...
}
You are using route model binding. And in laravel its default behaviour is to find model with id and return collection. It will not search for any other field. Of course you can change this behaviour but it can only search data by one field. To change this behaviour use getRouteKeyName method in model like:
public function getRouteKeyName()
{
return 'another_field_in_my_table';
}
You can also use explicit binding like:
Route::bind('MyController', function($value) {
return \App\MyModel::where('id', $value)->orWhere('another_field_in_my_table', $value)->first();
});
To learn more about explicit binding check docs.
You'll have to define route separately. You can group the routes by controller and middleware though. And once done, then, Inside your route, you need to change to this:
Route::get('/show/{post:columnName}', [ MyController::class, 'show' ])->middleware('auth:api');
Now your data will be fetched on the basis of your column name defined in the route.

Prepend/Append words to slug

I'm trying to get pretty links with Laravel.
I have posts with tags. My tagged posts are viewable via localhost/tag-slug. I store tags slugs in database.
I'm trying to append and prepend these slugs with some words so I can have something like:
localhost/awesome-laravel-posts
localhost/best-php-posts
I can get it to work with:
Route::get('/awesome-{tag}-posts', 'TagsController#index')->name('tag')
But when the tag slug contains a -, it fails.
Here is my model:
class Tag extends Model
{
public function jobs()
{
return $this->belongsToMany(Post::class);
}
public function getRouteKeyName()
{
return 'slug';
}
}
Any ideas how can I achieve this? Is it even possible?
Of course u can do tha. I recommend dedicated library for example Eloquent Sluggable: https://github.com/cviebrock/eloquent-sluggable
Installation:
composer require cviebrock/eloquent-sluggable:^4.3
Usage:
use Cviebrock\EloquentSluggable\Sluggable;
class Post extends Model
{
use Sluggable;
/**
* Return the sluggable configuration array for this model.
*
* #return array
*/
public function sluggable()
{
return [
'slug' => [
'source' => 'title'
]
];
}
}
Easy creation of slugs for your Eloquent models in Laravel.
You can use regex to define route arguments. You could do this and {tag} will match anything:
Route::get('/awesome-{tag}-posts', 'TagsController#index')
->where('tag', '.*')->name('tag');
You could probably write a better regex for your case.

Trying to make SEO friendly urls without using database

I'm building my first Laravel app. I have posts that belongs to different categories.
I'm trying to make SEO friendly urls /categories/category_name for categories without saving a 'slug' in database.
Here is what I came with so far:
My category model:
class Category extends Model
{
public function posts()
{
return $this->hasMany(Post::class);
}
public function getSlugAttribute(): string
{
return str_slug($this->name);
}
public function getUrlAttribute(): string
{
return action('CategoriesController#index', [$this->id, $this->slug]);
}
}
My category controller
class CategoriesController extends Controller
{
public function index($id, $slug = '')
{
$posts = Category::findOrFail($id)->posts;
return view('index', compact('posts'));
}
}
My route:
Route::get('/categories/{slug}', 'CategoriesController#index')->name('category');
With {{ $post->category->url }} I get something like http://localhost/category/1?alpha but it works and display the appropriate posts. Route is returning an error.
How I get ride of the id? and make the route works?
Thank you in advance for your help!
I'm assuming that you have a human readable category name (ex My Category) and you are converting that to a slug in your URLs (ex my-category).
A naive approach to doing this would be to attempt to reverse the slug and lookup the category by name, but there are use-cases where this won't work.
class CategoriesController extends Controller
{
public function index($slug)
{
$name = ucwords(str_replace('-', ' ', $slug));
$posts = Category::where('name', '=', $name)->first()->posts;
return view('index', compact('posts'));
}
}
The problem with that approach is there are cases where information is lost when converting the name to a slug, for example if the proper category name contains a hyphen, the hyphen becomes a space when converting the resulting slug.
You can approach this a different way, without storing the slug, by modifying your route:
Route::get('/categories/{category}/{slug}', 'CategoriesController#index')->name('category');
In this case, you pass both the ID and the slug in the URL. You can either fetch the category from the ID or use route model binding to fetch it automatically. This technically means you can pass any string as the slug and it will still work. You can handle this by determining the actual slug and redirecting to the canonical URL if the end user provides an incorrect slug.
class CategoriesController extends Controller
{
public function index(Category $category, $slug)
{
if(str_slug($category->name) != $slug) {
// Redirect to the canonical URL
}
$posts = $category->posts;
return view('index', compact('posts'));
}
}
A benefit of keeping both the ID and the slug in the URL is you get redirection for free: If the category name changes, the new URL will work and the old URL will automatically redirect to the new one.
Side note: If you look the URL for his question you'll notice this is the approach StackOverflow uses.
I don't think this is possible. The reason behind is that you will need to search for the category using the slug but is not stored in the database, so you will need to convert the slug to its original value.
For example, if you have a category named "Modern art", the slug would look something like modern-art. If you pass that slug to your controller, you need to convert it back to "Modern art" to be able to retrieve the category by its name. But maybe the real name was "Modern Art" or "Modern art!" or something else.
I recommend you storing the slug in the database.

After create - why does it not return relationship?

When creating an entry using create() - Why does it return a relationship of pilot_id table instead of just showing the value of pilot_id?
For example, in the repository class:
public function createFlight()
$flight = App\Flight::create([
'name' => 'Flight 10'
'pilot_id' => 4
]);
return $flight;
}
In the controller:
public function show()
{
$data = $this->flight->createFlight();
return $data
}
It would return json on the screen but it is not showing the relationship table (pilot) of pilot_id.
Try adding the following to your Flight model, like so. By default you need to tell Laravel to include any relationships.
protected $with = ['pilot'];
That will make it so everytime it includes the relationship. If this is not desirable, then you will want to load the relationships when you return the flight, like so.
return $flight->load(['pilot']);
It shows pilot_id is 4 because that's what its value is. Did you create a relationship on the Flight so that Laravel knows how to retrieve the model for Pilot? It should look something like this:
public function pilot()
{
return $this->hasOne('App\Pilot');
}
When you return a model directly from the controller, it invokes the toJson() method to convert the object to a string. If you want to append the contents of a related model you can do so by adding the relationship to the $with variable on the Flight model.
protected $with = ['pilot']

Laravel 4 Display ads to load any pictures from another table

I have a problem with displaying data from the form. He wants to load the data from two tables joined the foreign key.
I do not know what I'm doing wrong because I Chaly time returns the message:
Undefined property: Illuminate\Database\Eloquent\Relations\HasMany::$file
offers tabel:
id
user_id
title
...
photosofoffers tabel:
id
offer_id <- primary key offers.id
file (url photos)
...
my view:
#foreach($offers as $offer)
{{ HTML::image($offer->photosofoffers()->file, $offer->title, array('width'=>'50')) }}
my model Offer:
protected $fillable = array('id_category','user_id', 'title', 'description', 'price', 'availability');
public static $rules = array(
'id_category'=>'required|integer',
'title'=>'required|min:2',
'description'=>'required|min:2',
'price'=>'required|numeric',
'availability'=>'integer'
);
public function photosofoffers(){
return $this->hasMany('Photosofoffer');
}
public function category() {
return $this->belongsTo('Category');
}
}
my model Photosofoffer
<?php
class Photosofoffer extends Eloquent {
public function offer(){
return $this->belongsTo('Offer');
}
public function offers() {
return $this->hasMany('Offer');
}
}
How to display ads to load any pictures from another table?
hasMany means there are many photos of one offer. Therefore is it wise to call $something->photosofoffer()->photo ? If the return is an object, you'll definitely get an error.
First do dd($offer->photosofoffers()) or dd($offer->photosofoffers) to see what's happening. Then, if the object is properly being derived, You need to check loop through it. like #foreach($offer->photosofoffers as $photo) your loop of diplaying image #endforeach.
If there is nothing being derived, change the Controller function where you collect the actual $offer and make it Model::with('photoofoffers')->get() or first()
That should clear this up.
Hope this helps.
YK.

Categories