PHP 언어 완벽 가이드 - 객체지향 프로그래밍
PHP Object-Oriented Programming
PHP 객체지향 프로그래밍
PHP는 PHP 5부터 본격적으로 객체지향 프로그래밍(OOP)을 지원하기 시작했으며, 이후 버전에서 계속해서 관련 기능이 개선되었습니다.
클래스와 객체
클래스 정의 및 객체 생성
<?php
// 클래스 정의
class User {
// 속성(프로퍼티)
public $name;
public $email;
private $password;
// 생성자
public function __construct($name, $email, $password) {
$this->name = $name;
$this->email = $email;
$this->password = $password;
}
// 메소드
public function login() {
return "사용자 {$this->email}이(가) 로그인했습니다.";
}
public function getInfo() {
return "이름: {$this->name}, 이메일: {$this->email}";
}
// 비밀번호 변경 메소드 (private 속성 접근)
public function changePassword($newPassword) {
$this->password = $newPassword;
return "비밀번호가 변경되었습니다.";
}
}
// 객체 생성
$user1 = new User("홍길동", "hong@example.com", "secret123");
// 객체 메소드 호출
echo $user1->login(); // 사용자 hong@example.com이(가) 로그인했습니다.
echo $user1->getInfo(); // 이름: 홍길동, 이메일: hong@example.com
// 공개 속성 접근
echo $user1->name; // 홍길동
$user1->name = "홍길순";
echo $user1->name; // 홍길순
// 비공개 속성 접근 시도
// echo $user1->password; // 오류: 비공개 속성에 접근할 수 없음
?>
접근 제어자
<?php
class Product {
public $name; // 어디서든 접근 가능
protected $price; // 이 클래스와 자식 클래스에서만 접근 가능
private $discount; // 이 클래스 내에서만 접근 가능
public function __construct($name, $price, $discount = 0) {
$this->name = $name;
$this->price = $price;
$this->discount = $discount;
}
public function getFinalPrice() {
return $this->price * (1 - $this->discount / 100);
}
protected function calculateTax($rate = 10) {
return $this->getFinalPrice() * ($rate / 100);
}
private function applySpecialDiscount() {
$this->discount += 5;
}
public function applyHolidayDiscount() {
$this->applySpecialDiscount(); // 자기 자신의 private 메소드 호출 가능
return "할인이 적용되었습니다.";
}
}
$product = new Product("스마트폰", 1000000, 10);
echo $product->name; // 스마트폰
echo $product->getFinalPrice(); // 900000
// 오류: protected 속성에 접근할 수 없음
// echo $product->price;
// 오류: protected 메소드에 접근할 수 없음
// $product->calculateTax();
// 오류: private 메소드에 접근할 수 없음
// $product->applySpecialDiscount();
// 공개 메소드를 통해 비공개 메소드 간접 호출
echo $product->applyHolidayDiscount(); // 할인이 적용되었습니다.
?>
상속
<?php
// 부모 클래스
class Vehicle {
protected $brand;
protected $color;
public function __construct($brand, $color) {
$this->brand = $brand;
$this->color = $color;
}
public function getInfo() {
return "브랜드: {$this->brand}, 색상: {$this->color}";
}
protected function startEngine() {
return "엔진 시동!";
}
}
// 자식 클래스
class Car extends Vehicle {
private $doors;
public function __construct($brand, $color, $doors) {
parent::__construct($brand, $color); // 부모 생성자 호출
$this->doors = $doors;
}
public function getInfo() {
// 부모 메소드 오버라이딩 및 확장
return parent::getInfo() . ", 문 개수: {$this->doors}";
}
public function drive() {
$engineStatus = $this->startEngine(); // 상속받은 protected 메소드 호출
return $engineStatus . " 자동차가 주행 중입니다.";
}
}
$car = new Car("현대", "검정", 4);
echo $car->getInfo(); // 브랜드: 현대, 색상: 검정, 문 개수: 4
echo $car->drive(); // 엔진 시동! 자동차가 주행 중입니다.
// instanceof 연산자로 객체 타입 확인
var_dump($car instanceof Car); // bool(true)
var_dump($car instanceof Vehicle); // bool(true)
?>
상수
<?php
class Math {
// 클래스 상수
const PI = 3.14159;
const E = 2.71828;
public function getCircleArea($radius) {
return self::PI * $radius * $radius;
}
}
// 상수 접근
echo Math::PI; // 3.14159
$math = new Math();
echo $math->getCircleArea(5); // 약 78.5397
// PHP 7.1+에서는 접근 제어자 지원
class Configuration {
public const VERSION = "1.0.0";
protected const SECRET_KEY = "abc123";
private const API_ENDPOINT = "https://api.example.com";
public function getApiUrl() {
return self::API_ENDPOINT . "/v" . self::VERSION;
}
}
echo Configuration::VERSION; // 1.0.0
// echo Configuration::SECRET_KEY; // 오류: protected 상수에 접근할 수 없음
?>
정적 멤버
<?php
class Database {
// 정적 속성
private static $connection = null;
private static $connectionCount = 0;
// 정적 메소드
public static function connect($host, $user, $password) {
if (self::$connection === null) {
// 실제로는 여기서 데이터베이스 연결을 설정
self::$connection = "DB connected to $host as $user";
self::$connectionCount++;
}
return self::$connection;
}
public static function getConnectionCount() {
return self::$connectionCount;
}
// 일반 메소드
public function query($sql) {
if (self::$connection === null) {
throw new Exception("먼저 연결을 설정해야 합니다.");
}
return "Query executed: $sql";
}
}
// 정적 메소드 호출
$dbConnection = Database::connect("localhost", "root", "password");
echo $dbConnection; // DB connected to localhost as root
echo Database::getConnectionCount(); // 1
// 일반 메소드를 사용하려면 인스턴스 필요
$db = new Database();
echo $db->query("SELECT * FROM users"); // Query executed: SELECT * FROM users
?>
추상 클래스
<?php
// 추상 클래스
abstract class Shape {
protected $color;
public function __construct($color) {
$this->color = $color;
}
// 일반 메소드
public function getColor() {
return $this->color;
}
// 추상 메소드 (자식 클래스에서 반드시 구현해야 함)
abstract public function getArea();
abstract public function getPerimeter();
}
// 구체 클래스 (추상 클래스 구현)
class Circle extends Shape {
private $radius;
public function __construct($color, $radius) {
parent::__construct($color);
$this->radius = $radius;
}
public function getArea() {
return pi() * $this->radius * $this->radius;
}
public function getPerimeter() {
return 2 * pi() * $this->radius;
}
}
class Rectangle extends Shape {
private $width;
private $height;
public function __construct($color, $width, $height) {
parent::__construct($color);
$this->width = $width;
$this->height = $height;
}
public function getArea() {
return $this->width * $this->height;
}
public function getPerimeter() {
return 2 * ($this->width + $this->height);
}
}
// 추상 클래스는 인스턴스화할 수 없음
// $shape = new Shape("빨강"); // 오류
$circle = new Circle("파랑", 5);
echo $circle->getColor(); // 파랑
echo $circle->getArea(); // 약 78.54
echo $circle->getPerimeter(); // 약 31.42
$rectangle = new Rectangle("초록", 4, 6);
echo $rectangle->getColor(); // 초록
echo $rectangle->getArea(); // 24
echo $rectangle->getPerimeter(); // 20
?>
인터페이스
<?php
// 인터페이스 정의
interface Drawable {
public function draw();
}
interface Resizable {
public function resize($scale);
}
// 여러 인터페이스 구현
class Square implements Drawable, Resizable {
private $size;
public function __construct($size) {
$this->size = $size;
}
public function draw() {
return "정사각형을 그립니다. 크기: {$this->size}";
}
public function resize($scale) {
$this->size *= $scale;
return "크기가 {$this->size}로 조정되었습니다.";
}
}
// 상속과 인터페이스 조합
abstract class UIElement {
protected $id;
public function __construct($id) {
$this->id = $id;
}
abstract public function render();
}
class Button extends UIElement implements Drawable, Resizable {
private $width;
private $height;
public function __construct($id, $width, $height) {
parent::__construct($id);
$this->width = $width;
$this->height = $height;
}
public function render() {
return "<button id='{$this->id}'>버튼</button>";
}
public function draw() {
return "버튼을 그립니다. 크기: {$this->width}x{$this->height}";
}
public function resize($scale) {
$this->width *= $scale;
$this->height *= $scale;
return "버튼 크기가 {$this->width}x{$this->height}로 조정되었습니다.";
}
}
// 인터페이스 사용
$square = new Square(10);
echo $square->draw(); // 정사각형을 그립니다. 크기: 10
echo $square->resize(2); // 크기가 20로 조정되었습니다.
$button = new Button("submit-btn", 100, 50);
echo $button->render(); // <button id='submit-btn'>버튼</button>
echo $button->draw(); // 버튼을 그립니다. 크기: 100x50
echo $button->resize(1.5); // 버튼 크기가 150x75로 조정되었습니다.
// 타입 힌트에 인터페이스 사용
function drawElements(array $elements) {
foreach ($elements as $element) {
if ($element instanceof Drawable) {
echo $element->draw() . "<br>";
}
}
}
drawElements([$square, $button]);
?>
트레이트 (PHP 5.4+)
<?php
// 트레이트 정의
trait Logger {
private $logCount = 0;
public function log($message) {
$this->logCount++;
return date('Y-m-d H:i:s') . " - $message (로그 #{$this->logCount})";
}
public function getLogCount() {
return $this->logCount;
}
}
trait FileSystem {
public function readFile($path) {
return "파일 읽기: $path";
}
public function writeFile($path, $content) {
return "파일 쓰기: $path, 내용: $content";
}
}
// 클래스에 트레이트 사용
class User {
use Logger;
private $name;
public function __construct($name) {
$this->name = $name;
$this->log("사용자 {$this->name} 생성됨");
}
}
class FileManager {
// 여러 트레이트 사용
use Logger, FileSystem;
public function copyFile($source, $destination) {
$this->log("파일 복사: $source -> $destination");
$content = $this->readFile($source);
$this->writeFile($destination, $content);
return "파일이 복사되었습니다.";
}
}
$user = new User("홍길동");
echo $user->log("로그인 시도"); // 2025-06-05 12:34:56 - 로그인 시도 (로그 #2)
echo $user->getLogCount(); // 2
$fileManager = new FileManager();
echo $fileManager->log("파일 관리자 초기화"); // 2025-06-05 12:34:56 - 파일 관리자 초기화 (로그 #1)
echo $fileManager->copyFile("/path/to/source.txt", "/path/to/destination.txt");
?>
메소드 체이닝
<?php
class Query {
private $table;
private $where = [];
private $orderBy;
private $limit;
public function from($table) {
$this->table = $table;
return $this; // 메소드 체이닝을 위해 $this 반환
}
public function where($column, $operator, $value) {
$this->where[] = "$column $operator '$value'";
return $this;
}
public function orderBy($column, $direction = 'ASC') {
$this->orderBy = "$column $direction";
return $this;
}
public function limit($limit) {
$this->limit = $limit;
return $this;
}
public function build() {
$sql = "SELECT * FROM {$this->table}";
if (!empty($this->where)) {
$sql .= " WHERE " . implode(" AND ", $this->where);
}
if ($this->orderBy) {
$sql .= " ORDER BY {$this->orderBy}";
}
if ($this->limit) {
$sql .= " LIMIT {$this->limit}";
}
return $sql;
}
}
// 메소드 체이닝 사용
$query = new Query();
$sql = $query->from('users')
->where('age', '>', 18)
->where('status', '=', 'active')
->orderBy('created_at', 'DESC')
->limit(10)
->build();
echo $sql;
// 출력: SELECT * FROM users WHERE age > '18' AND status = 'active' ORDER BY created_at DESC LIMIT 10
?>
매직 메소드
<?php
class Person {
private $data = [];
// 매직 메소드: 설정되지 않은 속성에 접근할 때 호출
public function __get($name) {
if (array_key_exists($name, $this->data)) {
return $this->data[$name];
}
return null;
}
// 매직 메소드: 설정되지 않은 속성에 값을 할당할 때 호출
public function __set($name, $value) {
$this->data[$name] = $value;
}
// 매직 메소드: isset() 함수 호출 시 호출
public function __isset($name) {
return isset($this->data[$name]);
}
// 매직 메소드: unset() 함수 호출 시 호출
public function __unset($name) {
unset($this->data[$name]);
}
// 매직 메소드: 객체를 문자열로 변환할 때 호출
public function __toString() {
return "Person 객체: " . json_encode($this->data);
}
// 매직 메소드: 정의되지 않은 메소드 호출 시 호출
public function __call($name, $arguments) {
$prefix = substr($name, 0, 3);
if ($prefix === 'get') {
$property = lcfirst(substr($name, 3));
return $this->__get($property);
} elseif ($prefix === 'set') {
$property = lcfirst(substr($name, 3));
$this->__set($property, $arguments[0]);
return $this;
}
throw new Exception("메소드 '$name'은(는) 존재하지 않습니다.");
}
// 매직 메소드: 정의되지 않은 정적 메소드 호출 시 호출
public static function __callStatic($name, $arguments) {
return "정적 메소드 '$name'이(가) 호출됨";
}
// 매직 메소드: 객체가 복제될 때 호출
public function __clone() {
// 깊은 복사를 위한 로직
$this->data = array_map(function($item) {
return is_object($item) ? clone $item : $item;
}, $this->data);
}
// 매직 메소드: serialize() 함수 호출 시 호출
public function __sleep() {
// 직렬화할 속성 배열 반환
return array_keys($this->data);
}
// 매직 메소드: unserialize() 함수 호출 시 호출
public function __wakeup() {
// 역직렬화 후 초기화 작업
}
}
$person = new Person();
$person->name = "홍길동"; // __set() 호출
$person->age = 25; // __set() 호출
echo $person->name; // __get() 호출: 홍길동
echo isset($person->name); // __isset() 호출: true
echo $person->getName(); // __call() 호출: 홍길동
$person->setEmail("hong@example.com"); // __call() 호출
echo $person; // __toString() 호출: Person 객체: {"name":"홍길동","age":25,"email":"hong@example.com"}
unset($person->age); // __unset() 호출
echo Person::sayHello(); // __callStatic() 호출: 정적 메소드 'sayHello'이(가) 호출됨
?>