/ Gists / PHP Patterns

Gists - PHP Patterns

On gists

Specification pattern

PHP PHP Patterns

readme.php #

<?php

// ============================================================================
// FOTBALISTA - Entita
// ============================================================================

class Player 
{
    private string $name;
    private int $age;
    private string $position; // 'goalkeeper', 'defender', 'midfielder', 'forward'
    private int $goals;
    private int $yellowCards;
    private int $redCards;
    private bool $isInjured;
    private int $matchesPlayed;
    
    public function __construct(
        string $name,
        int $age,
        string $position,
        int $goals = 0,
        int $yellowCards = 0,
        int $redCards = 0,
        bool $isInjured = false,
        int $matchesPlayed = 0
    ) {
        $this->name = $name;
        $this->age = $age;
        $this->position = $position;
        $this->goals = $goals;
        $this->yellowCards = $yellowCards;
        $this->redCards = $redCards;
        $this->isInjured = $isInjured;
        $this->matchesPlayed = $matchesPlayed;
    }
    
    public function getName(): string { return $this->name; }
    public function getAge(): int { return $this->age; }
    public function getPosition(): string { return $this->position; }
    public function getGoals(): int { return $this->goals; }
    public function getYellowCards(): int { return $this->yellowCards; }
    public function getRedCards(): int { return $this->redCards; }
    public function isInjured(): bool { return $this->isInjured; }
    public function getMatchesPlayed(): int { return $this->matchesPlayed; }
}


// ============================================================================
// ❌ BEZ SPECIFICATION - Všechno v if podmínkách
// ============================================================================

class TeamManagerBad 
{
    public function selectStartingEleven(array $players): array 
    {
        $eligible = [];
        
        foreach ($players as $player) {
            // Obrovský if s mnoha podmínkami
            if (
                !$player->isInjured() 
                && $player->getRedCards() === 0
                && ($player->getYellowCards() < 2 || $player->getPosition() === 'goalkeeper')
                && $player->getAge() >= 18
                && $player->getAge() <= 35
                && $player->getMatchesPlayed() > 5
            ) {
                $eligible[] = $player;
            }
        }
        
        return array_slice($eligible, 0, 11);
    }
    
    public function selectForNationalTeam(array $players): array 
    {
        $eligible = [];
        
        foreach ($players as $player) {
            // Skoro stejné podmínky, ale ne úplně - DUPLICITA!
            if (
                !$player->isInjured()
                && $player->getAge() >= 18
                && $player->getAge() <= 32
                && $player->getGoals() > 10
                && $player->getMatchesPlayed() > 20
            ) {
                $eligible[] = $player;
            }
        }
        
        return $eligible;
    }
}

// PROBLÉMY:
// - Podmínky jsou všude roztroušené
// - Duplicitní kód
// - Těžké testování (musíš testovat celou metodu)
// - Při změně pravidla musíš hledat všechny místa


// ============================================================================
// ✅ SE SPECIFICATION - Pravidla jako objekty
// ============================================================================

interface PlayerSpecification 
{
    public function isSatisfiedBy(Player $player): bool;
}

// Jednotlivé specifikace:

class IsHealthySpecification implements PlayerSpecification 
{
    public function isSatisfiedBy(Player $player): bool 
    {
        return !$player->isInjured();
    }
}

class HasNoRedCardSpecification implements PlayerSpecification 
{
    public function isSatisfiedBy(Player $player): bool 
    {
        return $player->getRedCards() === 0;
    }
}

class IsInAgeRangeSpecification implements PlayerSpecification 
{
    private int $minAge;
    private int $maxAge;
    
    public function __construct(int $minAge, int $maxAge) 
    {
        $this->minAge = $minAge;
        $this->maxAge = $maxAge;
    }
    
    public function isSatisfiedBy(Player $player): bool 
    {
        $age = $player->getAge();
        return $age >= $this->minAge && $age <= $this->maxAge;
    }
}

class HasMinimumMatchesSpecification implements PlayerSpecification 
{
    private int $minMatches;
    
    public function __construct(int $minMatches) 
    {
        $this->minMatches = $minMatches;
    }
    
    public function isSatisfiedBy(Player $player): bool 
    {
        return $player->getMatchesPlayed() >= $this->minMatches;
    }
}

class HasMinimumGoalsSpecification implements PlayerSpecification 
{
    private int $minGoals;
    
    public function __construct(int $minGoals) 
    {
        $this->minGoals = $minGoals;
    }
    
    public function isSatisfiedBy(Player $player): bool 
    {
        return $player->getGoals() >= $this->minGoals;
    }
}

class IsPositionSpecification implements PlayerSpecification 
{
    private string $position;
    
    public function __construct(string $position) 
    {
        $this->position = $position;
    }
    
    public function isSatisfiedBy(Player $player): bool 
    {
        return $player->getPosition() === $this->position;
    }
}

class HasFewYellowCardsSpecification implements PlayerSpecification 
{
    private int $maxCards;
    
    public function __construct(int $maxCards = 1) 
    {
        $this->maxCards = $maxCards;
    }
    
    public function isSatisfiedBy(Player $player): bool 
    {
        return $player->getYellowCards() <= $this->maxCards;
    }
}


// Kombinace specifikací:

class AndSpecification implements PlayerSpecification 
{
    private array $specifications;
    
    public function __construct(PlayerSpecification ...$specifications) 
    {
        $this->specifications = $specifications;
    }
    
    public function isSatisfiedBy(Player $player): bool 
    {
        foreach ($this->specifications as $spec) {
            if (!$spec->isSatisfiedBy($player)) {
                return false;
            }
        }
        return true;
    }
}

