<?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
*/
<?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! 💪
<?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 🚀
<?php
/**
* ========================================
* PHP/OOP Best Practices & Patterns
* ========================================
*/
// ============================================================================
// 1. ✅ KONSTANTY MÍSTO "MAGIC STRINGS"
// ============================================================================
// ❌ ŠPATNĚ - Magic strings, překlepy nenajdeš
if ($order->state === 'pending') { }
if ($order->state === 'pendig') { } // Překlep!
// ✅ DOBŘE - Konstanty, IDE ti pomůže
class OrderState
{
const PENDING = 'pending';
const SHIPPED = 'shipped';
const DELIVERED = 'delivered';
}
if ($order->state === OrderState::PENDING) { }
if ($order->state === OrderState::PENDIG) { } // IDE error!
// ============================================================================
// 2. 🎯 ENUM PATTERN (PHP 7.4) - Rozšířené konstanty
// ============================================================================
class OrderStateEnum
{
const PENDING = 1;
const SHIPPED = 2;
const DELIVERED = 3;
private static $names = [
self::PENDING => 'Čeká na zpracování',
self::SHIPPED => 'Odesláno',
self::DELIVERED => 'Doručeno',
];
private static $colors = [
self::PENDING => 'orange',
self::SHIPPED => 'blue',
self::DELIVERED => 'green',
];
public static function getName(int $state): string
{
return self::$names[$state] ?? 'Neznámý stav';
}
public static function getColor(int $state): string
{
return self::$colors[$state] ?? 'gray';
}
public static function getAll(): array
{
return self::$names;
}
}
// Použití:
$stateName = OrderStateEnum::getName(OrderStateEnum::PENDING); // "Čeká na zpracování"
$color = OrderStateEnum::getColor(OrderStateEnum::PENDING); // "orange"
// ============================================================================
// 3. 🚀 VALUE OBJECTS - Type Safety & Zapouzdření Logiky
// ============================================================================
// PŘÍKLAD 1: Money - Nemůžeš sčítat různé měny
// ❌ ŠPATNĚ
function calculateTotal(float $priceEur, float $priceCzk): float
{
return $priceEur + $priceCzk; // Sčítáš EUR + CZK?? 😱
}
$total = calculateTotal(100, 2500); // 2600 čeho??
// ✅ DOBŘE - Value Object
class Money
{
private float $amount;
private string $currency;
public function __construct(float $amount, string $currency)
{
$this->amount = $amount;
$this->currency = $currency;
}
public function add(Money $other): Money
{
if ($this->currency !== $other->currency) {
throw new \InvalidArgumentException(
"Cannot add {$other->currency} to {$this->currency}"
);
}
return new Money($this->amount + $other->amount, $this->currency);
}
public function multiply(float $multiplier): Money
{
return new Money($this->amount * $multiplier, $this->currency);
}
public function getAmount(): float { return $this->amount; }
public function getCurrency(): string { return $this->currency; }
}
// Použití:
$priceEur = new Money(100, 'EUR');
$priceCzk = new Money(2500, 'CZK');
$total = $priceEur->add($priceCzk); // ❌ Exception! Nelze EUR + CZK
$discount = $priceEur->multiply(0.9); // ✅ 90 EUR
// PŘÍKLAD 2: Percentage - Validace 0-100
// ❌ ŠPATNĚ
function applyDiscount(float $price, int $percent): float
{
return $price - ($price * $percent / 100);
}
$final = applyDiscount(1000, 150); // 150% sleva?? -500 Kč? 😱
// ✅ DOBŘE - Value Object
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);
}
}
function applyDiscountSafe(float $price, Percentage $percent): float
{
return $price - $percent->applyTo($price);
}
$final = applyDiscountSafe(1000, new Percentage(150)); // ❌ Exception!
$final = applyDiscountSafe(1000, new Percentage(20)); // ✅ 800 Kč
// PŘÍKLAD 3: OrderState Value Object - Jen platné stavy
class OrderStateVO
{
private int $value;
private const NEW = 1;
private const PAID = 2;
private const SHIPPED = 3;
private static $valid = [self::NEW, self::PAID, self::SHIPPED];
private function __construct(int $value)
{
if (!in_array($value, self::$valid)) {
throw new \InvalidArgumentException("Invalid state: $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 function isNew(): bool { return $this->value === self::NEW; }
public function isPaid(): bool { return $this->value === self::PAID; }
public function getValue(): int { return $this->value; }
}
// Použití:
$state = OrderStateVO::paid(); // ✅ Jen platné stavy!
$state = new OrderStateVO(999); // ❌ Private constructor - nelze!
if ($state->isPaid()) { /* Ship order */ }
// ============================================================================
// 4. 🛡️ NULL OBJECT PATTERN - Žádné null checks
// ============================================================================
// ❌ ŠPATNĚ - Null checks všude
$user = $userRepo->find($id);
if ($user !== null) {
echo $user->getName();
} else {
echo 'Guest';
}
// ✅ DOBŘE - Null Object
interface UserInterface
{
public function getName(): string;
public function isGuest(): bool;
}
class User implements UserInterface
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string { return $this->name; }
public function isGuest(): bool { return false; }
}
class GuestUser implements UserInterface
{
public function getName(): string { return 'Guest'; }
public function isGuest(): bool { return true; }
}
// Použití:
$user = $userRepo->find($id) ?? new GuestUser();
echo $user->getName(); // Vždy funguje, žádný null check!
// ============================================================================
// 5. 🎨 EARLY RETURN - Guard Clauses místo vnořených if
// ============================================================================
// ❌ ŠPATNĚ - Pyramid of Doom
function processOrder($data)
{
if ($data !== null) {
if ($data->isValid()) {
if ($data->hasPermission()) {
return $data->process();
} else {
return 'No permission';
}
} else {
return 'Invalid';
}
} else {
return 'No data';
}
}
// ✅ DOBŘE - Guard Clauses (Early Return)
function processOrderClean($data)
{
if ($data === null) {
return 'No data';
}
if (!$data->isValid()) {
return 'Invalid';
}
if (!$data->hasPermission()) {
return 'No permission';
}
return $data->process();
}
// ============================================================================
// 6. 🔒 IMMUTABLE OBJECTS - Objekty se nemění
// ============================================================================
// ❌ ŠPATNĚ - Mutable (někdo změní a nevíš kde)
class MutablePrice
{
public float $value;
public function __construct(float $value)
{
$this->value = $value;
}
public function setValue(float $value)
{
$this->value = $value;
}
}
$price = new MutablePrice(100);
someFunction($price); // Co se stalo uvnitř?
echo $price->value; // Kdo ví... možná 100, možná 200?
// ✅ DOBŘE - Immutable (vrací nové instance)
class ImmutablePrice
{
private float $value;
public function __construct(float $value)
{
$this->value = $value;
}
public function getValue(): float { return $this->value; }
public function withDiscount(int $percent): self
{
return new self($this->value * (100 - $percent) / 100);
}
public function withTax(float $taxRate): self
{
return new self($this->value * (1 + $taxRate));
}
}
// Použití:
$price = new ImmutablePrice(100);
$discounted = $price->withDiscount(10); // Nový objekt
$withTax = $discounted->withTax(0.21); // Další nový objekt
echo $price->getValue(); // Stále 100! Původní nezměněn
echo $discounted->getValue(); // 90
echo $withTax->getValue(); // 108.9
// ============================================================================
// 7. 🎯 TYPE HINTS VŠUDE (PHP 7.4)
// ============================================================================
// ❌ ŠPATNĚ - Bez type hints
function calculate($a, $b)
{
return $a + $b;
}
calculate('10', '20'); // "1020" (string concatenation) 😱
calculate([1, 2], [3, 4]); // Fatal error až za běhu
// ✅ DOBŘE - S type hints
function calculateSafe(int $a, int $b): int
{
return $a + $b;
}
calculateSafe('10', '20'); // TypeError hned! ✅
calculateSafe(10, 20); // 30 ✅
// ============================================================================
// 8. 📦 DEPENDENCY INJECTION místo new
// ============================================================================
// ❌ ŠPATNĚ - Tight Coupling (pevné závislosti)
class OrderService
{
public function process()
{
$mailer = new Mailer(); // ❌ Nelze testovat, nelze změnit
$logger = new Logger(); // ❌ Pevně spojené třídy
$mailer->send('order@email.com', 'Order processed');
$logger->log('Order processed');
}
}
// ✅ DOBŘE - Dependency Injection
class OrderServiceWithDI
{
private Mailer $mailer;
private Logger $logger;
public function __construct(Mailer $mailer, Logger $logger)
{
$this->mailer = $mailer;
$this->logger = $logger;
}
public function process()
{
$this->mailer->send('order@email.com', 'Order processed');
$this->logger->log('Order processed');
}
}
// Použití - lze snadno měnit implementace, testovat s mocky
$service = new OrderServiceWithDI(
new SmtpMailer(), // nebo MockMailer() v testech
new FileLogger() // nebo DatabaseLogger()
);
// ============================================================================
// 9. 🔥 SINGLE RESPONSIBILITY - Jedna třída = jeden důvod ke změně
// ============================================================================
// ❌ ŠPATNĚ - Dělá všechno
class OrderGod
{
public function createOrder($data) { /* ... */ }
public function sendEmail($to, $subject) { /* ... */ }
public function logToDatabase($message) { /* ... */ }
public function validateCreditCard($number) { /* ... */ }
public function generatePdf($order) { /* ... */ }
}
// ✅ DOBŘE - Každá třída má jednu odpovědnost
class OrderCreator
{
public function create($data) { /* ... */ }
}
class EmailSender
{
public function send($to, $subject, $body) { /* ... */ }
}
class Logger
{
public function log($message) { /* ... */ }
}
class CreditCardValidator
{
public function validate($number): bool { /* ... */ }
}
class PdfGenerator
{
public function generate($order): string { /* ... */ }
}
// ============================================================================
// 10. 🎭 INTERFACE SEGREGATION - Malé, specifické interfaces
// ============================================================================
// ❌ ŠPATNĚ - Obrovský interface, vynucuje nepotřebné metody
interface WorkerGod
{
public function work();
public function eat();
public function sleep();
public function getPaid();
}
class Robot implements WorkerGod
{
public function work() { /* OK */ }
public function eat() { /* Robot nejí! */ }
public function sleep() { /* Robot nespí! */ }
public function getPaid() { /* Robot nedostává výplatu! */ }
}
// ✅ DOBŘE - Malé, specifické interfaces
interface Workable
{
public function work();
}
interface Eatable
{
public function eat();
}
interface Sleepable
{
public function sleep();
}
interface Payable
{
public function getPaid();
}
class Human implements Workable, Eatable, Sleepable, Payable
{
public function work() { /* ... */ }
public function eat() { /* ... */ }
public function sleep() { /* ... */ }
public function getPaid() { /* ... */ }
}
class RobotWorker implements Workable
{
public function work() { /* ... */ }
// Jen to, co skutečně umí!
}
// ============================================================================
// 11. 🏭 FACTORY PATTERN - Vytváření komplexních objektů
// ============================================================================
// ❌ ŠPATNĚ - Složité vytváření všude v kódu
$order = new Order();
$order->setCustomer($customer);
$order->setShippingAddress($address);
$order->setPaymentMethod($payment);
$order->setState(OrderState::NEW);
$order->setCreatedAt(new DateTime());
// ... 20 dalších setterů
// ✅ DOBŘE - Factory zapouzdřuje logiku vytváření
class OrderFactory
{
public function createFromCart(Cart $cart, Customer $customer): Order
{
$order = new Order();
$order->setCustomer($customer);
$order->setShippingAddress($customer->getDefaultAddress());
$order->setPaymentMethod($cart->getPaymentMethod());
$order->setState(OrderState::NEW);
$order->setCreatedAt(new DateTime());
$order->setTotal($cart->getTotal());
foreach ($cart->getItems() as $item) {
$order->addItem($item);
}
return $order;
}
}
// Použití:
$orderFactory = new OrderFactory();
$order = $orderFactory->createFromCart($cart, $customer);
// ============================================================================
// 12. 🔗 METHOD CHAINING (Fluent Interface)
// ============================================================================
// ❌ ŠPATNĚ - Hodně řádků
$query = new QueryBuilder();
$query->select('*');
$query->from('users');
$query->where('active', true);
$query->orderBy('created_at', 'DESC');
$query->limit(10);
$result = $query->get();
// ✅ DOBŘE - Fluent interface (vrací $this)
class QueryBuilder
{
private string $select = '';
private string $from = '';
private array $where = [];
public function select(string $columns): self
{
$this->select = $columns;
return $this; // Vrací sebe sama
}
public function from(string $table): self
{
$this->from = $table;
return $this;
}
public function where(string $column, $value): self
{
$this->where[] = [$column, $value];
return $this;
}
public function get(): array
{
// Execute query
return [];
}
}
// Použití - řetězení metod:
$result = (new QueryBuilder())
->select('*')
->from('users')
->where('active', true)
->orderBy('created_at', 'DESC')
->limit(10)
->get();
// ============================================================================
// 13. 💎 BONUS: Praktické tipy
// ============================================================================
// TIP 1: Používej ?? místo ternary pro null checks
$name = $user->name ?? 'Guest'; // místo: $user->name ? $user->name : 'Guest'
// TIP 2: Spaceship operator pro porovnání
usort($array, fn($a, $b) => $a <=> $b); // místo: $a > $b ? 1 : ($a < $b ? -1 : 0)
// TIP 3: Null safe operator (PHP 8.0+, ale dobrý vědět)
// $country = $user?->getAddress()?->getCountry()?->getName();
// TIP 4: Array destructuring
[$firstName, $lastName] = explode(' ', $fullName);
// TIP 5: Arrow functions (PHP 7.4+)
$prices = array_map(fn($item) => $item->price, $items);
// TIP 6: Typed properties (PHP 7.4+)
class Product
{
private int $id;
private string $name;
private float $price;
private ?string $description = null; // Nullable
}
// TIP 7: Spread operator
$array1 = [1, 2, 3];
$array2 = [...$array1, 4, 5, 6]; // [1, 2, 3, 4, 5, 6]
// TIP 8: Named arguments (PHP 8.0+, ale dobrý vědět)
// createOrder(customer: $customer, total: 1000, shipping: 'DHL');
// ============================================================================
// 📚 SHRNUTÍ - Co používat VŽDY:
// ============================================================================
/*
1. ✅ Konstanty místo magic strings/numbers
2. ✅ Type hints všude (int, string, array, vlastní třídy)
3. ✅ Early return (guard clauses)
4. ✅ Dependency Injection
5. ✅ Single Responsibility (malé, zaměřené třídy)
6. ✅ Value Objects pro důležitá data (Money, Email, Percentage)
7. ✅ Immutabilita kde to dává smysl
8. ✅ Null Object pattern místo null checks
9. ✅ PHPDoc kde type hints nestačí
10. ✅ Smysluplné názvy (ne $a, $tmp, $data)
❌ NIKDY:
- Magic strings/numbers bez konstant
- Velké třídy (God Objects)
- new v business logice (použij DI)
- Public properties (použij gettery/settery)
- Hluboké vnořené ify (pyramid of doom)
*/
Máš to! Celé najednou pro Gist. Chceš ještě něco konkrétního? 🚀
<!--
https://play.tailwindcss.com/V4B6tDWkg1
-->
<div class="[--color:red] md:[--color:green]">
<div class="aspect-square w-20 bg-[var(--color)]">1</div>
<div class="aspect-square w-20 bg-[--color]">2</div>
</div>
<?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
}
/* https://www.bitovi.com/blog/css-only-type-grinding-casting-tokens-into-useful-values */
@property --variant {
syntax: "primary|secondary|success|error";
initial-value: primary;
inherits: true;
}
@property --_v-primary-else-0 {
syntax: "primary|<integer>"; initial-value: 0; inherits: true;
}
@property --_v-primary-bit {
syntax: "<integer>"; initial-value: 1; inherits: true;
}
.element {
--variant: primary;
--_v-primary-else-0: var(--variant);
--_v-primary-bit: var(--_v-primary-else-0);
}
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tailwind CSS - Advanced Triky</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="p-8 bg-gray-50">
<h1 class="text-3xl font-bold mb-8">Tailwind CSS - Advanced Triky 🚀</h1>
<!-- ============================================ -->
<!-- #2 PEER SELEKTORY -->
<!-- ============================================ -->
<section class="mb-12 bg-white p-6 rounded-lg border-2 border-blue-200">
<h2 class="text-2xl font-bold mb-4 text-blue-700">#2 Peer selektory</h2>
<p class="text-gray-600 mb-4">Sourozenec ovlivňuje sourozence</p>
<div class="bg-gray-50 p-4 rounded mb-4">
<code class="text-sm">peer + peer-checked:class</code>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Checkbox ovládá label</h3>
<div class="flex items-center gap-3">
<input type="checkbox" id="check1" class="peer w-5 h-5" />
<label for="check1" class="peer-checked:text-blue-600 peer-checked:font-bold cursor-pointer">
Zaškrtni checkbox → label se změní
</label>
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Named peers (více peer prvků)</h3>
<div class="space-y-3">
<div>
<input type="radio" name="opt" id="opt1" class="peer/opt1 hidden" />
<label for="opt1" class="block p-3 border-2 border-gray-300 rounded cursor-pointer peer-checked/opt1:border-green-500 peer-checked/opt1:bg-green-50">
Varianta 1
</label>
<div class="hidden peer-checked/opt1:block mt-2 p-3 bg-green-100 rounded">
✅ Obsah pro variantu 1
</div>
</div>
<div>
<input type="radio" name="opt" id="opt2" class="peer/opt2 hidden" />
<label for="opt2" class="block p-3 border-2 border-gray-300 rounded cursor-pointer peer-checked/opt2:border-purple-500 peer-checked/opt2:bg-purple-50">
Varianta 2
</label>
<div class="hidden peer-checked/opt2:block mt-2 p-3 bg-purple-100 rounded">
✅ Obsah pro variantu 2
</div>
</div>
</div>
</div>
</section>
<!-- ============================================ -->
<!-- #4 MULTIPLE CONDITIONS -->
<!-- ============================================ -->
<section class="mb-12 bg-white p-6 rounded-lg border-2 border-green-200">
<h2 class="text-2xl font-bold mb-4 text-green-700">#4 Multiple conditions (AND)</h2>
<p class="text-gray-600 mb-4">Několik podmínek musí platit zároveň</p>
<div class="bg-gray-50 p-4 rounded mb-4">
<code class="text-sm">data-[active]:data-[type=premium]:bg-gold</code>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Obě podmínky musí platit</h3>
<div data-active data-type="premium" class="p-4 border-2 border-gray-300 data-[active]:data-[type=premium]:bg-yellow-100 data-[active]:data-[type=premium]:border-yellow-500 data-[active]:data-[type=premium]:font-bold">
✅ data-active + data-type="premium" → žluté pozadí
</div>
<div data-active class="p-4 mt-2 border-2 border-gray-300 data-[active]:data-[type=premium]:bg-yellow-100 data-[active]:data-[type=premium]:border-yellow-500">
❌ Jen data-active (chybí premium) → normální
</div>
<div data-type="premium" class="p-4 mt-2 border-2 border-gray-300 data-[active]:data-[type=premium]:bg-yellow-100 data-[active]:data-[type=premium]:border-yellow-500">
❌ Jen data-type="premium" (chybí active) → normální
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Kombinace s hover a data atributem</h3>
<button data-variant="danger" class="p-3 bg-red-500 text-white rounded hover:data-[variant=danger]:bg-red-700 hover:data-[variant=danger]:scale-105 transition">
Hover + data-variant="danger" → tmavší a větší
</button>
</div>
</section>
<!-- ============================================ -->
<!-- #5 NTH-CHILD SELEKTORY -->
<!-- ============================================ -->
<section class="mb-12 bg-white p-6 rounded-lg border-2 border-purple-200">
<h2 class="text-2xl font-bold mb-4 text-purple-700">#5 Nth-child selektory</h2>
<p class="text-gray-600 mb-4">Stylování podle pozice v DOM</p>
<div class="bg-gray-50 p-4 rounded mb-4">
<code class="text-sm">[&>*:nth-child(odd)]:bg-gray-100</code>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Liché/sudé řádky (zebra pattern)</h3>
<div class="[&>*:nth-child(odd)]:bg-gray-100 [&>*:nth-child(even)]:bg-white border-2 border-gray-300 rounded overflow-hidden">
<div class="p-3">Řádek 1 (lichý) - šedé pozadí</div>
<div class="p-3">Řádek 2 (sudý) - bílé pozadí</div>
<div class="p-3">Řádek 3 (lichý) - šedé pozadí</div>
<div class="p-3">Řádek 4 (sudý) - bílé pozadí</div>
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">První a poslední prvek</h3>
<div class="[&>*:first-child]:font-bold [&>*:first-child]:text-green-600 [&>*:last-child]:font-bold [&>*:last-child]:text-red-600 space-y-2">
<div class="p-3 border rounded">První prvek - zelený tučný</div>
<div class="p-3 border rounded">Prostřední prvek - normální</div>
<div class="p-3 border rounded">Poslední prvek - červený tučný</div>
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Každý třetí prvek</h3>
<div class="[&>*:nth-child(3n)]:bg-blue-100 [&>*:nth-child(3n)]:border-blue-500 space-y-2">
<div class="p-2 border-2 rounded">Prvek 1</div>
<div class="p-2 border-2 rounded">Prvek 2</div>
<div class="p-2 border-2 rounded">Prvek 3 - modrý (3n)</div>
<div class="p-2 border-2 rounded">Prvek 4</div>
<div class="p-2 border-2 rounded">Prvek 5</div>
<div class="p-2 border-2 rounded">Prvek 6 - modrý (3n)</div>
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Not last-child (margin mezi prvky)</h3>
<div class="[&>*:not(:last-child)]:mb-4">
<div class="p-3 bg-gray-100 rounded">Prvek s marginem dole</div>
<div class="p-3 bg-gray-100 rounded">Prvek s marginem dole</div>
<div class="p-3 bg-gray-100 rounded">Poslední prvek BEZ marginu</div>
</div>
</div>
</section>
<!-- ============================================ -->
<!-- #6 BEFORE/AFTER -->
<!-- ============================================ -->
<section class="mb-12 bg-white p-6 rounded-lg border-2 border-orange-200">
<h2 class="text-2xl font-bold mb-4 text-orange-700">#6 Before/After pseudo-elementy</h2>
<p class="text-gray-600 mb-4">Přidání obsahu bez HTML</p>
<div class="bg-gray-50 p-4 rounded mb-4">
<code class="text-sm">before:content-['→'] after:content-['✓']</code>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Ikony před/za textem</h3>
<div class="before:content-['→'] before:mr-2 before:text-blue-500 p-3 border rounded">
Text se šipkou před
</div>
<div class="after:content-['✓'] after:ml-2 after:text-green-500 p-3 border rounded mt-2">
Text se zaškrtávátkem za
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Dekorativní čára</h3>
<h2 class="text-xl font-bold before:content-[''] before:inline-block before:w-12 before:h-1 before:bg-purple-500 before:mr-3 before:align-middle">
Nadpis s čárou
</h2>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Badge/štítek</h3>
<div class="relative inline-block">
<button class="px-4 py-2 bg-blue-500 text-white rounded after:content-['3'] after:absolute after:-top-2 after:-right-2 after:bg-red-500 after:text-white after:rounded-full after:w-6 after:h-6 after:flex after:items-center after:justify-center after:text-xs after:font-bold">
Notifikace
</button>
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Citace s úvozovkami</h3>
<blockquote class="before:content-['"'] after:content-['"'] before:text-4xl after:text-4xl before:text-gray-400 after:text-gray-400 p-4 bg-gray-50 rounded italic">
<span class="text-lg">Toto je citace s automatickými úvozovkami</span>
</blockquote>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Tooltip (hover efekt)</h3>
<span class="relative inline-block cursor-help underline decoration-dotted hover:after:content-['Tohle_je_tooltip'] hover:after:absolute hover:after:left-1/2 hover:after:-translate-x-1/2 hover:after:top-full hover:after:mt-2 hover:after:bg-gray-800 hover:after:text-white hover:after:px-3 hover:after:py-1 hover:after:rounded hover:after:text-sm hover:after:whitespace-nowrap">
Najeď myší
</span>
</div>
</section>
<!-- ============================================ -->
<!-- #7 SUPPORTS QUERIES -->
<!-- ============================================ -->
<section class="mb-12 bg-white p-6 rounded-lg border-2 border-pink-200">
<h2 class="text-2xl font-bold mb-4 text-pink-700">#7 Supports queries (detekce CSS features)</h2>
<p class="text-gray-600 mb-4">Aplikuj styly jen když prohlížeč podporuje feature</p>
<div class="bg-gray-50 p-4 rounded mb-4">
<code class="text-sm">supports-[backdrop-filter]:backdrop-blur-lg</code>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Backdrop blur (fallback)</h3>
<div class="relative h-40 bg-gradient-to-r from-blue-400 to-purple-500 rounded overflow-hidden">
<img src="https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400" class="w-full h-full object-cover" alt="pozadí" />
<div class="absolute inset-0 flex items-center justify-center">
<div class="bg-white/30 supports-[backdrop-filter]:backdrop-blur-md supports-[backdrop-filter]:bg-white/20 p-6 rounded-lg">
<p class="font-bold text-white">
S podporou: blur efekt<br/>
Bez podpory: silnější opacity
</p>
</div>
</div>
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Grid layout (fallback na flex)</h3>
<div class="flex flex-wrap gap-4 supports-[display:grid]:grid supports-[display:grid]:grid-cols-3">
<div class="p-4 bg-blue-100 rounded">Box 1</div>
<div class="p-4 bg-blue-100 rounded">Box 2</div>
<div class="p-4 bg-blue-100 rounded">Box 3</div>
</div>
<p class="text-sm text-gray-600 mt-2">S podporou Grid: 3 sloupce | Bez: flex wrap</p>
</div>
</section>
<!-- ============================================ -->
<!-- #10 ARBITRARY PROPERTIES -->
<!-- ============================================ -->
<section class="mb-12 bg-white p-6 rounded-lg border-2 border-indigo-200">
<h2 class="text-2xl font-bold mb-4 text-indigo-700">#10 Arbitrary properties (vlastní CSS)</h2>
<p class="text-gray-600 mb-4">Použij libovolnou CSS property</p>
<div class="bg-gray-50 p-4 rounded mb-4">
<code class="text-sm">[property:value]</code>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Mask image (gradient fade)</h3>
<div class="[mask-image:linear-gradient(to_bottom,black_50%,transparent)] bg-gradient-to-r from-purple-400 to-pink-400 h-32 flex items-center justify-center text-white font-bold">
Fade efekt dole
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Text stroke</h3>
<h2 class="text-5xl font-bold text-transparent [-webkit-text-stroke:2px_black]">
OUTLINED TEXT
</h2>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Custom scrollbar</h3>
<div class="h-32 overflow-y-scroll border-2 border-gray-300 rounded p-4 [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-track]:bg-gray-100 [&::-webkit-scrollbar-thumb]:bg-blue-500 [&::-webkit-scrollbar-thumb]:rounded">
<p>Scrolluj dolů a uvidíš vlastní scrollbar.</p>
<p class="mt-4">Lorem ipsum dolor sit amet.</p>
<p class="mt-4">Lorem ipsum dolor sit amet.</p>
<p class="mt-4">Lorem ipsum dolor sit amet.</p>
<p class="mt-4">Lorem ipsum dolor sit amet.</p>
<p class="mt-4">Konec obsahu.</p>
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Aspect ratio nestandardní</h3>
<div class="[aspect-ratio:21/9] bg-gradient-to-r from-orange-400 to-red-400 rounded flex items-center justify-center text-white font-bold">
21:9 Ultrawide poměr
</div>
</div>
</section>
<!-- ============================================ -->
<!-- #13 ARBITRARY VARIANTS S MODIFIERS -->
<!-- ============================================ -->
<section class="mb-12 bg-white p-6 rounded-lg border-2 border-teal-200">
<h2 class="text-2xl font-bold mb-4 text-teal-700">#13 Arbitrary variants s media queries</h2>
<p class="text-gray-600 mb-4">Vlastní podmínky a media queries</p>
<div class="bg-gray-50 p-4 rounded mb-4">
<code class="text-sm">[@media(hover:hover)]:hover:scale-110</code>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Hover jen na zařízeních s hover (ne mobil)</h3>
<button class="px-6 py-3 bg-blue-500 text-white rounded transition [@media(hover:hover)]:hover:scale-110 [@media(hover:hover)]:hover:bg-blue-600">
Hover efekt jen na PC/notebooku
</button>
<p class="text-sm text-gray-600 mt-2">Na mobilu (touch) se škálování neaplikuje</p>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Dark mode s prefers-color-scheme</h3>
<div class="p-4 border-2 rounded bg-white [@media(prefers-color-scheme:dark)]:bg-gray-800 [@media(prefers-color-scheme:dark)]:text-white [@media(prefers-color-scheme:dark)]:border-gray-600">
Tento box reaguje na systémové nastavení dark mode
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Orientace zařízení</h3>
<div class="p-4 border-2 rounded bg-blue-100 [@media(orientation:portrait)]:bg-green-100 [@media(orientation:landscape)]:bg-orange-100">
<p class="font-semibold">Otoč zařízení:</p>
<p class="text-sm">Portrait (na výšku) = zelená</p>
<p class="text-sm">Landscape (na šířku) = oranžová</p>
</div>
</div>
<div class="mb-6">
<h3 class="font-semibold mb-3">Vlastní breakpoint</h3>
<div class="p-4 border-2 rounded bg-gray-100 [@media(min-width:800px)]:bg-purple-100 [@media(min-width:800px)]:text-purple-800">
Pod 800px: šedá | Nad 800px: fialová
</div>
</div>
</section>
<!-- ============================================ -->
<!-- SHRNUTÍ -->
<!-- ============================================ -->
<section class="bg-gradient-to-r from-blue-50 to-purple-50 p-6 rounded-lg border-2 border-blue-300">
<h2 class="text-2xl font-bold mb-6">📚 Rychlé shrnutí</h2>
<div class="grid md:grid-cols-2 gap-4">
<div class="bg-white p-4 rounded">
<h3 class="font-bold text-blue-600 mb-2">#2 Peer</h3>
<code class="text-xs">peer-checked:text-blue-500</code>
<p class="text-sm text-gray-600 mt-1">Sourozenec ovlivňuje sourozence</p>
</div>
<div class="bg-white p-4 rounded">
<h3 class="font-bold text-green-600 mb-2">#4 Multiple conditions</h3>
<code class="text-xs">data-[a]:data-[b]:class</code>
<p class="text-sm text-gray-600 mt-1">Obě podmínky musí platit (AND)</p>
</div>
<div class="bg-white p-4 rounded">
<h3 class="font-bold text-purple-600 mb-2">#5 Nth-child</h3>
<code class="text-xs">[&>*:nth-child(odd)]:bg-gray</code>
<p class="text-sm text-gray-600 mt-1">Stylování podle pozice</p>
</div>
<div class="bg-white p-4 rounded">
<h3 class="font-bold text-orange-600 mb-2">#6 Before/After</h3>
<code class="text-xs">before:content-['→']</code>
<p class="text-sm text-gray-600 mt-1">Pseudo-elementy s obsahem</p>
</div>
<div class="bg-white p-4 rounded">
<h3 class="font-bold text-pink-600 mb-2">#7 Supports</h3>
<code class="text-xs">supports-[backdrop-filter]:blur</code>
<p class="text-sm text-gray-600 mt-1">Feature detection + fallback</p>
</div>
<div class="bg-white p-4 rounded">
<h3 class="font-bold text-indigo-600 mb-2">#10 Arbitrary properties</h3>
<code class="text-xs">[mask-image:gradient]</code>
<p class="text-sm text-gray-600 mt-1">Libovolné CSS properties</p>
</div>
<div class="bg-white p-4 rounded">
<h3 class="font-bold text-teal-600 mb-2">#13 Arbitrary variants</h3>
<code class="text-xs">[@media(hover:hover)]:hover</code>
<p class="text-sm text-gray-600 mt-1">Vlastní media queries</p>
</div>
</div>
</section>
</body>
</html>
<!doctype html>
<html lang="cs">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tailwind Selektory - Kompletní průvodce</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50 p-8">
<h1 class="mb-8 text-3xl font-bold">Tailwind CSS - Kompletní průvodce selektory</h1>
<!-- ============================================ -->
<!-- ČÁST 1: STYLOVÁNÍ SAMOTNÉHO ELEMENTU -->
<!-- ============================================ -->
<section class="mb-16 rounded-lg border-2 border-blue-200 bg-white p-6">
<h2 class="mb-6 text-2xl font-bold text-blue-700">ČÁST 1: Stylování samotného elementu</h2>
<!-- #1 Data atribut bez hodnoty -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#1 Data atribut BEZ hodnoty na elementu</h3>
<div class="mb-2 rounded bg-gray-50 p-4"><code class="text-sm">data-[active]:class</code> nebo <code class="text-sm">data-active:class</code></div>
<div data-active class="border-2 border-gray-300 p-4 data-[active]:border-purple-500 data-[active]:bg-purple-50">✅ Mám data-active → fialový</div>
<div class="mt-2 border-2 border-gray-300 p-4 data-[active]:border-purple-500 data-[active]:bg-purple-50">❌ Nemám data-active → šedý</div>
</div>
<!-- #2 Data atribut S hodnotou -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#2 Data atribut S konkrétní hodnotou na elementu</h3>
<div class="mb-2 rounded bg-gray-50 p-4"><code class="text-sm">data-[state=open]:class</code> - kontroluje přesnou hodnotu</div>
<div data-state="open" class="border-2 border-gray-300 p-4 data-[state=open]:border-green-500 data-[state=open]:bg-green-50">✅ data-state="open" → zelený</div>
<div data-state="closed" class="mt-2 border-2 border-gray-300 p-4 data-[state=open]:border-green-500 data-[state=open]:bg-green-50">❌ data-state="closed" → šedý</div>
</div>
<!-- #3 Data atribut - jen přítomnost -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#3 Data atribut - kontrola jen PŘÍTOMNOSTI (ignoruje hodnotu)</h3>
<div class="mb-2 rounded bg-gray-50 p-4"><code class="text-sm">data-state:class</code> - stačí, že atribut existuje</div>
<div data-state="open" class="border-2 border-gray-300 p-4 data-state:border-orange-500 data-state:bg-orange-50">✅ data-state="open" → oranžový</div>
<div data-state="closed" class="mt-2 border-2 border-gray-300 p-4 data-state:border-orange-500 data-state:bg-orange-50">✅ data-state="closed" → také oranžový (hodnota se ignoruje)</div>
<div class="mt-2 border-2 border-gray-300 p-4 data-state:border-orange-500 data-state:bg-orange-50">❌ bez data-state → šedý</div>
</div>
<!-- #4 CSS třída -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#4 CSS třída na elementu</h3>
<div class="mb-2 rounded bg-gray-50 p-4">
<code class="text-sm">[&.active]:class</code>
</div>
<div class="active border-2 border-gray-300 p-4 [&.active]:border-blue-500 [&.active]:bg-blue-50">✅ Mám třídu .active → modrý</div>
<div class="mt-2 border-2 border-gray-300 p-4 [&.active]:border-blue-500 [&.active]:bg-blue-50">❌ Nemám třídu .active → šedý</div>
</div>
</section>
<!-- ============================================ -->
<!-- ČÁST 2: STYLOVÁNÍ PODLE RODIČE (GROUP) -->
<!-- ============================================ -->
<section class="mb-16 rounded-lg border-2 border-green-200 bg-white p-6">
<h2 class="mb-6 text-2xl font-bold text-green-700">ČÁST 2: Stylování podle rodiče s GROUP (čistší syntaxe)</h2>
<p class="mb-6 text-gray-600">Rodič musí mít třídu <code class="rounded bg-gray-100 px-2 py-1">group</code></p>
<!-- #5 Group + data atribut bez hodnoty -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#5 Group + data atribut BEZ hodnoty</h3>
<div class="mb-2 rounded bg-gray-50 p-4">
<code class="text-sm">group-data-[active]:class</code>
</div>
<div data-active class="group border-2 border-gray-300 bg-gray-100 p-4">
<p class="group-data-[active]:font-bold group-data-[active]:text-purple-600">✅ Rodič (group) má data-active → fialový text</p>
</div>
<div class="group mt-2 border-2 border-gray-300 bg-gray-100 p-4">
<p class="group-data-[active]:font-bold group-data-[active]:text-purple-600">❌ Rodič nemá data-active → normální</p>
</div>
</div>
<!-- #6 Group + data atribut s hodnotou -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#6 Group + data atribut S hodnotou</h3>
<div class="mb-2 rounded bg-gray-50 p-4">
<code class="text-sm">group-data-[state=danger]:class</code>
</div>
<div data-state="danger" class="group border-2 border-gray-300 bg-gray-100 p-4">
<p class="group-data-[state=danger]:font-bold group-data-[state=danger]:text-red-600">✅ Rodič má data-state="danger" → červený</p>
</div>
<div data-state="success" class="group mt-2 border-2 border-gray-300 bg-gray-100 p-4">
<p class="group-data-[state=danger]:font-bold group-data-[state=danger]:text-red-600">❌ Rodič má data-state="success" → normální</p>
</div>
</div>
<!-- #7 Group + CSS třída -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#7 Group + CSS třída</h3>
<div class="mb-2 rounded bg-gray-50 p-4">
<code class="text-sm">group-[.card--danger]:class</code>
</div>
<div class="group card--danger border-2 border-gray-300 bg-orange-600 p-4">
<a href="#" class="text-black group-[.card--danger]:font-bold group-[.card--danger]:text-white"> ✅ Rodič má .card--danger → bílý text </a>
</div>
<div class="group mt-2 border-2 border-gray-300 bg-gray-100 p-4">
<a href="#" class="text-black group-[.card--danger]:font-bold group-[.card--danger]:text-white"> ❌ Rodič nemá .card--danger → černý text </a>
</div>
</div>
</section>
<!-- ============================================ -->
<!-- ČÁST 3: STYLOVÁNÍ PODLE PŘEDKA (ARBITRARY) -->
<!-- ============================================ -->
<section class="mb-16 rounded-lg border-2 border-purple-200 bg-white p-6">
<h2 class="mb-6 text-2xl font-bold text-purple-700">ČÁST 3: Stylování podle předka bez GROUP (flexibilnější)</h2>
<p class="mb-6 text-gray-600">Rodič/předek <strong>nemusí</strong> mít třídu <code class="rounded bg-gray-100 px-2 py-1">group</code></p>
<!-- #8 Arbitrary + data atribut bez hodnoty -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#8 Arbitrary selektor + data atribut BEZ hodnoty</h3>
<div class="mb-2 rounded bg-gray-50 p-4">
<code class="text-sm">[[data-expanded]_&]:class</code>
</div>
<div data-expanded class="border-2 border-gray-300 bg-gray-100 p-4">
<p class="[[data-expanded]_&]:font-bold [[data-expanded]_&]:text-purple-600">✅ Předek má data-expanded → fialový</p>
</div>
<div class="mt-2 border-2 border-gray-300 bg-gray-100 p-4">
<p class="[[data-expanded]_&]:font-bold [[data-expanded]_&]:text-purple-600">❌ Předek nemá data-expanded → normální</p>
</div>
</div>
<!-- #9 Arbitrary + data atribut s hodnotou -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#9 Arbitrary selektor + data atribut S hodnotou</h3>
<div class="mb-2 rounded bg-gray-50 p-4">
<code class="text-sm">[[data-theme='dark']_&]:class</code>
</div>
<div data-theme="dark" class="border-2 border-gray-300 bg-gray-800 p-4">
<p class="text-gray-400 [[data-theme='dark']_&]:font-bold [[data-theme='dark']_&]:text-orange-400">✅ Předek má data-theme="dark" → oranžový</p>
</div>
<div data-theme="light" class="mt-2 border-2 border-gray-300 bg-gray-100 p-4">
<p class="text-gray-400 [[data-theme='dark']_&]:font-bold [[data-theme='dark']_&]:text-orange-400">❌ Předek má data-theme="light" → šedý</p>
</div>
</div>
<!-- #10 Arbitrary + CSS třída -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#10 Arbitrary selektor + CSS třída</h3>
<div class="mb-2 rounded bg-gray-50 p-4">
<code class="text-sm">[.is-open_&]:class</code>
</div>
<div class="is-open border-2 border-gray-300 bg-gray-100 p-4">
<p class="[.is-open_&]:font-bold [.is-open_&]:text-blue-600">✅ Předek má .is-open → modrý</p>
</div>
<div class="mt-2 border-2 border-gray-300 bg-gray-100 p-4">
<p class="[.is-open_&]:font-bold [.is-open_&]:text-blue-600">❌ Předek nemá .is-open → normální</p>
</div>
</div>
</section>
<!-- ============================================ -->
<!-- ČÁST 4: STYLOVÁNÍ POTOMKA PODLE RODIČE -->
<!-- ============================================ -->
<section class="mb-16 rounded-lg border-2 border-orange-200 bg-white p-6">
<h2 class="mb-6 text-2xl font-bold text-orange-700">ČÁST 4: Stylování POTOMKA podle stavu RODIČE</h2>
<p class="mb-6 text-gray-600">Rodič má atribut/třídu a stylujeme jeho potomka (dítě, vnuka...)</p>
<!-- #11 Potomek podle data atributu rodiče -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#11 Stylování SVG podle data atributu rodiče</h3>
<div class="mb-2 rounded bg-gray-50 p-4">
<code class="text-sm">[&[data-collapsed=true]_svg]:rotate-180</code>
<p class="mt-1 text-xs text-gray-600">Čteš to: "Když <strong>tento element</strong> má data-collapsed=true, otoč jeho <strong>svg potomka</strong>"</p>
</div>
<div data-collapsed="true" class="border-2 border-gray-300 bg-gray-100 p-4 [&[data-collapsed=true]_svg]:rotate-180 [&[data-collapsed=true]_svg]:text-red-500">
<svg class="h-6 w-6 transition-transform" fill="currentColor" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<span class="ml-2">✅ Rodič má data-collapsed="true" → SVG je otočené a červené</span>
</div>
<div class="mt-2 border-2 border-gray-300 bg-gray-100 p-4 [&[data-collapsed=true]_svg]:rotate-180 [&[data-collapsed=true]_svg]:text-red-500">
<svg class="h-6 w-6 transition-transform" fill="currentColor" viewBox="0 0 20 20">
<path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
</svg>
<span class="ml-2">❌ Rodič nemá data-collapsed → SVG normální</span>
</div>
</div>
<!-- #12 Potomek podle CSS třídy rodiče -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#12 Stylování potomka podle CSS třídy rodiče</h3>
<div class="mb-2 rounded bg-gray-50 p-4">
<code class="text-sm">[&.card--premium_span]:text-yellow-500</code>
<p class="mt-1 text-xs text-gray-600">Čteš to: "Když <strong>tento element</strong> má třídu .card--premium, nastyl jeho <strong>span potomka</strong>"</p>
</div>
<div class="card--premium border-2 border-gray-300 bg-gradient-to-r from-yellow-50 to-orange-50 p-4 [&.card--premium_span]:font-bold [&.card--premium_span]:text-yellow-600">
<span>⭐</span>
<span class="ml-2">✅ Rodič má .card--premium → span je žlutý a tučný</span>
</div>
<div class="mt-2 border-2 border-gray-300 bg-gray-100 p-4 [&.card--premium_span]:font-bold [&.card--premium_span]:text-yellow-600">
<span>⭐</span>
<span class="ml-2">❌ Rodič nemá .card--premium → span normální</span>
</div>
</div>
<!-- #13 Složitější příklad - více úrovní -->
<div class="mb-8">
<h3 class="mb-3 text-lg font-semibold">#13 Komplexnější příklad - vnořené elementy</h3>
<div class="mb-2 rounded bg-gray-50 p-4">
<code class="text-sm">[&[data-state=error]_.icon]:text-red-500</code>
<p class="mt-1 text-xs text-gray-600">Styluje <strong>jakéhokoliv potomka</strong> s třídou .icon</p>
</div>
<div data-state="error" class="border-2 border-red-300 bg-red-50 p-4 [&[data-state=error]_.icon]:scale-125 [&[data-state=error]_.icon]:text-red-600">
<div class="flex items-center gap-2">
<span class="icon transition-transform">⚠️</span>
<span>✅ Rodič má data-state="error" → ikona je větší a červená</span>
</div>
<div class="mt-2 ml-8">
<span class="icon transition-transform">❌</span>
<span class="ml-2">Vnořená ikona je také stylovaná</span>
</div>
</div>
<div class="mt-2 border-2 border-gray-300 bg-gray-100 p-4 [&[data-state=error]_.icon]:scale-125 [&[data-state=error]_.icon]:text-red-600">
<div class="flex items-center gap-2">
<span class="icon transition-transform">⚠️</span>
<span>❌ Rodič nemá data-state="error" → ikona normální</span>
</div>
</div>
</div>
<div class="rounded border-l-4 border-orange-400 bg-orange-50 p-4">
<p class="mb-2 font-semibold text-orange-800">🔑 Klíčový rozdíl:</p>
<div class="space-y-2 text-sm">
<div>
<code class="rounded bg-white px-2 py-1">[[data-collapsed=true]_&]:class</code>
<p class="ml-4 text-gray-700">→ Styluje <strong>tento element</strong>, když <strong>předek</strong> má atribut</p>
</div>
<div>
<code class="rounded bg-white px-2 py-1">[&[data-collapsed=true]_svg]:class</code>
<p class="ml-4 text-gray-700">→ Styluje <strong>potomka</strong>, když <strong>tento element</strong> má atribut</p>
</div>
</div>
</div>
</section>
<!-- ============================================ -->
<!-- KDY CO POUŽÍT -->
<!-- ============================================ -->
<section class="mb-8 rounded-lg border-2 border-blue-300 bg-gradient-to-r from-blue-50 to-purple-50 p-6">
<h2 class="mb-6 text-2xl font-bold">🎯 Kdy co použít?</h2>
<div class="space-y-4">
<div class="rounded-lg bg-white p-4">
<h3 class="mb-2 font-bold text-green-700">✅ Použij GROUP syntaxe když:</h3>
<ul class="ml-6 list-disc space-y-1 text-gray-700">
<li>Máš jasný rodič-dítě vztah v komponentě</li>
<li>Chceš čitelnější a kratší kód</li>
<li>Používáš to opakovaně v jedné komponentě</li>
</ul>
<div class="mt-3 rounded bg-green-50 p-3 text-sm">
<code>group-data-[active]:text-purple-500</code>
</div>
</div>
<div class="rounded-lg bg-white p-4">
<h3 class="mb-2 font-bold text-purple-700">✅ Použij ARBITRARY selektory když:</h3>
<ul class="ml-6 list-disc space-y-1 text-gray-700">
<li>Nechceš přidávat třídu <code class="rounded bg-gray-100 px-1">group</code> na rodiče</li>
<li>Předek je vzdálenější (ne přímý rodič)</li>
<li>Potřebuješ flexibilnější podmínky</li>
</ul>
<div class="mt-3 rounded bg-purple-50 p-3 text-sm">
<code>[[data-theme='dark']_&]:text-white</code>
</div>
</div>
<div class="rounded-lg bg-white p-4">
<h3 class="mb-2 font-bold text-orange-700">✅ Stylování potomka podle rodiče:</h3>
<ul class="ml-6 list-disc space-y-1 text-gray-700">
<li>Když chceš stylovat dítě/vnuka podle stavu rodiče</li>
<li>Rodič má atribut/třídu a potomek se podle toho mění</li>
<li>Užitečné pro ikony, SVG, nested elementy</li>
</ul>
<div class="mt-3 rounded bg-orange-50 p-3 text-sm">
<code>[&[data-collapsed=true]_svg]:rotate-180</code>
</div>
</div>
<div class="rounded-lg bg-white p-4">
<h3 class="mb-2 font-bold text-blue-700">✅ Přímé selektory na elementu:</h3>
<ul class="ml-6 list-disc space-y-1 text-gray-700">
<li>Když stylování závisí jen na vlastním stavu elementu</li>
<li>Nejjednodušší a nejrychlejší varianta</li>
</ul>
<div class="mt-3 rounded bg-blue-50 p-3 text-sm">
<code>data-[active]:bg-blue-500</code>
</div>
</div>
</div>
</section>
<!-- ============================================ -->
<!-- RYCHLÁ REFERENČNÍ TABULKA -->
<!-- ============================================ -->
<section class="rounded-lg border-2 border-gray-300 bg-white p-6">
<h2 class="mb-6 text-2xl font-bold">📋 Rychlá referenční tabulka</h2>
<div class="overflow-x-auto">
<table class="w-full border-collapse text-sm">
<thead>
<tr class="bg-gray-100">
<th class="border p-3 text-left">Situace</th>
<th class="border p-3 text-left">Syntaxe</th>
<th class="border p-3 text-left">Příklad</th>
</tr>
</thead>
<tbody>
<tr>
<td class="border p-3 font-semibold" colspan="3">NA ELEMENTU:</td>
</tr>
<tr>
<td class="border p-3">Data atribut bez hodnoty</td>
<td class="border p-3"><code>data-[active]:class</code></td>
<td class="border p-3"><code>data-[active]:bg-blue-500</code></td>
</tr>
<tr>
<td class="border p-3">Data atribut s hodnotou</td>
<td class="border p-3"><code>data-[state=open]:class</code></td>
<td class="border p-3"><code>data-[state=open]:text-green-500</code></td>
</tr>
<tr>
<td class="border p-3">Data atribut - jen přítomnost</td>
<td class="border p-3"><code>data-state:class</code></td>
<td class="border p-3"><code>data-state:border-orange-500</code></td>
</tr>
<tr>
<td class="border p-3">CSS třída</td>
<td class="border p-3"><code>[&.active]:class</code></td>
<td class="border p-3"><code>[&.active]:font-bold</code></td>
</tr>
<tr>
<td class="border p-3 font-semibold" colspan="3">NA RODIČI (s GROUP):</td>
</tr>
<tr>
<td class="border p-3">Data atribut bez hodnoty</td>
<td class="border p-3"><code>group-data-[active]:class</code></td>
<td class="border p-3"><code>group-data-[active]:text-purple-500</code></td>
</tr>
<tr>
<td class="border p-3">Data atribut s hodnotou</td>
<td class="border p-3"><code>group-data-[state=open]:class</code></td>
<td class="border p-3"><code>group-data-[state=open]:block</code></td>
</tr>
<tr>
<td class="border p-3">CSS třída</td>
<td class="border p-3"><code>group-[.danger]:class</code></td>
<td class="border p-3"><code>group-[.danger]:text-red-500</code></td>
</tr>
<tr>
<td class="border p-3 font-semibold" colspan="3">NA PŘEDKOVI (bez GROUP):</td>
</tr>
<tr>
<td class="border p-3">Data atribut bez hodnoty</td>
<td class="border p-3"><code>[[data-expanded]_&]:class</code></td>
<td class="border p-3"><code>[[data-expanded]_&]:block</code></td>
</tr>
<tr>
<td class="border p-3">Data atribut s hodnotou</td>
<td class="border p-3"><code>[[data-theme='dark']_&]:class</code></td>
<td class="border p-3"><code>[[data-theme='dark']_&]:text-white</code></td>
</tr>
<tr>
<td class="border p-3">CSS třída</td>
<td class="border p-3"><code>[.parent_&]:class</code></td>
<td class="border p-3"><code>[.is-open_&]:opacity-100</code></td>
</tr>
<tr>
<td class="border p-3 font-semibold" colspan="3">POTOMEK PODLE RODIČE:</td>
</tr>
<tr>
<td class="border p-3">Potomek podle data atributu</td>
<td class="border p-3"><code>[&[data-collapsed=true]_svg]:class</code></td>
<td class="border p-3"><code>[&[data-collapsed=true]_svg]:rotate-180</code></td>
</tr>
<tr>
<td class="border p-3">Potomek podle CSS třídy</td>
<td class="border p-3"><code>[&.premium_span]:class</code></td>
<td class="border p-3"><code>[&.premium_span]:text-yellow-500</code></td>
</tr>
<tr>
<td class="border p-3">Potomek s třídou</td>
<td class="border p-3"><code>[&[data-state=error]_.icon]:class</code></td>
<td class="border p-3"><code>[&[data-state=error]_.icon]:text-red-500</code></td>
</tr>
</tbody>
</table>
</div>
</section>
</body>
</html>
// https://medium.com/@ThinkingLoop/12-dom-tricks-no-libraries-required-61268e398c50
// 1) Query once, act many
const $ = (sel, root = document) => root.querySelector(sel);
const $$ = (sel, root = document) => [...root.querySelectorAll(sel)];
const app = $('#app');
const buttons = $$('.action', app);
buttons.forEach(btn => btn.addEventListener('click', onAction));
// 2) Event delegation (one listener, infinite items)
$('#list').addEventListener('click', e => {
const btn = e.target.closest('[data-action="remove"]');
if (!btn) return;
e.preventDefault();
btn.closest('li')?.remove();
});
// when we dont want use e.preventDeafult() -> pasive: true, better optimalization
window.addEventListener('scroll', onScroll, { passive: true });
// 3) Build with DocumentFragment (no layout thrash)
const frag = document.createDocumentFragment();
for (const item of data) {
const li = document.createElement('li');
li.textContent = item.name;
frag.appendChild(li);
}
$('#list').appendChild(frag); // one commit
// 4) Render HTML safely with <template>
<template id="row-tpl">
<li class="row">
<span class="name"></span>
<button data-action="remove">×</button>
</li>
</template>
function renderRow(user) {
const tpl = $('#row-tpl').content.cloneNode(true);
tpl.querySelector('.name').textContent = user.name;
return tpl;
}
const frag = document.createDocumentFragment();
users.forEach(u => frag.appendChild(renderRow(u)));
$('#list').appendChild(frag);
// 5) Class toggles beat inline styles
.card { transition: transform .2s ease; }
.card.is-active { transform: scale(1.02); }
$('.card').classList.toggle('is-active', shouldHighlight);
// 6) dataset for behavior, not style
<button data-action="copy" data-copy-text="Hello!">Copy</button>
document.addEventListener('click', e => {
const btn = e.target.closest('[data-action="copy"]');
if (!btn) return;
navigator.clipboard.writeText(btn.dataset.copyText);
});
// 7) Observe, don’t poll (Intersection/Resize/Mutation)
// IntersectionObserver: lazy-load images
const io = new IntersectionObserver(entries => {
entries.forEach(({ isIntersecting, target }) => {
if (!isIntersecting) return;
target.src = target.dataset.src;
io.unobserve(target);
});
}, { rootMargin: '200px' });
$$('img[data-src]').forEach(img => io.observe(img));
// ResizeObserver: reflow grids on size change
const ro = new ResizeObserver(entries => {
for (const { target } of entries) layout(target);
});
ro.observe($('#grid'));
// 8) Batch mutations with requestAnimationFrame
function highlightRow(row) {
// READ
const { top } = row.getBoundingClientRect();
// Schedule WRITE
requestAnimationFrame(() => {
row.style.setProperty('--start', `${top}px`);
row.classList.add('highlight');
});
}
// 9) Virtualize long lists with windowing
const viewport = $('#viewport');
const rowHeight = 32;
let start = 0, end = 0;
viewport.addEventListener('scroll', render);
window.addEventListener('resize', render);
function render() {
const { scrollTop, clientHeight } = viewport;
start = Math.floor(scrollTop / rowHeight);
end = start + Math.ceil(clientHeight / rowHeight) + 5;
const frag = document.createDocumentFragment();
viewport.innerHTML = ''; // clear (or recycle nodes for extra perf)
for (let i = start; i < Math.min(end, data.length); i++) {
const div = document.createElement('div');
div.className = 'row';
div.style.top = `${i * rowHeight}px`;
div.textContent = data[i].name;
frag.appendChild(div);
}
viewport.appendChild(frag);
}
#viewport { position: relative; overflow: auto; height: 400px; }
.row { position: absolute; height: 32px; line-height: 32px; left: 0; right: 0; }
// 10) Custom events for clean boundaries
/ producer
function notifySaved(node, payload) {
node.dispatchEvent(new CustomEvent('saved', { detail: payload, bubbles: true }));
}
// consumer
document.addEventListener('saved', e => {
const { id } = e.detail;
toast(`Saved #${id}`);
});
// 11) Progressive enhancement with feature checks
const supportsClipboard = !!navigator.clipboard?.writeText;
if (supportsClipboard) {
document.body.classList.add('has-clipboard');
// enable fancy copy button
} else {
// use <input> select() + execCommand fallback or plain text
}
// 12) Micro-router: data-route + history.pushState
<nav>
<a href="/settings" data-route>Settings</a>
<a href="/reports" data-route>Reports</a>
</nav>
<main id="view"></main>
const routes = {
'/settings': () => $('#view').textContent = 'Settings ⚙️',
'/reports': () => $('#view').textContent = 'Reports 📈',
};
function go(path) {
history.pushState({}, '', path);
routes[path]?.();
}
document.addEventListener('click', e => {
const link = e.target.closest('a[data-route]');
if (!link) return;
e.preventDefault();
go(new URL(link.href).pathname);
});
window.addEventListener('popstate', () => routes[location.pathname]?.());
routes[location.pathname]?.(); // initial