본 도구에서 사용하는 오픈소스 라이브러리

본 도구의 코드에는 1개의 오픈소스 라이브러리가 포함되어 있습니다.

PHP 치트시트 — 간결한 참고서

PHP 8.3 문법, 타입, 배열 그리고 가장 자주 쓰이는 표준 라이브러리의 치트시트 매뉴얼로, 일상적인 사례의 약 80%를 다룹니다.

Php

PHP PHP 8.3 (LTS 계열)

Zend Engine (PHP 런타임) · 명령형 · OO · 함수형 · 동적 (선택적 정적 타입 지원)

추천 학습 경로

먼저 php hello.php와 php -S 내장 서버로 실행하는 방법을 익히고 → 변수, 타입, 제어 흐름을 익히고 → 함수, 클로저, 배열로 들어가며 → 참조 시맨틱과 객체 핸들을 이해하고 → 클래스, 인터페이스, 예외로 코드를 구성한 다음 → 필요할 때 파일 I/O, cURL, 시간과 정규 표현식을 배우고 → Fiber/pcntl로 동시성을 처리합니다. FAQ 섹션은 다시 돌아와 함정을 피할 때 유용합니다.

1.Hello World 와 실행 환경

PHP 프로그램 실행, 내장 웹 서버, 명령행 인자, 종료 코드.

최소 프로그램

PHP 파일은 <?php 로 시작하고, CLI에서는 php 파일명으로 실행한다. echo가 문자열을 출력한다.

1
2
3
4
5
6
7
8
9
echo "Hello, world!\n";
// 작은따옴표는 이스케이프와 보간을 해석하지 않음
echo 'Hello, world!\n';
echo "\n";
// 큰따옴표 안에서 변수 보간
$name = 'PHP';
echo "Hello, $name!\n";

CLI 실행

php 명령으로 스크립트를 직접 실행한다. php -l은 문법을 검사하고, php -r은 코드를 곧바로 실행한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 스크립트 파일 실행:
// $ php hello.php
// 출력 Hello, world!
// 한 줄 코드 직접 실행:
// $ php -r 'echo "hi\n";'
// 문법 검사(실행 안 함):
// $ php -l hello.php
// 대화형 셸 시작:
// $ php -a
// 버전 확인:
// $ php -v

내장 웹 서버

php -S는 내장 서버를 띄우고 디렉터리를 문서 루트로 삼아, 로컬 개발과 디버깅에 적합하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 프로젝트 디렉터리에서 시작:
// $ php -S localhost:8000
// 문서 루트 지정:
// $ php -S localhost:8000 -t public
// 라우트 스크립트 지정:
// $ php -S localhost:8000 router.php
// 시작 후 브라우저로 접속:
// http://localhost:8000/index.php
// 포트가 사용 중이면 시작에 실패함

파일 구조

<?php 와 ?> 태그가 PHP와 HTML을 가른다. 순수 PHP 파일은 닫는 태그를 생략하는 것이 권장된다.

1
2
3
4
5
6
7
echo "纯 PHP 文件顶部直接写代码\n";
// 종료 태그 ?> 는 생략 권장
// 파일 끝 개행이 출력으로 취급되는 것 방지
// 템플릿 혼용 시 ?> 로 HTML 로 복귀:
// <p><?= $title ?></p>
// 단축 태그 <?= 는 echo 와 동등

명령행 인자

$argv는 인자 배열이고, $argc는 인자 개수이며, argv[0]은 스크립트 이름이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 실행:$ php args.php a b c
var_dump($argc); // 4
var_dump($argv);
// ["args.php", "a", "b", "c"]
// 업무 파라미터 가져오기:
$name = $argv[1] ?? 'guest';
echo "hello, $name\n";
// 모든 파라미터 순회:
foreach (array_slice($argv, 1) as $arg) {
echo "arg: $arg\n";
}

종료 코드

exit() 또는 die()로 스크립트를 끝내고 상태 코드를 반환한다. 0이면 성공, 0이 아니면 오류다.

1
2
3
4
5
6
7
8
9
10
11
12
// 정상 종료(0 반환):
exit(0);
// 오류 정보와 함께 종료:
die("发生错误\n"); // exit 와 동일
// 상태 코드와 함께 종료:
exit(1); // 0 이 아니면 실패를 나타냄
// 스크립트 자연 종료는 exit(0) 과 동등
// CLI 성공/실패 판정:
// $ php run.php && echo "ok"

파일 포함

require는 반드시 존재하는 파일을 포함하고, include는 실패해도 경고만 낸다. _once는 중복 포함을 막는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 필수 파일 로드:
require 'config.php';
// 선택 파일 로드(실패는 경고만):
include 'optional.php';
// 중복 로드 방지:
require_once 'lib/functions.php';
include_once 'helper.php';
// __DIR__ 로 절대 경로 구성:
require __DIR__ . '/../src/bootstrap.php';
// include 는 불리언을 반환해 성패를 판단 가능

환경 점검

php -i는 설정을 출력하고, php -m은 확장 모듈을 나열하며, phpinfo()는 전체 정보를 출력한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 커맨드라인으로 설정 확인:
// $ php -i
// 특정 설정 항목 확인:
// $ php -i | grep memory_limit
// 로드된 확장 나열:
// $ php -m
// 확장 설치 확인:
// $ php -m | grep mbstring
// 페이지에 전체 정보 출력:
phpinfo();
// 프로덕션에서는 반드시 끄고 설정 노출 방지

2.변수와 상수

변수 선언, 상수, 타입 선언, 엄격 모드, 스코프.

변수 선언

변수는 $로 시작하고 타입 선언이 필요 없다. 첫 대입에서 만들어지며, 이름은 대소문자를 구분한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// $ 접두사 + 이름,타입 선언 없음:
$count = 0;
$name = 'Rex';
$price = 19.99;
$active = true;
// 변수명은 대소문자 구분:
$Foo = 1;
$foo = 2; // 서로 다른 변수
// 미정의 변수 읽기는 경고 발생:
// echo $undefined;
// 이름은 숫자로 시작할 수 없음

값 복사 대입

일반 대입은 값으로 복사되며 스칼라는 독립적이다. 배열도 복사되지만 내부적으로 Copy-on-Write로 최적화된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 스칼라 할당은 서로 영향 없음:
$a = 5;
$b = $a;
$b = 10;
echo $a; // 여전히 5
// 배열 할당도 복사:
$arr1 = [1, 2, 3];
$arr2 = $arr1;
$arr2[0] = 99;
echo $arr1[0]; // 1(영향 없음)
// 참조 할당은 &,포인터 절 참조

상수

const는 네임스페이스 상수를 정의하고, define()은 전역 상수를 정의한다. 정의 후에는 변경할 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 네임스페이스 레벨 상수:
const MAX_SIZE = 1024;
// 전역 상수:
define('APP_NAME', 'demo');
// 상수는 식에서 사용 가능:
$half = MAX_SIZE / 2;
// 정의 여부 판정:
defined('APP_NAME'); // true
// 동적으로 상수명 읽기:
constant('MAX_SIZE'); // 1024
// 상수명은 전부 대문자 관례

매직 상수

__DIR__, __FILE__, __LINE__ 등 컴파일 시점 상수는 해당 위치에서 평가되며, 주로 로그에 쓰인다.

1
2
3
4
5
6
7
8
9
10
11
echo __DIR__; // 파일이 있는 디렉터리
echo __FILE__; // 파일 전체 경로
echo __LINE__; // 현재 줄 번호
echo __FUNCTION__; // 현재 함수명
echo __CLASS__; // 현재 클래스명
echo __NAMESPACE__; // 현재 네임스페이스
// 로그 기록 위치:
error_log(__FILE__ . ':' . __LINE__);
// __DIR__ 로 경로 결합이 가장 안전

스코프와 static

함수 안 변수는 기본적으로 지역 변수다. global은 전역 변수를 가져오고, static은 여러 호출 사이의 값을 유지한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$global_count = 0;
function countUp(): void
{
static $n = 0; // 호출 사이 유지
$n++;
echo $n;
}
// 함수 내에서 전역 접근:
function readGlobal(): int
{
global $global_count;
return $global_count;
}
countUp(); // 1
countUp(); // 2

인자와 반환 타입

인자와 반환값에 타입을 선언할 수 있다. 약 모드에서는 자동 변환되며, strict_types와 함께 쓰면 엄격해진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 인자와 반환 타입:
function add(int $a, int $b): int
{
return $a + $b;
}
// nullable 타입 ?int:
function find(?int $id): ?array
{
return $id === null ? null : [$id];
}
// 유니온 타입 int|float:
function area(int|float $w): float
{
return (float) $w * 2;
}

엄격 모드

declare(strict_types=1)은 타입을 엄격하게 검증하며, 일치하지 않으면 곧바로 TypeError를 던진다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 반드시 파일 첫 줄에 작성:
declare(strict_types=1);
function add(int $a, int $b): int
{
return $a + $b;
}
add(1, 2); // 3
// add('1', 2); // 엄격 모드는 TypeError 를 던짐
// 약한 모드에서는 '1' 이 자동으로 1 이 됨
// 파일마다 선언,파일을 넘는 상속은 없음

명명 규약

클래스와 네임스페이스는 파스칼 케이스, 함수와 변수는 카멜 케이스, 상수는 모두 대문자로 쓰며, PSR-12를 따른다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 클래스명은 파스칼 케이스:
class UserProfile {}
// 함수와 변수는 카멜 케이스:
function getUserName() {}
$userName = 'Rex';
// 상수는 전부 대문자:
const MAX_ITEMS = 100;
// 프라이빗 멤버는 밑줄 가능:
class Demo
{
private $privateData;
}
// PSR-12:클래스는 파스칼,메서드는 카멜

3.데이터 타입

스칼라, 배열, null, 열거형, 타입 시스템을 다루고, 약 변환과 타입 검사를 이해한다.

스칼라 타입

int, float, string, bool 네 가지 스칼라가 있다. 문자열은 바이트 시퀀스이며 한자는 멀티바이트로 처리된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$age = 30; // int
$pi = 3.14; // float
$ok = true; // bool
$name = 'Rex'; // string
// 정수 오버플로는 자동으로 부동소수점이 됨:
$big = PHP_INT_MAX + 1; // float
// 문자열은 바이트 시퀀스:
$s = 'Hello, 世界';
// 명시적 타입 변환:
(int) '42'; // 42
(string) 123; // '123'

배열

PHP 배열은 순서가 있는 맵이며, 정수와 문자열 키를 모두 쓰고 리스트와 사전을 동시에 담당한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 리스트:
$colors = ['red', 'green', 'blue'];
// 연관 배열:
$user = ['name' => 'Rex', 'age' => 5];
// 혼합 키:
$mixed = [1, 'a', true];
// 요소 추가:
$colors[] = 'yellow';
// 길이 가져오기:
count($colors); // 4
// 키 순서는 삽입 순서

null과 빈 값

null은 값이 없음을 뜻한다. 대입되지 않은 변수, 명시적인 null, 함수의 null 반환이 모두 null이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$value = null; // 명시적 null
// null 여부 판정:
$value === null; // true
is_null($value); // true
// nullable 인자 타입:
function greet(?string $name): void
{
echo $name ?? 'guest';
}
// ?? 로 기본값 제공:
$name = $input['name'] ?? 'guest';

callable과 iterable

callable은 호출 가능을 뜻하고, iterable은 순회 가능한 배열 또는 Traversable 객체를 뜻한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// iterable 인자는 배열이나 객체를 받을 수 있음:
function sumAll(iterable $items): int
{
$total = 0;
foreach ($items as $n) {
$total += $n;
}
return $total;
}
sumAll([1, 2, 3]); // 6
// callable 은 함수명이나 클로저를 받음:
function apply(callable $fn, int $x): int
{
return $fn($x);
}
apply(fn($x) => $x * 2, 5); // 10

mixed와 void

mixed는 임의 타입, void는 반환값 없음, never는 절대 반환하지 않음을 뜻한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// mixed:임의 타입:
function debug(mixed $v): void
{
var_dump($v);
}
// void:반환값 없음:
function logMsg(string $msg): void
{
echo $msg . "\n";
}
// never:절대 반환하지 않음:
function abort(): never
{
throw new \RuntimeException('终止');
}
// never 함수는 예외를 던지거나 exit 필요

열거형 enum