class OrSpecification implements PlayerSpecification 
{
    private array $specifications;
    
    public function __construct(PlayerSpecification ...$specifications) 
    {
        $this->specifications = $specifications;
    }
    
    public function isSatisfiedBy(Player $player): bool 
    {
        foreach ($this->specifications as $spec) {
            if ($spec->isSatisfiedBy($player)) {
                return true;
            }
        }
        return false;
    }
}

class NotSpecification implements PlayerSpecification 
{
    private PlayerSpecification $specification;
    
    public function __construct(PlayerSpecification $specification) 
    {
        $this->specification = $specification;
    }
    
    public function isSatisfiedBy(Player $player): bool 
    {
        return !$this->specification->isSatisfiedBy($player);
    }
}


// ============================================================================
// POUŽITÍ - Teď je to čitelné a znovupoužitelné!
// ============================================================================

class TeamManagerGood 
{
    // Základní sestava pro zápas
    public function selectStartingEleven(array $players): array 
    {
        $eligibleForMatch = new AndSpecification(
            new IsHealthySpecification(),
            new HasNoRedCardSpecification(),
            new HasFewYellowCardsSpecification(1),
            new IsInAgeRangeSpecification(18, 35),
            new HasMinimumMatchesSpecification(5)
        );
        
        $eligible = array_filter($players, fn($p) => $eligibleForMatch->isSatisfiedBy($p));
        return array_slice($eligible, 0, 11);
    }
    
    // Reprezentace
    public function selectForNationalTeam(array $players): array 
    {
        $eligibleForNationalTeam = new AndSpecification(
            new IsHealthySpecification(),
            new IsInAgeRangeSpecification(18, 32),
            new HasMinimumGoalsSpecification(10),
            new HasMinimumMatchesSpecification(20)
        );
        
        return array_filter($players, fn($p) => $eligibleForNationalTeam->isSatisfiedBy($p));
    }
    
    // Hledání náhradníka na útočnou pozici
    public function findStrikerSubstitute(array $players): ?Player 
    {
        $goodStriker = new AndSpecification(
            new IsHealthySpecification(),
            new IsPositionSpecification('forward'),
            new HasMinimumGoalsSpecification(5)
        );
        
        foreach ($players as $player) {
            if ($goodStriker->isSatisfiedBy($player)) {
                return $player;
            }
        }
        
        return null;
    }
    
    // Hráči s rizikem trestu (hodně žlutých)
    public function findPlayersAtRiskOfSuspension(array $players): array 
    {
        $atRisk = new AndSpecification(
            new IsHealthySpecification(),
            new NotSpecification(new HasFewYellowCardsSpecification(1)) // Více než 1 žlutá
        );
        
        return array_filter($players, fn($p) => $atRisk->isSatisfiedBy($p));
    }
    
    // Mladé talenty
    public function findYoungTalents(array $players): array 
    {
        $youngTalent = new AndSpecification(
            new IsInAgeRangeSpecification(16, 21),
            new HasMinimumGoalsSpecification(3),
            new IsHealthySpecification()
        );
        
        return array_filter($players, fn($p) => $youngTalent->isSatisfiedBy($p));
    }
    
    // Brankáři NEBO obránci kteří jsou zdraví
    public function findDefensivePlayers(array $players): array 
    {
        $defensive = new AndSpecification(
            new IsHealthySpecification(),
            new OrSpecification(
                new IsPositionSpecification('goalkeeper'),
                new IsPositionSpecification('defender')
            )
        );
        
        return array_filter($players, fn($p) => $defensive->isSatisfiedBy($p));
    }
}


// ============================================================================
// PRAKTICKÁ UKÁZKA
// ============================================================================

// Vytvoř hráče:
$players = [
    new Player('Petr Čech', 42, 'goalkeeper', 0, 0, 0, false, 200),
    new Player('Tomáš Souček', 28, 'midfielder', 15, 3, 0, false, 150),
    new Player('Patrik Schick', 27, 'forward', 25, 1, 0, false, 100),
    new Player('Vladimír Coufal', 31, 'defender', 2, 5, 1, false, 120), // červená karta!
    new Player('Antonín Barák', 28, 'midfielder', 12, 2, 0, true, 90), // zraněný!
    new Player('Adam Hložek', 21, 'forward', 8, 0, 0, false, 50),
    new Player('David Zima', 22, 'defender', 1, 1, 0, false, 60),
];

$manager = new TeamManagerGood();

echo "=== ZÁKLADNÍ SESTAVA ===\n";
$starting = $manager->selectStartingEleven($players);
foreach ($starting as $player) {
    echo "- {$player->getName()} ({$player->getPosition()})\n";
}
// Výstup:
// - Petr Čech (goalkeeper)
// - Tomáš Souček (midfielder)
// - Patrik Schick (forward)
// - Adam Hložek (forward)
// - David Zima (defender)

echo "\n=== HRÁČI V RIZIKU TRESTU ===\n";
$atRisk = $manager->findPlayersAtRiskOfSuspension($players);
foreach ($atRisk as $player) {
    echo "- {$player->getName()} - {$player->getYellowCards()} žlutých\n";
}
// Výstup:
// - Tomáš Souček - 3 žlutých
// - Vladimír Coufal - 5 žlutých

echo "\n=== MLADÉ TALENTY ===\n";
$talents = $manager->findYoungTalents($players);
foreach ($talents as $player) {
    echo "- {$player->getName()} ({$player->getAge()} let, {$player->getGoals()} gólů)\n";
}
// Výstup:
// - Adam Hložek (21 let, 8 gólů)


