-
-
Save ericlbarnes/3b5d3c49482f2a190619699de660ee9f to your computer and use it in GitHub Desktop.
<?php | |
namespace App\Services; | |
use App\Post; | |
class Slug | |
{ | |
/** | |
* @param $title | |
* @param int $id | |
* @return string | |
* @throws \Exception | |
*/ | |
public function createSlug($title, $id = 0) | |
{ | |
// Normalize the title | |
$slug = str_slug($title); | |
// Get any that could possibly be related. | |
// This cuts the queries down by doing it once. | |
$allSlugs = $this->getRelatedSlugs($slug, $id); | |
// If we haven't used it before then we are all good. | |
if (! $allSlugs->contains('slug', $slug)){ | |
return $slug; | |
} | |
// Just append numbers like a savage until we find not used. | |
for ($i = 1; $i <= 10; $i++) { | |
$newSlug = $slug.'-'.$i; | |
if (! $allSlugs->contains('slug', $newSlug)) { | |
return $newSlug; | |
} | |
} | |
throw new \Exception('Can not create a unique slug'); | |
} | |
protected function getRelatedSlugs($slug, $id = 0) | |
{ | |
return Post::select('slug')->where('slug', 'like', $slug.'%') | |
->where('id', '<>', $id) | |
->get(); | |
} | |
} |
@AucT Hi AucT, what is the function & variable to call? Example please.
Thank you.
I found this little snippet in my old project:
public function setSlugAttribute($value)
{
$slug = str_slug($value);
// Look for exisiting slugs
$existingSlugs = static::whereRaw("slug REGEXP '^{$slug}(-[0-9]*)?$'");
// If no matching slugs were found, return early
if ($existingSlugs->count() === 0)
return $slug;
// Get slugs in reversed order, and pick the first
$lastSlugNumber = intval(str_replace($slug . '-', '', $existingSlugs->orderBy('slug', 'desc')->first()->slug));
return $slug . '-' . ($lastSlugNumber + 1);
}
//invoking for default (e.g. \App\Post)
$slugLibrary = new \App\Services\Slug();
//invoking for custom entity
$slugLibrary = new \App\Services\Slug(\App\Person::class);
//usage
$slug = $slugLibrary->createSlug('George Lucas');
@ericbarnes and @AucT, a while loop will be much better than for, for loop has limit till 10 slug after that it will throw exceptions
Helpful utility class. Also on @AucT's edit, added option to set the model slug attribute right in the construct function.
<?php
namespace App\Services;
class Slug
{
private $entity;
private $slugAttr;
public function __construct($entity = \App\Post::class, $slugAttr = 'slug')
{
$this->entity = $entity;
$this->slugAttr = $slugAttr;
}
/**
* @param $title
* @param int $id
* @return string
* @throws \Exception
*/
public function createSlug($title, $id = 0)
{
// Normalize the title
$slug = str_slug($title);
// Get any that could possibly be related.
// This cuts the queries down by doing it once.
$allSlugs = $this->getRelatedSlugs($slug, $id);
// If we haven't used it before then we are all good.
if (!$allSlugs->contains($this->slugAttr, $slug)) {
return $slug;
}
// Just append numbers like a savage until we find not used.
for ($i = 1; $i <= 10; $i++) {
$newSlug = $slug . '-' . $i;
if (!$allSlugs->contains($this->slugAttr, $newSlug)) {
return $newSlug;
}
}
throw new \Exception('Can not create a unique slug');
}
protected function getRelatedSlugs($slug, $id = 0)
{
return call_user_func(array($this->entity, 'select'), $this->slugAttr)->where($this->slugAttr, 'like', $slug . '%')
->where('id', '<>', $id)
->get();
}
}
Hey folks, I kinda changed:
<?php
namespace App\Services;
/**
* Slug Class
*/
class Slug
{
public $entity;
public $attribute;
/**
*
*/
public function __construct($entity, $attribute = 'slug')
{
$this->entity = $entity;
$this->attribute = $attribute;
}
/**
* @param $title
* @param int $id
* @return string
* @throws \Exception
*/
public function create($title)
{
if (empty($title)) {
throw new \Exception('Title is empty');
}
// Normalize the title
$slug = str_slug($title);
$slugStoraged = $this->getRelated($slug);
if ($slugStoraged == null) {
return $slug;
}
$lastNum = intval(str_replace($slug . '-', '', $slugStoraged->{$this->attribute}));
if (is_numeric($lastNum)) {
return $slug . '-' . ++$lastNum;
}
}
public function getRelated($slug)
{
return call_user_func([$this->entity, 'select'], $this->attribute)
->where($this->attribute, 'like', $slug . '%')
->orderBy($this->attribute, 'desc')
->first();
}
}
I'm new to laravel, I have created a file called Slug.php and put it into App\Providers folder, When I Call
$slug = new \App\Services\Slug(); in my PostController it throws "Class 'App\Services\Slug' not found" error! Where should I put that file ?
I need help because i am new to laravel too sorry my question might look silly but i need to understand .. Is this class is a service provider or a helper or what? and where should i locate this file i am confused a bit because i don't know about namespace App\Services
is for service provider or something else
If it's a service provider then why it's not namespaced with namespace App\Providers
and why it doesn't extend extends ServiceProvider
in addition that class name should end with the word Provider
When i tried to use this class this error appeared
My code:
use App\Services\Slug;
$slugLibrary = new Slug(\App\Show::class);
$slug = $slugLibrary->createSlug($show->title);
I'm write simple trait which you can use in Eloquent models for slug auto-generation.
<?php
namespace App\Models\Traits;
use Illuminate\Database\Eloquent\Model;
/**
* Trait Sluggable.
*
* Sources:
* @see https://github.com/martinbean/laravel-sluggable-trait/
* @see https://gist.github.com/ericlbarnes/3b5d3c49482f2a190619699de660ee9f
* @see https://interworks.com.mk/the-easiest-way-to-create-unique-slugs-for-blog-posts-in-laravel/
*/
trait Sluggable
{
/**
* Boot the sluggable trait for a model.
*
* @return void
*/
public static function bootSluggable()
{
static::saving(function (Model $model) {
if (empty($model->getSlug())) {
$slug = self::generateUniqueSlug($model);
$model->setSlug($slug);
}
});
}
/**
* The name of the column to use for slugs.
*
* @return string
*/
public function getSlugColumnName()
{
return 'slug';
}
/**
* Get the string to create a slug from.
*
* @return string
*/
public function getSluggableString()
{
return $this->getAttribute('name');
}
/**
* Get the current slug value.
*
* @return string
*/
public function getSlug()
{
return $this->getAttribute($this->getSlugColumnName());
}
/**
* Set the slug to the given value.
*
* @param string $value
* @return $this
*/
public function setSlug($value)
{
$this->setAttribute($this->getSlugColumnName(), $value);
return $this;
}
/**
* @param Model $model
* @return string
* @throws \Exception
*/
private static function generateUniqueSlug(Model $model): string
{
$slug = empty($model->getSlug()) ? trim(str_slug($model->getSluggableString())) : $model->getSlug();
$attribute = trim($model->getSlugColumnName());
if (empty($slug) || empty($attribute)) {
throw new \Exception('Incorrect slug attribute or sluggable string for model! Check your "fillable" array.');
}
$modelsWithRelatedSlug = $model
->withoutGlobalScopes()
->withTrashed()
->where($attribute, 'LIKE', $slug.'%')
->get([$attribute]);
$i = 0;
while ($modelsWithRelatedSlug->contains($attribute, $slug)) {
++$i;
$matches = [];
if (preg_match('/^(.*?)-(\d+)$/', $slug, $matches)) {
$nextNum = $matches[2] + $i;
$slug = "{$matches[1]}-$nextNum";
} else {
$slug = "$slug-$i";
}
}
$model = $model
->withoutGlobalScopes()
->where($attribute, $slug)
->first([$attribute]);
if ($model) {
// Still not unique...
$slug = self::generateUniqueSlug($model);
}
return $slug;
}
}
Usage:
use App\Helpers\CoolSlug;
$slugLibrary = new CoolSlug(\App\Post::class);
return $slugLibrary->createSlug('test');
CoolSlug Class:
<?php
namespace App\Helpers;
class CoolSlug {
private $entity;
/**
* Instantiate a new CoolSlug instance.
*/
public function __construct($entity) {
$this->entity = $entity;
}
/**
* Generate a URL friendly "slug" from the given string.
*
* @param $title String
* @return string
* @throws \Exception
*/
public function createSlug($title) {
// Normalize the title
$slug = \Illuminate\Support\Str::slug($title, '-');
// Get any that could possibly be related.
// This cuts the queries down by doing it once.
$allSlugs = $this->getRelatedSlugs($slug);
// If we haven't used it before then we are all good.
if($allSlugs == 0) {
return $slug;
}
// Just append numbers like a savage until we find not used.
for($i = 1; $i <= 20; $i++) {
$newSlug = $slug. '-'. $i;
if($this->getRelatedSlugs($newSlug) == 0) {
return $newSlug;
}
}
throw new \Exception('Can not create a unique slug.');
}
protected function getRelatedSlugs($slug) {
return call_user_func(array($this->entity, 'select'), 'permalink')->where('permalink', $slug)->count();
}
}
you can make more reusable by adding entity. So you can use it not only for posts but for other entity.
here simple example