왜 코드를 나누어야 할까
작은 예제에서는 index.php 한 파일에 클래스와 함수를 모두 넣어도 실행된다. 그러나 게시판, 로그인, 관리자 페이지, API가 함께 있는 프로젝트라면 한 파일 방식은 금방 한계에 부딪힌다. 어디에 어떤 코드가 있는지 찾기 어렵고, 같은 이름의 클래스가 생기면 충돌한다. 파일마다 require_once를 계속 추가하는 일도 번거롭다.
실무 PHP 프로젝트는 보통 코드를 역할별 파일과 폴더로 나눈다. 그리고 네임스페이스로 이름 충돌을 막고, Composer로 클래스 자동 로딩과 외부 라이브러리를 관리한다.
쉽게 말해 네임스페이스는 "클래스 이름 앞에 붙이는 주소"이고, Composer는 "내 코드와 외부 패키지를 일관된 규칙으로 불러오게 도와주는 도구"다.
AI 생성 이미지
네임스페이스로 이름 충돌 막기
다음 클래스의 이름은 User다.
<?php
class User
{
public function name(): string
{
return '일반 사용자';
}
}처음에는 문제가 없어 보인다. 하지만 관리자 사용자도 표현해야 해서 또 다른 User 클래스를 만들면 같은 이름을 두 번 선언하게 된다. PHP는 어떤 User를 말하는지 알 수 없으므로 오류가 난다.
이때 네임스페이스를 사용한다.
<?php
namespace AppMember;
class User
{
public function name(): string
{
return '일반 사용자';
}
}<?php
namespace AppAdmin;
class User
{
public function name(): string
{
return '관리자';
}
}이제 두 클래스의 실제 이름은 각각 다음과 같다.
AppMemberUser
AppAdminUser클래스 이름은 둘 다 User지만, 전체 주소가 다르므로 충돌하지 않는다. 이것을 완전한 클래스 이름이라고 생각하면 된다.
사용할 때는 전체 이름을 그대로 써도 된다.
<?php
$member = new AppMemberUser();
$admin = new AppAdminUser();하지만 코드가 길어진다. 그래서 보통 use로 가져와 짧게 쓴다.
<?php
use AppMemberUser;
$user = new User();
echo $user->name();같은 파일에서 이름이 같은 두 클래스를 함께 써야 한다면 별칭을 붙인다.
<?php
use AppMemberUser as MemberUser;
use AppAdminUser as AdminUser;
$member = new MemberUser();
$admin = new AdminUser();namespace는 파일의 맨 위쪽에 둔다. 보통 <?php 바로 다음에 쓰지만, declare(strict_types=1);를 사용할 때는 declare 다음에 namespace를 둔다. use는 네임스페이스 선언 아래에 두는 것이 일반적이다.
PSR-4: 폴더와 클래스 이름을 맞추는 규칙
네임스페이스만 정해도 이름 충돌은 줄어든다. 하지만 PHP가 클래스를 자동으로 찾으려면 "이 클래스는 어느 파일에 있는가?"라는 규칙이 필요하다. 이때 널리 쓰이는 표준이 PSR-4 오토로딩이다.
PSR-4의 핵심은 간단하다. 네임스페이스 앞부분을 특정 폴더에 연결하고, 나머지 네임스페이스는 하위 폴더와 파일명으로 바꾼다. 예를 들어 App을 src/ 폴더에 연결했다면 다음처럼 대응된다.1
AppModelsPost -> src/Models/Post.php
AppServicesMailer -> src/Services/Mailer.php
AppHttpController -> src/Http/Controller.php중요한 점은 대소문자까지 맞아야 한다는 것이다. 개발 중인 컴퓨터에서는 우연히 동작해도 Linux 서버에서는 Post.php와 post.php를 다른 파일로 볼 수 있다. 클래스가 Post라면 파일명도 Post.php로 맞추는 습관을 들이자.
다음과 같은 구조를 만들 수 있다.
my-app/
├── composer.json
├── public/
│ └── index.php
└── src/
└── Models/
└── Post.phpsrc/Models/Post.php 파일은 다음처럼 작성한다.
<?php
declare(strict_types=1);
namespace AppModels;
class Post
{
public function __construct(
private string $title,
private string $body
) {
}
public function title(): string
{
return $this->title;
}
public function summary(): string
{
return mb_substr($this->body, 0, 50);
}
}여기서 AppModelsPost는 src/Models/Post.php와 연결된다. App은 src/에 해당하고, ModelsPost는 Models/Post.php가 된다.
Composer 설치와 composer.json 작성
Composer는 PHP의 대표적인 의존성 관리 도구다. 의존성이란 내 프로젝트가 실행되기 위해 필요한 외부 코드나 라이브러리를 뜻한다. Composer는 외부 패키지를 설치할 뿐 아니라, 우리가 만든 클래스도 자동으로 불러올 수 있게 오토로더를 만들어 준다.
설치 후 터미널에서 다음 명령이 동작하는지 확인한다.
composer --version프로젝트 루트에 composer.json을 만든다.
{
"name": "study/php-textbook-app",
"description": "A small PHP project for learning namespaces and Composer",
"type": "project",
"require": {
"php": "^8.2"
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}autoload.psr-4는 App 네임스페이스를 src/ 폴더에 연결한다는 뜻이다. Composer의 composer.json은 PSR-4 오토로딩과 require, require-dev 같은 의존성 구분을 공식적으로 지원한다.2
설정을 만든 뒤 다음 명령을 실행한다.
composer dump-autoload그러면 vendor/ 폴더 안에 vendor/autoload.php 파일이 생성된다. 이 파일이 Composer 오토로더다. 이제 클래스 파일을 직접 require_once로 하나씩 불러오지 않아도 된다. 애플리케이션의 시작 지점에서 Composer 오토로더만 한 번 불러오면 된다.
public/index.php를 작성해 보자.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use AppModelsPost;
$post = new Post(
'네임스페이스 배우기',
'프로젝트가 커질수록 클래스 이름과 파일 구조를 일관되게 관리해야 합니다.'
);
?>
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>Composer 예제</title>
</head>
<body>
<h1><?= htmlspecialchars($post->title(), ENT_QUOTES, 'UTF-8') ?></h1>
<p><?= htmlspecialchars($post->summary(), ENT_QUOTES, 'UTF-8') ?></p>
</body>
</html>실행 흐름은 다음과 같다.
index.php가vendor/autoload.php를 불러온다.코드에서
AppModelsPost클래스를 사용한다.Composer가
App은src/에 있다고 판단한다.src/Models/Post.php파일을 찾아 자동으로 로드한다.
이제 새 클래스를 추가할 때마다 파일마다 require_once를 추가할 필요가 없다. 단, composer.json의 오토로드 설정을 바꾸었다면 composer dump-autoload를 다시 실행해야 한다. 클래스 파일만 PSR-4 규칙에 맞게 추가한 경우에는 보통 다시 실행하지 않아도 된다.
외부 패키지 설치하기
Composer의 또 다른 역할은 외부 라이브러리 설치다. 예를 들어 날짜와 시간을 편하게 다루는 nesbot/carbon 패키지를 설치해 보자.
composer require nesbot/carbon명령을 실행하면 composer.json의 require 영역에 패키지가 추가되고, 실제 코드는 vendor/ 폴더에 설치된다. 함께 생성되는 composer.lock 파일은 "지금 설치된 정확한 패키지 버전"을 기록한다. 팀 프로젝트에서는 composer.json뿐 아니라 composer.lock도 함께 관리해야 같은 버전으로 설치할 수 있다.
패키지는 우리 코드에서 use로 가져와 사용할 수 있다.
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use CarbonCarbon;
$now = Carbon::now('Asia/Seoul');
echo $now->format('Y-m-d H:i');외부 패키지를 쓴다고 해서 보안과 검증이 자동으로 해결되는 것은 아니다. 패키지는 문제를 빠르게 풀어 주는 도구다. 입력값 검증, 안전한 출력, prepared statement 같은 기본 원칙은 계속 지켜야 한다.
운영 의존성과 개발 의존성
Composer에는 의존성을 나누는 두 영역이 있다.
{
"require": {
"php": "^8.2",
"nesbot/carbon": "^3.0"
},
"require-dev": {
"phpunit/phpunit": "^11.0"
}
}require는 실제 서비스 실행에 필요한 패키지를 넣는다. 날짜 처리, 라우터, 데이터베이스 관련 라이브러리처럼 운영 서버에서도 필요한 코드다.
require-dev는 개발할 때만 필요한 패키지를 넣는다. 테스트 도구, 코드 스타일 검사기, 정적 분석 도구가 여기에 해당한다. 예를 들어 PHPUnit은 테스트를 실행할 때 필요하지만 사용자가 접속하는 운영 화면에는 필요하지 않다.
개발 의존성을 설치하려면 다음처럼 쓴다.
composer require --dev phpunit/phpunit운영 서버에서는 보통 다음처럼 개발용 패키지를 제외하고 설치한다.
composer install --no-dev --optimize-autoloader이렇게 하면 배포 파일이 줄어들고, 운영에 필요 없는 도구가 서버에 올라가지 않는다. 프로젝트가 커질수록 이 구분은 중요해진다. 반대로 실제 서비스 코드에서 사용하는 패키지는 운영 서버에서도 설치되어야 하므로 require에 두어야 한다.
실습 과제
src/Models/User.php파일을 만들고AppModelsUser클래스를 작성해 보자.User클래스에name()메서드를 만들고 이름을 반환하게 하자.public/index.php에서use AppModelsUser;를 선언한 뒤 객체를 생성해 이름을 출력하자.src/Services/UserFormatter.php를 만들고AppServicesUserFormatter클래스를 작성해 보자.UserFormatter가User객체를 받아 화면에 출력할 문자열을 만들게 해 보자.
출력할 때는 이전 장들에서 반복한 것처럼 htmlspecialchars()를 사용한다.
자주 발생하는 오류 점검
Class not found 오류
가장 흔한 원인은 네임스페이스, 폴더, 파일명 중 하나가 맞지 않는 경우다.
AppModelsPost -> src/Models/Post.php이 규칙이 정확히 맞는지 확인하자. 파일명 대소문자도 확인한다.
vendor/autoload.php를 불러오지 않음
Composer 오토로더를 불러오기 전에 클래스를 사용하면 PHP는 클래스를 찾지 못한다. 시작 파일에 다음 코드가 있는지 확인한다.
require_once __DIR__ . '/../vendor/autoload.php';composer.json을 수정하고 dump-autoload를 실행하지 않음
클래스 파일만 추가한 경우에는 대개 바로 동작한다. 그러나 autoload 설정 자체를 바꾸었다면 다음 명령을 다시 실행한다.
composer dump-autoloadvendor 폴더가 없음
프로젝트를 새로 내려받았는데 vendor/가 없다면 다음 명령으로 의존성을 설치한다.
composer install일반적으로 vendor/ 폴더는 Git에 올리지 않는다. 대신 composer.json과 composer.lock을 올리고, 필요한 사람이 composer install로 다시 설치한다.
개발 의존성을 운영 코드에서 사용함
require-dev에 넣은 패키지는 운영 서버에서 설치되지 않을 수 있다. 운영 코드가 개발용 패키지에 의존하면 배포 후 오류가 난다. 실제 서비스 실행에 필요한 패키지는 require에 두고, 테스트나 검사 도구만 require-dev에 둔다.
핵심 정리
네임스페이스는 클래스 이름에 주소를 붙여 충돌을 막는다. PSR-4는 그 주소를 실제 폴더와 파일에 연결하는 규칙이다. Composer는 이 규칙을 바탕으로 우리 클래스와 외부 패키지를 자동으로 불러온다.
앞으로 더 큰 PHP 애플리케이션을 만들 때는 vendor/autoload.php를 시작점에서 한 번 불러오고, src/ 아래에 역할별 클래스를 배치하는 구조를 기본으로 삼자. 이 습관이 잡히면 MVC 구조, 테스트, 배포 단계로 넘어갈 준비가 된다.
참고
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.