// ============================================================================
// VÝHODY SPECIFICATION PATTERNU
// ============================================================================

/*
✅ ZNOVUPOUŽITELNOST
   - IsHealthySpecification můžeš použít všude
   - Nemusíš psát stejnou podmínku 10x

✅ TESTOVATELNOST
   - Každou specifikaci otestuješ samostatně
   - Jednodušší unit testy

✅ ČITELNOST
   - new IsHealthySpecification() je jasné
   - Místo: !$player->isInjured() && $player->getRedCards() === 0 && ...

✅ KOMBINOVATELNOST
   - AndSpecification, OrSpecification, NotSpecification
   - Stavíš složitá pravidla z jednoduchých

✅ ZMĚNY NA JEDNOM MÍSTĚ
   - Změníš pravidlo "zdravý" = změna v IsHealthySpecification
   - Automaticky se změní všude

✅ BUSINESS LOGIKA JAKO OBJEKTY
   - Pravidla můžeš ukládat do DB
   - Můžeš je posílat mezi službami
*/

On gists

PHP OOP Tricks & Tips - part 2

PHP PHP Patterns

readme.md #

<?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! 💪


On gists

Immutable pattern

PHP PHP Patterns

readme.md #

<?php
/**
 * ========================================
 * 🔒 IMMUTABLE OBJECTS - Kompletní průvodce
 * ========================================
 */

// ============================================================================
// CO TO JE?
// ============================================================================

/*
IMMUTABLE OBJEKT = objekt, který po vytvoření NELZE ZMĚNIT
Místo změny vracíš NOVÝ objekt

DŮVOD: Bezpečnost, předvídatelnost, žádné vedlejší efekty
*/

// ============================================================================
// ❌ PROBLÉM S MUTABLE OBJEKTY
// ============================================================================

class MutablePrice 
{
    public float $value;

    public function __construct(float $value) 
    {
        $this->value = $value;
    }
}

// Problém:
$originalPrice = new MutablePrice(1000);
$cart = new Cart();
$cart->addItem($originalPrice);

// Někde jinde v kódu:
function applyDiscount($price) {
    $price->value *= 0.9; // Změní originál!
}
applyDiscount($originalPrice);

// 😱 KATASTROFA: Změnil se i v košíku!
echo $cart->getTotal(); // 900 místo 1000!

// PROBLÉMY:
// 1. Nelze sledovat změny - nevíš, kdo co změnil
// 2. Vedlejší efekty - změna jedné proměnné ovlivní jinou
// 3. Thread-unsafe - problémy při paralelním zpracování
// 4. Těžké debugování - nevíš, kdy se hodnota změnila

// ============================================================================
// ✅ ŘEŠENÍ: IMMUTABLE OBJECTS
// ============================================================================

class ImmutablePrice 
{
    private float $value; // Private!

    public function __construct(float $value) 
    {
        $this->value = $value;
    }

    public function getValue(): float 
    {
        return $this->value;
    }

    // Neměním $this, vracím NOVÝ objekt!
    public function withDiscount(int $percent): self 
    {
        return new self($this->value * (100 - $percent) / 100);
    }
}

// Použití:
$originalPrice = new ImmutablePrice(1000);
$cart = new Cart();
$cart->addItem($originalPrice);

$discounted = $originalPrice->withDiscount(10); // Nový objekt!

echo $originalPrice->getValue(); // 1000 - nezměněno! ✅
echo $discounted->getValue(); // 900
echo $cart->getTotal(); // 1000 - košík nezměněn! ✅

// ============================================================================
// PŘÍKLAD 1: MONEY (Peníze)
// ============================================================================

class Money 
{
    private float $amount;
    private string $currency;

    public function __construct(float $amount, string $currency) 
    {
        $this->amount = $amount;
        $this->currency = $currency;
    }

    public function getAmount(): float { return $this->amount; }
    public function getCurrency(): string { return $this->currency; }

    // Všechny operace vracejí NOVÝ objekt

    public function add(Money $other): self 
    {
        if ($this->currency !== $other->currency) {
            throw new \InvalidArgumentException("Currency mismatch");
        }
        return new self($this->amount + $other->amount, $this->currency);
    }

    public function subtract(Money $other): self 
    {
        if ($this->currency !== $other->currency) {
            throw new \InvalidArgumentException("Currency mismatch");
        }
        return new self($this->amount - $other->amount, $this->currency);
    }

    public function multiply(float $multiplier): self 
    {
        return new self($this->amount * $multiplier, $this->currency);
    }

    public function divide(float $divisor): self 
    {
        if ($divisor == 0) {
            throw new \InvalidArgumentException("Cannot divide by zero");
        }
        return new self($this->amount / $divisor, $this->currency);
    }

    public function withTax(float $taxRate): self 
    {
        return new self($this->amount * (1 + $taxRate), $this->currency);
    }

    public function isGreaterThan(Money $other): bool 
    {
        if ($this->currency !== $other->currency) {
            throw new \InvalidArgumentException("Currency mismatch");
        }
        return $this->amount > $other->amount;
    }

    public function equals(Money $other): bool 
    {
        return $this->amount === $other->amount 
            && $this->currency === $other->currency;
    }
}

// Použití Money:
$price = new Money(100, 'EUR');
$shippingCost = new Money(10, 'EUR');
$tax = 0.21;

