Komplexe Models

25 minutes MITTELSTUFE

Meistern Sie das leistungsstarke Aggregations und Kompositions Muster von GEMVC mithilfe der _ Präfix Konvention. Erfahren Sie, wie Sie komplexe Models mit Beziehungen, spezielle Models ohne Tabellen und mehrere aggregierte Models erstellen.

What You'll Learn

_ Präfix Konvention

Eigenschaften, die mit _ beginnen, werden bei CRUD ignoriert

Model Aggregation

Andere Models aggregieren (z. B. ProfileModel in UserModel)

Spezielle Models

Models ohne Tabellen für die Komposition erstellen

Video Coming Soon...

Was ist die _ Präfix Konvention?

GEMVC hat eine leistungsstarke Konvention: **Eigenschaften, die mit `_` beginnen, werden in allen CRUD Tabellen Operationen vollständig ignoriert!** Dies ermöglicht es Ihnen, komplexe Models mit Aggregation und Komposition zu erstellen, ohne Datenbank Operationen zu beeinflussen.

🎯 Wichtiger Punkt:

Eigenschaften, die mit _ beginnen, werden **vollständig ignoriert** bei:

  • INSERT Operationen
  • UPDATE Operationen
  • ❌ Tabellen Schema Generierung
  • ✅ Aber können für Aggregation/Komposition verwendet werden!
  • **Model Aggregation** - Schließen Sie andere Models ein (z. B. ProfileModel in UserModel)
  • **Spezielle Models** - Erstellen Sie Models ohne Tabellen für die Komposition
  • **Mehrere Models** - Definieren Sie mehrere Models als Eigenschaften
  • **Typensicherheit** - Volle PHPStan Level 9 Unterstützung für aggregierte Models
  • **Saubere Architektur** - Trennen Sie Datenbankspalten von aggregierten Daten
1

Wie die _ Präfix Konvention funktioniert

Eigenschaften Sichtbarkeits Regeln

GEMVC hat spezifische Regeln, wie Eigenschaften basierend auf ihrer Sichtbarkeit und Benennung behandelt werden:

Eigenschafts Typ Datenbank SELECT Abfragen INSERT/UPDATE
public $name ✅ Enthalten ✅ Zurückgegeben ✅ Enthalten
protected $password ✅ Enthalten ❌ Nicht zurückgegeben ✅ Enthalten
public $_profile ❌ Ignoriert ❌ Ignoriert ❌ Ignoriert

Anwendungsfälle:

  • public - Normale Datenbankspalten, die in Abfragen zurückgegeben werden
  • protected - Datenbankspalten, die in SELECT **NICHT** zurückgegeben werden (z. B. Passwort)
  • _Eigenschaft - Aggregations/Kompositions Eigenschaften (nicht in der Datenbank)
2

Beispiel 1: Aggregieren eines Profil Models

User Model mit Profil

Der häufigste Anwendungsfall: Aggregieren eines verwandten Models (wie ProfileModel) in UserModel. Die _profile Eigenschaft wird in allen Datenbank Operationen ignoriert.

Schritt 1: ProfileModel erstellen (Spezial Model ohne Tabelle)

Erstellen Sie zuerst ein ProfileModel, das keine Tabelle benötigt. Dies ist ein "spezielles Model", das nur für die Komposition verwendet wird:

Terminal
<?php
namespace App\Model;

// Spezielles Model - keine Tabelle nötig!
// Wird nur für Aggregation/Komposition verwendet
class ProfileModel
{
    public int $id;
    public int $user_id;
    public string $bio;
    public ?string $avatar_url;
    public ?string $website;
    
    // Dieses Model erbt nicht von Table
    // Es ist nur ein Daten Container für die Aggregation
}

Schritt 2: Profil zu UserModel hinzufügen

Fügen Sie nun das ProfileModel zu UserModel hinzu, indem Sie das _ Präfix verwenden:

Terminal
<?php
namespace App\Model;

use App\Table\UserTable;
use App\Model\ProfileModel;

class UserModel extends UserTable
{
    // Datenbankspalten
    public int $id;
    public string $name;
    public string $email;
    
    // Aggregiertes Model - WIRD bei CRUD Operationen IGNORIERT!
    public ?ProfileModel $_profile = null;
    