enum(8.1+)은 명명된 상수 집합을 정의한다. 순수 열거형과 값이 있는 열거형이 있으며, 메서드를 추가할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 순수 열거형:
enum Color
{
case Red;
case Green;
case Blue;
}
// 값을 가진 열거형:
enum Status: string
{
case Ok = 'ok';
case NotFound = '404';
}
// 값 가져오기와 비교:
$s = Status::Ok;
$s->value; // 'ok'
$s === Status::Ok; // true
// match 와 결합해 안전하게 매치

유니온과 인터섹션 타입

int|float은 둘 중 하나를 뜻하고, 인터섹션 타입은 여러 인터페이스 제약을 동시에 만족해야 함을 요구한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 유니온 타입(8.0+):
function format(int|float $n): string
{
return number_format((float) $n, 2);
}
// nullable 축약 ?T 는 T|null 과 동등:
function maybe(?string $s): string
{
return $s ?? '空';
}
// 교차 타입(8.1+):
function dump(\Countable&\Traversable $c): void
{
echo count($c);
}
// DNF 타입 8.2+:(A&B)|null

약 타입 변환

약 모드에서는 연산 시 타입이 자동으로 변환되며, 문자열과 숫자의 비교 규칙은 특수하니 FAQ를 참고하라.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 문자열과 숫자 연산은 자동 변환:
$n = '3' + 2; // 5
$n = '3' * '2'; // 6
// 문자열을 숫자로 강제 변환:
(int) '42px'; // 42
(float) '3.5.9'; // 3.5
// 불리언으로 강제 변환:
(bool) '0'; // false
(bool) '0.0'; // true('0' 아님)
// 엄격 모드에서는 이것들이 오류가 됨

타입 검사 함수

is_int, is_string 같은 is_* 계열은 타입을 판별하고 불리언을 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
is_int(3); // true
is_float(3.0); // true
is_string('x'); // true
is_bool(true); // true
is_array([1]); // true
is_null(null); // true
is_numeric('42'); // true(숫자로 변환 가능)
is_callable('strlen'); // true
// 관대한 판정은 is_numeric:
is_int('42'); // false
is_numeric('42'); // true

4.참조와 메모리 의미

PHP에는 날 포인터가 없다: 변수 참조, Copy-on-Write, 객체 핸들, null 처리.

참조란 무엇인가

PHP 참조는 변수의 별칭으로, 두 이름이 같은 내용을 가리키므로 한쪽을 바꾸면 다른 쪽도 함께 바뀐다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 참조 할당으로,$b 가 $a 의 별칭이 됨:
$a = 1;
$b = &$a;
$b = 2;
echo $a; // 2(함께 바뀜)
// 배열 요소도 참조 가능:
$arr = [1, 2];
$last = &$arr[1];
$last = 99;
echo $arr[1]; // 99
// 참조는 주소 포인터가 아님
// 주소를 얻거나 포인터 연산은 불가

Copy-on-Write

배열과 객체 대입 시 처음에는 데이터를 공유하고, 실제로 쓸 때 비로소 복사되어 메모리를 절약한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 할당은 먼저 공유하고,즉시 복사하지 않음:
$a = [1, 2, 3];
$b = $a;
echo $b[0]; // 1,읽기만 복사 없음
// 쓰기 시 복사 발생:
$b[0] = 99;
echo $a[0]; // 1(영향 없음)
// 큰 배열 할당은 저렴함:
// 수정할 때만 복사 비용
// foreach 내 수정은 새 배열 권장

참조 대입

unset은 현재 이름의 참조만 해제하며, 다른 별칭이 가리키는 내용에는 영향을 주지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$a = 'original';
$b = &$a;
// unset 은 $b 의 참조만 끊음:
unset($b);
echo $a; // original(남아 있음)
// 배열 요소 간 참조:
$data = [1, 2];
$alias = &$data[0];
$data[0] = 100;
echo $alias; // 100
// 참조 카운트 관찰:
// debug_zval_dump($a)

참조 인자

인자 앞에 &를 붙이면 참조로 전달되며, 함수 안 수정이 호출 측 변수에 반영된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 참조 인자로 원본 변수 변경:
function increment(int &$n): void
{
$n++;
}
$x = 5;
increment($x);
echo $x; // 6
// 반환값과 함께 상태 전달:
function parse(string $s, &$ok): int
{
$ok = is_numeric($s);
return (int) $s;
}
parse('42', $valid); // $valid 가 true 가 됨

객체 핸들

객체 변수는 핸들을 저장하므로, 대입과 인자 전달 모두 같은 인스턴스를 가리킨다. 복사본이 필요하면 clone을 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Counter
{
public int $n = 0;
}
$c1 = new Counter();
$c2 = $c1; // 동일 인스턴스
$c2->n = 5;
echo $c1->n; // 5(공유)
// 독립 복사본은 clone:
$c3 = clone $c1;
$c3->n = 0;
echo $c1->n; // 여전히 5
// === 로 같은 인스턴스인지 판정:
$c1 === $c2; // true

null 처리

isset은 존재하면서 null 아님을 판별하고, is_null은 null 여부를, empty는 비어 있는지 판별한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$arr = ['a' => null, 'b' => 1];
// isset:존재하고 null 아님:
isset($arr['a']); // false(값은 null)
isset($arr['b']); // true
isset($arr['x']); // false(존재하지 않음)
// is_null:null 인지:
is_null($arr['a']); // true
// 존재하지 않는 키 직접 접근은 경고:
// $v = $arr['x'];
// ?? 로 안전하게 읽기:
$v = $arr['x'] ?? '默认';

Null 병합 ??

?? 는 왼쪽이 없거나 null일 때 오른쪽을 반환하며, 체이닝과 throw와 함께 쓸 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 존재하지 않을 수 있는 키 읽기:
$name = $_GET['name'] ?? 'guest';
// 체이닝 널 병합:
$config = $a ?? $b ?? 'default';
// throw 와 결합(8.0+):
$value = $input['key'] ?? throw new \InvalidArgumentException('缺 key');
// 미정의 키 경고를 발생시키지 않음:
// 직접 첨자 접근만 경고를 냄
// 할당 연산자 ??=(8.0+)

가변 변수

$$var는 변수 값을 다른 변수의 이름으로 쓰며, 가독성이 나쁘므로 배열 사용을 권한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 가변 변수:
$name = 'foo';
$foo = 42;
echo $$name; // 42(즉 $foo)
// 배열 방식이 더 명확:
$values = ['foo' => 42];
echo $values[$name]; // 42
// 중첩 가변 변수는 혼동되기 쉬움:
// $$$name 은 거의 읽을 수 없음
// 연관 배열로 대체 권장

unset과 메모리

unset은 이름을 해제하고 참조 카운트를 줄이며, 0이 되면 메모리가 회수 가능하다. 순환 참조는 GC가 처리한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 큰 변수 해제:
$big = str_repeat('x', 1000000);
unset($big); // 즉시 회수 가능
// 배열 요소 해제:
$arr = [1, 2, 3];
unset($arr[1]);
// 키는 재정렬되지 않고,값은 null 이 됨
// 순환 참조:
class Node
{
public ?Node $next = null;
}
$a = new Node();
$b = new Node();
$a->next = $b;
$b->next = $a; // 환 구조 형성
unset($a, $b);
// 순환 가비지 컬렉터가 정리

5.제어 흐름

조건, 반복, switch, match 그리고 break/continue.

if / else

if는 조건을 판별해 분기를 실행하고, elseif가 차례로 시도하며, else가 기본값으로 작동한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$n = 0;
if ($n > 0) {
echo "正数";
} elseif ($n === 0) {
echo "零";
} else {
echo "负数";
}
// 한 줄 축약:
if ($n > 0) echo "正数";
// return 으로 조기 종료:
// 중첩 깊이를 줄임

삼항 연산자

조건 ? 참 값 : 거짓 값 은 표현식이라 대입이 가능하지만, 중첩은 가독성을 해친다.

1
2
3
4
5
6
7
8
9
10
$score = 85;
$grade = $score >= 90 ? 'A' : 'B';
echo $grade; // B
// ?: 와 ?? 의 차이:
$x = $a ?: '默认'; // $a 가 거짓일 때 기본값
$y = $a ?? '默认'; // $a 가 null 일 때 기본값
// 중첩 삼항은 가독성이 낮음:
// if 나 match 로 분해 권장

switch 문

switch는 기본적으로 느슨한 비교를 하며, 매칭 후 break를 쓰지 않으면 fall-through 된다. default가 기본값이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$status = 200;
switch ($status) {
case 200:
case 201:
echo "成功";
break;
case 404:
echo "未找到";
break;
default:
echo "其它";
}
// case '0' 은 0 과 관대하게 매치되니 주의
// 복잡한 판정은 match 권장

match 표현식

match(8.0+)는 엄격 비교하며 값을 반환한다. 분기는 자동으로 반환값이 되고, break가 필요 없다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$status = 404;
$label = match ($status) {
200, 201 => '成功',
404, 410 => '未找到',
default => '其它',
};
echo $label; // 未找到
// default 가 없고 매치 안 되면 예외를 던짐
// UnhandledMatchError 예외
// match true 로 조건 판정:
$result = match (true) {
$score >= 90 => 'A',
$score >= 80 => 'B',
default => 'C',
};

for 반복문

for(초기식; 조건식; 증감식)는 고전적인 카운터 반복으로, 증감식을 자유롭게 제어할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for ($i = 0; $i < 3; $i++) {
echo "$i ";
} // 0 1 2
// 감소 스텝:
for ($i = 10; $i >= 0; $i -= 2) {
echo "$i ";
} // 10 8 6 4 2 0
// 다중 루프:
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 2; $j++) {
echo "$i$j ";
}
}

foreach 순회

foreach는 배열과 객체를 순회하며 키와 값을 동시에 꺼낼 수 있다. 참조로 수정할 때는 남은 참조에 주의해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$items = ['a' => 1, 'b' => 2];
// 값만 가져오기:
foreach ($items as $v) {
echo "$v ";
} // 1 2
// 키와 값 가져오기:
foreach ($items as $k => $v) {
echo "$k=$v ";
} // a=1 b=2
// 참조로 요소 수정:
foreach ($items as &$v) {
$v *= 2;
}
unset($v); // 핵심!참조 끊기

while와 do-while

while는 먼저 조건을 판단한 후 실행하므로 0회 실행될 수 있으며, do-while는 최소 한 번은 실행됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// while 은 먼저 판정:
$n = 0;
while ($n < 3) {
echo $n++;
} // 012
// do-while 은 최소 1회:
$i = 10;
do {
echo $i;
} while ($i < 3); // 10
// 파일을 한 줄씩 읽는 흔한 방식:
$h = fopen('log.txt', 'r');
while (($line = fgets($h)) !== false) {
echo $line;
}
fclose($h);

break와 continue

break는 반복문이나 switch를 빠져나가고, continue는 다음 반복으로 이동하며, 숫자를 지정해 여러 겹을 한 번에 빠져나갈 수도 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
for ($i = 0; $i < 10; $i++) {
if ($i % 2 === 0) continue; // 짝수 건너뛰기
if ($i > 5) break; // 조기 종료
echo "$i ";
} // 1 3 5
// 이중 루프 탈출:
for ($i = 0; $i < 3; $i++) {
for ($j = 0; $j < 3; $j++) {
if ($j === 1) break 2;
echo "$i$j ";
}
} // 00 10

6.함수와 클로저

함수 정의, 기본 매개변수, 가변 매개변수, 명명된 매개변수, 클로저, 제너레이터를 다룹니다.

함수 정의

function 키워드로 함수를 정의하며, 매개변수에 타입을 지정하고 return으로 값을 반환합니다. 함수명은 대소문자를 구분하지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function add(int $a, int $b): int
{
return $a + $b;
}
// 호출:
add(1, 2); // 3
// 함수명은 대소문자 구분 없음:
ADD(1, 2); // 3
// 먼저 호출 후 정의해도 됨:
sayHi();
function sayHi(): void
{
echo "hi\n";
}
// 같은 이름 함수 중복 정의는 치명적 오류

기본 매개변수

