Clean url generators are crucial for search engine optimization or just to tidy up the site navigation. They are even more important if you work with international characters, accented vowels /à, è, ì, .../, cedilla /ç/, dieresis /ë/, tilde /ñ/ and so on.
First of all we need to strip all special characters and punctuation away. This is easily accomplished with something like:
function toAscii($str) {
$clean = preg_replace("/[^a-zA-Z0-9\/_|+ -]/", '', $str);
$clean = strtolower(trim($clean, '-'));
$clean = preg_replace("/[\/_|+ -]+/", '-', $clean);
return $clean;
}
With our toAscii function we can convert a string like “Hi! I’m the title of your page!” to hi-im-the-title-of-your-page. This is nice, but what happens with a title like “A piñata is a paper container filled with candy”?
The result will be a-piata-is-a-paper-container-filled-with-candy, which is not cool. We need to convert all special characters to the closest ascii character equivalent.
There are many ways to do this, maybe the easiest is by using iconv.
setlocale(LC_ALL, 'en_US.UTF8');
function toAscii($str) {
$clean = iconv('UTF-8', 'ASCII//TRANSLIT', $str);
$clean = preg_replace("/[^a-zA-Z0-9\/_| -]/", '', $clean);
$clean = strtolower(trim($clean, '-'));
$clean = preg_replace("/[\/_| -]+/", '-', $clean);
return $clean;
}
I always work with UTF-8 but you can obviously use any character encoding recognized by your system. The piñata text is now transliterated into a-pinata-is-a-paper-container-filled-with-candy. Lovable.
If they are not Spanish, users will hardly search your site for the word piñata, they will most likely search for pinata. So you may want to store both versions in your database. You may have a title field with the actual displayed text and a slug field containing its ascii version counterpart.
We can add a delimiter parameter to our function so we can use it to generate both clean urls and slugs (in newspaper editing, a slug is a short name given to an article that is in production, source).
setlocale(LC_ALL, 'en_US.UTF8');
function toAscii($str, $delimiter='-') {
$clean = iconv('UTF-8', 'ASCII//TRANSLIT', $str);
$clean = preg_replace("/[^a-zA-Z0-9\/_|+ -]/", '', $clean);
$clean = strtolower(trim($clean, '-'));
$clean = preg_replace("/[\/_|+ -]+/", $delimiter, $clean);
return $clean;
}
// echo toAscii("A piñata is a paper container filled with candy.", ' ');
// returns: a pinata is a paper container filled with candy
There’s one more thing. The string “I’ll be back!” is converted to ill-be-back. This may or may not be an issue depending on your application. If you use the function to generate a searchable slug for example, looking for “ill” would return the famous Terminator quote that probably isn’t what you wanted.
setlocale(LC_ALL, 'en_US.UTF8');
function toAscii($str, $replace=array(), $delimiter='-') {
if( !empty($replace) ) {
$str = str_replace((array)$replace, ' ', $str);
}
$clean = iconv('UTF-8', 'ASCII//TRANSLIT', $str);
$clean = preg_replace("/[^a-zA-Z0-9\/_|+ -]/", '', $clean);
$clean = strtolower(trim($clean, '-'));
$clean = preg_replace("/[\/_|+ -]+/", $delimiter, $clean);
return $clean;
}
You can now pass custom delimiters to the function. Calling toAscii("I'll be back!", "'") you’ll get i-ll-be-back. Also note that the apostrophe is replaced before the string is converted to ascii as character encoding conversion may lead to weird results, for example é is converted to 'e, so the apostrophe needs to be parsed before the string is mangled by iconv.
The function seems now complete. Lets stress test it.
echo toAscii("Mess'd up --text-- just (to) stress /test/ ?our! `little` \\clean\\ url fun.ction!?-->");
returns: messd-up-text-just-to-stress-test-our-little-clean-url-function
echo toAscii("Perché l'erba è verde?", "'"); // Italian
returns: perche-l-erba-e-verde
echo toAscii("Peux-tu m'aider s'il te plaît?", "'"); // French
returns: peux-tu-m-aider-s-il-te-plait
echo toAscii("Tänk efter nu – förr'n vi föser dig bort"); // Swedish
returns: tank-efter-nu-forrn-vi-foser-dig-bort
echo toAscii("ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöùúûüýÿ");
returns: aaaaaaaeceeeeiiiidnooooouuuuyssaaaaaaaeceeeeiiiidnooooouuuuyy
echo toAscii("Custom`delimiter*example", array('*', '`'));
returns: custom-delimiter-example
echo toAscii("My+Last_Crazy|delimiter/example", '', ' ');
returns: my last crazy delimiter example
I’m sure we are far from perfection and probably some php/regex guru will soon bury me under my ignorance suggesting an über-simple alternative to my function. What do you thing?

