1

I am working on a basic blog application in Codeigniter 3.1.8 and Bootstrap 4. The posts table has a slug column that I intend to use at making SEO friendly URLs.

Because every individual post's slug will be a part of it's URL, the slug column is, of course, unique. I use CI's url_title() method to make the slug out of the post title:

$slug = url_title($this->input->post('title'), 'dash', TRUE);

Given the above described situation, it is obvious that a problem appears when 2 posts have the exact same title ("Who loves a butterfly?"):

Duplicate entry 'who-loves-a-butterfly' for key 'slug'

I need a way to make the duplicate title post generate a "numbered" slug: "who-loves-a-butterfly-1" and so on, if necessary.

The Codeigniter 3 documentation does not provide such a way for its url_title() method? Is there a way I could modify it or an alternative to it?

Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252
  • 3
    As I understand, your problem is duplicate key in database. `url_title` just convert a string to url format and do nothing with database. You must write a custom function to check if `slug` existed on DB or not. – Nghi Ho Oct 12 '18 at 19:30
  • Nonce the slug and you're golden – Javier Larroulet Oct 12 '18 at 21:46

4 Answers4

1

I'm using a custom function sanitize() - normally in a model - to create that unique slug entry: please read code comment as it documents the flow

function sanitize($string,$table='posts'){
    // sanitize string, remove Latin chars like 'ç ' and add - instead of white-space
    // based on: http://stackoverflow.com/a/2103815/2275490
    $str= strtolower(trim(preg_replace('~[^0-9a-z]+~i', '-', html_entity_decode(preg_replace('~&([a-z]{1,2})(?:acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i', '$1', htmlentities($string, ENT_QUOTES, 'UTF-8')), ENT_QUOTES, 'UTF-8')), '-'));
    // check how often header exists in table "posts"
    // add counted suffix to pretty_url if already exists
    $query = $this->db  ->where('slug', $string)
                        ->get($table);
    if($query->num_rows()>0) 
        $str=$str.'-'.$query->num_rows(); // allways returns the latest number for same slug

    return $str;                    
}
Vickel
  • 7,879
  • 6
  • 35
  • 56
1

There's a lot of creative approaches you could use. It all depends on your personal preference.

The first one, with query builder enabled would be to use the built-in form_validation and check if your slug complies with is_unique and if it isn't, alter it slightly and re-check until the validation passes. Even though form_validation is used mainly to validate and sanitize user input, you can use it for pretty much anything.

Another approach would be to always make sure you're getting a unique slug on the first try by appending a nonce or a random number or some other unique parameter to the generated slug before insertion. For example:

$slug = url_title($this->input->post('title'), 'dash', TRUE).strtotime(date('Y-m-d H:i:s'));

The above, which is probably the simplest approach possible, will work as long as no two posts with exactly the same title are created at the exact same second. That would reduce the chance of collisions (almost) entirely.

A third solution, a little bit more cumbersome, would involve calculating the base slug with $slug = url_title($this->input->post('title'), 'dash', TRUE); and then:

1.- pass the slug to a simple database query (either right there in the controller or inside a model, your choice)

$this->db->select('count(*) as slugcount');
$this->db->from('your_table');
$this->db->where('slug', $slug);

$query = $this->db->get();
return $query->row(0)->slugcount;

If the slug is truly unique, you'll be returned 0, if there's one previous entry you'll be returned 1 and so on.

Afterwards you choose: The first time you create a slug, append a "-0" or a "-1" (depending on what's best for SEO) The second time you append a "-1" or a "-2" (depending on what you defined in the previous one) and so on and so on

So, for example, you would alter your slug with

$slug = $slug."-".$slugcount;

would that work?

Javier Larroulet
  • 3,047
  • 3
  • 13
  • 30
  • A simple, creative solution. Still, not SEO friendly. – Razvan Zamfir Oct 12 '18 at 21:50
  • Just curious... what would you consider SEO friendly? I'm on a break and perhaps I could help you figure this one out – Javier Larroulet Oct 12 '18 at 21:53
  • added a third non-native alternative which I believe adheres better to the end result you are looking for – Javier Larroulet Oct 12 '18 at 22:04
  • 1
    It worked perfectly. See how I applied your solution in my application **[HERE](https://stackoverflow.com/a/52791768/4512005)**. Thanks a lot! – Razvan Zamfir Oct 13 '18 at 10:03
  • Please help me with **[How can I replace post id with slug in the singe post url only?](https://stackoverflow.com/questions/52801378/codeigniter-blog-application-replace-post-id-with-slug-in-the-singe-post-url-on)**. Thanks! – Razvan Zamfir Oct 14 '18 at 09:46
0

Here is the full solution applied in my blog application:

In the Posts_model model:

public function slug_count($slug){
    $this->db->select('count(*) as slugcount');
    $this->db->from('posts');
    $this->db->where('slug', $slug);
    $query = $this->db->get();
    return $query->row(0)->slugcount;
}

public function create_post($post_image, $slug) {
    $data = [
        'title' => $this->input->post('title'),
        'slug' => $slug,
        'description' => $this->input->post('desc'),
        'content' => $this->input->post('body'),
        'post_image' => $post_image,
        'author_id' => $this->session->userdata('user_id'),
        'cat_id' => $this->input->post('category'),
        'created_at' => date('Y-m-d H:i:s')
    ];
    return $this->db->insert('posts', $data);
}

In the Posts controller:

// Create slug (from title)
$slug = url_title($this->input->post('title'), 'dash', TRUE);
$slugcount = $this->Posts_model->slug_count($slug);
if ($slugcount > 0) {
    $slug = $slug."-".$slugcount;
}
Razvan Zamfir
  • 4,209
  • 6
  • 38
  • 252
0

This is a function which will create always unique slug for any table. In Model

function get_unique_slug($slug,$table)
{
$slug = url_title($this->input->post('title'), 'dash', TRUE);
$this->db->select('count(*) as slugcount');
$this->db->from($table);
$this->db->where('slug like', $slug.'%');

$query = $this->db->get();
if($query->row(0)->slugcount>0)
{
    return $slug.'-'.$query->row(0)->slugcount;
}
else
{
    return $slug;
}
}

In Controller

$slug = $this->crud_model->get_unique_slug($slug,'category');
Cool Perfectionist
  • 205
  • 1
  • 3
  • 18