Skip to content

Instantly share code, notes, and snippets.

@Kcko
Created October 24, 2025 13:24
Show Gist options
  • Save Kcko/fa74e5372441e74b6f05fa87406d4eaa to your computer and use it in GitHub Desktop.
Save Kcko/fa74e5372441e74b6f05fa87406d4eaa to your computer and use it in GitHub Desktop.
<?php
/**
 * ========================================
 * 📦 Další praktické OOP koncepty (krátce)
 * ========================================
 */

// ============================================================================
// 1. 📦 DTO (Data Transfer Objects) - Typované přenosy dat
// ============================================================================

// ❌ ŠPATNĚ - Arrays všude
function createOrder(array $data) 
{
    $customerId = $data['customer_id']; // Může chybět!
    $items = $data['items']; // Co je uvnitř? Kdo ví...
    $note = $data['note'] ?? null;
}

$orderData = [
    'customer_id' => 123,
    'items' => [...],
    'shipping' => 'DHL'
];
createOrder($orderData);

// ✅ DOBŘE - Typovaný DTO
class CreateOrderDTO 
{
    public int $customerId;
    public array $items;
    public string $shippingMethod;
    public ?string $note;
    
    public function __construct(int $customerId, array $items, string $shippingMethod, ?string $note = null) 
    {
        $this->customerId = $customerId;
        $this->items = $items;
        $this->shippingMethod = $shippingMethod;
        $this->note = $note;
    }
    
    // Nebo ze JSON
    public static function fromArray(array $data): self 
    {
        return new self(
            $data['customer_id'],
            $data['items'],
            $data['shipping_method'],
            $data['note'] ?? null
        );
    }
}

function createOrderSafe(CreateOrderDTO $dto) 
{
    // Máš jistotu, že všechno je tam a správného typu!
    $customerId = $dto->customerId;
    $items = $dto->items;
}

$dto = new CreateOrderDTO(123, [...], 'DHL', 'Handle with care');
createOrderSafe($dto);


// ============================================================================
// 2. 🗄️ REPOSITORY PATTERN - Databázová logika na jednom místě
// ============================================================================

// ❌ ŠPATNĚ - SQL všude v kódu
class OrderController 
{
    public function show(int $id) 
    {
        $order = $this->db->query("SELECT * FROM orders WHERE id = ?", $id)->fetch();
        // SQL logika roztroušená po celé aplikaci
    }
    
    public function listByCustomer(int $customerId) 
    {
        $orders = $this->db->query("SELECT * FROM orders WHERE customer_id = ?", $customerId)->fetchAll();
    }
}

// ✅ DOBŘE - Repository
interface OrderRepository 
{
    public function find(int $id): ?Order;
    public function findByCustomer(int $customerId): array;
    public function findRecent(int $limit = 10): array;
    public function save(Order $order): void;
    public function delete(Order $order): void;
}

class DatabaseOrderRepository implements OrderRepository 
{
    private Context $db;
    
    public function __construct(Context $db) 
    {
        $this->db = $db;
    }
    
    public function find(int $id): ?Order 
    {
        $row = $this->db->table('orders')->get($id);
        return $row ? $this->createOrderFromRow($row) : null;
    }
    
    public function findByCustomer(int $customerId): array 
    {
        return $this->db->table('orders')
            ->where('customer_id', $customerId)
            ->fetchAll();
    }
    
    public function findRecent(int $limit = 10): array 
    {
        return $this->db->table('orders')
            ->order('created_at DESC')
            ->limit($limit)
            ->fetchAll();
    }
    
    public function save(Order $order): void 
    {
        // Logika ukládání
    }
    
    private function createOrderFromRow($row): Order 
    {
        // Konverze řádku na entitu
    }
}

// Použití:
class OrderService 
{
    private OrderRepository $orderRepo;
    
    public function __construct(OrderRepository $orderRepo) 
    {
        $this->orderRepo = $orderRepo;
    }
    
    public function getCustomerOrders(int $customerId): array 
    {
        return $this->orderRepo->findByCustomer($customerId);
    }
}

// VÝHODY:
// - Veškerá databázová logika na jednom místě
// - Snadné testování (mock repository)
// - Lze změnit databázi bez změny business logiky


// ============================================================================
// 3. 🎯 STRATEGY PATTERN - Nahrazení obrovských if/switch
// ============================================================================

// ❌ ŠPATNĚ - Obrovský if/switch
class PaymentProcessor 
{
    public function process(Order $order, string $paymentType) 
    {
        if ($paymentType === 'credit_card') {
            // 50 řádků kódu pro credit card
            $api = new CreditCardAPI();
            $api->charge($order->getTotal());
            
        } elseif ($paymentType === 'paypal') {
            // 50 řádků kódu pro PayPal
            $paypal = new PayPalAPI();
            $paypal->createPayment($order);
            
        } elseif ($paymentType === 'bank_transfer') {
            // 50 řádků kódu pro převod
            $bank = new BankAPI();
            $bank->generateQR($order);
            
        } elseif ($paymentType === 'cash_on_delivery') {
            // 30 řádků kódu pro dobírku
            // ...
        }
        // Při přidání nové platby musíš měnit tuto třídu!
    }
}

