'How to get unique slug to same post-title for other time too?
I have tried the codes as follows.
$post->title = $request->title;
$post->body = $request->body;
$post->slug = str_slug($post->title,'%');
The code was running great but now I have the post with same title so its throwing error as it is set to unique in db. Is there any way I can get another slug?
Solution 1:[1]
If you are facing a slug colision, in this case best way to go would be adding an extra integer at the end example :
mysite.dev/my-post
mysite.dev/my-post-1
For this you could use a package to generate a slug
Of if you want to do it yourself then in the model add slugattribute
public function setSlugAttribute($value) {
if (static::whereSlug($slug = str_slug($value))->exists()) {
$slug = $this->incrementSlug($slug);
}
$this->attributes['slug'] = $slug;
}
So the setSlugAtrribute will always check if the slug for given model exists if so then it will increment the slug as I meantioned above, by simply calling the below method.
public function incrementSlug($slug) {
$original = $slug;
$count = 2;
while (static::whereSlug($slug)->exists()) {
$slug = "{$original}-" . $count++;
}
return $slug;
}
Basically we are always checking if the slug exists in the database, if yes then we use an accessor to change the slug by adding an integer at the end this way you will never end having the slug duplication issue.
Solution 2:[2]
Create our own function that generate unique slug from any title. Look at the code and that is pretty much clear.
Reference code from this link.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Product;
class ProductController extends Controller
{
public function store(Request $request)
{
$product = new Product;
$product->title = $request->title;
$product->slug = $this->createSlug($request->title);
$product->save();
}
public function createSlug($title, $id = 0)
{
$slug = str_slug($title);
$allSlugs = $this->getRelatedSlugs($slug, $id);
if (! $allSlugs->contains('slug', $slug)){
return $slug;
}
$i = 1;
$is_contain = true;
do {
$newSlug = $slug . '-' . $i;
if (!$allSlugs->contains('slug', $newSlug)) {
$is_contain = false;
return $newSlug;
}
$i++;
} while ($is_contain);
}
protected function getRelatedSlugs($slug, $id = 0)
{
return Product::select('slug')->where('slug', 'like', $slug.'%')
->where('id', '<>', $id)
->get();
}
}
Finally. We've created an Unique slug in Laravel.
localhost:8000/kwee-dev
localhost:8000/kwee-dev-1
Solution 3:[3]
I had the same problem, that's my solution:
- I would check if there is the same title in the database.
- If yes return rows with the same title
- Increment return value by 1
- If there are no posts/listing with the same title than if check will not be executed
/**
* Create a slug from title
* @param string $title
* @return string $slug
*/
protected function createSlug(string $title): string
{
$slugsFound = $this->getSlugs($title);
$counter = 0;
$counter += $slugsFound;
$slug = str_slug($title, $separator = "-", app()->getLocale());
if ($counter) {
$slug = $slug . '-' . $counter;
}
return $slug;
}
/**
* Find same listing with same title
* @param string $title
* @return int $total
*/
protected function getSlugs($title): int
{
return Listing::select()->where('title', 'like', $title)->count();
}
Solution 4:[4]
You can easily solve this common issue with few lines of code using one of these two packages:
Laravel Sluggable
https://github.com/spatie/laravel-sluggable
Just install the package running:
composer require spatie/laravel-sluggable
Then add the code below to your model:
use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
class Post extends Model
{
use HasSlug;
public function getSlugOptions() : SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('title')
->saveSlugsTo('slug');
}
or as alternative:
Eloquent sluggable
https://github.com/cviebrock/eloquent-sluggable
You can install it typing:
composer require cviebrock/eloquent-sluggable
And then adding to your model:
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'
]
];
}
}
Solution 5:[5]
This is the one that I am using to get SEO friendly unique slug.
Model
# verify and return custom slug string
public function slugify($text)
{
$slug = strtolower($text);
$slug = str_replace(array('[\', \']'), '', $slug);
$slug = preg_replace('/\[.*\]/U', '', $slug);
$slug = preg_replace('/&(amp;)?#?[a-z0-9]+;/i', '-', $slug);
$slug = htmlentities($slug, ENT_COMPAT, 'utf-8');
$slug = preg_replace('/&([a-z])(acute|uml|circ|grave|ring|cedil|slash|tilde|caron|lig|quot|rsquo);/i', '\\1', $slug );
$slug = preg_replace(array('/[^a-z0-9]/i', '/[-]+/') , '-', $slug);
# slug repeat check
$latest = $this->whereRaw("slug REGEXP '^{$slug}(-[0-9]+)?$'")
->latest('id')
->value('slug');
if($latest){
$pieces = explode('-', $latest);
$number = intval(end($pieces));
$slug .= '-' . ($number + 1);
}
return $slug;
}
Controller
$post = new POST();
$post->title = $request->title;
$post->slug = $school->slugify($request->title);
$post->save();
return redirect()->back()->with('success', 'Successfully created new record');
Solution 6:[6]
Several months ago I've searched convenient solution for automation slug creating and found interesting decision with Laravel's Mutator by Taner Fejzulovski.
But, when I started to check it and debug, I've understand that code not completely done. So, I've updated and refactored it based on my needs and understanding of checks.
I have used there recursive method for a complete check of previously created slugs in the database. Maybe it will be also useful for you and others too!
/**
* Set the proper slug attribute.
*
* @param string $value
* @return mixed
*/
public function setSlugAttribute($value)
{
if(static::whereSlug($slug = Str::slug($value))->exists())
{
if(static::whereSlug($slug)->get('id')->first()->id !== $this->id){
$slug = $this->incrementSlug($slug);
if(static::whereSlug($slug)->exists()){
return $this->setSlugAttribute($slug);
}
}
}
$this->attributes['slug'] = $slug;
}
/**
* Increment slug
*
* @param string $slug
* @return string
**/
public function incrementSlug($slug)
{
// Get the slug of the created post earlier
$max = static::whereSlug($slug)->latest('id')->value('slug');
if (is_numeric($max[-1])) {
return preg_replace_callback('/(\d+)$/', function ($matches) {
return $matches[1] + 1;
}, $max);
}
return "{$slug}-2";
}
Solution 7:[7]
try it
$slug = SlugHelper::createSlug("art-street-bird-mdf-wall-plaquewall-sign-for-home-decoration-ready-to-hang-wall-decor-6mm-black-rs-199-amazon-2174");
$slug = SlugHelper::createSlug("art-street-bird-mdf-wall-plaquewall-sign-for-home-decoration-ready-to-hang-wall-decor-6mm-black-rs-199-amazon-2174", 51, true);
$slug = SlugHelper::createSlug("art-street-bird-mdf-wall-plaquewall-sign-for-home-decoration-ready-to-hang-wall-decor-6mm-black-rs-199-amazon-2174", 51, false);
$slug = SlugHelper::createSlug("art-street-bird-mdf-wall-plaquewall-sign-for-home-decoration-ready-to-hang-wall-decor-6mm-black-rs-199-amazon-2174", 51, true, true, Product::class, 'sku');
// If you used Laravel Eloquent Model's SoftDeletes (https://laravel.com/docs/8.x/eloquent#soft-deleting)
// then pass true as a one more addition parameter which named **`includeTrashed`**
// after the `attribute` parameter` in `createSlug` function. otherwise pass false.
// default value of includeTrashed is also false. see below
$slug = SlugHelper::createSlug("art-street-bird-mdf-wall-plaquewall-sign-for-home-decoration-ready-to-hang-wall-decor-6mm-black-rs-199-amazon-2174", 51, true, true, Product::class, 'sku', true);
Comman Helper Class Function created for Slug which named SlugHelper class
<?php
namespace Modules\CoreProduct\Utilities;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Modules\CoreProduct\Entities\Product;
use Illuminate\Support\Str;
class SlugHelper
{
/**
* model Obj, mandatory if wants to unique slug
*
* @var Model
*/
public static $modelObj;
/**
* The attribute is a database field name for check uniqueness of slugs. mandatory if you pass $uniqueSlug as true.
* for ex: I have "sku" named DB field then I have to pass $attribute = "sku"
*
* @var string
*/
public static $attribute;
/**
* if you wants to Enforce uniqueness of slugs with Soft Deleted Rows also
*
* @var boolean
*/
public static $includeTrashed;
/**
* If you are setting a maximum length on your slugs, you may not want the
* truncated string to split a word in half.
*
* e.g. with a maxLength of 12:
* if you pass true, "my source string" -> "my-source" .
* if you pass false, "my source string" -> "my-source-st"
*
* @var bool
*/
public static $maxLengthKeepWords;
/**
* The first suffix to add to a slug to make it unique
*
* default value is 2. for adding incremental integers, we start counting
* at 2, so the list of slugs would be,
*
* e.g.:
* - my-post
* - my-post-2
* - my-post-3
*
* @var integer
*/
public static $firstSuffix = 2;
/**
* Separator to use when generating slugs. Defaults to a hyphen.
*
* @var string
*/
public static $separator = '-';
/**
* Generate a URL friendly "slug" from a given string
*
* @param string $title
* @param integer|null $maxLength // maximum length of a generated slug. pass it as a positive integer if you want to make sure your slugs aren't too long.
* @param boolean $maxLengthKeepWords // pass bool true if you may not want the truncated string to split a word in half. e.g. with a maxLength of 12: "my source string" -> "my-source" . if you pass false then output will like this "my source string" -> "my-source-st"
* @param boolean $uniqueSlug // pass bool true if you wants to Enforce uniqueness of slugs
* @param Model $modelObj // pass Model. mandatory if you pass $uniqueSlug as true.
* @param string $attribute // database field name for check uniqueness of slugs. mandatory if you pass $uniqueSlug as true. for ex: I have "sku" named DB field then I have to pass $attribute = "sku"
* @param boolean $includeTrashed // if you wants to Enforce uniqueness of slugs with Soft Deleted Rows also
* @return string
*/
public static function createSlug(string $title, ?int $maxLength = null, bool $maxLengthKeepWords = true, bool $uniqueSlug = false, $modelObj = null, string $attribute = null, bool $includeTrashed = false)
{
self::$modelObj = $modelObj;
self::$attribute = $attribute;
self::$maxLengthKeepWords = $maxLengthKeepWords;
self::$includeTrashed = $includeTrashed;
echo $slug = Str::slug($title);
$slug = self::truncateOverLimitSlug($slug, $maxLength, self::$separator);
$slug = self::makeSlugUnique($slug, self::$separator, $uniqueSlug, $maxLength);
echo "<h3>". $slug . "</h3>";
return $slug;
}
/**
* Checks if the slug should be unique, and makes it so if needed.
*
* @param string $slug
* @param string $separator
* @param boolean $uniqueSlug
* @param integer|null $maxLength
* @return string
*/
private function makeSlugUnique(string $slug, string $separator, bool $uniqueSlug, ?int $maxLength = null)
{
if(!$uniqueSlug){
// $slug = self::truncateOverLimitSlug($slug, $maxLength, $separator);
return $slug;
}
$original = $slug;
$suffix = self::$firstSuffix;
while(self::getExistingSlugs($slug, self::$includeTrashed)){
if($maxLength){
$newMaxLength = $maxLength - strlen($separator . $suffix);
}
$slug = self::truncateOverLimitSlug($original, $newMaxLength, self::$separator) . "{$separator}" . $suffix;
// $slug = "{$original}". "{$separator}" . $suffix;
$suffix++;
}
return $slug;
/* if(!$uniqueSlug){
$slug = self::truncateOverLimitSlug($slug, $maxLength, $separator);
return $slug;
}
$list = self::getExistingSlugs($slug, true);
if($list->count() === 0 || $list->contains($slug) === false){
return $slug;
}
$suffix = self::generateSuffix($slug, $separator, $list, self::$firstSuffix);
if($maxLength){
$maxLength = $maxLength - strlen($separator . $suffix);
}
$slug = self::truncateOverLimitSlug($slug, $maxLength, $separator);
return $slug . $separator . $suffix; */
}
/**
* Truncate the slug using maxLength parameter
*
* @param string $slug
* @param integer|null $maxLength
* @param string $separator
* @return string
*/
private function truncateOverLimitSlug(string $slug, ?int $maxLength = null, string $separator)
{
$len = mb_strlen($slug);
if (is_string($slug) && $maxLength && $len > $maxLength) {
$reverseOffset = $maxLength - $len;
$lastSeparatorPos = mb_strrpos($slug, $separator, $reverseOffset);
if (self::$maxLengthKeepWords && $lastSeparatorPos !== false) {
$slug = mb_substr($slug, 0, $lastSeparatorPos);
} else {
$slug = trim(mb_substr($slug, 0, $maxLength), $separator);
}
}
return $slug;
}
// /**
// * Generate a unique suffix for the given slug (and list of existing, "similar" slugs.
// *
// * @param string $slug
// * @param string $separator
// * @param Collection $list
// * @param integer $firstSuffix
// * @return mixed
// */
// private function generateSuffix(string $slug, string $separator, Collection $list, int $firstSuffix)
// {
// $len = strlen($slug . $separator);
// // If the slug already exists, but belongs to
// // our model, return the current suffix.
// if ($list->search($slug) === 'sku') {
// $suffix = explode($separator, $slug);
// return end($suffix);
// }
// $list->transform(function($value, $key) use ($len) {
// return (int) substr($value, $len);
// });
// $max = $list->max();
// // return one more than the largest value,
// // or return the first suffix the first time
// return (string) ($max === 0 ? $firstSuffix : $max + 1);
// }
/**
* Get all existing slugs that are similar to the given slug.
*
* @param string $slug
* @param boolean $includeTrashed
* @return bool
*/
private function getExistingSlugs(string $slug, bool $includeTrashed)
{
if(is_string(self::$modelObj)){
$modelObj = new self::$modelObj(); // initialize the Model Class, if user pass Product::class. because of string type.
}else{
$modelObj = self::$modelObj; // instance of Model Class, means user passed object of equivalent model class
}
$query = $modelObj;
if($includeTrashed === true){
$query = $modelObj->withTrashed();
}
$query = $modelObj->where(self::$attribute, $slug);
return $query->exists();
// var_dump(self::$modelObj);
/* $results = $modelObj->where(self::$attribute, $slug)->select(self::$attribute)->get();
return $results->pluck(self::$attribute); */
}
}
I hope this one helps to anyone.
Note: I used Laravel version 8+
Solution 8:[8]
it's work for me
public function generateSlug($name)
{
$slug=Str::slug($name);
// dd($slug,"show");
if (Business::where('profile_link',Str::slug($name))->exists()) {
$max = Business::where('name','LIKE',$name)->latest()->value('profile_link');
if(is_numeric($max[-1])) {
// dd($max);
return preg_replace_callback('/(\d+)$/', function($mathces) {
// dd($mathces[1] + 1);
return $mathces[1] + 1;
}, $max);
}
// dd("{$slug}-2");
return "{$slug}-2";
}
// dd($slug);
return $slug;
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Nyein Chan Aung |
Solution 3 | riom |
Solution 4 | |
Solution 5 | Mohit Prajapati |
Solution 6 | |
Solution 7 | Harsh Patel |
Solution 8 |