IDs are often numbers. Unfortunately there are only 10 digits to work with, so if you have a lot of records, IDs tend to get very lengthy. For computers that’s OK. ButOther title options where
- How to create unique short string IDs with PHP & MySQL
- Or how to create IDs similar to YouTube e.g. yzNjIBEdyww
I created this function a long time ago. Time to be nice and share. human beings like their IDs as short as possible. So how can we make IDs shorter? Well, we could borrow characters from the alphabet as have them pose as additional numbers…. Alphabet to the rescue!
More is Less - the ‘math’
The alphabet has 26 characters. That’s a lot more than 10 digits. If we also distinguish upper- and lowercase, and add digits to the bunch or the heck of it, we already have (26 x 2 + 10) 62 options we can use per position in the ID.
Now of course we can also add additional funny characters to ‘the bunch’ like - / * & # but those may cause problems in URLs and that’s our target audience for now.
OK so because there are roughly 6x more characters we will use per position, IDs will get much shorter. We can just fit a lot more data in each position.
This is basically what url shortening services do like tinyurl, is.gd, or bit.ly. But similar IDs can also be found at youtube: http://www.youtube.com/watch?v=yzNjIBEdyww
Convert your IDs
Now unlike Database servers: webservers are easy to scale so you can let them do a bit of converting to ease the life of your users, while keeping your database fast with numbers (MySQL really likes them plain numbers ;).
To do the conversion I’ve written a PHP function that can translate big numbers to short strings and vice versa. I call it: alphaID.
The resulting string is not hard to decipher, but it can be a very nice feature to make URLs or directorie structures more compact and significant.
So basically:
- when someone requests rLHWfKd
- alphaID() converts it to 999999999999
- you lookup the record for id 999999999999 in your database
Source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
| /**
* Translates a number to a short alhanumeric version
*
* Translated any number up to 9007199254740992
* to a shorter version in letters e.g.:
* 9007199254740989 --> PpQXn7COf
*
* specifiying the second argument true, it will
* translate back e.g.:
* PpQXn7COf --> 9007199254740989
*
* this function is based on any2dec && dec2any by
* fragmer[at]mail[dot]ru
* see: http://nl3.php.net/manual/en/function.base-convert.php#52450
*
* If you want the alphaID to be at least 3 letter long, use the
* $pad_up = 3 argument
*
* In most cases this is better than totally random ID generators
* because this can easily avoid duplicate ID's.
* For example if you correlate the alpha ID to an auto incrementing ID
* in your database, you're done.
*
* The reverse is done because it makes it slightly more cryptic,
* but it also makes it easier to spread lots of IDs in different
* directories on your filesystem. Example:
* $part1 = substr($alpha_id,0,1);
* $part2 = substr($alpha_id,1,1);
* $part3 = substr($alpha_id,2,strlen($alpha_id));
* $destindir = "/".$part1."/".$part2."/".$part3;
* // by reversing, directories are more evenly spread out. The
* // first 26 directories already occupy 26 main levels
*
* more info on limitation:
* - http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/165372
*
* if you really need this for bigger numbers you probably have to look
* at things like: http://theserverpages.com/php/manual/en/ref.bc.php
* or: http://theserverpages.com/php/manual/en/ref.gmp.php
* but I haven't really dugg into this. If you have more info on those
* matters feel free to leave a comment.
*
* @author Kevin van Zonneveld <kevin@vanzonneveld.net>
* @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
* @license http://www.opensource.org/licenses/bsd-license.php New BSD Licence
* @version SVN: Release: $Id: alphaID.inc.php 344 2009-06-10 17:43:59Z kevin $
* @link http://kevin.vanzonneveld.net/
*
* @param mixed $in String or long input to translate
* @param boolean $to_num Reverses translation when true
* @param mixed $pad_up Number or boolean padds the result up to a specified length
*
* @return mixed string or long
*/
function alphaID($in, $to_num = false, $pad_up = false)
{
$index = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$base = strlen($index);
if ($to_num) {
// Digital number <<-- alphabet letter code
$in = strrev($in);
$out = 0;
$len = strlen($in) - 1;
for ($t = 0; $t <= $len; $t++) {
$bcpow = bcpow($base, $len - $t);
$out = $out + strpos($index, substr($in, $t, 1)) * $bcpow;
}
if (is_numeric($pad_up)) {
$pad_up--;
if ($pad_up > 0) {
$out -= pow($base, $pad_up);
}
}
} else {
// Digital number -->> alphabet letter code
if (is_numeric($pad_up)) {
$pad_up--;
if ($pad_up > 0) {
$in += pow($base, $pad_up);
}
}
$out = "";
for ($t = floor(log10($in) / log10($base)); $t >= 0; $t--) {
$a = floor($in / bcpow($base, $t));
$out = $out . substr($index, $a, 1);
$in = $in - ($a * bcpow($base, $t));
}
$out = strrev($out); // reverse
}
return $out;
}
echo alphaID(27101984); //M2IPb
echo alphaID('M2IPb', TRUE); |
More features
- There also is an optional third argument: $pad_up. This enables you to make the resulting alphaId at least X characters long.
- You can support even more characters (making the resulting alphaID even smaller) by adding characters to the $index var at the top of the function body.
From Kevin van Zonneveld’s blog
Trong 1 dự án gặp đến 2 lỗi có thể nói là nghiêm trọng
- Cannot use object of type __PHP_Incomplete_Class as array
- 406 Not Acceptable
Lỗi thứ nhất “thường” xảy ra ở khu vực tương tự như sau:
1
2
3
4
5
6
| $user = isset($_SESSION['user'])?$_SESSION['user']:'';
if ($user['user_id']<0 || $user['user_id'] == '')
{
_redirect('user/login');
}
return true; |
Phần này (backend) tưởng chừng chả có lỗi gì (tôi làm phần frontend, 1 anh nữa làm phần backend) nhưng thật ra lỗi be bét bởi:
- Khi session user chưa được khởi tạo, biến $user sẽ = chuỗi trống
- Khi $user là chuỗi rỗng thì $user làm gì có phần tử user_id
Thêm vào nữa, đó là tên biến với tên session bị trùng nhau
Nếu chạy trên môi trường dev (XAMPP) thì không vấn đề gì xảy ra, nhưng chạy trên môi trường LAMPP (đặc biệt là trên server của Nhân Hòa) thì sẽ bị lỗi Cannot use object of type __PHP_Incomplete_Class as array
Thứ hai, lỗi 406 Not Acceptable. Không hiểu sao khi vào phần cấu hình của website, chỉ có 3 field dạng Wysiwyg editor (FCKeditor), cứ ấn save thì chết (hiện ra trang trắng xóa). Sau khi xem response code, thấy báo 406 mà chả hiểu tại sao lại bị. Toàn những ký tự có thể nói là hợp lệ vậy mà server nó vẫn từ chối là sao? Bí quá, debug bằng cách…test từng editor 1 :”> và nhận thấy rằng nó chết ở editor có chứa ảnh upload lên với đường dẫn dạng như sau: /home/xxx/public_html/uploads. Hình như SA của Nhân Hòa sợ local attack hay sao ấy mà cấm các truy cập kiểu này??? Không biết nữa, chỉ biết là khi tôi thay cái path đó bằng url path thì mọi thứ okie. Đến mệt
Hôm nay ở cty anh Linh đã hỏi có ai biết hàm kiểm tra thư mục đã tồn tại hay chưa không? (anh ấy đang làm cái liên quan đến theme thì phải :-?). Theo phản xạ tôi bật ngay ra hàm is_dir, ngay lúc đấy chỉ biết hàm is_dir là kiểm tra xem tên truyền vào có phải là thư mục hay không, đúng như tên gọi của hàm: is_dir. Mà dù sao thì phải là thư mục thì mới tồn tại được chứ nhỉ?
Còn thằng ku Trường nó bảo dùng hàm file_exists. Lúc đó tôi cũng chẳng biết là file_exists có thể check exists cả file lẫn folder :”> Tuy nhiên cái cần nhấn mạnh ở đây là: tôi đã có lần từng dùng hàm file_exists để kiểm tra sự tồn tại của 1 file và luôn nhận được kết quả FALSE mặc dù file nó nằm chình ình ra đấy nên tôi khá e dè trong việc sử dụng hàm này. Khi phải check sự tồn tại của file hay thư mục thì tôi hay dùng is_file và is_dir.
Lúc đó cũng chí chóe 1 lúc nhưng lý do chính để bảo nên dùng is_dir thì tôi chẳng thế nhớ được. Đang bận làm nên không có thời gian search (nhớ không nhầm thì trong PHPVietnam cũng có nhắc đến => nó đây: http://groups.google.com/group/phpvietnam/browse_thread/thread/88631b0e7cec117/199d1957c9df6e73?lnk=gst&q=file_exists#199d1957c9df6e73)
Bây giờ test thử xem nào:
1
2
3
4
5
6
7
8
9
10
| exec('chown mrkhanh test_dir');
if( file_exists('test_dir') )
{
echo 'test_dir is existed.';
}
else
{
echo 'test_dir does not exist.';
} |
Mới chạy thử trên host (bluehost) thì thấy nó chỉ hiện trang trắng, error 500 :”>
Để hôm nào test trên thằng Ubuntu vậy :-s
Nhưng mà PHP manual nói rồi, chẳng lẽ sai :-”
This function returns FALSE for files inaccessible due to safe mode restrictions. However these files still can be included if they are located in safe_mode_include_dir.
Còn đây là giải thích của anh pcdinh (Phạm Công Định):
file template được up lên với sở hữu của root trong khi apache lại chạy dưới 1 user khác.
Hôm nay tôi gặp phải 1 “tình huống” khá là củ chuối. Phần upload file (thật ra là upload file để update lại các store - khá nhiều, hiện tại là 1500 store) có validate file size, file type. Tôi test “khá kỹ ” ở local & mọi thứ đều ổn. Thông số local của tôi như sau:
- Windows XP/Vertrigo
- post_max_size = 100M
- upload_max_size = 20M
Khi up 1 file có dung lượng quá 20MB (<100MB) thì hệ thống hiện thông báo rất đẹp với nội dung là bạn đã upload quá dung lượng quy định bla bla…:-@
Thế nhưng sau khi request done trên PMS (project management system), bên test report lại là khi upload 1 file lớn hơn 16MB thì chẳng hiện gì cả, không lỗi & giống như tự F5 lại trang.
Xem phpinfo của môi trường trên testing thì như sau:
- post_max_size = 8M
- upload_max_size = 16M
Vậy là dung lượng vượt quá post_max_size rồi, và PHP đã làm thế này đối với đoạn code của tôi:
If the size of post data is greater than post_max_size, the $_POST and $_FILES superglobals are empty
Thế cho nên đoạn code:
1
2
3
4
| $postMaxSize = int_get('post_max_size');
if($postMaxSize < $userFile['size']) {
//process error
} |
chẳng hoạt động gì cả 
Trên php.net có 1 trick để xử lý trường hợp này:
This can be tracked in various ways, e.g. by passing the $_GET variable to the script processing the data, i.e.
Và đây là đoạn code của tôi:
1
2
3
| if(isset($_GET['processed']) && !count($_POST)) {
die('Error message here...');
} |
Chạy ngon
Rất cảm ơn anh NBThanh (Nguyễn Bá Thành) đã giúp đỡ em khắc phục vấn đề này
Ở bài trước tôi có đề cập đến việc export dữ liệu ra file CSV. Tuy nhiên, nếu export dữ liệu có chứa các ký tự Unicode thì sẽ không hiển thị được mặc dù khi mở bằng Notepad thì vẫn hiển thị đúng, tuy nhiên khi mở bằng Excel thì không hiển thị chính xác. Search trên mạng thấy giải pháp của anh Nguyễn Văn Hùng (Hưng?) đã giải quyết được (tôi mới test với German characters trong dự án Shop24 - rất okie).
Bài toán: export dữ liệu tiếng Việt UTF-8 thành file CSV có thể hiển thị đúng khi mở bằng Excel.
3 điểm dẫn đến thành công:
+ Dùng TAB (\t) thay cho COMMA (,) để phân tách các cột
+ Convert Encoding của dữ liệu cần output bằng UTF-16LE
+ Gắn chr(255)chr(254) vào đầu của kết quả cuối cùng trước khi output
PHP code đầy đủ (export order list):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
| /**
* EXPORT ORDER LIST TO CSV FILE
* @author khanhdn
*/
function exportCSV() {
global $user;
member_access();
$memberInfo = member_info();
$result = drupal_query("SELECT order_id
,order_code
,bill_firstname
,bill_lastname
,order_modify_date
,gand_total
,order_status
FROM {order}
WHERE shop_code = '".$memberInfo['shop_code']."'
ORDER BY order_creation_date DESC");
$status_options = array(
'1' => 'Neu'
,'2' => 'In Bearbeitung'
,'3' => 'Auf der Post'
,'4' => 'Ausgeführt'
,'5' => 'Zurück'
);
$csv = "Order code\tCustomer Name\tModify Date\tOrder Total\tOrder Status\r\n";
if(count($result['data']))
{
foreach($result['data'] as $row)
{
$order_list = array(
'order_code' => "$row->order_code"
,'customer_name' => $row->bill_firstname. ' ' .$row->bill_lastname
,'modify_date' => mysqlTimestamp(strtotime($row->order_modify_date),'d.m.Y')
,'order_total' => 'CHF '.$row->gand_total
,'order_status' => $status_options[$row->order_status]
);
$csv .= join("\t", $order_list)."\r\n";
}
}
$csv = chr(255).chr(254).mb_convert_encoding($csv, "UTF-16LE", "UTF-8");
header("Content-type: application/x-msdownload");
header("Content-disposition: csv; filename=" . date("Y-m-d") .
"_order_list.csv; size=".strlen($csv));
echo $csv;
exit();
} |
Source: Nguyễn Văn Hùng weblog
Hôm nay tôi dính phải 1 bug khá củ chuối: sau khi submit form, back button không hoạt động trên IE6 browser. Khi click vào nút back này sẽ xuất hiện 1 thông báo như sau:
Warning: page has expired The page you requested was created using information you submitted in a form. This page is no longer available. As a security precaution, Internet Explorer does not automatically resubmit your information for you.
To resubmit your information and view this Web page, click the Refresh button.
Sau một hồi tham khảo các cách giải quyết trên mạng thì sinh ra được cái hàm này:
1
2
3
4
5
6
7
8
9
10
11
12
13
| /**
* Fix back button on IE6 (stupid) browser
* @author khanhdn
*/
function fixBackButtonOnIE() {
//drupal_set_header("Expires: Sat, 27 Oct 1984 08:52:00 GMT GMT"); // Always expired (1)
//drupal_set_header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified (2)
drupal_set_header("Cache-Control: no-store, no-cache, must-revalidate"); // HTTP/1.1 (3)
drupal_set_header("Cache-Control: public"); (4)
drupal_set_header("Pragma: no-cache"); // HTTP/1.0 (5)
ini_set('session.cache_limiter', 'private'); (6)
} |
Tạm comment cái (1) (2), vẫn chạy okie. Có lẽ chỉ cần giữ lại (3) (4) (5) là đủ???
Dù sao cũng giải quyết được cái bug này >”<
Mấy hôm trước lại được làm 1 task liên quan đến thao tác với thư mục bằng PHP
Thật ra trước đó đã từng làm rồi nhưng giờ vẫn khoái…viết lại, bởi quan trọng nhất là cái class đó…không còn trong máy tính ở cty nữa
Ở nhà thì ko tiện remote connect vào (có ai mở hộ máy đâu mà vào :-p) nên quyết định viết lại & phải chạy được cả trên Unix & Windows.
Class DirManager tạm thời có mấy method sau:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
| <?php
/**
* File manager class
* @author khanhdn
*/
class DirManager {
public $sourcePath = '';
public $destinationPath = '';
private $operaSystem = '';
static $instance;
public function getInstance()
{
if (self::$instance == null) {
self::$instance = new DirManager();
}
return self::$instance;
}
public function __construct() {
}
/**
* check opera system
* @return bool true if Linux, false if Windows
*/
private function isLinuxOS() {
$this->operaSystem = $_SERVER['SERVER_SOFTWARE'];
if(stripos($this->operaSystem, 'Win32')) {
return false;
}
return true;
}
/**
* Create directory
* @param string folderName
*/
public function createDir() {
if(!is_dir($this->sourcePath)) {
if(!mkdir($this->sourcePath, 0777)) {
die('Could not create sub-domain.');
}
}
else {
die($this->sourcePath.' is exist.');
}
}
/**
* copy using php function
* @param string source
* @param string dest
*/
private function windowsCopyDir($source, $dest)
{
if(!is_dir($dest)) {
mkdir($dest, 0777);
}
if($curdir = opendir($source)) {
while($file = readdir($curdir)) {
if($file != '.' && $file != '..') {
$srcfile = $source . '/' . $file;
$dstfile = $dest . '/' . $file;
if(is_file($srcfile)) {
copy($srcfile, $dstfile);
}
else if(is_dir($srcfile)) {
$this->windowsCopyDir($srcfile, $dstfile);
}
}
}
closedir($curdir);
}
}
/**
* Copy directory and all sub directory in it
* @param bool useCommand
*/
public function copyDir($useCommand=true) {
if(is_dir($this->sourcePath)) {
if($this->isLinuxOS()) {
if($useCommand) {
exec("cd {$this->sourcePath}; shopt -s dotglob; cp -pr . {$this->destinationPath}");
}
else {
$this->WindowsCopyDir($this->sourcePath, $this->destinationPath);
}
}
else {
if($useCommand) {
exec("xcopy {$this->sourcePath} {$this->destinationPath} /E");
}
else {
$this->WindowsCopyDir($this->sourcePath, $this->destinationPath);
}
}
}
else {
die($this->sourcePath.' is not a directory.');
}
}
/**
* Remove directory and all sub directory in it
* @param bool useCommand
*/
public function removeDir($useCommand=false) {
if(is_dir($this->sourcePath)) {
if($this->isLinuxOS()) {
if($useCommand) {
exec("rm -rf {$this->sourcePath} {$this->destinationPath}");
}
else {
$this->windowsRemoveDir($this->sourcePath);
}
}
else {
if($useCommand) {
exec("DELLTREE {$this->sourcePath}");
}
else {
$this->windowsRemoveDir($this->sourcePath);
}
}
}
else {
die($this->sourcePath.' is not a directory.');
}
}
/**
* @param string source folder to delete
* @param int level
*/
private function windowsRemoveDir($source, $level=0) {
// Trim the trailing slash
$source = preg_replace("|^(.+?)/*$|", "\\1", $source);
if(is_dir($source)) {
if ( ! $current_dir = @opendir($source))
return;
while(FALSE !== ($filename = @readdir($current_dir))) {
if ($filename != "." && $filename != "..") {
if (is_dir($source.'/'.$filename)) {
$this->windowsRemoveDir($source.'/'.$filename, $level + 1);
}
else {
unlink($source.'/'.$filename);
}
}
}
@closedir($current_dir);
@rmdir($source);
}
else {
unlink($source);
}
}
}
?> |
Đoạn code trên chắc chắn với những lập trình viên có kinh nghiệm hay nói cách khác là trình độ, họ sẽ khẽ mĩm cười & chẳng nói gì đâu. Còn nhiều vấn đề. Tôi cũng biết điều đó
Viết OOP không phải cứ quẳng 1 đống hàm vào trong class thì nó là OOP :cry: Nhưng, tạm thời đến lúc này trình độ của tôi chỉ có vậy. Sẽ còn phải cố nhiều đây :clown:
Một điểm thú vị khi viết method copyDir đó là nếu set $useCommand = true, tức là sử dụng command line để copy files & folders (nhanh hơn là dùng PHP để đọc & copy), nếu chạy trên Windows, okie, chạy tốt (trên máy của tôi :p), còn trên Linux thì nó lại copy nguyên cái thư mục nguồn vào bên trong thư mục đích. Cái tôi cần ở đây là chỉ copy các file & folder ở bên trong thư mục nguồn vào thư mục đích.
Và tôi sai ở đây:
1
| exec("cp -pr {$this->sourcePath} {$this->destinationPath}"); |
Đúng ra phải là:
1
| exec("cd {$this->sourcePath}; shopt -s dotglob; cp -pr . {$this->destinationPath}"); |
Tức là:
- Set thư mục nguồn làm thư mục gốc (nhảy vào bên trong thư mục nguồn).
- Hiện tất cả file ẩn.
- Copy tất cả file & folder ở thư mục gốc vào thư mục đích
=> Chạy rất nuột :p
1
2
3
4
| $dirObj = DirManager::getInstance();
$dirObj->sourcePath = 'foo';
$dirObj->destinationPath = 'bar';
$dirObj->copyDir(); |
Toàn bộ đám bậu sậu trong foo đã nằm trọn trong vòng tay của bar :p
Phản hồi mới