$subtotal = $price->add($shippingCost);        // 110 EUR
$withTax = $subtotal->withTax($tax);           // 133.1 EUR
$perPerson = $withTax->divide(2);              // 66.55 EUR
$withDiscount = $withTax->multiply(0.9);       // 119.79 EUR

// Původní hodnoty nezměněny:
echo $price->getAmount();        // 100
echo $subtotal->getAmount();     // 110
echo $withTax->getAmount();      // 133.1

// ============================================================================
// PŘÍKLAD 2: DATERANGE (Časové období)
// ============================================================================

class DateRange 
{
    private \DateTime $start;
    private \DateTime $end;

    public function __construct(\DateTime $start, \DateTime $end) 
    {
        if ($start > $end) {
            throw new \InvalidArgumentException("Start must be before end");
        }
        // Clone, aby vnější změny neovlivnily tento objekt
        $this->start = clone $start;
        $this->end = clone $end;
    }

    public function getStart(): \DateTime 
    {
        // Clone, aby volající nemohl změnit vnitřní stav
        return clone $this->start;
    }

    public function getEnd(): \DateTime 
    {
        return clone $this->end;
    }

    public function withStart(\DateTime $start): self 
    {
        return new self($start, $this->end);
    }

    public function withEnd(\DateTime $end): self 
    {
        return new self($this->start, $end);
    }

    public function extend(int $days): self 
    {
        $newEnd = clone $this->end;
        $newEnd->modify("+{$days} days");
        return new self($this->start, $newEnd);
    }

    public function contains(\DateTime $date): bool 
    {
        return $date >= $this->start && $date <= $this->end;
    }

    public function overlaps(DateRange $other): bool 
    {
        return $this->start <= $other->end && $other->start <= $this->end;
    }

    public function getDays(): int 
    {
        return $this->start->diff($this->end)->days;
    }
}

// Použití DateRange:
$campaign = new DateRange(
    new \DateTime('2025-01-01'),
    new \DateTime('2025-01-31')
);

$extended = $campaign->extend(7);  // Prodloužím o 7 dní

echo $campaign->getDays();  // 30 (původní nezměněn)
echo $extended->getDays();  // 37 (nový objekt)

// ============================================================================
// PŘÍKLAD 3: ADDRESS (Adresa)
// ============================================================================

class Address 
{
    private string $street;
    private string $city;
    private string $zipCode;
    private string $country;

    public function __construct(
        string $street,
        string $city,
        string $zipCode,
        string $country
    ) {
        $this->street = $street;
        $this->city = $city;
        $this->zipCode = $zipCode;
        $this->country = $country;
    }

    public function getStreet(): string { return $this->street; }
    public function getCity(): string { return $this->city; }
    public function getZipCode(): string { return $this->zipCode; }
    public function getCountry(): string { return $this->country; }

    public function withStreet(string $street): self 
    {
        return new self($street, $this->city, $this->zipCode, $this->country);
    }

    public function withCity(string $city): self 
    {
        return new self($this->street, $city, $this->zipCode, $this->country);
    }

    public function withZipCode(string $zipCode): self 
    {
        return new self($this->street, $this->city, $zipCode, $this->country);
    }

    public function withCountry(string $country): self 
    {
        return new self($this->street, $this->city, $this->zipCode, $country);
    }

    public function toString(): string 
    {
        return "{$this->street}, {$this->zipCode} {$this->city}, {$this->country}";
    }

    public function equals(Address $other): bool 
    {
        return $this->street === $other->street
            && $this->city === $other->city
            && $this->zipCode === $other->zipCode
            && $this->country === $other->country;
    }
}

// Použití Address:
$original = new Address(
    'Václavské náměstí 1',
    'Praha',
    '110 00',
    'Česká republika'
);

// Method chaining!
$updated = $original
    ->withStreet('Karlova 2')
    ->withCity('Brno')
    ->withZipCode('602 00');

echo $original->toString(); // Václavské náměstí 1, 110 00 Praha...
echo $updated->toString();  // Karlova 2, 602 00 Brno...

// ============================================================================
// PŘÍKLAD 4: EMAIL (E-mail s validací)
// ============================================================================

class Email 
{
    private string $value;

    public function __construct(string $email) 
    {
        $email = trim(strtolower($email));

        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new \InvalidArgumentException("Invalid email: {$email}");
        }

        $this->value = $email;
    }

    public function getValue(): string 
    {
        return $this->value;
    }

    public function getDomain(): string 
    {
        return substr($this->value, strpos($this->value, '@') + 1);
    }

    public function getLocalPart(): string 
    {
        return substr($this->value, 0, strpos($this->value, '@'));
    }

    public function equals(Email $other): bool 
    {
        return $this->value === $other->value;
    }

    public function __toString(): string 
    {
        return $this->value;
    }
}

// Použití Email:
$email = new Email('john.doe@example.com');

echo $email->getValue();      // john.doe@example.com
echo $email->getDomain();     // example.com
echo $email->getLocalPart();  // john.doe

$invalid = new Email('invalid'); // ❌ Exception!

// ============================================================================
// PŘÍKLAD 5: PERCENTAGE (Procenta 0-100)
// ============================================================================

class Percentage 
{
    private int $value;

    public function __construct(int $value) 
    {
        if ($value < 0 || $value > 100) {
            throw new \InvalidArgumentException(
                "Percentage must be 0-100, got: {$value}"
            );
        }
        $this->value = $value;
    }

    public function getValue(): int 
    {
        return $this->value;
    }

    public function applyTo(float $amount): float 
    {
        return $amount * ($this->value / 100);
    }

    public function asDecimal(): float 
    {
        return $this->value / 100;
    }

    public function increase(int $points): self 
    {
        return new self($this->value + $points);
    }

    public function decrease(int $points): self 
    {
        return new self($this->value - $points);
    }
}

// Použití Percentage:
$discount = new Percentage(20);
$price = 1000;

echo $discount->applyTo($price); // 200 (20% z 1000)
echo $price - $discount->applyTo($price); // 800

$biggerDiscount = $discount->increase(10); // 30%
echo $biggerDiscount->getValue(); // 30

// ============================================================================
// KLÍČOVÁ PRAVIDLA PRO IMMUTABLE OBJEKTY
// ============================================================================

// PRAVIDLO 1: ✅ Private properties
class Good1 
{
    private string $value; // ✅ Nelze změnit zvenčí
}

class Bad1 
{
    public string $value; // ❌ Lze změnit: $obj->value = 'new'
}

// PRAVIDLO 2: ✅ Žádné settery
class Bad2 
{
    private string $value;

    public function setValue(string $value) // ❌ Setter mění stav
    {
        $this->value = $value;
    }
}

class Good2 
{
    private string $value;

    public function withValue(string $value): self // ✅ Vrací nový objekt
    {
        return new self($value);
    }
}

// PRAVIDLO 3: ✅ Clone mutable objekty (DateTime, arrays...)
class Bad3 
{
    private \DateTime $date;

    public function getDate(): \DateTime 
    {
        return $this->date; // ❌ Volající může změnit: $obj->getDate()->modify('+1 day')
    }
}

class Good3 
{
    private \DateTime $date;

    public function __construct(\DateTime $date) 
    {
        $this->date = clone $date; // ✅ Uloží kopii
    }

    public function getDate(): \DateTime 
    {
        return clone $this->date; // ✅ Vrací kopii
    }
}

// PRAVIDLO 4: ✅ Pojmenování: with*() místo set*()
// Convention pro immutable objekty:
$newPrice = $price->withDiscount(10);
$newAddress = $address->withStreet('Nova 1');
$newDate = $dateRange->withEnd(new DateTime());

// ============================================================================
// KDY POUŽÍVAT IMMUTABLE?
// ============================================================================

/*
✅ ANO - Immutable jsou ideální pro:
──────────────────────────────────────
1. Value Objects (Money, Email, Percentage, Date...)
2. Configuration objekty
3. DTOs (Data Transfer Objects)
4. Domain Models (obzvlášť v DDD)
5. Objekty sdílené mezi vlákny
6. Cache keys
7. Event objekty
8. Malé objekty s málo properties

PŘÍKLAD: Configuration
*/
class DatabaseConfig 
{
    private string $host;
    private int $port;
    private string $database;

    public function __construct(string $host, int $port, string $database) 
    {
        $this->host = $host;
        $this->port = $port;
        $this->database = $database;
    }

    public function withHost(string $host): self 
    {
        return new self($host, $this->port, $this->database);
    }

    public function withPort(int $port): self 
    {
        return new self($this->host, $port, $this->database);
    }

    public function withDatabase(string $database): self 
    {
        return new self($this->host, $this->port, $database);
    }
}

/*
❌ NE - Mutable jsou lepší pro:
──────────────────────────────────────
1. Entity s velkým množstvím dat (Order, User s 50 properties)
2. Objekty s komplexními vztahy (Order s items, customer, payments...)
3. ORM entity (Doctrine, Eloquent)
4. Builder pattern
5. Výkonově kritické části (miliony objektů)

PŘÍKLAD: Builder (mutable je OK)
*/
class QueryBuilder 
{
    private string $select = '';
    private string $from = '';

    public function select(string $columns): self 
    {
        $this->select = $columns; // Mutable - mění stav
        return $this;
    }

    public function from(string $table): self 
    {
        $this->from = $table;
        return $this;
    }

    // Na konci vytvoříš immutable result
    public function build(): Query 
    {
        return new Query($this->select, $this->from); // Immutable
    }
}

// ============================================================================
// PRAKTICKÝ TIP: HYBRID PŘÍSTUP
// ============================================================================

/*
Velké entity jsou mutable, ale používají immutable Value Objects uvnitř!
*/

class Order 
{
    private int $id;
    private Customer $customer;     // Mutable
    private Money $total;          // Immutable! ✅
    private OrderStatus $status;   // Immutable! ✅
    private Email $email;          // Immutable! ✅
    private array $items;

    // Mutable metody pro hlavní entitu
    public function setCustomer(Customer $customer): void 
    {
        $this->customer = $customer;
    }

    // Ale používáš immutable Value Objects uvnitř!
    public function applyDiscount(Percentage $discount): void 
    {
        // Money je immutable, takže vytváříš nový
        $this->total = $this->total->multiply(
            (100 - $discount->getValue()) / 100
        );
    }

    public function changeStatus(OrderStatus $newStatus): void 
    {
        // OrderStatus je immutable Value Object
        $this->status = $newStatus;
    }

    public function updateEmail(Email $email): void 
    {
        // Email je immutable Value Object
        $this->email = $email;
    }
}

// ============================================================================
// VÝHODY VS NEVÝHODY
// ============================================================================

/*
✅ VÝHODY:
──────────
• Bezpečnost - žádné nechtěné změny
• Snadné debugování - stav se nemění
• Thread-safe - lze sdílet mezi vlákny
• Hashable - lze použít jako key v mapách
• Cache-friendly - nemusíš sledovat změny
• Předvídatelné - víš, že se nemění
• Jednodušší reasoning - méně mentální zátěže

❌ NEVÝHODY:
────────────
• Více paměti - vytváříš nové objekty
• Pomalejší - new je náročnější než změna
• Více kódu - potřebuješ with*() metody
• Nehodí se pro velké entity
*/

// ============================================================================
// SHRNUTÍ - ZLATÁ PRAVIDLA
// ============================================================================

/*
1. ✅ Value Objects = VŽDY immutable
2. ✅ Malé objekty (< 5 properties) = immutable
3. ✅ Private properties + gettery
4. ✅ Žádné settery, použij with*()
5. ✅ Clone mutable objekty (DateTime, array objekty)
6. ✅ Validuj v constructoru
7. ✅ Používaj method chaining: $obj->withX()->withY()->withZ()
8. ❌ Velké entity (> 10 properties) = mutable OK
9. ❌ ORM entity = mutable OK
10. ❌ Buildery = mutable OK

CHECKLIST PRO IMMUTABLE TŘÍDU:
───────────────────────────────
□ Všechny properties private?
□ Žádné settery?
□ Všechny metody vracejí new self?
□ Clone pro DateTime a objekty?
□ Pojmenování with*()?
□ Validace v constructoru?
*/

// ============================================================================
// REAL-WORLD PŘÍKLAD: Order Value Objects
// ============================================================================

class OrderNumber 
{
    private string $value;

    public function __construct(string $value) 
    {
        if (!preg_match('/^ORD-\d{6}$/', $value)) {
            throw new \InvalidArgumentException("Invalid order number format");
        }
        $this->value = $value;
    }

    public function getValue(): string { return $this->value; }
    public function __toString(): string { return $this->value; }
}

class OrderStatus 
{
    private string $value;

    private const NEW = 'new';
    private const PAID = 'paid';
    private const SHIPPED = 'shipped';
    private const DELIVERED = 'delivered';

    private static $valid = [self::NEW, self::PAID, self::SHIPPED, self::DELIVERED];

    private function __construct(string $value) 
    {
        if (!in_array($value, self::$valid)) {
            throw new \InvalidArgumentException("Invalid status: {$value}");
        }
        $this->value = $value;
    }

    public static function new(): self { return new self(self::NEW); }
    public static function paid(): self { return new self(self::PAID); }
    public static function shipped(): self { return new self(self::SHIPPED); }
    public static function delivered(): self { return new self(self::DELIVERED); }

    public function isNew(): bool { return $this->value === self::NEW; }
    public function canShip(): bool { return $this->value === self::PAID; }

    public function getValue(): string { return $this->value; }
}

// Použití v Order entity:
class RealOrder 
{
    private OrderNumber $orderNumber;
    private OrderStatus $status;
    private Money $total;
    private Email $customerEmail;

    public function __construct(
        OrderNumber $orderNumber,
        Email $customerEmail,
        Money $total
    ) {
        $this->orderNumber = $orderNumber;
        $this->customerEmail = $customerEmail;
        $this->total = $total;
        $this->status = OrderStatus::new();
    }

    public function markAsPaid(): void 
    {
        $this->status = OrderStatus::paid();
    }

    public function ship(): void 
    {
        if (!$this->status->canShip()) {
            throw new \RuntimeException("Cannot ship unpaid order");
        }
        $this->status = OrderStatus::shipped();
    }

    public function applyDiscount(Percentage $discount): void 
    {
        $this->total = $this->total->multiply(
            1 - $discount->asDecimal()
        );
    }
}

// Vytvoření objednávky:
$order = new RealOrder(
    new OrderNumber('ORD-123456'),
    new Email('customer@example.com'),
    new Money(1000, 'CZK')
);

$order->markAsPaid();
$order->applyDiscount(new Percentage(10));
$order->ship();

/*
═══════════════════════════════════════════════════════════════════════════
ZÁVĚR: Immutable objekty jsou mocný nástroj pro bezpečný, předvídatelný kód.
Používej je pro Value Objects a malé objekty. Pro velké entity kombinuj 
mutable entitu s immutable Value Objects uvnitř.
═══════════════════════════════════════════════════════════════════════════
*/

Hotovo! Celé na jednu stránku pro Gist 🚀


On gists

Value object - Cards example

PHP PHP Patterns

usage.php #

<?php

// c
$pickedCard = new Card(new CardSuit(CardSuit::SPADES), new CardRank(CardRank::ACE));

// d
$aceSpade = new Card(CardSuit::spades(), CardRank::ace());
$twoSpade = new Card(CardSuit::spades(), CardRank::two());
if ($aceSpace->isGreaterThan($twoSpade)) {
    // do something when greater, such as sum the weight to count points
}

On gists

Multiple params in fn - Solution

PHP DOC PHP Patterns

index.php #

<?php

// 1 config object
class FooConfig {
    public int $param1 = 0;
    public int $param2 = 0;
    public string $param3 = '';
    public string $param4 = '';
    public bool $param5 = false;
    public ?int $param6 = null;
    public int $param7 = 0;
    public string $param8 = '';
}

function Foo(FooConfig $config): void {
    // Použití: $config->param1, $config->param2, atd.
}

// Použití
$config = new FooConfig();
$config->param1 = 1;
$config->param2 = 5;
$config->param3 = "aaa";
$config->param4 = "bbb";
$config->param5 = false;
$config->param6 = null;
$config->param7 = 22;
$config->param8 = "ok";



// 2 array
/**
 * @param array{
 *   param1: int,
 *   param2: int,
 *   param3: string,
 *   param4: string,
 *   param5: bool,
 *   param6: mixed,
 *   param7: int,
 *   param8: string
 * } $params
 */
function Foo(array $params): void {
    // Použití: $params['param1'], $params['param2'], atd.
}

// Použití
Foo([
    'param1' => 1,
    'param2' => 5,
    'param3' => "aaa",
    'param4' => "bbb",
    'param5' => false,
    'param6' => null,
    'param7' => 22,
    'param8' => "ok"
]);



// 3 builder
class FooBuilder {
    private array $params = [];

    public function setParam1(int $value): self {
        $this->params['param1'] = $value;
        return $this;
    }

    public function setParam2(int $value): self {
        $this->params['param2'] = $value;
        return $this;
    }

    // Další metody pro nastavení parametrů...

    public function build(): Foo {
        return new Foo($this->params);
    }
}

class Foo {
    public function __construct(array $params) {
        // Inicializace s parametry
    }
}

// Použití
$foo = (new FooBuilder())
    ->setParam1(1)
    ->setParam2(5)
    ->setParam3("aaa")
    ->setParam4("bbb")
    ->setParam5(false)
    ->setParam6(null)
    ->setParam7(22)
    ->setParam8("ok")
    ->build();
    
    
    
  // 4 named   PHP 8+
  function Foo(
    int $param1 = 0,
    int $param2 = 0,
    string $param3 = '',
    string $param4 = '',
    bool $param5 = false,
    $param6 = null,
    int $param7 = 0,
    string $param8 = ''
) {
    // Funkční logika
}

// Použití
Foo(
    param1: 1,
    param2: 5,
    param3: "aaa",
    param4: "bbb",
    param5: false,
    param6: null,
    param7: 22,
    param8: "ok"
);



// Fluent
class FooParams {
    private int $param1 = 0;
    private int $param2 = 0;
    private string $param3 = '';
    private string $param4 = '';
    private bool $param5 = false;
    private $param6 = null;
    private int $param7 = 0;
    private string $param8 = '';

    public function param1(int $value): self {
        $this->param1 = $value;
        return $this;
    }

    public function param2(int $value): self {
        $this->param2 = $value;
        return $this;
    }

    // Další metody pro ostatní parametry...

    public function getParams(): array {
        return [
            'param1' => $this->param1,
            'param2' => $this->param2,
            // ...
        ];
    }
}

function Foo(array $params) {
    // Funkční logika
}

// Použití
$params = (new FooParams())
    ->param1(1)
    ->param2(5)
    ->param3("aaa")
    ->param4("bbb")
    ->param5(false)
    ->param6(null)
    ->param7(22)
    ->param8("ok");

Foo($params->getParams());

On gists

SelectionResolver

Nette PHP Nette-Database PHP Patterns

SelectionResolver.php #

<?php

// usage
		$this->paymentList = $this->payment->getPaymentSelectionResolver()
				->getBaseSelection()
				//->setCountry($this->order->billing__country_id)
				->setUserGroups($this->user->isLoggedIn() ? $this->user->getRoles() : NULL)
				->setOnlyForProduct($this->payment->getPaymentOnlyForProduct($this->order->getItems($this->order::ITEM_PRODUCT)))
				->toArray();
				
// ...				
    public function getPaymentSelectionResolver()
    {
        return $this->paymentSelectionResolver;
    }
    
    
// ...
class PaymentEshopOrderSelectionResolver
{
    /**
     * @var Context
     */
    protected $connection;

    /**
     * 
     * @var Nette\Database\Table\Selection
     */
    protected $selection;


    public function __construct(Context $connection)
    {
        $this->connection = $connection;
    }

    
    public function getBaseSelection()
    {
        $this->selection = $this->connection->table('payment_type')
            ->where('active = 1');
        return $this;
    }


    public function setCountry($countryId)
    {
        $this->selection->where(':payment_type_country.country_id IN (?) OR all_countries = 1', (array) $countryId);
        //$this->selection->where(':transport_type_variant.country_id IS NULL OR :transport_type_variant.country_id IN (?)', (array) $countryId);
        return $this;
    }


    public function setOnlyForProduct($paymentTypeIds = NULL)
    {
        if ($paymentTypeIds === NULL)
        {
            $this->selection->where('only_for_product = 0 OR only_for_product IS NULL');
        }
        else
        {
            $this->selection->where('payment_type.id IN (?)', (array) $paymentTypeIds);
        }
        return $this;
    }


    public function setUserGroups($usegroupsId = NULL)
    {
        if ($usegroupsId === NULL)
        {
            $this->selection->where(':payment_type_usergroup.usergroup_id IS NULL');
        }
        else
        {
            $this->selection->where(':payment_type_usergroup.usergroup_id IS NULL OR :payment_type_usergroup.usergroup_id  IN (?)', $usegroupsId);
        }
        return $this;
    }


    public function toArray()
    {
        $rows = [];
        foreach ($this->selection as $row)
        {
            $rows[$row->id] = $row;
        }

        return $rows;
    }

}

On gists

AfterSaveAction, events (own Kdyby implementation)

AW PHP Patterns

aftersaveEvent.php #

<?php

final class EshopOrderAfterSaveRegister extends Nette\Object {

	/**
	 * Nette callback array of after order save actions
	 * 
	 * @var array
	 */
	public $onAfterSave;

	/**
	 * Array with IAfterSaveAction objects
	 * 
	 * @var array
	 */
	private $afterSaveActions = array();

	/**
	 * Nette callback array of actions after success payment
	 * 
	 * @var array
	 */
	public $onAfterSuccessPayment;

	/**
	 * Array with IAfterSaveAction objects
	 * 
	 * @var array
	 */
	private $afterSuccessPaymentActions = array();

	/**
	 * Nette callback array of actions after fail payment
	 * 
	 * @var array
	 */
	public $onAfterFailPayment;

	/**
	 * Array with IAfterSaveAction objects
	 * 
	 * @var array
	 */
	private $afterFailPaymentActions = array();

	/**
	 * Instance of current presenter
	 * 
	 * @var Presenter
	 */
	private $presenter;

	/**
	 * Model EshopOrder
	 * 
	 * @var EshopOrder
	 */
	private $modelEshopOrder;

	public function __construct(EshopOrder $modelEshopOrder) {
		$this->modelEshopOrder = $modelEshopOrder;
	}

	/**
	 * Register after save action
	 * 
	 * @param IAfterSaveAction
	 */
	public function addAfterSaveAction(IAfterSaveAction $action) {
		$this->afterSaveActions[] = $action;
		return $this;
	}

	/**
	 * Register after success payment action
	 * 
	 * @param IAfterSaveAction
	 */
	public function addAfterSuccessPaymentAction(IAfterSaveAction $action) {
		$this->afterSuccessPaymentActions[] = $action;
		return $this;
	}

	/**
	 * Register after success payment action
	 * 
	 * @param IAfterSaveAction
	 */
	public function addAfterFailPaymentAction(IAfterSaveAction $action) {
		$this->afterFailPaymentActions[] = $action;
		return $this;
	}

	/**
	 * Application presenter setter
	 * 
	 * @param Presenter
	 */
	public function setPresenter(Presenter $presenter) {
		$this->presenter = $presenter;
		return $this;
	}

	/**
	 * Inicialize and invoke after save actions
	 * 
	 * @param  int
	 * @return void
	 */
	public function invokeAfterSaveActions($orderId) {
		foreach($this->afterSaveActions as $action) {
			$action->setPresenter($this->presenter);
			$this->onAfterSave[] = array($action, 'doAction');
		}

		$eshopOrder 	= $this->modelEshopOrder->getOrder($orderId);
		$orderItems 	= $this->modelEshopOrder->getOrderItems($orderId);
		$orderVouchers 	= $this->modelEshopOrder->getOrderVouchers($orderId);

		$this->onAfterSave($eshopOrder, $orderItems, $orderVouchers);
	}

	/**
	 * Inicialize and invoke after success payment actions
	 * 
	 * @param  int
	 * @return void
	 */
	public function invokeAfterSuccessPaymentActions($orderId) {
		foreach($this->afterSuccessPaymentActions as $action) {
			$action->setPresenter($this->presenter);
			$this->onAfterSuccessPayment[] = array($action, 'doAction');
		}

		$eshopOrder 	= $this->modelEshopOrder->getOrder($orderId);
		$orderItems 	= $this->modelEshopOrder->getOrderItems($orderId);
		$orderVouchers 	= $this->modelEshopOrder->getOrderVouchers($orderId);

		$this->onAfterSuccessPayment($eshopOrder, $orderItems, $orderVouchers);
	}

	/**
	 * Inicialize and invoke after success payment actions
	 * 
	 * @param  int
	 * @return void
	 */
	public function invokeAfterFailPaymentActions($orderId) {
		foreach($this->afterFailPaymentActions as $action) {
			$action->setPresenter($this->presenter);
			$this->onAfterFailPayment[] = array($action, 'doAction');
		}

		$eshopOrder 	= $this->modelEshopOrder->getOrder($orderId);
		$orderItems 	= $this->modelEshopOrder->getOrderItems($orderId);
		$orderVouchers 	= $this->modelEshopOrder->getOrderVouchers($orderId);

		$this->onAfterFailPayment($eshopOrder, $orderItems, $orderVouchers);
	}

}

On gists

MSP Vat resolver

PHP AW PHP Patterns

solution.php #

<?php

namespace Model;
use Nette;


class VatCzSkResolver
{
	private static $instance;

	public $eshopOrderFormMasoprofit;
	
	public function __construct(EshopOrderFormMasoprofit $eshopOrderFormMasoprofit)
	{
		self::$instance = $this;
		$this->eshopOrderFormMasoprofit = $eshopOrderFormMasoprofit;
	}

	public static function getDphRewrite()
	{
		if (!self::$instance) { // nejsme na frontendu
			return null;
		}
		
		if ($billingAddress = self::$instance->eshopOrderFormMasoprofit->getBillingAddress()) {
			if (isset($billingAddress['country_id']) && $billingAddress['country_id'] == 186) {
				if (isset($billingAddress['ic_vat']) && $billingAddress['ic_vat'] && isset($billingAddress['dic']) && $billingAddress['dic']) {
					return 0;
				}

				return 20;
			}
		}

		return null;
	}
}

On gists

0. Multiple constructors

PHP Patterns

multiple.php #

<?php
class File {
	protected $resource;
	
	protected function __construct() {
		// objekt nepůjde zvenku přímo vytvořit
	}
	
	static function createFromFile($filename, $mode) {
		$return = new self; // vytvoří objekt třídy File
		// od PHP 5.3 lze místo self použít static - vytvoří i potomky
		$return->resource = fOpen($filename, $mode);
		return $return;
	}
	
	static function createFromSocket($hostname, $port = -1) {
		$return = new self;
		$return->resource = fSockOpen($hostname, $port);
		return $return;
	}
}

$file = File::createFromFile(__FILE__, "r");