PHP 8.2/8.3 ๋ชจ๋ ๋ฌธ๋ฒ, Composer, Nullsafe(?->), Match ํํ์, ์์ฑ์ ํ๋กํผํฐ ์น๊ฒฉ, PDO ์์ ํ ์ฟผ๋ฆฌ, ์ํผ๊ธ๋ก๋ฒ ๋ฐ ์ค๋ฌด ๋ณด์ ๋ ํผ๋ฐ์ค
## 1. PHP 8.x ์ค์น & ๋ด์ฅ ์น ์๋ฒ (Setup & Built-in Server)
```bash
# ๐ [1] OS๋ณ PHP 8.x ์ต์ ๋ฒ์ ์ค์น
# macOS (Homebrew)
brew install php
# Ubuntu / Debian (Ondลej Surรฝ PPA ๊ถ์ฅ)
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install -y php8.3 php8.3-cli php8.3-fpm php8.3-mysql php8.3-xml php8.3-curl php8.3-mbstring
# ๐ช Windows (winget)
winget install PHP.PHP.8.3
# [2] ์ค์น ๋ฐ ๋ชจ๋ ํ์ธ
php -v
php -m # ์ค์น๋ ํ์ฅ ๋ชจ๋ ๋ชฉ๋ก
# [3] ๊ฐ๋ฐ์ฉ ๋ก์ปฌ ๋ด์ฅ ์น ์๋ฒ ์ฆ์ ์คํ (ํฌํธ 8000)
# ํ์ฌ ๋๋ ํ ๋ฆฌ ๊ธฐ์ค
php -S localhost:8000
# ํน์ public ๋๋ ํ ๋ฆฌ๋ฅผ ๋ฃจํธ๋ก ์ง์ ์คํ
php -S localhost:8000 -t public
```
## 2. ๋ชจ๋ PHP 8.x ์ ๊ธฐ๋ฅ (PHP 8.0 ~ 8.3 Modern Features)
```php
<?php
// [1] Nullsafe ์ฐ์ฐ์ (?->) - ์ค๊ฐ ๊ฐ์ฒด๊ฐ null์ด์ด๋ ์๋ฌ ์์ด null ๋ฐํ
$country = $session?->user?->getAddress()?->country;
// [2] Match ํํ์ (Switch ๋ฌธ ๋์ฒด, ์๊ฒฉํ === ๋น๊ต ๋ฐ ๊ฐ ๋ฐํ)
$status = match ($statusCode) {
200, 201 => '์ฑ๊ณต',
400, 404 => 'ํด๋ผ์ด์ธํธ ์ค๋ฅ',
500 => '์๋ฒ ์ค๋ฅ',
default => '์ ์ ์์',
};
// [3] ์์ฑ์ ํ๋กํผํฐ ์น๊ฒฉ (Constructor Property Promotion)
// ํ๋ ์ ์ธ, ๋งค๊ฐ๋ณ์ ์ ์ธ, ํ ๋น์ ์์ฑ์ ์ธ์์์ ํ ๋ฒ์ ์ฒ๋ฆฌ
class User {
public function __construct(
public readonly int $id, // readonly: ์์ฑ ํ ๋ณ๊ฒฝ ๋ถ๊ฐ
public string $name,
public string $email = '',
private ?string $password = null,
) {}
}
// [4] ๋ช
๋ช
๋ ์ธ์ (Named Arguments)
// ์์์ ๊ตฌ์ ๋ฐ์ง ์๊ณ ์ํ๋ ๋งค๊ฐ๋ณ์๋ง ๋ช
์์ ์ผ๋ก ์ ๋ฌ
$user = new User(id: 1, name: 'Lucky', email: 'lucky@test.com');
// [5] Enum (์ด๊ฑฐํ - PHP 8.1+)
enum UserRole: string {
case Admin = 'ADMIN';
case Manager = 'MANAGER';
case Guest = 'GUEST';
}
```
## 3. ๋ณ์, ๋ฐ์ดํฐ ํ์
& ์ฐ์ฐ์ (Variables & Operators)
```php
<?php
// [1] ์๊ฒฉํ ํ์
๋ชจ๋ ์ ์ธ (ํ์ผ ์ต์๋จ ํ์ ๊ถ์ฅ)
declare(strict_types=1);
$name = "Lucky";
$age = 28;
$isMember = true;
$pi = 3.14159;
// [2] ๋ ๋ณํฉ ์ฐ์ฐ์ (?? ๋ฐ ??=)
// ๋ณ์๊ฐ ์กด์ฌํ์ง ์๊ฑฐ๋ null์ด๋ฉด ๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ
$username = $_GET['user'] ?? 'Guest';
// ๋ ๋ณํฉ ํ ๋น: ๋ณ์๊ฐ null์ผ ๋๋ง ๊ฐ ํ ๋น
$config['timeout'] ??= 30;
// [3] ์ผํญ ์ฐ์ฐ์ ๋จ์ถ
$result = $isValid ?: '์คํจ'; // $isValid ? $isValid : '์คํจ'
// [4] ์ฐ์ฃผ์ ์ฐ์ฐ์ (<=>) - ๋ ๊ฐ ๋น๊ต (-1, 0, 1 ๋ฐํ)
// ์ ๋ ฌ ์ฝ๋ฐฑ ํจ์ ์์ฑ ์ ๋งค์ฐ ํธ๋ฆฌ
usort($items, fn($a, $b) => $a['price'] <=> $b['price']);
// [5] ์๊ฒฉํ ๋น๊ต (===) ์์น
// == (ํ์
๋ณํ ๋์จํ ๋น๊ต: 0 == "0" -> true)
// === (ํ์
๊ณผ ๊ฐ์ด ๋ชจ๋ ์ผ์น: 0 === "0" -> false)
```
## 4. ๋ฐฐ์ด ๋ค๋ฃจ๊ธฐ (Arrays: Indexed & Associative)
```php
<?php
// [1] ์ธ๋ฑ์ค ๋ฐฐ์ด & ์ฐ๊ด ๋ฐฐ์ด
$fruits = ['apple', 'banana', 'cherry'];
$user = [
'id' => 10,
'name' => 'Lucky',
'roles' => ['admin', 'editor']
];
// [2] ๋ฐฐ์ด ๊ตฌ์กฐ ๋ถํด ํ ๋น (Destructuring) & Spread ์ฐ์ฐ์ (...)
[$first, $second] = $fruits;
$allFruits = ['mango', ...$fruits]; // ['mango', 'apple', 'banana', 'cherry']
// [3] ์ค๋ฌด ํ์ ๋ด์ฅ ๋ฐฐ์ด ํจ์
// 1. ๊ฐ ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฌ
if (in_array('apple', $fruits, true)) { /* ... */ }
// 2. ํค ์กด์ฌ ์ฌ๋ถ ๊ฒ์ฌ
if (array_key_exists('name', $user)) { /* ... */ }
// 3. ๋ณํ (array_map) & ํํฐ๋ง (array_filter)
$numbers = [1, 2, 3, 4, 5, 6];
$evens = array_filter($numbers, fn($n) => $n % 2 === 0); // [2, 4, 6]
$squares = array_map(fn($n) => $n * 2, $numbers); // [2, 4, 6, 8, 10, 12]
// 4. ์ปฌ๋ผ ์ถ์ถ (array_column: DB ๊ฒฐ๊ณผ์
๊ฐ๊ณต์ ํ์)
$users = [
['id' => 1, 'name' => 'Alice'],
['id' => 2, 'name' => 'Bob']
];
$names = array_column($users, 'name', 'id'); // [1 => 'Alice', 2 => 'Bob']
```
## 5. ๋ฌธ์์ด & ์ ํธ๋ฆฌํฐ ํจ์ (Strings: PHP 8+)
```php
<?php
$str = "Hello, Lucky World!";
// [1] ํ๋์ ๋ฌธ์์ด ํ์ ํจ์ (PHP 8.0+)
str_contains($str, "Lucky"); // true (ํฌํจ ์ฌ๋ถ)
str_starts_with($str, "Hello"); // true (์ ๋์ฌ ๊ฒ์ฌ)
str_ends_with($str, "World!"); // true (์ ๋ฏธ์ฌ ๊ฒ์ฌ)
// [2] ๋ถํ ๋ฐ ๊ฒฐํฉ (explode / implode)
$tags = "php,laravel,backend";
$tagArray = explode(',', $tags); // ['php', 'laravel', 'backend']
$tagString = implode(' | ', $tagArray); // "php | laravel | backend"
// [3] ๊ณต๋ฐฑ ์ ๊ฑฐ & ๋์๋ฌธ์ & ํฌ๋งทํ
$clean = trim(" hello \n"); // "hello"
$upper = strtoupper("lucky"); // "LUCKY"
$padded = sprintf("ID: %04d", 5); // "ID: 0005"
$length = mb_strlen("ํ๊ธ", "UTF-8"); // 2 (๋ฉํฐ๋ฐ์ดํธ ๋ฌธ์์ด ๊ธธ์ด)
```
## 6. ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ (OOP: Classes, Interfaces, Traits)
```php
<?php
interface Identifiable {
public function getId(): int;
}
abstract class BaseEntity implements Identifiable {
public function __construct(protected int $id) {}
public function getId(): int { return $this->id; }
}
// ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์ฝ๋ ์กฐ๊ฐ (Trait)
trait Timestampable {
public \DateTimeImmutable $createdAt;
public function initTimestamp(): void {
$this->createdAt = new \DateTimeImmutable();
}
}
class Product extends BaseEntity {
use Timestampable; // ํธ๋ ์ดํธ ์ฌ์ฉ
public function __construct(
int $id,
public string $title,
public int $price
) {
parent::__construct($id);
$this->initTimestamp();
}
}
```
## 7. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๋ (PDO & Prepared Statements)
```php
<?php
// [1] PDO ์ฐ๊ฒฐ ์์ฑ (MySQL / MariaDB)
$dsn = "mysql:host=127.0.0.1;dbname=app_db;charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // ์๋ฌ ๋ฐ์ ์ ์์ธ ๋์ง
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // ์ฐ๊ด ๋ฐฐ์ด๋ก ์กฐํ
PDO::ATTR_EMULATE_PREPARES => false, // ๋ค์ดํฐ๋ธ ํ๋ฆฌํ์ด๋ ์คํ
์ดํธ๋จผํธ ๊ฐ์
];
$pdo = new PDO($dsn, "db_user", "db_pass", $options);
// [2] ์์ ํ ์ฟผ๋ฆฌ ์กฐํ (SQL Injection ์๋ฒฝ ์ฐจ๋จ)
$stmt = $pdo->prepare("SELECT id, name, email FROM users WHERE role = :role AND is_active = :active");
$stmt->execute([
':role' => 'ADMIN',
':active' => 1
]);
$admins = $stmt->fetchAll(); // ์ ์ฒด ํ ๋ฐํ
// [3] ๋ฐ์ดํฐ ์ฝ์
& ํธ๋์ญ์
try {
$pdo->beginTransaction();
$insert = $pdo->prepare("INSERT INTO users (name, email) VALUES (?, ?)");
$insert->execute(["Lucky", "lucky@test.com"]);
$newId = $pdo->lastInsertId();
$pdo->commit();
} catch (Exception $e) {
$pdo->rollBack();
error_log("DB ํธ๋์ญ์
์คํจ: " . $e->getMessage());
}
```
## 8. ์น ์์ฒญ & ์ํผ๊ธ๋ก๋ฒ (Superglobals: GET, POST, FILES)
```php
<?php
// [1] GET / POST ํ๋ผ๋ฏธํฐ ์์ ํ ์์
$keyword = filter_input(INPUT_GET, 'keyword', FILTER_DEFAULT);
$page = filter_input(INPUT_GET, 'page', FILTER_VALIDATE_INT) ?: 1;
// [2] ์ธ์
(Session) & ์ฟ ํค(Cookie)
session_start();
$_SESSION['user_id'] = 100; // ์ธ์
๋ฐ์ดํฐ ์ ์ฅ
$currentUserId = $_SESSION['user_id'] ?? null;
// ๋ณด์ ์ฟ ํค ์ค์ (HttpOnly, Secure, SameSite)
setcookie('theme', 'dark', [
'expires' => time() + 86400 * 30,
'path' => '/',
'secure' => true, // HTTPS์์๋ง ์ ์ก
'httponly' => true, // ์๋ฐ์คํฌ๋ฆฝํธ ์ ๊ทผ ์ฐจ๋จ (XSS ๋ฐฉ์ด)
'samesite' => 'Lax'
]);
// [3] ์์ ํ ํ์ผ ์
๋ก๋ ๊ฒ์ฆ ($_FILES)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['avatar'])) {
$file = $_FILES['avatar'];
if ($file['error'] === UPLOAD_ERR_OK) {
$allowedTypes = ['image/jpeg', 'image/png'];
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
if (in_array($mime, $allowedTypes, true)) {
$dest = 'uploads/' . bin2hex(random_bytes(16)) . '.jpg';
move_uploaded_file($file['tmp_name'], $dest);
}
}
}
```
## 9. JSON API ์๋ต ์ฒ๋ฆฌ (JSON & HTTP Headers)
```php
<?php
// REST API ํ์ค JSON ์๋ต ์์ฑ
header('Content-Type: application/json; charset=utf-8');
$requestBody = file_get_contents('php://input'); // ์์ JSON ๋ฐ๋ ์์
$payload = json_decode($requestBody, true, 512, JSON_THROW_ON_ERROR);
$response = [
'success' => true,
'data' => [
'id' => 1,
'message' => '์ฒ๋ฆฌ๊ฐ ์๋ฃ๋์์ต๋๋ค.'
],
'timestamp' => time()
];
http_response_code(200); // HTTP 200 OK
echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
```
## 10. ์์ธ ์ฒ๋ฆฌ & ์๋ฌ ํธ๋ค๋ง (Exception Handling)
```php
<?php
class UserNotFoundException extends Exception {}
function findUser(int $id): array {
if ($id <= 0) {
throw new InvalidArgumentException("ID๋ ์์์ฌ์ผ ํฉ๋๋ค.");
}
// ์ฌ์ฉ์ ์์ ์
throw new UserNotFoundException("์ฌ์ฉ์๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค: {$id}");
}
try {
$user = findUser(-1);
} catch (InvalidArgumentException $e) {
echo "์๋ชป๋ ์ธ์: " . $e->getMessage();
} catch (UserNotFoundException $e) {
echo "์ฌ์ฉ์ ์์: " . $e->getMessage();
} catch (Throwable $e) { // ๋ชจ๋ ์๋ฌ ๋ฐ ์์ธ์ ์ต์์ ์ธํฐํ์ด์ค
echo "์๊ธฐ์น ๋ชปํ ์น๋ช
์ ์ค๋ฅ: " . $e->getMessage();
} finally {
// ๋ฆฌ์์ค ์ ๋ฆฌ
}
```
## 11. ํจํค์ง ๊ด๋ฆฌ & ์คํ ๋ก๋ฉ (Composer & PSR-4)
```bash
# ๐ฆ Composer ํต์ฌ ๋ช
๋ น์ด
composer init # ๋ํํ์ผ๋ก composer.json ์์ฑ
composer require guzzlehttp/guzzle # ์ธ๋ถ ํจํค์ง ์ค์น
composer require --dev phpunit/phpunit # ๊ฐ๋ฐ ์ ์ฉ ํจํค์ง ์ค์น
composer install # composer.lock ๊ธฐ์ค์ผ๋ก ์ผ๊ด ์ค์น
composer update # ํจํค์ง ์ต์ ๋ฒ์ ๊ฐฑ์
composer dump-autoload -o # ์คํ ๋ก๋ ํด๋์ค๋งต ์ต์ ํ (ํ๋ก๋์
๋ฐฐํฌ ํ์)
```
```json
// composer.json - PSR-4 ์คํ ๋ก๋ฉ ์ค์
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
}
```
```php
<?php
// index.php - ์ง์
์ ์์ ๋จ 1์ค๋ก ๋ชจ๋ ํด๋์ค ์๋ ๋ก๋ฉ
require_once __DIR__ . '/vendor/autoload.php';
use App\Services\OrderService;
$orderService = new OrderService();
```
## 12. ์ค๋ฌด ๋ณด์ & php.ini ์ฑ๋ฅ ์ต์ ํ (Security & OPcache)
```php
<?php
// [1] XSS ๋ฐฉ์ด: ์ถ๋ ฅ ์ ๋ฐ๋์ ์ด์ค์ผ์ดํ ์ฒ๋ฆฌ
echo htmlspecialchars($userInput, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
// [2] ๋น๋ฐ๋ฒํธ ์์ ํ ์ํธํ (Bcrypt / Argon2)
$hash = password_hash("mysecretpass", PASSWORD_DEFAULT);
// ๋น๋ฐ๋ฒํธ ์ผ์น ๊ฒ์ฆ
if (password_verify("mysecretpass", $hash)) {
// ์ธ์ฆ ์ฑ๊ณต
}
```
```ini
; [3] php.ini ํ๋ก๋์
ํ์ ๊ถ์ฅ ์ค์
display_errors = Off ; ๋ธ๋ผ์ฐ์ ์ ์๋ฌ ์์ธ ๋
ธ์ถ ์ฐจ๋จ (๋ณด์)
log_errors = On ; ์๋ฌ๋ ํ์ผ๋ก ์์ ํ๊ฒ ๊ธฐ๋ก
error_log = /var/log/php_errors.log
expose_php = Off ; HTTP ์๋ต ํค๋์์ PHP ๋ฒ์ ์จ๊น
upload_max_filesize = 20M
post_max_size = 25M
; [4] OPcache ํ์ฑํ (PHP ๋ฐ์ดํธ์ฝ๋ ์บ์๋ก ์ฑ๋ฅ 3๋ฐฐ ์ด์ ํฅ์)
opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 8
opcache.max_accelerated_files = 10000
opcache.validate_timestamps = 0 ; ํ๋ก๋์
: ํ์ผ ๋ณ๊ฒฝ ๊ฐ์ง ๋นํ์ฑํ๋ก ๋์คํฌ I/O ์ ๊ฑฐ
```
์๊ฒฌ ๋ฐ ์ง๋ฌธ
0์์ง ๋ฑ๋ก๋ ์๊ฒฌ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋๊ธ์ ๋จ๊ฒจ๋ณด์ธ์!
๋๊ธ ์์
๋๊ธ ์ญ์