매개변수에 기본값을 지정할 수 있어 호출 시 생략할 수 있습니다. 기본값은 반드시 상수 표현식이어야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
function greet(string $name, string $title = 'Mr.'): string
{
return "$title $name";
}
greet('Nick'); // Mr. Nick
greet('Anna', 'Ms.'); // Ms. Anna
// 기본값은 상수식이어야 함:
// 함수 호출이나 변수는 쓸 수 없음
// 선택 인자는 필수 인자 뒤에 둠:
// function f($a, $b = 1, $c = 2)

가변 매개변수

...$args는 나머지 모든 매개변수를 배열로 수집하며, 매개변수 목록의 마지막에 위치해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function sum(int ...$nums): int
{
return array_sum($nums);
}
sum(1, 2, 3); // 6
sum(10, 20); // 30
sum(); // 0
// 배열 전달에는 펼침 필요:
$arr = [1, 2, 3];
sum(...$arr); // 6
// 타입 선언이 각 요소를 제약:
// int ...$nums 로 전부 int 보장

명명된 매개변수

호출 시 매개변수명: 값 형태로 전달하며(8.0+), 선택적 매개변수를 건너뛰고 순서에 구애받지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function makeProfile(
string $name,
string $email = '',
int $age = 0,
): array {
return [$name, $email, $age];
}
// email 을 건너뛰고 age 에 직접 전달:
makeProfile(
name: 'Rex',
age: 5,
);
// ['Rex', '', 5]
// 배열 펼침과 결합:
$args = ['name' => 'Rex', 'age' => 5];
makeProfile(...$args);

반환 타입

반환 타입은 콜론 뒤에 선언하며, void, ?T, 유니온 타입, 배열을 지원합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function nothing(): void {} // 반환값 없음
function maybe(): ?int // null 일 수 있음
{
return null;
}
function both(): int|string // 유니온 반환
{
return 'ok';
}
function shape(): array // 배열 반환
{
return ['name' => 'Rex'];
}
function never(): never // 절대 반환하지 않음
{
throw new \Exception('终止');
}
// 반환 타입 불일치면 TypeError

클로저

익명 함수는 function()으로 정의하며, use로 외부 변수를 캡처합니다. 변수에 대입하거나 콜백으로 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 익명 함수를 변수에 할당:
$square = function (int $x): int {
return $x * $x;
};
$square(4); // 16
// use 로 값 캡처:
$factor = 2;
$mul = function ($x) use ($factor): int {
return $x * $factor;
};
$mul(5); // 10
// use 로 참조 캡처하면 외부를 수정 가능:
$count = 0;
$inc = function () use (&$count): void {
$count++;
};
$inc();
echo $count; // 1

화살표 함수

fn() => 표현식은 외부 변수를 자동으로 값으로 캡처하며, 단일 표현식으로 간결하게 작성해 콜백에서 자주 사용됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$factor = 2;
$double = fn(int $x) => $x * $factor;
$double(10); // 20
// 배열 콜백이 더 간결:
$nums = [1, 2, 3, 4];
$evens = array_filter(
$nums,
fn($n) => $n % 2 === 0,
); // [2, 4]
// 화살표 함수는 식이 하나뿐:
// 여러 문은 일반 클로저 필요
// 값 캡처,외부 수정은 use & 클로저

호출 가능 객체

8.1+에서는 strlen(...)로 first-class callable을 생성해 함수를 일급 값으로 다룰 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 일급 콜러블(8.1+):
$len = strlen(...);
$len('hello'); // 5
// 객체 메서드:
$c = new Counter();
$inc = $c->increment(...);
$inc();
// callable 타입으로 인자 수신:
function run(callable $fn): void
{
echo $fn();
}
run(fn() => 'done');
// call_user_func 은 옛 방식
// 새 코드는 (...) 구문 우선

제너레이터

yield로 값을 한 개씩 생성하는 함수를 제너레이터라 하며, 지연 평가로 대용량 데이터를 다룰 때 메모리를 절약합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function rangeGen(int $n): Generator
{
for ($i = 0; $i < $n; $i++) {
yield $i;
}
}
foreach (rangeGen(5) as $v) {
echo "$v "; // 0 1 2 3 4
}
// 제너레이터는 지연,쓰면서 생성:
$gen = rangeGen(3);
$gen->current(); // 0
$gen->next();
$gen->current(); // 1
// 키-값 생성:
yield 'key' => 'value';

7.문자열

문자열 리터럴, 보간, 연결, 멀티바이트 처리와 자주 쓰는 함수를 다룹니다.

문자열 리터럴

작은따옴표는 그대로 출력하고, 큰따옴표는 이스케이프와 변수를 해석합니다. 두 가지 스타일을 모두 흔히 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 작은따옴표는 그대로 출력:
echo 'Hello\n'; // 이스케이프 안 됨
// 큰따옴표는 이스케이프 해석:
echo "Hello\n"; // 줄바꿈
// 큰따옴표 안 변수 보간:
$name = 'Rex';
echo "hi $name"; // hi Rex
// 작은따옴표 안의 작은따옴표는 이스케이프 필요:
echo 'it\'s';
// 여러 줄 텍스트는 연결이나 sprintf 사용
// heredoc 은 들여쓰기 영향 받기 쉬움

변수 보간

큰따옴표 안에서 $var를 그대로 보간하며, 복잡한 표현식은 중괄호 {$arr['k']}를 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$name = 'Rex';
$age = 5;
// 단순 변수:
echo "姓名 $name"; // 姓名 Rex
// 배열 요소는 중괄호 사용:
$user = ['name' => 'Rex'];
echo "{$user['name']}"; // Rex
// 객체 속성:
class Dog
{
public string $name = '旺财';
}
$dog = new Dog();
echo "{$dog->name}"; // 旺财
// 복잡한 식은 중괄호로 감쌈
// 간단한 경우는 . 로 연결

문자열 연결

. 으로 두 문자열을 연결하고, .= 으로 덧붙입니다. 여러 번 연결할 때는 implode나 sprintf를 쓰면 더 읽기 쉽습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$first = 'Rex';
$last = 'Wang';
// 연결:
$full = $first . ' ' . $last;
echo $full; // Rex Wang
// 추가:
$msg = '';
$msg .= 'hello ';
$msg .= 'world';
echo $msg; // hello world
// 배열을 문자열로:
implode('-', [1, 2, 3]); // 1-2-3
// 숫자 연결은 자동으로 문자열 변환:
echo '第' . 3 . '名'; // 第3名

멀티바이트 함수

CJK 등 멀티바이트 문자는 mb_ 접두 함수가 문자 단위로 계산하며, strlen은 바이트 단위로 계산합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$s = 'Hello, 世界';
// 바이트 수 vs 문자 수:
strlen($s); // 12(바이트)
mb_strlen($s); // 8(문자)
// 문자 단위로 잘라내기:
mb_substr($s, 0, 5); // 'Hello'
// 바이트로 자르면 중국어가 깨짐
// 대소문자 변환:
mb_strtoupper('hello');
// 문자 배열로 분해:
mb_str_split($s);
// 중국어 처리는 반드시 mb_ 계열 사용

서식 지정 sprintf

sprintf/printf는 %s, %d 같은 자리표시자로 서식을 지정하며, 연결보다 읽기 쉽습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$name = 'Rex';
$age = 5;
// 포맷:
$line = sprintf('%s 今年 %d 岁', $name, $age);
echo $line; // Rex 今年 5 岁
// 직접 출력:
printf('%.2f', 3.14159); // 3.14
// 자주 쓰는 플레이스홀더:
// %s 문자열 %d 정수 %f 부동소수점
// %b 2진수 %x 16진수
// %-5s 왼쪽 정렬 %05d 0 채움
// 인자 순서는 교체 가능:
sprintf('%2$s-%1$s', 'a', 'b'); // b-a

찾기와 바꾸기

strpos는 위치를 찾고, str_contains는 포함 여부를 판단하며, str_replace로 일괄 치환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$s = 'Hello, world!';
// 포함 여부 판정(8.0+):
str_contains($s, 'world'); // true
// 위치 찾기:
strpos($s, 'world'); // 7
strpos($s, 'xyz'); // false
// 치환:
str_replace('world', 'PHP', $s);
// 'Hello, PHP!'
// 대소문자 무시 치환:
str_ireplace('HELLO', 'Hi', $s);
// 위치 지정 치환:
// substr_replace($s, 'X', 0, 5)

분할과 결합

explode는 구분자로 문자열을 배열로 분할하고, implode는 배열을 다시 문자열로 결합합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 분할:
$parts = explode(',', 'a,b,c');
// ['a', 'b', 'c']
// 개수 제한:
explode(',', 'a,b,c', 2);
// ['a', 'b,c']
// 빈 문자열 요소도 유지:
explode(',', 'a,,c');
// ['a', '', 'c']
// 결합:
implode(' | ', ['a', 'b']);
// 'a | b'
// 공백으로 분할:
preg_split('/\s+/', 'a b c');
// ['a', 'b', 'c']

대소문자와 공백 제거

strtolower/strtoupper는 대소문자를 변환하고, trim은 양쪽 공백을 제거하며, ltrim/rtrim은 한쪽만 제거합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$s = ' Hello World ';
// 앞뒤 공백 제거:
trim($s); // 'Hello World'
// 한쪽만 제거:
ltrim(' hi'); // 'hi'
rtrim('hi '); // 'hi'
// 대소문자:
strtolower('ABC'); // 'abc'
strtoupper('abc'); // 'ABC'
ucfirst('hello'); // 'Hello'
ucwords('hello world');// 'Hello World'
// 지정 문자 제거:
trim('...hi...', '.'); // 'hi'

8.배열과 컬렉션

배열 생성, 추가·삭제·수정·조회, 순회, 정렬과 Spl 자료구조를 다룹니다.

배열 생성

[] 또는 array()로 배열을 생성합니다. 배열 리터럴은 자동으로 0부터 증가하는 정수 키를 할당합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 리터럴 생성:
$list = [1, 2, 3];
// 키 지정:
$map = ['a' => 1, 'b' => 2];
// 키 생략 시 자동 증가:
$list = ['x', 'y']; // 키 0,1
// 빈 배열:
$empty = [];
// 키 지정 추가:
$arr[5] = 'five';
$arr[] = 'six'; // 키 6
// 숫자 문자열 키는 자동으로 정수 변환

접근과 수정

인덱스로 요소에 접근하고, []로 추가하며, unset으로 삭제합니다. 존재하지 않는 키를 읽으면 경고가 발생합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$user = ['name' => 'Rex', 'age' => 5];
// 접근:
echo $user['name']; // Rex
// 수정:
$user['age'] = 6;
// 추가:
$user['city'] = '北京';
// 삭제:
unset($user['age']);
// 안전하게 읽어 경고 회피:
$age = $user['age'] ?? '未知';
// 중첩 접근:
$data['a']['b']['c'];

추가·삭제와 개수

array_push/array_pop은 스택의 끝을 다루고, array_shift/unshift는 큐의 앞을 다루며, count로 개수를 셉니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$stack = [1, 2, 3];
// 끝에 추가:
$stack[] = 4; // 권장
array_push($stack, 5, 6);
// 끝을 팝:
$last = array_pop($stack); // 6
// 앞을 시프트:
$first = array_shift($stack); // 1
// 앞에 삽입:
array_unshift($stack, 0);
// 카운트:
count($stack);
// 존재 판정:
in_array(2, $stack); // true

배열 순회

foreach는 값 또는 키·값 쌍을 순회하며, 리스트 해체로 2차원 배열을 순회할 수도 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$users = [
['name' => 'Rex', 'age' => 5],
['name' => 'Anna', 'age' => 30],
];
// 값만 가져오기:
foreach ($users as $u) {
echo $u['name'];
}
// 키와 값 가져오기:
foreach ($users as $i => $u) {
echo "$i: {$u['name']}";
}
// 리스트 분해:
foreach ($users as ['name' => $n]) {
echo $n;
}
// while+each 는 옛 방식,사용 금지

매핑과 필터링

array_map은 요소를 하나씩 변환하고, array_filter는 콜백으로 걸러내며, array_reduce는 값을 하나로 축약합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$nums = [1, 2, 3, 4];
// 매핑:
$doubled = array_map(
fn($n) => $n * 2,
$nums,
); // [2, 4, 6, 8]
// 필터:
$evens = array_filter(
$nums,
fn($n) => $n % 2 === 0,
); // [2, 4]
// 리듀스:
$sum = array_reduce(
$nums,
fn($acc, $n) => $acc + $n,
0,
); // 10
// 여러 배열 동시 매핑:
array_map(fn($a, $b) => $a + $b, [1, 2], [3, 4]);

