PHP · 기본
웹으로 시작하는 PHP
문자열과 정규식 - 한글을 안전하게
mb_* 함수와 UTF-8, sprintf·number_format, preg_match·preg_replace, 입력 정리
개발자KR · 원고 갱신
이 장에서 배우는 것
빵집 예약 사이트에는 손님이 직접 타이핑한 글이 들어온다. 이름 앞뒤에 전각 공백이 붙어 있고, 전화번호는 하이픈이 있기도 없기도 하며, 메모에는 줄바꿈이 섞인다. 이 글은 한글이 들어간 문자열을 PHP가 어떻게 다루는지 알아야 안전하게 처리할 수 있다. 이 장에서는 글자 단위로 문자열을 다루는 mb_* 함수, 숫자와 문자열을 원하는 모양으로 찍는 sprintf와 number_format, 패턴으로 문자열을 검사하고 바꾸는 preg_match와 preg_replace를 배운다. 마지막에는 이 도구들을 묶어 입력을 정리하는 함수를 만든다.
- 바이트 수와 글자 수, 화면 너비가 서로 다르다는 것을 설명하고 mb_strlen, mb_substr, mb_strwidth, mb_strimwidth를 구분해 쓴다.
- sprintf와 number_format으로 주문번호와 금액을 정해진 모양으로 만든다.
- 정규식의 기본 요소와
u플래그를 이해하고 preg_match, preg_match_all, preg_replace로 검사와 치환을 한다. - 사용자 입력을 검사하고 정리하는 순서(UTF-8 검사, 공백 정리, 형식 검사)를 함수로 만든다.
문제 상황
예약 주문 폼으로 다음과 같은 값이 들어왔다고 하자.
- 이름: 앞에 전각 공백(한글 입력기에서 나오는 넓은 빈칸)이 붙은
김민수 - 전화번호:
010 1234-5678 - 주문 내용:
소금빵 x4, 바게트*2, 마카롱 x 2 - 메모: 공백과 줄바꿈이 여러 개 섞인 문장
이 값들로 해야 할 일은 여러 가지다. 이름의 앞뒤 공백을 지우고, 전화번호를 010-1234-5678 꼴로 통일하고, 주문 내용을 품목과 수량으로 쪼개고, 영수증에는 이름을 김*수처럼 가려 찍고, 금액에는 세 자리마다 쉼표를 넣고, 목록 화면에서는 긴 메모를 잘라 보여 주어야 한다.
이 작업을 앞 장까지 배운 함수만으로 하면 곧 문제가 생긴다. strlen('소금빵')은 3이 아니라 9를 돌려주고, substr로 글자를 자르면 한글이 깨진다. 이 장은 그 이유부터 설명한다.
바이트, 글자, 너비: mb_* 함수와 UTF-8
PHP 문자열은 바이트 열이다
PHP의 문자열은 글자의 나열이 아니라 바이트(byte, 컴퓨터가 저장하는 가장 작은 단위) 의 나열이다. 영문자와 숫자는 1바이트이므로 문제가 눈에 띄지 않는다. 그러나 오늘날 웹에서 쓰는 문자 인코딩(글자를 바이트로 바꾸는 규칙)인 UTF-8에서는 한글 한 글자가 3바이트를 차지한다. 그래서 strlen, substr, strtoupper처럼 이름에 mb가 없는 함수는 글자를 세거나 자르는 것이 아니라 바이트를 세거나 자른다.
mb_* 함수는 mbstring 확장(여러 바이트로 된 글자를 다루는 내장 확장)이 제공한다. 이름 앞의 mb는 multibyte의 줄임말이다. 대부분의 PHP 설치본에 들어 있으며, php -m을 실행해 목록에 mbstring이 있는지 확인하면 된다. 이 확장은 별도 설정이 없으면 UTF-8을 기준으로 동작한다. 소스 파일도 UTF-8로 저장해야 한다.
세 가지 길이
한글 문자열에는 서로 다른 길이가 세 가지 있다.
- 바이트 수:
strlen. 저장 공간이나 전송 크기를 볼 때 쓴다. - 글자 수:
mb_strlen. 이름이 2자에서 10자 사이인지 확인할 때 쓴다. - 표시 너비:
mb_strwidth. 한글처럼 넓게 그려지는 글자를 2칸, 영문·숫자를 1칸으로 센다. 글자 수가 같아도 화면에서 차지하는 폭은 다르므로, 정해진 폭에 맞춰 자를 때 쓴다.
글자 단위로 자르려면 mb_substr을 쓴다. 시작 위치에 음수를 주면 끝에서부터 센다. 폭에 맞춰 자르고 끝에 표시를 붙이려면 mb_strimwidth를 쓴다. 이 함수의 폭에는 끝에 붙일 표시(...)의 폭도 포함된다. 글자를 낱개 배열로 나누는 함수는 mb_str_split이고, 대소문자 변환은 mb_strtoupper와 mb_strtolower다. 예를 들어 mb_strtoupper('éclair')는 ÉCLAIR를 돌려주지만 strtoupper는 바이트 단위라서 é를 바꾸지 못한다. 글자 수를 맞춰 빈칸을 채우는 mb_str_pad는 PHP 8.3부터 제공되므로 8.4 이상인 이 책의 환경에서 쓸 수 있다.
깨진 바이트를 걸러 내기
브라우저가 UTF-8이 아닌 값을 보내거나 누군가 일부러 이상한 바이트를 보낼 수 있다. mb_check_encoding($text, 'UTF-8')은 문자열이 올바른 UTF-8이면 true를 돌려준다. 사용자 입력은 다른 처리를 하기 전에 이 검사를 먼저 하는 것이 좋다. 뒤에서 볼 preg_* 함수도 깨진 바이트를 만나면 실패한다.
또 하나 알아 둘 것이 있다. 앞 장까지 쓴 trim은 ASCII 공백, 탭, 줄바꿈만 지운다. 한글 입력기에서 나오는 전각 공백(U+3000)은 지우지 못한다. 이 공백은 아래의 정규식으로 처리한다.
서식 만들기: sprintf와 number_format
sprintf는 자리표시자가 들어 있는 서식 문자열에 값을 끼워 문자열을 만든다. printf는 같은 서식으로 만든 문자열을 바로 출력한다. 자리표시자는 %로 시작한다.
| 자리표시자 | 뜻 | 예 | 결과 |
|---|---|---|---|
%s | 문자열 | sprintf('%s', '빵') | 빵 |
%d | 정수 | sprintf('%d', 42) | 42 |
%04d | 0으로 채운 4칸 정수 | sprintf('%04d', 7) | 0007 |
%.2f | 소수점 아래 2자리 | sprintf('%.2f', 3.14159) | 3.14 |
%-6s | 왼쪽 정렬 6칸(바이트 기준) | sprintf('[%-6s]', 'ab') | [ab ] |
%% | 퍼센트 기호 자체 | sprintf('%d%%', 5) | 5% |
칸 수를 채우는 기능은 바이트를 기준으로 한다는 점을 기억해 두자. 한글이 섞인 표를 맞출 때 이 점이 문제가 되며, 아래 「실무에서 자주 틀리는 것」에서 다시 본다.
number_format은 숫자를 사람이 읽기 좋게 바꾼다. 인수를 하나만 주면 소수점 이하를 반올림해서 버리고 세 자리마다 쉼표를 넣는다. number_format(12500)은 '12,500'이고, number_format(1234567.891, 2)는 '1,234,567.89'다. 결과가 문자열이라는 점에 주의한다. 쉼표가 들어 있으므로 이 값으로 다시 계산하면 안 된다. 계산은 정수 그대로 하고, 화면에 내보내는 마지막 순간에만 number_format을 부른다.
패턴으로 찾고 바꾸기: 정규식
정규식(regular expression)은 문자열이 어떤 모양인지를 적는 작은 언어다. PHP에서는 preg_로 시작하는 함수들이 정규식을 쓴다. 패턴은 문자열로 넘기며, 앞뒤를 같은 기호(구분자)로 감싼다. 이 책에서는 /를 쓴다. 닫는 구분자 뒤에 붙는 글자는 옵션(플래그)이다.
한글을 다룰 때 가장 중요한 옵션은 u다. 이 옵션이 없으면 패턴과 대상 문자열을 바이트 단위로 읽고, 있으면 UTF-8 글자 단위로 읽는다. 한글이 들어갈 가능성이 있는 패턴에는 항상 u를 붙이는 습관을 들이자.
| 요소 | 뜻 | 예 | 맞는 문자열 |
|---|---|---|---|
\d | 숫자 한 글자 | /\d\d/ | 07 |
\s | 공백류 한 글자 | /a\sb/ | a b |
\p{Hangul} | 한글 한 글자(u 필요) | /\p{Hangul}+/u | 소금빵 |
+, {3,4} | 1번 이상, 3~4번 반복 | /\d{3,4}/ | 1234 |
^, $ | 문자열의 처음과 끝 | /^\d+$/ | 2026 |
(?<이름>…) | 이름 붙은 묶음 | /(?<qty>\d+)/ | qty에 숫자 저장 |
검사: preg_match
preg_match($패턴, $문자열, $결과)는 맞는 곳이 있으면 1, 없으면 0, 오류가 나면 false를 돌려준다. 묶음에 이름을 붙이면 $결과['이름']으로 꺼낼 수 있다. 전체가 패턴과 맞는지 볼 때는 ^와 $로 양끝을 고정해야 한다. 고정하지 않으면 문자열 중간의 일부만 맞아도 통과한다. 결과를 비교할 때는 === 1로 써서 오류(false)와 불일치(0)를 함께 걸러 낸다.
여러 곳을 한꺼번에 찾을 때는 preg_match_all을 쓴다. 옵션에 PREG_SET_ORDER를 주면 맞은 곳마다 하나의 배열이 만들어져 foreach로 돌리기 편하다.
치환: preg_replace
preg_replace($패턴, $바꿀 문자열, $대상)은 맞은 곳을 모두 바꾼다. 패턴의 소괄호로 묶은 부분은 바꿀 문자열 안에서 $1, $2로 다시 쓸 수 있다. 이 함수는 대상이 깨진 UTF-8이면 null을 돌려준다. 반환값이 문자열이라고 믿고 바로 쓰면 안 되는 이유가 여기에 있다.
입력 정리의 순서
사용자 입력을 받으면 아래 순서로 처리한다. 앞 단계를 통과하지 못한 값은 뒤 단계로 보내지 않는다.
먼저 UTF-8 검사를 하고, 다음에 공백을 정리하고, 마지막에 길이와 형식을 검사한다. 이 순서를 바꾸면 안 되는 이유가 있다. 공백을 정리하기 전에 길이를 재면 앞뒤 공백이 글자 수에 들어가고, 깨진 바이트를 두고 정규식을 돌리면 null이 튀어나온다. 이 책에서는 검사에 실패한 값을 null로 돌려주는 방식을 쓴다. 호출한 쪽은 null인지 확인해 오류 메시지를 정한다.
완성 코드
아래 파일 하나를 main.php로 저장한다. 폼에서 받은 값을 흉내 낸 배열을 $input에 직접 적어 두었다. 요청을 실제로 받는 방법은 뒤에서 요청과 응답을 다루는 장에서 배운다.
main.php
<?php
declare(strict_types=1);
const MENU = [
'소금빵' => 3200,
'바게트' => 4500,
'단팥빵' => 2800,
];
const DISCOUNT_FROM = 20000;
const DISCOUNT_PERCENT = 5;
function cleanText(string $raw): string
{
$text = preg_replace('/[\s\p{Z}]+/u', ' ', $raw);
if ($text === null) {
return '';
}
return trim($text);
}
function normalizeName(string $raw): ?string
{
if (!mb_check_encoding($raw, 'UTF-8')) {
return null;
}
$name = cleanText($raw);
$length = mb_strlen($name);
if ($length < 2 || $length > 10) {
return null;
}
if (preg_match('/^[\p{Hangul}A-Za-z ]+$/u', $name) !== 1) {
return null;
}
return $name;
}
function normalizePhone(string $raw): ?string
{
$digits = preg_replace('/\D+/', '', $raw) ?? '';
$pattern = '/^(?<a>01[016789])(?<b>\d{3,4})(?<c>\d{4})$/';
if (preg_match($pattern, $digits, $m) !== 1) {
return null;
}
return sprintf('%s-%s-%s', $m['a'], $m['b'], $m['c']);
}
function maskName(string $name): string
{
$length = mb_strlen($name);
if ($length < 2) {
return $name;
}
$first = mb_substr($name, 0, 1);
if ($length === 2) {
return $first . '*';
}
return $first . str_repeat('*', $length - 2) . mb_substr($name, -1);
}
function maskPhone(string $phone): string
{
return preg_replace('/(\d{3})-\d{3,4}-(\d{4})/', '$1-****-$2', $phone) ?? $phone;
}
function parseItems(string $text): array
{
$count = preg_match_all(
'/(?<item>\p{Hangul}+)\s*[x*]\s*(?<qty>\d+)/u',
$text,
$matches,
PREG_SET_ORDER
);
$items = [];
if ($count === false || $count === 0) {
return $items;
}
foreach ($matches as $match) {
$item = $match['item'];
$items[$item] = ($items[$item] ?? 0) + (int) $match['qty'];
}
return $items;
}
function buildReceipt(string $orderNo, string $name, string $phone, array $items): string
{
$lines = [
sprintf('주문번호 %s', $orderNo),
sprintf('고객 %s (%s)', maskName($name), maskPhone($phone)),
];
$total = 0;
foreach ($items as $item => $qty) {
if (!isset(MENU[$item])) {
$lines[] = sprintf('- %s: 메뉴에 없어 제외', $item);
continue;
}
$subtotal = MENU[$item] * $qty;
$total += $subtotal;
$lines[] = sprintf('- %s x %d = %s원', $item, $qty, number_format($subtotal));
}
$discount = $total >= DISCOUNT_FROM ? intdiv($total * DISCOUNT_PERCENT, 100) : 0;
$lines[] = sprintf('합계 %s원', number_format($total));
if ($discount > 0) {
$lines[] = sprintf('할인 %.1f%% -%s원', DISCOUNT_PERCENT, number_format($discount));
}
$lines[] = sprintf('결제 %s원', number_format($total - $discount));
return implode("\n", $lines);
}
function showLengths(string $text): void
{
printf("문자열: %s\n", $text);
printf("strlen : %d\n", strlen($text));
printf("mb_strlen : %d\n", mb_strlen($text));
printf("mb_strwidth : %d\n", mb_strwidth($text));
printf("글자 나누기 : %s\n", implode(' / ', mb_str_split($text)));
}
$input = [
'name' => "\u{3000}김민수 \t",
'phone' => '010 1234-5678',
'items' => '소금빵 x4, 바게트*2, 마카롱 x 2',
'memo' => " 내일 오전\n10시에 찾으러 갑니다 ",
];
echo "== 글자 세기 ==\n";
showLengths('소금빵');
echo "\n== 이름 검사 ==\n";
$names = [
'한 글자' => '김',
'숫자 섞임' => 'Kim123',
'깨진 바이트' => "\xEC\x82",
'영문' => ' Anna Lee ',
];
foreach ($names as $label => $raw) {
printf("%s: %s\n", $label, normalizeName($raw) ?? '거부');
}
echo "\n== 전화번호 검사 ==\n";
foreach (['010.999.8888', '02-123-4567', '010-12'] as $raw) {
printf("%s: %s\n", $raw, normalizePhone($raw) ?? '거부');
}
echo "\n== 주문 영수증 ==\n";
$name = normalizeName($input['name']);
$phone = normalizePhone($input['phone']);
if ($name === null || $phone === null) {
echo "입력을 확인해 주세요.\n";
exit(1);
}
$orderNo = sprintf('BK-%s-%04d', '20260930', 7);
echo buildReceipt($orderNo, $name, $phone, parseItems($input['items'])), "\n";
printf("메모: %s\n", mb_strimwidth(cleanText($input['memo']), 0, 25, '...'));
줄별 해설
상수와 cleanText
MENU는 품목 이름을 키로, 가격을 값으로 하는 연관 배열이다. 세 상수는 파일 맨 위에 두어 가격 정책이 바뀔 때 한 곳만 고치게 했다.
cleanText는 패턴 /[\s\p{Z}]+/u로 공백류가 하나 이상 이어진 부분을 찾아 공백 하나로 바꾼다. \s는 탭과 줄바꿈을, \p{Z}는 전각 공백을 포함한 모든 구분 공백을 잡는다. 앞뒤에 남은 공백 한 칸은 마지막의 trim이 지운다. preg_replace가 null을 돌려주는 경우(깨진 UTF-8)를 if로 처리해서 함수가 언제나 문자열을 돌려주게 했다.
normalizeName과 normalizePhone
normalizeName은 앞서 정리한 순서를 그대로 따른다. UTF-8을 검사하고, cleanText로 정리하고, mb_strlen으로 2~10자인지 보고, 마지막으로 한글·영문·공백만 있는지 preg_match로 본다. 패턴 [\p{Hangul}A-Za-z ]의 끝에 있는 공백 한 칸도 허용 문자다. !== 1은 오류와 불일치를 모두 거부로 처리한다.
normalizePhone은 숫자가 아닌 것(\D)을 모두 지워 숫자만 남긴다. 그다음 패턴이 010 같은 앞자리 세 개, 가운데 3~4자리, 끝 4자리를 묶음 a, b, c로 받는다. 가운데의 {3,4}는 처음에 4자리를 가져가 보고, 뒤가 모자라면 3자리로 물러난다. 그래서 11자리와 10자리 번호를 모두 받을 수 있다. 통과하면 sprintf로 하이픈을 넣어 돌려준다.
마스킹
maskName은 글자 수에 따라 갈린다. 한 글자면 그대로 두고, 두 글자면 첫 글자 뒤에 *를 붙이고, 세 글자 이상이면 첫 글자와 마지막 글자만 남긴다. 마지막 글자는 mb_substr($name, -1)로 꺼낸다. maskPhone은 preg_replace의 $1, $2로 가운데 부분을 별표로 바꾼다. 실패했을 때는 ??로 원래 값을 돌려주게 했는데, 이 함수는 정규화된 전화번호만 받으므로 실제로 그 경우는 생기지 않는다.
parseItems
preg_match_all은 한글 품목명, 공백, x 또는 *, 공백, 수량 순서로 이어진 곳을 모두 찾는다. [x*]는 대괄호 안의 글자 중 하나라는 뜻이며, 안에서는 *가 특별한 의미를 잃는다. 같은 품목이 두 번 나오면 수량을 더한다. $items[$item] ?? 0은 키가 아직 없을 때 0으로 시작하게 한다. 반환값이 false이거나 0이면 빈 배열을 돌려준다.
buildReceipt
메뉴에 없는 품목은 isset(MENU[$item])로 걸러 내고 continue로 다음 품목으로 넘어간다. 금액은 끝까지 정수로 계산하고 number_format은 문자열로 만드는 순간에만 쓴다. 할인은 합계가 2만 원 이상일 때 5퍼센트이며, intdiv로 나머지를 버린 정수를 얻는다. %.1f%%는 5를 5.0으로 찍고 뒤에 퍼센트 기호를 붙인다. 마지막에 implode로 줄들을 줄바꿈으로 이어 붙인다.
showLengths와 실행부
showLengths는 같은 문자열의 바이트 수, 글자 수, 표시 너비, 글자 나누기 결과를 나란히 보여 준다. 실행부의 $names 배열은 라벨과 입력값의 짝이다. "\xEC\x82"는 한글 한 글자(3바이트) 중 앞 2바이트만 있는 깨진 문자열이라서 UTF-8 검사에서 걸린다. 마지막 줄의 mb_strimwidth는 표시 너비 25에 맞추되 끝의 ...(너비 3)까지 포함하므로 내용은 너비 22 이내로 잘린다.
실행 결과
$ php main.php
== 글자 세기 ==
문자열: 소금빵
strlen : 9
mb_strlen : 3
mb_strwidth : 6
글자 나누기 : 소 / 금 / 빵
== 이름 검사 ==
한 글자: 거부
숫자 섞임: 거부
깨진 바이트: 거부
영문: Anna Lee
== 전화번호 검사 ==
010.999.8888: 010-999-8888
02-123-4567: 거부
010-12: 거부
== 주문 영수증 ==
주문번호 BK-20260930-0007
고객 김*수 (010-****-5678)
- 소금빵 x 4 = 12,800원
- 바게트 x 2 = 9,000원
- 마카롱: 메뉴에 없어 제외
합계 21,800원
할인 5.0% -1,090원
결제 20,710원
메모: 내일 오전 10시에 찾으...
실무에서 자주 틀리는 것
substr로 한글을 자른다
틀린 코드는 바이트 4개를 잘라 낸다. 소 한 글자(3바이트)와 금의 첫 바이트가 남아 깨진 글자가 출력된다.
echo substr('소금빵', 0, 4); // 소 뒤에 깨진 바이트가 붙는다
글자 단위 함수로 고친다.
echo mb_substr('소금빵', 0, 2); // 소금
sprintf로 한글 칸을 맞춘다
%-8s는 8바이트가 되도록 공백을 채운다. 빵은 3바이트이므로 공백이 5칸만 붙는다. 영문 ab는 6칸이 붙어서 줄이 맞지 않는다.
echo sprintf('[%-8s]', '빵'); // [빵 ] 공백 5칸
화면 너비를 기준으로 직접 채운다. 빵의 표시 너비는 2이므로 공백 6칸이 붙는다.
$text = '빵';
echo '[' . $text . str_repeat(' ', max(0, 8 - mb_strwidth($text))) . ']'; // [빵 ]
패턴에 u 플래그를 빼먹는다
플래그가 없으면 점(.)이 글자가 아니라 바이트 하나에 대응한다. 빵은 3바이트이므로 글자 하나를 기대한 패턴이 맞지 않는다.
var_dump(preg_match('/^.$/', '빵')); // int(0)
var_dump(preg_match('/^.$/u', '빵')); // int(1)
preg_replace의 null을 그대로 넘긴다
대상이 깨진 UTF-8이면 preg_replace는 null을 돌려준다. declare(strict_types=1)가 켜진 파일에서 이 값을 trim에 넘기면 TypeError로 프로그램이 멈춘다.
$raw = "\xEC\x82";
echo trim(preg_replace('/\s+/u', ' ', $raw)); // TypeError: trim() 인수는 string 이어야 한다
먼저 UTF-8을 검사하거나 null일 때의 대체값을 정해 둔다.
$raw = "\xEC\x82";
$text = preg_replace('/\s+/u', ' ', $raw) ?? '';
echo trim($text); // 빈 문자열
한눈에 보기
| 하는 일 | 바이트 단위 | 글자 단위 | 비고 |
|---|---|---|---|
| 길이 | strlen | mb_strlen | 소금빵은 9와 3 |
| 화면 너비 | 없음 | mb_strwidth | 한글 2, 영문 1 |
| 자르기 | substr | mb_substr | 음수 시작 위치 가능 |
| 너비로 자르기 | 없음 | mb_strimwidth | 끝 표시의 너비 포함 |
| 대문자로 | strtoupper | mb_strtoupper | 바이트판은 ASCII만 |
| 낱글자 배열 | str_split | mb_str_split | 한글은 mb 사용 |
| 칸 채우기 | str_pad | mb_str_pad | PHP 8.3 이상 |
| UTF-8 검사 | 없음 | mb_check_encoding | 입력 처리의 첫 단계 |
서식은 sprintf와 number_format, 검사는 preg_match, 여러 곳 찾기는 preg_match_all, 치환은 preg_replace가 맡는다. 한글이 들어가는 패턴에는 u를 붙이고, 반환값의 false와 null을 확인한다.
연습 문제
- 문자열
'바닐라 슈크림빵'의 바이트 수, 글자 수, 표시 너비를 먼저 손으로 계산한 다음 코드로 확인하라. - 주문번호
'BK-20260930-0007'에서 날짜와 순번을 이름 붙은 묶음으로 꺼내는preg_match코드를 작성하라. 순번은 정수 7로 바꿔 출력한다. - 금액
1234567을1,234,567원꼴로, 할인율 수치7.5를7.5%꼴로 만드는 한 줄씩의 코드를 작성하라. - 메모
'010-1234-5678로 연락 주세요'안의 전화번호 가운데 네 자리를****로 바꾸는 코드를 작성하라.
정답과 해설
- 글자는 바닐라(3), 공백(1), 슈크림빵(4)으로 8글자다. 한글 7자는 각각 3바이트이고 공백은 1바이트이므로 바이트 수는 7×3+1=22다. 표시 너비는 한글 7자가 각 2, 공백이 1이므로 15다.
$s = '바닐라 슈크림빵'; echo strlen($s), ' ', mb_strlen($s), ' ', mb_strwidth($s), "\n"; // 22 8 15 - 날짜는 숫자 8자리, 순번은 4자리로 잡고 양끝을 고정한다. 순번
'0007'은 문자열이므로(int)로 바꿔야 7이 된다.$no = 'BK-20260930-0007'; if (preg_match('/^BK-(?<date>\d{8})-(?<seq>\d{4})$/', $no, $m) === 1) { echo $m['date'], ' ', (int) $m['seq'], "\n"; // 20260930 7 } - 쉼표는
number_format, 소수 한 자리와 퍼센트 기호는sprintf로 만든다. 퍼센트 기호를 찍으려면%%로 써야 한다.echo number_format(1234567) . '원'; // 1,234,567원 echo sprintf('%.1f%%', 7.5); // 7.5% - 앞 세 자리와 끝 네 자리를 묶음으로 잡아
$1,$2로 되살린다. 가운데는 묶지 않았으므로 결과에서 사라지고 별표가 들어간다.$memo = '010-1234-5678로 연락 주세요'; echo preg_replace('/(\d{3})-\d{4}-(\d{4})/', '$1-****-$2', $memo); // 010-****-5678로 연락 주세요
READER FEEDBACK
질문·의견
내용에 관한 질문이나 더 나은 설명을 위한 의견을 남겨 주세요. 오탈자는 위의 제보 양식이 더 빨리 반영됩니다. 이 댓글은 원래 게시글과 같은 자리에 쌓입니다.
댓글 0
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.