๊ฐœ์š”

Doctrine DBAL์€ PHP ์–ธ์–ด๋ฅผ ์œ„ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ถ”์ƒํ™” ๊ณ„์ธต์ด๋‹ค. PHP ์ƒํƒœ๊ณ„์—์„œ ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ถ”์ƒํ™” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ค‘ ํ•˜๋‚˜๋กœ, ํŠนํžˆ Symfony ํ”„๋ ˆ์ž„์›Œํฌ์™€ ๊ธด๋ฐ€ํ•˜๊ฒŒ ํ†ตํ•ฉ๋˜์–ด ์žˆ๋‹ค. Laravel 11์—์„œ๋Š” DBAL์— ๋Œ€ํ•œ ์˜์กด์„ฑ์ด ์ œ๊ฑฐ๋˜์—ˆ๋‹ค

์œ ์‚ฌ ๋„๊ตฌ ๋น„๊ต

๋‹ค๋ฅธ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์˜ ์œ ์‚ฌํ•œ ๋„๊ตฌ๋“ค:

  • Java: Hibernate
  • Python: SQLAlchemy
  • Node.js: Sequelize
  • Ruby: ActiveRecord

PHP ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ ๋ฐฉ์‹์˜ ๋ฐœ์ „

timeline
    title PHP ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ ๋ฐฉ์‹์˜ ๋ฐœ์ „
    section ์ดˆ๊ธฐ
        mysql_* ํ•จ์ˆ˜ : ์ดˆ๊ธฐ MySQL ์ „์šฉ ํ•จ์ˆ˜
    section ์ค‘๊ธฐ
        PDO : PHP Data Objects ๋„์ž…
    section ํ˜„์žฌ
        Doctrine DBAL : ๊ณ ๊ธ‰ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ถ”์ƒํ™”
        Doctrine ORM : ๊ฐ์ฒด ๊ด€๊ณ„ ๋งคํ•‘

1. ๊ฐœ๋… ์ดํ•ด

1.1 DBAL์˜ ์ •์˜์™€ ํ•„์š”์„ฑ

DBAL(Database Abstraction Layer)์€ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹œ์Šคํ…œ์„ ์ผ๊ด€๋œ ๋ฐฉ์‹์œผ๋กœ ๋‹ค๋ฃจ๊ธฐ ์œ„ํ•œ ์ถ”์ƒํ™” ๊ณ„์ธต์ด๋‹ค.

์‹ค์ƒํ™œ ๋น„์œ 

์—ฌํ–‰์ž๊ฐ€ ๊ฐ ๋‚˜๋ผ๋งˆ๋‹ค ๋‹ค๋ฅธ ์ „๊ธฐ ์ฝ˜์„ผํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ๋ฉ€ํ‹ฐ์–ด๋Œ‘ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, DBAL์€ ์„œ๋กœ ๋‹ค๋ฅธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‹œ์Šคํ…œ์„ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” โ€˜๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์–ด๋Œ‘ํ„ฐโ€™์ด๋‹ค.

1.2 ์‹œ์Šคํ…œ ์•„ํ‚คํ…์ฒ˜

graph TB
    subgraph "Application Layer"
        A[PHP Application]
        B[Doctrine DBAL]
    end
    
    subgraph "Database Layer"
        C[Database Drivers]
        D[MySQL]
        E[PostgreSQL]
        F[SQLite]
        G[Oracle]
    end
    
    A -->|Uses| B
    B -->|Abstracts| C
    C -->|Connects| D
    C -->|Connects| E
    C -->|Connects| F
    C -->|Connects| G

2. ํ™˜๊ฒฝ ์„ค์ •๊ณผ ๊ธฐ๋ณธ ์‚ฌ์šฉ

2.1 ์„ค์น˜ ๋ฐ ์ดˆ๊ธฐ ์„ค์ •

// Composer๋ฅผ ํ†ตํ•œ ์„ค์น˜
// $ composer require doctrine/dbal
 
// ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์„ค์ •
$connectionParams = [
    'dbname' => 'my_database',
    'user' => 'db_user',
    'password' => 'db_password',
    'host' => 'localhost',
    'driver' => 'pdo_mysql',
    // ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•œ ์ถ”๊ฐ€ ์˜ต์…˜
    'driverOptions' => [
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES 'UTF8'"
    ]
];
 
// Connection ๊ฐ์ฒด ์ƒ์„ฑ
$connection = \Doctrine\DBAL\DriverManager::getConnection($connectionParams);

2.2 ๊ธฐ๋ณธ ๋ฐ์ดํ„ฐ ์กฐ์ž‘

// 1. ๋ฐ์ดํ„ฐ ์กฐํšŒ
// ์ž˜๋ชป๋œ ์˜ˆ์‹œ - SQL Injection ์ทจ์•ฝ์ 
$userId = $_GET['id'];
$wrongQuery = "SELECT * FROM users WHERE id = $userId"; // ์ ˆ๋Œ€ ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ ๋จ
 
// ์˜ฌ๋ฐ”๋ฅธ ์˜ˆ์‹œ - ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ ์‚ฌ์šฉ
$queryBuilder = $connection->createQueryBuilder();
$result = $queryBuilder
    ->select('*')
    ->from('users')
    ->where('id = :id')
    ->setParameter('id', $userId)
    ->executeQuery()
    ->fetchAssociative();
 
// 2. ๋ฐ์ดํ„ฐ ์‚ฝ์ž…
$connection->insert('users', [
    'name' => 'ํ™๊ธธ๋™',
    'email' => 'hong@example.com',
    'created_at' => new \DateTime()
]);
 