    /**
     * Benutzer mit geladenem Profil abrufen
     */
    public function withProfile(): self
    {
        if ($this->_profile === null && $this->id) {
            // Profil aus Datenbank laden
            $profileTable = new ProfileTable();
            $this->_profile = $profileTable->selectByUserId($this->id);
        }
        return $this;
    }
    
    /**
     * Profil festlegen
     */
    public function setProfile(ProfileModel $profile): void
    {
        $this->_profile = $profile;
    }
}

Nutzungs Beispiel:

Terminal
<?php
// Benutzer erstellen
$user = new UserModel();
$user->id = 1;
$user->name = 'John';
$user->email = 'john@example.com';

// Profil hinzufügen (wird bei Datenbank Operationen ignoriert!)
$profile = new ProfileModel();
$profile->bio = 'Software Developer';
$profile->avatar_url = 'https://example.com/avatar.jpg';
$user->_profile = $profile;

// Benutzer speichern (Profil wird NICHT eingefügt, nur Benutzerdaten!)
$user->insertSingleQuery();  // Fügt nur ein: id, name, email

// Später Benutzer mit Profil laden
$user = (new UserTable())->selectById(1);
$user->withProfile();  // Lädt Profil in die _profile Eigenschaft

// Aggregierte Daten verwenden
echo $user->_profile->bio;  // 'Software Developer'
3

Beispiel 2: Aggregieren eines Model Arrays

User Model mit Bestellungs Array

Sie können auch Arrays von Models aggregieren. Dies ist perfekt für Eins zu Viele Beziehungen:

Terminal
<?php
namespace App\Model;

use App\Table\UserTable;
use App\Model\OrderModel;

class UserModel extends UserTable
{
    // Datenbankspalten
    public int $id;
    public string $name;
    public string $email;
    
    /**
     * Array von Bestellungs Models - wird bei CRUD Operationen ignoriert
     * @var array<OrderModel>
     */
    public array $_orders = [];
    
    /**
     * Bestellungen des Benutzers abrufen
     * @return array<OrderModel>
     */
    public function orders(): array
    {
        if (empty($this->_orders) && $this->id) {
            $orderTable = new OrderTable();
            $this->_orders = $orderTable->selectByUserId($this->id);
        }
        return $this->_orders;
    }
    
    /**
     * Bestellung zum Benutzer hinzufügen
     */
    public function addOrder(OrderModel $order): void
    {
        $this->_orders[] = $order;
    }
    
    /**
     * Bestellung für Benutzer erstellen
     */
    public function createOrder(array $orderData): OrderModel
    {
        $order = new OrderModel();
        $order->user_id = $this->id;
        $order->amount = $orderData['amount'];
        $order->insertSingleQuery();
        
        $this->_orders[] = $order;
        return $order;
    }
}

Nutzungs Beispiel:

Terminal
<?php
// Benutzer erstellen
$user = new UserModel();
$user->id = 1;
$user->name = 'John';

// Bestellungen hinzufügen (wird bei Datenbank Operationen ignoriert!)
$order1 = new OrderModel();
$order1->amount = 100;
$user->addOrder($order1);

$order2 = new OrderModel();
$order2->amount = 200;
$user->addOrder($order2);

// Benutzer speichern (Bestellungs Array wird NICHT eingefügt!)
$user->insertSingleQuery();  // Fügt nur ein: id, name

// Später tatsächliche Bestellungen in der Datenbank erstellen
foreach ($user->_orders as $order) {
    $order->user_id = $user->id;
    $order->insertSingleQuery();  // Jede Bestellung separat speichern
}
4

Beispiel 3: Komplexe Beziehungen

Product Model mit mehreren Aggregationen

Sie können mehrere Models in einem einzigen Model aggregieren. Hier ist ein ProductModel mit Kategorie und Bewertungen:

Terminal
<?php
namespace App\Model;

use App\Table\ProductTable;
use App\Model\CategoryModel;
use App\Model\ReviewModel;

class ProductModel extends ProductTable
{
    // Datenbankspalten
    public int $id;
    public string $name;
    public float $price;
    public ?int $category_id;
    