Cool. I’ve seen some articles about this topic, but they’re not complete as this. If the language is English, everything seems to be OK, but with other languages, it’s not simple. I’ve tried many methods to take n first characters of a string, but I was not successful. This post helps me a lot. Thank you very much.
I’m liking it. A few minor tweaks and I think it’s something i’ll be implementing in a few of my applications!
Thanks for the post - good work!
Rgds,
JB
[...] The perfect PHP Clean URL Generator - The perfect slug generator for PHP developers! [...]
very very lovable!! thanks!
BUT, there were a BUT.. i’ve had a little problem:
echo toAscii(”Perché l’erba è verde?”, “‘”); // Italian
didn’t return: perche-l-erba-e-verde
but it returned:
Notice: iconv() [function.iconv]: Detected an illegal character in input string in {file} on line {line}
perch
so i’ve add a little line.. and it solved (or it seems to solve).
so here you’re the new function:
function toAscii($str, $replace=array(), $delimiter='-', $charset='ISO-8859-1') {$str = iconv($charset, 'UTF-8', $str); // by lelebart
if( !empty($replace) ) {
$str = str_replace((array)$replace, ' ', $str);
}
$clean = iconv('UTF-8', 'ASCII//TRANSLIT', $str);
$clean = preg_replace("/[^a-zA-Z0-9\/_|+ -]/”, ”, $clean);
$clean = strtolower(trim($clean, ‘-’));
$clean = preg_replace(”/[\/_|+ -]+/”, $delimiter, $clean);
return $clean;
}
hope to be helpul/useful
@lelebart, that depends on the fact that you are probably working in latin-1 instead of utf-8. If you work in iso-8859-1 you can simply replace all references to UTF-8 into ISO-8859-1. You don’t need to convert latin-1 to uft-8 first.
@Matteo Spinelli: thanks. i didn’t know that. ^^
so i need a simple line like that?
$clean = iconv(’ISO-8859-1′, ‘ASCII//TRANSLIT’, $str);
@lelebart, yep. Also remember to set the right setlocale.
I can’t thank you enough for this wonderful script.
Before I found this I used to clean urls using str_replace for each kind of special character…this script has saved me tons of time.
!! 5 STARS !!
Very nice indeed!!!
Wow,
This is the shortest solution I’ve ever seen. Nice, clean code, congrats and thanks.
Thank you very much, it’s just what I was looking for this morning.
Just one thing: I don’t understand what you intended to do with:
$str = str_replace((array)$replace, ‘ ‘, $str);
and how do you expect to pass the $replace parameter. The default is array() but in the example below you pass “‘” that is a string and in the function i’ts converted to array(”‘”). But what if I pass “‘,;.”, it will be converted into array(”‘,;.”) by (array)$replace and it won’t work as expected in str_replace(). I should pass the $replace parameter directly as an array(”‘”,”,”,”;”,”.”) instead.
Hope it helps if I got the point, otherwise I didn’t get that part of the code.
If the above is valid, then if you want to specify the $delimiter using the default $replace empty array(), you may want to modify the function to check for $replace type with is_string or !is_array:
if(is_string($replace)) { // gonna use default $replace
$delimiter = $replace;
$replace = array();
}
Bye.
@francesco: yes the $replace parameter must be an array so that you can specify multiple character delimiters (have a look at the custom delimiters example). It shouldn’t be needed to check if the parameter is a string or an array. I convert it to array anyway and it should work.
If you don’t want to worry about setlocale() causing you problems, I’d recommend merging this example with the example on this page - http://www.randomsequence.com/articles/removing-accented-utf-8-characters-with-php/
Possibly the best article on the subject. Thank you.
It’s very nice… but how about website that are not in a US locale?
setlocale(LC_ALL, ‘en_US.UTF8′);
It will mess up the settings, is there anyway around this?
@Chris,
you can use any locale you have installed on your system, I’d suggest to stick with UTF8 though (it_IT.UTF8, fr_FR.UTF8, etc…). If that is not possible remember to change the iconv parameter as well.
In your third and fourth examples, you trim the cleaned string from dashes. Is there a reason for this, or should it be trimmed by the delimiter?
$clean = strtolower(trim($clean, ‘-’));
to
$clean = strtolower(trim($clean, $delimiter));
Aled, thanks for pointing that out. Actually you’d need to trim for all the following characters: /_|+ -
It’s just to be sure that the string doesn’t end with a delimiter.
The perfect PHP clean url generator…
via: http://cubiq.org/the-perfect-php-clean-url-generator/12
In my hunt for the perfect clean url (smart url, slug, permalink, whatever) generator Ive always slipped in some exception or bug that made the function a piece of junk. But I recently foun…
This is an odd request, but if I wanted to keep UTF-8 characters but continue to strip miscellaneous characters such as ASCII punctuation, how would I modify this function?
I know it’s not SEO preferred but our niche apps need their native encoding (UTF-8) and not ASCII substitutions.
@Mike: I’m afraid you have to create an array of chatacters you wish to substitute.
Sorry for double comment, I tried the original function and it’s simply deleting all our Russian/Chinese/etc. encoded strings. If you could help me with a modification that keeps UTF-8 characters but cleans out punctuation I would be much obliged. =)
@Matteo: Sorry I didn’t see your comment before my last post.
I’m not sure I need an array, I’m not trying to keep any specific UTF-8 characters. I’m just trying to keep all UTF-8 characters but do the rest of your function (strip punctuation, replace whitespace with dashes, etc. slug preparing). I’m been scouring the net for a function that just strips punctuation/prepares slugs but leaves UTF-8 untouched.
I found a Python version of what I want but not sure how to convert it to PHP:
http://semicolons.org/archives/friendly-urls.html