정렬

sort는 값을 정렬하고, ksort는 키를 기준으로 하며, asort는 값을 기준으로 키 연관을 유지하고, usort는 사용자 정의 비교 함수를 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$nums = [3, 1, 4, 1, 5];
// 값으로 정렬(키 재생성):
sort($nums); // [1, 1, 3, 4, 5]
rsort($nums); // 내림차순
// 키를 유지하며 정렬:
$ages = ['a' => 30, 'b' => 20];
asort($ages); // 값 오름차순,키 유지
ksort($ages); // 키로 정렬
// 커스텀 비교:
usort(
$users,
fn($x, $y) => $x['age'] <=> $y['age'],
);
// 다차원 열로 정렬:
array_multisort($ages, SORT_DESC);

병합과 슬라이스

array_merge는 배열을 병합하고, array_slice는 부분 배열을 추출하며, array_combine은 키와 값으로 합칩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 병합(문자열 키는 후자가 덮어씀):
$a = ['a' => 1, 'b' => 2];
$b = ['b' => 3, 'c' => 4];
$m = array_merge($a, $b);
// ['a' => 1, 'b' => 3, 'c' => 4]
// 숫자 키 병합은 재정렬됨:
array_merge([1], [2]); // [1, 2]
// 부분 배열 가져오기:
$nums = [1, 2, 3, 4, 5];
array_slice($nums, 1, 2); // [2, 3]
// 키와 값을 결합:
array_combine(['a', 'b'], [1, 2]);
// ['a' => 1, 'b' => 2]
// 중복 제거:
array_unique([1, 1, 2]); // [1, 2]

해체 할당

리스트 [] 또는 키·값 [] 해체로 배열 요소를 여러 변수에 할당하며, foreach 안에서도 사용할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 리스트 분해:
[$a, $b] = [1, 2];
echo $a; // 1
// 키 지정 분해:
[0 => $x, 2 => $z] = [1, 2, 3];
echo $x; // 1
// 키-값 분해:
['name' => $n] = ['name' => 'Rex'];
echo $n; // Rex
// 나머지 수집:
[$first, ...$rest] = [1, 2, 3];
// $rest = [2, 3]
// 함수 반환 배열 분해:
[$min, $max] = minMax($nums);

Spl 자료구조

SplStack, SplQueue, SplFixedArray는 고정된 기능을 갖춘 자료구조를 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 스택(후입선출):
$stack = new SplStack();
$stack->push('a');
$stack->push('b');
$stack->pop(); // 'b'
// 큐(선입선출):
$queue = new SplQueue();
$queue->enqueue('a');
$queue->enqueue('b');
$queue->dequeue(); // 'a'
// 고정 크기 배열:
$fixed = new SplFixedArray(3);
$fixed[0] = 'hi';
$fixed->setSize(5);
// 객체 집합:
$store = new SplObjectStorage();
$store->attach($obj, 'meta');
$store->contains($obj);

9.메모리와 성능

참조 카운팅, 순환 참조 GC, WeakReference, 메모리 측정을 다룹니다.

참조 카운팅

zval이 참조 수를 기록하고 0이 되면 즉시 해제합니다. 대입과 매개변수 전달 모두 카운트를 증가시킵니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 각 변수는 참조 카운트 보유:
$a = 'hello'; // 카운트 1
$b = $a; // 카운트 2
unset($a); // 카운트 1
unset($b); // 카운트 0,회수
// 쓰기 시 복사:쓸 때만 복사
// 카운트 관찰(xdebug 필요):
// xdebug_debug_zval('a')

순환 참조 GC

객체가 서로 참조하면 참조 카운트가 0이 되지 않으므로 순환 가비지 컬렉터가 주기적으로 정리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Node
{
public ?Node $next = null;
}
$a = new Node();
$b = new Node();
$a->next = $b;
$b->next = $a; // 환 구조 형성
unset($a, $b);
// 참조 카운트는 1 유지,0 이 안 됨
// GC 버퍼가 차면 자동 스캔:
gc_collect_cycles(); // 수동 회수
// 오래 사는 객체는 적극적으로 연결 해제:
// $a->next = null;

WeakReference 약한 참조

WeakReference(8.0+)는 객체를 보유하지만 가비지 컬렉션을 막지 않으며, 캐시 같은 상황에 적합합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Cache
{
public array $data = [];
}
$obj = new Cache();
// 약한 참조는 강한 참조 카운트를 늘리지 않음:
$weak = WeakReference::create($obj);
$weak->get(); // Cache 인스턴스
unset($obj);
// 객체가 회수된 후:
$weak->get(); // null
// 약한 맵 8.4+:WeakMap
// 객체에 추가 데이터를 연결하기에 적합

메모리 측정

memory_get_usage는 현재 사용량을, memory_get_peak_usage는 최대 사용량을 확인합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 현재 메모리 사용량(바이트):
$used = memory_get_usage();
// 피크 사용량:
$peak = memory_get_peak_usage();
// MB 표시로 변환:
printf('%.2f MB', $peak / 1048576);
// 메모리 상한 변경:
// ini_set('memory_limit', '512M');
// CLI 시작 시 지정:
// $ php -d memory_limit=1G script.php
// 큰 배열은 처리 후 즉시 unset

적시 해제

큰 변수를 사용한 뒤 unset으로 참조 카운트를 0으로 만들어 피크를 낮추면 배치 처리가 더 안정적입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 큰 배열 처리 후 해제:
$lines = file('big.log');
unset($lines);
// 루프 안에서 분할 처리:
$result = [];
for ($i = 0; $i < 1000; $i++) {
$result[] = str_repeat('x', 100000);
if ($i % 100 === 99) {
// 나눠서 기록 후 비우기:
$result = [];
}
}
// 객체 참조를 null 로 만들어 회수 촉진:
// $obj = null;

OPcache 캐시

OPcache는 컴파일된 바이트코드를 캐시해 매 요청마다 다시 컴파일하지 않도록 하며, 운영 환경에서는 반드시 켜야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 활성화 확인:
// $ php -m | grep opcache
// php.ini 권장:
// opcache.enable=1
// opcache.memory_consumption=128
// opcache.max_accelerated_files=10000
// CLI 에서는 기본 비활성:
// $ php -d opcache.enable=1 script.php
// 바이트코드 캐시로 컴파일 생략:
// 트래픽 많은 페이지 처리량이 크게 향상
// 개발 환경에서는 임시로 끌 수 있음

대용량 데이터 처리

매우 큰 배열은 메모리를 많이 차지하므로 제너레이터, 스트림 읽기, SplFixedArray로 사용량을 제어합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 제너레이터로 전체 배열 대체:
function readLines(string $file): Generator
{
$h = fopen($file, 'r');
while (($line = fgets($h)) !== false) {
yield rtrim($line);
}
fclose($h);
}
// 읽으며 처리,메모리 일정:
foreach (readLines('big.log') as $line) {
process($line);
}
// 많은 정수는 SplFixedArray:
$fixed = new SplFixedArray(1000000);
// 큰 파일을 한 번에 읽는 것 피하기

성능 요점

문자열 연결 반복을 피하고, 미리 할당하며, 연결을 재사용하고, 불필요한 쿼리를 줄입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 연결은 루프 내 누적을 피함:
$parts = [];
foreach ($items as $it) {
$parts[] = $it->name;
}
$result = implode(',', $parts);
// 연결 객체 재사용:
$pdo = new PDO($dsn); // 싱글턴 공유
// 루프 밖에서 카운트:
$count = count($list);
// 데이터베이스 중복 쿼리 회피:
// 한 번만 조회하고,메모리에서 필터
// opcache 와 버퍼링 결합
// 핫 경로는 단순하게 유지

10.객체 지향

클래스, 생성자, 가시성, 상속, 인터페이스, trait, 읽기 전용 속성을 다룹니다.

클래스 정의

class로 클래스를 정의하고, 속성에 타입을 지정하며, 메서드는 클래스 안에서 선언합니다. new로 인스턴스를 생성합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class User
{
public string $name = '';
public int $age = 0;
public function describe(): string
{
return "{$this->name}, {$this->age}";
}
}
$u = new User();
$u->name = 'Rex';
$u->age = 5;
echo $u->describe(); // Rex, 5
// 속성은 선언 시 초기화 필요
// 또는 생성자에서 대입

생성자와 속성 승격

__construct 생성자로 객체를 초기화합니다. 8.0+에서는 생성자 매개변수를 바로 속성으로 승격할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 전통적인 방식:
class OldUser
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
// 프로퍼티 프로모션(8.0+):
class User
{
public function __construct(
public string $name,
public int $age,
) {}
}
$u = new User('Rex', 5);
echo $u->name; // Rex
// 승격된 인자는 자동으로 프로퍼티가 됨

가시성

public은 공개, protected는 자식 클래스에서 접근 가능, private은 해당 클래스만 접근하며 기본값은 public입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class BankAccount
{
public string $owner = '';
protected float $balance = 0;
private string $secret = 'xx';
public function deposit(float $n): void
{
$this->balance += $n;
}
}
class Child extends BankAccount
{
public function peek(): float
{
return $this->balance; // protected 는 접근 가능
}
}
$acc = new BankAccount();
$acc->owner = 'Rex';
// $acc->balance = 1; // 접근 불가

상속

extends로 부모 클래스를 상속하고 메서드를 재정의하며, parent::로 부모 클래스의 메서드를 호출합니다.

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
class Animal
{
public function __construct(
protected string $name,
) {}
public function speak(): string
{
return "$this->name: ...";
}
}
class Dog extends Animal
{
public function speak(): string
{
return parent::speak() . ' 汪汪';
}
}
$dog = new Dog('旺财');
echo $dog->speak(); // 旺财: ... 汪汪
// 하위 클래스는 부모의 메서드와 속성을 덮어씀
// final 메서드는 재정의 불가

인터페이스

interface는 메서드 시그니처를 정의하고 implements로 구현합니다. 클래스는 여러 인터페이스를 구현할 수 있습니다.

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
interface Speakable
{
public function speak(): string;
}
interface Runnable
{
public function run(): void;
}
// 여러 인터페이스 구현:
class Robot implements Speakable, Runnable
{
public function speak(): string
{
return 'beep';
}
public function run(): void
{
echo "running\n";
}
}
// 인터페이스는 인터페이스를 상속 가능:
// interface X extends Speakable {}
// 인터페이스에 의존해 교체 용이

추상 클래스

abstract class는 인스턴스화할 수 없으며, 추상 메서드는 서브클래스에서 구현합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
abstract class Shape
{
abstract public function area(): float;
public function describe(): string
{
return '面积: ' . $this->area();
}
}
class Circle extends Shape
{
public function __construct(
private float $r,
) {}
public function area(): float
{
return 3.14159 * $this->r * $this->r;
}
}
$c = new Circle(2);
echo $c->describe(); // 面积: 12.56636

trait 재사용

trait은 여러 클래스에 메서드를 횡적으로 재사용하며, use로 가져오고 여러 trait을 조합할 수 있습니다.

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
trait Loggable
{
public function log(string $msg): void
{
echo "[LOG] $msg\n";
}
}
trait Timestampable
{
public function createdAt(): string
{
return date('Y-m-d H:i:s');
}
}
class Order
{
use Loggable, Timestampable;
public function create(): void
{
$this->log('创建订单');
}
}
$order = new Order();
$order->create(); // [LOG] 创建订单
// 클래스의 메서드가 trait 보다 우선

정적 멤버

static 속성과 메서드는 인스턴스가 아닌 클래스에 속하며, self:: 또는 클래스명::으로 접근합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Counter
{
private static int $total = 0;
public static function hit(): void
{
self::$total++;
}
public static function get(): int
{
return self::$total;
}
}
Counter::hit();
Counter::hit();
echo Counter::get(); // 2
// 정적 메서드 안에는 $this 가 없음:
// 비정적 멤버에는 접근 불가
// 정적 속성은 모든 인스턴스 공유
// 지연 정적 바인딩은 static:: 사용

읽기 전용 속성