// 3. ๋ฐ์ดํ„ฐ ์ˆ˜์ •
$connection->update('users', 
    ['name' => '๊น€์ฒ ์ˆ˜'], // ๋ณ€๊ฒฝํ•  ๋ฐ์ดํ„ฐ
    ['id' => 1] // WHERE ์กฐ๊ฑด
);
 
// 4. ๋ฐ์ดํ„ฐ ์‚ญ์ œ
$connection->delete('users', ['id' => 1]);

3. ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ํ™œ์šฉ

3.1 ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ

sequenceDiagram
    participant A as Application
    participant T as Transaction Manager
    participant D as Database
    
    A->>T: beginTransaction()
    A->>D: Query 1
    A->>D: Query 2
    alt ์„ฑ๊ณต
        A->>T: commit()
        T->>D: ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ €์žฅ
    else ์‹คํŒจ
        A->>T: rollback()
        T->>D: ๋ณ€๊ฒฝ์‚ฌํ•ญ ์ทจ์†Œ
    end
// ํŠธ๋žœ์žญ์…˜ ์ฒ˜๋ฆฌ ์˜ˆ์‹œ
class OrderService {
    private $connection;
    
    public function processOrder(int $orderId, int $productId): void {
        $this->connection->beginTransaction();
        try {
            // 1. ์žฌ๊ณ  ํ™•์ธ
            $stock = $this->checkStock($productId);
            if ($stock <= 0) {
                throw new \Exception('์žฌ๊ณ  ๋ถ€์กฑ');
            }
            
            // 2. ์ฃผ๋ฌธ ์ฒ˜๋ฆฌ
            $this->updateOrder($orderId);
            
            // 3. ์žฌ๊ณ  ๊ฐ์†Œ
            $this->decreaseStock($productId);
            
            $this->connection->commit();
        } catch (\Exception $e) {
            $this->connection->rollBack();
            throw $e;
        }
    }
}

3.2 ์„ฑ๋Šฅ ์ตœ์ ํ™”

๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ์˜ˆ์‹œ

class BatchProcessor {
    private $connection;
    private const BATCH_SIZE = 1000;
    
    public function processBatch(array $items): void {
        $this->connection->beginTransaction();
        try {
            foreach ($items as $i => $item) {
                $this->connection->insert('items', $item);
                
                if (($i + 1) % self::BATCH_SIZE === 0) {
                    $this->connection->commit();
                    $this->connection->beginTransaction();
                }
            }
            $this->connection->commit();
        } catch (\Exception $e) {
            $this->connection->rollBack();
            throw $e;
        }
    }
}

4. ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ

4.1 SQL Injection ๋ฐฉ์ง€

// ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ
$name = $_POST['name'];
$query = "SELECT * FROM users WHERE name = '$name'"; // ์ทจ์•ฝ!
 
// ์•ˆ์ „ํ•œ ์ฝ”๋“œ
$qb = $connection->createQueryBuilder();
$query = $qb
    ->select('*')
    ->from('users')
    ->where('name = :name')
    ->setParameter('name', $name);

4.2 ๊ถŒํ•œ ๊ด€๋ฆฌ

// ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์‚ฌ์šฉ์ž ๊ถŒํ•œ ์„ค์ • ์˜ˆ์‹œ
$connectionParams['user'] = 'read_only_user';
$connectionParams['password'] = 'read_only_pass';
 
// ์ฝ๊ธฐ ์ „์šฉ ์—ฐ๊ฒฐ ์ƒ์„ฑ
$readOnlyConnection = DriverManager::getConnection($connectionParams);

5. ๋ฌธ์ œ ํ•ด๊ฒฐ ๊ฐ€์ด๋“œ

5.1 ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ

Connection ๊ด€๋ จ ๋ฌธ์ œ

try {
    $connection = DriverManager::getConnection($connectionParams);
} catch (\Doctrine\DBAL\Exception\ConnectionException $e) {
    // ์—ฐ๊ฒฐ ์‹คํŒจ ์ฒ˜๋ฆฌ
    $logger->error('๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์‹คํŒจ: ' . $e->getMessage());
    throw new DatabaseConnectionException('๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
}

6. ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋””๋ฒ„๊น…

6.1 ์ฟผ๋ฆฌ ๋กœ๊น…

$configuration = new \Doctrine\DBAL\Configuration();
$logger = new \Doctrine\DBAL\Logging\DebugStack();
$configuration->setSQLLogger($logger);
 
$connection = DriverManager::getConnection($connectionParams, $configuration);
 
// ์ฟผ๋ฆฌ ์‹คํ–‰ ํ›„
foreach ($logger->queries as $query) {
    echo sprintf(
        "์‹คํ–‰ ์‹œ๊ฐ„: %f์ดˆ, SQL: %s\n",
        $query['executionMS'],
        $query['sql']
    );
}

7. ๊ฒฐ๋ก 

Doctrine DBAL์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ƒํ™ฉ์—์„œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค:

  • ๋‹ค์ค‘ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ง€์›์ด ํ•„์š”ํ•œ ํ”„๋กœ์ ํŠธ
  • Type-safeํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐ์ž‘์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ฒค๋” ๋…๋ฆฝ์„ฑ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ

7.1 Best Practices ์š”์•ฝ

  1. ํ•ญ์ƒ ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ”์ธ๋”ฉ ์‚ฌ์šฉ
  2. ํŠธ๋žœ์žญ์…˜ ๋ฒ”์œ„ ์ตœ์†Œํ™”
  3. ๋ฐฐ์น˜ ์ฒ˜๋ฆฌ ํ™œ์šฉ
  4. ์ ์ ˆํ•œ ๋กœ๊น…๊ณผ ๋ชจ๋‹ˆํ„ฐ๋ง ๊ตฌํ˜„
  5. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ํ’€๋ง ์‚ฌ์šฉ