    /**
     * Einzelnes verwandtes Model
     * @var CategoryModel|null
     */
    public ?CategoryModel $_category = null;
    
    /**
     * Array von verwandten Models
     * @var array<ReviewModel>
     */
    public array $_reviews = [];
    
    /**
     * Produkt mit Kategorie und Bewertungen laden
     */
    public function loadRelations(): self
    {
        if ($this->id) {
            // Kategorie laden
            if ($this->category_id) {
                $categoryTable = new CategoryTable();
                $this->_category = $categoryTable->selectById($this->category_id);
            }
            
            // Bewertungen laden
            $reviewTable = new ReviewTable();
            $this->_reviews = $reviewTable->selectByProductId($this->id);
        }
        return $this;
    }
    
    /**
     * Durchschnittliche Bewertung abrufen
     */
    public function getAverageRating(): float
    {
        if (empty($this->_reviews)) {
            return 0.0;
        }
        
        $total = 0;
        foreach ($this->_reviews as $review) {
            $total += $review->rating;
        }
        
        return round($total / count($this->_reviews), 2);
    }
}

Nutzungs Beispiel:

Terminal
<?php
// Produkt mit allen Beziehungen laden
$product = (new ProductTable())->selectById(1);
$product->loadRelations();

// Aggregierte Daten verwenden
echo $product->_category->name;  // 'Electronics'
echo $product->getAverageRating();  // 4.5

// Produkt speichern (Kategorie und Bewertungen werden NICHT beeinflusst!)
$product->price = 999.99;
$product->updateSingleQuery();  // Aktualisiert nur: id, name, price
5

Erstellen spezieller Models ohne Tabellen

Daten Container Models

Manchmal benötigen Sie ein Model, das keine Datenbanktabelle hat. Dies sind "spezielle Models", die nur für die Aggregation und Komposition verwendet werden. Es handelt sich um einfache PHP Klassen, die nicht von Table erben.

Beispiel: AddressModel (Keine Tabelle)

Terminal
<?php
namespace App\Model;

// Spezielles Model - keine Tabelle nötig!
// Wird nur für Aggregation/Komposition verwendet
class AddressModel
{
    public string $street;
    public string $city;
    public string $state;
    public string $zip_code;
    public string $country;
    
    // Dieses Model erbt nicht von Table
    // Es ist nur ein Daten Container
    // Kann in UserModel verwendet werden als: public ?AddressModel $_address = null;
}

Beispiel: SettingsModel (Keine Tabelle)

Terminal
<?php
namespace App\Model;

// Spezielles Model für Benutzer Einstellungen
// Keine Datenbanktabelle - nur Komposition
class SettingsModel
{
    public bool $email_notifications = true;
    public bool $sms_notifications = false;
    public string $theme = 'light';
    public string $language = 'en';
    
    // Kann in UserModel verwendet werden als: public ?SettingsModel $_settings = null;
}

Verwenden spezieller Models in UserModel

Terminal
<?php
namespace App\Model;

use App\Table\UserTable;
use App\Model\ProfileModel;
use App\Model\AddressModel;
use App\Model\SettingsModel;

class UserModel extends UserTable
{
    // Datenbankspalten
    public int $id;
    public string $name;
    public string $email;
    
    // Mehrere aggregierte Models
    public ?ProfileModel $_profile = null;
    public ?AddressModel $_address = null;
    public ?SettingsModel $_settings = null;
    
    /**
     * Alle aggregierten Daten laden
     */
    public function loadAll(): self
    {
        if ($this->id) {
            // Profil laden
            $this->withProfile();
            
            // Adresse laden
            $addressTable = new AddressTable();
            $this->_address = $addressTable->selectByUserId($this->id);
            
            // Einstellungen laden
            $settingsTable = new SettingsTable();
            $this->_settings = $settingsTable->selectByUserId($this->id);
        }
        return $this;
    }
}
6

Allgemeine Muster

Muster für verschiedene Anwendungsfälle

Hier sind allgemeine Muster für die Verwendung der _ Präfix Konvention:

Muster 1: Einzelnes Model Aggregation

Terminal
<?php
// Einzelnes verwandtes Model
public ?ProfileModel $_profile = null;

// Nutzung
$user->_profile = $profile;
$user->insertSingleQuery();  // Profil ignoriert