readonly 속성(8.1+)은 선언과 생성자에서 한 번만 값을 할당할 수 있으며, 이후에는 읽기 전용입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Point
{
public function __construct(
public readonly float $x,
public readonly float $y,
) {}
}
$p = new Point(1.0, 2.0);
echo $p->x; // 1
// $p->x = 3; // 읽기 전용 오류
// 읽기 전용 클래스(8.2+):
readonly class Config
{
public function __construct(
public string $dsn,
) {}
}
// 값 객체와 불변 설정에 적합
// 프로퍼티 승격 + readonly 자주 사용

11.오류 처리

예외 계층, try/catch/finally, 사용자 정의 예외 및 오류 처리를 다룹니다.

try / catch

try 블록에서 던진 예외를 catch가 잡으며, 잡은 후 프로그램은 계속 실행됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
$n = 1 / 0; // 8.0+ 는 DivisionByZeroError 를 던짐
} catch (\DivisionByZeroError $e) {
echo '除数不能为零';
}
// 예외 포착:
try {
throw new \RuntimeException('出错了');
} catch (\RuntimeException $e) {
echo $e->getMessage(); // 出错了
}
// 포착 안 된 예외는 치명적 오류가 됨
// 스크립트가 종료되고,오류 코드 255

Throwable 계층

Throwable이 기본 클래스이며, Error는 엔진 오류, Exception은 프로그램 예외를 나타냅니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Throwable
// ├── Error(엔진급)
// │ ├── TypeError
// │ ├── ValueError
// │ └── DivisionByZeroError
// └── Exception(프로그램급)
// ├── LogicException
// └── RuntimeException
// 임의의 오류/예외 포착:
try {
risky();
} catch (\Throwable $e) {
echo $e->getMessage();
}
// Error 와 Exception 모두 Throwable 구현
// 각각 catch 해 정밀 처리 가능

사용자 정의 예외

Exception 또는 RuntimeException을 상속해 비즈니스 예외를 정의하고 추가 필드를 담습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ValidationException extends \RuntimeException
{
public function __construct(
public readonly string $field,
string $message = '',
) {
parent::__construct(
$message ?: "字段 {$field} 无效",
);
}
}
// 던지기:
throw new ValidationException('email');
// 포착 시 필드 가져오기:
try {
validate($input);
} catch (ValidationException $e) {
echo $e->field; // email
echo $e->getMessage();
}

다중 catch

여러 예외 타입을 catch할 때는 |로 구분하며, 선언된 순서대로 매칭되고 서브클래스가 부모보다 먼저 옵니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
try {
parse($input);
} catch (\InvalidArgumentException | \LengthException $e) {
// 두 종류 예외를 동일하게 처리
echo $e->getMessage();
} catch (\Exception $e) {
// 기타 예외는 따로 처리
logError($e);
}
// 매치 순서는 위에서 아래:
// 구체적 예외를 먼저,부모 클래스를 나중에
// 8.0+ 는 변수명 생략 가능:
// catch (\Exception) { ... }

finally

finally는 예외 발생 여부와 관계없이 실행되며, 리소스 정리와 연결 종료에 사용됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function process(): string
{
$conn = openConnection();
try {
return doWork($conn);
} finally {
$conn->close(); // 반드시 실행
}
}
// return 이어도 먼저 finally 실행
// 파일 핸들 해제:
$h = fopen('a.txt', 'r');
try {
// 처리
} finally {
fclose($h);
}
// finally 만 쓰고,예외를 포착하지 않아도 됨

전역 예외 핸들러

set_exception_handler로 잡히지 않은 예외를 마지막에 처리해 로그와 응답을 일관되게 처리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
set_exception_handler(function (\Throwable $e): void {
// 로그 기록:
error_log($e->getMessage());
// 500 반환:
http_response_code(500);
echo '服务器开小差了';
});
// 이후 미포착 예외는 모두 여기로
// 기본 핸들러 복원:
restore_exception_handler();
// set_error_handler 는 비예외 오류 처리
// 8.0+ 에서는 대부분의 오류도 예외

Error와 Exception

엔진 오류는 Error(타입/인자)이고 비즈니스 예외는 Exception이므로 분리해 잡습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// TypeError:타입 불일치:
function add(int $a, int $b): int
{
return $a + $b;
}
try {
add('a', 'b'); // TypeError 를 던짐
} catch (\TypeError $e) {
echo '参数类型错误';
}
// ValueError:값이 부적합:
try {
array_chunk([1, 2], 0); // ValueError 를 던짐
} catch (\ValueError $e) {
echo '长度必须为正';
}
// 엔진 오류는 대부분 Error 상속
// Exception 과 분리해 포착

예외 체인

catch에서 새 예외를 던질 때 원래 예외를 세 번째 인자로 전달하며, getPrevious로 추적합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
try {
$data = fetchFromApi();
} catch (\HttpException $e) {
throw new \RuntimeException(
'获取数据失败',
0,
$e, // 이전 예외
);
}
// 원인 추적:
try {
loadAll();
} catch (\RuntimeException $e) {
$prev = $e->getPrevious();
if ($prev) {
echo $prev->getMessage();
}
}
// 로그에 전체 호출 스택 기록:
// error_log($e->getTraceAsString());

12.파일 I/O

파일 읽기/쓰기, JSON, CSV, 표준 스트림을 다룹니다.

파일 읽기

file_get_contents는 파일 전체를 문자열로 읽으며, 작은 파일에 적합하고 실패 시 false를 반환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 파일 전체 읽기:
$text = file_get_contents('data.txt');
if ($text === false) {
echo '读取失败';
}
// 배열로 읽기(각 줄 1 요소):
$lines = file('log.txt');
// 줄바꿈과 빈 줄 무시:
$lines = file(
'log.txt',
FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES,
);
// 원격 읽기(allow_url_fopen 필요):
$json = file_get_contents('https://example.com/api');

파일 쓰기

file_put_contents는 문자열을 쓰며, FILE_APPEND로 추가 모드이고 쓴 바이트 수를 반환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 덮어쓰기:
$n = file_put_contents('out.txt', 'Hello!');
// 추가 모드:
file_put_contents('log.txt', "更多\n", FILE_APPEND);
// 배열 쓰기:
$lines = ['a', 'b'];
file_put_contents('list.txt', implode("\n", $lines));
// 쓰기 실패 확인:
if (file_put_contents('x.txt', 'data') === false) {
echo '写入失败';
}
// 권한 문제는 false 를 반환

줄 단위 읽기

fopen + fgets로 줄 단위로 읽어 메모리 사용량이 일정하므로 대용량 파일에 적합합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$h = fopen('big.log', 'r');
if ($h === false) {
exit('无法打开文件');
}
while (($line = fgets($h)) !== false) {
// 줄 끝 줄바꿈 제거:
$line = rtrim($line, "\r\n");
process($line);
}
fclose($h);
// 제너레이터로 감싸면 다루기 쉬움:
// function lines(string $f): Generator
// 큰 파일은 스트림 처리하고,한 번에 읽지 않음

열기 모드

fopen의 r/w/a 모드가 읽기/쓰기 동작을 제어하며, w는 기존 내용을 비웁니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// r:읽기 전용,파일 필수
$h = fopen('a.txt', 'r');
// r+:읽기/쓰기,파일 필수
$h = fopen('a.txt', 'r+');
// w:쓰기,비우거나 생성
$h = fopen('a.txt', 'w');
// a:추가,끝에 쓰기
$h = fopen('a.txt', 'a');
// b:바이너리 모드(Windows 에서 필요)
$h = fopen('img.bin', 'wb');
// 쓰고 나면 fclose 로 핸들 해제
// 핸들 누수는 장수명 프로세스에서 쌓임

JSON 인코딩/디코딩

json_encode는 JSON으로 변환하고 json_decode가 파싱합니다. 두 번째 인자에 true를 주면 배열을 반환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$data = ['name' => 'Rex', 'age' => 5];
// 인코딩:
$json = json_encode($data);
// {"name":"Rex","age":5}
// 정렬과 중국어 비이스케이프:
json_encode(
$data,
JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE,
);
// 배열로 파싱:
$arr = json_decode($json, true);
// 객체로 파싱:
$obj = json_decode($json);
// 오류 확인:
if (json_last_error() !== JSON_ERROR_NONE) {
echo json_last_error_msg();
}

CSV 읽기/쓰기

fgetcsv는 CSV 한 줄을 파싱하고 fputcsv는 한 줄을 쓰며, 따옴표와 이스케이프를 자동으로 처리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 읽기:
$h = fopen('data.csv', 'r');
$headers = fgetcsv($h);
while (($row = fgetcsv($h)) !== false) {
$item = array_combine($headers, $row);
echo $item['name'];
}
fclose($h);
// 쓰기:
$h = fopen('out.csv', 'w');
fputcsv($h, ['name', 'age']);
fputcsv($h, ['Rex', 5]);
fclose($h);
// 쉼표/따옴표 자동 이스케이프
// 구분자 커스텀 가능:fgetcsv($h, 0, ';')

디렉터리와 glob

glob은 패턴으로 파일을 매칭하고 scandir은 디렉터리를 나열하며 mkdir은 디렉터리를 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 와일드카드로 파일 매치:
foreach (glob('data/*.csv') as $file) {
echo $file;
}
// 디렉터리 나열:
$items = scandir('.');
// 타입 판정:
is_dir('src'); // true
is_file('a.txt'); // true
// 디렉터리 생성(상위 포함):
mkdir('logs/2026', 0755, true);
// 파일 삭제:
unlink('tmp.txt');
// 존재 판정:
file_exists('a.txt');

표준 스트림

STDIN, STDOUT, STDERR 상수는 표준 입력/출력에 대응하며 CLI에서 자주 사용됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 한 줄 입력:
$line = fgets(STDIN);
$name = trim($line);
// 표준 출력으로 출력:
fwrite(STDOUT, "hello $name\n");
// 오류 스트림으로 출력:
fwrite(STDERR, "警告:出错了\n");
// 줄 단위로 입력 처리:
while (($line = fgets(STDIN)) !== false) {
echo strtoupper($line);
}
// 파이프와 조합:
// echo "hi" | php app.php

13.자주 겪는 함정

일상 개발에서 가장 자주 빠지는 함정과 올바른 작성법을 다룹니다.

==와 ===

==는 느슨한 비교로 타입을 변환하고, ===는 타입과 값을 엄격히 비교합니다. 판정에는 가능한 한 ===를 사용하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:느슨한 비교의 의외
0 == 'foo'; // true
0 == ''; // true
'1' == 1; // true
// GOOD:엄격 비교
0 === 'foo'; // false
'1' === 1; // false
// 0 판정은 엄격히 구분:
$count = 0;
if ($count == false) { /* 오판 */ }
if ($count === 0) { /* 정확함 */ }
// 관례:특수한 경우 외에는 === 사용

foreach 참조 함정

foreach에서 &로 요소를 수정한 뒤에도 마지막 요소는 여전히 원래 배열을 가리키므로, $item을 재사용하면 데이터가 오염됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$items = [1, 2, 3];
// BAD:루프 후 $item 이 참조를 남김
foreach ($items as &$item) {
$item *= 2;
}
// 나중에 $item 을 재사용하면 배열을 실수로 수정
// GOOD:쓴 뒤 unset 으로 해제
foreach ($items as &$item) {
$item *= 2;
}
unset($item);
// 또는 인덱스로 수정:
foreach ($items as $i => $v) {
$items[$i] = $v * 2;
}

isset과 키 존재

isset은 값이 null인 키를 존재하지 않는 것으로 간주하므로, 키의 실제 존재 여부는 array_key_exists로 확인하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$arr = ['a' => null];
// BAD:키는 존재하지만 값은 null
isset($arr['a']); // false
// GOOD:키가 실제로 존재하는지 판정
array_key_exists('a', $arr); // true
// isset 은 읽기 전 판정에 사용:
$name = isset($arr['b']) ? $arr['b'] : '默认';
// ?? 은 isset 과 동일하며 기본값 제공:
$name = $arr['b'] ?? '默认';
// null 과 부재를 구분하려면 array_key_exists

느슨한 비교의 함정

느슨한 비교에서는 문자열과 숫자의 비교 규칙이 특수해서 '0'과 false가 같으므로 명시적 변환이 필요합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// BAD:느슨한 비교의 의외
'0' == false; // true
'0' == null; // true
'abc' == 0; // true
'1e3' == 1000; // true(과학적 표기)
// GOOD:명시적 변환 후 비교
(int) 'abc' === 0; // 의도가 명확
filter_var('1e3', FILTER_VALIDATE_INT);
// 해시 비교는 hash_equals:
// hash_equals($a, $b) 는 타이밍 공격 방어
// 데이터베이스 결과 반환 타입 주의

정의되지 않은 키 경고

존재하지 않는 배열 키에 접근하면(8.0+) 경고가 발생하므로 ?? 또는 isset으로 미리 확인하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$user = ['name' => 'Rex'];
// BAD:없는 키 직접 접근
$city = $user['city'];
// PHP Warning: Undefined array key
// GOOD:null 병합으로 기본값 제공
$city = $user['city'] ?? '未知';
// 또는 먼저 판정 후 접근:
$city = isset($user['city'])
? $user['city']
: '';
// 인자 기본값:
function f(array $cfg = []) {
$x = $cfg['x'] ?? 0;
}

출력 후 응답 헤더 전송

이미 내용을 출력한 뒤 header()를 호출하면 실패하므로, 먼저 버퍼링을 켜거나 응답 헤더를 먼저 보내야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// BAD:출력 후 응답 헤더 전송
echo "hello\n";
// header('Location: /login'); // 오류
// GOOD:먼저 응답 헤더 전송 후 출력
header('Location: /login');
exit;
// 제어 불가 시 먼저 버퍼 시작:
ob_start();
echo "later\n";
ob_end_flush();
// 이미 출력됐는지 확인:
// headers_sent()

empty와 isset

empty는 0, '', '0' 등에 대해 모두 true를 반환하고 isset은 null 여부만 보므로, 의미에 맞게 선택해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$age = 0;
$name = '';
// BAD:empty 는 0 을 빈 값으로 봄
if (empty($age)) { /* 설정 안 됨으로 오판 */ }
// GOOD:숫자 0 은 유효한 값
if (!isset($age) || $age === '') { /* 미설정 */ }
// empty 의미:
empty(0); // true
empty('0'); // true
empty(''); // true
empty(null); // true
// 폼 빈 값 판정:
// trim($val) !== '' 가 더 명확

SQL 인젝션 방지

SQL 문자열을 그대로 이어 붙이면 인젝션 위험이 있으므로 PDO의 prepared statement로 파라미터를 바인딩하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$id = $_GET['id'];
// BAD:문자열 연결 SQL(인젝션 위험)
// $sql = "SELECT * FROM users WHERE id = $id";
// GOOD:준비된 문 + 바인드 파라미터
$pdo = new PDO($dsn, $user, $pass);
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE id = ?',
);
$stmt->execute([$id]);
$rows = $stmt->fetchAll();
// 명명된 파라미터:
$stmt = $pdo->prepare(
'SELECT * FROM users WHERE id = :id',
);
$stmt->execute([':id' => $id]);
// 입력 검증:
// filter_var($id, FILTER_VALIDATE_INT)

14.동시성과 코루틴

PHP의 동시성 모델: 멀티프로세스, 코루틴, Fiber, 그리고 서드파티 확장을 다룹니다.

프로세스 모델

PHP는 기본적으로 단일 스레드이며 요청마다 독립 프로세스로 실행되고 변수는 요청 종료 시 해제됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// PHP 기본 단일 스레드:
// 각 요청은 독립 프로세스/스레드로 실행
// 공유 메모리 제한,쓰기 시 복사:
// 자식 프로세스는 메모리 전체를 복사하지 않음
// 공유 상태 문제 없음:
// 변수는 요청 종료와 함께 해제
// 동시성이 필요할 때의 선택지:
// 1. 다중 프로세스 pcntl_fork
// 2. 코루틴 Fiber / Swoole
// 3. parallel 확장 진짜 스레드
// CLI 서버 모드:
// $ php -S localhost:8000

pcntl_fork 멀티프로세스

pcntl_fork는 자식 프로세스를 분기하며, 부모와 자식이 각각 실행되고 CLI에서만 사용 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// CLI 전용,pcntl 확장 활성화 필요:
// $ php -m | grep pcntl
$pid = pcntl_fork();
if ($pid === -1) {
exit('fork 失败');
}
if ($pid === 0) {
// 자식 프로세스:반환값 0
echo "子进程 PID " . getmypid() . "\n";
exit(0);
}
// 부모 프로세스:자식 프로세스 대기
pcntl_wait($status);
echo "父进程 PID " . getmypid() . "\n";
// 자식 프로세스는 코드와 메모리 스냅샷 공유
// 변경은 서로 영향 없음(쓰기 시 복사)

시그널 처리

pcntl_signal은 시그널 핸들러를 등록하고, dispatch는 대기 중인 시그널을 디스패치합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
declare(ticks = 1);
// 시그널 핸들러 등록:
pcntl_signal(SIGINT, function (int $signo): void {
echo "收到信号 $signo,退出\n";
exit(0);
});
// 대기 중인 시그널 처리:
while (true) {
pcntl_signal_dispatch();
usleep(100000);
}
// 자주 쓰는 시그널:
// SIGTERM 종료 SIGINT Ctrl+C
// SIGHUP 끊김 SIGCHLD 자식 프로세스 종료
// 프로덕션 환경은 시그널로 우아한 종료

parallel 확장

parallel은 PECL 확장으로 진정한 멀티스레드 worker를 제공하며 내장되어 있지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// parallel 은 PECL 확장,내장 아님:
// $ pecl install parallel
use parallel\Runtime;
use parallel\Future;
// 워커 스레드 시작:
$rt = new Runtime();
$future = $rt->run(function (int $n): int {
return $n * $n;
}, [7]);
// 차단하고 결과 가져오기:
$result = $future->value(); // 49
// 스레드 공유 변수는 명시적으로 인자 전달
// 외부 변수 캡처 불가
// 프로덕션 멀티코어 집약 작업에 사용 가능
// Web 환경은 보통 다중 프로세스가 더 안정적

Swoole 코루틴

Swoole은 고성능 네트워크 프레임워크로, 코루틴 스케줄링과 상주 메모리로 고부하 동시 연결에 적합합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Swoole 은 고성능 네트워크 프레임워크 확장:
// $ pecl install swoole
// 코루틴 서버 생성:
use Swoole\Coroutine\Http\Server;
$server = new Server('0.0.0.0', 9501);
$server->handle('/', function ($req, $res) {
// 연결마다 1 코루틴:
$res->end("hello\n");
});
$server->start();
// 상주 메모리 + 코루틴 스케줄링:
// 고동시성에도 메모리가 스레드 모델보다 훨씬 적음
// 다중 프로세스 worker 도 지원:
// Swoole\Server 의 worker_num 설정

Fiber 코루틴

Fiber(8.1+)는 네이티브 코루틴으로 suspend로 일시 중단, resume으로 재개하며 수동으로 스케줄링합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Fiber(8.1+)코루틴,수동 전환:
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('first');
echo "恢复,收到 $value\n";
});
// 시작,첫 suspend 까지 실행:
$value = $fiber->start();
echo "挂起返回 $value\n"; // first
// 재개하며 값 전달:
$fiber->resume('second');
// 恢复,收到 second
// 단일 스레드 내 협력,병렬 아님
// 비동기 코드를 동기식으로 작성하는 데 사용

비동기 라이브러리

내장 async/await는 없으므로 ReactPHP, Amp 같은 이벤트 루프 라이브러리로 비동기를 구현합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 내장 async/await 키워드 없음:
// 이벤트 루프 라이브러리로 비동기 I/O 구현
// ReactPHP 이벤트 루프 예시:
use React\EventLoop\Factory;
$loop = Factory::create();
$loop->addTimer(1.0, function () {
echo "ok\n";
});
$loop->run();
// 비동기 라이브러리의 대표 추상화:
// Promise / Deferred / await
// 단순 동시성은 다중 프로세스로 충분
// 고동시성 I/O 는 Swoole/ReactPHP

프로세스 간 통신

멀티프로세스 간 데이터 공유는 메시지 큐, 파일 또는 공유 메모리로 처리하며, 같은 파일을 직접 수정하는 방식은 피하세요.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 다중 프로세스 간 데이터 공유:
// 1. 파일/데이터베이스
// 2. 메시지 큐(Redis 등)
// 3. 공유 메모리 shmop
// 4. 파이프
// 메시지 큐 간단 예시:
// Redis 리스트를 작업 큐로:
// $redis->lpush('jobs', $task);
// 자식 프로세스가 파이프에 기록:
// posix_kill($pid, SIGUSR1);
// 작업 결과 수집:
// 각 worker 가 별도 파일에 기록
// 프로덕션 시스템에서 자주 사용:
// RabbitMQ / Kafka / Redis Stream
// 여러 프로세스가 같은 파일을 직접 수정하지 않기

15.네트워크

HTTP 클라이언트, curl, URL 파싱, 세션, 소켓을 다룹니다.

HTTP 요청

file_get_contents로 간단한 요청을 보내고 stream_context_create로 타임아웃과 헤더를 설정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 단순 GET(allow_url_fopen):
$body = file_get_contents(
'https://example.com/api',
);
// 타임아웃 포함:
$ctx = stream_context_create([
'http' => ['timeout' => 5],
]);
$body = file_get_contents(
'https://example.com',
false,
$ctx,
);
// 프로덕션은 curl 이나 guzzle 권장
// 상태 코드와 세부 제어 지원

curl 확장

curl_init + curl_exec로 요청을 완전히 제어하고 상태 코드와 오류 정보를 읽습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// curl 확장(CLI 내장):
$ch = curl_init(
'https://api.example.com/data',
);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer xxx',
],
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
// 오류 확인:
if (curl_errno($ch)) {
echo curl_error($ch);
}
// POST 데이터:
// CURLOPT_POST + CURLOPT_POSTFIELDS

POST와 Guzzle

stream_context로 POST JSON을 보낼 수 있지만, 실무에서는 Guzzle 클라이언트를 우선 사용합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// POST JSON:
$data = json_encode(['name' => 'Rex']);
$ctx = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n",
'content' => $data,
],
]);
$res = file_get_contents(
'https://example.com/api',
false,
$ctx,
);
// Guzzle 이 더 실무에 부합:
// $client->request('POST', $url, [
// 'json' => $payload,
// ]);
// 요청 헤더 구분은 CRLF

요청과 응답 헤더

header()는 응답 헤더를 전송하고, http_response_code는 상태 코드를 설정하며, 출력 전에 호출해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 응답 헤더 읽기:
$ctx = stream_context_create([
'http' => ['ignore_errors' => true],
]);
$body = file_get_contents(
'https://example.com',
false,
$ctx,
);
$headers = $http_response_header ?? [];
// 응답 헤더 전송:
header('Content-Type: application/json');
header('Cache-Control: max-age=3600');
// 상태 코드 설정:
http_response_code(404);
// 요청 헤더 읽기:
// getallheaders() 는 연관 배열 반환
// 출력 전에 반드시 header 전송

URL 파싱

parse_url은 URL의 각 구성 요소를 분해하고, parse_str은 쿼리 문자열을 파싱하며, http_build_query는 문자열을 결합합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$url = 'https://user:[email protected]:8080/a?q=1#top';
// 각 구성 요소 파싱:
$parts = parse_url($url);
// [
// 'scheme' => 'https',
// 'host' => 'example.com',
// 'port' => 8080,
// 'path' => '/a',
// 'query' => 'q=1',
// 'fragment' => 'top',
// ]
// 쿼리 문자열:
parse_str('a=1&b=2', $out);
// $out = ['a' => '1', 'b' => '2']
// URL 인코딩 파라미터 조립:
http_build_query(['a' => 1, 'b' => 2]);
// a=1&b=2

쿠키와 세션

session_start는 세션을 시작하고, setcookie는 쿠키를 쓰며, $_COOKIE로 읽습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 세션 시작:
session_start();
// 세션 cookie 자동 생성
// 세션 데이터 읽기/쓰기:
$_SESSION['user_id'] = 123;
echo $_SESSION['user_id'];
// cookie 설정:
setcookie('theme', 'dark', [
'expires' => time() + 3600,
'path' => '/',
'httponly' => true,
'samesite' => 'Lax',
]);
// 읽기:
echo $_COOKIE['theme'] ?? '默认';
// 세션 파기:
session_destroy();
// 세션 데이터는 서버,cookie 는 id 만

소켓

stream_socket_client는 원격에 연결하고, fwrite는 전송하며, fgets는 읽습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 클라이언트 소켓:
$sock = stream_socket_client(
'tcp://example.com:80',
$errno,
$errstr,
10,
);
if (!$sock) {
echo "连接失败: $errstr";
}
fwrite($sock, "GET / HTTP/1.0\r\n\r\n");
while (!feof($sock)) {
echo fgets($sock);
}
fclose($sock);
// 서버 측 대기:
// stream_socket_server('tcp://0.0.0.0:9000')
// 프로덕션 프로토콜 서버는 Swoole 사용
// 수제 socket 은 경계 문제 발생 쉬움

SSE와 WebSocket

SSE는 단방향 텍스트 푸시이고, WebSocket은 양방향 통신이므로 필요에 따라 선택합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SSE:서버가 텍스트 스트림 푸시
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
while (true) {
echo "data: " . json_encode(['t' => time()]) . "\n\n";
flush();
sleep(1);
}
// 클라이언트는 EventSource 로 수신
// 단방향,HTTP 기반이라 단순하고 안정적
// WebSocket 양방향 통신:
// Swoole、Ratchet 같은 확장 필요
// 선택:단순 푸시는 SSE
// 양방향 상호작용에만 WebSocket

16.날짜와 시간

타임스탬프, 서식 지정, 파싱, 시간대 및 시간 연산.

현재 시간 가져오기

time()은 초 단위 타임스탬프를 반환하고, microtime은 밀리초 단위를 반환하며, date는 서식을 지정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 현재 타임스탬프(초):
$ts = time();
// 밀리초 단위:
$ms = microtime(true);
// 현재 시간 포맷:
echo date('Y-m-d H:i:s'); // 2026-08-02 12:30:00
// 기본 타임존 읽기:
date_default_timezone_get();
// 타임존 설정:
// date_default_timezone_set('Asia/Shanghai');
// 구식 time() 은 date() 의 두 번째 인자와 동일

날짜 서식 지정

date()는 서식 문자를 사용해 타임스탬프를 렌더링하며, 연월일시분초와 로컬라이즈된 표기를 지원합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ts = strtotime('2026-08-02 12:30:00');
// 포맷:
echo date('Y-m-d', $ts); // 2026-08-02
echo date('H:i:s', $ts); // 12:30:00
echo date('D, d M Y', $ts); // Sun, 02 Aug 2026
echo date('Y年m月d日', $ts); // 2026年08月02日
// 자주 쓰는 형식 기호:
// Y 네 자리 연 m 두 자리 월 d 두 자리 일
// H 24시간 i 분 s 초
// D 요일 약자 M 월 약자
// 타임스탬프 변환(반대):
// strtotime('2026-08-02')

날짜 파싱

strtotime은 날짜 텍스트와 상대 시간을 파싱하며, 실패 시 false를 반환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 날짜 문자열을 타임스탬프로 파싱:
$ts = strtotime('2026-08-02 12:30:00');
echo $ts;
// 상대 시간:
strtotime('+1 day'); // 내일
strtotime('next Monday'); // 다음 주 월요일
strtotime('-2 weeks'); // 2주 전
// 파싱 실패 시 false 반환:
$bad = strtotime('不是日期');
// false
// 복잡한 파싱은 DateTime:
// new DateTime('2026-08-02 12:30:00')
// 타임존이 파싱 결과에 영향 줌

타임스탬프

Unix 타임스탬프는 UTC 초 단위 값이며, DateTime은 타임스탬프와 객체 간 상호 변환을 제공합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 타임스탬프(epoch 초):
$ts = time();
// 지정 시각 생성:
$dt = new DateTime('2026-08-02 12:30:00');
$ts = $dt->getTimestamp();
// 타임스탬프 포맷:
$dt = new DateTime();
$dt->setTimestamp($ts);
echo $dt->format('Y-m-d');
// 밀리초 타임스탬프(서버):
$ms = (int) (microtime(true) * 1000);
// 타임스탬프는 UTC 초 수
// 표시 시 로컬 타임존으로 환산

시간대

date_default_timezone_set은 기본 시간대를 설정하고, DateTime은 독립적인 시간대를 가질 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 기본 타임존 설정:
date_default_timezone_set('Asia/Shanghai');
// 전체 타임존 보기:
// DateTimeZone::listIdentifiers()
// DateTime 타임존 지정:
$dt = new DateTime('now', new DateTimeZone('UTC'));
echo $dt->format('Y-m-d H:i:s');
// 타임존 변환:
$dt->setTimezone(new DateTimeZone('Asia/Tokyo'));
echo $dt->format('Y-m-d H:i:s P');
// 프로덕션은 UTC 로 통일 저장
// 표시 계층은 사용자 타임존으로 변환
// date('T') 로 타임존 추측 금지

시간 간격

DateInterval은 시간 길이를 나타내며, DateTime의 add/sub로 간격을 더하거나 뺍니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 두 시각의 차이:
$start = new DateTime('2026-08-01');
$end = new DateTime('2026-08-02');
$diff = $start->diff($end);
echo $diff->days; // 1
// 시간 추가:
$dt = new DateTime('2026-08-02');
$dt->add(new DateInterval('P3D')); // 3일 추가
echo $dt->format('Y-m-d'); // 2026-08-05
// 시간 빼기:
$dt->sub(new DateInterval('PT2H')); // 2시간 감산
// DateInterval 형식:
// P 기간 D 일 T 시간 부분
// P1Y 1년 P2M 2개월 PT1H 1시간
// 간격의 일 수 환산:
// $diff->days 총 일 수

시간 차이 계산

DateTime의 diff는 DateInterval을 반환하며, 나이 또는 남은 일수 등을 읽을 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 나이 계산:
$birth = new DateTime('1990-05-20');
$now = new DateTime();
$diff = $birth->diff($now);
echo $diff->y . ' 岁'; // 정수 연
// 차이의 각 필드:
$diff->y; // 년
$diff->m; // 월
$diff->d; // 일
$diff->h; // 시간
$diff->i; // 분
// 사람이 읽을 수 있는 형식:
echo $diff->format('%y 年 %m 月 %d 天');
// 총 초 수:
// $diff->s + $diff->i * 60 + ...
// 음수는 invert 플래그 반전

일시 정지와 타이머

sleep은 블로킹 일시 정지이며, 이벤트 루프 타이머로 비차단 방식의 지연 작업을 스케줄링할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 실행 일시정지(초):
sleep(2);
// 마이크로초 일시정지:
usleep(500000); // 0.5 초
// 작업 지연(ReactPHP):
use React\EventLoop\Factory;
$loop = Factory::create();
$loop->addTimer(1.0, function () {
echo "1 秒后执行\n";
});
$loop->run();
// Swoole 타이머:
// Swoole\Timer::tick(1000, fn() => ...)
// CLI 장기 작업은 sleep 차단 주의
// 고동시성에는 이벤트 루프 타이머 사용

17.프로세스와 CLI

명령줄 인수, 입출력, 환경 변수 및 프로세스 제어.

명령행 인자

$argv 배열은 명령줄 인수를 저장하며, $argv[0]은 스크립트 이름입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// $ php app.php arg1 arg2
// $argv[0] = 'app.php'
// $argv[1] = 'arg1'
// $argv[2] = 'arg2'
// 인자 개수:
echo count($argv); // 3
// 스크립트명 제외 순회:
foreach (array_slice($argv, 1) as $arg) {
echo $arg;
}
// 옵션 파싱:
// getopt('a:b::') 다음 절 참조
// stdin 입력 읽기:
// fgets(STDIN)

입력 읽기

fgets(STDIN)은 한 줄을 읽고, stream_get_contents는 전체를 읽으며, trim으로 줄바꿈을 제거합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 대화형 입력:
echo '请输入姓名:';
$name = trim(fgets(STDIN));
// 여러 줄 일괄 읽기:
while (($line = fgets(STDIN)) !== false) {
echo strtoupper($line);
}
// 전체 입력 읽기:
$all = stream_get_contents(STDIN);
// 파이프 입력 여부 판정:
// stream_isatty(STDIN)
// 입력이 비어 있을 수 있음:
// 대화 전 posix_isatty 확인

외부 명령 실행

shell_exec은 출력을 가져오고, exec은 상태 코드를 가져오며, 인수 전달 시 escapeshellarg로 인젝션을 방지합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 외부 명령 실행 후 출력 반환:
$out = shell_exec('ls -la');
echo $out;
// 상태 코드 가져오기:
exec('git status', $lines, $code);
// $code 0 은 성공을 의미
// 배열은 각 줄로 출력:
// $lines 는 줄마다 1 요소
// 사용자 입력을 명령에 연결하는 것 금지:
// $cmd = 'rm ' . $_GET['f']; // 위험
// 인자 전달 시 escapeshellarg 사용:
// exec('cat ' . escapeshellarg($file))

환경 변수

getenv는 환경 변수를 읽고, putenv는 설정하며, 민감한 설정은 환경 변수로 처리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 환경 변수 읽기:
echo getenv('HOME');
// 환경 변수 설정:
putenv('APP_ENV=production');
echo getenv('APP_ENV');
// 전체 읽기:
print_r(getenv());
// $_ENV 슈퍼글로벌:
echo $_ENV['PATH'] ?? '';
// .env 파일용 대표 라이브러리:
// vlucas/phpdotenv
// 민감 정보는 환경 변수로
// 코드베이스에 하드코딩 금지

프로세스 정보

getmypid는 현재 PID를 가져오고, posix_* 계열로 사용자와 부모 프로세스를 조회하며, hrtime으로 시간을 측정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 현재 프로세스 PID:
$pid = getmypid();
// 부모 프로세스 PID:
echo posix_getppid();
// 현재 사용자:
echo posix_getuid();
echo posix_getpwuid(posix_getuid())['name'];
// 실행 시간:
// hrtime() 으로 마이크로초 측정:
$start = hrtime(true);
work();
$ms = (hrtime(true) - $start) / 1e6;
echo "$ms ms";
// CLI 프로세스 정보:
// 명령어 이름 / proc/self/cmdline

프로세스 시그널

pcntl_signal은 시그널 처리를 제어하고, pcntl_wait는 자식 프로세스 종료를 기다립니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 시그널 무시:
pcntl_signal(SIGTERM, SIG_IGN);
// 기본 처리:
pcntl_signal(SIGTERM, SIG_DFL);
// 자식 프로세스 대기(비차단):
$pid = pcntl_fork();
if ($pid === 0) { exit(0); }
// 차단 대기:
pcntl_wait($status);
// 종료 코드 확인:
echo pcntl_wexitstatus($status);
// 프로세스에 시그널 보내기:
// posix_kill($pid, SIGTERM)
// 시그널 처리 안에서 복잡한 작업 피하기

파이프와 터미널

stream_isatty로 인터랙티브 터미널 여부를 판단하고, stderr로 진행률을 출력하며, exit으로 종료 코드를 제어합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 파이프에서 stdin 읽기:
// echo "hi" | php app.php
// 출력 스트림 리다이렉트:
// php app.php > out.txt 2>&1
// 대화형 터미널 여부 판정:
if (stream_isatty(STDIN)) {
echo "交互模式\n";
} else {
echo "管道输入\n";
}
// 진행률은 stderr 로 출력:
fwrite(STDERR, "处理中...\n");
// 백그라운드 실행:
// $ php app.php &
// 종료 코드 제어:
exit(0); // 성공
exit(1); // 실패

종료 코드

exit()는 종료 코드를 전달하며, 0이 성공이고 0이 아니면 실패이며, 셸에서는 $?로 읽습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 정상 종료:
exit(0);
// 업무 오류:
if (!$data) {
exit(1); // 일반 오류
}
// 커스텀 코드:
exit(2); // 사용법 오류
exit(3); // 입력이 유효하지 않음
// 스크립트 내 조기 종료:
return; // 또는 exit
// shell 에서 확인:
// php app.php
// echo $? // 이전 명령의 종료 코드
// 0 은 성공,0 이 아닌 것은 실패
// 128+ 는 시그널로 종료됨을 의미

18.정규 표현식

일치, 캡처, 치환, 분할 및 멀티바이트 모드.

preg_match