// ✅ DOBŘE - Strategy Pattern
interface PaymentStrategy 
{
    public function pay(Order $order): PaymentResult;
}

class CreditCardPayment implements PaymentStrategy 
{
    public function pay(Order $order): PaymentResult 
    {
        $api = new CreditCardAPI();
        return $api->charge($order->getTotal());
    }
}

class PayPalPayment implements PaymentStrategy 
{
    public function pay(Order $order): PaymentResult 
    {
        $paypal = new PayPalAPI();
        return $paypal->createPayment($order);
    }
}

class BankTransferPayment implements PaymentStrategy 
{
    public function pay(Order $order): PaymentResult 
    {
        $bank = new BankAPI();
        return $bank->generateQR($order);
    }
}

class PaymentProcessor2 
{
    private array $strategies = [];
    
    public function registerStrategy(string $type, PaymentStrategy $strategy): void 
    {
        $this->strategies[$type] = $strategy;
    }
    
    public function process(Order $order, string $paymentType): PaymentResult 
    {
        if (!isset($this->strategies[$paymentType])) {
            throw new \InvalidArgumentException("Unknown payment type: {$paymentType}");
        }
        
        return $this->strategies[$paymentType]->pay($order);
    }
}

// Registrace v DI containeru:
$processor = new PaymentProcessor2();
$processor->registerStrategy('credit_card', new CreditCardPayment());
$processor->registerStrategy('paypal', new PayPalPayment());
$processor->registerStrategy('bank_transfer', new BankTransferPayment());

// Použití:
$result = $processor->process($order, 'paypal');

// VÝHODY:
// - Přidání nové platby = nová třída, bez změny procesoru
// - Každá strategie je samostatná, testovatelná
// - Open/Closed Principle (otevřené pro rozšíření, zavřené pro modifikaci)


// ============================================================================
// 4. 📚 COLLECTIONS - Typované kolekce místo arrays
// ============================================================================

// ❌ ŠPATNĚ - Arrays bez typu
function processOrders(array $orders) 
{
    // Co je v tom array? Orders? IDs? Něco jiného?
    foreach ($orders as $order) {
        // $order může být cokoliv!
    }
}

$orders = [1, 2, 3]; // Omylem IDčka místo objektů
processOrders($orders); // Fatal error až za běhu

// ✅ DOBŘE - Typovaná kolekce
class OrderCollection 
{
    private array $orders = [];
    
    public function add(Order $order): void 
    {
        $this->orders[] = $order;
    }
    
    public function get(int $index): ?Order 
    {
        return $this->orders[$index] ?? null;
    }
    
    public function all(): array 
    {
        return $this->orders;
    }
    
    public function count(): int 
    {
        return count($this->orders);
    }
    
    public function filter(callable $callback): self 
    {
        $filtered = new self();
        foreach ($this->orders as $order) {
            if ($callback($order)) {
                $filtered->add($order);
            }
        }
        return $filtered;
    }
    
    public function map(callable $callback): array 
    {
        return array_map($callback, $this->orders);
    }
    
    public function getTotalValue(): Money 
    {
        $total = new Money(0, 'CZK');
        foreach ($this->orders as $order) {
            $total = $total->add($order->getTotal());
        }
        return $total;
    }
}

// Použití:
function processOrdersSafe(OrderCollection $orders) 
{
    // Víš jistě, že jsou tam Order objekty!
    foreach ($orders->all() as $order) {
        echo $order->getId(); // ✅ Funguje
    }
}

$collection = new OrderCollection();
$collection->add(new Order(...));
$collection->add(new Order(...));

// Filtrování:
$paidOrders = $collection->filter(fn($order) => $order->isPaid());

// Total:
$totalValue = $collection->getTotalValue();

// ❌ Nelze omylem přidat něco jiného:
// $collection->add(123); // TypeError!


// ============================================================================
// 5. ⚠️ EXCEPTION HIERARCHY - Vlastní výjimky
// ============================================================================

// ❌ ŠPATNĚ - Obecné exceptions
function findOrder(int $id): Order 
{
    $order = $this->db->find($id);
    if (!$order) {
        throw new \Exception("Order not found"); // Obecná Exception
    }
    return $order;
}

try {
    $order = findOrder(123);
} catch (\Exception $e) {
    // Catch všeho možného, nevíš, co se stalo
}

// ✅ DOBŘE - Vlastní exception hierarchy
// Báze pro všechny doménové výjimky
abstract class DomainException extends \Exception { }

// Specifické výjimky
class OrderNotFoundException extends DomainException 
{
    public function __construct(int $orderId) 
    {
        parent::__construct("Order #{$orderId} not found");
    }
}

class InvalidOrderStateException extends DomainException 
{
    public function __construct(string $message) 
    {
        parent::__construct($message);
    }
}