Muster 2: Array Aggregation

Terminal
<?php
// Array von verwandten Models
public array $_orders = [];

// Nutzung
$user->_orders = [$order1, $order2];
$user->insertSingleQuery();  // Bestellungen ignoriert

Muster 3: Private mit Getter

Terminal
<?php
// Private Aggregation mit Getter
private array $_reviews = [];

public function getReviews(): array
{
    return $this->_reviews;
}

Muster 4: Protected Aggregation

Terminal
<?php
// Protected Aggregation
protected ?CategoryModel $_category = null;

// Kann nur innerhalb der Klasse oder Unterklassen zugegriffen werden
7

PHPStan Level 9 Typensicherheit

Volle Typensicherheit für aggregierte Models

GEMVC unterstützt volle PHPStan Level 9 Typensicherheit für aggregierte Models. Verwenden Sie korrekte Type Hints und PHPDoc Annotationen:

✅ Einzelnes Model mit Type Hint

Terminal
<?php
// ✅ Nullable einzelnes Model
public ?ProfileModel $_profile = null;

// PHPStan weiß, dass dies ProfileModel oder null ist
if ($this->_profile !== null) {
    echo $this->_profile->bio;  // ✅ Typ sicher!
}

✅ Array mit PHPDoc

Terminal
<?php
/**
 * Array von Bestellungs Models - wird bei CRUD Operationen ignoriert
 * @var array<OrderModel>
 */
public array $_orders = [];

// PHPStan weiß, dass dies array<OrderModel> ist
foreach ($this->_orders as $order) {
    echo $order->amount;  // ✅ Typ sicher!
}

✅ Typisiertes Array mit Index

Terminal
<?php
/**
 * Array von Bewertungen, indiziert nach Bewertungs ID
 * @var array<int, ReviewModel>
 */
public array $_reviews = [];

// PHPStan weiß, dass Schlüssel int und Werte ReviewModel sind
foreach ($this->_reviews as $reviewId => $review) {
    echo $reviewId;  // ✅ int
    echo $review->rating;  // ✅ ReviewModel Eigenschaft
}

Best Practices

💡 Beschreibende Namen verwenden

Verwenden Sie klare, beschreibende Namen für aggregierte Eigenschaften:

Terminal
<?php
public ?ProfileModel $_profile = null;      // ✅ Klar
public array $_orders = [];                 // ✅ Klar
public array $_reviews = [];               // ✅ Klar

// ❌ Vermeiden
public ?ProfileModel $_p = null;           // ❌ Unklar
public array $_data = [];                  // ❌ Zu generisch

💡 Type Hints hinzufügen

Fügen Sie immer Type Hints für die PHPStan Level 9 Konformität hinzu:

Terminal
<?php
// ✅ Einzelnes Model
public ?ProfileModel $_profile = null;

// ✅ Array mit PHPDoc
/** @var array<OrderModel> */
public array $_orders = [];

// ✅ Typisiertes Array
/** @var array<int, ReviewModel> */
public array $_reviews = [];

💡 Hilfsmethoden erstellen

Erstellen Sie Hilfsmethoden zum Laden und Zugreifen auf aggregierte Daten:

Terminal
<?php
public function withProfile(): self
{
    if ($this->_profile === null && $this->id) {
        $profileTable = new ProfileTable();
        $this->_profile = $profileTable->selectByUserId($this->id);
    }
    return $this;
}

public function orders(): array
{
    if (empty($this->_orders) && $this->id) {
        $orderTable = new OrderTable();
        $this->_orders = $orderTable->selectByUserId($this->id);
    }
    return $this->_orders;
}

💡 Lazy Loading

Laden Sie aggregierte Daten nur bei Bedarf. Laden Sie nicht alles im Voraus:

Terminal
<?php
// ✅ Nur bei Bedarf laden
public function withProfile(): self
{
    if ($this->_profile === null && $this->id) {
        // Nur laden, wenn noch nicht geladen
        $this->_profile = (new ProfileTable())->selectByUserId($this->id);
    }
    return $this;
}

// ❌ Nicht alles im Konstruktor laden
public function __construct()
{
    // ❌ Schlecht - lädt alles im Voraus
    $this->loadAllRelations();
}

💡 Trennung beibehalten

Mischen Sie Datenbankspalten nicht mit aggregierten Eigenschaften. Halten Sie sie klar getrennt:

Terminal
<?php
class UserModel extends UserTable
{
    // Datenbankspalten (kein _ Präfix)
    public int $id;
    public string $name;
    public string $email;
    
    // Aggregierte Models (_ Präfix)
    public ?ProfileModel $_profile = null;
    public array $_orders = [];
}

Komplettes Beispiel: UserModel mit mehreren Aggregationen

Hier ist ein vollständiges Beispiel, das zeigt, wie Sie die _ Präfix Konvention in einem realen Szenario verwenden:

Terminal
<?php
namespace App\Model;

use App\Table\UserTable;
use App\Model\ProfileModel;
use App\Model\AddressModel;
use App\Model\OrderModel;

class UserModel extends UserTable
{
    // Datenbankspalten
    public int $id;
    public string $name;
    public string $email;
    protected string $password;
    
    // Aggregierte Models - WIRD bei CRUD Operationen IGNORIERT
    public ?ProfileModel $_profile = null;
    public ?AddressModel $_address = null;
    /** @var array<OrderModel> */
    public array $_orders = [];
    
    /**
     * Benutzer mit Profil laden
     */
    public function withProfile(): self
    {
        if ($this->_profile === null && $this->id) {
            $profileTable = new ProfileTable();
            $this->_profile = $profileTable->selectByUserId($this->id);
        }
        return $this;
    }
    
    /**
     * Benutzer mit Adresse laden
     */
    public function withAddress(): self
    {
        if ($this->_address === null && $this->id) {
            $addressTable = new AddressTable();
            $this->_address = $addressTable->selectByUserId($this->id);
        }
        return $this;
    }
    
    /**
     * Benutzer mit Bestellungen laden
     */
    public function withOrders(): self
    {
        if (empty($this->_orders) && $this->id) {
            $orderTable = new OrderTable();
            $this->_orders = $orderTable->selectByUserId($this->id);
        }
        return $this;
    }
    
    /**
     * Alle aggregierten Daten laden
     */
    public function loadAll(): self
    {
        return $this->withProfile()->withAddress()->withOrders();
    }
}

Nutzung in der API Antwort:

Terminal
<?php
// In Controller oder Model
$user = (new UserTable())->selectById($id);
$user->loadAll();  // Lädt Profil, Adresse, Bestellungen

// In Antwort zurückgeben
return Response::success([
    'id' => $user->id,
    'name' => $user->name,
    'email' => $user->email,
    'profile' => $user->_profile,      // Aggregierte Daten
    'address' => $user->_address,     // Aggregierte Daten
    'orders' => $user->_orders         // Aggregierte Daten
], 1, 'Benutzer erfolgreich abgerufen');

Wichtige Hinweise

  • **Vollständig ignoriert:** Eigenschaften, die mit _ beginnen, werden in INSERT, UPDATE und der Schema Generierung **vollständig ignoriert**. Sie berühren niemals die Datenbank.
  • **Spezielle Models:** Sie können Models ohne Tabellen (nur PHP Klassen) für die Aggregation erstellen. Sie müssen nicht von Table erben.
  • **Typensicherheit:** Fügen Sie immer Type Hints und PHPDoc Annotationen für die PHPStan Level 9 Konformität hinzu. Verwenden Sie @var array<ModelName> für Arrays.
  • **Lazy Loading:** Laden Sie aggregierte Daten nur bei Bedarf. Laden Sie nicht alles im Konstruktor – verwenden Sie Hilfsmethoden wie withProfile().
  • **Klare Trennung:** Halten Sie Datenbankspalten (kein _ Präfix) klar von aggregierten Eigenschaften (mit _ Präfix) getrennt, um Klarheit zu gewährleisten.

🧩 Komplexe Models gemeistert!

Ausgezeichnet! Sie haben gelernt, wie Sie die leistungsstarke _ Präfix Konvention von GEMVC verwenden, um komplexe Models mit Aggregation und Komposition zu erstellen. Denken Sie daran: Eigenschaften, die mit _ beginnen, werden bei CRUD Operationen vollständig ignoriert!