preg_match는 첫 번째 결과를 매칭하고, 캡처 그룹은 $m에 저장되며, 명명된 그룹은 이름으로 가져옵니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 첫 번째 결과 매치:
$s = '订单号 A123 和 B456';
if (preg_match('/[A-Z]\d{3}/', $s, $m)) {
echo $m[0]; // A123
}
// 캡처 그룹:
preg_match('/([A-Z])(\d{3})/', $s, $m);
echo $m[1]; // A
echo $m[2]; // 123
// 명명된 그룹:
preg_match('/(?<type>[A-Z])(?<num>\d{3})/', $s, $m);
echo $m['type']; // A
// 정규식 오류는 false 반환

preg_match_all

모든 결과를 매칭하며, 기본적으로 그룹별로 정렬되고, PREG_SET_ORDER는 매치별로 정렬됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$s = '订单号 A123 和 B456';
preg_match_all('/[A-Z]\d{3}/', $s, $m);
print_r($m[0]);
// ['A123', 'B456']
// 그룹 구조:
preg_match_all('/([A-Z])(\d{3})/', $s, $m);
// $m[0] 전체 매치
// $m[1] 1번째 그룹
// $m[2] 2번째 그룹
// 키-값 캡처:
preg_match_all(
'/(\w+)=(\d+)/',
'a=1&b=2',
$m,
PREG_SET_ORDER,
);
// $m[0] = ['a=1', 'a', '1']
// 두 번째 인자가 반환 구조 결정

preg_replace

정규 표현식으로 치환하며, $1로 캡처 그룹을 역참조하고, preg_replace_callback은 콜백으로 처리합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 매치 치환:
$s = '电话 138-1234-5678';
$r = preg_replace('/\d{3}-\d{4}-\d{4}/', '[隐藏]', $s);
// '电话 [隐藏]'
// 캡처 그룹으로 재구성:
preg_replace(
'/(\d{4})(\d{4})/',
'$1-$2',
'13812345678',
);
// '1381-2345-678'(예시)
// 역참조:
preg_replace('/(\w+)\s+\1/', '$1', 'hi hi there');
// 'hi there'
// 배열로 일괄 치환:
preg_replace(['/a/', '/b/'], ['A', 'B'], 'abc');
// 콜백 치환:
preg_replace_callback('/\d+/', fn($m) => $m[0] * 2, '1 2');

자주 쓰는 패턴

문자 클래스, 수량자와 탐욕적 매칭, 정규 표현식 구분 기호와 이스케이프 규칙.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 문자 클래스:
\d // 숫자 [0-9]
\w // 단어 문자 [A-Za-z0-9_]
\s // 공백
// 수량자:
* // 0회 이상
+ // 1회 이상
? // 0회 또는 1회
{n,m} // n회에서 m회
// 탐욕적 vs 게으름:
'<b>x</b>'
/<.*>/ // 탐욕적:<b>x</b>
/<.*?>/ // 게으름:<b>
// 이스케이프:
\. \( \) \\ // \\ 접두사 필요
// 구분자:
// 첫 글자가 구분자,끝에 수식자 추가
// '/' 안의 이스케이프는 '\/' 로 작성

캡처와 어서션

캡처 그룹, 비캡처 그룹, 명명된 그룹 그리고 전후방 어서션.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 캡처 그룹:
/(\d{4})-(\d{2})/
// 2026-08 에 매치
// 비캡처 그룹:
/(?:ab)+/
// 그룹화만,성능 더 좋음
// 명명된 그룹:
/(?P<year>\d{4})-(?P<month>\d{2})/
// $m['year']
// 역참조:
/(\w+)\s+\1/
// \1 은 1번째 그룹 내용 참조
// 어서션:
/(?=\d{5}$)/ // 전방 탐색
/(?<=^\d{3})/ // 후방 탐색
// 그룹 내 교체:
/gr(a|e)y/

앵커와 경계

^는 행의 시작, $는 행의 끝, \b는 단어 경계이며, m 수정자로 여러 줄 매칭을 지원합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 줄 시작/끝:
/^php/ // php 로 시작
/php$/ // php 로 끝
// 단어 경계:
/\bcat\b/ // 독립 단어 cat 에 매치
// category、concatenate 에는 매치 안 됨
// 여러 줄 모드:
// 수식자 m 으로 ^ $ 가 각 줄에 매치
preg_match_all(
'/^\d+/m',
"12\nabc\n34",
$m,
);
// ['12', '34']
// 완전 일치:
// /^...$/ 에 \z 를 더해 끝 줄바꿈 방지
// 단어 경계는 중국어에 적용 불가

정규 표현식 분할

preg_split은 정규 표현식으로 문자열을 분할하며, 구분 기호를 유지하거나 개수를 제한할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 정규식으로 분할:
$parts = preg_split('/[,;]/', 'a,b;c');
// ['a', 'b', 'c']
// 구분자 유지:
preg_split('/(,)/', 'a,b', -1, PREG_SPLIT_DELIM_CAPTURE);
// ['a', ',', 'b']
// 개수 제한:
preg_split('/\s+/', 'a b c d', 2);
// ['a', 'b c d']
// 공백으로 분할:
preg_split('/\s+/', ' a b ');
// ['', 'a', 'b'](앞뒤 공백)
// 빈 요소 제거는 array_filter 와 조합
// 단순 구분은 explode 로 충분

멀티바이트와 u 수정자

중국어를 처리할 때 u 수정자를 추가하고, preg_quote로 사용자 입력을 이스케이프합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 중국어 처리에는 u 수식자:
preg_match('/\p{Han}+/u', '你好世界abc', $m);
echo $m[0]; // 你好世界
// 멀티바이트 문자 클래스:
/\p{Han}/ // 한자
/\p{L}/ // 임의의 문자
/\p{N}/ // 숫자
// 이스케이프(위험):
// 사용자 입력을 정규식으로 만들기 전에 이스케이프:
$lit = preg_quote($_GET['q'], '/');
// 이스케이프:
preg_match(
'/' . $lit . '/u',
'a+b',
$m,
);
// u 수식자로 깨짐 방지

19.빌드와 도구 체인

Composer 의존성 관리, 오토로딩 및 CLI 빌드 흐름.

프로젝트 초기화

composer init은 composer.json을 생성하며, 의존성과 오토로딩 규칙을 선언합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 프로젝트 초기화:
// $ composer init
// composer.json 생성:
// {
// "name": "vendor/demo",
// "type": "project",
// "require": { "php": "^8.1" },
// "autoload": { "psr-4": { "App\\": "src/" } }
// }
// 대화형 마법사로 생성
// 이후 composer install 실행
// PHP 버전 제약은 require 에 기록
// ^8.1 은 8.1.x 이상을 뜻함

의존성 설치

composer install은 lock에 따라 설치하고, require로 새 의존성을 추가합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 의존성 설치:
// $ composer install
// 프로덕션 의존성만 설치:
// $ composer install --no-dev
// 단일 패키지 설치:
// $ composer require monolog/monolog
// 제거:
// $ composer remove monolog/monolog
// 최신 제약 버전으로 업데이트:
// $ composer update
// 의존성 문제 확인:
// $ composer validate
// $ composer audit
// 프로덕션 환경에서는 --no-dev 추가
// 공격 표면과 용량 감소
// composer.lock 은 반드시 커밋
// 정확한 버전을 잠가 재현 가능

의존성 가져오기

require vendor/autoload.php 후에는 use로 의존성 클래스를 사용할 수 있으며, 오토로딩이 적용됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 프로젝트에서 의존성 사용:
require __DIR__ . '/vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
$log = new Logger('app');
$log->pushHandler(
new StreamHandler('php://stderr'),
);
$log->info('启动完成');
// 오토로더는 install 이 생성
// autoload 설정을 바꿀 때마다 dump:
// $ composer dump-autoload
// 클래스명-파일 매핑:
// PSR-4 네임스페이스는 디렉터리 대응

PSR-4 오토로딩

네임스페이스와 디렉터리가 일대일로 대응되며, App\은 src/에 매핑되고 dump-autoload가 필요합니다.

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
// composer.json:
// {
// "autoload": {
// "psr-4": {
// "App\\": "src/"
// }
// }
// }
// src/User.php:
// namespace App;
// class User {}
// 자동 로드 후:
use App\User;
$u = new User();
// 네임스페이스 경로 일대일 대응:
// App\\Controller\\Home
// → src/Controller/Home.php
// 설정 변경 후 재생성:
// $ composer dump-autoload
// PSR-4 는 클래스 접두사만 매핑
// 기타 네임스페이스는 영향 없음

Composer 스크립트

composer.json의 scripts는 명령 별칭을 정의하며, 설치 훅 콜백을 지원합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// composer.json 스크립트:
// {
// "scripts": {
// "test": "phpunit",
// "lint": "php -l src/*.php"
// }
// }
// 실행:
// $ composer test
// $ composer lint
// 내장 스크립트 훅:
// pre-install-cmd、post-install-cmd
// 라이프사이클 콜백:
// 설치 전후에 정리/권한 설정 실행
// 커스텀 명령은 scripts 에 정리
// 긴 명령을 수기로 입력하지 않기
// 크로스 플랫폼 경로 주의
// 백슬래시 대신 / 사용

버전 제약

^, ~, >= 같은 제약 문법으로 의존성 버전 범위를 제어하고, lock으로 정확한 버전을 고정합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 버전 제약 구문:
// ^8.1 8.1.x 허용(>=8.1 <9)
// ~8.1 8.1.x 허용(<8.2)
// >=8.1 임의의 >=8.1
// * 임의 버전
// 1.2.3 정확한 버전
// 브랜치 참조:
// dev-master
// v2.0.0-beta
// 정확한 버전 잠금:
// composer.lock 은 실제 버전을 기록
// 지정 버전 설치:
// $ composer require foo/bar:^2.0
// 업그레이드 가능 확인:
// $ composer outdated
// 프로덕션은 잠긴 버전으로 배포

CLI 릴리스 명령

프레임워크 명령(artisan / console)은 마이그레이션, 캐시 및 예약 작업을 실행합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 명령 도움말 보기:
// $ php bin/console list
// 커스텀 명령 실행:
// $ php artisan migrate
// $ php bin/console cache:clear
// 앱 키 생성:
// $ php artisan key:generate
// 데이터베이스 마이그레이션:
// $ php artisan migrate --seed
// 정기 작업:
// $ crontab -e
// * * * * * php /path/app/cron.php
// 프로덕션 명령에는 --env=production 추가
// CI 와 연동해 자동 배포
// 자주 쓰는 프레임워크 명령 접두사:
// Symfony bin/console
// Laravel artisan

PHP 설정

php -i / php --ini / php -m으로 런타임을 확인하고, ini_get으로 설정을 조회합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 설정 확인:
// $ php -i 전체 설정
// $ php --ini 로드된 ini 경로
// $ php -m 로드된 확장
// 확장 확인:
// $ php -m | grep curl
// 실행 시 조회:
echo ini_get('memory_limit');
echo ini_get('upload_max_filesize');
// 실행 시 변경:
ini_set('memory_limit', '512M');
// CLI 인자로 설정 지정:
// $ php -d error_reporting=E_ALL app.php
// 프로덕션 설정 확인:
// $ php -v
// $ php --ri opcache

공식 링크

공식 문서와 리소스에 대한 직접 링크.

이 치트시트에 대해

이 페이지는 PHP 8.3의 자급식 치트시트 매뉴얼로, 실제 프로젝트에서 언어 핵심과 가장 자주 쓰이는 표준 라이브러리 사용 사례의 약 80%를 다룹니다. 내용은 현대적 관행에 중점을 둡니다: declare(strict_types=1) 엄격 모드, enum 열거형, readonly 읽기 전용 속성, match 표현식, 명명된 인수, 화살표 함수와 first-class callable. 권위 있는 참고 자료는 공식 PHP 매뉴얼과 PHP The Right Way를 참조하세요. 19개 장은 각각 한 가지 주제에 집중합니다 — 첫 번째 프로그램부터 배열, 객체 지향과 흔한 실수까지. 각 장은 8–14개의 예제가 있는 소절로 나뉘며(각 5–20줄), 총 약 150개의 주제를 다룹니다. 코드 스니펫은 의도적으로 짧고 자기 설명적입니다. 모든 처리는 브라우저에서 완료됩니다 — 업로드 없음, 추적 없음. 이 페이지는 GuruToolkit 무료 개발자 도구 모음의 일부입니다; 코드 스니펫은 어떠한 보증 없이 자유롭게 사용할 수 있습니다.

버전 2.1.0