Asked  7 Months ago    Answers:  5   Viewed   40 times

I have heard of people using slugs for generating clean urls. I have no idea how it works. Currently i have a codeigniter site which generates url's like this

www.site.com/index.php/blog/view/7

From what i understand by maintaining a slug field it is possible to achieve urls like

www.site.com/index.php/blog/view/once-upon-a-time

How is this done? Especially in reference to codeigniter?

 Answers

58

I just store the slugs in my database table, in a column called slug, then find a post with the slug, like this:

public function view($slug)
{
    $query = $this->db->get_where('posts', array('slug' => $slug), 1);

    // Fetch the post row, display the post view, etc...
}

Also, to easily derive a slug from your post title, just use url_title() of the URL helper:

// Use dashes to separate words;
// third param is true to change all letters to lowercase
$slug = url_title($title, 'dash', true);

A little bonus: you may wish to implement a unique key constraint to the slug column, that ensures that each post has a unique slug so it's not ambiguous which post CodeIgniter should look for. Of course, you should probably be giving your posts unique titles in the first place, but putting that in place enforces the rule and prevents your application from screwing up.

Wednesday, March 31, 2021
 
THEK
answered 7 Months ago
100

That is exactly my requirement too and I was using routes like

$route['logued/presse-access'] = "logued/presse_access";

In my previous project I needed to create 300-400 routing rules, most of them are due to dash to underscore conversion.

For my next project I eagerly want to avoid it. I have done some quick hack and tested it, though have not used in any live server, its working for me. Do the following..

Make sure the subclass_prefix is as follows in your system/application/config/config.php

$config['subclass_prefix'] = 'MY_';

Then upload a file named MY_Router.php in system/application/libraries directory.

<?php

class MY_Router extends CI_Router { 
    function set_class($class) 
    {
        //$this->class = $class;
        $this->class = str_replace('-', '_', $class);
        //echo 'class:'.$this->class;
    }

    function set_method($method) 
    {
//      $this->method = $method;
        $this->method = str_replace('-', '_', $method);
    }

    function _validate_request($segments)
    {
        // Does the requested controller exist in the root folder?
        if (file_exists(APPPATH.'controllers/'.str_replace('-', '_', $segments[0]).EXT))
        {
            return $segments;
        }
        // Is the controller in a sub-folder?
        if (is_dir(APPPATH.'controllers/'.$segments[0]))
        {       
            // Set the directory and remove it from the segment array
            $this->set_directory($segments[0]);
            $segments = array_slice($segments, 1);

            if (count($segments) > 0)
            {
                // Does the requested controller exist in the sub-folder?
                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().str_replace('-', '_', $segments[0]).EXT))
                {
                    show_404($this->fetch_directory().$segments[0]);
                }
            }
            else
            {
                $this->set_class($this->default_controller);
                $this->set_method('index');

                // Does the default controller exist in the sub-folder?
                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.EXT))
                {
                    $this->directory = '';
                    return array();
                }

            }

            return $segments;
        }

        // Can't find the requested controller...
        show_404($segments[0]);
    }
}

Now you can freely use url like http://example.com/logued/presse-access and it will call the proper controller and function by automatically converting dash to underscore.

Edit: Here is our Codeigniter 2 solution which overrides the new CI_Router functions:

<?php

class MY_Router extends CI_Router { 
    function set_class($class) 
    {
        $this->class = str_replace('-', '_', $class);
    }

    function set_method($method) 
    {
        $this->method = str_replace('-', '_', $method);
    }

    function set_directory($dir) {
        $this->directory = $dir.'/';
    }

    function _validate_request($segments)
    {
        if (count($segments) == 0)
        {
            return $segments;
        }

        // Does the requested controller exist in the root folder?
        if (file_exists(APPPATH.'controllers/'.str_replace('-', '_', $segments[0]).'.php'))
        {
            return $segments;
        }

        // Is the controller in a sub-folder?
        if (is_dir(APPPATH.'controllers/'.$segments[0]))
        {
            // Set the directory and remove it from the segment array
            $this->set_directory($segments[0]);
            $segments = array_slice($segments, 1);


            while(count($segments) > 0 && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]))
            {
                // Set the directory and remove it from the segment array
                $this->set_directory($this->directory . $segments[0]);
                $segments = array_slice($segments, 1);
            }