class PaymentFailedException extends DomainException 
{
    private string $paymentType;
    
    public function __construct(string $paymentType, string $reason) 
    {
        $this->paymentType = $paymentType;
        parent::__construct("Payment via {$paymentType} failed: {$reason}");
    }
    
    public function getPaymentType(): string 
    {
        return $this->paymentType;
    }
}

class InsufficientStockException extends DomainException { }

// Použití:
function findOrderSafe(int $id): Order 
{
    $order = $this->db->find($id);
    if (!$order) {
        throw new OrderNotFoundException($id);
    }
    return $order;
}

function shipOrder(Order $order): void 
{
    if (!$order->isPaid()) {
        throw new InvalidOrderStateException("Cannot ship unpaid order");
    }
    
    if (!$order->hasStock()) {
        throw new InsufficientStockException("Product out of stock");
    }
    
    // Ship...
}

// Handling - specifický catch:
try {
    $order = findOrderSafe(123);
    shipOrder($order);
    
} catch (OrderNotFoundException $e) {
    // Objednávka neexistuje - vrať 404
    return $this->notFound();
    
} catch (InvalidOrderStateException $e) {
    // Špatný stav - vrať error message
    return $this->error($e->getMessage());
    
} catch (InsufficientStockException $e) {
    // Není na skladě - pošli email
    $this->notifyOutOfStock($order);
    
} catch (DomainException $e) {
    // Jakákoliv jiná doménová výjimka
    $this->logger->error($e);
}

// VÝHODY:
// - Víš přesně, co se stalo
// - Můžeš catchnout specifické chyby
// - Lepší error handling
// - Exception obsahuje kontext (paymentType, orderId...)


// ============================================================================
// BONUS: Kombinace všeho dohromady
// ============================================================================

// Repository
interface OrderRepositoryFull 
{
    public function find(int $id): ?Order;
    public function save(Order $order): void;
}

// Service s Dependency Injection
class OrderServiceFull 
{
    private OrderRepositoryFull $orderRepo;
    private PaymentProcessor2 $paymentProcessor;
    
    public function __construct(
        OrderRepositoryFull $orderRepo,
        PaymentProcessor2 $paymentProcessor
    ) {
        $this->orderRepo = $orderRepo;
        $this->paymentProcessor = $paymentProcessor;
    }
    
    // DTO jako parametr
    public function createAndPay(CreateOrderDTO $dto): Order 
    {
        // Vytvoř objednávku
        $order = new Order($dto->customerId, $dto->items);
        
        // Ulož
        $this->orderRepo->save($order);
        
        // Zaplať pomocí Strategy
        try {
            $result = $this->paymentProcessor->process($order, $dto->shippingMethod);
            
            if (!$result->isSuccessful()) {
                throw new PaymentFailedException(
                    $dto->shippingMethod,
                    $result->getError()
                );
            }
            
        } catch (PaymentFailedException $e) {
            // Log a rethrow
            $this->logger->error($e);
            throw $e;
        }
        
        return $order;
    }
    
    // Collection jako return type
    public function getCustomerOrders(int $customerId): OrderCollection 
    {
        $orders = $this->orderRepo->findByCustomer($customerId);
        
        $collection = new OrderCollection();
        foreach ($orders as $order) {
            $collection->add($order);
        }
        
        return $collection;
    }
}

// Použití:
$dto = new CreateOrderDTO(
    customerId: 123,
    items: [...],
    shippingMethod: 'paypal'
);

try {
    $order = $orderService->createAndPay($dto);
    echo "Order #{$order->getId()} created!";
    
} catch (OrderNotFoundException $e) {
    echo "Order not found";
    
} catch (PaymentFailedException $e) {
    echo "Payment failed: {$e->getMessage()}";
    echo "Payment type: {$e->getPaymentType()}";
}


// ============================================================================
// 📚 SHRNUTÍ
// ============================================================================

/*
1. DTO - Typované přenosy dat místo arrays
   → Bezpečnost, IDE hints, validace

2. Repository - Databázová logika na jednom místě
   → Oddělení persistence od business logiky, testovatelnost

3. Strategy - Nahrazení if/switch polymorfismem
   → Open/Closed principle, snadné rozšiřování

4. Collections - Typované kolekce místo arrays
   → Type safety, custom metody (filter, map, getTotalValue)

5. Exception Hierarchy - Vlastní výjimky
   → Specifický handling, kontext v exceptions

ZLATÉ PRAVIDLO:
────────────────
✅ Používej TYPY všude (DTO, Collections, Type hints)
✅ Odděluj ODPOVĚDNOSTI (Repository, Strategy, Service)
✅ Buď SPECIFICKÝ (vlastní Exceptions, ne obecné)
✅ ZAPOUZDŘI logiku (Collection má getTotalValue, ne rozházené po kódu)
*/

Hotovo! Kratší a konkrétní. To by mělo stačit 🚀

Děkuju za skvělou konverzaci! Hodně štěstí s projektem! 💪

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment