Generate SEO friendly URLs (slugs) [closed]
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Definition
A slug is the part of a URL which identifies a page using human-readable keywords. To make the URL easier for users to type, special characters are often removed or replaced as well. For instance, accented characters are usually replaced by letters from the English alphabet; punctuation marks are generally removed; and spaces (which have to be encoded as %20 or +) are replaced by dashes (-) or underscores (_), which are more aesthetically pleasing.
Context
I developed a photo-sharing website on which users can upload, share and view photos. All pages are generated automatically without my grip on the title. Because the title of a photo or the name of a user may contain accented characters or spaces, I needed a function to automatically create slugs and keep readable URLs. I created the following function which replaces accented characters (âèêëçî), removes punctuation and bad characters (#@&~^!) and transforms spaces in dashes.
Questions
- What do you think about this function?
- Do you know any other functions to create slugs?
Code
You might want to delete this question here and repost on codereview.stackexchange.com because feedback and improve it is more on-topic there.
8 Answers 8
I like the php-slugs code at google code solution. But if you want a simpler one that works with UTF-8:
function format_uri( $string, $separator = '-' ) < $accents_regex = '~&([a-z])(?:acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i'; $special_cases = array( '&' => 'and', "'" => ''); $string = mb_strtolower( trim( $string ), 'UTF-8' ); $string = str_replace( array_keys($special_cases), array_values( $special_cases), $string ); $string = preg_replace( $accents_regex, '$1', htmlentities( $string, ENT_QUOTES, 'UTF-8' ) ); $string = preg_replace("/[^a-z0-9]/u", "$separator", $string); $string = preg_replace("/[$separator]+/u", "$separator", $string); return $string; >
A few people have linked to «php-slugs» on google.com, but it looks like their page is a little screwy now, so here it is if anyone needs it:
// source: https://code.google.com/archive/p/php-slugs/ function my_str_split($string) < $slen=strlen($string); for($i=0; $i; > return $sArray; > function noDiacritics($string) < //cyrylic transcription $cyrylicFrom = array('А', 'Б', 'В', 'Г', 'Д', 'Е', 'Ё', 'Ж', 'З', 'И', 'Й', 'К', 'Л', 'М', 'Н', 'О', 'П', 'Р', 'С', 'Т', 'У', 'Ф', 'Х', 'Ц', 'Ч', 'Ш', 'Щ', 'Ъ', 'Ы', 'Ь', 'Э', 'Ю', 'Я', 'а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и', 'й', 'к', 'л', 'м', 'н', 'о', 'п', 'р', 'с', 'т', 'у', 'ф', 'х', 'ц', 'ч', 'ш', 'щ', 'ъ', 'ы', 'ь', 'э', 'ю', 'я'); $cyrylicTo = array('A', 'B', 'W', 'G', 'D', 'Ie', 'Io', 'Z', 'Z', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'R', 'S', 'T', 'U', 'F', 'Ch', 'C', 'Tch', 'Sh', 'Shtch', '', 'Y', '', 'E', 'Iu', 'Ia', 'a', 'b', 'w', 'g', 'd', 'ie', 'io', 'z', 'z', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', 't', 'u', 'f', 'ch', 'c', 'tch', 'sh', 'shtch', '', 'y', '', 'e', 'iu', 'ia'); $from = array("Á", "À", "Â", "Ä", "Ă", "Ā", "Ã", "Å", "Ą", "Æ", "Ć", "Ċ", "Ĉ", "Č", "Ç", "Ď", "Đ", "Ð", "É", "È", "Ė", "Ê", "Ë", "Ě", "Ē", "Ę", "Ə", "Ġ", "Ĝ", "Ğ", "Ģ", "á", "à", "â", "ä", "ă", "ā", "ã", "å", "ą", "æ", "ć", "ċ", "ĉ", "č", "ç", "ď", "đ", "ð", "é", "è", "ė", "ê", "ë", "ě", "ē", "ę", "ə", "ġ", "ĝ", "ğ", "ģ", "Ĥ", "Ħ", "I", "Í", "Ì", "İ", "Î", "Ï", "Ī", "Į", "IJ", "Ĵ", "Ķ", "Ļ", "Ł", "Ń", "Ň", "Ñ", "Ņ", "Ó", "Ò", "Ô", "Ö", "Õ", "Ő", "Ø", "Ơ", "Œ", "ĥ", "ħ", "ı", "í", "ì", "i", "î", "ï", "ī", "į", "ij", "ĵ", "ķ", "ļ", "ł", "ń", "ň", "ñ", "ņ", "ó", "ò", "ô", "ö", "õ", "ő", "ø", "ơ", "œ", "Ŕ", "Ř", "Ś", "Ŝ", "Š", "Ş", "Ť", "Ţ", "Þ", "Ú", "Ù", "Û", "Ü", "Ŭ", "Ū", "Ů", "Ų", "Ű", "Ư", "Ŵ", "Ý", "Ŷ", "Ÿ", "Ź", "Ż", "Ž", "ŕ", "ř", "ś", "ŝ", "š", "ş", "ß", "ť", "ţ", "þ", "ú", "ù", "û", "ü", "ŭ", "ū", "ů", "ų", "ű", "ư", "ŵ", "ý", "ŷ", "ÿ", "ź", "ż", "ž"); $to = array("A", "A", "A", "AE", "A", "A", "A", "A", "A", "AE", "C", "C", "C", "C", "C", "D", "D", "D", "E", "E", "E", "E", "E", "E", "E", "E", "G", "G", "G", "G", "G", "a", "a", "a", "ae", "ae", "a", "a", "a", "a", "ae", "c", "c", "c", "c", "c", "d", "d", "d", "e", "e", "e", "e", "e", "e", "e", "e", "g", "g", "g", "g", "g", "H", "H", "I", "I", "I", "I", "I", "I", "I", "I", "IJ", "J", "K", "L", "L", "N", "N", "N", "N", "O", "O", "O", "OE", "O", "O", "O", "O", "CE", "h", "h", "i", "i", "i", "i", "i", "i", "i", "i", "ij", "j", "k", "l", "l", "n", "n", "n", "n", "o", "o", "o", "oe", "o", "o", "o", "o", "o", "R", "R", "S", "S", "S", "S", "T", "T", "T", "U", "U", "U", "UE", "U", "U", "U", "U", "U", "U", "W", "Y", "Y", "Y", "Z", "Z", "Z", "r", "r", "s", "s", "s", "s", "ss", "t", "t", "b", "u", "u", "u", "ue", "u", "u", "u", "u", "u", "u", "w", "y", "y", "y", "z", "z", "z"); $from = array_merge($from, $cyrylicFrom); $to = array_merge($to, $cyrylicTo); $newstring=str_replace($from, $to, $string); return $newstring; >function makeSlugs($string, $maxlen=0) < $newStringTab=array(); $string=strtolower(noDiacritics($string)); if(function_exists('str_split')) < $stringTab=str_split($string); >else < $stringTab=my_str_split($string); >$numbers=array("0","1","2","3","4","5","6","7","8","9","-"); //$numbers=array("0","1","2","3","4","5","6","7","8","9"); foreach($stringTab as $letter) < if(in_array($letter, range("a", "z")) || in_array($letter, $numbers)) < $newStringTab[]=$letter; >elseif($letter==" ") < $newStringTab[]="-"; >> if(count($newStringTab)) < $newString=implode($newStringTab); if($maxlen>0) < $newString=substr($newString, 0, $maxlen); >$newString = removeDuplicates('--', '-', $newString); > else < $newString=''; >return $newString; > function checkSlug($sSlug) < if(preg_match("/^[a-zA-Z0-9]+[a-zA-Z0-9\-]*$/", $sSlug) == 1) < return true; >return false; > function removeDuplicates($sSearch, $sReplace, $sSubject) < $i=0; do< $sSubject=str_replace($sSearch, $sReplace, $sSubject); $pos=strpos($sSubject, $sSearch); $i++; if($i>100) < die('removeDuplicates() loop error'); >>while($pos!==false); return $sSubject; >
Rather than a huge horrible and incomplete list of replacements, you’d be better off normalizing the string and then removing non-ascii characters
@BlueRaja-DannyPflughoeft Since this is Google’s original code I’m not going to edit it. I’d encourage you to add another answer with improvements to this code.
@SirDerpington I am wondering if this answer should be editable, since it is effectively a copy-paste of code.google.com/archive/p/php-slugs
@rybo111 Yeah I know what you mean. I think it should be since — I don’t know why- some characters in the $to and $from arrays were missing. It just said «?» instead of the actual char.
setlocale(LC_ALL, 'en_US.UTF8'); function slugify($text) < // replace non letter or digits by - $text = preg_replace('~[^\\pL\d]+~u', '-', $text); // trim $text = trim($text, '-'); // transliterate $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); // lowercase $text = strtolower($text); // remove unwanted characters $text = preg_replace('~[^-\w]+~', '', $text); if (empty($text)) < return 'n-a'; >return $text; > $slug = slugify($var);
I found this on the net, does exactly as you want, but keeps the case.
This isn’t very robust as it’s only able to handle the characters listed. What about Cyrillic? Hebrew? Other obscure non-ASCII symbols like ² , º , ‘ , etc.?
This really works fine. Returns correct clean url slug.
$string = '(1234) S*m@#ith S)&+*t `E>land - - 1!_2)#3)(*4""5'; // remove all non alphanumeric characters except spaces $clean = preg_replace('/[^a-zA-Z0-9\s]/', '', strtolower($string)); // replace one or multiple spaces into single dash (-) $clean = preg_replace('!\s+!', '-', $clean); echo $clean; // 1234-smith-st-exampleland-12345
This code will cause the elimination of all the characters that are not in the regex, it’s like a white list solution. But be careful because most of the international programmers will need a solution that transforms «café» into «cafe» and not into «caf» like this code does.
function seourl($phrase, $maxLength = 100000000000000)
function remove_accents($string) < $a = 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûýýþÿŔŕ'; $b = 'aaaaaaaceeeeiiiidnoooooouuuuybsaaaaaaaceeeeiiiidnoooooouuuyybyRr'; $string = strtr(utf8_decode($string), utf8_decode($a), $b); return utf8_encode($string); >function format_slug($title) < $title = remove_accents($title); $title = trim(strtolower($title)); $title = preg_replace('#[^a-z0-9\\-/]#i', '_', $title); return trim(preg_replace('/-+/', '-', $title), '-/'); >
This is the class we use and while it can perform individual operations, it also has the ability to turn strings (or paths) into slug versions (only a-z , 0-9 , and — are in the final output). It also does a couple extra things such as convert ampersands ( & ) to the word and .
echo (new Str('My Cover Letter & Résumé'))->slugify()->__toString();
string = $string; > public function __toString() < return $this->string; > public function cleanForUrlPath(): self < $path = ''; // Loop through path sections (separated by `/`) // and slugify each section. foreach (explode('/', $this->string) as $section) < $section = (new static($section))->slugify()->__toString(); if ($section !== '') < $path .= "/$section"; >> // Save the cleaned path $this->string = "$path/"; return $this; > public function cleanUpSlugDashes(): self < // Remove extra dashes $this->string = preg_replace('/--+/', '-', $this->string); // Remove leading and trailing dashes $this->string = trim($this->string, '-'); return $this; > /** * Replace symbols with word replacements. * Eg, `&` becomes ` and `. */ public function convertSymbolsToWords(): self < $this->string = strtr($this->string, [ '@' => ' at ', '%' => ' percent ', '&' => ' and ', ]); return $this; > public static function getSpacerCharacters( array $with = [], array $without = [] ): array < return array_unique(array_diff(array_merge([ ' ', // space '…', // ellipsis '–', // en dash '—', // em dash '/', // slash '\\', // backslash ':', // colon ';', // semi-colon '.', // period '+', // plus sign '#', // pound sign '~', // tilde '_', // underscore '|', // pipe ], array_values($with)), array_values($without))); >public function lower(): self < $this->string = strtolower($this->string); return $this; > /** * Replaces all accented characters * with similar ASCII characters. */ public function removeAccents(): self < // If no accented characters are found, // return the given string as-is. if (!preg_match('/[\x80-\xff]/', $this->string)) < return $this; >// Instantiate Transliterator if we haven't already if (!isset(self::$accent_transliterator)) < self::$accent_transliterator = Transliterator::create( 'Any-Latin; Latin-ASCII;' ); if (self::$accent_transliterator === null) < // @codeCoverageIgnoreStart throw new RuntimeException( 'Could not create a transliterator' ); // @codeCoverageIgnoreEnd >> // Save transliterated string $this->string = (self::$accent_transliterator)->transliterate( $this->string ); return $this; > public function replace($search, $replace) < $this->string = str_replace($search, $replace, $this->string); return $this; > public function replaceRegex($pattern, $replacement): self < $this->string = preg_replace($pattern, $replacement, $this->string); return $this; > /** * @param int $length number of bytes to shorten the string to */ public function shorten(int $length): self < // If the string is already `$length` or shorter, // return it as-is. if (strlen($this->string) // Shorten by 2 additional characters // to account for the three periods that are appended. // Only need to shorten by 2 // as there's always at least one character (space) removed // when the last word is popped off of the array. $length -= 2; // Shorten the string to `$length` and split into words $words = explode(' ', substr($this->string, 0, $length)); // Discard the last word as it's a partial word, // or empty if the last character happened to be a space. // If there's only one word, // then it was longer than `$length` // and the truncated version should be returned. if (count($words) > 1) < array_pop($words); >// Save the shortened string with ". " appended $this->string = rtrim(implode(' ', $words), ':').'. '; return $this; > public function slugify(): self < // If the string is already a slug if (preg_match('/^[a-z0-9\\-]+$/', $this->string)) < return $this; >// - Normalize accents // - Normalize symbols // - Lowercase // - Replace space characters with dashes // - Remove non-slug characters // - Clean up leading, trailing, and consecutive dashes return $this ->removeAccents() ->convertSymbolsToWords() ->lower() ->spacersToDashes() ->replaceRegex('/([^a-z0-9\\-]+)/', '') ->cleanUpSlugDashes(); > public function spacersToDashes(): self < return $this->replace(static::getSpacerCharacters(), '-'); > >
How to Create URL Slug from String in PHP
The creation of a user-friendly URL is an essential part of the work with any website. It is crucial for the SEO of the webpage. Moreover, it helps the visitors to assure that the website is correct. Let’s consider the following URL:
http://www.example.com/this is the example demo page.
It will be rendered by the browser in this way:
http://www.example.com/this%20is%20the%20example%20demo%20page
So, you can notice, that the URL above can’t be considered as a user-friendly one. Hence, it is necessary to interchange the spaces with hyphens. Here is how it can be done:
function createUrlSlug($urlString) < $slug = preg_replace('/[^A-Za-z0-9-]+/', '-', $urlString); return $slug; > echo createUrlSlug('this is the example demo page'); // This will return 'this-is-the-example-demo-page' ?>
A More Extended Version
In this section, we will show you an extensive version of the URL slug function. With the help of this version, you will manage to detect double hyphens and change them with one, as well as to deal with characters with any accents. Here is a detailed example:
function slugify($urlString) < $search = ['Ș', 'Ț', 'ş', 'ţ', 'Ş', 'Ţ', 'ș', 'ț', 'î', 'â', 'ă', 'Î', ' ', 'Ă', 'ë', 'Ë']; $replace = ['s', 't', 's', 't', 's', 't', 's', 't', 'i', 'a', 'a', 'i', 'a', 'a', 'e', 'E']; $str = str_ireplace($search, $replace, strtolower(trim($urlString))); $str = preg_replace('/[^\w\d\-\ ]/', '', $str); $str = str_replace(' ', '-', $str); return preg_replace('/\-', '-', $str); > ?>
About URL Slugs
A slug is considered a human-friendly, easy-readable identifier, applied for detecting a resource.
As a rule, a slug is used when one intends to refer to an item, upholding the capability of seeing what the given item is. A good slug should be unique, simple, as well as descriptive. The main point of using slugs is improving the SEO and helping the visitors to make sure the given website is correct.