PHP 엔지니어링: 핵심 개념 심층 탐구
Source: Dev.to
PHP에서 변수 스코프 (변수가 어디에 존재하고 사라지는가)
스코프는 변수가 정의되고 접근 가능한 컨텍스트를 정의합니다.
로컬 스코프: 함수의 개인 공간
함수(또는 메서드) 내부에 선언된 변수는 로컬 스코프를 가집니다. 함수가 시작될 때 생성되고 함수 실행이 끝나면 파괴됩니다.
function calculateArea($width, $height) {
// $area, $width, $height 모두 로컬 변수입니다.
$area = $width * $height;
echo "Area is: " . $area;
}
// echo $area; // Fatal Error: Undefined variable $area
비유: 보안이 된 회의실 안의 개인 노트북 — 내용은 그 회의에만 관련됩니다.
글로벌 스코프: 공개 선언
함수나 클래스 밖에서 선언된 변수는 글로벌 스코프에 속합니다. 함수는 기본적으로 글로벌 변수를 직접 접근할 수 없습니다.
$site_name = "MyApp"; // 글로벌 변수
function showSite() {
// 글로벌 변수를 함수의 로컬 스코프로 명시적으로 가져옵니다
global $site_name;
echo "Welcome to " . $site_name;
}
showSite(); // Output: Welcome to MyApp
중요: 변수를 함수 인자로 전달하는 것이
global을 사용하는 것보다 보통 더 바람직합니다.
비유: 사무실 로비에 있는 공지판 — 모두가 볼 수 있지만, PHP에게 함수 밖을 보라고 명시적으로 알려줘야 합니다.
정적 스코프: 지속되는 함수 메모리
함수 내부에 static으로 선언된 변수는 해당 함수에 로컬이지만 호출 사이에 유지됩니다.
function counter() {
static $count = 0; // 한 번만 초기화
$count++;
return $count;
}
echo counter(); // 1
echo counter(); // 2
echo counter(); // 3
실용 예시: 간단한 ID 생성기
정적 변수를 사용하면 전역 카운터 없이도 고유하고 순차적인 ID를 만들 수 있습니다.
function generate_id() {
static $current_id = 0; // 한 번만 초기화
$current_id++;
return $current_id;
}
echo "User ID: " . generate_id() . "\n"; // User ID: 1
echo "Order ID: " . generate_id() . "\n"; // Order ID: 2
실용 예시: 싱글톤 패턴 (데이터베이스 연결)
싱글톤 패턴은 정적 프로퍼티를 이용해 클래스 인스턴스가 하나만 존재하도록 보장합니다.
class Database
{
private static $instance = null;
public static function getInstance(): Database
{
if (self::$instance === null) {
// 연결을 **한 번만** 생성
self::$instance = new self();
}
return self::$instance;
}
// private 생성자는 직접 객체 생성을 방지합니다
private function __construct() {}
}
$db1 = Database::getInstance();
$db2 = Database::getInstance(); // $db1과 $db2는 같은 객체
왜 중요한가: 정적 스코프는 전역 변수나 클래스 프로퍼티에 의존하지 않고도 호출 횟수나 ID 생성기와 같은 상태를 추적하는 데 필수적입니다.
비유: 책상 위에 남겨진 포스트잇 — 떠났다가 돌아와도 그대로 남아 있습니다.
레퍼런스(&) — 원본 데이터 직접 작업하기
PHP에서는 대부분의 변수들이 값에 의한 전달(복사)됩니다. 레퍼런스를 사용하면 같은 변수 내용을 가리키는 별칭을 만들 수 있어 참조에 의한 전달이 가능합니다.
일반 복사 (값에 의한 전달)
$a = 10;
$b = $a; // $b는 10의 복사본을 가짐
$b = 20; // 변경되는 것은 $b만
echo $a; // Output: 10
레퍼런스 버전 (공유 메모리)
$a = 10;
$b =& $a; // $b는 이제 $a의 별칭
$b = 20; // $b를 바꾸면 $a도 바뀜
echo $a; // Output: 20
비유: 같은 TV를 제어하는 두 개의 리모컨. 어느 리모컨을 눌러도 하나의 TV에 영향을 줍니다.
함수에서 레퍼런스 (명시적 값에 의한 전달)
function addOne(&$num) {
$num++;
}
$x = 5;
addOne($x);
echo $x; // Output: 6
foreach에서 레퍼런스 (위험 구역)
foreach ($arr as &$v)를 사용하면 $v가 배열 각 요소의 별칭이 됩니다. 루프가 끝난 뒤 반드시 레퍼런스를 해제해야 합니다.
$arr = [1, 2, 3];
foreach ($arr as &$v) {
$v *= 2;
}
unset($v); // 레퍼런스 해제
클로저 — 기억하는 함수
클로저는 익명 함수이며 변수에 저장하거나 인자로 전달할 수 있고, 생성된 스코프의 변수를 접근할 수 있습니다.
$greet = function ($name) {
return "Hello " . $name;
};
echo $greet("Mamu"); // Hello Mamu
비유: 프로그래밍된 동작을 담은 휴대용 마이크로칩 — 어디서든 꺼내어 실행할 수 있습니다.
use — 클로저 안에서 변수 캡처하기
익명 함수는 기본적으로 외부 스코프 변수를 접근할 수 없습니다. use 키워드를 사용해 해당 변수를 클로저 안으로 가져옵니다.
값에 의한 캡처
$name = "Mamu";
$fn = function () use ($name) {
return $name;
};
$name = "Ali"; // 캡처 후 변경
echo $fn(); // Output: Mamu
레퍼런스에 의한 캡처
$count = 0;
$fn = function () use (&$count) {
$count++;
};
$fn();
$fn();
echo $count; // Output: 2
비유: 클로저가 원본 변수에 연결된 살아있는 전선을 잡고 있는 것과 같습니다. 스냅샷이 아니라 실시간으로 연결됩니다.
렉시컬 스코핑 (작성 위치 규칙)
렉시컬 스코핑은 클로저가 작성된 위치의 변수를 접근한다는 의미이며, 실행되는 위치의 변수를 보지는 않습니다.
$message = "Outside"; // 스코프 A
$fn = function () use ($message) {
echo $message; // 스코프 A에서 캡처됨
};
function run($cb) {
$message = "Inside"; // 스코프 B
$cb();
}
run($fn); // Prints "Outside"
기억할 규칙: 클로저는 호출 지점이 아니라 생성된 스코프(렉시컬)에서 변수를 사용합니다.
실전 예시: 동적 배열 필터 만들기
function makeFilter($min, $max) {
return function ($value) use ($min, $max) {
return $value >= $min && $value 12 [2] => 17 )
위 예시에서 클로저는 $min과 $max를 값으로 캡처하여 array_filter가 전역 변수 없이도 사용자 정의 범위 검사를 수행하도록 합니다.