            if (count($segments) > 0)
            {
                // Does the requested controller exist in the sub-folder?
                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().str_replace('-', '_', $segments[0]).'.php'))
                {
                    if ( ! empty($this->routes['404_override']))
                    {
                        $x = explode('/', $this->routes['404_override']);

                        $this->set_directory('');
                        $this->set_class($x[0]);
                        $this->set_method(isset($x[1]) ? $x[1] : 'index');

                        return $x;
                    }
                    else
                    {
                        show_404($this->fetch_directory().$segments[0]);
                    }
                }
            }
            else
            {
                // Is the method being specified in the route?
                if (strpos($this->default_controller, '/') !== FALSE)
                {
                    $x = explode('/', $this->default_controller);

                    $this->set_class($x[0]);
                    $this->set_method($x[1]);
                }
                else
                {
                    $this->set_class($this->default_controller);
                    $this->set_method('index');
                }

                // Does the default controller exist in the sub-folder?
                if ( ! file_exists(APPPATH.'controllers/'.$this->fetch_directory().$this->default_controller.'.php'))
                {
                    $this->directory = '';
                    return array();
                }

            }

            return $segments;
        }


        // If we've gotten this far it means that the URI does not correlate to a valid
        // controller class.  We will now see if there is an override
        if ( ! empty($this->routes['404_override']))
        {
            $x = explode('/', $this->routes['404_override']);

            $this->set_class($x[0]);
            $this->set_method(isset($x[1]) ? $x[1] : 'index');

            return $x;
        }


        // Nothing else to do at this point but show a 404
        show_404($segments[0]);
    }
}

Now one has to place this file like application/core/MY_Router.php and make sure he has subclass_prefix is defined as $config['subclass_prefix'] = 'MY_'; in application/config/config.php

Few extra lines of code has been added in method _validate_request():

while(count($segments) > 0 && is_dir(APPPATH.'controllers/'.$this->directory.$segments[0]))
{
    // Set the directory and remove it from the segment array
    $this->set_directory($this->directory . $segments[0]);
    $segments = array_slice($segments, 1);
}

It is used so that one can make use of multi-level subdirectory inside controllers directory, whereas normally we can use single level subdirectory inside controllers folder and can call it in url. One can remove this code if it not necessary but it has no harm on the normal flow.

Wednesday, March 31, 2021
 
Webroots
answered 7 Months ago
91

Say you needed to call a function called get_user_info that retrieves the users info from a database. You could have a function like this:

class Home extends Controller {

    function __construct() {
        parent::__construct();
    }

    function index() {
        $user = $this->get_user_info($_SESSION['user_id']);
        echo "Hello " . $user['first_name'];
    }

    function get_user_info($user_id) {
        $query = $this->db->query("SELECT * FROM users WHERE user_id = ?", array($user_id));
        return $query->row_array();
    }
}

However, what if you needed to call get_user_info on another page?

In this scenario you would have to copy and paste the function into every page making it difficult to maintain if you have lots of pages (what if you needed to change the query to JOIN onto another table?)

This also goes against the don't repeat yourself principle.

Models are meant to handle all data logic and representation, returning data already to be loaded into views. Most commonly they are used as a way of grouping database functions together making it easier for you to change them without modifying all of your controllers.

class User extends Model {

    function __construct() {
        parent::Model();
    }

    function get_user_info($user_id) {
        $query = $this->db->query("SELECT * FROM users WHERE user_id = ?", array($user_id));
        return $query->row_array();
    }

}

In the above we have now created a model called user. In our home controller we can now change the code to read:

class Home extends Controller {

    function __construct() {
        parent::__construct();
    }

    function index() {
        $this->load->model('user');
        $user = $this->user->get_user_info($_SESSION['user_id']);
        echo "Hello " . $user['first_name'];
    }
}

And now you can change your get_user_info function without having to change X number of controllers that rely on the same function.

Wednesday, March 31, 2021
 
danjah
answered 7 Months ago
38

Update in model

replace

public function get_post($id) {
   $query = $this->db->get_where('posts', array('id' => $id));

with

public function get_post($slug) {
    $query = $this->db->get_where('posts', array('slug' => $slug)); 

Update in Controller

replace

public function post($id) {

with

public function post($slug) {

and replace

$data['post'] = $this->Posts_model->get_post($id);

with

$data['post'] = $this->Posts_model->get_post($slug);
Wednesday, March 31, 2021
 
Novalirium
answered 7 Months ago
74

Where does $slug come from?

Your URI looks like this: www.example.com/controller/method/arg1/arg2/arg3 (w/o query string)

Is there anything missing?

Well, there is few things you should do:

  1. Use autoload (config/autoload.php) to load your most used models, or if this model is not used widely at least load it at class constructor.

  2. You are not passing $slug argument to model method, this will not work even if you will fix your routing.

    $data['single_news'] = $this->news_model->get_single();

  3. Better to show 404 if you can't find a slug, don't refetch all data on fail.

To fix 404 error follow those steps:

  1. Check your .htaccess file for errors

  2. Check uri_protocol setting in config

  3. Try routing like this $route['news/((.+)$)?'] = "news/singe_news/$1"; (this should replace both routes for news)

Saturday, May 29, 2021
 
eliotlencelot
answered 5 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :